From c600b635796726c04b1a526d9a913b0ba6755169 Mon Sep 17 00:00:00 2001 From: Jack Pope Date: Tue, 28 May 2024 19:55:14 +0100 Subject: [PATCH] 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. --- .../src/__tests__/ReactTestUtils-test.js | 735 --------------- .../src/test-utils/ReactTestUtilsFB.js | 884 ------------------ packages/react-dom/test-utils.fb.js | 10 - packages/shared/ReactFeatureFlags.js | 2 - .../forks/ReactFeatureFlags.native-fb.js | 2 - .../forks/ReactFeatureFlags.native-oss.js | 1 - .../forks/ReactFeatureFlags.test-renderer.js | 1 - ...actFeatureFlags.test-renderer.native-fb.js | 1 - .../ReactFeatureFlags.test-renderer.www.js | 1 - .../shared/forks/ReactFeatureFlags.www.js | 2 - scripts/rollup/bundles.js | 2 +- 11 files changed, 1 insertion(+), 1640 deletions(-) delete mode 100644 packages/react-dom/src/__tests__/ReactTestUtils-test.js delete mode 100644 packages/react-dom/src/test-utils/ReactTestUtilsFB.js delete mode 100644 packages/react-dom/test-utils.fb.js diff --git a/packages/react-dom/src/__tests__/ReactTestUtils-test.js b/packages/react-dom/src/__tests__/ReactTestUtils-test.js deleted file mode 100644 index 534b3538eb49b..0000000000000 --- a/packages/react-dom/src/__tests__/ReactTestUtils-test.js +++ /dev/null @@ -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 || - 'test doc', - ); - 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(Hello); - }); - - 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 ( -
- -
- ); - } - } - const container = document.createElement('div'); - const root = ReactDOMClient.createRoot(container); - let renderedComponent; - await act(() => { - root.render( (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 ( -
- Hello Jim -
- ); - } - } - - const container = document.createElement('div'); - const root = ReactDOMClient.createRoot(container); - let renderedComponent; - await act(() => { - root.render( (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 ( -
- Hello Jim -
- ); - } - } - - const container = document.createElement('div'); - const root = ReactDOMClient.createRoot(container); - let renderedComponent; - await act(() => { - root.render( (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 ( -
- Hello Jim -
- ); - } - } - - const container = document.createElement('div'); - const root = ReactDOMClient.createRoot(container); - let renderedComponent; - await act(() => { - root.render( (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
{this.props.children}
; - } - } - - const container = document.createElement('div'); - const root = ReactDOMClient.createRoot(container); - await act(() => { - root.render( - - {null} -
purple
-
, - ); - }); - let tree; - await act(() => { - root.render( - (tree = current)}> -
orange
-
purple
-
, - ); - }); - - 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 ( - - - hello - - hello, world - - ); - } - } - - const markup = ReactDOMServer.renderToString(); - const testDocument = getTestDocument(markup); - let component; - await act(() => { - ReactDOMClient.hydrateRoot( - testDocument, - (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 = () => ( -
-
-
- ); - - class SomeComponent extends React.Component { - render() { - return ( -
- -
-
- ); - } - } - - const container = document.createElement('div'); - const root = ReactDOMClient.createRoot(container); - let inst; - await act(() => { - root.render( (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(); - }); - 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 ( -
- -
- ); - } - } - - 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( - (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
; - } - } - - const element = document.createElement('div'); - const root = ReactDOMClient.createRoot(element); - let instance; - await act(() => { - root.render( (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(
(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 ( -
- -
- ); - } - } - - const container = document.createElement('div'); - const root = ReactDOMClient.createRoot(container); - let instance; - await act(() => { - root.render( (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 ( -
(CHILD = current)} - onMouseEnter={() => { - recordID(CHILD); - }} - /> - ); - } - - class ChildWrapper extends React.PureComponent { - render() { - return ; - } - } - - const container = document.createElement('div'); - const root = ReactDOMClient.createRoot(container); - await act(() => { - root.render( -
-
- -
-
, - ); - }); - 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(); - - 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( (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}, - ); - }); -}); diff --git a/packages/react-dom/src/test-utils/ReactTestUtilsFB.js b/packages/react-dom/src/test-utils/ReactTestUtilsFB.js deleted file mode 100644 index 39ac99c3bc100..0000000000000 --- a/packages/react-dom/src/test-utils/ReactTestUtilsFB.js +++ /dev/null @@ -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 - *
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, -}; diff --git a/packages/react-dom/test-utils.fb.js b/packages/react-dom/test-utils.fb.js deleted file mode 100644 index dc43cbed3fcbd..0000000000000 --- a/packages/react-dom/test-utils.fb.js +++ /dev/null @@ -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'; diff --git a/packages/shared/ReactFeatureFlags.js b/packages/shared/ReactFeatureFlags.js index 35a2e822e480c..adec53c109352 100644 --- a/packages/shared/ReactFeatureFlags.js +++ b/packages/shared/ReactFeatureFlags.js @@ -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 equivalent to instead of export const enableRenderableContext = true; diff --git a/packages/shared/forks/ReactFeatureFlags.native-fb.js b/packages/shared/forks/ReactFeatureFlags.native-fb.js index 7ea580bd70c5a..f5387abb03c41 100644 --- a/packages/shared/forks/ReactFeatureFlags.native-fb.js +++ b/packages/shared/forks/ReactFeatureFlags.native-fb.js @@ -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. diff --git a/packages/shared/forks/ReactFeatureFlags.native-oss.js b/packages/shared/forks/ReactFeatureFlags.native-oss.js index 7e16ee25b4578..f6820d3bf5803 100644 --- a/packages/shared/forks/ReactFeatureFlags.native-oss.js +++ b/packages/shared/forks/ReactFeatureFlags.native-oss.js @@ -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__; diff --git a/packages/shared/forks/ReactFeatureFlags.test-renderer.js b/packages/shared/forks/ReactFeatureFlags.test-renderer.js index 4504d08fecca0..24d94adaf82ec 100644 --- a/packages/shared/forks/ReactFeatureFlags.test-renderer.js +++ b/packages/shared/forks/ReactFeatureFlags.test-renderer.js @@ -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; diff --git a/packages/shared/forks/ReactFeatureFlags.test-renderer.native-fb.js b/packages/shared/forks/ReactFeatureFlags.test-renderer.native-fb.js index 41aea40e765d2..731aa42147579 100644 --- a/packages/shared/forks/ReactFeatureFlags.test-renderer.native-fb.js +++ b/packages/shared/forks/ReactFeatureFlags.test-renderer.native-fb.js @@ -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; diff --git a/packages/shared/forks/ReactFeatureFlags.test-renderer.www.js b/packages/shared/forks/ReactFeatureFlags.test-renderer.www.js index 0aac888de99ec..9f5aa656c8bc1 100644 --- a/packages/shared/forks/ReactFeatureFlags.test-renderer.www.js +++ b/packages/shared/forks/ReactFeatureFlags.test-renderer.www.js @@ -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; diff --git a/packages/shared/forks/ReactFeatureFlags.www.js b/packages/shared/forks/ReactFeatureFlags.www.js index f135724f99944..de8fdc2c0a186 100644 --- a/packages/shared/forks/ReactFeatureFlags.www.js +++ b/packages/shared/forks/ReactFeatureFlags.www.js @@ -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. diff --git a/scripts/rollup/bundles.js b/scripts/rollup/bundles.js index 09ffb11723348..66e59124ca2d5 100644 --- a/scripts/rollup/bundles.js +++ b/scripts/rollup/bundles.js @@ -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,