UNPKG

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