From f209b573858e65713835d36b56678cc5d928ff46 Mon Sep 17 00:00:00 2001 From: Sebastian Silbermann Date: Wed, 27 Mar 2024 16:04:56 +0100 Subject: [PATCH] ReactDOM: Remove every test-util except `act()` (#28541) --- .../src/__tests__/ReactTestUtils-test.js | 40 +++++- .../src/test-utils/ReactTestUtils.js | 120 ++++++++++++++++++ packages/shared/ReactFeatureFlags.js | 2 + .../forks/ReactFeatureFlags.native-fb.js | 1 + .../forks/ReactFeatureFlags.native-oss.js | 1 + .../forks/ReactFeatureFlags.test-renderer.js | 1 + .../ReactFeatureFlags.test-renderer.native.js | 1 + .../ReactFeatureFlags.test-renderer.www.js | 1 + .../shared/forks/ReactFeatureFlags.www.js | 2 + 9 files changed, 164 insertions(+), 5 deletions(-) diff --git a/packages/react-dom/src/__tests__/ReactTestUtils-test.js b/packages/react-dom/src/__tests__/ReactTestUtils-test.js index 2fa0ea75a36e..6fe2ce4e2f75 100644 --- a/packages/react-dom/src/__tests__/ReactTestUtils-test.js +++ b/packages/react-dom/src/__tests__/ReactTestUtils-test.js @@ -31,6 +31,7 @@ describe('ReactTestUtils', () => { expect(Object.keys(ReactTestUtils.Simulate).sort()).toMatchSnapshot(); }); + // @gate !disableDOMTestUtils it('gives Jest mocks a passthrough implementation with mockComponent()', async () => { class MockedComponent extends React.Component { render() { @@ -60,6 +61,7 @@ describe('ReactTestUtils', () => { expect(container.textContent).toBe('Hello'); }); + // @gate !disableDOMTestUtils it('can scryRenderedComponentsWithType', async () => { class Child extends React.Component { render() { @@ -88,6 +90,7 @@ describe('ReactTestUtils', () => { expect(scryResults.length).toBe(1); }); + // @gate !disableDOMTestUtils it('can scryRenderedDOMComponentsWithClass with TextComponent', async () => { class Wrapper extends React.Component { render() { @@ -112,6 +115,7 @@ describe('ReactTestUtils', () => { expect(scryResults.length).toBe(0); }); + // @gate !disableDOMTestUtils it('can scryRenderedDOMComponentsWithClass with className contains \\n', async () => { class Wrapper extends React.Component { render() { @@ -136,6 +140,7 @@ describe('ReactTestUtils', () => { expect(scryResults.length).toBe(1); }); + // @gate !disableDOMTestUtils it('can scryRenderedDOMComponentsWithClass with multiple classes', async () => { class Wrapper extends React.Component { render() { @@ -187,6 +192,7 @@ describe('ReactTestUtils', () => { expect(scryResults5.length).toBe(0); }); + // @gate !disableDOMTestUtils it('traverses children in the correct order', async () => { class Wrapper extends React.Component { render() { @@ -225,6 +231,7 @@ describe('ReactTestUtils', () => { expect(log).toEqual(['orangepurple', 'orange', 'purple']); }); + // @gate !disableDOMTestUtils it('should support injected wrapper components as DOM components', async () => { const injectedDOMComponents = [ 'button', @@ -291,6 +298,7 @@ describe('ReactTestUtils', () => { expect(ReactTestUtils.isDOMComponent(component.bodyRef.current)).toBe(true); }); + // @gate !disableDOMTestUtils it('can scry with stateless components involved', async () => { const Function = () => (
@@ -320,6 +328,7 @@ describe('ReactTestUtils', () => { 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'); @@ -377,6 +386,7 @@ describe('ReactTestUtils', () => { }); describe('Simulate', () => { + // @gate !disableDOMTestUtils it('should change the value of an input field', async () => { const obj = { handler: function (e) { @@ -399,6 +409,7 @@ describe('ReactTestUtils', () => { ); }); + // @gate !disableDOMTestUtils it('should change the value of an input field in a component', async () => { class SomeComponent extends React.Component { inputRef = React.createRef(); @@ -442,6 +453,7 @@ describe('ReactTestUtils', () => { ); }); + // @gate !disableDOMTestUtils it('should not warn when used with extra properties', async () => { const CLIENT_X = 100; @@ -468,6 +480,7 @@ describe('ReactTestUtils', () => { }); }); + // @gate !disableDOMTestUtils it('should set the type of the event', async () => { let event; const stub = jest.fn().mockImplementation(e => { @@ -488,6 +501,7 @@ describe('ReactTestUtils', () => { expect(event.nativeEvent.type).toBe('keydown'); }); + // @gate !disableDOMTestUtils it('should work with renderIntoDocument', async () => { const onChange = jest.fn(); @@ -520,6 +534,7 @@ describe('ReactTestUtils', () => { ); }); + // @gate !disableDOMTestUtils it('should have mouse enter simulated by test utils', async () => { const idCallOrder = []; const recordID = function (id) { @@ -560,8 +575,17 @@ describe('ReactTestUtils', () => { }); expect(idCallOrder).toEqual([CHILD]); }); + + // @gate disableDOMTestUtils + it('throws', async () => { + expect(ReactTestUtils.Simulate.click).toThrow( + '`Simulate` was removed from `react-dom/test-utils`. ' + + 'See https://react.dev/warnings/react-dom-test-utils for more info.', + ); + }); }); + // @gate !disableDOMTestUtils it('should call setState callback with no arguments', async () => { let mockArgs; class Component extends React.Component { @@ -573,14 +597,12 @@ describe('ReactTestUtils', () => { } } - const container = document.createElement('div'); - const root = ReactDOMClient.createRoot(container); - await act(() => { - root.render(); - }); + 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() { @@ -602,4 +624,12 @@ describe('ReactTestUtils', () => { expect(renderedComponentType).toBe(instance); }); + + // @gate disableDOMTestUtils + it('throws on every removed function', async () => { + expect(ReactTestUtils.isDOMComponent).toThrow( + '`isDOMComponent` was removed from `react-dom/test-utils`. ' + + 'See https://react.dev/warnings/react-dom-test-utils for more info.', + ); + }); }); diff --git a/packages/react-dom/src/test-utils/ReactTestUtils.js b/packages/react-dom/src/test-utils/ReactTestUtils.js index 7391ea2b6f6f..823cf8e46a69 100644 --- a/packages/react-dom/src/test-utils/ReactTestUtils.js +++ b/packages/react-dom/src/test-utils/ReactTestUtils.js @@ -21,6 +21,7 @@ import { } 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'; @@ -126,6 +127,13 @@ function validateClassInstance(inst, methodName) { * @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 @@ -136,22 +144,57 @@ function renderIntoDocument(element) { } 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 @@ -165,6 +208,13 @@ function isCompositeComponent(inst) { } 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; } @@ -174,6 +224,13 @@ function isCompositeComponentWithType(inst, 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 []; @@ -188,6 +245,13 @@ function findAllInRenderedTree(inst, test) { * @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)) { @@ -223,6 +287,13 @@ function scryRenderedDOMComponentsWithClass(root, classNames) { * @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) { @@ -243,6 +314,13 @@ function findRenderedDOMComponentWithClass(root, className) { * @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 ( @@ -259,6 +337,13 @@ function scryRenderedDOMComponentsWithTag(root, tagName) { * @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) { @@ -278,6 +363,13 @@ function findRenderedDOMComponentWithTag(root, tagName) { * @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); @@ -291,6 +383,13 @@ function scryRenderedComponentsWithType(root, componentType) { * @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) { @@ -319,6 +418,13 @@ function findRenderedComponentWithType(root, componentType) { * @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; @@ -340,6 +446,13 @@ function mockComponent(module, mockTagName) { } 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}], }; @@ -578,6 +691,13 @@ const directDispatchEventTypes = new Set([ */ 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 ' + diff --git a/packages/shared/ReactFeatureFlags.js b/packages/shared/ReactFeatureFlags.js index bd5fcbe16b65..9747dd38f669 100644 --- a/packages/shared/ReactFeatureFlags.js +++ b/packages/shared/ReactFeatureFlags.js @@ -187,6 +187,8 @@ export const enableReactTestRendererWarning = __NEXT_MAJOR__; // 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 5abca0fcaee6..e51541b1bb93 100644 --- a/packages/shared/forks/ReactFeatureFlags.native-fb.js +++ b/packages/shared/forks/ReactFeatureFlags.native-fb.js @@ -100,6 +100,7 @@ export const disableStringRefs = false; export const enableReactTestRendererWarning = 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); diff --git a/packages/shared/forks/ReactFeatureFlags.native-oss.js b/packages/shared/forks/ReactFeatureFlags.native-oss.js index df009598096e..d447207b98e8 100644 --- a/packages/shared/forks/ReactFeatureFlags.native-oss.js +++ b/packages/shared/forks/ReactFeatureFlags.native-oss.js @@ -21,6 +21,7 @@ const __TODO_NEXT_RN_MAJOR__ = false; export const enableRefAsProp = __TODO_NEXT_RN_MAJOR__; export const disableStringRefs = __TODO_NEXT_RN_MAJOR__; export const disableLegacyMode = __TODO_NEXT_RN_MAJOR__; +export const disableDOMTestUtils = __TODO_NEXT_RN_MAJOR__; export const enableBigIntSupport = __TODO_NEXT_RN_MAJOR__; export const useModernStrictMode = __TODO_NEXT_RN_MAJOR__; export const enableReactTestRendererWarning = __TODO_NEXT_RN_MAJOR__; diff --git a/packages/shared/forks/ReactFeatureFlags.test-renderer.js b/packages/shared/forks/ReactFeatureFlags.test-renderer.js index 957ac77f51d8..bce10070683f 100644 --- a/packages/shared/forks/ReactFeatureFlags.test-renderer.js +++ b/packages/shared/forks/ReactFeatureFlags.test-renderer.js @@ -92,6 +92,7 @@ export const disableStringRefs = __NEXT_MAJOR__; 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__; export const disableModulePatternComponents = __NEXT_MAJOR__; export const enableRenderableContext = __NEXT_MAJOR__; diff --git a/packages/shared/forks/ReactFeatureFlags.test-renderer.native.js b/packages/shared/forks/ReactFeatureFlags.test-renderer.native.js index d2aea07967b7..b184d47d8fe3 100644 --- a/packages/shared/forks/ReactFeatureFlags.test-renderer.native.js +++ b/packages/shared/forks/ReactFeatureFlags.test-renderer.native.js @@ -87,6 +87,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 db8c11d543b8..87b5e0302aea 100644 --- a/packages/shared/forks/ReactFeatureFlags.test-renderer.www.js +++ b/packages/shared/forks/ReactFeatureFlags.test-renderer.www.js @@ -89,6 +89,7 @@ export const disableStringRefs = false; export const enableReactTestRendererWarning = false; export const disableLegacyMode = false; +export const disableDOMTestUtils = false; export const enableBigIntSupport = true; diff --git a/packages/shared/forks/ReactFeatureFlags.www.js b/packages/shared/forks/ReactFeatureFlags.www.js index 70467e4413e8..24b43e1157c4 100644 --- a/packages/shared/forks/ReactFeatureFlags.www.js +++ b/packages/shared/forks/ReactFeatureFlags.www.js @@ -121,5 +121,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);