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