diff --git a/.gitignore b/.gitignore index 67045665d..269a8d92f 100644 --- a/.gitignore +++ b/.gitignore @@ -102,3 +102,9 @@ dist # TernJS port file .tern-port + +### Emacs/vi ### +*~ +*.swo +*.swp + diff --git a/src/editors/components/EditorHeader/.HeaderTitle.jsx.swp b/src/editors/components/EditorHeader/.HeaderTitle.jsx.swp deleted file mode 100644 index 0046a3806..000000000 Binary files a/src/editors/components/EditorHeader/.HeaderTitle.jsx.swp and /dev/null differ diff --git a/src/setupTest.js b/src/setupTest.js index 9984d6e31..be8991ff4 100644 --- a/src/setupTest.js +++ b/src/setupTest.js @@ -43,3 +43,41 @@ process.env.LOGO_URL = 'https://edx-cdn.org/v3/default/logo.svg'; process.env.LOGO_TRADEMARK_URL = 'https://edx-cdn.org/v3/default/logo-trademark.svg'; process.env.LOGO_WHITE_URL = 'https://edx-cdn.org/v3/default/logo-white.svg'; process.env.FAVICON_URL = 'https://edx-cdn.org/v3/default/favicon.ico'; + +jest.mock('@edx/frontend-platform/i18n', () => { + const i18n = jest.requireActual('@edx/frontend-platform/i18n'); + const PropTypes = jest.requireActual('prop-types'); + return { + ...i18n, + intlShape: PropTypes.shape({ + formatMessage: PropTypes.func, + }), + defineMessages: m => m, + FormattedMessage: () => 'FormattedMessage', + }; +}); + +jest.mock('@edx/paragon', () => jest.requireActual('testUtils').mockNestedComponents({ + ActionRow: 'ActionRow', + Button: 'Button', + Icon: 'Icon', + IconButton: 'IconButton', + ModalDialog: { + Header: 'ModalDialog.Header', + Title: 'ModalDialog.Title', + }, + Form: { + Control: { + Feedback: 'Form.Control.Feedback', + }, + Group: 'Form.Group', + Label: 'Form.Label', + }, + Spinner: 'Spinner', + Toast: 'Toast', +})); + +jest.mock('@edx/paragon/icons', () => ({ + Close: jest.fn().mockName('icons.Close'), + Edit: jest.fn().mockName('icons.Edit'), +})); diff --git a/src/testUtils.js b/src/testUtils.js new file mode 100644 index 000000000..b1f346b36 --- /dev/null +++ b/src/testUtils.js @@ -0,0 +1,59 @@ +/** + * Mocked formatMessage provided by react-intl + */ +export const formatMessage = (msg, values) => { + let message = msg.defaultMessage; + if (values === undefined) { + return message; + } + Object.keys(values).forEach((key) => { + // eslint-disable-next-line + message = message.replace(`{${key}}`, values[key]); + }); + return message; +}; + +/** + * Mock a single component, or a nested component so that its children render nicely + * in snapshots. + * @param {string} name - parent component name + * @param {obj} contents - object of child components with intended component + * render name. + * @return {func} - mock component with nested children. + * + * usage: + * mockNestedComponent('Card', { Body: 'Card.Body', Form: { Control: { Feedback: 'Form.Control.Feedback' }}... }); + * mockNestedComponent('IconButton', 'IconButton'); + */ +export const mockNestedComponent = (name, contents) => { + if (typeof contents !== 'object') { + return contents; + } + const fn = () => name; + Object.defineProperty(fn, 'name', { value: name }); + Object.keys(contents).forEach((nestedName) => { + const value = contents[nestedName]; + fn[nestedName] = typeof value !== 'object' + ? value + : mockNestedComponent(`${name}.${nestedName}`, value); + }); + return fn; +}; + +/** + * Mock a module of components. nested components will be rendered nicely in snapshots. + * @param {obj} mapping - component module mock config. + * @return {obj} - module of flat and nested components that will render nicely in snapshots. + * usage: + * mockNestedComponents({ + * Card: { Body: 'Card.Body' }, + * IconButton: 'IconButton', + * }) + */ +export const mockNestedComponents = (mapping) => Object.entries(mapping).reduce( + (obj, [name, value]) => ({ + ...obj, + [name]: mockNestedComponent(name, value), + }), + {}, +);