diff --git a/packages/react-dom/src/__tests__/ReactTestUtils-test.js b/packages/react-dom/src/__tests__/ReactTestUtils-test.js
index fcc332737ebcb..3bd2b5d59aa83 100644
--- a/packages/react-dom/src/__tests__/ReactTestUtils-test.js
+++ b/packages/react-dom/src/__tests__/ReactTestUtils-test.js
@@ -9,24 +9,543 @@
'use strict';
+import * as React from 'react';
+import * as ReactDOM from 'react-dom';
+import * as ReactDOMServer from 'react-dom/server';
import * as ReactTestUtils from 'react-dom/test-utils';
+function getTestDocument(markup) {
+ const doc = document.implementation.createHTMLDocument('');
+ doc.open();
+ doc.write(
+ markup ||
+ '
(CHILD = current)}
+ onMouseEnter={() => {
+ recordID(CHILD);
+ }}
+ />
+ );
+ }
+
+ class ChildWrapper extends React.PureComponent {
+ render() {
+ return
;
+ }
+ }
+
+ ReactTestUtils.renderIntoDocument(
+
,
+ );
+
+ ReactTestUtils.Simulate.mouseEnter(CHILD);
+ expect(idCallOrder).toEqual([CHILD]);
+ });
+
+ // @gate disableDOMTestUtils
+ it('throws with a message recommending the relevant React Testing Library API', async () => {
+ expect(ReactTestUtils.Simulate.click).toThrowError(
+ '`Simulate` was removed from `react-dom/test-utils`. ' +
+ 'For testing events, we recommend `fireEvent.click` from `@testing-library/react` instead: https://testing-library.com/docs/dom-testing-library/api-events/.',
+ );
+ });
+ });
+
+ // @gate !disableDOMTestUtils
+ it('should call setState callback with no arguments', () => {
+ let mockArgs;
+ class Component extends React.Component {
+ componentDidMount() {
+ this.setState({}, (...args) => (mockArgs = args));
+ }
+ render() {
+ return false;
+ }
+ }
+
+ ReactTestUtils.renderIntoDocument(
);
+ expect(mockArgs.length).toEqual(0);
+ });
+
+ // @gate !disableDOMTestUtils
+ it('should find rendered component with type in document', () => {
+ class MyComponent extends React.Component {
+ render() {
+ return true;
+ }
+ }
+
+ const instance = ReactTestUtils.renderIntoDocument(
);
+ const renderedComponentType = ReactTestUtils.findRenderedComponentWithType(
+ instance,
+ MyComponent,
+ );
+
+ expect(renderedComponentType).toBe(instance);
});
+ // @gate disableDOMTestUtils
it('throws on every removed function with a special message', async () => {
expect(ReactTestUtils.isDOMComponent).toThrowError(
'`isDOMComponent` was removed from `react-dom/test-utils`. ' +
'For testing React, we recommend React Testing Library instead: https://testing-library.com/docs/react-testing-library/intro.',
);
});
-
- it('Simulate throws with a message recommending the relevant React Testing Library API', async () => {
- expect(ReactTestUtils.Simulate.click).toThrowError(
- '`Simulate` was removed from `react-dom/test-utils`. ' +
- 'For testing events, we recommend `fireEvent.click` from `@testing-library/react` instead: https://testing-library.com/docs/dom-testing-library/api-events/.',
- );
- });
});
diff --git a/packages/react-dom/src/__tests__/__snapshots__/ReactTestUtils-test.js.snap b/packages/react-dom/src/__tests__/__snapshots__/ReactTestUtils-test.js.snap
new file mode 100644
index 0000000000000..8dda81dcae42b
--- /dev/null
+++ b/packages/react-dom/src/__tests__/__snapshots__/ReactTestUtils-test.js.snap
@@ -0,0 +1,91 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`ReactTestUtils Simulate should have locally attached media events 1`] = `
+[
+ "abort",
+ "animationEnd",
+ "animationIteration",
+ "animationStart",
+ "auxClick",
+ "beforeInput",
+ "blur",
+ "canPlay",
+ "canPlayThrough",
+ "cancel",
+ "change",
+ "click",
+ "close",
+ "compositionEnd",
+ "compositionStart",
+ "compositionUpdate",
+ "contextMenu",
+ "copy",
+ "cut",
+ "doubleClick",
+ "drag",
+ "dragEnd",
+ "dragEnter",
+ "dragExit",
+ "dragLeave",
+ "dragOver",
+ "dragStart",
+ "drop",
+ "durationChange",
+ "emptied",
+ "encrypted",
+ "ended",
+ "error",
+ "focus",
+ "gotPointerCapture",
+ "input",
+ "invalid",
+ "keyDown",
+ "keyPress",
+ "keyUp",
+ "load",
+ "loadStart",
+ "loadedData",
+ "loadedMetadata",
+ "lostPointerCapture",
+ "mouseDown",
+ "mouseEnter",
+ "mouseLeave",
+ "mouseMove",
+ "mouseOut",
+ "mouseOver",
+ "mouseUp",
+ "paste",
+ "pause",
+ "play",
+ "playing",
+ "pointerCancel",
+ "pointerDown",
+ "pointerEnter",
+ "pointerLeave",
+ "pointerMove",
+ "pointerOut",
+ "pointerOver",
+ "pointerUp",
+ "progress",
+ "rateChange",
+ "reset",
+ "resize",
+ "scroll",
+ "seeked",
+ "seeking",
+ "select",
+ "stalled",
+ "submit",
+ "suspend",
+ "timeUpdate",
+ "toggle",
+ "touchCancel",
+ "touchEnd",
+ "touchMove",
+ "touchStart",
+ "transitionEnd",
+ "volumeChange",
+ "waiting",
+ "wheel",
+]
+`;
diff --git a/packages/react-dom/src/test-utils/ReactTestUtils.js b/packages/react-dom/src/test-utils/ReactTestUtils.js
index e6f21c1716208..da5adc9d20a9b 100644
--- a/packages/react-dom/src/test-utils/ReactTestUtils.js
+++ b/packages/react-dom/src/test-utils/ReactTestUtils.js
@@ -3,58 +3,767 @@
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
+ *
+ * @noflow
*/
import * as React from 'react';
+import * as ReactDOM from 'react-dom';
+import {findCurrentFiberUsingSlowPath} from 'react-reconciler/src/ReactFiberTreeReflection';
+import {get as getInstance} from 'shared/ReactInstanceMap';
+import {
+ ClassComponent,
+ FunctionComponent,
+ HostComponent,
+ HostHoistable,
+ HostSingleton,
+ HostText,
+} from 'react-reconciler/src/ReactWorkTags';
+import {SyntheticEvent} from 'react-dom-bindings/src/events/SyntheticEvent';
+import {ELEMENT_NODE} from 'react-dom-bindings/src/client/HTMLNodeType';
+import {disableDOMTestUtils, enableFloat} from 'shared/ReactFeatureFlags';
+import assign from 'shared/assign';
+import isArray from 'shared/isArray';
+
+// Keep in sync with ReactDOM.js:
+const SecretInternals =
+ ReactDOM.__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED;
+const EventInternals = SecretInternals.Events;
+const getInstanceFromNode = EventInternals[0];
+const getNodeFromInstance = EventInternals[1];
+const getFiberCurrentPropsFromNode = EventInternals[2];
+const enqueueStateRestore = EventInternals[3];
+const restoreStateIfNeeded = EventInternals[4];
// TODO: Add a warning if this API is accessed with advice to switch to
// importing directly from the React package instead.
const act = React.act;
-function makeRemovedFunction(name) {
- return function () {
+function Event(suffix) {}
+
+let hasWarnedAboutDeprecatedMockComponent = false;
+
+/**
+ * @class ReactTestUtils
+ */
+
+function findAllInRenderedFiberTreeInternal(fiber, test) {
+ if (!fiber) {
+ return [];
+ }
+ const currentParent = findCurrentFiberUsingSlowPath(fiber);
+ if (!currentParent) {
+ return [];
+ }
+ let node = currentParent;
+ const ret = [];
+ while (true) {
+ if (
+ node.tag === HostComponent ||
+ node.tag === HostText ||
+ node.tag === ClassComponent ||
+ node.tag === FunctionComponent ||
+ (enableFloat ? node.tag === HostHoistable : false) ||
+ node.tag === HostSingleton
+ ) {
+ const publicInst = node.stateNode;
+ if (test(publicInst)) {
+ ret.push(publicInst);
+ }
+ }
+ if (node.child) {
+ node.child.return = node;
+ node = node.child;
+ continue;
+ }
+ if (node === currentParent) {
+ return ret;
+ }
+ while (!node.sibling) {
+ if (!node.return || node.return === currentParent) {
+ return ret;
+ }
+ node = node.return;
+ }
+ node.sibling.return = node.return;
+ node = node.sibling;
+ }
+}
+
+function validateClassInstance(inst, methodName) {
+ if (!inst) {
+ // This is probably too relaxed but it's existing behavior.
+ return;
+ }
+ if (getInstance(inst)) {
+ // This is a public instance indeed.
+ return;
+ }
+ let received;
+ const stringified = String(inst);
+ if (isArray(inst)) {
+ received = 'an array';
+ } else if (inst && inst.nodeType === ELEMENT_NODE && inst.tagName) {
+ received = 'a DOM node';
+ } else if (stringified === '[object Object]') {
+ received = 'object with keys {' + Object.keys(inst).join(', ') + '}';
+ } else {
+ received = stringified;
+ }
+
+ throw new Error(
+ `The first argument must be a React class instance. ` +
+ `Instead received: ${received}.`,
+ );
+}
+
+/**
+ * Utilities for making it easy to test React components.
+ *
+ * See https://reactjs.org/docs/test-utils.html
+ *
+ * Todo: Support the entire DOM.scry query syntax. For now, these simple
+ * utilities will suffice for testing purposes.
+ * @lends ReactTestUtils
+ */
+function renderIntoDocument(element) {
+ if (disableDOMTestUtils) {
+ throw new Error(
+ '`renderIntoDocument` was removed from `react-dom/test-utils`. ' +
+ 'For testing React, we recommend React Testing Library instead: https://testing-library.com/docs/react-testing-library/intro.',
+ );
+ }
+
+ const div = document.createElement('div');
+ // None of our tests actually require attaching the container to the
+ // DOM, and doing so creates a mess that we rely on test isolation to
+ // clean up, so we're going to stop honoring the name of this method
+ // (and probably rename it eventually) if no problems arise.
+ // document.documentElement.appendChild(div);
+ return ReactDOM.render(element, div);
+}
+
+function isElement(element) {
+ if (disableDOMTestUtils) {
+ throw new Error(
+ '`isElement` was removed from `react-dom/test-utils`. ' +
+ 'For testing React, we recommend React Testing Library instead: https://testing-library.com/docs/react-testing-library/intro.',
+ );
+ }
+
+ return React.isValidElement(element);
+}
+
+function isElementOfType(inst, convenienceConstructor) {
+ if (disableDOMTestUtils) {
+ throw new Error(
+ '`isElementOfType` was removed from `react-dom/test-utils`. ' +
+ 'For testing React, we recommend React Testing Library instead: https://testing-library.com/docs/react-testing-library/intro.',
+ );
+ }
+
+ return React.isValidElement(inst) && inst.type === convenienceConstructor;
+}
+
+function isDOMComponent(inst) {
+ if (disableDOMTestUtils) {
+ throw new Error(
+ '`isDOMComponent` was removed from `react-dom/test-utils`. ' +
+ 'For testing React, we recommend React Testing Library instead: https://testing-library.com/docs/react-testing-library/intro.',
+ );
+ }
+
+ return !!(inst && inst.nodeType === ELEMENT_NODE && inst.tagName);
+}
+
+function isDOMComponentElement(inst) {
+ if (disableDOMTestUtils) {
+ throw new Error(
+ '`isDOMComponentElement` was removed from `react-dom/test-utils`. ' +
+ 'For testing React, we recommend React Testing Library instead: https://testing-library.com/docs/react-testing-library/intro.',
+ );
+ }
+
+ return !!(inst && React.isValidElement(inst) && !!inst.tagName);
+}
+
+function isCompositeComponent(inst) {
+ if (disableDOMTestUtils) {
+ throw new Error(
+ '`isCompositeComponent` was removed from `react-dom/test-utils`. ' +
+ 'For testing React, we recommend React Testing Library instead: https://testing-library.com/docs/react-testing-library/intro.',
+ );
+ }
+
+ if (isDOMComponent(inst)) {
+ // Accessing inst.setState warns; just return false as that'll be what
+ // this returns when we have DOM nodes as refs directly
+ return false;
+ }
+ return (
+ inst != null &&
+ typeof inst.render === 'function' &&
+ typeof inst.setState === 'function'
+ );
+}
+
+function isCompositeComponentWithType(inst, type) {
+ if (disableDOMTestUtils) {
+ throw new Error(
+ '`isCompositeComponentWithType` was removed from `react-dom/test-utils`. ' +
+ 'For testing React, we recommend React Testing Library instead: https://testing-library.com/docs/react-testing-library/intro.',
+ );
+ }
+
+ if (!isCompositeComponent(inst)) {
+ return false;
+ }
+ const internalInstance = getInstance(inst);
+ const constructor = internalInstance.type;
+ return constructor === type;
+}
+
+function findAllInRenderedTree(inst, test) {
+ if (disableDOMTestUtils) {
+ throw new Error(
+ '`findAllInRenderedTree` was removed from `react-dom/test-utils`. ' +
+ 'For testing React, we recommend React Testing Library instead: https://testing-library.com/docs/react-testing-library/intro.',
+ );
+ }
+
+ validateClassInstance(inst, 'findAllInRenderedTree');
+ if (!inst) {
+ return [];
+ }
+ const internalInstance = getInstance(inst);
+ return findAllInRenderedFiberTreeInternal(internalInstance, test);
+}
+
+/**
+ * Finds all instances of components in the rendered tree that are DOM
+ * components with the class name matching `className`.
+ * @return {array} an array of all the matches.
+ */
+function scryRenderedDOMComponentsWithClass(root, classNames) {
+ if (disableDOMTestUtils) {
+ throw new Error(
+ '`scryRenderedDOMComponentsWithClass` was removed from `react-dom/test-utils`. ' +
+ 'For testing React, we recommend React Testing Library instead: https://testing-library.com/docs/react-testing-library/intro.',
+ );
+ }
+
+ validateClassInstance(root, 'scryRenderedDOMComponentsWithClass');
+ return findAllInRenderedTree(root, function (inst) {
+ if (isDOMComponent(inst)) {
+ let className = inst.className;
+ if (typeof className !== 'string') {
+ // SVG, probably.
+ className = inst.getAttribute('class') || '';
+ }
+ const classList = className.split(/\s+/);
+
+ if (!isArray(classNames)) {
+ if (classNames === undefined) {
+ throw new Error(
+ 'TestUtils.scryRenderedDOMComponentsWithClass expects a ' +
+ 'className as a second argument.',
+ );
+ }
+
+ classNames = classNames.split(/\s+/);
+ }
+ return classNames.every(function (name) {
+ return classList.indexOf(name) !== -1;
+ });
+ }
+ return false;
+ });
+}
+
+/**
+ * Like scryRenderedDOMComponentsWithClass but expects there to be one result,
+ * and returns that one result, or throws exception if there is any other
+ * number of matches besides one.
+ * @return {!ReactDOMComponent} The one match.
+ */
+function findRenderedDOMComponentWithClass(root, className) {
+ if (disableDOMTestUtils) {
+ throw new Error(
+ '`findRenderedDOMComponentWithClass` was removed from `react-dom/test-utils`. ' +
+ 'For testing React, we recommend React Testing Library instead: https://testing-library.com/docs/react-testing-library/intro.',
+ );
+ }
+
+ validateClassInstance(root, 'findRenderedDOMComponentWithClass');
+ const all = scryRenderedDOMComponentsWithClass(root, className);
+ if (all.length !== 1) {
+ throw new Error(
+ 'Did not find exactly one match (found: ' +
+ all.length +
+ ') ' +
+ 'for class:' +
+ className,
+ );
+ }
+ return all[0];
+}
+
+/**
+ * Finds all instances of components in the rendered tree that are DOM
+ * components with the tag name matching `tagName`.
+ * @return {array} an array of all the matches.
+ */
+function scryRenderedDOMComponentsWithTag(root, tagName) {
+ if (disableDOMTestUtils) {
+ throw new Error(
+ '`scryRenderedDOMComponentsWithTag` was removed from `react-dom/test-utils`. ' +
+ 'For testing React, we recommend React Testing Library instead: https://testing-library.com/docs/react-testing-library/intro.',
+ );
+ }
+
+ validateClassInstance(root, 'scryRenderedDOMComponentsWithTag');
+ return findAllInRenderedTree(root, function (inst) {
+ return (
+ isDOMComponent(inst) &&
+ inst.tagName.toUpperCase() === tagName.toUpperCase()
+ );
+ });
+}
+
+/**
+ * Like scryRenderedDOMComponentsWithTag but expects there to be one result,
+ * and returns that one result, or throws exception if there is any other
+ * number of matches besides one.
+ * @return {!ReactDOMComponent} The one match.
+ */
+function findRenderedDOMComponentWithTag(root, tagName) {
+ if (disableDOMTestUtils) {
+ throw new Error(
+ '`findRenderedDOMComponentWithTag` was removed from `react-dom/test-utils`. ' +
+ 'For testing React, we recommend React Testing Library instead: https://testing-library.com/docs/react-testing-library/intro.',
+ );
+ }
+
+ validateClassInstance(root, 'findRenderedDOMComponentWithTag');
+ const all = scryRenderedDOMComponentsWithTag(root, tagName);
+ if (all.length !== 1) {
+ throw new Error(
+ 'Did not find exactly one match (found: ' +
+ all.length +
+ ') ' +
+ 'for tag:' +
+ tagName,
+ );
+ }
+ return all[0];
+}
+
+/**
+ * Finds all instances of components with type equal to `componentType`.
+ * @return {array} an array of all the matches.
+ */
+function scryRenderedComponentsWithType(root, componentType) {
+ if (disableDOMTestUtils) {
+ throw new Error(
+ '`scryRenderedComponentsWithType` was removed from `react-dom/test-utils`. ' +
+ 'For testing React, we recommend React Testing Library instead: https://testing-library.com/docs/react-testing-library/intro.',
+ );
+ }
+
+ validateClassInstance(root, 'scryRenderedComponentsWithType');
+ return findAllInRenderedTree(root, function (inst) {
+ return isCompositeComponentWithType(inst, componentType);
+ });
+}
+
+/**
+ * Same as `scryRenderedComponentsWithType` but expects there to be one result
+ * and returns that one result, or throws exception if there is any other
+ * number of matches besides one.
+ * @return {!ReactComponent} The one match.
+ */
+function findRenderedComponentWithType(root, componentType) {
+ if (disableDOMTestUtils) {
+ throw new Error(
+ '`findRenderedComponentWithType` was removed from `react-dom/test-utils`. ' +
+ 'For testing React, we recommend React Testing Library instead: https://testing-library.com/docs/react-testing-library/intro.',
+ );
+ }
+
+ validateClassInstance(root, 'findRenderedComponentWithType');
+ const all = scryRenderedComponentsWithType(root, componentType);
+ if (all.length !== 1) {
+ throw new Error(
+ 'Did not find exactly one match (found: ' +
+ all.length +
+ ') ' +
+ 'for componentType:' +
+ componentType,
+ );
+ }
+ return all[0];
+}
+
+/**
+ * Pass a mocked component module to this method to augment it with
+ * useful methods that allow it to be used as a dummy React component.
+ * Instead of rendering as usual, the component will become a simple
+ *
containing any provided children.
+ *
+ * @param {object} module the mock function object exported from a
+ * module that defines the component to be mocked
+ * @param {?string} mockTagName optional dummy root tag name to return
+ * from render method (overrides
+ * module.mockTagName if provided)
+ * @return {object} the ReactTestUtils object (for chaining)
+ */
+function mockComponent(module, mockTagName) {
+ if (disableDOMTestUtils) {
+ throw new Error(
+ '`mockComponent` was removed from `react-dom/test-utils`. ' +
+ 'For testing React, we recommend shallow rendering with Enzyme or React Testing Library instead: https://testing-library.com/docs/react-testing-library/intro.',
+ );
+ }
+
+ if (__DEV__) {
+ if (!hasWarnedAboutDeprecatedMockComponent) {
+ hasWarnedAboutDeprecatedMockComponent = true;
+ console.warn(
+ 'ReactTestUtils.mockComponent() is deprecated. ' +
+ 'Use shallow rendering or jest.mock() instead.\n\n' +
+ 'See https://react.dev/link/test-utils-mock-component for more information.',
+ );
+ }
+ }
+
+ mockTagName = mockTagName || module.mockTagName || 'div';
+
+ module.prototype.render.mockImplementation(function () {
+ return React.createElement(mockTagName, null, this.props.children);
+ });
+
+ return this;
+}
+
+function nativeTouchData(x, y) {
+ if (disableDOMTestUtils) {
+ throw new Error(
+ '`nativeTouchData` was removed from `react-dom/test-utils`. ' +
+ 'For testing React, we recommend React Testing Library instead: https://testing-library.com/docs/react-testing-library/intro.',
+ );
+ }
+
+ return {
+ touches: [{pageX: x, pageY: y}],
+ };
+}
+
+// Start of inline: the below functions were inlined from
+// EventPropagator.js, as they deviated from ReactDOM's newer
+// implementations.
+
+let hasError: boolean = false;
+let caughtError: mixed = null;
+
+/**
+ * Dispatch the event to the listener.
+ * @param {SyntheticEvent} event SyntheticEvent to handle
+ * @param {function} listener Application-level callback
+ * @param {*} inst Internal component instance
+ */
+function executeDispatch(event, listener, inst) {
+ event.currentTarget = getNodeFromInstance(inst);
+ try {
+ listener(event);
+ } catch (error) {
+ if (!hasError) {
+ hasError = true;
+ caughtError = error;
+ }
+ }
+ event.currentTarget = null;
+}
+
+/**
+ * Standard/simple iteration through an event's collected dispatches.
+ */
+function executeDispatchesInOrder(event) {
+ const dispatchListeners = event._dispatchListeners;
+ const dispatchInstances = event._dispatchInstances;
+ if (isArray(dispatchListeners)) {
+ for (let i = 0; i < dispatchListeners.length; i++) {
+ if (event.isPropagationStopped()) {
+ break;
+ }
+ // Listeners and Instances are two parallel arrays that are always in sync.
+ executeDispatch(event, dispatchListeners[i], dispatchInstances[i]);
+ }
+ } else if (dispatchListeners) {
+ executeDispatch(event, dispatchListeners, dispatchInstances);
+ }
+ event._dispatchListeners = null;
+ event._dispatchInstances = null;
+}
+
+/**
+ * Dispatches an event and releases it back into the pool, unless persistent.
+ *
+ * @param {?object} event Synthetic event to be dispatched.
+ * @private
+ */
+function executeDispatchesAndRelease(event /* ReactSyntheticEvent */) {
+ if (event) {
+ executeDispatchesInOrder(event);
+
+ if (!event.isPersistent()) {
+ event.constructor.release(event);
+ }
+ }
+}
+
+function isInteractive(tag) {
+ return (
+ tag === 'button' ||
+ tag === 'input' ||
+ tag === 'select' ||
+ tag === 'textarea'
+ );
+}
+
+function getParent(inst) {
+ do {
+ inst = inst.return;
+ // TODO: If this is a HostRoot we might want to bail out.
+ // That is depending on if we want nested subtrees (layers) to bubble
+ // events to their parent. We could also go through parentNode on the
+ // host node but that wouldn't work for React Native and doesn't let us
+ // do the portal feature.
+ } while (inst && inst.tag !== HostComponent && inst.tag !== HostSingleton);
+ if (inst) {
+ return inst;
+ }
+ return null;
+}
+
+/**
+ * Simulates the traversal of a two-phase, capture/bubble event dispatch.
+ */
+export function traverseTwoPhase(inst, fn, arg) {
+ if (disableDOMTestUtils) {
throw new Error(
- '`' +
- name +
- '` was removed from `react-dom/test-utils`. ' +
+ '`traverseTwoPhase` was removed from `react-dom/test-utils`. ' +
'For testing React, we recommend React Testing Library instead: https://testing-library.com/docs/react-testing-library/intro.',
);
+ }
+
+ const path = [];
+ while (inst) {
+ path.push(inst);
+ inst = getParent(inst);
+ }
+ let i;
+ for (i = path.length; i-- > 0; ) {
+ fn(path[i], 'captured', arg);
+ }
+ for (i = 0; i < path.length; i++) {
+ fn(path[i], 'bubbled', arg);
+ }
+}
+
+function shouldPreventMouseEvent(name, type, props) {
+ switch (name) {
+ case 'onClick':
+ case 'onClickCapture':
+ case 'onDoubleClick':
+ case 'onDoubleClickCapture':
+ case 'onMouseDown':
+ case 'onMouseDownCapture':
+ case 'onMouseMove':
+ case 'onMouseMoveCapture':
+ case 'onMouseUp':
+ case 'onMouseUpCapture':
+ case 'onMouseEnter':
+ return !!(props.disabled && isInteractive(type));
+ default:
+ return false;
+ }
+}
+
+/**
+ * @param {object} inst The instance, which is the source of events.
+ * @param {string} registrationName Name of listener (e.g. `onClick`).
+ * @return {?function} The stored callback.
+ */
+function getListener(inst /* Fiber */, registrationName: string) {
+ // TODO: shouldPreventMouseEvent is DOM-specific and definitely should not
+ // live here; needs to be moved to a better place soon
+ const stateNode = inst.stateNode;
+ if (!stateNode) {
+ // Work in progress (ex: onload events in incremental mode).
+ return null;
+ }
+ const props = getFiberCurrentPropsFromNode(stateNode);
+ if (!props) {
+ // Work in progress.
+ return null;
+ }
+ const listener = props[registrationName];
+ if (shouldPreventMouseEvent(registrationName, inst.type, props)) {
+ return null;
+ }
+
+ if (listener && typeof listener !== 'function') {
+ throw new Error(
+ `Expected \`${registrationName}\` listener to be a function, instead got a value of \`${typeof listener}\` type.`,
+ );
+ }
+
+ return listener;
+}
+
+function listenerAtPhase(inst, event, propagationPhase: PropagationPhases) {
+ let registrationName = event._reactName;
+ if (propagationPhase === 'captured') {
+ registrationName += 'Capture';
+ }
+ return getListener(inst, registrationName);
+}
+
+function accumulateDispatches(inst, ignoredDirection, event) {
+ if (inst && event && event._reactName) {
+ const registrationName = event._reactName;
+ const listener = getListener(inst, registrationName);
+ if (listener) {
+ if (event._dispatchListeners == null) {
+ event._dispatchListeners = [];
+ }
+ if (event._dispatchInstances == null) {
+ event._dispatchInstances = [];
+ }
+ event._dispatchListeners.push(listener);
+ event._dispatchInstances.push(inst);
+ }
+ }
+}
+
+function accumulateDirectionalDispatches(inst, phase, event) {
+ if (__DEV__) {
+ if (!inst) {
+ console.error('Dispatching inst must not be null');
+ }
+ }
+ const listener = listenerAtPhase(inst, event, phase);
+ if (listener) {
+ if (event._dispatchListeners == null) {
+ event._dispatchListeners = [];
+ }
+ if (event._dispatchInstances == null) {
+ event._dispatchInstances = [];
+ }
+ event._dispatchListeners.push(listener);
+ event._dispatchInstances.push(inst);
+ }
+}
+
+function accumulateDirectDispatchesSingle(event) {
+ if (event && event._reactName) {
+ accumulateDispatches(event._targetInst, null, event);
+ }
+}
+
+function accumulateTwoPhaseDispatchesSingle(event) {
+ if (event && event._reactName) {
+ traverseTwoPhase(event._targetInst, accumulateDirectionalDispatches, event);
+ }
+}
+
+// End of inline
+
+const Simulate = {};
+
+const directDispatchEventTypes = new Set([
+ 'mouseEnter',
+ 'mouseLeave',
+ 'pointerEnter',
+ 'pointerLeave',
+]);
+
+/**
+ * Exports:
+ *
+ * - `Simulate.click(Element)`
+ * - `Simulate.mouseMove(Element)`
+ * - `Simulate.change(Element)`
+ * - ... (All keys from event plugin `eventTypes` objects)
+ */
+function makeSimulator(eventType) {
+ return function (domNode, eventData) {
+ if (disableDOMTestUtils) {
+ throw new Error(
+ '`Simulate` was removed from `react-dom/test-utils`. ' +
+ 'For testing events, we recommend `fireEvent.' +
+ eventType +
+ '` from `@testing-library/react` instead: https://testing-library.com/docs/dom-testing-library/api-events/.',
+ );
+ }
+
+ if (React.isValidElement(domNode)) {
+ throw new Error(
+ 'TestUtils.Simulate expected a DOM node as the first argument but received ' +
+ 'a React element. Pass the DOM node you wish to simulate the event on instead. ' +
+ 'Note that TestUtils.Simulate will not work if you are using shallow rendering.',
+ );
+ }
+
+ if (isCompositeComponent(domNode)) {
+ throw new Error(
+ 'TestUtils.Simulate expected a DOM node as the first argument but received ' +
+ 'a component instance. Pass the DOM node you wish to simulate the event on instead.',
+ );
+ }
+
+ const reactName = 'on' + eventType[0].toUpperCase() + eventType.slice(1);
+ const fakeNativeEvent = new Event();
+ fakeNativeEvent.target = domNode;
+ fakeNativeEvent.type = eventType.toLowerCase();
+
+ const targetInst = getInstanceFromNode(domNode);
+ const event = new SyntheticEvent(
+ reactName,
+ fakeNativeEvent.type,
+ targetInst,
+ fakeNativeEvent,
+ domNode,
+ );
+
+ // Since we aren't using pooling, always persist the event. This will make
+ // sure it's marked and won't warn when setting additional properties.
+ event.persist();
+ assign(event, eventData);
+
+ if (directDispatchEventTypes.has(eventType)) {
+ accumulateDirectDispatchesSingle(event);
+ } else {
+ accumulateTwoPhaseDispatchesSingle(event);
+ }
+
+ ReactDOM.unstable_batchedUpdates(function () {
+ // Normally extractEvent enqueues a state restore, but we'll just always
+ // do that since we're by-passing it here.
+ enqueueStateRestore(domNode);
+ executeDispatchesAndRelease(event);
+ if (hasError) {
+ const error = caughtError;
+ hasError = false;
+ caughtError = null;
+ throw error;
+ }
+ });
+ restoreStateIfNeeded();
};
}
-const renderIntoDocument = makeRemovedFunction('renderIntoDocument');
-const isElement = makeRemovedFunction('isElement');
-const isElementOfType = makeRemovedFunction('isElementOfType');
-const isDOMComponent = makeRemovedFunction('isDOMComponent');
-const isDOMComponentElement = makeRemovedFunction('isDOMComponentElement');
-const isCompositeComponent = makeRemovedFunction('isCompositeComponent');
-const isCompositeComponentWithType = makeRemovedFunction(
- 'isCompositeComponentWithType',
-);
-const findAllInRenderedTree = makeRemovedFunction('findAllInRenderedTree');
-const scryRenderedDOMComponentsWithClass = makeRemovedFunction(
- 'scryRenderedDOMComponentsWithClass',
-);
-const findRenderedDOMComponentWithClass = makeRemovedFunction(
- 'findRenderedDOMComponentWithClass',
-);
-const scryRenderedDOMComponentsWithTag = makeRemovedFunction(
- 'scryRenderedDOMComponentsWithTag',
-);
-const findRenderedDOMComponentWithTag = makeRemovedFunction(
- 'findRenderedDOMComponentWithTag',
-);
-const scryRenderedComponentsWithType = makeRemovedFunction(
- 'scryRenderedComponentsWithType',
-);
-const findRenderedComponentWithType = makeRemovedFunction(
- 'findRenderedComponentWithType',
-);
-const mockComponent = makeRemovedFunction('mockComponent');
-const nativeTouchData = makeRemovedFunction('nativeTouchData');
-
-// Snapshot of events supported by Simulate before we removed it.
-// Do not add new events here since the new ones were never supported in the first place.
+// A one-time snapshot with no plans to update. We'll probably want to deprecate Simulate API.
const simulatedEventTypes = [
'blur',
'cancel',
@@ -142,23 +851,14 @@ const simulatedEventTypes = [
'compositionStart',
'compositionUpdate',
];
-
-const Simulate = {};
-
-simulatedEventTypes.forEach(eventType => {
- Simulate[eventType] = function () {
- throw new Error(
- '`Simulate` was removed from `react-dom/test-utils`. ' +
- 'For testing events, we recommend `fireEvent.' +
- eventType +
- '` from `@testing-library/react` instead: https://testing-library.com/docs/dom-testing-library/api-events/.',
- );
- };
-});
+function buildSimulators() {
+ simulatedEventTypes.forEach(eventType => {
+ Simulate[eventType] = makeSimulator(eventType);
+ });
+}
+buildSimulators();
export {
- act,
- // Removed APIs
renderIntoDocument,
isElement,
isElementOfType,
@@ -176,4 +876,5 @@ export {
mockComponent,
nativeTouchData,
Simulate,
+ act,
};
diff --git a/packages/shared/ReactFeatureFlags.js b/packages/shared/ReactFeatureFlags.js
index ff03d8849afe2..06e19cd59832a 100644
--- a/packages/shared/ReactFeatureFlags.js
+++ b/packages/shared/ReactFeatureFlags.js
@@ -198,6 +198,8 @@ export const enableReactTestRendererWarning = false;
// before removing them in stable in the next Major
export const disableLegacyMode = __NEXT_MAJOR__;
+export const disableDOMTestUtils = __NEXT_MAJOR__;
+
// HTML boolean attributes need a special PropertyInfoRecord.
// Between support of these attributes in browsers and React supporting them as
// boolean props library users can use them as `
`.
diff --git a/packages/shared/forks/ReactFeatureFlags.native-fb.js b/packages/shared/forks/ReactFeatureFlags.native-fb.js
index 93a8d66ce7da3..10a2f038e7192 100644
--- a/packages/shared/forks/ReactFeatureFlags.native-fb.js
+++ b/packages/shared/forks/ReactFeatureFlags.native-fb.js
@@ -102,6 +102,7 @@ export const disableStringRefs = false;
export const enableReactTestRendererWarning = false;
export const disableLegacyMode = false;
+export const disableDOMTestUtils = true;
export const enableBigIntSupport = false;
diff --git a/packages/shared/forks/ReactFeatureFlags.native-oss.js b/packages/shared/forks/ReactFeatureFlags.native-oss.js
index acc051edcc661..4d2892affe3f7 100644
--- a/packages/shared/forks/ReactFeatureFlags.native-oss.js
+++ b/packages/shared/forks/ReactFeatureFlags.native-oss.js
@@ -96,6 +96,7 @@ export const enableReactTestRendererWarning = false;
export const enableBigIntSupport = false;
export const disableLegacyMode = false;
+export const disableDOMTestUtils = true;
// Flow magic to verify the exports of this file match the original version.
((((null: any): ExportsType): FeatureFlagsType): ExportsType);
diff --git a/packages/shared/forks/ReactFeatureFlags.test-renderer.js b/packages/shared/forks/ReactFeatureFlags.test-renderer.js
index 366e475a4f57a..67921ccd88b7c 100644
--- a/packages/shared/forks/ReactFeatureFlags.test-renderer.js
+++ b/packages/shared/forks/ReactFeatureFlags.test-renderer.js
@@ -99,6 +99,7 @@ export const enableReactTestRendererWarning = false;
export const enableBigIntSupport = __NEXT_MAJOR__;
export const disableLegacyMode = __NEXT_MAJOR__;
export const disableLegacyContext = __NEXT_MAJOR__;
+export const disableDOMTestUtils = __NEXT_MAJOR__;
export const enableNewBooleanProps = __NEXT_MAJOR__;
// Flow magic to verify the exports of this file match the original version.
diff --git a/packages/shared/forks/ReactFeatureFlags.test-renderer.native.js b/packages/shared/forks/ReactFeatureFlags.test-renderer.native.js
index fd9531385971b..a377452f10cb3 100644
--- a/packages/shared/forks/ReactFeatureFlags.test-renderer.native.js
+++ b/packages/shared/forks/ReactFeatureFlags.test-renderer.native.js
@@ -91,6 +91,7 @@ export const disableStringRefs = false;
export const enableReactTestRendererWarning = false;
export const disableLegacyMode = false;
+export const disableDOMTestUtils = false;
export const enableBigIntSupport = false;
diff --git a/packages/shared/forks/ReactFeatureFlags.test-renderer.www.js b/packages/shared/forks/ReactFeatureFlags.test-renderer.www.js
index f6b6be8054609..9288935ce57d4 100644
--- a/packages/shared/forks/ReactFeatureFlags.test-renderer.www.js
+++ b/packages/shared/forks/ReactFeatureFlags.test-renderer.www.js
@@ -93,6 +93,7 @@ export const disableStringRefs = false;
export const enableReactTestRendererWarning = false;
export const disableLegacyMode = false;
+export const disableDOMTestUtils = false;
export const enableBigIntSupport = false;
diff --git a/packages/shared/forks/ReactFeatureFlags.www.js b/packages/shared/forks/ReactFeatureFlags.www.js
index d2364057ecadf..683c3a4ff8705 100644
--- a/packages/shared/forks/ReactFeatureFlags.www.js
+++ b/packages/shared/forks/ReactFeatureFlags.www.js
@@ -126,5 +126,7 @@ export const disableStringRefs = false;
export const disableLegacyMode = false;
+export const disableDOMTestUtils = false;
+
// Flow magic to verify the exports of this file match the original version.
((((null: any): ExportsType): FeatureFlagsType): ExportsType);