UNPKG

16.2 kBJavaScriptView Raw
1/**
2 * Copyright (c) 2013-present, Facebook, Inc.
3 *
4 * This source code is licensed under the MIT license found in the
5 * LICENSE file in the root directory of this source tree.
6 *
7 */
8
9'use strict';
10
11var _prodInvariant = require('./reactProdInvariant'),
12 _assign = require('object-assign');
13
14var EventConstants = require('./EventConstants');
15var EventPluginHub = require('./EventPluginHub');
16var EventPluginRegistry = require('./EventPluginRegistry');
17var EventPropagators = require('./EventPropagators');
18var React = require('react/lib/React');
19var ReactDOM = require('./ReactDOM');
20var ReactDOMComponentTree = require('./ReactDOMComponentTree');
21var ReactBrowserEventEmitter = require('./ReactBrowserEventEmitter');
22var ReactInstanceMap = require('./ReactInstanceMap');
23var ReactUpdates = require('./ReactUpdates');
24var SyntheticEvent = require('./SyntheticEvent');
25var ReactShallowRenderer = require('./ReactShallowRenderer');
26
27var findDOMNode = require('./findDOMNode');
28var invariant = require('fbjs/lib/invariant');
29var warning = require('fbjs/lib/warning');
30
31var topLevelTypes = EventConstants.topLevelTypes;
32
33function Event(suffix) {}
34
35// In react 16+ shallowRenderer will not be accessible via ReactTestUtils.createRenderer()
36// Instead it will be available via react-test-renderer/shallow
37// Maintain backwards compat for 15.5.0 release, but warn about using the deprecated method
38var hasWarnedAboutCreateRenderer = false;
39function createRendererWithWarning() {
40 process.env.NODE_ENV !== 'production' ? warning(hasWarnedAboutCreateRenderer, 'Shallow renderer has been moved to react-test-renderer/shallow. ' + 'Update references to remove this warning.') : void 0;
41 hasWarnedAboutCreateRenderer = true;
42
43 return new ReactShallowRenderer();
44}
45
46/**
47 * @class ReactTestUtils
48 */
49
50function findAllInRenderedTreeInternal(inst, test) {
51 if (!inst || !inst.getPublicInstance) {
52 return [];
53 }
54 var publicInst = inst.getPublicInstance();
55 var ret = test(publicInst) ? [publicInst] : [];
56 var currentElement = inst._currentElement;
57 if (ReactTestUtils.isDOMComponent(publicInst)) {
58 var renderedChildren = inst._renderedChildren;
59 var key;
60 for (key in renderedChildren) {
61 if (!renderedChildren.hasOwnProperty(key)) {
62 continue;
63 }
64 ret = ret.concat(findAllInRenderedTreeInternal(renderedChildren[key], test));
65 }
66 } else if (React.isValidElement(currentElement) && typeof currentElement.type === 'function') {
67 ret = ret.concat(findAllInRenderedTreeInternal(inst._renderedComponent, test));
68 }
69 return ret;
70}
71
72/**
73 * Utilities for making it easy to test React components.
74 *
75 * See https://facebook.github.io/react/docs/test-utils.html
76 *
77 * Todo: Support the entire DOM.scry query syntax. For now, these simple
78 * utilities will suffice for testing purposes.
79 * @lends ReactTestUtils
80 */
81var ReactTestUtils = {
82 renderIntoDocument: function (element) {
83 var div = document.createElement('div');
84 // None of our tests actually require attaching the container to the
85 // DOM, and doing so creates a mess that we rely on test isolation to
86 // clean up, so we're going to stop honoring the name of this method
87 // (and probably rename it eventually) if no problems arise.
88 // document.documentElement.appendChild(div);
89 return ReactDOM.render(element, div);
90 },
91
92 isElement: function (element) {
93 return React.isValidElement(element);
94 },
95
96 isElementOfType: function (inst, convenienceConstructor) {
97 return React.isValidElement(inst) && inst.type === convenienceConstructor;
98 },
99
100 isDOMComponent: function (inst) {
101 return !!(inst && inst.nodeType === 1 && inst.tagName);
102 },
103
104 isDOMComponentElement: function (inst) {
105 return !!(inst && React.isValidElement(inst) && !!inst.tagName);
106 },
107
108 isCompositeComponent: function (inst) {
109 if (ReactTestUtils.isDOMComponent(inst)) {
110 // Accessing inst.setState warns; just return false as that'll be what
111 // this returns when we have DOM nodes as refs directly
112 return false;
113 }
114 return inst != null && typeof inst.render === 'function' && typeof inst.setState === 'function';
115 },
116
117 isCompositeComponentWithType: function (inst, type) {
118 if (!ReactTestUtils.isCompositeComponent(inst)) {
119 return false;
120 }
121 var internalInstance = ReactInstanceMap.get(inst);
122 var constructor = internalInstance._currentElement.type;
123
124 return constructor === type;
125 },
126
127 isCompositeComponentElement: function (inst) {
128 if (!React.isValidElement(inst)) {
129 return false;
130 }
131 // We check the prototype of the type that will get mounted, not the
132 // instance itself. This is a future proof way of duck typing.
133 var prototype = inst.type.prototype;
134 return typeof prototype.render === 'function' && typeof prototype.setState === 'function';
135 },
136
137 isCompositeComponentElementWithType: function (inst, type) {
138 var internalInstance = ReactInstanceMap.get(inst);
139 var constructor = internalInstance._currentElement.type;
140
141 return !!(ReactTestUtils.isCompositeComponentElement(inst) && constructor === type);
142 },
143
144 getRenderedChildOfCompositeComponent: function (inst) {
145 if (!ReactTestUtils.isCompositeComponent(inst)) {
146 return null;
147 }
148 var internalInstance = ReactInstanceMap.get(inst);
149 return internalInstance._renderedComponent.getPublicInstance();
150 },
151
152 findAllInRenderedTree: function (inst, test) {
153 if (!inst) {
154 return [];
155 }
156 !ReactTestUtils.isCompositeComponent(inst) ? process.env.NODE_ENV !== 'production' ? invariant(false, 'findAllInRenderedTree(...): instance must be a composite component') : _prodInvariant('10') : void 0;
157 return findAllInRenderedTreeInternal(ReactInstanceMap.get(inst), test);
158 },
159
160 /**
161 * Finds all instance of components in the rendered tree that are DOM
162 * components with the class name matching `className`.
163 * @return {array} an array of all the matches.
164 */
165 scryRenderedDOMComponentsWithClass: function (root, classNames) {
166 return ReactTestUtils.findAllInRenderedTree(root, function (inst) {
167 if (ReactTestUtils.isDOMComponent(inst)) {
168 var className = inst.className;
169 if (typeof className !== 'string') {
170 // SVG, probably.
171 className = inst.getAttribute('class') || '';
172 }
173 var classList = className.split(/\s+/);
174
175 if (!Array.isArray(classNames)) {
176 !(classNames !== undefined) ? process.env.NODE_ENV !== 'production' ? invariant(false, 'TestUtils.scryRenderedDOMComponentsWithClass expects a className as a second argument.') : _prodInvariant('11') : void 0;
177 classNames = classNames.split(/\s+/);
178 }
179 return classNames.every(function (name) {
180 return classList.indexOf(name) !== -1;
181 });
182 }
183 return false;
184 });
185 },
186
187 /**
188 * Like scryRenderedDOMComponentsWithClass but expects there to be one result,
189 * and returns that one result, or throws exception if there is any other
190 * number of matches besides one.
191 * @return {!ReactDOMComponent} The one match.
192 */
193 findRenderedDOMComponentWithClass: function (root, className) {
194 var all = ReactTestUtils.scryRenderedDOMComponentsWithClass(root, className);
195 if (all.length !== 1) {
196 throw new Error('Did not find exactly one match (found: ' + all.length + ') ' + 'for class:' + className);
197 }
198 return all[0];
199 },
200
201 /**
202 * Finds all instance of components in the rendered tree that are DOM
203 * components with the tag name matching `tagName`.
204 * @return {array} an array of all the matches.
205 */
206 scryRenderedDOMComponentsWithTag: function (root, tagName) {
207 return ReactTestUtils.findAllInRenderedTree(root, function (inst) {
208 return ReactTestUtils.isDOMComponent(inst) && inst.tagName.toUpperCase() === tagName.toUpperCase();
209 });
210 },
211
212 /**
213 * Like scryRenderedDOMComponentsWithTag but expects there to be one result,
214 * and returns that one result, or throws exception if there is any other
215 * number of matches besides one.
216 * @return {!ReactDOMComponent} The one match.
217 */
218 findRenderedDOMComponentWithTag: function (root, tagName) {
219 var all = ReactTestUtils.scryRenderedDOMComponentsWithTag(root, tagName);
220 if (all.length !== 1) {
221 throw new Error('Did not find exactly one match (found: ' + all.length + ') ' + 'for tag:' + tagName);
222 }
223 return all[0];
224 },
225
226 /**
227 * Finds all instances of components with type equal to `componentType`.
228 * @return {array} an array of all the matches.
229 */
230 scryRenderedComponentsWithType: function (root, componentType) {
231 return ReactTestUtils.findAllInRenderedTree(root, function (inst) {
232 return ReactTestUtils.isCompositeComponentWithType(inst, componentType);
233 });
234 },
235
236 /**
237 * Same as `scryRenderedComponentsWithType` but expects there to be one result
238 * and returns that one result, or throws exception if there is any other
239 * number of matches besides one.
240 * @return {!ReactComponent} The one match.
241 */
242 findRenderedComponentWithType: function (root, componentType) {
243 var all = ReactTestUtils.scryRenderedComponentsWithType(root, componentType);
244 if (all.length !== 1) {
245 throw new Error('Did not find exactly one match (found: ' + all.length + ') ' + 'for componentType:' + componentType);
246 }
247 return all[0];
248 },
249
250 /**
251 * Pass a mocked component module to this method to augment it with
252 * useful methods that allow it to be used as a dummy React component.
253 * Instead of rendering as usual, the component will become a simple
254 * <div> containing any provided children.
255 *
256 * @param {object} module the mock function object exported from a
257 * module that defines the component to be mocked
258 * @param {?string} mockTagName optional dummy root tag name to return
259 * from render method (overrides
260 * module.mockTagName if provided)
261 * @return {object} the ReactTestUtils object (for chaining)
262 */
263 mockComponent: function (module, mockTagName) {
264 mockTagName = mockTagName || module.mockTagName || 'div';
265
266 module.prototype.render.mockImplementation(function () {
267 return React.createElement(mockTagName, null, this.props.children);
268 });
269
270 return this;
271 },
272
273 /**
274 * Simulates a top level event being dispatched from a raw event that occurred
275 * on an `Element` node.
276 * @param {Object} topLevelType A type from `EventConstants.topLevelTypes`
277 * @param {!Element} node The dom to simulate an event occurring on.
278 * @param {?Event} fakeNativeEvent Fake native event to use in SyntheticEvent.
279 */
280 simulateNativeEventOnNode: function (topLevelType, node, fakeNativeEvent) {
281 fakeNativeEvent.target = node;
282 fakeNativeEvent.simulated = true;
283 ReactBrowserEventEmitter.ReactEventListener.dispatchEvent(topLevelType, fakeNativeEvent);
284 },
285
286 /**
287 * Simulates a top level event being dispatched from a raw event that occurred
288 * on the `ReactDOMComponent` `comp`.
289 * @param {Object} topLevelType A type from `EventConstants.topLevelTypes`.
290 * @param {!ReactDOMComponent} comp
291 * @param {?Event} fakeNativeEvent Fake native event to use in SyntheticEvent.
292 */
293 simulateNativeEventOnDOMComponent: function (topLevelType, comp, fakeNativeEvent) {
294 ReactTestUtils.simulateNativeEventOnNode(topLevelType, findDOMNode(comp), fakeNativeEvent);
295 },
296
297 nativeTouchData: function (x, y) {
298 return {
299 touches: [{ pageX: x, pageY: y }]
300 };
301 },
302
303 createRenderer: createRendererWithWarning,
304
305 Simulate: null,
306 SimulateNative: {}
307};
308
309/**
310 * Exports:
311 *
312 * - `ReactTestUtils.Simulate.click(Element/ReactDOMComponent)`
313 * - `ReactTestUtils.Simulate.mouseMove(Element/ReactDOMComponent)`
314 * - `ReactTestUtils.Simulate.change(Element/ReactDOMComponent)`
315 * - ... (All keys from event plugin `eventTypes` objects)
316 */
317function makeSimulator(eventType) {
318 return function (domComponentOrNode, eventData) {
319 var node;
320 !!React.isValidElement(domComponentOrNode) ? process.env.NODE_ENV !== 'production' ? invariant(false, 'TestUtils.Simulate expects a component instance and not a ReactElement.TestUtils.Simulate will not work if you are using shallow rendering.') : _prodInvariant('14') : void 0;
321 if (ReactTestUtils.isDOMComponent(domComponentOrNode)) {
322 node = findDOMNode(domComponentOrNode);
323 } else if (domComponentOrNode.tagName) {
324 node = domComponentOrNode;
325 }
326
327 var dispatchConfig = EventPluginRegistry.eventNameDispatchConfigs[eventType];
328
329 var fakeNativeEvent = new Event();
330 fakeNativeEvent.target = node;
331 fakeNativeEvent.type = eventType.toLowerCase();
332
333 // We don't use SyntheticEvent.getPooled in order to not have to worry about
334 // properly destroying any properties assigned from `eventData` upon release
335 var event = new SyntheticEvent(dispatchConfig, ReactDOMComponentTree.getInstanceFromNode(node), fakeNativeEvent, node);
336 // Since we aren't using pooling, always persist the event. This will make
337 // sure it's marked and won't warn when setting additional properties.
338 event.persist();
339 _assign(event, eventData);
340
341 if (dispatchConfig.phasedRegistrationNames) {
342 EventPropagators.accumulateTwoPhaseDispatches(event);
343 } else {
344 EventPropagators.accumulateDirectDispatches(event);
345 }
346
347 ReactUpdates.batchedUpdates(function () {
348 EventPluginHub.enqueueEvents(event);
349 EventPluginHub.processEventQueue(true);
350 });
351 };
352}
353
354function buildSimulators() {
355 ReactTestUtils.Simulate = {};
356
357 var eventType;
358 for (eventType in EventPluginRegistry.eventNameDispatchConfigs) {
359 /**
360 * @param {!Element|ReactDOMComponent} domComponentOrNode
361 * @param {?object} eventData Fake event data to use in SyntheticEvent.
362 */
363 ReactTestUtils.Simulate[eventType] = makeSimulator(eventType);
364 }
365}
366
367// Rebuild ReactTestUtils.Simulate whenever event plugins are injected
368var oldInjectEventPluginOrder = EventPluginHub.injection.injectEventPluginOrder;
369EventPluginHub.injection.injectEventPluginOrder = function () {
370 oldInjectEventPluginOrder.apply(this, arguments);
371 buildSimulators();
372};
373var oldInjectEventPlugins = EventPluginHub.injection.injectEventPluginsByName;
374EventPluginHub.injection.injectEventPluginsByName = function () {
375 oldInjectEventPlugins.apply(this, arguments);
376 buildSimulators();
377};
378
379buildSimulators();
380
381/**
382 * Exports:
383 *
384 * - `ReactTestUtils.SimulateNative.click(Element/ReactDOMComponent)`
385 * - `ReactTestUtils.SimulateNative.mouseMove(Element/ReactDOMComponent)`
386 * - `ReactTestUtils.SimulateNative.mouseIn/ReactDOMComponent)`
387 * - `ReactTestUtils.SimulateNative.mouseOut(Element/ReactDOMComponent)`
388 * - ... (All keys from `EventConstants.topLevelTypes`)
389 *
390 * Note: Top level event types are a subset of the entire set of handler types
391 * (which include a broader set of "synthetic" events). For example, onDragDone
392 * is a synthetic event. Except when testing an event plugin or React's event
393 * handling code specifically, you probably want to use ReactTestUtils.Simulate
394 * to dispatch synthetic events.
395 */
396
397function makeNativeSimulator(eventType) {
398 return function (domComponentOrNode, nativeEventData) {
399 var fakeNativeEvent = new Event(eventType);
400 _assign(fakeNativeEvent, nativeEventData);
401 if (ReactTestUtils.isDOMComponent(domComponentOrNode)) {
402 ReactTestUtils.simulateNativeEventOnDOMComponent(eventType, domComponentOrNode, fakeNativeEvent);
403 } else if (domComponentOrNode.tagName) {
404 // Will allow on actual dom nodes.
405 ReactTestUtils.simulateNativeEventOnNode(eventType, domComponentOrNode, fakeNativeEvent);
406 }
407 };
408}
409
410Object.keys(topLevelTypes).forEach(function (eventType) {
411 // Event type is stored as 'topClick' - we transform that to 'click'
412 var convenienceName = eventType.indexOf('top') === 0 ? eventType.charAt(3).toLowerCase() + eventType.substr(4) : eventType;
413 /**
414 * @param {!Element|ReactDOMComponent} domComponentOrNode
415 * @param {?Event} nativeEventData Fake native event to use in SyntheticEvent.
416 */
417 ReactTestUtils.SimulateNative[convenienceName] = makeNativeSimulator(eventType);
418});
419
420module.exports = ReactTestUtils;
\No newline at end of file