1 | import React from 'react';
|
2 | import ReactDOM from 'react-dom';
|
3 |
|
4 | import ReactDOMServer from 'react-dom/server';
|
5 |
|
6 | import TestUtils from 'react-addons-test-utils';
|
7 | import values from 'object.values';
|
8 | import { isElement, isValidElementType } from 'react-is';
|
9 | import { EnzymeAdapter } from 'enzyme';
|
10 | import {
|
11 | displayNameOfNode,
|
12 | elementToTree,
|
13 | mapNativeEventNames,
|
14 | propFromEvent,
|
15 | withSetStateAllowed,
|
16 | assertDomAvailable,
|
17 | createRenderWrapper,
|
18 | createMountWrapper,
|
19 | propsWithKeysAndRef,
|
20 | ensureKeyOrUndefined,
|
21 | } from 'enzyme-adapter-utils';
|
22 |
|
23 | function typeToNodeType(type) {
|
24 | if (typeof type === 'function') {
|
25 | if (type.prototype && typeof type.prototype.render === 'function') {
|
26 | return 'class';
|
27 | }
|
28 | return 'function';
|
29 | }
|
30 | return 'host';
|
31 | }
|
32 |
|
33 | function instanceToTree(inst) {
|
34 | if (!inst || typeof inst !== 'object') {
|
35 | return inst;
|
36 | }
|
37 | const el = inst._currentElement;
|
38 | if (!el) {
|
39 | return null;
|
40 | }
|
41 | if (typeof el !== 'object') {
|
42 | return el;
|
43 | }
|
44 | if (inst._tag) {
|
45 | if (typeof el !== 'object') {
|
46 | return el;
|
47 | }
|
48 | const children = inst._renderedChildren || { '.0': el.props.children };
|
49 | return {
|
50 | nodeType: 'host',
|
51 | type: el.type,
|
52 | props: el.props,
|
53 | key: ensureKeyOrUndefined(el.key),
|
54 | ref: el.ref,
|
55 | instance: ReactDOM.findDOMNode(inst.getPublicInstance()) || null,
|
56 | rendered: values(children).map(instanceToTree),
|
57 | };
|
58 | }
|
59 | if (inst._renderedComponent) {
|
60 | return {
|
61 | nodeType: typeToNodeType(el.type),
|
62 | type: el.type,
|
63 | props: el.props,
|
64 | key: ensureKeyOrUndefined(el.key),
|
65 | ref: el.ref,
|
66 | instance: inst._instance || null,
|
67 | rendered: instanceToTree(inst._renderedComponent),
|
68 | };
|
69 | }
|
70 | throw new Error('Enzyme Internal Error: unknown instance encountered');
|
71 | }
|
72 |
|
73 | class ReactFourteenAdapter extends EnzymeAdapter {
|
74 | constructor() {
|
75 | super();
|
76 |
|
77 | const { lifecycles } = this.options;
|
78 | this.options = {
|
79 | ...this.options,
|
80 | supportPrevContextArgumentOfComponentDidUpdate: true,
|
81 | lifecycles: {
|
82 | ...lifecycles,
|
83 | componentDidUpdate: {
|
84 | prevContext: true,
|
85 | },
|
86 | },
|
87 | };
|
88 | }
|
89 |
|
90 | createMountRenderer(options) {
|
91 | assertDomAvailable('mount');
|
92 | const domNode = options.attachTo || global.document.createElement('div');
|
93 | let instance = null;
|
94 | const adapter = this;
|
95 | return {
|
96 | render(el, context, callback) {
|
97 | if (instance === null) {
|
98 | const { type, props, ref } = el;
|
99 | const wrapperProps = {
|
100 | Component: type,
|
101 | props,
|
102 | context,
|
103 | ...(ref && { ref }),
|
104 | };
|
105 | const ReactWrapperComponent = createMountWrapper(el, { ...options, adapter });
|
106 | const wrappedEl = React.createElement(ReactWrapperComponent, wrapperProps);
|
107 | instance = ReactDOM.render(wrappedEl, domNode);
|
108 | if (typeof callback === 'function') {
|
109 | callback();
|
110 | }
|
111 | } else {
|
112 | instance.setChildProps(el.props, context, callback);
|
113 | }
|
114 | },
|
115 | unmount() {
|
116 | ReactDOM.unmountComponentAtNode(domNode);
|
117 | instance = null;
|
118 | },
|
119 | getNode() {
|
120 | return instance ? instanceToTree(instance._reactInternalInstance).rendered : null;
|
121 | },
|
122 | simulateEvent(node, event, mock) {
|
123 | const mappedEvent = mapNativeEventNames(event);
|
124 | const eventFn = TestUtils.Simulate[mappedEvent];
|
125 | if (!eventFn) {
|
126 | throw new TypeError(`ReactWrapper::simulate() event '${event}' does not exist`);
|
127 | }
|
128 |
|
129 | eventFn(ReactDOM.findDOMNode(node.instance), mock);
|
130 | },
|
131 | batchedUpdates(fn) {
|
132 | return ReactDOM.unstable_batchedUpdates(fn);
|
133 | },
|
134 | };
|
135 | }
|
136 |
|
137 | createShallowRenderer() {
|
138 | const renderer = TestUtils.createRenderer();
|
139 | let isDOM = false;
|
140 | let cachedNode = null;
|
141 | return {
|
142 | render(el, context) {
|
143 | cachedNode = el;
|
144 |
|
145 | if (typeof el.type === 'string') {
|
146 | isDOM = true;
|
147 | } else {
|
148 | isDOM = false;
|
149 | return withSetStateAllowed(() => renderer.render(el, context));
|
150 | }
|
151 | },
|
152 | unmount() {
|
153 | renderer.unmount();
|
154 | },
|
155 | getNode() {
|
156 | if (isDOM) {
|
157 | return elementToTree(cachedNode);
|
158 | }
|
159 | const output = renderer.getRenderOutput();
|
160 | return {
|
161 | nodeType: typeToNodeType(cachedNode.type),
|
162 | type: cachedNode.type,
|
163 | props: cachedNode.props,
|
164 | key: ensureKeyOrUndefined(cachedNode.key),
|
165 | ref: cachedNode.ref,
|
166 | instance: renderer._instance._instance,
|
167 | rendered: elementToTree(output),
|
168 | };
|
169 | },
|
170 | simulateEvent(node, event, ...args) {
|
171 | const handler = node.props[propFromEvent(event)];
|
172 | if (handler) {
|
173 | withSetStateAllowed(() => {
|
174 |
|
175 |
|
176 | ReactDOM.unstable_batchedUpdates(() => {
|
177 | handler(...args);
|
178 | });
|
179 | });
|
180 | }
|
181 | },
|
182 | batchedUpdates(fn) {
|
183 | return withSetStateAllowed(() => ReactDOM.unstable_batchedUpdates(fn));
|
184 | },
|
185 | };
|
186 | }
|
187 |
|
188 | createStringRenderer(options) {
|
189 | return {
|
190 | render(el, context) {
|
191 | if (options.context && (el.type.contextTypes || options.childContextTypes)) {
|
192 | const childContextTypes = {
|
193 | ...(el.type.contextTypes || {}),
|
194 | ...options.childContextTypes,
|
195 | };
|
196 | const ContextWrapper = createRenderWrapper(el, context, childContextTypes);
|
197 | return ReactDOMServer.renderToStaticMarkup(React.createElement(ContextWrapper));
|
198 | }
|
199 | return ReactDOMServer.renderToStaticMarkup(el);
|
200 | },
|
201 | };
|
202 | }
|
203 |
|
204 |
|
205 |
|
206 |
|
207 | createRenderer(options) {
|
208 | switch (options.mode) {
|
209 | case EnzymeAdapter.MODES.MOUNT: return this.createMountRenderer(options);
|
210 | case EnzymeAdapter.MODES.SHALLOW: return this.createShallowRenderer(options);
|
211 | case EnzymeAdapter.MODES.STRING: return this.createStringRenderer(options);
|
212 | default:
|
213 | throw new Error(`Enzyme Internal Error: Unrecognized mode: ${options.mode}`);
|
214 | }
|
215 | }
|
216 |
|
217 |
|
218 |
|
219 |
|
220 |
|
221 | nodeToElement(node) {
|
222 | if (!node || typeof node !== 'object') return null;
|
223 | return React.createElement(node.type, propsWithKeysAndRef(node));
|
224 | }
|
225 |
|
226 | elementToNode(element) {
|
227 | return elementToTree(element);
|
228 | }
|
229 |
|
230 | nodeToHostNode(node) {
|
231 | return ReactDOM.findDOMNode(node.instance);
|
232 | }
|
233 |
|
234 | displayNameOfNode(node) {
|
235 | return displayNameOfNode(node);
|
236 | }
|
237 |
|
238 | isValidElement(element) {
|
239 | return isElement(element);
|
240 | }
|
241 |
|
242 | isValidElementType(object) {
|
243 | return isValidElementType(object);
|
244 | }
|
245 |
|
246 | createElement(...args) {
|
247 | return React.createElement(...args);
|
248 | }
|
249 | }
|
250 |
|
251 | module.exports = ReactFourteenAdapter;
|