Clean up disableDOMTestUtils (#29610)

`disableDOMTestUtils` and the FB build `ReactTestUtilsFB` allowed us to
finish migrating internal callsites off of ReactTestUtils. Now that
usage is cleaned up, we can remove the flag, build artifact, and test
coverage for the deprecated utility methods.
This commit is contained in:
Jack Pope
2024-05-28 14:55:14 -04:00
committed by GitHub
parent 681a4aa810
commit 2787eebe52
11 changed files with 1 additions and 1640 deletions
-735
View File
@@ -1,735 +0,0 @@
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @emails react-core
*/
'use strict';
let React;
let ReactDOMClient;
let ReactDOMServer;
let ReactTestUtils;
let act;
function getTestDocument(markup) {
const doc = document.implementation.createHTMLDocument('');
doc.open();
doc.write(
markup ||
'<!doctype html><html><meta charset=utf-8><title>test doc</title>',
);
doc.close();
return doc;
}
describe('ReactTestUtils', () => {
beforeEach(() => {
React = require('react');
ReactDOMClient = require('react-dom/client');
ReactDOMServer = require('react-dom/server');
ReactTestUtils = require('react-dom/test-utils');
act = require('internal-test-utils').act;
});
// @gate !disableDOMTestUtils
it('Simulate should have locally attached media events', () => {
expect(Object.keys(ReactTestUtils.Simulate).sort()).toMatchInlineSnapshot(`
[
"abort",
"animationEnd",
"animationIteration",
"animationStart",
"auxClick",
"beforeInput",
"beforeToggle",
"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",
"transitionCancel",
"transitionEnd",
"transitionRun",
"transitionStart",
"volumeChange",
"waiting",
"wheel",
]
`);
});
// @gate !disableDOMTestUtils
it('gives Jest mocks a passthrough implementation with mockComponent()', async () => {
class MockedComponent extends React.Component {
render() {
throw new Error('Should not get here.');
}
}
// This is close enough to what a Jest mock would give us.
MockedComponent.prototype.render = jest.fn();
// Patch it up so it returns its children.
expect(() => ReactTestUtils.mockComponent(MockedComponent)).toWarnDev(
'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.',
{withoutStack: true},
);
// De-duplication check
ReactTestUtils.mockComponent(MockedComponent);
const container = document.createElement('div');
const root = ReactDOMClient.createRoot(container);
await act(() => {
root.render(<MockedComponent>Hello</MockedComponent>);
});
expect(container.textContent).toBe('Hello');
});
// @gate !disableDOMTestUtils
it('can scryRenderedComponentsWithType', async () => {
class Child extends React.Component {
render() {
return null;
}
}
class Wrapper extends React.Component {
render() {
return (
<div>
<Child />
</div>
);
}
}
const container = document.createElement('div');
const root = ReactDOMClient.createRoot(container);
let renderedComponent;
await act(() => {
root.render(<Wrapper ref={current => (renderedComponent = current)} />);
});
const scryResults = ReactTestUtils.scryRenderedComponentsWithType(
renderedComponent,
Child,
);
expect(scryResults.length).toBe(1);
});
// @gate !disableDOMTestUtils
it('can scryRenderedDOMComponentsWithClass with TextComponent', async () => {
class Wrapper extends React.Component {
render() {
return (
<div>
Hello <span>Jim</span>
</div>
);
}
}
const container = document.createElement('div');
const root = ReactDOMClient.createRoot(container);
let renderedComponent;
await act(() => {
root.render(<Wrapper ref={current => (renderedComponent = current)} />);
});
const scryResults = ReactTestUtils.scryRenderedDOMComponentsWithClass(
renderedComponent,
'NonExistentClass',
);
expect(scryResults.length).toBe(0);
});
// @gate !disableDOMTestUtils
it('can scryRenderedDOMComponentsWithClass with className contains \\n', async () => {
class Wrapper extends React.Component {
render() {
return (
<div>
Hello <span className={'x\ny'}>Jim</span>
</div>
);
}
}
const container = document.createElement('div');
const root = ReactDOMClient.createRoot(container);
let renderedComponent;
await act(() => {
root.render(<Wrapper ref={current => (renderedComponent = current)} />);
});
const scryResults = ReactTestUtils.scryRenderedDOMComponentsWithClass(
renderedComponent,
'x',
);
expect(scryResults.length).toBe(1);
});
// @gate !disableDOMTestUtils
it('can scryRenderedDOMComponentsWithClass with multiple classes', async () => {
class Wrapper extends React.Component {
render() {
return (
<div>
Hello <span className={'x y z'}>Jim</span>
</div>
);
}
}
const container = document.createElement('div');
const root = ReactDOMClient.createRoot(container);
let renderedComponent;
await act(() => {
root.render(<Wrapper ref={current => (renderedComponent = current)} />);
});
const scryResults1 = ReactTestUtils.scryRenderedDOMComponentsWithClass(
renderedComponent,
'x y',
);
expect(scryResults1.length).toBe(1);
const scryResults2 = ReactTestUtils.scryRenderedDOMComponentsWithClass(
renderedComponent,
'x z',
);
expect(scryResults2.length).toBe(1);
const scryResults3 = ReactTestUtils.scryRenderedDOMComponentsWithClass(
renderedComponent,
['x', 'y'],
);
expect(scryResults3.length).toBe(1);
expect(scryResults1[0]).toBe(scryResults2[0]);
expect(scryResults1[0]).toBe(scryResults3[0]);
const scryResults4 = ReactTestUtils.scryRenderedDOMComponentsWithClass(
renderedComponent,
['x', 'a'],
);
expect(scryResults4.length).toBe(0);
const scryResults5 = ReactTestUtils.scryRenderedDOMComponentsWithClass(
renderedComponent,
['x a'],
);
expect(scryResults5.length).toBe(0);
});
// @gate !disableDOMTestUtils
it('traverses children in the correct order', async () => {
class Wrapper extends React.Component {
render() {
return <div>{this.props.children}</div>;
}
}
const container = document.createElement('div');
const root = ReactDOMClient.createRoot(container);
await act(() => {
root.render(
<Wrapper>
{null}
<div>purple</div>
</Wrapper>,
);
});
let tree;
await act(() => {
root.render(
<Wrapper ref={current => (tree = current)}>
<div>orange</div>
<div>purple</div>
</Wrapper>,
);
});
const log = [];
ReactTestUtils.findAllInRenderedTree(tree, function (child) {
if (ReactTestUtils.isDOMComponent(child)) {
log.push(child.textContent);
}
});
// Should be document order, not mount order (which would be purple, orange)
expect(log).toEqual(['orangepurple', 'orange', 'purple']);
});
// @gate !disableDOMTestUtils
it('should support injected wrapper components as DOM components', async () => {
const injectedDOMComponents = [
'button',
'form',
'iframe',
'img',
'input',
'option',
'select',
'textarea',
];
// eslint-disable-next-line no-for-of-loops/no-for-of-loops
for (const type of injectedDOMComponents) {
const container = document.createElement('div');
const root = ReactDOMClient.createRoot(container);
let testComponent;
await act(() => {
root.render(
React.createElement(type, {
ref: current => (testComponent = current),
}),
);
});
expect(testComponent.tagName).toBe(type.toUpperCase());
expect(ReactTestUtils.isDOMComponent(testComponent)).toBe(true);
}
// Full-page components (html, head, body) can't be rendered into a div
// directly...
class Root extends React.Component {
htmlRef = React.createRef();
headRef = React.createRef();
bodyRef = React.createRef();
render() {
return (
<html ref={this.htmlRef}>
<head ref={this.headRef}>
<title>hello</title>
</head>
<body ref={this.bodyRef}>hello, world</body>
</html>
);
}
}
const markup = ReactDOMServer.renderToString(<Root />);
const testDocument = getTestDocument(markup);
let component;
await act(() => {
ReactDOMClient.hydrateRoot(
testDocument,
<Root ref={current => (component = current)} />,
);
});
expect(component.htmlRef.current.tagName).toBe('HTML');
expect(component.headRef.current.tagName).toBe('HEAD');
expect(component.bodyRef.current.tagName).toBe('BODY');
expect(ReactTestUtils.isDOMComponent(component.htmlRef.current)).toBe(true);
expect(ReactTestUtils.isDOMComponent(component.headRef.current)).toBe(true);
expect(ReactTestUtils.isDOMComponent(component.bodyRef.current)).toBe(true);
});
// @gate !disableDOMTestUtils
it('can scry with stateless components involved', async () => {
const Function = () => (
<div>
<hr />
</div>
);
class SomeComponent extends React.Component {
render() {
return (
<div>
<Function />
<hr />
</div>
);
}
}
const container = document.createElement('div');
const root = ReactDOMClient.createRoot(container);
let inst;
await act(() => {
root.render(<SomeComponent ref={current => (inst = current)} />);
});
const hrs = ReactTestUtils.scryRenderedDOMComponentsWithTag(inst, 'hr');
expect(hrs.length).toBe(2);
});
// @gate !disableDOMTestUtils
it('provides a clear error when passing invalid objects to scry', () => {
// This is probably too relaxed but it's existing behavior.
ReactTestUtils.findAllInRenderedTree(null, 'span');
ReactTestUtils.findAllInRenderedTree(undefined, 'span');
ReactTestUtils.findAllInRenderedTree('', 'span');
ReactTestUtils.findAllInRenderedTree(0, 'span');
ReactTestUtils.findAllInRenderedTree(false, 'span');
expect(() => {
ReactTestUtils.findAllInRenderedTree([], 'span');
}).toThrow(
'The first argument must be a React class instance. ' +
'Instead received: an array.',
);
expect(() => {
ReactTestUtils.scryRenderedDOMComponentsWithClass(10, 'button');
}).toThrow(
'The first argument must be a React class instance. ' +
'Instead received: 10.',
);
expect(() => {
ReactTestUtils.findRenderedDOMComponentWithClass('hello', 'button');
}).toThrow(
'The first argument must be a React class instance. ' +
'Instead received: hello.',
);
expect(() => {
ReactTestUtils.scryRenderedDOMComponentsWithTag(
{x: true, y: false},
'span',
);
}).toThrow(
'The first argument must be a React class instance. ' +
'Instead received: object with keys {x, y}.',
);
const div = document.createElement('div');
expect(() => {
ReactTestUtils.findRenderedDOMComponentWithTag(div, 'span');
}).toThrow(
'The first argument must be a React class instance. ' +
'Instead received: a DOM node.',
);
expect(() => {
ReactTestUtils.scryRenderedComponentsWithType(true, 'span');
}).toThrow(
'The first argument must be a React class instance. ' +
'Instead received: true.',
);
expect(() => {
ReactTestUtils.findRenderedComponentWithType(true, 'span');
}).toThrow(
'The first argument must be a React class instance. ' +
'Instead received: true.',
);
});
describe('Simulate', () => {
// @gate !disableDOMTestUtils
it('should change the value of an input field', async () => {
const obj = {
handler: function (e) {
e.persist();
},
};
spyOnDevAndProd(obj, 'handler');
const container = document.createElement('div');
const root = ReactDOMClient.createRoot(container);
await act(() => {
root.render(<input type="text" onChange={obj.handler} />);
});
const node = container.firstChild;
node.value = 'giraffe';
ReactTestUtils.Simulate.change(node);
expect(obj.handler).toHaveBeenCalledWith(
expect.objectContaining({target: node}),
);
});
// @gate !disableDOMTestUtils
it('should change the value of an input field in a component', async () => {
class SomeComponent extends React.Component {
inputRef = React.createRef();
render() {
return (
<div>
<input
type="text"
ref={this.inputRef}
onChange={this.props.handleChange}
/>
</div>
);
}
}
const obj = {
handler: function (e) {
e.persist();
},
};
spyOnDevAndProd(obj, 'handler');
const container = document.createElement('div');
const root = ReactDOMClient.createRoot(container);
let instance;
await act(() => {
root.render(
<SomeComponent
handleChange={obj.handler}
ref={current => (instance = current)}
/>,
);
});
const node = instance.inputRef.current;
node.value = 'zebra';
ReactTestUtils.Simulate.change(node);
expect(obj.handler).toHaveBeenCalledWith(
expect.objectContaining({target: node}),
);
});
// @gate !disableDOMTestUtils
it('should not warn when used with extra properties', async () => {
const CLIENT_X = 100;
class Component extends React.Component {
childRef = React.createRef();
handleClick = e => {
expect(e.clientX).toBe(CLIENT_X);
};
render() {
return <div onClick={this.handleClick} ref={this.childRef} />;
}
}
const element = document.createElement('div');
const root = ReactDOMClient.createRoot(element);
let instance;
await act(() => {
root.render(<Component ref={current => (instance = current)} />);
});
ReactTestUtils.Simulate.click(instance.childRef.current, {
clientX: CLIENT_X,
});
});
// @gate !disableDOMTestUtils
it('should set the type of the event', async () => {
let event;
const stub = jest.fn().mockImplementation(e => {
e.persist();
event = e;
});
const container = document.createElement('div');
const root = ReactDOMClient.createRoot(container);
let node;
await act(() => {
root.render(<div onKeyDown={stub} ref={current => (node = current)} />);
});
ReactTestUtils.Simulate.keyDown(node);
expect(event.type).toBe('keydown');
expect(event.nativeEvent.type).toBe('keydown');
});
// @gate !disableDOMTestUtils
it('should work with renderIntoDocument', async () => {
const onChange = jest.fn();
class MyComponent extends React.Component {
render() {
return (
<div>
<input type="text" onChange={onChange} />
</div>
);
}
}
const container = document.createElement('div');
const root = ReactDOMClient.createRoot(container);
let instance;
await act(() => {
root.render(<MyComponent ref={current => (instance = current)} />);
});
const input = ReactTestUtils.findRenderedDOMComponentWithTag(
instance,
'input',
);
input.value = 'giraffe';
ReactTestUtils.Simulate.change(input);
expect(onChange).toHaveBeenCalledWith(
expect.objectContaining({target: input}),
);
});
// @gate !disableDOMTestUtils
it('should have mouse enter simulated by test utils', async () => {
const idCallOrder = [];
const recordID = function (id) {
idCallOrder.push(id);
};
let CHILD;
function Child(props) {
return (
<div
ref={current => (CHILD = current)}
onMouseEnter={() => {
recordID(CHILD);
}}
/>
);
}
class ChildWrapper extends React.PureComponent {
render() {
return <Child />;
}
}
const container = document.createElement('div');
const root = ReactDOMClient.createRoot(container);
await act(() => {
root.render(
<div>
<div>
<ChildWrapper />
<button disabled={true} />
</div>
</div>,
);
});
await act(() => {
ReactTestUtils.Simulate.mouseEnter(CHILD);
});
expect(idCallOrder).toEqual([CHILD]);
});
});
// @gate !disableDOMTestUtils
// @gate !disableLegacyMode
it('should call setState callback with no arguments', async () => {
let mockArgs;
class Component extends React.Component {
componentDidMount() {
this.setState({}, (...args) => (mockArgs = args));
}
render() {
return false;
}
}
ReactTestUtils.renderIntoDocument(<Component />);
expect(mockArgs.length).toEqual(0);
});
// @gate !disableDOMTestUtils
it('should find rendered component with type in document', async () => {
class MyComponent extends React.Component {
render() {
return true;
}
}
const container = document.createElement('div');
const root = ReactDOMClient.createRoot(container);
let instance;
await act(() => {
root.render(<MyComponent ref={current => (instance = current)} />);
});
const renderedComponentType = ReactTestUtils.findRenderedComponentWithType(
instance,
MyComponent,
);
expect(renderedComponentType).toBe(instance);
});
// @gate __DEV__
it('warns when using `act`', () => {
expect(() => {
ReactTestUtils.act(() => {});
}).toErrorDev(
[
'`ReactDOMTestUtils.act` is deprecated in favor of `React.act`. ' +
'Import `act` from `react` instead of `react-dom/test-utils`. ' +
'See https://react.dev/warnings/react-dom-test-utils for more info.',
],
{withoutStack: true},
);
});
});
-884
View File
@@ -1,884 +0,0 @@
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* 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} from 'shared/ReactFeatureFlags';
import assign from 'shared/assign';
import isArray from 'shared/isArray';
// Keep in sync with ReactDOM.js:
const SecretInternals =
ReactDOM.__DOM_INTERNALS_DO_NOT_USE_OR_WARN_USERS_THEY_CANNOT_UPGRADE;
const EventInternals = SecretInternals.Events;
const getInstanceFromNode = EventInternals[0];
const getNodeFromInstance = EventInternals[1];
const getFiberCurrentPropsFromNode = EventInternals[2];
const enqueueStateRestore = EventInternals[3];
const restoreStateIfNeeded = EventInternals[4];
let didWarnAboutUsingAct = false;
function act(callback) {
if (didWarnAboutUsingAct === false) {
didWarnAboutUsingAct = true;
console.error(
'`ReactDOMTestUtils.act` is deprecated in favor of `React.act`. ' +
'Import `act` from `react` instead of `react-dom/test-utils`. ' +
'See https://react.dev/warnings/react-dom-test-utils for more info.',
);
}
return React.act(callback);
}
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 ||
node.tag === HostHoistable ||
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`. ' +
'See https://react.dev/warnings/react-dom-test-utils for more info.',
);
}
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`. ' +
'See https://react.dev/warnings/react-dom-test-utils for more info.',
);
}
return React.isValidElement(element);
}
function isElementOfType(inst, convenienceConstructor) {
if (disableDOMTestUtils) {
throw new Error(
'`isElementOfType` was removed from `react-dom/test-utils`. ' +
'See https://react.dev/warnings/react-dom-test-utils for more info.',
);
}
return React.isValidElement(inst) && inst.type === convenienceConstructor;
}
function isDOMComponent(inst) {
if (disableDOMTestUtils) {
throw new Error(
'`isDOMComponent` was removed from `react-dom/test-utils`. ' +
'See https://react.dev/warnings/react-dom-test-utils for more info.',
);
}
return !!(inst && inst.nodeType === ELEMENT_NODE && inst.tagName);
}
function isDOMComponentElement(inst) {
if (disableDOMTestUtils) {
throw new Error(
'`isDOMComponentElement` was removed from `react-dom/test-utils`. ' +
'See https://react.dev/warnings/react-dom-test-utils for more info.',
);
}
return !!(inst && React.isValidElement(inst) && !!inst.tagName);
}
function isCompositeComponent(inst) {
if (disableDOMTestUtils) {
throw new Error(
'`isCompositeComponent` was removed from `react-dom/test-utils`. ' +
'See https://react.dev/warnings/react-dom-test-utils for more info.',
);
}
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`. ' +
'See https://react.dev/warnings/react-dom-test-utils for more info.',
);
}
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`. ' +
'See https://react.dev/warnings/react-dom-test-utils for more info.',
);
}
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`. ' +
'See https://react.dev/warnings/react-dom-test-utils for more info.',
);
}
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`. ' +
'See https://react.dev/warnings/react-dom-test-utils for more info.',
);
}
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`. ' +
'See https://react.dev/warnings/react-dom-test-utils for more info.',
);
}
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`. ' +
'See https://react.dev/warnings/react-dom-test-utils for more info.',
);
}
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`. ' +
'See https://react.dev/warnings/react-dom-test-utils for more info.',
);
}
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`. ' +
'See https://react.dev/warnings/react-dom-test-utils for more info.',
);
}
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
* <div> 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`. ' +
'See https://react.dev/warnings/react-dom-test-utils for more info.',
);
}
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`. ' +
'See https://react.dev/warnings/react-dom-test-utils for more info.',
);
}
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) {
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`. ' +
'See https://react.dev/warnings/react-dom-test-utils for more info.',
);
}
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();
};
}
// A one-time snapshot with no plans to update. We'll probably want to deprecate Simulate API.
const simulatedEventTypes = [
'blur',
'cancel',
'click',
'close',
'contextMenu',
'copy',
'cut',
'auxClick',
'doubleClick',
'dragEnd',
'dragStart',
'drop',
'focus',
'input',
'invalid',
'keyDown',
'keyPress',
'keyUp',
'mouseDown',
'mouseUp',
'paste',
'pause',
'play',
'pointerCancel',
'pointerDown',
'pointerUp',
'rateChange',
'reset',
'resize',
'seeked',
'submit',
'touchCancel',
'touchEnd',
'touchStart',
'volumeChange',
'drag',
'dragEnter',
'dragExit',
'dragLeave',
'dragOver',
'mouseMove',
'mouseOut',
'mouseOver',
'pointerMove',
'pointerOut',
'pointerOver',
'scroll',
'toggle',
'touchMove',
'wheel',
'abort',
'animationEnd',
'animationIteration',
'animationStart',
'canPlay',
'canPlayThrough',
'durationChange',
'emptied',
'encrypted',
'ended',
'error',
'gotPointerCapture',
'load',
'loadedData',
'loadedMetadata',
'loadStart',
'lostPointerCapture',
'playing',
'progress',
'seeking',
'stalled',
'suspend',
'timeUpdate',
'transitionRun',
'transitionStart',
'transitionCancel',
'transitionEnd',
'waiting',
'mouseEnter',
'mouseLeave',
'pointerEnter',
'pointerLeave',
'change',
'select',
'beforeInput',
'beforeToggle',
'compositionEnd',
'compositionStart',
'compositionUpdate',
];
function buildSimulators() {
simulatedEventTypes.forEach(eventType => {
Simulate[eventType] = makeSimulator(eventType);
});
}
buildSimulators();
export {
renderIntoDocument,
isElement,
isElementOfType,
isDOMComponent,
isDOMComponentElement,
isCompositeComponent,
isCompositeComponentWithType,
findAllInRenderedTree,
scryRenderedDOMComponentsWithClass,
findRenderedDOMComponentWithClass,
scryRenderedDOMComponentsWithTag,
findRenderedDOMComponentWithTag,
scryRenderedComponentsWithType,
findRenderedComponentWithType,
mockComponent,
nativeTouchData,
Simulate,
act,
};
-10
View File
@@ -1,10 +0,0 @@
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @flow
*/
export * from './src/test-utils/ReactTestUtilsFB';
-2
View File
@@ -192,8 +192,6 @@ export const enableReactTestRendererWarning = true;
// before removing them in stable in the next Major
export const disableLegacyMode = true;
export const disableDOMTestUtils = true;
// Make <Context> equivalent to <Context.Provider> instead of <Context.Consumer>
export const enableRenderableContext = true;
@@ -98,8 +98,6 @@ export const disableStringRefs = true;
export const enableReactTestRendererWarning = false;
export const disableLegacyMode = false;
export const disableDOMTestUtils = false;
export const enableOwnerStacks = false;
// Flow magic to verify the exports of this file match the original version.
@@ -22,7 +22,6 @@ export const enableRefAsProp = __TODO_NEXT_RN_MAJOR__;
export const disableStringRefs = __TODO_NEXT_RN_MAJOR__;
export const enableFastJSX = __TODO_NEXT_RN_MAJOR__;
export const disableLegacyMode = __TODO_NEXT_RN_MAJOR__;
export const disableDOMTestUtils = __TODO_NEXT_RN_MAJOR__;
export const useModernStrictMode = __TODO_NEXT_RN_MAJOR__;
export const enableReactTestRendererWarning = __TODO_NEXT_RN_MAJOR__;
export const enableAsyncActions = __TODO_NEXT_RN_MAJOR__;
@@ -92,7 +92,6 @@ export const disableStringRefs = true;
export const enableFastJSX = true;
export const disableLegacyMode = true;
export const disableLegacyContext = true;
export const disableDOMTestUtils = true;
export const enableRenderableContext = true;
export const enableReactTestRendererWarning = true;
export const disableDefaultPropsExceptForClasses = true;
@@ -85,7 +85,6 @@ export const enableFastJSX = true;
export const enableReactTestRendererWarning = false;
export const disableLegacyMode = false;
export const disableDOMTestUtils = false;
export const disableDefaultPropsExceptForClasses = false;
export const enableAddPropertiesFastPath = false;
@@ -85,7 +85,6 @@ export const enableFastJSX = false;
export const enableReactTestRendererWarning = false;
export const disableLegacyMode = false;
export const disableDOMTestUtils = false;
export const disableDefaultPropsExceptForClasses = false;
export const enableAddPropertiesFastPath = false;
@@ -120,8 +120,6 @@ export const disableStringRefs = false;
export const disableLegacyMode = __EXPERIMENTAL__;
export const disableDOMTestUtils = false;
export const enableOwnerStacks = false;
// Flow magic to verify the exports of this file match the original version.
+1 -1
View File
@@ -231,7 +231,7 @@ const bundles = [
/******* Test Utils *******/
{
moduleType: RENDERER_UTILS,
bundleTypes: [FB_WWW_DEV, NODE_DEV, NODE_PROD],
bundleTypes: [NODE_DEV, NODE_PROD],
entry: 'react-dom/test-utils',
global: 'ReactTestUtils',
minifyWithProdErrorCodes: false,