[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',