@samitouri / QOS-React-1 / commits / 01bb3c5632

[TestUtils] Build limited test-utils (#28782)

We landed a flag to disable test utils in many builds but we need to fork the entrypoint to make it work with tests properly. This also removes test-utils implementations from builds that do not support it. Currently in OSS builds the only thing in test-utils is a reexport of `act`

Josh Story committed Apr 8, 2024 at 12:27 UTC 01bb3c5632ef47751b0ef78a693cab9102c1ed0d
6 files changed +996 -971
packages/react-dom/src/__tests__/ReactTestUtils-test.js
+103 -22
@@ -9,11 +9,11 @@
9
10 'use strict';
11
12 -import {act} from 'internal-test-utils';
13 -import * as React from 'react';
14 -import * as ReactDOMClient from 'react-dom/client';
15 -import * as ReactDOMServer from 'react-dom/server';
16 -import * as ReactTestUtils from 'react-dom/test-utils';
12 +let React;
13 +let ReactDOMClient;
14 +let ReactDOMServer;
15 +let ReactTestUtils;
16 +let act;
17
18 function getTestDocument(markup) {
19 const doc = document.implementation.createHTMLDocument('');
@@ -27,8 +27,105 @@ function getTestDocument(markup) {
27 }
28
29 describe('ReactTestUtils', () => {
30 + beforeEach(() => {
31 + React = require('react');
32 + ReactDOMClient = require('react-dom/client');
33 + ReactDOMServer = require('react-dom/server');
34 + ReactTestUtils = require('react-dom/test-utils');
35 + act = require('internal-test-utils').act;
36 + });
37 +
38 + // @gate !disableDOMTestUtils
39 it('Simulate should have locally attached media events', () => {
31 - expect(Object.keys(ReactTestUtils.Simulate).sort()).toMatchSnapshot();
40 + expect(Object.keys(ReactTestUtils.Simulate).sort()).toMatchInlineSnapshot(`
41 + [
42 + "abort",
43 + "animationEnd",
44 + "animationIteration",
45 + "animationStart",
46 + "auxClick",
47 + "beforeInput",
48 + "blur",
49 + "canPlay",
50 + "canPlayThrough",
51 + "cancel",
52 + "change",
53 + "click",
54 + "close",
55 + "compositionEnd",
56 + "compositionStart",
57 + "compositionUpdate",
58 + "contextMenu",
59 + "copy",
60 + "cut",
61 + "doubleClick",
62 + "drag",
63 + "dragEnd",
64 + "dragEnter",
65 + "dragExit",
66 + "dragLeave",
67 + "dragOver",
68 + "dragStart",
69 + "drop",
70 + "durationChange",
71 + "emptied",
72 + "encrypted",
73 + "ended",
74 + "error",
75 + "focus",
76 + "gotPointerCapture",
77 + "input",
78 + "invalid",
79 + "keyDown",
80 + "keyPress",
81 + "keyUp",
82 + "load",
83 + "loadStart",
84 + "loadedData",
85 + "loadedMetadata",
86 + "lostPointerCapture",
87 + "mouseDown",
88 + "mouseEnter",
89 + "mouseLeave",
90 + "mouseMove",
91 + "mouseOut",
92 + "mouseOver",
93 + "mouseUp",
94 + "paste",
95 + "pause",
96 + "play",
97 + "playing",
98 + "pointerCancel",
99 + "pointerDown",
100 + "pointerEnter",
101 + "pointerLeave",
102 + "pointerMove",
103 + "pointerOut",
104 + "pointerOver",
105 + "pointerUp",
106 + "progress",
107 + "rateChange",
108 + "reset",
109 + "resize",
110 + "scroll",
111 + "seeked",
112 + "seeking",
113 + "select",
114 + "stalled",
115 + "submit",
116 + "suspend",
117 + "timeUpdate",
118 + "toggle",
119 + "touchCancel",
120 + "touchEnd",
121 + "touchMove",
122 + "touchStart",
123 + "transitionEnd",
124 + "volumeChange",
125 + "waiting",
126 + "wheel",
127 + ]
128 + `);
129 });
130
131 // @gate !disableDOMTestUtils
@@ -575,14 +672,6 @@ describe('ReactTestUtils', () => {
672 });
673 expect(idCallOrder).toEqual([CHILD]);
674 });
578 -
579 - // @gate disableDOMTestUtils
580 - it('throws', async () => {
581 - expect(ReactTestUtils.Simulate.click).toThrow(
582 - '`Simulate` was removed from `react-dom/test-utils`. ' +
583 - 'See https://react.dev/warnings/react-dom-test-utils for more info.',
584 - );
585 - });
675 });
676
677 // @gate !disableDOMTestUtils
@@ -626,14 +715,6 @@ describe('ReactTestUtils', () => {
715 expect(renderedComponentType).toBe(instance);
716 });
717
629 - // @gate disableDOMTestUtils
630 - it('throws on every removed function', async () => {
631 - expect(ReactTestUtils.isDOMComponent).toThrow(
632 - '`isDOMComponent` was removed from `react-dom/test-utils`. ' +
633 - 'See https://react.dev/warnings/react-dom-test-utils for more info.',
634 - );
635 - });
636 -
718 // @gate __DEV__
719 it('warns when using `act`', () => {
720 expect(() => {
packages/react-dom/src/__tests__/__snapshots__/ReactTestUtils-test.js.snap deleted
-91
@@ -1,91 +0,0 @@
1 -// Jest Snapshot v1, https://goo.gl/fbAQLP
2 -
3 -exports[`ReactTestUtils Simulate should have locally attached media events 1`] = `
4 -[
5 - "abort",
6 - "animationEnd",
7 - "animationIteration",
8 - "animationStart",
9 - "auxClick",
10 - "beforeInput",
11 - "blur",
12 - "canPlay",
13 - "canPlayThrough",
14 - "cancel",
15 - "change",
16 - "click",
17 - "close",
18 - "compositionEnd",
19 - "compositionStart",
20 - "compositionUpdate",
21 - "contextMenu",
22 - "copy",
23 - "cut",
24 - "doubleClick",
25 - "drag",
26 - "dragEnd",
27 - "dragEnter",
28 - "dragExit",
29 - "dragLeave",
30 - "dragOver",
31 - "dragStart",
32 - "drop",
33 - "durationChange",
34 - "emptied",
35 - "encrypted",
36 - "ended",
37 - "error",
38 - "focus",
39 - "gotPointerCapture",
40 - "input",
41 - "invalid",
42 - "keyDown",
43 - "keyPress",
44 - "keyUp",
45 - "load",
46 - "loadStart",
47 - "loadedData",
48 - "loadedMetadata",
49 - "lostPointerCapture",
50 - "mouseDown",
51 - "mouseEnter",
52 - "mouseLeave",
53 - "mouseMove",
54 - "mouseOut",
55 - "mouseOver",
56 - "mouseUp",
57 - "paste",
58 - "pause",
59 - "play",
60 - "playing",
61 - "pointerCancel",
62 - "pointerDown",
63 - "pointerEnter",
64 - "pointerLeave",
65 - "pointerMove",
66 - "pointerOut",
67 - "pointerOver",
68 - "pointerUp",
69 - "progress",
70 - "rateChange",
71 - "reset",
72 - "resize",
73 - "scroll",
74 - "seeked",
75 - "seeking",
76 - "select",
77 - "stalled",
78 - "submit",
79 - "suspend",
80 - "timeUpdate",
81 - "toggle",
82 - "touchCancel",
83 - "touchEnd",
84 - "touchMove",
85 - "touchStart",
86 - "transitionEnd",
87 - "volumeChange",
88 - "waiting",
89 - "wheel",
90 -]
91 -`;
packages/react-dom/src/test-utils/ReactTestUtils.js
+1 -858
@@ -8,35 +8,9 @@
8 */
9
10 import * as React from 'react';
11 -import * as ReactDOM from 'react-dom';
12 -import {findCurrentFiberUsingSlowPath} from 'react-reconciler/src/ReactFiberTreeReflection';
13 -import {get as getInstance} from 'shared/ReactInstanceMap';
14 -import {
15 - ClassComponent,
16 - FunctionComponent,
17 - HostComponent,
18 - HostHoistable,
19 - HostSingleton,
20 - HostText,
21 -} from 'react-reconciler/src/ReactWorkTags';
22 -import {SyntheticEvent} from 'react-dom-bindings/src/events/SyntheticEvent';
23 -import {ELEMENT_NODE} from 'react-dom-bindings/src/client/HTMLNodeType';
24 -import {disableDOMTestUtils} from 'shared/ReactFeatureFlags';
25 -import assign from 'shared/assign';
26 -import isArray from 'shared/isArray';
27 -
28 -// Keep in sync with ReactDOM.js:
29 -const SecretInternals =
30 - ReactDOM.__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED;
31 -const EventInternals = SecretInternals.Events;
32 -const getInstanceFromNode = EventInternals[0];
33 -const getNodeFromInstance = EventInternals[1];
34 -const getFiberCurrentPropsFromNode = EventInternals[2];
35 -const enqueueStateRestore = EventInternals[3];
36 -const restoreStateIfNeeded = EventInternals[4];
11
12 let didWarnAboutUsingAct = false;
39 -function act(callback) {
13 +export function act(callback) {
14 if (didWarnAboutUsingAct === false) {
15 didWarnAboutUsingAct = true;
16 console.error(
@@ -47,834 +21,3 @@ function act(callback) {
21 }
22 return React.act(callback);
23 }
50 -
51 -function Event(suffix) {}
52 -
53 -let hasWarnedAboutDeprecatedMockComponent = false;
54 -
55 -/**
56 - * @class ReactTestUtils
57 - */
58 -
59 -function findAllInRenderedFiberTreeInternal(fiber, test) {
60 - if (!fiber) {
61 - return [];
62 - }
63 - const currentParent = findCurrentFiberUsingSlowPath(fiber);
64 - if (!currentParent) {
65 - return [];
66 - }
67 - let node = currentParent;
68 - const ret = [];
69 - while (true) {
70 - if (
71 - node.tag === HostComponent ||
72 - node.tag === HostText ||
73 - node.tag === ClassComponent ||
74 - node.tag === FunctionComponent ||
75 - node.tag === HostHoistable ||
76 - node.tag === HostSingleton
77 - ) {
78 - const publicInst = node.stateNode;
79 - if (test(publicInst)) {
80 - ret.push(publicInst);
81 - }
82 - }
83 - if (node.child) {
84 - node.child.return = node;
85 - node = node.child;
86 - continue;
87 - }
88 - if (node === currentParent) {
89 - return ret;
90 - }
91 - while (!node.sibling) {
92 - if (!node.return || node.return === currentParent) {
93 - return ret;
94 - }
95 - node = node.return;
96 - }
97 - node.sibling.return = node.return;
98 - node = node.sibling;
99 - }
100 -}
101 -
102 -function validateClassInstance(inst, methodName) {
103 - if (!inst) {
104 - // This is probably too relaxed but it's existing behavior.
105 - return;
106 - }
107 - if (getInstance(inst)) {
108 - // This is a public instance indeed.
109 - return;
110 - }
111 - let received;
112 - const stringified = String(inst);
113 - if (isArray(inst)) {
114 - received = 'an array';
115 - } else if (inst && inst.nodeType === ELEMENT_NODE && inst.tagName) {
116 - received = 'a DOM node';
117 - } else if (stringified === '[object Object]') {
118 - received = 'object with keys {' + Object.keys(inst).join(', ') + '}';
119 - } else {
120 - received = stringified;
121 - }
122 -
123 - throw new Error(
124 - `The first argument must be a React class instance. ` +
125 - `Instead received: ${received}.`,
126 - );
127 -}
128 -
129 -/**
130 - * Utilities for making it easy to test React components.
131 - *
132 - * See https://reactjs.org/docs/test-utils.html
133 - *
134 - * Todo: Support the entire DOM.scry query syntax. For now, these simple
135 - * utilities will suffice for testing purposes.
136 - * @lends ReactTestUtils
137 - */
138 -function renderIntoDocument(element) {
139 - if (disableDOMTestUtils) {
140 - throw new Error(
141 - '`renderIntoDocument` was removed from `react-dom/test-utils`. ' +
142 - 'See https://react.dev/warnings/react-dom-test-utils for more info.',
143 - );
144 - }
145 -
146 - const div = document.createElement('div');
147 - // None of our tests actually require attaching the container to the
148 - // DOM, and doing so creates a mess that we rely on test isolation to
149 - // clean up, so we're going to stop honoring the name of this method
150 - // (and probably rename it eventually) if no problems arise.
151 - // document.documentElement.appendChild(div);
152 - return ReactDOM.render(element, div);
153 -}
154 -
155 -function isElement(element) {
156 - if (disableDOMTestUtils) {
157 - throw new Error(
158 - '`isElement` was removed from `react-dom/test-utils`. ' +
159 - 'See https://react.dev/warnings/react-dom-test-utils for more info.',
160 - );
161 - }
162 -
163 - return React.isValidElement(element);
164 -}
165 -
166 -function isElementOfType(inst, convenienceConstructor) {
167 - if (disableDOMTestUtils) {
168 - throw new Error(
169 - '`isElementOfType` was removed from `react-dom/test-utils`. ' +
170 - 'See https://react.dev/warnings/react-dom-test-utils for more info.',
171 - );
172 - }
173 -
174 - return React.isValidElement(inst) && inst.type === convenienceConstructor;
175 -}
176 -
177 -function isDOMComponent(inst) {
178 - if (disableDOMTestUtils) {
179 - throw new Error(
180 - '`isDOMComponent` was removed from `react-dom/test-utils`. ' +
181 - 'See https://react.dev/warnings/react-dom-test-utils for more info.',
182 - );
183 - }
184 -
185 - return !!(inst && inst.nodeType === ELEMENT_NODE && inst.tagName);
186 -}
187 -
188 -function isDOMComponentElement(inst) {
189 - if (disableDOMTestUtils) {
190 - throw new Error(
191 - '`isDOMComponentElement` was removed from `react-dom/test-utils`. ' +
192 - 'See https://react.dev/warnings/react-dom-test-utils for more info.',
193 - );
194 - }
195 -
196 - return !!(inst && React.isValidElement(inst) && !!inst.tagName);
197 -}
198 -
199 -function isCompositeComponent(inst) {
200 - if (disableDOMTestUtils) {
201 - throw new Error(
202 - '`isCompositeComponent` was removed from `react-dom/test-utils`. ' +
203 - 'See https://react.dev/warnings/react-dom-test-utils for more info.',
204 - );
205 - }
206 -
207 - if (isDOMComponent(inst)) {
208 - // Accessing inst.setState warns; just return false as that'll be what
209 - // this returns when we have DOM nodes as refs directly
210 - return false;
211 - }
212 - return (
213 - inst != null &&
214 - typeof inst.render === 'function' &&
215 - typeof inst.setState === 'function'
216 - );
217 -}
218 -
219 -function isCompositeComponentWithType(inst, type) {
220 - if (disableDOMTestUtils) {
221 - throw new Error(
222 - '`isCompositeComponentWithType` was removed from `react-dom/test-utils`. ' +
223 - 'See https://react.dev/warnings/react-dom-test-utils for more info.',
224 - );
225 - }
226 -
227 - if (!isCompositeComponent(inst)) {
228 - return false;
229 - }
230 - const internalInstance = getInstance(inst);
231 - const constructor = internalInstance.type;
232 - return constructor === type;
233 -}
234 -
235 -function findAllInRenderedTree(inst, test) {
236 - if (disableDOMTestUtils) {
237 - throw new Error(
238 - '`findAllInRenderedTree` was removed from `react-dom/test-utils`. ' +
239 - 'See https://react.dev/warnings/react-dom-test-utils for more info.',
240 - );
241 - }
242 -
243 - validateClassInstance(inst, 'findAllInRenderedTree');
244 - if (!inst) {
245 - return [];
246 - }
247 - const internalInstance = getInstance(inst);
248 - return findAllInRenderedFiberTreeInternal(internalInstance, test);
249 -}
250 -
251 -/**
252 - * Finds all instances of components in the rendered tree that are DOM
253 - * components with the class name matching `className`.
254 - * @return {array} an array of all the matches.
255 - */
256 -function scryRenderedDOMComponentsWithClass(root, classNames) {
257 - if (disableDOMTestUtils) {
258 - throw new Error(
259 - '`scryRenderedDOMComponentsWithClass` was removed from `react-dom/test-utils`. ' +
260 - 'See https://react.dev/warnings/react-dom-test-utils for more info.',
261 - );
262 - }
263 -
264 - validateClassInstance(root, 'scryRenderedDOMComponentsWithClass');
265 - return findAllInRenderedTree(root, function (inst) {
266 - if (isDOMComponent(inst)) {
267 - let className = inst.className;
268 - if (typeof className !== 'string') {
269 - // SVG, probably.
270 - className = inst.getAttribute('class') || '';
271 - }
272 - const classList = className.split(/\s+/);
273 -
274 - if (!isArray(classNames)) {
275 - if (classNames === undefined) {
276 - throw new Error(
277 - 'TestUtils.scryRenderedDOMComponentsWithClass expects a ' +
278 - 'className as a second argument.',
279 - );
280 - }
281 -
282 - classNames = classNames.split(/\s+/);
283 - }
284 - return classNames.every(function (name) {
285 - return classList.indexOf(name) !== -1;
286 - });
287 - }
288 - return false;
289 - });
290 -}
291 -
292 -/**
293 - * Like scryRenderedDOMComponentsWithClass but expects there to be one result,
294 - * and returns that one result, or throws exception if there is any other
295 - * number of matches besides one.
296 - * @return {!ReactDOMComponent} The one match.
297 - */
298 -function findRenderedDOMComponentWithClass(root, className) {
299 - if (disableDOMTestUtils) {
300 - throw new Error(
301 - '`findRenderedDOMComponentWithClass` was removed from `react-dom/test-utils`. ' +
302 - 'See https://react.dev/warnings/react-dom-test-utils for more info.',
303 - );
304 - }
305 -
306 - validateClassInstance(root, 'findRenderedDOMComponentWithClass');
307 - const all = scryRenderedDOMComponentsWithClass(root, className);
308 - if (all.length !== 1) {
309 - throw new Error(
310 - 'Did not find exactly one match (found: ' +
311 - all.length +
312 - ') ' +
313 - 'for class:' +
314 - className,
315 - );
316 - }
317 - return all[0];
318 -}
319 -
320 -/**
321 - * Finds all instances of components in the rendered tree that are DOM
322 - * components with the tag name matching `tagName`.
323 - * @return {array} an array of all the matches.
324 - */
325 -function scryRenderedDOMComponentsWithTag(root, tagName) {
326 - if (disableDOMTestUtils) {
327 - throw new Error(
328 - '`scryRenderedDOMComponentsWithTag` was removed from `react-dom/test-utils`. ' +
329 - 'See https://react.dev/warnings/react-dom-test-utils for more info.',
330 - );
331 - }
332 -
333 - validateClassInstance(root, 'scryRenderedDOMComponentsWithTag');
334 - return findAllInRenderedTree(root, function (inst) {
335 - return (
336 - isDOMComponent(inst) &&
337 - inst.tagName.toUpperCase() === tagName.toUpperCase()
338 - );
339 - });
340 -}
341 -
342 -/**
343 - * Like scryRenderedDOMComponentsWithTag but expects there to be one result,
344 - * and returns that one result, or throws exception if there is any other
345 - * number of matches besides one.
346 - * @return {!ReactDOMComponent} The one match.
347 - */
348 -function findRenderedDOMComponentWithTag(root, tagName) {
349 - if (disableDOMTestUtils) {
350 - throw new Error(
351 - '`findRenderedDOMComponentWithTag` was removed from `react-dom/test-utils`. ' +
352 - 'See https://react.dev/warnings/react-dom-test-utils for more info.',
353 - );
354 - }
355 -
356 - validateClassInstance(root, 'findRenderedDOMComponentWithTag');
357 - const all = scryRenderedDOMComponentsWithTag(root, tagName);
358 - if (all.length !== 1) {
359 - throw new Error(
360 - 'Did not find exactly one match (found: ' +
361 - all.length +
362 - ') ' +
363 - 'for tag:' +
364 - tagName,
365 - );
366 - }
367 - return all[0];
368 -}
369 -
370 -/**
371 - * Finds all instances of components with type equal to `componentType`.
372 - * @return {array} an array of all the matches.
373 - */
374 -function scryRenderedComponentsWithType(root, componentType) {
375 - if (disableDOMTestUtils) {
376 - throw new Error(
377 - '`scryRenderedComponentsWithType` was removed from `react-dom/test-utils`. ' +
378 - 'See https://react.dev/warnings/react-dom-test-utils for more info.',
379 - );
380 - }
381 -
382 - validateClassInstance(root, 'scryRenderedComponentsWithType');
383 - return findAllInRenderedTree(root, function (inst) {
384 - return isCompositeComponentWithType(inst, componentType);
385 - });
386 -}
387 -
388 -/**
389 - * Same as `scryRenderedComponentsWithType` but expects there to be one result
390 - * and returns that one result, or throws exception if there is any other
391 - * number of matches besides one.
392 - * @return {!ReactComponent} The one match.
393 - */
394 -function findRenderedComponentWithType(root, componentType) {
395 - if (disableDOMTestUtils) {
396 - throw new Error(
397 - '`findRenderedComponentWithType` was removed from `react-dom/test-utils`. ' +
398 - 'See https://react.dev/warnings/react-dom-test-utils for more info.',
399 - );
400 - }
401 -
402 - validateClassInstance(root, 'findRenderedComponentWithType');
403 - const all = scryRenderedComponentsWithType(root, componentType);
404 - if (all.length !== 1) {
405 - throw new Error(
406 - 'Did not find exactly one match (found: ' +
407 - all.length +
408 - ') ' +
409 - 'for componentType:' +
410 - componentType,
411 - );
412 - }
413 - return all[0];
414 -}
415 -
416 -/**
417 - * Pass a mocked component module to this method to augment it with
418 - * useful methods that allow it to be used as a dummy React component.
419 - * Instead of rendering as usual, the component will become a simple
420 - * <div> containing any provided children.
421 - *
422 - * @param {object} module the mock function object exported from a
423 - * module that defines the component to be mocked
424 - * @param {?string} mockTagName optional dummy root tag name to return
425 - * from render method (overrides
426 - * module.mockTagName if provided)
427 - * @return {object} the ReactTestUtils object (for chaining)
428 - */
429 -function mockComponent(module, mockTagName) {
430 - if (disableDOMTestUtils) {
431 - throw new Error(
432 - '`mockComponent` was removed from `react-dom/test-utils`. ' +
433 - 'See https://react.dev/warnings/react-dom-test-utils for more info.',
434 - );
435 - }
436 -
437 - if (__DEV__) {
438 - if (!hasWarnedAboutDeprecatedMockComponent) {
439 - hasWarnedAboutDeprecatedMockComponent = true;
440 - console.warn(
441 - 'ReactTestUtils.mockComponent() is deprecated. ' +
442 - 'Use shallow rendering or jest.mock() instead.\n\n' +
443 - 'See https://react.dev/link/test-utils-mock-component for more information.',
444 - );
445 - }
446 - }
447 -
448 - mockTagName = mockTagName || module.mockTagName || 'div';
449 -
450 - module.prototype.render.mockImplementation(function () {
451 - return React.createElement(mockTagName, null, this.props.children);
452 - });
453 -
454 - return this;
455 -}
456 -
457 -function nativeTouchData(x, y) {
458 - if (disableDOMTestUtils) {
459 - throw new Error(
460 - '`nativeTouchData` was removed from `react-dom/test-utils`. ' +
461 - 'See https://react.dev/warnings/react-dom-test-utils for more info.',
462 - );
463 - }
464 -
465 - return {
466 - touches: [{pageX: x, pageY: y}],
467 - };
468 -}
469 -
470 -// Start of inline: the below functions were inlined from
471 -// EventPropagator.js, as they deviated from ReactDOM's newer
472 -// implementations.
473 -
474 -let hasError: boolean = false;
475 -let caughtError: mixed = null;
476 -
477 -/**
478 - * Dispatch the event to the listener.
479 - * @param {SyntheticEvent} event SyntheticEvent to handle
480 - * @param {function} listener Application-level callback
481 - * @param {*} inst Internal component instance
482 - */
483 -function executeDispatch(event, listener, inst) {
484 - event.currentTarget = getNodeFromInstance(inst);
485 - try {
486 - listener(event);
487 - } catch (error) {
488 - if (!hasError) {
489 - hasError = true;
490 - caughtError = error;
491 - }
492 - }
493 - event.currentTarget = null;
494 -}
495 -
496 -/**
497 - * Standard/simple iteration through an event's collected dispatches.
498 - */
499 -function executeDispatchesInOrder(event) {
500 - const dispatchListeners = event._dispatchListeners;
501 - const dispatchInstances = event._dispatchInstances;
502 - if (isArray(dispatchListeners)) {
503 - for (let i = 0; i < dispatchListeners.length; i++) {
504 - if (event.isPropagationStopped()) {
505 - break;
506 - }
507 - // Listeners and Instances are two parallel arrays that are always in sync.
508 - executeDispatch(event, dispatchListeners[i], dispatchInstances[i]);
509 - }
510 - } else if (dispatchListeners) {
511 - executeDispatch(event, dispatchListeners, dispatchInstances);
512 - }
513 - event._dispatchListeners = null;
514 - event._dispatchInstances = null;
515 -}
516 -
517 -/**
518 - * Dispatches an event and releases it back into the pool, unless persistent.
519 - *
520 - * @param {?object} event Synthetic event to be dispatched.
521 - * @private
522 - */
523 -function executeDispatchesAndRelease(event /* ReactSyntheticEvent */) {
524 - if (event) {
525 - executeDispatchesInOrder(event);
526 -
527 - if (!event.isPersistent()) {
528 - event.constructor.release(event);
529 - }
530 - }
531 -}
532 -
533 -function isInteractive(tag) {
534 - return (
535 - tag === 'button' ||
536 - tag === 'input' ||
537 - tag === 'select' ||
538 - tag === 'textarea'
539 - );
540 -}
541 -
542 -function getParent(inst) {
543 - do {
544 - inst = inst.return;
545 - // TODO: If this is a HostRoot we might want to bail out.
546 - // That is depending on if we want nested subtrees (layers) to bubble
547 - // events to their parent. We could also go through parentNode on the
548 - // host node but that wouldn't work for React Native and doesn't let us
549 - // do the portal feature.
550 - } while (inst && inst.tag !== HostComponent && inst.tag !== HostSingleton);
551 - if (inst) {
552 - return inst;
553 - }
554 - return null;
555 -}
556 -
557 -/**
558 - * Simulates the traversal of a two-phase, capture/bubble event dispatch.
559 - */
560 -export function traverseTwoPhase(inst, fn, arg) {
561 - const path = [];
562 - while (inst) {
563 - path.push(inst);
564 - inst = getParent(inst);
565 - }
566 - let i;
567 - for (i = path.length; i-- > 0; ) {
568 - fn(path[i], 'captured', arg);
569 - }
570 - for (i = 0; i < path.length; i++) {
571 - fn(path[i], 'bubbled', arg);
572 - }
573 -}
574 -
575 -function shouldPreventMouseEvent(name, type, props) {
576 - switch (name) {
577 - case 'onClick':
578 - case 'onClickCapture':
579 - case 'onDoubleClick':
580 - case 'onDoubleClickCapture':
581 - case 'onMouseDown':
582 - case 'onMouseDownCapture':
583 - case 'onMouseMove':
584 - case 'onMouseMoveCapture':
585 - case 'onMouseUp':
586 - case 'onMouseUpCapture':
587 - case 'onMouseEnter':
588 - return !!(props.disabled && isInteractive(type));
589 - default:
590 - return false;
591 - }
592 -}
593 -
594 -/**
595 - * @param {object} inst The instance, which is the source of events.
596 - * @param {string} registrationName Name of listener (e.g. `onClick`).
597 - * @return {?function} The stored callback.
598 - */
599 -function getListener(inst /* Fiber */, registrationName: string) {
600 - // TODO: shouldPreventMouseEvent is DOM-specific and definitely should not
601 - // live here; needs to be moved to a better place soon
602 - const stateNode = inst.stateNode;
603 - if (!stateNode) {
604 - // Work in progress (ex: onload events in incremental mode).
605 - return null;
606 - }
607 - const props = getFiberCurrentPropsFromNode(stateNode);
608 - if (!props) {
609 - // Work in progress.
610 - return null;
611 - }
612 - const listener = props[registrationName];
613 - if (shouldPreventMouseEvent(registrationName, inst.type, props)) {
614 - return null;
615 - }
616 -
617 - if (listener && typeof listener !== 'function') {
618 - throw new Error(
619 - `Expected \`${registrationName}\` listener to be a function, instead got a value of \`${typeof listener}\` type.`,
620 - );
621 - }
622 -
623 - return listener;
624 -}
625 -
626 -function listenerAtPhase(inst, event, propagationPhase: PropagationPhases) {
627 - let registrationName = event._reactName;
628 - if (propagationPhase === 'captured') {
629 - registrationName += 'Capture';
630 - }
631 - return getListener(inst, registrationName);
632 -}
633 -
634 -function accumulateDispatches(inst, ignoredDirection, event) {
635 - if (inst && event && event._reactName) {
636 - const registrationName = event._reactName;
637 - const listener = getListener(inst, registrationName);
638 - if (listener) {
639 - if (event._dispatchListeners == null) {
640 - event._dispatchListeners = [];
641 - }
642 - if (event._dispatchInstances == null) {
643 - event._dispatchInstances = [];
644 - }
645 - event._dispatchListeners.push(listener);
646 - event._dispatchInstances.push(inst);
647 - }
648 - }
649 -}
650 -
651 -function accumulateDirectionalDispatches(inst, phase, event) {
652 - if (__DEV__) {
653 - if (!inst) {
654 - console.error('Dispatching inst must not be null');
655 - }
656 - }
657 - const listener = listenerAtPhase(inst, event, phase);
658 - if (listener) {
659 - if (event._dispatchListeners == null) {
660 - event._dispatchListeners = [];
661 - }
662 - if (event._dispatchInstances == null) {
663 - event._dispatchInstances = [];
664 - }
665 - event._dispatchListeners.push(listener);
666 - event._dispatchInstances.push(inst);
667 - }
668 -}
669 -
670 -function accumulateDirectDispatchesSingle(event) {
671 - if (event && event._reactName) {
672 - accumulateDispatches(event._targetInst, null, event);
673 - }
674 -}
675 -
676 -function accumulateTwoPhaseDispatchesSingle(event) {
677 - if (event && event._reactName) {
678 - traverseTwoPhase(event._targetInst, accumulateDirectionalDispatches, event);
679 - }
680 -}
681 -
682 -// End of inline
683 -
684 -const Simulate = {};
685 -
686 -const directDispatchEventTypes = new Set([
687 - 'mouseEnter',
688 - 'mouseLeave',
689 - 'pointerEnter',
690 - 'pointerLeave',
691 -]);
692 -
693 -/**
694 - * Exports:
695 - *
696 - * - `Simulate.click(Element)`
697 - * - `Simulate.mouseMove(Element)`
698 - * - `Simulate.change(Element)`
699 - * - ... (All keys from event plugin `eventTypes` objects)
700 - */
701 -function makeSimulator(eventType) {
702 - return function (domNode, eventData) {
703 - if (disableDOMTestUtils) {
704 - throw new Error(
705 - '`Simulate` was removed from `react-dom/test-utils`. ' +
706 - 'See https://react.dev/warnings/react-dom-test-utils for more info.',
707 - );
708 - }
709 -
710 - if (React.isValidElement(domNode)) {
711 - throw new Error(
712 - 'TestUtils.Simulate expected a DOM node as the first argument but received ' +
713 - 'a React element. Pass the DOM node you wish to simulate the event on instead. ' +
714 - 'Note that TestUtils.Simulate will not work if you are using shallow rendering.',
715 - );
716 - }
717 -
718 - if (isCompositeComponent(domNode)) {
719 - throw new Error(
720 - 'TestUtils.Simulate expected a DOM node as the first argument but received ' +
721 - 'a component instance. Pass the DOM node you wish to simulate the event on instead.',
722 - );
723 - }
724 -
725 - const reactName = 'on' + eventType[0].toUpperCase() + eventType.slice(1);
726 - const fakeNativeEvent = new Event();
727 - fakeNativeEvent.target = domNode;
728 - fakeNativeEvent.type = eventType.toLowerCase();
729 -
730 - const targetInst = getInstanceFromNode(domNode);
731 - const event = new SyntheticEvent(
732 - reactName,
733 - fakeNativeEvent.type,
734 - targetInst,
735 - fakeNativeEvent,
736 - domNode,
737 - );
738 -
739 - // Since we aren't using pooling, always persist the event. This will make
740 - // sure it's marked and won't warn when setting additional properties.
741 - event.persist();
742 - assign(event, eventData);
743 -
744 - if (directDispatchEventTypes.has(eventType)) {
745 - accumulateDirectDispatchesSingle(event);
746 - } else {
747 - accumulateTwoPhaseDispatchesSingle(event);
748 - }
749 -
750 - ReactDOM.unstable_batchedUpdates(function () {
751 - // Normally extractEvent enqueues a state restore, but we'll just always
752 - // do that since we're by-passing it here.
753 - enqueueStateRestore(domNode);
754 - executeDispatchesAndRelease(event);
755 - if (hasError) {
756 - const error = caughtError;
757 - hasError = false;
758 - caughtError = null;
759 - throw error;
760 - }
761 - });
762 - restoreStateIfNeeded();
763 - };
764 -}
765 -
766 -// A one-time snapshot with no plans to update. We'll probably want to deprecate Simulate API.
767 -const simulatedEventTypes = [
768 - 'blur',
769 - 'cancel',
770 - 'click',
771 - 'close',
772 - 'contextMenu',
773 - 'copy',
774 - 'cut',
775 - 'auxClick',
776 - 'doubleClick',
777 - 'dragEnd',
778 - 'dragStart',
779 - 'drop',
780 - 'focus',
781 - 'input',
782 - 'invalid',
783 - 'keyDown',
784 - 'keyPress',
785 - 'keyUp',
786 - 'mouseDown',
787 - 'mouseUp',
788 - 'paste',
789 - 'pause',
790 - 'play',
791 - 'pointerCancel',
792 - 'pointerDown',
793 - 'pointerUp',
794 - 'rateChange',
795 - 'reset',
796 - 'resize',
797 - 'seeked',
798 - 'submit',
799 - 'touchCancel',
800 - 'touchEnd',
801 - 'touchStart',
802 - 'volumeChange',
803 - 'drag',
804 - 'dragEnter',
805 - 'dragExit',
806 - 'dragLeave',
807 - 'dragOver',
808 - 'mouseMove',
809 - 'mouseOut',
810 - 'mouseOver',
811 - 'pointerMove',
812 - 'pointerOut',
813 - 'pointerOver',
814 - 'scroll',
815 - 'toggle',
816 - 'touchMove',
817 - 'wheel',
818 - 'abort',
819 - 'animationEnd',
820 - 'animationIteration',
821 - 'animationStart',
822 - 'canPlay',
823 - 'canPlayThrough',
824 - 'durationChange',
825 - 'emptied',
826 - 'encrypted',
827 - 'ended',
828 - 'error',
829 - 'gotPointerCapture',
830 - 'load',
831 - 'loadedData',
832 - 'loadedMetadata',
833 - 'loadStart',
834 - 'lostPointerCapture',
835 - 'playing',
836 - 'progress',
837 - 'seeking',
838 - 'stalled',
839 - 'suspend',
840 - 'timeUpdate',
841 - 'transitionEnd',
842 - 'waiting',
843 - 'mouseEnter',
844 - 'mouseLeave',
845 - 'pointerEnter',
846 - 'pointerLeave',
847 - 'change',
848 - 'select',
849 - 'beforeInput',
850 - 'compositionEnd',
851 - 'compositionStart',
852 - 'compositionUpdate',
853 -];
854 -function buildSimulators() {
855 - simulatedEventTypes.forEach(eventType => {
856 - Simulate[eventType] = makeSimulator(eventType);
857 - });
858 -}
859 -buildSimulators();
860 -
861 -export {
862 - renderIntoDocument,
863 - isElement,
864 - isElementOfType,
865 - isDOMComponent,
866 - isDOMComponentElement,
867 - isCompositeComponent,
868 - isCompositeComponentWithType,
869 - findAllInRenderedTree,
870 - scryRenderedDOMComponentsWithClass,
871 - findRenderedDOMComponentWithClass,
872 - scryRenderedDOMComponentsWithTag,
873 - findRenderedDOMComponentWithTag,
874 - scryRenderedComponentsWithType,
875 - findRenderedComponentWithType,
876 - mockComponent,
877 - nativeTouchData,
878 - Simulate,
879 - act,
880 -};
packages/react-dom/src/test-utils/ReactTestUtilsFB.js new
+880
@@ -0,0 +1,880 @@
1 +/**
2 + * Copyright (c) Meta Platforms, Inc. and affiliates.
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 + * @noflow
8 + */
9 +
10 +import * as React from 'react';
11 +import * as ReactDOM from 'react-dom';
12 +import {findCurrentFiberUsingSlowPath} from 'react-reconciler/src/ReactFiberTreeReflection';
13 +import {get as getInstance} from 'shared/ReactInstanceMap';
14 +import {
15 + ClassComponent,
16 + FunctionComponent,
17 + HostComponent,
18 + HostHoistable,
19 + HostSingleton,
20 + HostText,
21 +} from 'react-reconciler/src/ReactWorkTags';
22 +import {SyntheticEvent} from 'react-dom-bindings/src/events/SyntheticEvent';
23 +import {ELEMENT_NODE} from 'react-dom-bindings/src/client/HTMLNodeType';
24 +import {disableDOMTestUtils} from 'shared/ReactFeatureFlags';
25 +import assign from 'shared/assign';
26 +import isArray from 'shared/isArray';
27 +
28 +// Keep in sync with ReactDOM.js:
29 +const SecretInternals =
30 + ReactDOM.__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED;
31 +const EventInternals = SecretInternals.Events;
32 +const getInstanceFromNode = EventInternals[0];
33 +const getNodeFromInstance = EventInternals[1];
34 +const getFiberCurrentPropsFromNode = EventInternals[2];
35 +const enqueueStateRestore = EventInternals[3];
36 +const restoreStateIfNeeded = EventInternals[4];
37 +
38 +let didWarnAboutUsingAct = false;
39 +function act(callback) {
40 + if (didWarnAboutUsingAct === false) {
41 + didWarnAboutUsingAct = true;
42 + console.error(
43 + '`ReactDOMTestUtils.act` is deprecated in favor of `React.act`. ' +
44 + 'Import `act` from `react` instead of `react-dom/test-utils`. ' +
45 + 'See https://react.dev/warnings/react-dom-test-utils for more info.',
46 + );
47 + }
48 + return React.act(callback);
49 +}
50 +
51 +function Event(suffix) {}
52 +
53 +let hasWarnedAboutDeprecatedMockComponent = false;
54 +
55 +/**
56 + * @class ReactTestUtils
57 + */
58 +
59 +function findAllInRenderedFiberTreeInternal(fiber, test) {
60 + if (!fiber) {
61 + return [];
62 + }
63 + const currentParent = findCurrentFiberUsingSlowPath(fiber);
64 + if (!currentParent) {
65 + return [];
66 + }
67 + let node = currentParent;
68 + const ret = [];
69 + while (true) {
70 + if (
71 + node.tag === HostComponent ||
72 + node.tag === HostText ||
73 + node.tag === ClassComponent ||
74 + node.tag === FunctionComponent ||
75 + node.tag === HostHoistable ||
76 + node.tag === HostSingleton
77 + ) {
78 + const publicInst = node.stateNode;
79 + if (test(publicInst)) {
80 + ret.push(publicInst);
81 + }
82 + }
83 + if (node.child) {
84 + node.child.return = node;
85 + node = node.child;
86 + continue;
87 + }
88 + if (node === currentParent) {
89 + return ret;
90 + }
91 + while (!node.sibling) {
92 + if (!node.return || node.return === currentParent) {
93 + return ret;
94 + }
95 + node = node.return;
96 + }
97 + node.sibling.return = node.return;
98 + node = node.sibling;
99 + }
100 +}
101 +
102 +function validateClassInstance(inst, methodName) {
103 + if (!inst) {
104 + // This is probably too relaxed but it's existing behavior.
105 + return;
106 + }
107 + if (getInstance(inst)) {
108 + // This is a public instance indeed.
109 + return;
110 + }
111 + let received;
112 + const stringified = String(inst);
113 + if (isArray(inst)) {
114 + received = 'an array';
115 + } else if (inst && inst.nodeType === ELEMENT_NODE && inst.tagName) {
116 + received = 'a DOM node';
117 + } else if (stringified === '[object Object]') {
118 + received = 'object with keys {' + Object.keys(inst).join(', ') + '}';
119 + } else {
120 + received = stringified;
121 + }
122 +
123 + throw new Error(
124 + `The first argument must be a React class instance. ` +
125 + `Instead received: ${received}.`,
126 + );
127 +}
128 +
129 +/**
130 + * Utilities for making it easy to test React components.
131 + *
132 + * See https://reactjs.org/docs/test-utils.html
133 + *
134 + * Todo: Support the entire DOM.scry query syntax. For now, these simple
135 + * utilities will suffice for testing purposes.
136 + * @lends ReactTestUtils
137 + */
138 +function renderIntoDocument(element) {
139 + if (disableDOMTestUtils) {
140 + throw new Error(
141 + '`renderIntoDocument` was removed from `react-dom/test-utils`. ' +
142 + 'See https://react.dev/warnings/react-dom-test-utils for more info.',
143 + );
144 + }
145 +
146 + const div = document.createElement('div');
147 + // None of our tests actually require attaching the container to the
148 + // DOM, and doing so creates a mess that we rely on test isolation to
149 + // clean up, so we're going to stop honoring the name of this method
150 + // (and probably rename it eventually) if no problems arise.
151 + // document.documentElement.appendChild(div);
152 + return ReactDOM.render(element, div);
153 +}
154 +
155 +function isElement(element) {
156 + if (disableDOMTestUtils) {
157 + throw new Error(
158 + '`isElement` was removed from `react-dom/test-utils`. ' +
159 + 'See https://react.dev/warnings/react-dom-test-utils for more info.',
160 + );
161 + }
162 +
163 + return React.isValidElement(element);
164 +}
165 +
166 +function isElementOfType(inst, convenienceConstructor) {
167 + if (disableDOMTestUtils) {
168 + throw new Error(
169 + '`isElementOfType` was removed from `react-dom/test-utils`. ' +
170 + 'See https://react.dev/warnings/react-dom-test-utils for more info.',
171 + );
172 + }
173 +
174 + return React.isValidElement(inst) && inst.type === convenienceConstructor;
175 +}
176 +
177 +function isDOMComponent(inst) {
178 + if (disableDOMTestUtils) {
179 + throw new Error(
180 + '`isDOMComponent` was removed from `react-dom/test-utils`. ' +
181 + 'See https://react.dev/warnings/react-dom-test-utils for more info.',
182 + );
183 + }
184 +
185 + return !!(inst && inst.nodeType === ELEMENT_NODE && inst.tagName);
186 +}
187 +
188 +function isDOMComponentElement(inst) {
189 + if (disableDOMTestUtils) {
190 + throw new Error(
191 + '`isDOMComponentElement` was removed from `react-dom/test-utils`. ' +
192 + 'See https://react.dev/warnings/react-dom-test-utils for more info.',
193 + );
194 + }
195 +
196 + return !!(inst && React.isValidElement(inst) && !!inst.tagName);
197 +}
198 +
199 +function isCompositeComponent(inst) {
200 + if (disableDOMTestUtils) {
201 + throw new Error(
202 + '`isCompositeComponent` was removed from `react-dom/test-utils`. ' +
203 + 'See https://react.dev/warnings/react-dom-test-utils for more info.',
204 + );
205 + }
206 +
207 + if (isDOMComponent(inst)) {
208 + // Accessing inst.setState warns; just return false as that'll be what
209 + // this returns when we have DOM nodes as refs directly
210 + return false;
211 + }
212 + return (
213 + inst != null &&
214 + typeof inst.render === 'function' &&
215 + typeof inst.setState === 'function'
216 + );
217 +}
218 +
219 +function isCompositeComponentWithType(inst, type) {
220 + if (disableDOMTestUtils) {
221 + throw new Error(
222 + '`isCompositeComponentWithType` was removed from `react-dom/test-utils`. ' +
223 + 'See https://react.dev/warnings/react-dom-test-utils for more info.',
224 + );
225 + }
226 +
227 + if (!isCompositeComponent(inst)) {
228 + return false;
229 + }
230 + const internalInstance = getInstance(inst);
231 + const constructor = internalInstance.type;
232 + return constructor === type;
233 +}
234 +
235 +function findAllInRenderedTree(inst, test) {
236 + if (disableDOMTestUtils) {
237 + throw new Error(
238 + '`findAllInRenderedTree` was removed from `react-dom/test-utils`. ' +
239 + 'See https://react.dev/warnings/react-dom-test-utils for more info.',
240 + );
241 + }
242 +
243 + validateClassInstance(inst, 'findAllInRenderedTree');
244 + if (!inst) {
245 + return [];
246 + }
247 + const internalInstance = getInstance(inst);
248 + return findAllInRenderedFiberTreeInternal(internalInstance, test);
249 +}
250 +
251 +/**
252 + * Finds all instances of components in the rendered tree that are DOM
253 + * components with the class name matching `className`.
254 + * @return {array} an array of all the matches.
255 + */
256 +function scryRenderedDOMComponentsWithClass(root, classNames) {
257 + if (disableDOMTestUtils) {
258 + throw new Error(
259 + '`scryRenderedDOMComponentsWithClass` was removed from `react-dom/test-utils`. ' +
260 + 'See https://react.dev/warnings/react-dom-test-utils for more info.',
261 + );
262 + }
263 +
264 + validateClassInstance(root, 'scryRenderedDOMComponentsWithClass');
265 + return findAllInRenderedTree(root, function (inst) {
266 + if (isDOMComponent(inst)) {
267 + let className = inst.className;
268 + if (typeof className !== 'string') {
269 + // SVG, probably.
270 + className = inst.getAttribute('class') || '';
271 + }
272 + const classList = className.split(/\s+/);
273 +
274 + if (!isArray(classNames)) {
275 + if (classNames === undefined) {
276 + throw new Error(
277 + 'TestUtils.scryRenderedDOMComponentsWithClass expects a ' +
278 + 'className as a second argument.',
279 + );
280 + }
281 +
282 + classNames = classNames.split(/\s+/);
283 + }
284 + return classNames.every(function (name) {
285 + return classList.indexOf(name) !== -1;
286 + });
287 + }
288 + return false;
289 + });
290 +}
291 +
292 +/**
293 + * Like scryRenderedDOMComponentsWithClass but expects there to be one result,
294 + * and returns that one result, or throws exception if there is any other
295 + * number of matches besides one.
296 + * @return {!ReactDOMComponent} The one match.
297 + */
298 +function findRenderedDOMComponentWithClass(root, className) {
299 + if (disableDOMTestUtils) {
300 + throw new Error(
301 + '`findRenderedDOMComponentWithClass` was removed from `react-dom/test-utils`. ' +
302 + 'See https://react.dev/warnings/react-dom-test-utils for more info.',
303 + );
304 + }
305 +
306 + validateClassInstance(root, 'findRenderedDOMComponentWithClass');
307 + const all = scryRenderedDOMComponentsWithClass(root, className);
308 + if (all.length !== 1) {
309 + throw new Error(
310 + 'Did not find exactly one match (found: ' +
311 + all.length +
312 + ') ' +
313 + 'for class:' +
314 + className,
315 + );
316 + }
317 + return all[0];
318 +}
319 +
320 +/**
321 + * Finds all instances of components in the rendered tree that are DOM
322 + * components with the tag name matching `tagName`.
323 + * @return {array} an array of all the matches.
324 + */
325 +function scryRenderedDOMComponentsWithTag(root, tagName) {
326 + if (disableDOMTestUtils) {
327 + throw new Error(
328 + '`scryRenderedDOMComponentsWithTag` was removed from `react-dom/test-utils`. ' +
329 + 'See https://react.dev/warnings/react-dom-test-utils for more info.',
330 + );
331 + }
332 +
333 + validateClassInstance(root, 'scryRenderedDOMComponentsWithTag');
334 + return findAllInRenderedTree(root, function (inst) {
335 + return (
336 + isDOMComponent(inst) &&
337 + inst.tagName.toUpperCase() === tagName.toUpperCase()
338 + );
339 + });
340 +}
341 +
342 +/**
343 + * Like scryRenderedDOMComponentsWithTag but expects there to be one result,
344 + * and returns that one result, or throws exception if there is any other
345 + * number of matches besides one.
346 + * @return {!ReactDOMComponent} The one match.
347 + */
348 +function findRenderedDOMComponentWithTag(root, tagName) {
349 + if (disableDOMTestUtils) {
350 + throw new Error(
351 + '`findRenderedDOMComponentWithTag` was removed from `react-dom/test-utils`. ' +
352 + 'See https://react.dev/warnings/react-dom-test-utils for more info.',
353 + );
354 + }
355 +
356 + validateClassInstance(root, 'findRenderedDOMComponentWithTag');
357 + const all = scryRenderedDOMComponentsWithTag(root, tagName);
358 + if (all.length !== 1) {
359 + throw new Error(
360 + 'Did not find exactly one match (found: ' +
361 + all.length +
362 + ') ' +
363 + 'for tag:' +
364 + tagName,
365 + );
366 + }
367 + return all[0];
368 +}
369 +
370 +/**
371 + * Finds all instances of components with type equal to `componentType`.
372 + * @return {array} an array of all the matches.
373 + */
374 +function scryRenderedComponentsWithType(root, componentType) {
375 + if (disableDOMTestUtils) {
376 + throw new Error(
377 + '`scryRenderedComponentsWithType` was removed from `react-dom/test-utils`. ' +
378 + 'See https://react.dev/warnings/react-dom-test-utils for more info.',
379 + );
380 + }
381 +
382 + validateClassInstance(root, 'scryRenderedComponentsWithType');
383 + return findAllInRenderedTree(root, function (inst) {
384 + return isCompositeComponentWithType(inst, componentType);
385 + });
386 +}
387 +
388 +/**
389 + * Same as `scryRenderedComponentsWithType` but expects there to be one result
390 + * and returns that one result, or throws exception if there is any other
391 + * number of matches besides one.
392 + * @return {!ReactComponent} The one match.
393 + */
394 +function findRenderedComponentWithType(root, componentType) {
395 + if (disableDOMTestUtils) {
396 + throw new Error(
397 + '`findRenderedComponentWithType` was removed from `react-dom/test-utils`. ' +
398 + 'See https://react.dev/warnings/react-dom-test-utils for more info.',
399 + );
400 + }
401 +
402 + validateClassInstance(root, 'findRenderedComponentWithType');
403 + const all = scryRenderedComponentsWithType(root, componentType);
404 + if (all.length !== 1) {
405 + throw new Error(
406 + 'Did not find exactly one match (found: ' +
407 + all.length +
408 + ') ' +
409 + 'for componentType:' +
410 + componentType,
411 + );
412 + }
413 + return all[0];
414 +}
415 +
416 +/**
417 + * Pass a mocked component module to this method to augment it with
418 + * useful methods that allow it to be used as a dummy React component.
419 + * Instead of rendering as usual, the component will become a simple
420 + * <div> containing any provided children.
421 + *
422 + * @param {object} module the mock function object exported from a
423 + * module that defines the component to be mocked
424 + * @param {?string} mockTagName optional dummy root tag name to return
425 + * from render method (overrides
426 + * module.mockTagName if provided)
427 + * @return {object} the ReactTestUtils object (for chaining)
428 + */
429 +function mockComponent(module, mockTagName) {
430 + if (disableDOMTestUtils) {
431 + throw new Error(
432 + '`mockComponent` was removed from `react-dom/test-utils`. ' +
433 + 'See https://react.dev/warnings/react-dom-test-utils for more info.',
434 + );
435 + }
436 +
437 + if (__DEV__) {
438 + if (!hasWarnedAboutDeprecatedMockComponent) {
439 + hasWarnedAboutDeprecatedMockComponent = true;
440 + console.warn(
441 + 'ReactTestUtils.mockComponent() is deprecated. ' +
442 + 'Use shallow rendering or jest.mock() instead.\n\n' +
443 + 'See https://react.dev/link/test-utils-mock-component for more information.',
444 + );
445 + }
446 + }
447 +
448 + mockTagName = mockTagName || module.mockTagName || 'div';
449 +
450 + module.prototype.render.mockImplementation(function () {
451 + return React.createElement(mockTagName, null, this.props.children);
452 + });
453 +
454 + return this;
455 +}
456 +
457 +function nativeTouchData(x, y) {
458 + if (disableDOMTestUtils) {
459 + throw new Error(
460 + '`nativeTouchData` was removed from `react-dom/test-utils`. ' +
461 + 'See https://react.dev/warnings/react-dom-test-utils for more info.',
462 + );
463 + }
464 +
465 + return {
466 + touches: [{pageX: x, pageY: y}],
467 + };
468 +}
469 +
470 +// Start of inline: the below functions were inlined from
471 +// EventPropagator.js, as they deviated from ReactDOM's newer
472 +// implementations.
473 +
474 +let hasError: boolean = false;
475 +let caughtError: mixed = null;
476 +
477 +/**
478 + * Dispatch the event to the listener.
479 + * @param {SyntheticEvent} event SyntheticEvent to handle
480 + * @param {function} listener Application-level callback
481 + * @param {*} inst Internal component instance
482 + */
483 +function executeDispatch(event, listener, inst) {
484 + event.currentTarget = getNodeFromInstance(inst);
485 + try {
486 + listener(event);
487 + } catch (error) {
488 + if (!hasError) {
489 + hasError = true;
490 + caughtError = error;
491 + }
492 + }
493 + event.currentTarget = null;
494 +}
495 +
496 +/**
497 + * Standard/simple iteration through an event's collected dispatches.
498 + */
499 +function executeDispatchesInOrder(event) {
500 + const dispatchListeners = event._dispatchListeners;
501 + const dispatchInstances = event._dispatchInstances;
502 + if (isArray(dispatchListeners)) {
503 + for (let i = 0; i < dispatchListeners.length; i++) {
504 + if (event.isPropagationStopped()) {
505 + break;
506 + }
507 + // Listeners and Instances are two parallel arrays that are always in sync.
508 + executeDispatch(event, dispatchListeners[i], dispatchInstances[i]);
509 + }
510 + } else if (dispatchListeners) {
511 + executeDispatch(event, dispatchListeners, dispatchInstances);
512 + }
513 + event._dispatchListeners = null;
514 + event._dispatchInstances = null;
515 +}
516 +
517 +/**
518 + * Dispatches an event and releases it back into the pool, unless persistent.
519 + *
520 + * @param {?object} event Synthetic event to be dispatched.
521 + * @private
522 + */
523 +function executeDispatchesAndRelease(event /* ReactSyntheticEvent */) {
524 + if (event) {
525 + executeDispatchesInOrder(event);
526 +
527 + if (!event.isPersistent()) {
528 + event.constructor.release(event);
529 + }
530 + }
531 +}
532 +
533 +function isInteractive(tag) {
534 + return (
535 + tag === 'button' ||
536 + tag === 'input' ||
537 + tag === 'select' ||
538 + tag === 'textarea'
539 + );
540 +}
541 +
542 +function getParent(inst) {
543 + do {
544 + inst = inst.return;
545 + // TODO: If this is a HostRoot we might want to bail out.
546 + // That is depending on if we want nested subtrees (layers) to bubble
547 + // events to their parent. We could also go through parentNode on the
548 + // host node but that wouldn't work for React Native and doesn't let us
549 + // do the portal feature.
550 + } while (inst && inst.tag !== HostComponent && inst.tag !== HostSingleton);
551 + if (inst) {
552 + return inst;
553 + }
554 + return null;
555 +}
556 +
557 +/**
558 + * Simulates the traversal of a two-phase, capture/bubble event dispatch.
559 + */
560 +export function traverseTwoPhase(inst, fn, arg) {
561 + const path = [];
562 + while (inst) {
563 + path.push(inst);
564 + inst = getParent(inst);
565 + }
566 + let i;
567 + for (i = path.length; i-- > 0; ) {
568 + fn(path[i], 'captured', arg);
569 + }
570 + for (i = 0; i < path.length; i++) {
571 + fn(path[i], 'bubbled', arg);
572 + }
573 +}
574 +
575 +function shouldPreventMouseEvent(name, type, props) {
576 + switch (name) {
577 + case 'onClick':
578 + case 'onClickCapture':
579 + case 'onDoubleClick':
580 + case 'onDoubleClickCapture':
581 + case 'onMouseDown':
582 + case 'onMouseDownCapture':
583 + case 'onMouseMove':
584 + case 'onMouseMoveCapture':
585 + case 'onMouseUp':
586 + case 'onMouseUpCapture':
587 + case 'onMouseEnter':
588 + return !!(props.disabled && isInteractive(type));
589 + default:
590 + return false;
591 + }
592 +}
593 +
594 +/**
595 + * @param {object} inst The instance, which is the source of events.
596 + * @param {string} registrationName Name of listener (e.g. `onClick`).
597 + * @return {?function} The stored callback.
598 + */
599 +function getListener(inst /* Fiber */, registrationName: string) {
600 + // TODO: shouldPreventMouseEvent is DOM-specific and definitely should not
601 + // live here; needs to be moved to a better place soon
602 + const stateNode = inst.stateNode;
603 + if (!stateNode) {
604 + // Work in progress (ex: onload events in incremental mode).
605 + return null;
606 + }
607 + const props = getFiberCurrentPropsFromNode(stateNode);
608 + if (!props) {
609 + // Work in progress.
610 + return null;
611 + }
612 + const listener = props[registrationName];
613 + if (shouldPreventMouseEvent(registrationName, inst.type, props)) {
614 + return null;
615 + }
616 +
617 + if (listener && typeof listener !== 'function') {
618 + throw new Error(
619 + `Expected \`${registrationName}\` listener to be a function, instead got a value of \`${typeof listener}\` type.`,
620 + );
621 + }
622 +
623 + return listener;
624 +}
625 +
626 +function listenerAtPhase(inst, event, propagationPhase: PropagationPhases) {
627 + let registrationName = event._reactName;
628 + if (propagationPhase === 'captured') {
629 + registrationName += 'Capture';
630 + }
631 + return getListener(inst, registrationName);
632 +}
633 +
634 +function accumulateDispatches(inst, ignoredDirection, event) {
635 + if (inst && event && event._reactName) {
636 + const registrationName = event._reactName;
637 + const listener = getListener(inst, registrationName);
638 + if (listener) {
639 + if (event._dispatchListeners == null) {
640 + event._dispatchListeners = [];
641 + }
642 + if (event._dispatchInstances == null) {
643 + event._dispatchInstances = [];
644 + }
645 + event._dispatchListeners.push(listener);
646 + event._dispatchInstances.push(inst);
647 + }
648 + }
649 +}
650 +
651 +function accumulateDirectionalDispatches(inst, phase, event) {
652 + if (__DEV__) {
653 + if (!inst) {
654 + console.error('Dispatching inst must not be null');
655 + }
656 + }
657 + const listener = listenerAtPhase(inst, event, phase);
658 + if (listener) {
659 + if (event._dispatchListeners == null) {
660 + event._dispatchListeners = [];
661 + }
662 + if (event._dispatchInstances == null) {
663 + event._dispatchInstances = [];
664 + }
665 + event._dispatchListeners.push(listener);
666 + event._dispatchInstances.push(inst);
667 + }
668 +}
669 +
670 +function accumulateDirectDispatchesSingle(event) {
671 + if (event && event._reactName) {
672 + accumulateDispatches(event._targetInst, null, event);
673 + }
674 +}
675 +
676 +function accumulateTwoPhaseDispatchesSingle(event) {
677 + if (event && event._reactName) {
678 + traverseTwoPhase(event._targetInst, accumulateDirectionalDispatches, event);
679 + }
680 +}
681 +
682 +// End of inline
683 +
684 +const Simulate = {};
685 +
686 +const directDispatchEventTypes = new Set([
687 + 'mouseEnter',
688 + 'mouseLeave',
689 + 'pointerEnter',
690 + 'pointerLeave',
691 +]);
692 +
693 +/**
694 + * Exports:
695 + *
696 + * - `Simulate.click(Element)`
697 + * - `Simulate.mouseMove(Element)`
698 + * - `Simulate.change(Element)`
699 + * - ... (All keys from event plugin `eventTypes` objects)
700 + */
701 +function makeSimulator(eventType) {
702 + return function (domNode, eventData) {
703 + if (disableDOMTestUtils) {
704 + throw new Error(
705 + '`Simulate` was removed from `react-dom/test-utils`. ' +
706 + 'See https://react.dev/warnings/react-dom-test-utils for more info.',
707 + );
708 + }
709 +
710 + if (React.isValidElement(domNode)) {
711 + throw new Error(
712 + 'TestUtils.Simulate expected a DOM node as the first argument but received ' +
713 + 'a React element. Pass the DOM node you wish to simulate the event on instead. ' +
714 + 'Note that TestUtils.Simulate will not work if you are using shallow rendering.',
715 + );
716 + }
717 +
718 + if (isCompositeComponent(domNode)) {
719 + throw new Error(
720 + 'TestUtils.Simulate expected a DOM node as the first argument but received ' +
721 + 'a component instance. Pass the DOM node you wish to simulate the event on instead.',
722 + );
723 + }
724 +
725 + const reactName = 'on' + eventType[0].toUpperCase() + eventType.slice(1);
726 + const fakeNativeEvent = new Event();
727 + fakeNativeEvent.target = domNode;
728 + fakeNativeEvent.type = eventType.toLowerCase();
729 +
730 + const targetInst = getInstanceFromNode(domNode);
731 + const event = new SyntheticEvent(
732 + reactName,
733 + fakeNativeEvent.type,
734 + targetInst,
735 + fakeNativeEvent,
736 + domNode,
737 + );
738 +
739 + // Since we aren't using pooling, always persist the event. This will make
740 + // sure it's marked and won't warn when setting additional properties.
741 + event.persist();
742 + assign(event, eventData);
743 +
744 + if (directDispatchEventTypes.has(eventType)) {
745 + accumulateDirectDispatchesSingle(event);
746 + } else {
747 + accumulateTwoPhaseDispatchesSingle(event);
748 + }
749 +
750 + ReactDOM.unstable_batchedUpdates(function () {
751 + // Normally extractEvent enqueues a state restore, but we'll just always
752 + // do that since we're by-passing it here.
753 + enqueueStateRestore(domNode);
754 + executeDispatchesAndRelease(event);
755 + if (hasError) {
756 + const error = caughtError;
757 + hasError = false;
758 + caughtError = null;
759 + throw error;
760 + }
761 + });
762 + restoreStateIfNeeded();
763 + };
764 +}
765 +
766 +// A one-time snapshot with no plans to update. We'll probably want to deprecate Simulate API.
767 +const simulatedEventTypes = [
768 + 'blur',
769 + 'cancel',
770 + 'click',
771 + 'close',
772 + 'contextMenu',
773 + 'copy',
774 + 'cut',
775 + 'auxClick',
776 + 'doubleClick',
777 + 'dragEnd',
778 + 'dragStart',
779 + 'drop',
780 + 'focus',
781 + 'input',
782 + 'invalid',
783 + 'keyDown',
784 + 'keyPress',
785 + 'keyUp',
786 + 'mouseDown',
787 + 'mouseUp',
788 + 'paste',
789 + 'pause',
790 + 'play',
791 + 'pointerCancel',
792 + 'pointerDown',
793 + 'pointerUp',
794 + 'rateChange',
795 + 'reset',
796 + 'resize',
797 + 'seeked',
798 + 'submit',
799 + 'touchCancel',
800 + 'touchEnd',
801 + 'touchStart',
802 + 'volumeChange',
803 + 'drag',
804 + 'dragEnter',
805 + 'dragExit',
806 + 'dragLeave',
807 + 'dragOver',
808 + 'mouseMove',
809 + 'mouseOut',
810 + 'mouseOver',
811 + 'pointerMove',
812 + 'pointerOut',
813 + 'pointerOver',
814 + 'scroll',
815 + 'toggle',
816 + 'touchMove',
817 + 'wheel',
818 + 'abort',
819 + 'animationEnd',
820 + 'animationIteration',
821 + 'animationStart',
822 + 'canPlay',
823 + 'canPlayThrough',
824 + 'durationChange',
825 + 'emptied',
826 + 'encrypted',
827 + 'ended',
828 + 'error',
829 + 'gotPointerCapture',
830 + 'load',
831 + 'loadedData',
832 + 'loadedMetadata',
833 + 'loadStart',
834 + 'lostPointerCapture',
835 + 'playing',
836 + 'progress',
837 + 'seeking',
838 + 'stalled',
839 + 'suspend',
840 + 'timeUpdate',
841 + 'transitionEnd',
842 + 'waiting',
843 + 'mouseEnter',
844 + 'mouseLeave',
845 + 'pointerEnter',
846 + 'pointerLeave',
847 + 'change',
848 + 'select',
849 + 'beforeInput',
850 + 'compositionEnd',
851 + 'compositionStart',
852 + 'compositionUpdate',
853 +];
854 +function buildSimulators() {
855 + simulatedEventTypes.forEach(eventType => {
856 + Simulate[eventType] = makeSimulator(eventType);
857 + });
858 +}
859 +buildSimulators();
860 +
861 +export {
862 + renderIntoDocument,
863 + isElement,
864 + isElementOfType,
865 + isDOMComponent,
866 + isDOMComponentElement,
867 + isCompositeComponent,
868 + isCompositeComponentWithType,
869 + findAllInRenderedTree,
870 + scryRenderedDOMComponentsWithClass,
871 + findRenderedDOMComponentWithClass,
872 + scryRenderedDOMComponentsWithTag,
873 + findRenderedDOMComponentWithTag,
874 + scryRenderedComponentsWithType,
875 + findRenderedComponentWithType,
876 + mockComponent,
877 + nativeTouchData,
878 + Simulate,
879 + act,
880 +};
packages/react-dom/test-utils.fb.js new
+10
@@ -0,0 +1,10 @@
1 +/**
2 + * Copyright (c) Meta Platforms, Inc. and affiliates.
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 + * @flow
8 + */
9 +
10 +export * from './src/test-utils/ReactTestUtilsFB';
scripts/shared/inlinedHostConfigs.js
+2
@@ -15,6 +15,7 @@ module.exports = [
15 'react-dom/unstable_testing',
16 'react-dom/src/server/react-dom-server.node.js',
17 'react-dom/static.node',
18 + 'react-dom/test-utils',
19 'react-dom/server-rendering-stub',
20 'react-dom/unstable_server-external-runtime',
21 'react-server-dom-webpack/server.node.unbundled',
@@ -29,6 +30,7 @@ module.exports = [
30 'react-dom/server.node',
31 'react-dom/static',
32 'react-dom/static.node',
33 + 'react-dom/test-utils',
34 'react-dom/src/server/react-dom-server.node',
35 'react-dom/src/server/ReactDOMFizzServerNode.js', // react-dom/server.node
36 'react-dom/src/server/ReactDOMFizzStaticNode.js',