Skip to content

Instantly share code, notes, and snippets.

@thehig
Created January 23, 2019 18:07
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save thehig/27b090e154ca80218e1709bc0564825b to your computer and use it in GitHub Desktop.
Save thehig/27b090e154ca80218e1709bc0564825b to your computer and use it in GitHub Desktop.
js: decorated enzyme
// 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