Created
January 23, 2019 18:07
-
-
Save thehig/27b090e154ca80218e1709bc0564825b to your computer and use it in GitHub Desktop.
js: decorated enzyme
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// Derivative work of https://github.com/yahoo/react-intl/wiki/Testing-with-React-Intl#helper-function-1 | |
// Related to https://gist.github.com/mirague/c05f4da0d781a9b339b501f1d5d33c37/ | |
import React from 'react'; | |
import PropTypes from 'prop-types'; | |
import merge from 'lodash.merge'; | |
import { | |
configure, | |
mount as enzMount, | |
shallow as enzShallow, | |
render as enzRender | |
} from 'enzyme'; | |
import Adapter from 'enzyme-adapter-react-16'; | |
configure({ adapter: new Adapter() }); | |
// === I18N === | |
// Configure an i18n provider that will use the default language from the sample config | |
import { IntlProvider, intlShape } from 'react-intl'; | |
import { defaultLanguage } from 'shared/i18n/utils'; | |
// Use the message bundler to assemble all the `defaultMessages` bundles | |
import messageBundler from './messageBundler'; | |
const messages = messageBundler(); | |
const locale = defaultLanguage(messages); | |
const intlProvider = new IntlProvider( | |
{ locale, messages: messages[locale] }, | |
{} | |
); | |
const intlContext = intlProvider.getChildContext().intl; | |
// === ROUTER === | |
// Note: Even though this adds the router props, sometimes things will still fail | |
// with error: "You should not use <Link> outside a <Router>" | |
// To circumvent this replace | |
// .addDecorator(StoryRouter()) | |
// With | |
// .addDecorator(getStory => <MemoryRouter>{getStory()}</MemoryRouter>) | |
import createRouterContext from 'react-router-test-context'; | |
const routerContext = createRouterContext().router; | |
// === REDUX === | |
import configureMockStore from 'redux-mock-store'; | |
import { middlewares } from 'core/redux/store'; | |
export const mockStoreCreator = configureMockStore(middlewares); | |
/** | |
* Create enzyme functions that will inject props into the provided component | |
* | |
* @param {Object} injectProps Props to be injected into the component | |
* @param {Object} injectPropTypes Prop Types to be injected into the component | |
* | |
* @returns { mount, shallow, render } with the injected props automatically injected around the component | |
*/ | |
const createDecoratedEnzyme = (injectProps = {}, injectPropTypes = {}) => { | |
function nodeWithAddedProps(node) { | |
return React.cloneElement(node, injectProps); | |
} | |
/** | |
* Enzyme shallow render node with injected props | |
*/ | |
function shallow(node, { context } = {}) { | |
return enzShallow(nodeWithAddedProps(node), { | |
context: { ...injectProps, ...context } | |
}); | |
} | |
/** | |
* Enzyme mount node with injected props | |
*/ | |
function mount(node, { context, childContextTypes } = {}) { | |
return enzMount(nodeWithAddedProps(node), { | |
context: { ...injectProps, ...context }, | |
childContextTypes: { | |
...injectPropTypes, | |
...childContextTypes | |
} | |
}); | |
} | |
/** | |
* Enzyme render node with injected props | |
*/ | |
function render(node, { context, childContextTypes } = {}) { | |
return enzRender(nodeWithAddedProps(node), { | |
context: { ...injectProps, ...context }, | |
childContextTypes: { | |
...injectPropTypes, | |
...childContextTypes | |
} | |
}); | |
} | |
return { shallow, mount, render }; | |
}; | |
/** | |
* Create a set of enzyme mount, shallow and render that can inject intl, store and router as requested | |
* | |
* @param {intl} Boolean should intl be injected | |
* @param {store} Boolean should store be injected | |
* @param {router} Boolean should router be injected | |
*/ | |
export default function decoratedEnzyme( | |
{ intl = false, store = false, router = false } = {}, | |
{ | |
injectIntl = intlContext, | |
injectIntlTypes = intlShape, | |
injectStore = mockStoreCreator({}), | |
injectStoreTypes = PropTypes.object, | |
injectRouter = routerContext, | |
injectRouterTypes = PropTypes.object | |
} = {} | |
) { | |
// === INJECT === | |
let injectProps = {}; | |
let injectPropTypes = {}; | |
if (intl === true) { | |
injectProps.intl = injectIntl; | |
injectPropTypes.intl = injectIntlTypes; | |
} | |
if (store === true) { | |
injectProps.store = injectStore; | |
injectPropTypes.store = injectStoreTypes; | |
} | |
if (router === true) { | |
injectProps.router = injectRouter; | |
injectPropTypes.router = injectRouterTypes; | |
} | |
return createDecoratedEnzyme(injectProps, injectPropTypes); | |
} | |
/** | |
* Create a shallow renderer for a component | |
* | |
* @param {function} Component - The component to render with enzyme shallow | |
* @param {Object} decorators - Defines which decorators will be injected into Component eg: `{ intl: true, store: true, router: true }` | |
* @param {function} defaultStore - Returns object of values that will be added to every redux store | |
* @param {function} defaultProps - Returns object of values that will be spread to every Component | |
* @param {function} postProcess - Function run on wrapper before returning | |
* | |
* @returns {function} - A shallow renderer that will render Component as defined above | |
* | |
* @param {Object} store - Values to be injected to redux store | |
* @param {Object} props - Values to be spread onto component | |
* | |
* @example Creating a shallow renderer with default store and default props | |
* | |
* const shallow = shallowRenderer({ | |
* Component: AuditLog, | |
* decorators: { intl: true, store: true }, | |
* defaultStore: () => ({ error: undefined }), | |
* defaultProps: () => ({ ready: false }), | |
* postProcess: wrapper => wrapper.dive().dive() | |
* }); | |
* | |
* @example Using the renderer | |
* | |
* const wrapper = shallow({ | |
* store: { loading: false, data: undefined }, | |
* props: { ready: true } | |
* }); | |
*/ | |
export const shallowRenderer = ({ | |
Component, | |
decorators = { store: true }, | |
defaultStore = () => ({}), | |
defaultProps = () => ({}), | |
postProcess = f => f | |
} = {}) => ({ store = {}, props = {} } = {}) => { | |
// Use decoratedEnzyme to create { mount, shallow, render } with appropriate contexts | |
// Create mock store that has middlewares injected, and is populated with the default and test data | |
const { shallow } = decoratedEnzyme(decorators, { | |
injectStore: mockStoreCreator({ | |
...defaultStore(), | |
...store | |
}) | |
}); | |
const instanceProps = merge({}, defaultProps(), props); | |
// Render the Component with the default and test props | |
const wrapper = shallow(<Component {...instanceProps} />); //-? $.text() | |
// Apply any enzyme post processing like `.dive()` | |
return postProcess(wrapper); | |
}; |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment