diff --git a/packages/react-reconciler/src/__tests__/ReactHooks-test.internal.js b/packages/react-reconciler/src/__tests__/ReactHooks-test.internal.js index c43d4ac3e1e01..ce0a45be89f54 100644 --- a/packages/react-reconciler/src/__tests__/ReactHooks-test.internal.js +++ b/packages/react-reconciler/src/__tests__/ReactHooks-test.internal.js @@ -25,7 +25,6 @@ let waitForThrow; describe('ReactHooks', () => { beforeEach(() => { jest.resetModules(); - ReactFeatureFlags = require('shared/ReactFeatureFlags'); React = require('react'); @@ -42,16 +41,20 @@ describe('ReactHooks', () => { if (__DEV__) { // useDebugValue is a DEV-only hook - it('useDebugValue throws when used in a class component', () => { + it('useDebugValue throws when used in a class component', async () => { class Example extends React.Component { render() { React.useDebugValue('abc'); return null; } } - expect(() => { - ReactTestRenderer.create(); - }).toThrow( + await expect(async () => { + await act(() => { + ReactTestRenderer.create(, { + unstable_isConcurrent: true, + }); + }); + }).rejects.toThrow( 'Invalid hook call. Hooks can only be called inside of the body of a function component. This could happen' + ' for one of the following reasons:\n' + '1. You might have mismatching versions of React and the renderer (such as React DOM)\n' + @@ -569,7 +572,7 @@ describe('ReactHooks', () => { expect(root).toMatchRenderedOutput('105'); }); - it('warns about variable number of dependencies', () => { + it('warns about variable number of dependencies', async () => { const {useLayoutEffect} = React; function App(props) { useLayoutEffect(() => { @@ -577,10 +580,17 @@ describe('ReactHooks', () => { }, props.dependencies); return props.dependencies; } - const root = ReactTestRenderer.create(); + let root; + await act(() => { + root = ReactTestRenderer.create(, { + unstable_isConcurrent: true, + }); + }); assertLog(['Did commit: A']); - expect(() => { - root.update(); + await expect(async () => { + await act(() => { + root.update(); + }); }).toErrorDev([ 'Warning: The final argument passed to useLayoutEffect changed size ' + 'between renders. The order and size of this array must remain ' + @@ -590,7 +600,7 @@ describe('ReactHooks', () => { ]); }); - it('warns if switching from dependencies to no dependencies', () => { + it('warns if switching from dependencies to no dependencies', async () => { const {useMemo} = React; function App({text, hasDeps}) { const resolvedText = useMemo( @@ -603,13 +613,20 @@ describe('ReactHooks', () => { return resolvedText; } - const root = ReactTestRenderer.create(null); - root.update(); + let root; + await act(() => { + root = ReactTestRenderer.create(null, {unstable_isConcurrent: true}); + }); + await act(() => { + root.update(); + }); assertLog(['Compute']); expect(root).toMatchRenderedOutput('HELLO'); - expect(() => { - root.update(); + await expect(async () => { + await act(() => { + root.update(); + }); }).toErrorDev([ 'Warning: useMemo received a final argument during this render, but ' + 'not during the previous render. Even though the final argument is ' + @@ -630,7 +647,9 @@ describe('ReactHooks', () => { await expect(async () => { await act(() => { - ReactTestRenderer.create(); + ReactTestRenderer.create(, { + unstable_isConcurrent: true, + }); }); }).toErrorDev([ 'Warning: useEffect received a final argument that is not an array (instead, received `string`). ' + @@ -644,7 +663,9 @@ describe('ReactHooks', () => { ]); await expect(async () => { await act(() => { - ReactTestRenderer.create(); + ReactTestRenderer.create(, { + unstable_isConcurrent: true, + }); }); }).toErrorDev([ 'Warning: useEffect received a final argument that is not an array (instead, received `number`). ' + @@ -658,7 +679,9 @@ describe('ReactHooks', () => { ]); await expect(async () => { await act(() => { - ReactTestRenderer.create(); + ReactTestRenderer.create(, { + unstable_isConcurrent: true, + }); }); }).toErrorDev([ 'Warning: useEffect received a final argument that is not an array (instead, received `object`). ' + @@ -672,13 +695,19 @@ describe('ReactHooks', () => { ]); await act(() => { - ReactTestRenderer.create(); - ReactTestRenderer.create(); - ReactTestRenderer.create(); + ReactTestRenderer.create(, { + unstable_isConcurrent: true, + }); + ReactTestRenderer.create(, { + unstable_isConcurrent: true, + }); + ReactTestRenderer.create(, { + unstable_isConcurrent: true, + }); }); }); - it('warns if deps is not an array for useImperativeHandle', () => { + it('warns if deps is not an array for useImperativeHandle', async () => { const {useImperativeHandle} = React; const App = React.forwardRef((props, ref) => { @@ -686,15 +715,31 @@ describe('ReactHooks', () => { return null; }); - expect(() => { - ReactTestRenderer.create(); + await expect(async () => { + await act(() => { + ReactTestRenderer.create(, { + unstable_isConcurrent: true, + }); + }); }).toErrorDev([ 'Warning: useImperativeHandle received a final argument that is not an array (instead, received `string`). ' + 'When specified, the final argument must be an array.', ]); - ReactTestRenderer.create(); - ReactTestRenderer.create(); - ReactTestRenderer.create(); + await act(() => { + ReactTestRenderer.create(, { + unstable_isConcurrent: true, + }); + }); + await act(() => { + ReactTestRenderer.create(, { + unstable_isConcurrent: true, + }); + }); + await act(() => { + ReactTestRenderer.create(, { + unstable_isConcurrent: true, + }); + }); }); it('does not forget render phase useState updates inside an effect', async () => { @@ -713,7 +758,7 @@ describe('ReactHooks', () => { return counter; } - const root = ReactTestRenderer.create(null); + const root = ReactTestRenderer.create(null, {unstable_isConcurrent: true}); await act(() => { root.update(); }); @@ -737,7 +782,7 @@ describe('ReactHooks', () => { return counter; } - const root = ReactTestRenderer.create(null); + const root = ReactTestRenderer.create(null, {unstable_isConcurrent: true}); await act(() => { root.update(); }); @@ -760,14 +805,14 @@ describe('ReactHooks', () => { return counter; } - const root = ReactTestRenderer.create(null); + const root = ReactTestRenderer.create(null, {unstable_isConcurrent: true}); await act(() => { root.update(); }); expect(root).toMatchRenderedOutput('4'); }); - it('warns for bad useImperativeHandle first arg', () => { + it('warns for bad useImperativeHandle first arg', async () => { const {useImperativeHandle} = React; function App() { useImperativeHandle({ @@ -776,10 +821,12 @@ describe('ReactHooks', () => { return null; } - expect(() => { - expect(() => { - ReactTestRenderer.create(); - }).toThrow('create is not a function'); + await expect(async () => { + await expect(async () => { + await act(() => { + ReactTestRenderer.create(, {unstable_isConcurrent: true}); + }); + }).rejects.toThrow('create is not a function'); }).toErrorDev([ 'Expected useImperativeHandle() first argument to either be a ' + 'ref callback or React.createRef() object. ' + @@ -789,7 +836,7 @@ describe('ReactHooks', () => { ]); }); - it('warns for bad useImperativeHandle second arg', () => { + it('warns for bad useImperativeHandle second arg', async () => { const {useImperativeHandle} = React; const App = React.forwardRef((props, ref) => { useImperativeHandle(ref, { @@ -798,8 +845,10 @@ describe('ReactHooks', () => { return null; }); - expect(() => { - ReactTestRenderer.create(); + await expect(async () => { + await act(() => { + ReactTestRenderer.create(, {unstable_isConcurrent: true}); + }); }).toErrorDev([ 'Expected useImperativeHandle() second argument to be a function ' + 'that creates a handle. Instead received: object.', @@ -807,7 +856,7 @@ describe('ReactHooks', () => { }); // https://github.com/facebook/react/issues/14022 - it('works with ReactDOMServer calls inside a component', () => { + it('works with ReactDOMServer calls inside a component', async () => { const {useState} = React; function App(props) { const markup1 = ReactDOMServer.renderToString(

hello

); @@ -815,11 +864,14 @@ describe('ReactHooks', () => { const [counter] = useState(0); return markup1 + counter + markup2; } - const root = ReactTestRenderer.create(); + let root; + await act(() => { + root = ReactTestRenderer.create(, {unstable_isConcurrent: true}); + }); expect(root.toJSON()).toMatchSnapshot(); }); - it("throws when calling hooks inside .memo's compare function", () => { + it("throws when calling hooks inside .memo's compare function", async () => { const {useState} = React; function App() { useState(0); @@ -830,9 +882,18 @@ describe('ReactHooks', () => { return false; }); - const root = ReactTestRenderer.create(); + let root; + await act(() => { + root = ReactTestRenderer.create(, { + unstable_isConcurrent: true, + }); + }); // trying to render again should trigger comparison and throw - expect(() => root.update()).toThrow( + await expect( + act(() => { + root.update(); + }), + ).rejects.toThrow( 'Invalid hook call. Hooks can only be called inside of the body of a function component. This could happen for' + ' one of the following reasons:\n' + '1. You might have mismatching versions of React and the renderer (such as React DOM)\n' + @@ -841,7 +902,11 @@ describe('ReactHooks', () => { 'See https://react.dev/link/invalid-hook-call for tips about how to debug and fix this problem.', ); // the next round, it does a fresh mount, so should render - expect(() => root.update()).not.toThrow( + await expect( + act(() => { + root.update(); + }), + ).resolves.not.toThrow( 'Invalid hook call. Hooks can only be called inside of the body of a function component. This could happen for' + ' one of the following reasons:\n' + '1. You might have mismatching versions of React and the renderer (such as React DOM)\n' + @@ -850,7 +915,11 @@ describe('ReactHooks', () => { 'See https://react.dev/link/invalid-hook-call for tips about how to debug and fix this problem.', ); // and then again, fail - expect(() => root.update()).toThrow( + await expect( + act(() => { + root.update(); + }), + ).rejects.toThrow( 'Invalid hook call. Hooks can only be called inside of the body of a function component. This could happen for' + ' one of the following reasons:\n' + '1. You might have mismatching versions of React and the renderer (such as React DOM)\n' + @@ -860,7 +929,7 @@ describe('ReactHooks', () => { ); }); - it('warns when calling hooks inside useMemo', () => { + it('warns when calling hooks inside useMemo', async () => { const {useMemo, useState} = React; function App() { useMemo(() => { @@ -868,12 +937,16 @@ describe('ReactHooks', () => { }); return null; } - expect(() => ReactTestRenderer.create()).toErrorDev( + await expect(async () => { + await act(() => { + ReactTestRenderer.create(, {unstable_isConcurrent: true}); + }); + }).toErrorDev( 'Do not call Hooks inside useEffect(...), useMemo(...), or other built-in Hooks.', ); }); - it('warns when reading context inside useMemo', () => { + it('warns when reading context inside useMemo', async () => { const {useMemo, createContext} = React; const ReactCurrentDispatcher = React.__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED @@ -886,12 +959,14 @@ describe('ReactHooks', () => { }, []); } - expect(() => ReactTestRenderer.create()).toErrorDev( - 'Context can only be read while React is rendering', - ); + await expect(async () => { + await act(() => { + ReactTestRenderer.create(, {unstable_isConcurrent: true}); + }); + }).toErrorDev('Context can only be read while React is rendering'); }); - it('warns when reading context inside useMemo after reading outside it', () => { + it('warns when reading context inside useMemo after reading outside it', async () => { const {useMemo, createContext} = React; const ReactCurrentDispatcher = React.__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED @@ -908,9 +983,11 @@ describe('ReactHooks', () => { }, []); } - expect(() => ReactTestRenderer.create()).toErrorDev( - 'Context can only be read while React is rendering', - ); + await expect(async () => { + await act(() => { + ReactTestRenderer.create(, {unstable_isConcurrent: true}); + }); + }).toErrorDev('Context can only be read while React is rendering'); expect(firstRead).toBe('light'); expect(secondRead).toBe('light'); }); @@ -931,14 +1008,14 @@ describe('ReactHooks', () => { } await act(async () => { - ReactTestRenderer.create(); + ReactTestRenderer.create(, {unstable_isConcurrent: true}); // The exact message doesn't matter, just make sure we don't allow this await waitForThrow('Context can only be read while React is rendering'); }); }); // Throws because there's no runtime cost for being strict here. - it('throws when reading context inside useLayoutEffect', () => { + it('throws when reading context inside useLayoutEffect', async () => { const {useLayoutEffect, createContext} = React; const ReactCurrentDispatcher = React.__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED @@ -952,13 +1029,17 @@ describe('ReactHooks', () => { return null; } - expect(() => ReactTestRenderer.create()).toThrow( + await expect( + act(() => { + ReactTestRenderer.create(, {unstable_isConcurrent: true}); + }), + ).rejects.toThrow( // The exact message doesn't matter, just make sure we don't allow this 'Context can only be read while React is rendering', ); }); - it('warns when reading context inside useReducer', () => { + it('warns when reading context inside useReducer', async () => { const {useReducer, createContext} = React; const ReactCurrentDispatcher = React.__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED @@ -976,13 +1057,15 @@ describe('ReactHooks', () => { return null; } - expect(() => ReactTestRenderer.create()).toErrorDev([ - 'Context can only be read while React is rendering', - ]); + await expect(async () => { + await act(() => { + ReactTestRenderer.create(, {unstable_isConcurrent: true}); + }); + }).toErrorDev(['Context can only be read while React is rendering']); }); // Edge case. - it('warns when reading context inside eager useReducer', () => { + it('warns when reading context inside eager useReducer', async () => { const {useState, createContext} = React; const ThemeContext = createContext('light'); @@ -1007,20 +1090,23 @@ describe('ReactHooks', () => { } } - expect(() => - ReactTestRenderer.create( - <> - - - , - ), - ).toErrorDev([ + await expect(async () => { + await act(() => { + ReactTestRenderer.create( + <> + + + , + {unstable_isConcurrent: true}, + ); + }); + }).toErrorDev([ 'Context can only be read while React is rendering', 'Cannot update a component (`Fn`) while rendering a different component (`Cls`).', ]); }); - it('warns when calling hooks inside useReducer', () => { + it('warns when calling hooks inside useReducer', async () => { const {useReducer, useState, useRef} = React; function App() { @@ -1035,10 +1121,12 @@ describe('ReactHooks', () => { return value; } - expect(() => { - expect(() => { - ReactTestRenderer.create(); - }).toThrow( + await expect(async () => { + await expect(async () => { + await act(() => { + ReactTestRenderer.create(, {unstable_isConcurrent: true}); + }); + }).rejects.toThrow( 'Update hook called on initial render. This is likely a bug in React. Please file an issue.', ); }).toErrorDev([ @@ -1051,10 +1139,11 @@ describe('ReactHooks', () => { '1. useReducer useReducer\n' + '2. useState useRef\n' + ' ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n\n', + 'Do not call Hooks inside useEffect(...), useMemo(...), or other built-in Hooks', ]); }); - it("warns when calling hooks inside useState's initialize function", () => { + it("warns when calling hooks inside useState's initialize function", async () => { const {useState, useRef} = React; function App() { useState(() => { @@ -1063,7 +1152,11 @@ describe('ReactHooks', () => { }); return null; } - expect(() => ReactTestRenderer.create()).toErrorDev( + await expect(async () => { + await act(() => { + ReactTestRenderer.create(, {unstable_isConcurrent: true}); + }); + }).toErrorDev( 'Do not call Hooks inside useEffect(...), useMemo(...), or other built-in Hooks.', ); }); @@ -1097,15 +1190,21 @@ describe('ReactHooks', () => { } } - expect(() => { - ReactTestRenderer.create( - - - , - ); + await expect(async () => { + await act(() => { + ReactTestRenderer.create( + + + , + {unstable_isConcurrent: true}, + ); + }); }).toErrorDev([ 'Context can only be read while React is rendering', 'Do not call Hooks inside useEffect(...), useMemo(...), or other built-in Hooks', + + 'Context can only be read while React is rendering', + 'Do not call Hooks inside useEffect(...), useMemo(...), or other built-in Hooks', ]); function Valid() { @@ -1128,23 +1227,29 @@ describe('ReactHooks', () => { // Verify it doesn't think we're still inside a Hook. // Should have no warnings. await act(() => { - ReactTestRenderer.create(); + ReactTestRenderer.create(, {unstable_isConcurrent: true}); }); // Verify warnings don't get permanently disabled. - expect(() => { - ReactTestRenderer.create( - - - , - ); + await expect(async () => { + await act(() => { + ReactTestRenderer.create( + + + , + {unstable_isConcurrent: true}, + ); + }); }).toErrorDev([ 'Context can only be read while React is rendering', 'Do not call Hooks inside useEffect(...), useMemo(...), or other built-in Hooks', + + 'Context can only be read while React is rendering', + 'Do not call Hooks inside useEffect(...), useMemo(...), or other built-in Hooks', ]); }); - it('warns when reading context inside useMemo', () => { + it('warns when reading context inside useMemo', async () => { const {useMemo, createContext} = React; const ReactCurrentDispatcher = React.__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED @@ -1157,12 +1262,14 @@ describe('ReactHooks', () => { }, []); } - expect(() => ReactTestRenderer.create()).toErrorDev( - 'Context can only be read while React is rendering', - ); + await expect(async () => { + await act(() => { + ReactTestRenderer.create(, {unstable_isConcurrent: true}); + }); + }).toErrorDev('Context can only be read while React is rendering'); }); - it('double-invokes components with Hooks in Strict Mode', () => { + it('double-invokes components with Hooks in Strict Mode', async () => { ReactFeatureFlags.debugRenderPhaseSideEffectsForStrictMode = true; const {useState, StrictMode} = React; @@ -1211,74 +1318,105 @@ describe('ReactHooks', () => { }; } - const renderer = ReactTestRenderer.create(null); + let renderer; + await act(() => { + renderer = ReactTestRenderer.create(null, {unstable_isConcurrent: true}); + }); renderCount = 0; - renderer.update(); + await act(() => { + renderer.update(); + }); expect(renderCount).toBe(1); renderCount = 0; - renderer.update(); + await act(() => { + renderer.update(); + }); expect(renderCount).toBe(1); renderCount = 0; - renderer.update( - - - , - ); + await act(() => { + renderer.update( + + + , + ); + }); expect(renderCount).toBe(__DEV__ ? 2 : 1); renderCount = 0; - renderer.update( - - - , - ); + await act(() => { + renderer.update( + + + , + ); + }); expect(renderCount).toBe(__DEV__ ? 2 : 1); renderCount = 0; - renderer.update(); + await act(() => { + renderer.update(); + }); expect(renderCount).toBe(1); renderCount = 0; - renderer.update(); + await act(() => { + renderer.update(); + }); expect(renderCount).toBe(1); renderCount = 0; - renderer.update( - - - , - ); + await act(() => { + renderer.update( + + + , + ); + }); expect(renderCount).toBe(__DEV__ ? 2 : 1); renderCount = 0; - renderer.update( - - - , - ); + await act(() => { + renderer.update( + + + , + ); + }); expect(renderCount).toBe(__DEV__ ? 2 : 1); renderCount = 0; - renderer.update(); + await act(() => { + renderer.update(); + }); expect(renderCount).toBe(1); renderCount = 0; - renderer.update(); + await act(() => { + renderer.update(); + }); expect(renderCount).toBe(1); renderCount = 0; - renderer.update( - - - , - ); + await act(() => { + renderer.update( + + + , + ); + }); expect(renderCount).toBe(__DEV__ ? 2 : 1); renderCount = 0; - renderer.update( - - - , - ); + await act(() => { + renderer.update( + + + , + ); + }); expect(renderCount).toBe(__DEV__ ? 2 : 1); if (!require('shared/ReactFeatureFlags').disableModulePatternComponents) { renderCount = 0; - expect(() => renderer.update()).toErrorDev( + await expect(async () => { + await act(() => { + renderer.update(); + }); + }).toErrorDev( 'Warning: The component appears to be a function component that returns a class instance. ' + 'Change Factory to a class that extends React.Component instead. ' + "If you can't use a class try assigning the prototype on the function as a workaround. " + @@ -1287,90 +1425,120 @@ describe('ReactHooks', () => { ); expect(renderCount).toBe(1); renderCount = 0; - renderer.update(); + await act(() => { + renderer.update(); + }); expect(renderCount).toBe(1); renderCount = 0; - renderer.update( - - - , - ); + await act(() => { + renderer.update( + + + , + ); + }); expect(renderCount).toBe(__DEV__ ? 2 : 1); // Treated like a class renderCount = 0; - renderer.update( - - - , - ); + await act(() => { + renderer.update( + + + , + ); + }); expect(renderCount).toBe(__DEV__ ? 2 : 1); // Treated like a class } renderCount = 0; - renderer.update(); + await act(() => { + renderer.update(); + }); expect(renderCount).toBe(1); renderCount = 0; - renderer.update(); + await act(() => { + renderer.update(); + }); expect(renderCount).toBe(1); renderCount = 0; - renderer.update( - - - , - ); + await act(() => { + renderer.update( + + + , + ); + }); expect(renderCount).toBe(__DEV__ ? 2 : 1); // Has Hooks renderCount = 0; - renderer.update( - - - , - ); + await act(() => { + renderer.update( + + + , + ); + }); expect(renderCount).toBe(__DEV__ ? 2 : 1); // Has Hooks renderCount = 0; - renderer.update(); + await act(() => { + renderer.update(); + }); expect(renderCount).toBe(1); renderCount = 0; - renderer.update(); + await act(() => { + renderer.update(); + }); expect(renderCount).toBe(1); renderCount = 0; - renderer.update( - - - , - ); + await act(() => { + renderer.update( + + + , + ); + }); expect(renderCount).toBe(__DEV__ ? 2 : 1); // Has Hooks renderCount = 0; - renderer.update( - - - , - ); + await act(() => { + renderer.update( + + + , + ); + }); expect(renderCount).toBe(__DEV__ ? 2 : 1); // Has Hooks renderCount = 0; - renderer.update(); + await act(() => { + renderer.update(); + }); expect(renderCount).toBe(1); renderCount = 0; - renderer.update(); + await act(() => { + renderer.update(); + }); expect(renderCount).toBe(1); renderCount = 0; - renderer.update( - - - , - ); + await act(() => { + renderer.update( + + + , + ); + }); expect(renderCount).toBe(__DEV__ ? 2 : 1); // Has Hooks renderCount = 0; - renderer.update( - - - , - ); + await act(() => { + renderer.update( + + + , + ); + }); expect(renderCount).toBe(__DEV__ ? 2 : 1); // Has Hooks }); - it('double-invokes useMemo in DEV StrictMode despite []', () => { + it('double-invokes useMemo in DEV StrictMode despite []', async () => { ReactFeatureFlags.debugRenderPhaseSideEffectsForStrictMode = true; const {useMemo, StrictMode} = React; @@ -1383,11 +1551,14 @@ describe('ReactHooks', () => { } useMemoCount = 0; - ReactTestRenderer.create( - - - , - ); + await act(() => { + ReactTestRenderer.create( + + + , + {unstable_isConcurrent: true}, + ); + }); expect(useMemoCount).toBe(__DEV__ ? 2 : 1); // Has Hooks }); @@ -1482,7 +1653,9 @@ describe('ReactHooks', () => { } let root; await act(() => { - root = ReactTestRenderer.create(); + root = ReactTestRenderer.create(, { + unstable_isConcurrent: true, + }); }); await expect(async () => { try { @@ -1531,7 +1704,9 @@ describe('ReactHooks', () => { } let root; await act(() => { - root = ReactTestRenderer.create(); + root = ReactTestRenderer.create(, { + unstable_isConcurrent: true, + }); }); await expect(async () => { @@ -1585,21 +1760,23 @@ describe('ReactHooks', () => { } let root; await act(() => { - root = ReactTestRenderer.create(); + root = ReactTestRenderer.create(, { + unstable_isConcurrent: true, + }); }); - await act(() => { - expect(() => { + await expect(async () => { + await act(() => { root.update(); - }).toThrow('Rendered fewer hooks than expected. '); - }); + }); + }).rejects.toThrow('Rendered fewer hooks than expected. '); }); }); it( 'warns on using differently ordered hooks ' + '(useImperativeHandleHelper, useMemoHelper) on subsequent renders', - () => { + async () => { function App(props) { /* eslint-disable no-unused-vars */ if (props.update) { @@ -1614,15 +1791,19 @@ describe('ReactHooks', () => { return null; /* eslint-enable no-unused-vars */ } - const root = ReactTestRenderer.create(); - expect(() => { - try { + let root; + await act(() => { + root = ReactTestRenderer.create(, { + unstable_isConcurrent: true, + }); + }); + await expect(async () => { + await act(() => { root.update(); - } catch (error) { - // Swapping certain types of hooks will cause runtime errors. - // This is okay as far as this test is concerned. - // We just want to verify that warnings are always logged. - } + }).catch(e => {}); + // Swapping certain types of hooks will cause runtime errors. + // This is okay as far as this test is concerned. + // We just want to verify that warnings are always logged. }).toErrorDev([ 'Warning: React has detected a change in the order of Hooks called by App. ' + 'This will lead to bugs and errors if not fixed. For more information, ' + @@ -1638,11 +1819,13 @@ describe('ReactHooks', () => { ]); // further warnings for this component are silenced - root.update(); + await act(() => { + root.update(); + }); }, ); - it('detects a bad hook order even if the component throws', () => { + it('detects a bad hook order even if the component throws', async () => { const {useState, useReducer} = React; function useCustomHook() { useState(0); @@ -1660,11 +1843,18 @@ describe('ReactHooks', () => { return null; /* eslint-enable no-unused-vars */ } - const root = ReactTestRenderer.create(); - expect(() => { - expect(() => root.update()).toThrow( - 'custom error', - ); + let root; + await act(() => { + root = ReactTestRenderer.create(, { + unstable_isConcurrent: true, + }); + }); + await expect(async () => { + await expect(async () => { + await act(() => { + root.update(); + }); + }).rejects.toThrow('custom error'); }).toErrorDev([ 'Warning: React has detected a change in the order of Hooks called by App. ' + 'This will lead to bugs and errors if not fixed. For more information, ' + @@ -1696,14 +1886,17 @@ describe('ReactHooks', () => { return null; } - expect(() => { - ReactTestRenderer.create( - <> - - - , - ); - }).toThrow('Hello'); + await expect(async () => { + await act(() => { + ReactTestRenderer.create( + <> + + + , + {unstable_isConcurrent: true}, + ); + }); + }).rejects.toThrow('Hello'); if (__DEV__) { expect(console.error).toHaveBeenCalledTimes(2); @@ -1752,18 +1945,17 @@ describe('ReactHooks', () => { } await act(() => { - ReactTestRenderer.create(); + ReactTestRenderer.create(, {unstable_isConcurrent: true}); }); - expect(() => { - globalListener(); - globalListener(); - }).toErrorDev([ - 'An update to C inside a test was not wrapped in act', - 'An update to C inside a test was not wrapped in act', - // Note: should *not* warn about updates on unmounted component. - // Because there's no way for component to know it got unmounted. - ]); + // Note: should *not* warn about updates on unmounted component. + // Because there's no way for component to know it got unmounted. + await expect( + act(() => { + globalListener(); + globalListener(); + }), + ).resolves.not.toThrow(); }); // Regression test for https://github.com/facebook/react/issues/14790 @@ -1771,11 +1963,12 @@ describe('ReactHooks', () => { const {Suspense, useState} = React; let wasSuspended = false; + let resolve; function trySuspend() { if (!wasSuspended) { - throw new Promise(resolve => { + throw new Promise(r => { wasSuspended = true; - resolve(); + resolve = r; }); } } @@ -1787,14 +1980,17 @@ describe('ReactHooks', () => { } const Wrapper = React.memo(Child); - const root = ReactTestRenderer.create( - - - , - ); + let root; + await act(() => { + root = ReactTestRenderer.create( + + + , + {unstable_isConcurrent: true}, + ); + }); expect(root).toMatchRenderedOutput('loading'); - await Promise.resolve(); - await waitForAll([]); + await act(resolve); expect(root).toMatchRenderedOutput('hello'); }); @@ -1803,11 +1999,12 @@ describe('ReactHooks', () => { const {Suspense, useState} = React; let wasSuspended = false; + let resolve; function trySuspend() { if (!wasSuspended) { - throw new Promise(resolve => { + throw new Promise(r => { wasSuspended = true; - resolve(); + resolve = r; }); } } @@ -1819,14 +2016,17 @@ describe('ReactHooks', () => { } const Wrapper = React.forwardRef(render); - const root = ReactTestRenderer.create( - - - , - ); + let root; + await act(() => { + root = ReactTestRenderer.create( + + + , + {unstable_isConcurrent: true}, + ); + }); expect(root).toMatchRenderedOutput('loading'); - await Promise.resolve(); - await waitForAll([]); + await act(resolve); expect(root).toMatchRenderedOutput('hello'); }); @@ -1835,11 +2035,12 @@ describe('ReactHooks', () => { const {Suspense, useState} = React; let wasSuspended = false; + let resolve; function trySuspend() { if (!wasSuspended) { - throw new Promise(resolve => { + throw new Promise(r => { wasSuspended = true; - resolve(); + resolve = r; }); } } @@ -1851,14 +2052,17 @@ describe('ReactHooks', () => { } const Wrapper = React.memo(React.forwardRef(render)); - const root = ReactTestRenderer.create( - - - , - ); + let root; + await act(() => { + root = ReactTestRenderer.create( + + + , + {unstable_isConcurrent: true}, + ); + }); expect(root).toMatchRenderedOutput('loading'); - await Promise.resolve(); - await waitForAll([]); + await act(resolve); expect(root).toMatchRenderedOutput('hello'); }); @@ -1906,6 +2110,7 @@ describe('ReactHooks', () => { , + {unstable_isConcurrent: true}, ); });