From 8ba5ae075cf1b6a872d6810d2feb00cd6854349f Mon Sep 17 00:00:00 2001 From: Jack Pope Date: Thu, 29 Feb 2024 11:51:11 +0100 Subject: [PATCH 1/3] Only concurrent render in RTR --- ...rorBoundaryReconciliation-test.internal.js | 57 +- .../src/__tests__/ReactLazy-test.internal.js | 2 + .../src/ReactTestRenderer.js | 10 +- .../ReactTestRenderer-test.internal.js | 499 ++++++++++++------ .../src/__tests__/ReactTestRenderer-test.js | 74 +-- .../__tests__/ReactTestRendererAct-test.js | 6 +- .../ReactTestRendererTraversal-test.js | 95 ++-- ...ofilerDevToolsIntegration-test.internal.js | 66 ++- 8 files changed, 536 insertions(+), 273 deletions(-) diff --git a/packages/react-reconciler/src/__tests__/ErrorBoundaryReconciliation-test.internal.js b/packages/react-reconciler/src/__tests__/ErrorBoundaryReconciliation-test.internal.js index 449174a961fd6..b577bd1bc074e 100644 --- a/packages/react-reconciler/src/__tests__/ErrorBoundaryReconciliation-test.internal.js +++ b/packages/react-reconciler/src/__tests__/ErrorBoundaryReconciliation-test.internal.js @@ -46,45 +46,40 @@ describe('ErrorBoundaryReconciliation', () => { fail ? : ; }); - [true, false].forEach(isConcurrent => { - async function sharedTest(ErrorBoundary, fallbackTagName) { - let renderer; + async function sharedTest(ErrorBoundary, fallbackTagName) { + let renderer; + await act(() => { + renderer = ReactTestRenderer.create( + + + , + {unstable_isConcurrent: true}, + ); + }); + expect(renderer).toMatchRenderedOutput(); + await expect(async () => { await act(() => { - renderer = ReactTestRenderer.create( + renderer.update( - + , - {unstable_isConcurrent: isConcurrent}, ); }); - expect(renderer).toMatchRenderedOutput(); - - await expect(async () => { - await act(() => { - renderer.update( - - - , - ); - }); - }).toErrorDev(isConcurrent ? ['invalid', 'invalid'] : ['invalid']); - const Fallback = fallbackTagName; - expect(renderer).toMatchRenderedOutput(); - } + }).toErrorDev(['invalid', 'invalid']); + const Fallback = fallbackTagName; + expect(renderer).toMatchRenderedOutput(); + } - describe(isConcurrent ? 'concurrent' : 'sync', () => { - it('componentDidCatch can recover by rendering an element of the same type', () => - sharedTest(DidCatchErrorBoundary, 'span')); + it('componentDidCatch can recover by rendering an element of the same type', () => + sharedTest(DidCatchErrorBoundary, 'span')); - it('componentDidCatch can recover by rendering an element of a different type', () => - sharedTest(DidCatchErrorBoundary, 'div')); + it('componentDidCatch can recover by rendering an element of a different type', () => + sharedTest(DidCatchErrorBoundary, 'div')); - it('getDerivedStateFromError can recover by rendering an element of the same type', () => - sharedTest(GetDerivedErrorBoundary, 'span')); + it('getDerivedStateFromError can recover by rendering an element of the same type', () => + sharedTest(GetDerivedErrorBoundary, 'span')); - it('getDerivedStateFromError can recover by rendering an element of a different type', () => - sharedTest(GetDerivedErrorBoundary, 'div')); - }); - }); + it('getDerivedStateFromError can recover by rendering an element of a different type', () => + sharedTest(GetDerivedErrorBoundary, 'div')); }); diff --git a/packages/react-reconciler/src/__tests__/ReactLazy-test.internal.js b/packages/react-reconciler/src/__tests__/ReactLazy-test.internal.js index 850fabc5f4c2c..fa84ab8df427b 100644 --- a/packages/react-reconciler/src/__tests__/ReactLazy-test.internal.js +++ b/packages/react-reconciler/src/__tests__/ReactLazy-test.internal.js @@ -1451,6 +1451,7 @@ describe('ReactLazy', () => { }); describe('legacy mode', () => { + // @gate !disableLegacyMode it('mount and reorder lazy elements (legacy mode)', async () => { class Child extends React.Component { componentDidMount() { @@ -1520,6 +1521,7 @@ describe('ReactLazy', () => { expect(root).toMatchRenderedOutput('ba'); }); + // @gate !disableLegacyMode it('mount and reorder lazy types (legacy mode)', async () => { class Child extends React.Component { componentDidMount() { diff --git a/packages/react-test-renderer/src/ReactTestRenderer.js b/packages/react-test-renderer/src/ReactTestRenderer.js index 0037e65b93fcc..8b8dd59d95d08 100644 --- a/packages/react-test-renderer/src/ReactTestRenderer.js +++ b/packages/react-test-renderer/src/ReactTestRenderer.js @@ -55,6 +55,7 @@ import {ConcurrentRoot, LegacyRoot} from 'react-reconciler/src/ReactRootTags'; import { allowConcurrentByDefault, enableReactTestRendererWarning, + disableLegacyMode, } from 'shared/ReactFeatureFlags'; const act = React.act; @@ -485,7 +486,10 @@ function create( } let createNodeMock = defaultTestOptions.createNodeMock; - let isConcurrent = false; + const isConcurrentOnly = + disableLegacyMode === true && + global.IS_REACT_NATIVE_TEST_ENVIRONMENT !== true; + let isConcurrent = isConcurrentOnly; let isStrictMode = false; let concurrentUpdatesByDefault = null; if (typeof options === 'object' && options !== null) { @@ -493,8 +497,8 @@ function create( // $FlowFixMe[incompatible-type] found when upgrading Flow createNodeMock = options.createNodeMock; } - if (options.unstable_isConcurrent === true) { - isConcurrent = true; + if (!isConcurrentOnly) { + isConcurrent = options.unstable_isConcurrent; } if (options.unstable_strictMode === true) { isStrictMode = true; diff --git a/packages/react-test-renderer/src/__tests__/ReactTestRenderer-test.internal.js b/packages/react-test-renderer/src/__tests__/ReactTestRenderer-test.internal.js index 3de3c0d4ad440..107ec757c6338 100644 --- a/packages/react-test-renderer/src/__tests__/ReactTestRenderer-test.internal.js +++ b/packages/react-test-renderer/src/__tests__/ReactTestRenderer-test.internal.js @@ -14,14 +14,19 @@ const ReactFeatureFlags = require('shared/ReactFeatureFlags'); const React = require('react'); const ReactTestRenderer = require('react-test-renderer'); const {format: prettyFormat} = require('pretty-format'); +const InternalTestUtils = require('internal-test-utils'); +const waitForAll = InternalTestUtils.waitForAll; +const act = InternalTestUtils.act; +const Reconciler = require('react-reconciler/src/ReactFiberReconciler'); +const { + ConcurrentRoot, + LegacyRoot, +} = require('react-reconciler/src/ReactRootTags'); // Isolate noop renderer jest.resetModules(); const ReactNoop = require('react-noop-renderer'); -const InternalTestUtils = require('internal-test-utils'); -const waitForAll = InternalTestUtils.waitForAll; - // Kind of hacky, but we nullify all the instances to test the tree structure // with jasmine's deep equality function, and test the instances separate. We // also delete children props because testing them is more annoying and not @@ -78,11 +83,61 @@ describe('ReactTestRenderer', () => { global.IS_REACT_NATIVE_TEST_ENVIRONMENT = false; }); - it('renders a simple component', () => { + describe('root tags', () => { + let createContainerSpy; + beforeEach(() => { + createContainerSpy = jest.spyOn(Reconciler, 'createContainer'); + }); + + function expectTag(tag) { + expect(createContainerSpy).toHaveBeenCalledWith( + expect.anything(), + tag, + null, + expect.anything(), + null, + expect.anything(), + expect.anything(), + null, + ); + } + + // @gate disableLegacyMode + it('should render using concurrent root if disableLegacyMode', () => { + ReactTestRenderer.create(
); + expectTag(ConcurrentRoot); + }); + + // @gate !disableLegacyMode + it('should default to legacy root if not disableLegacyMode', () => { + ReactTestRenderer.create(
); + expectTag(LegacyRoot); + }); + + it('should allow unstable_isConcurrent if not disableLegacyMode', async () => { + ReactTestRenderer.create(
, { + unstable_isConcurrent: true, + }); + ReactTestRenderer.create(
); + expectTag(ConcurrentRoot); + }); + + it('should render legacy root when RN test environment', async () => { + global.IS_REACT_NATIVE_TEST_ENVIRONMENT = true; + ReactTestRenderer.create(
); + expectTag(LegacyRoot); + global.IS_REACT_NATIVE_TEST_ENVIRONMENT = false; + }); + }); + + it('renders a simple component', async () => { function Link() { return ; } - const renderer = ReactTestRenderer.create(); + let renderer; + await act(() => { + renderer = ReactTestRenderer.create(); + }); expect(renderer.toJSON()).toEqual({ type: 'a', props: {role: 'link'}, @@ -90,19 +145,25 @@ describe('ReactTestRenderer', () => { }); }); - it('renders a top-level empty component', () => { + it('renders a top-level empty component', async () => { function Empty() { return null; } - const renderer = ReactTestRenderer.create(); + let renderer; + await act(() => { + renderer = ReactTestRenderer.create(); + }); expect(renderer.toJSON()).toEqual(null); }); - it('exposes a type flag', () => { + it('exposes a type flag', async () => { function Link() { return ; } - const renderer = ReactTestRenderer.create(); + let renderer; + await act(() => { + renderer = ReactTestRenderer.create(); + }); const object = renderer.toJSON(); expect(object.$$typeof).toBe(Symbol.for('react.test.json')); @@ -114,7 +175,7 @@ describe('ReactTestRenderer', () => { } }); - it('can render a composite component', () => { + it('can render a composite component', async () => { class Component extends React.Component { render() { return ( @@ -129,7 +190,10 @@ describe('ReactTestRenderer', () => { return ; }; - const renderer = ReactTestRenderer.create(); + let renderer; + await act(() => { + renderer = ReactTestRenderer.create(); + }); expect(renderer.toJSON()).toEqual({ type: 'div', props: {className: 'purple'}, @@ -137,7 +201,7 @@ describe('ReactTestRenderer', () => { }); }); - it('renders some basics with an update', () => { + it('renders some basics with an update', async () => { let renders = 0; class Component extends React.Component { @@ -169,7 +233,10 @@ describe('ReactTestRenderer', () => { return null; }; - const renderer = ReactTestRenderer.create(); + let renderer; + await act(() => { + renderer = ReactTestRenderer.create(); + }); expect(renderer.toJSON()).toEqual({ type: 'div', props: {className: 'purple'}, @@ -178,7 +245,7 @@ describe('ReactTestRenderer', () => { expect(renders).toBe(6); }); - it('exposes the instance', () => { + it('exposes the instance', async () => { class Mouse extends React.Component { constructor() { super(); @@ -191,7 +258,10 @@ describe('ReactTestRenderer', () => { return
{this.state.mouse}
; } } - const renderer = ReactTestRenderer.create(); + let renderer; + await act(() => { + renderer = ReactTestRenderer.create(); + }); expect(renderer.toJSON()).toEqual({ type: 'div', @@ -200,7 +270,9 @@ describe('ReactTestRenderer', () => { }); const mouse = renderer.getInstance(); - mouse.handleMoose(); + await act(() => { + mouse.handleMoose(); + }); expect(renderer.toJSON()).toEqual({ type: 'div', children: ['moose'], @@ -208,15 +280,20 @@ describe('ReactTestRenderer', () => { }); }); - it('updates types', () => { - const renderer = ReactTestRenderer.create(
mouse
); + it('updates types', async () => { + let renderer; + await act(() => { + renderer = ReactTestRenderer.create(
mouse
); + }); + expect(renderer.toJSON()).toEqual({ type: 'div', props: {}, children: ['mouse'], }); - - renderer.update(mice); + await act(() => { + renderer.update(mice); + }); expect(renderer.toJSON()).toEqual({ type: 'span', props: {}, @@ -224,14 +301,18 @@ describe('ReactTestRenderer', () => { }); }); - it('updates children', () => { - const renderer = ReactTestRenderer.create( -
- A - B - C -
, - ); + it('updates children', async () => { + let renderer; + await act(() => { + renderer = ReactTestRenderer.create( +
+ A + B + C +
, + ); + }); + expect(renderer.toJSON()).toEqual({ type: 'div', props: {}, @@ -242,13 +323,15 @@ describe('ReactTestRenderer', () => { ], }); - renderer.update( -
- D - C - B -
, - ); + await act(() => { + renderer.update( +
+ D + C + B +
, + ); + }); expect(renderer.toJSON()).toEqual({ type: 'div', props: {}, @@ -260,7 +343,7 @@ describe('ReactTestRenderer', () => { }); }); - it('does the full lifecycle', () => { + it('does the full lifecycle', async () => { const log = []; class Log extends React.Component { render() { @@ -275,9 +358,16 @@ describe('ReactTestRenderer', () => { } } - const renderer = ReactTestRenderer.create(); - renderer.update(); - renderer.unmount(); + let renderer; + await act(() => { + renderer = ReactTestRenderer.create(); + }); + await act(() => { + renderer.update(); + }); + await act(() => { + renderer.unmount(); + }); expect(log).toEqual([ 'render Foo', @@ -289,14 +379,16 @@ describe('ReactTestRenderer', () => { ]); }); - it('gives a ref to native components', () => { + it('gives a ref to native components', async () => { const log = []; - ReactTestRenderer.create(
log.push(r)} />); + await act(() => { + ReactTestRenderer.create(
log.push(r)} />); + }); expect(log).toEqual([null]); }); // @gate !enableRefAsProp || !__DEV__ - it('warns correctly for refs on SFCs', () => { + it('warns correctly for refs on SFCs', async () => { function Bar() { return
Hello, world
; } @@ -312,8 +404,14 @@ describe('ReactTestRenderer', () => { return
; } } - ReactTestRenderer.create(); - expect(() => ReactTestRenderer.create()).toErrorDev( + await act(() => { + ReactTestRenderer.create(); + }); + await expect(async () => { + await act(() => { + ReactTestRenderer.create(); + }); + }).toErrorDev( 'Warning: Function components cannot be given refs. Attempts ' + 'to access this ref will fail. ' + 'Did you mean to use React.forwardRef()?\n' + @@ -322,7 +420,7 @@ describe('ReactTestRenderer', () => { ); }); - it('allows an optional createNodeMock function', () => { + it('allows an optional createNodeMock function', async () => { const mockDivInstance = {appendChild: () => {}}; const mockInputInstance = {focus: () => {}}; const mockListItemInstance = {click: () => {}}; @@ -351,27 +449,41 @@ describe('ReactTestRenderer', () => { return {}; } } - ReactTestRenderer.create(
log.push(r)} />, {createNodeMock}); - ReactTestRenderer.create( log.push(r)} />, { - createNodeMock, + await act(() => { + ReactTestRenderer.create(
log.push(r)} />, { + createNodeMock, + }); + }); + await act(() => { + ReactTestRenderer.create( log.push(r)} />, { + createNodeMock, + }); + }); + await act(() => { + ReactTestRenderer.create( +
+ +
    +
  • log.push(r)} /> +
+
    +
  • log.push(r)} /> +
  • log.push(r)} /> +
+
+
, + {createNodeMock, foobar: true}, + ); + }); + await act(() => { + ReactTestRenderer.create(, {createNodeMock}); + }); + await act(() => { + ReactTestRenderer.create(
log.push(r)} />); + }); + await act(() => { + ReactTestRenderer.create(
log.push(r)} />, {}); }); - ReactTestRenderer.create( -
- -
    -
  • log.push(r)} /> -
-
    -
  • log.push(r)} /> -
  • log.push(r)} /> -
-
-
, - {createNodeMock, foobar: true}, - ); - ReactTestRenderer.create(, {createNodeMock}); - ReactTestRenderer.create(
log.push(r)} />); - ReactTestRenderer.create(
log.push(r)} />, {}); expect(log).toEqual([ mockDivInstance, mockInputInstance, @@ -396,7 +508,7 @@ describe('ReactTestRenderer', () => { expect(() => inst.unmount()).not.toThrow(); }); - it('supports unmounting inner instances', () => { + it('supports unmounting inner instances', async () => { let count = 0; class Foo extends React.Component { componentWillUnmount() { @@ -406,19 +518,24 @@ describe('ReactTestRenderer', () => { return
; } } - const inst = ReactTestRenderer.create( -
- -
, - { - createNodeMock: () => 'foo', - }, - ); - expect(() => inst.unmount()).not.toThrow(); + let inst; + await act(() => { + inst = ReactTestRenderer.create( +
+ +
, + { + createNodeMock: () => 'foo', + }, + ); + }); + await act(() => { + inst.unmount(); + }); expect(count).toEqual(1); }); - it('supports updates when using refs', () => { + it('supports updates when using refs', async () => { const log = []; const createNodeMock = element => { log.push(element.type); @@ -433,14 +550,19 @@ describe('ReactTestRenderer', () => { ); } } - const inst = ReactTestRenderer.create(, { - createNodeMock, + let inst; + await act(() => { + inst = ReactTestRenderer.create(, { + createNodeMock, + }); + }); + await act(() => { + inst.update(); }); - inst.update(); expect(log).toEqual(['div', 'span']); }); - it('supports error boundaries', () => { + it('supports error boundaries', async () => { const log = []; class Angry extends React.Component { render() { @@ -489,13 +611,20 @@ describe('ReactTestRenderer', () => { } } - const renderer = ReactTestRenderer.create(); + let renderer; + await act(() => { + renderer = ReactTestRenderer.create(, { + unstable_isConcurrent: true, + }); + }); expect(renderer.toJSON()).toEqual({ type: 'div', props: {}, children: ['Happy Birthday!'], }); expect(log).toEqual([ + 'Boundary render', + 'Angry render', 'Boundary render', 'Angry render', 'Boundary componentDidMount', @@ -504,42 +633,53 @@ describe('ReactTestRenderer', () => { ]); }); - it('can update text nodes', () => { + it('can update text nodes', async () => { class Component extends React.Component { render() { return
{this.props.children}
; } } - const renderer = ReactTestRenderer.create(Hi); + let renderer; + await act(() => { + renderer = ReactTestRenderer.create(Hi); + }); expect(renderer.toJSON()).toEqual({ type: 'div', children: ['Hi'], props: {}, }); - renderer.update({['Hi', 'Bye']}); + await act(() => { + renderer.update({['Hi', 'Bye']}); + }); expect(renderer.toJSON()).toEqual({ type: 'div', children: ['Hi', 'Bye'], props: {}, }); - renderer.update(Bye); + await act(() => { + renderer.update(Bye); + }); expect(renderer.toJSON()).toEqual({ type: 'div', children: ['Bye'], props: {}, }); - renderer.update({42}); + await act(() => { + renderer.update({42}); + }); expect(renderer.toJSON()).toEqual({ type: 'div', children: ['42'], props: {}, }); - renderer.update( - -
- , - ); + await act(() => { + renderer.update( + +
+ , + ); + }); expect(renderer.toJSON()).toEqual({ type: 'div', children: [ @@ -553,10 +693,13 @@ describe('ReactTestRenderer', () => { }); }); - it('toTree() renders simple components returning host components', () => { + it('toTree() renders simple components returning host components', async () => { const Qoo = () => Hello World!; - const renderer = ReactTestRenderer.create(); + let renderer; + await act(() => { + renderer = ReactTestRenderer.create(); + }); const tree = renderer.toTree(); cleanNodeOrArray(tree); @@ -578,13 +721,16 @@ describe('ReactTestRenderer', () => { ); }); - it('toTree() handles nested Fragments', () => { + it('toTree() handles nested Fragments', async () => { const Foo = () => ( <> <>foo ); - const renderer = ReactTestRenderer.create(); + let renderer; + await act(() => { + renderer = ReactTestRenderer.create(); + }); const tree = renderer.toTree(); cleanNodeOrArray(tree); @@ -600,14 +746,17 @@ describe('ReactTestRenderer', () => { ); }); - it('toTree() handles null rendering components', () => { + it('toTree() handles null rendering components', async () => { class Foo extends React.Component { render() { return null; } } - const renderer = ReactTestRenderer.create(); + let renderer; + await act(() => { + renderer = ReactTestRenderer.create(); + }); const tree = renderer.toTree(); expect(tree.instance).toBeInstanceOf(Foo); @@ -623,15 +772,18 @@ describe('ReactTestRenderer', () => { }); }); - it('toTree() handles simple components that return arrays', () => { + it('toTree() handles simple components that return arrays', async () => { const Foo = ({children}) => children; - const renderer = ReactTestRenderer.create( - -
One
-
Two
-
, - ); + let renderer; + await act(() => { + renderer = ReactTestRenderer.create( + +
One
+
Two
+
, + ); + }); const tree = renderer.toTree(); @@ -663,25 +815,28 @@ describe('ReactTestRenderer', () => { ); }); - it('toTree() handles complicated tree of arrays', () => { + it('toTree() handles complicated tree of arrays', async () => { class Foo extends React.Component { render() { return this.props.children; } } - const renderer = ReactTestRenderer.create( -
- -
One
-
Two
+ let renderer; + await act(() => { + renderer = ReactTestRenderer.create( +
-
Three
+
One
+
Two
+ +
Three
+
- -
Four
-
, - ); +
Four
+
, + ); + }); const tree = renderer.toTree(); @@ -741,19 +896,22 @@ describe('ReactTestRenderer', () => { ); }); - it('toTree() handles complicated tree of fragments', () => { - const renderer = ReactTestRenderer.create( - <> + it('toTree() handles complicated tree of fragments', async () => { + let renderer; + await act(() => { + renderer = ReactTestRenderer.create( <> -
One
-
Two
<> -
Three
+
One
+
Two
+ <> +
Three
+ - -
Four
- , - ); +
Four
+ , + ); + }); const tree = renderer.toTree(); @@ -793,18 +951,22 @@ describe('ReactTestRenderer', () => { ); }); - it('root instance and createNodeMock ref return the same value', () => { + it('root instance and createNodeMock ref return the same value', async () => { const createNodeMock = ref => ({node: ref}); let refInst = null; - const renderer = ReactTestRenderer.create( -
(refInst = ref)} />, - {createNodeMock}, - ); + let renderer; + await act(() => { + renderer = ReactTestRenderer.create( +
(refInst = ref)} />, + {createNodeMock}, + ); + }); + const root = renderer.getInstance(); expect(root).toEqual(refInst); }); - it('toTree() renders complicated trees of composites and hosts', () => { + it('toTree() renders complicated trees of composites and hosts', async () => { // SFC returning host. no children props. const Qoo = () => Hello World!; @@ -835,7 +997,11 @@ describe('ReactTestRenderer', () => { } } - const renderer = ReactTestRenderer.create(); + let renderer; + await act(() => { + renderer = ReactTestRenderer.create(); + }); + const tree = renderer.toTree(); // we test for the presence of instances before nulling them out @@ -894,30 +1060,45 @@ describe('ReactTestRenderer', () => { ); }); - it('can update text nodes when rendered as root', () => { - const renderer = ReactTestRenderer.create(['Hello', 'world']); + it('can update text nodes when rendered as root', async () => { + let renderer; + await act(() => { + renderer = ReactTestRenderer.create(['Hello', 'world']); + }); expect(renderer.toJSON()).toEqual(['Hello', 'world']); - renderer.update(42); + await act(() => { + renderer.update(42); + }); expect(renderer.toJSON()).toEqual('42'); - renderer.update([42, 'world']); + await act(() => { + renderer.update([42, 'world']); + }); expect(renderer.toJSON()).toEqual(['42', 'world']); }); - it('can render and update root fragments', () => { + it('can render and update root fragments', async () => { const Component = props => props.children; - const renderer = ReactTestRenderer.create([ - Hi, - Bye, - ]); + let renderer; + await act(() => { + renderer = ReactTestRenderer.create([ + Hi, + Bye, + ]); + }); + expect(renderer.toJSON()).toEqual(['Hi', 'Bye']); - renderer.update(
); + await act(() => { + renderer.update(
); + }); expect(renderer.toJSON()).toEqual({ type: 'div', children: null, props: {}, }); - renderer.update([
goodbye
, 'world']); + await act(() => { + renderer.update([
goodbye
, 'world']); + }); expect(renderer.toJSON()).toEqual([ { type: 'div', @@ -928,7 +1109,7 @@ describe('ReactTestRenderer', () => { ]); }); - it('supports context providers and consumers', () => { + it('supports context providers and consumers', async () => { const {Consumer, Provider} = React.createContext('a'); function Child(props) { @@ -943,7 +1124,10 @@ describe('ReactTestRenderer', () => { ); } - const renderer = ReactTestRenderer.create(); + let renderer; + await act(() => { + renderer = ReactTestRenderer.create(); + }); const child = renderer.root.findByType(Child); expect(child.children).toEqual(['b']); expect(prettyFormat(renderer.toTree())).toEqual( @@ -965,7 +1149,7 @@ describe('ReactTestRenderer', () => { ); }); - it('supports modes', () => { + it('supports modes', async () => { function Child(props) { return props.value; } @@ -978,7 +1162,10 @@ describe('ReactTestRenderer', () => { ); } - const renderer = ReactTestRenderer.create(); + let renderer; + await act(() => { + renderer = ReactTestRenderer.create(); + }); const child = renderer.root.findByType(Child); expect(child.children).toEqual(['a']); expect(prettyFormat(renderer.toTree())).toEqual( @@ -1002,7 +1189,7 @@ describe('ReactTestRenderer', () => { ); }); - it('supports forwardRef', () => { + it('supports forwardRef', async () => { const InnerRefed = React.forwardRef((props, ref) => (
@@ -1020,7 +1207,10 @@ describe('ReactTestRenderer', () => { } } - const renderer = ReactTestRenderer.create(); + let renderer; + await act(() => { + renderer = ReactTestRenderer.create(); + }); const tree = renderer.toTree(); cleanNodeOrArray(tree); @@ -1065,12 +1255,17 @@ describe('ReactTestRenderer', () => { ); ReactNoop.render(); await waitForAll([]); - ReactTestRenderer.create(); + await act(() => { + ReactTestRenderer.create(); + }); }); - it('calling findByType() with an invalid component will fall back to "Unknown" for component name', () => { + it('calling findByType() with an invalid component will fall back to "Unknown" for component name', async () => { const App = () => null; - const renderer = ReactTestRenderer.create(); + let renderer; + await act(() => { + renderer = ReactTestRenderer.create(); + }); const NonComponent = {}; expect(() => { diff --git a/packages/react-test-renderer/src/__tests__/ReactTestRenderer-test.js b/packages/react-test-renderer/src/__tests__/ReactTestRenderer-test.js index 0118fa53f3ad3..e6ed9fa11c291 100644 --- a/packages/react-test-renderer/src/__tests__/ReactTestRenderer-test.js +++ b/packages/react-test-renderer/src/__tests__/ReactTestRenderer-test.js @@ -13,7 +13,7 @@ let ReactDOM; let React; let ReactCache; let ReactTestRenderer; -let waitForAll; +let act; describe('ReactTestRenderer', () => { beforeEach(() => { @@ -26,32 +26,28 @@ describe('ReactTestRenderer', () => { ReactCache = require('react-cache'); ReactTestRenderer = require('react-test-renderer'); const InternalTestUtils = require('internal-test-utils'); - waitForAll = InternalTestUtils.waitForAll; + act = InternalTestUtils.act; }); - it('should warn if used to render a ReactDOM portal', () => { + it('should warn if used to render a ReactDOM portal', async () => { const container = document.createElement('div'); - expect(() => { - let error; - try { + let error; + + await expect(async () => { + await act(() => { ReactTestRenderer.create(ReactDOM.createPortal('foo', container)); - } catch (e) { - error = e; - } - // After the update throws, a subsequent render is scheduled to - // unmount the whole tree. This update also causes an error, so React - // throws an AggregateError. - const errors = error.errors; - expect(errors.length).toBe(2); - expect(errors[0].message.includes('indexOf is not a function')).toBe( - true, - ); - expect(errors[1].message.includes('indexOf is not a function')).toBe( - true, - ); + }).catch(e => (error = e)); }).toErrorDev('An invalid container has been provided.', { withoutStack: true, }); + + // After the update throws, a subsequent render is scheduled to + // unmount the whole tree. This update also causes an error, so React + // throws an AggregateError. + const errors = error.errors; + expect(errors.length).toBe(2); + expect(errors[0].message.includes('indexOf is not a function')).toBe(true); + expect(errors[1].message.includes('indexOf is not a function')).toBe(true); }); describe('timed out Suspense hidden subtrees should not be observable via toJSON', () => { @@ -84,16 +80,23 @@ describe('ReactTestRenderer', () => { ); }; - const root = ReactTestRenderer.create(); - PendingResources.initial('initial'); - await waitForAll([]); + let root; + await act(() => { + root = ReactTestRenderer.create(); + }); + await act(() => { + PendingResources.initial('initial'); + }); expect(root.toJSON()).toEqual('initial'); - root.update(); + await act(() => { + root.update(); + }); expect(root.toJSON()).toEqual('fallback'); - PendingResources.dynamic('dynamic'); - await waitForAll([]); + await act(() => { + PendingResources.dynamic('dynamic'); + }); expect(root.toJSON()).toEqual('dynamic'); }); @@ -108,16 +111,23 @@ describe('ReactTestRenderer', () => { ); }; - const root = ReactTestRenderer.create(); - PendingResources.initial('initial'); - await waitForAll([]); + let root; + await act(() => { + root = ReactTestRenderer.create(); + }); + await act(() => { + PendingResources.initial('initial'); + }); expect(root.toJSON().children).toEqual(['initial']); - root.update(); + await act(() => { + root.update(); + }); expect(root.toJSON().children).toEqual(['fallback']); - PendingResources.dynamic('dynamic'); - await waitForAll([]); + await act(() => { + PendingResources.dynamic('dynamic'); + }); expect(root.toJSON().children).toEqual(['dynamic']); }); }); diff --git a/packages/react-test-renderer/src/__tests__/ReactTestRendererAct-test.js b/packages/react-test-renderer/src/__tests__/ReactTestRendererAct-test.js index 2306a895b785c..ef27de6d0fe2f 100644 --- a/packages/react-test-renderer/src/__tests__/ReactTestRendererAct-test.js +++ b/packages/react-test-renderer/src/__tests__/ReactTestRendererAct-test.js @@ -16,6 +16,7 @@ describe('ReactTestRenderer.act()', () => { const InternalTestUtils = require('internal-test-utils'); assertLog = InternalTestUtils.assertLog; + global.IS_REACT_ACT_ENVIRONMENT = true; }); // @gate __DEV__ @@ -91,7 +92,10 @@ describe('ReactTestRenderer.act()', () => { }); return step; } - const root = ReactTestRenderer.create(null); + let root; + await act(() => { + root = ReactTestRenderer.create(null); + }); await act(async () => { root.update(); }); diff --git a/packages/react-test-renderer/src/__tests__/ReactTestRendererTraversal-test.js b/packages/react-test-renderer/src/__tests__/ReactTestRendererTraversal-test.js index d61f0a7c9075a..b4f79d2c55f19 100644 --- a/packages/react-test-renderer/src/__tests__/ReactTestRendererTraversal-test.js +++ b/packages/react-test-renderer/src/__tests__/ReactTestRendererTraversal-test.js @@ -13,6 +13,7 @@ const React = require('react'); let ReactTestRenderer; let Context; +let act; const RCTView = 'RCTView'; const View = props => ; @@ -21,6 +22,7 @@ describe('ReactTestRendererTraversal', () => { beforeEach(() => { jest.resetModules(); ReactTestRenderer = require('react-test-renderer'); + act = require('internal-test-utils').act; Context = React.createContext(null); }); @@ -68,8 +70,11 @@ describe('ReactTestRendererTraversal', () => { )); - it('initializes', () => { - const render = ReactTestRenderer.create(); + it('initializes', async () => { + let render; + await act(() => { + render = ReactTestRenderer.create(); + }); const hasFooProp = node => node.props.hasOwnProperty('foo'); // assert .props, .type and .parent attributes @@ -80,8 +85,12 @@ describe('ReactTestRendererTraversal', () => { expect(foo.children[0].parent).toBe(foo); }); - it('searches via .find() / .findAll()', () => { - const render = ReactTestRenderer.create(); + it('searches via .find() / .findAll()', async () => { + let render; + await act(() => { + render = ReactTestRenderer.create(); + }); + const hasFooProp = node => node.props.hasOwnProperty('foo'); const hasBarProp = node => node.props.hasOwnProperty('bar'); const hasBazProp = node => node.props.hasOwnProperty('baz'); @@ -135,8 +144,11 @@ describe('ReactTestRendererTraversal', () => { expect(itself.findAll(hasBazProp)).toHaveLength(2); }); - it('searches via .findByType() / .findAllByType()', () => { - const render = ReactTestRenderer.create(); + it('searches via .findByType() / .findAllByType()', async () => { + let render; + await act(() => { + render = ReactTestRenderer.create(); + }); expect(() => render.root.findByType(ExampleFn)).not.toThrow(); // 1 match expect(() => render.root.findByType(View)).not.toThrow(); // 1 match @@ -159,8 +171,11 @@ describe('ReactTestRendererTraversal', () => { expect(fn[0].findAllByType(View)).toHaveLength(1); }); - it('searches via .findByProps() / .findAllByProps()', () => { - const render = ReactTestRenderer.create(); + it('searches via .findByProps() / .findAllByProps()', async () => { + let render; + await act(() => { + render = ReactTestRenderer.create(); + }); const foo = 'foo'; const bar = 'bar'; const baz = 'baz'; @@ -182,8 +197,11 @@ describe('ReactTestRendererTraversal', () => { expect(render.root.findAllByProps({qux})).toHaveLength(3); }); - it('skips special nodes', () => { - const render = ReactTestRenderer.create(); + it('skips special nodes', async () => { + let render; + await act(() => { + render = ReactTestRenderer.create(); + }); expect(render.root.findAllByType(React.Fragment)).toHaveLength(0); expect(render.root.findAllByType(Context.Consumer)).toHaveLength(0); expect(render.root.findAllByType(Context.Provider)).toHaveLength(0); @@ -200,47 +218,62 @@ describe('ReactTestRendererTraversal', () => { expect(nestedViews[2].parent).toBe(expectedParent); }); - it('can have special nodes as roots', () => { + it('can have special nodes as roots', async () => { const FR = React.forwardRef((props, ref) =>
); - expect( - ReactTestRenderer.create( + + let render1; + await act(() => { + render1 = ReactTestRenderer.create(
, - ).root.findAllByType('div').length, - ).toBe(2); - expect( - ReactTestRenderer.create( + ); + }); + expect(render1.root.findAllByType('div').length).toBe(2); + + let render2; + await act(() => { + render2 = ReactTestRenderer.create( <>
, - ).root.findAllByType('div').length, - ).toBe(2); - expect( - ReactTestRenderer.create( + ); + }); + expect(render2.root.findAllByType('div').length).toBe(2); + + let render3; + await act(() => { + render3 = ReactTestRenderer.create(
, - ).root.findAllByType('div').length, - ).toBe(2); - expect( - ReactTestRenderer.create( + ); + }); + expect(render3.root.findAllByType('div').length).toBe(2); + + let render4; + await act(() => { + render4 = ReactTestRenderer.create(
, - ).root.findAllByType('div').length, - ).toBe(2); - expect( - ReactTestRenderer.create( + ); + }); + expect(render4.root.findAllByType('div').length).toBe(2); + + let render5; + await act(() => { + render5 = ReactTestRenderer.create(
, - ).root.findAllByType('div').length, - ).toBe(2); + ); + }); + expect(render5.root.findAllByType('div').length).toBe(2); }); }); diff --git a/packages/react/src/__tests__/ReactProfilerDevToolsIntegration-test.internal.js b/packages/react/src/__tests__/ReactProfilerDevToolsIntegration-test.internal.js index 2f28d2b60d43b..ba29cbbe461f8 100644 --- a/packages/react/src/__tests__/ReactProfilerDevToolsIntegration-test.internal.js +++ b/packages/react/src/__tests__/ReactProfilerDevToolsIntegration-test.internal.js @@ -19,6 +19,7 @@ describe('ReactProfiler DevTools integration', () => { let hook; let waitForAll; let waitFor; + let act; beforeEach(() => { global.__REACT_DEVTOOLS_GLOBAL_HOOK__ = hook = { @@ -39,6 +40,7 @@ describe('ReactProfiler DevTools integration', () => { const InternalTestUtils = require('internal-test-utils'); waitForAll = InternalTestUtils.waitForAll; waitFor = InternalTestUtils.waitFor; + act = InternalTestUtils.act; AdvanceTime = class extends React.Component { static defaultProps = { @@ -56,7 +58,7 @@ describe('ReactProfiler DevTools integration', () => { }; }); - it('should auto-Profile all fibers if the DevTools hook is detected', () => { + it('should auto-Profile all fibers if the DevTools hook is detected', async () => { const App = ({multiplier}) => { Scheduler.unstable_advanceTime(2); return ( @@ -71,7 +73,12 @@ describe('ReactProfiler DevTools integration', () => { }; const onRender = jest.fn(() => {}); - const rendered = ReactTestRenderer.create(); + let rendered; + await act(() => { + rendered = ReactTestRenderer.create(, { + unstable_isConcurrent: true, + }); + }); expect(hook.onCommitFiberRoot).toHaveBeenCalledTimes(1); @@ -90,7 +97,9 @@ describe('ReactProfiler DevTools integration', () => { 12, ); - rendered.update(); + await act(() => { + rendered.update(); + }); // Measure observable timing using the Profiler component. // The time spent in App (above the Profiler) won't be included in the durations, @@ -107,14 +116,18 @@ describe('ReactProfiler DevTools integration', () => { ); }); - it('should reset the fiber stack correctly after an error when profiling host roots', () => { + it('should reset the fiber stack correctly after an error when profiling host roots', async () => { Scheduler.unstable_advanceTime(20); - const rendered = ReactTestRenderer.create( -
- -
, - ); + let rendered; + await act(() => { + rendered = ReactTestRenderer.create( +
+ +
, + {unstable_isConcurrent: true}, + ); + }); Scheduler.unstable_advanceTime(20); @@ -122,22 +135,26 @@ describe('ReactProfiler DevTools integration', () => { throw new Error('Oops!'); } - expect(() => { - rendered.update( - - - , - ); - }).toThrow('Oops!'); + await expect(async () => { + await act(() => { + rendered.update( + + + , + ); + }); + }).rejects.toThrow('Oops!'); Scheduler.unstable_advanceTime(20); - // But this should render correctly, if the profiler's fiber stack has been reset. - rendered.update( -
- -
, - ); + await act(() => { + // But this should render correctly, if the profiler's fiber stack has been reset. + rendered.update( +
+ +
, + ); + }); // Measure unobservable timing required by the DevTools profiler. // At this point, the base time should include only the most recent (not failed) render. @@ -154,7 +171,10 @@ describe('ReactProfiler DevTools integration', () => { return text; } - const root = ReactTestRenderer.create(null, {unstable_isConcurrent: true}); + let root; + await act(() => { + root = ReactTestRenderer.create(null, {unstable_isConcurrent: true}); + }); // Commit something root.update(); From 4da108a99b8a005d217e7fc3fbf7056cec37daa1 Mon Sep 17 00:00:00 2001 From: Jack Pope Date: Tue, 26 Mar 2024 18:30:17 -0400 Subject: [PATCH 2/3] fix test --- packages/react-test-renderer/src/ReactTestRenderer.js | 2 +- .../src/__tests__/ReactTestRenderer-test.internal.js | 3 +-- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/packages/react-test-renderer/src/ReactTestRenderer.js b/packages/react-test-renderer/src/ReactTestRenderer.js index 8b8dd59d95d08..56e37850da157 100644 --- a/packages/react-test-renderer/src/ReactTestRenderer.js +++ b/packages/react-test-renderer/src/ReactTestRenderer.js @@ -497,7 +497,7 @@ function create( // $FlowFixMe[incompatible-type] found when upgrading Flow createNodeMock = options.createNodeMock; } - if (!isConcurrentOnly) { + if (isConcurrentOnly === false) { isConcurrent = options.unstable_isConcurrent; } if (options.unstable_strictMode === true) { diff --git a/packages/react-test-renderer/src/__tests__/ReactTestRenderer-test.internal.js b/packages/react-test-renderer/src/__tests__/ReactTestRenderer-test.internal.js index 107ec757c6338..ec6cf159143f1 100644 --- a/packages/react-test-renderer/src/__tests__/ReactTestRenderer-test.internal.js +++ b/packages/react-test-renderer/src/__tests__/ReactTestRenderer-test.internal.js @@ -80,12 +80,12 @@ describe('ReactTestRenderer', () => { 'Warning: react-test-renderer is deprecated. See https://react.dev/warnings/react-test-renderer', {withoutStack: true}, ); - global.IS_REACT_NATIVE_TEST_ENVIRONMENT = false; }); describe('root tags', () => { let createContainerSpy; beforeEach(() => { + global.IS_REACT_NATIVE_TEST_ENVIRONMENT = false; createContainerSpy = jest.spyOn(Reconciler, 'createContainer'); }); @@ -126,7 +126,6 @@ describe('ReactTestRenderer', () => { global.IS_REACT_NATIVE_TEST_ENVIRONMENT = true; ReactTestRenderer.create(
); expectTag(LegacyRoot); - global.IS_REACT_NATIVE_TEST_ENVIRONMENT = false; }); }); From a5b7ae13d0ebdea0988a5c04011f6ba6d1488575 Mon Sep 17 00:00:00 2001 From: Jack Pope Date: Tue, 26 Mar 2024 18:43:58 -0400 Subject: [PATCH 3/3] empty