diff --git a/packages/react-refresh/src/__tests__/ReactFresh-test.js b/packages/react-refresh/src/__tests__/ReactFresh-test.js index 94e170e3346e6..38b74563c30a7 100644 --- a/packages/react-refresh/src/__tests__/ReactFresh-test.js +++ b/packages/react-refresh/src/__tests__/ReactFresh-test.js @@ -17,13 +17,13 @@ let ReactDOMClient; let ReactFreshRuntime; let Scheduler; let act; -let internalAct; let createReactClass; let waitFor; let assertLog; describe('ReactFresh', () => { let container; + let root; beforeEach(() => { if (__DEV__) { @@ -34,8 +34,7 @@ describe('ReactFresh', () => { ReactDOM = require('react-dom'); ReactDOMClient = require('react-dom/client'); Scheduler = require('scheduler'); - act = React.act; - internalAct = require('internal-test-utils').act; + act = require('internal-test-utils').act; const InternalTestUtils = require('internal-test-utils'); waitFor = InternalTestUtils.waitFor; @@ -47,6 +46,7 @@ describe('ReactFresh', () => { new React.Component().updater, ); container = document.createElement('div'); + root = ReactDOMClient.createRoot(container); document.body.appendChild(container); } }); @@ -63,10 +63,10 @@ describe('ReactFresh', () => { return Component; } - function render(version, props) { + async function render(version, props) { const Component = version(); - act(() => { - ReactDOM.render(, container); + await act(() => { + root.render(); }); return Component; } @@ -99,9 +99,9 @@ describe('ReactFresh', () => { ); } - it('can preserve state for compatible types', () => { + it('can preserve state for compatible types', async () => { if (__DEV__) { - const HelloV1 = render(() => { + const HelloV1 = await render(() => { function Hello() { const [val, setVal] = React.useState(0); return ( @@ -118,7 +118,7 @@ describe('ReactFresh', () => { const el = container.firstChild; expect(el.textContent).toBe('0'); expect(el.style.color).toBe('blue'); - act(() => { + await act(() => { el.dispatchEvent(new MouseEvent('click', {bubbles: true})); }); expect(el.textContent).toBe('1'); @@ -143,7 +143,7 @@ describe('ReactFresh', () => { expect(el.style.color).toBe('red'); // Bump the state again. - act(() => { + await act(() => { el.dispatchEvent(new MouseEvent('click', {bubbles: true})); }); expect(container.firstChild).toBe(el); @@ -153,15 +153,15 @@ describe('ReactFresh', () => { // Perform top-down renders with both fresh and stale types. // Neither should change the state or color. // They should always resolve to the latest version. - render(() => HelloV1); - render(() => HelloV2); - render(() => HelloV1); + await render(() => HelloV1); + await render(() => HelloV2); + await render(() => HelloV1); expect(container.firstChild).toBe(el); expect(el.textContent).toBe('2'); expect(el.style.color).toBe('red'); // Bump the state again. - act(() => { + await act(() => { el.dispatchEvent(new MouseEvent('click', {bubbles: true})); }); expect(container.firstChild).toBe(el); @@ -169,7 +169,7 @@ describe('ReactFresh', () => { expect(el.style.color).toBe('red'); // Finally, a render with incompatible type should reset it. - render(() => { + await render(() => { function Hello() { const [val, setVal] = React.useState(0); return ( @@ -189,9 +189,9 @@ describe('ReactFresh', () => { } }); - it('can preserve state for forwardRef', () => { + it('can preserve state for forwardRef', async () => { if (__DEV__) { - const OuterV1 = render(() => { + const OuterV1 = await render(() => { function Hello() { const [val, setVal] = React.useState(0); return ( @@ -211,7 +211,7 @@ describe('ReactFresh', () => { const el = container.firstChild; expect(el.textContent).toBe('0'); expect(el.style.color).toBe('blue'); - act(() => { + await act(() => { el.dispatchEvent(new MouseEvent('click', {bubbles: true})); }); expect(el.textContent).toBe('1'); @@ -239,7 +239,7 @@ describe('ReactFresh', () => { expect(el.style.color).toBe('red'); // Bump the state again. - act(() => { + await act(() => { el.dispatchEvent(new MouseEvent('click', {bubbles: true})); }); expect(container.firstChild).toBe(el); @@ -249,15 +249,15 @@ describe('ReactFresh', () => { // Perform top-down renders with both fresh and stale types. // Neither should change the state or color. // They should always resolve to the latest version. - render(() => OuterV1); - render(() => OuterV2); - render(() => OuterV1); + await render(() => OuterV1); + await render(() => OuterV2); + await render(() => OuterV1); expect(container.firstChild).toBe(el); expect(el.textContent).toBe('2'); expect(el.style.color).toBe('red'); // Finally, a render with incompatible type should reset it. - render(() => { + await render(() => { function Hello() { const [val, setVal] = React.useState(0); return ( @@ -279,9 +279,9 @@ describe('ReactFresh', () => { } }); - it('should not consider two forwardRefs around the same type to be equivalent', () => { + it('should not consider two forwardRefs around the same type to be equivalent', async () => { if (__DEV__) { - const ParentV1 = render( + const ParentV1 = await render( () => { function Hello() { const [val, setVal] = React.useState(0); @@ -317,32 +317,32 @@ describe('ReactFresh', () => { let el = container.firstChild; expect(el.textContent).toBe('0'); expect(el.style.color).toBe('blue'); - act(() => { + await act(() => { el.dispatchEvent(new MouseEvent('click', {bubbles: true})); }); expect(el.textContent).toBe('1'); // Switching up the inner types should reset the state. - render(() => ParentV1, {cond: false}); + await render(() => ParentV1, {cond: false}); expect(el).not.toBe(container.firstChild); el = container.firstChild; expect(el.textContent).toBe('0'); expect(el.style.color).toBe('blue'); - act(() => { + await act(() => { el.dispatchEvent(new MouseEvent('click', {bubbles: true})); }); expect(el.textContent).toBe('1'); // Switch them up back again. - render(() => ParentV1, {cond: true}); + await render(() => ParentV1, {cond: true}); expect(el).not.toBe(container.firstChild); el = container.firstChild; expect(el.textContent).toBe('0'); expect(el.style.color).toBe('blue'); // Now bump up the state to prepare for patching. - act(() => { + await act(() => { el.dispatchEvent(new MouseEvent('click', {bubbles: true})); }); expect(el.textContent).toBe('1'); @@ -383,14 +383,14 @@ describe('ReactFresh', () => { expect(el.style.color).toBe('red'); // Switching up the condition should still reset the state. - render(() => ParentV2, {cond: false}); + await render(() => ParentV2, {cond: false}); expect(el).not.toBe(container.firstChild); el = container.firstChild; expect(el.textContent).toBe('0'); expect(el.style.color).toBe('red'); // Now bump up the state to prepare for top-level renders. - act(() => { + await act(() => { el.dispatchEvent(new MouseEvent('click', {bubbles: true})); }); expect(el).toBe(container.firstChild); @@ -398,18 +398,18 @@ describe('ReactFresh', () => { expect(el.style.color).toBe('red'); // Finally, verify using top-level render with stale type keeps state. - render(() => ParentV1); - render(() => ParentV2); - render(() => ParentV1); + await render(() => ParentV1); + await render(() => ParentV2); + await render(() => ParentV1); expect(container.firstChild).toBe(el); expect(el.textContent).toBe('1'); expect(el.style.color).toBe('red'); } }); - it('can update forwardRef render function with its wrapper', () => { + it('can update forwardRef render function with its wrapper', async () => { if (__DEV__) { - render(() => { + await render(() => { function Hello({color}) { const [val, setVal] = React.useState(0); return ( @@ -429,7 +429,7 @@ describe('ReactFresh', () => { const el = container.firstChild; expect(el.textContent).toBe('0'); expect(el.style.color).toBe('blue'); - act(() => { + await act(() => { el.dispatchEvent(new MouseEvent('click', {bubbles: true})); }); expect(el.textContent).toBe('1'); @@ -458,9 +458,9 @@ describe('ReactFresh', () => { } }); - it('can update forwardRef render function in isolation', () => { + it('can update forwardRef render function in isolation', async () => { if (__DEV__) { - render(() => { + await render(() => { function Hello({color}) { const [val, setVal] = React.useState(0); return ( @@ -483,7 +483,7 @@ describe('ReactFresh', () => { const el = container.firstChild; expect(el.textContent).toBe('0'); expect(el.style.color).toBe('blue'); - act(() => { + await act(() => { el.dispatchEvent(new MouseEvent('click', {bubbles: true})); }); expect(el.textContent).toBe('1'); @@ -515,9 +515,9 @@ describe('ReactFresh', () => { } }); - it('can preserve state for simple memo', () => { + it('can preserve state for simple memo', async () => { if (__DEV__) { - const OuterV1 = render(() => { + const OuterV1 = await render(() => { function Hello() { const [val, setVal] = React.useState(0); return ( @@ -537,7 +537,7 @@ describe('ReactFresh', () => { const el = container.firstChild; expect(el.textContent).toBe('0'); expect(el.style.color).toBe('blue'); - act(() => { + await act(() => { el.dispatchEvent(new MouseEvent('click', {bubbles: true})); }); expect(el.textContent).toBe('1'); @@ -565,7 +565,7 @@ describe('ReactFresh', () => { expect(el.style.color).toBe('red'); // Bump the state again. - act(() => { + await act(() => { el.dispatchEvent(new MouseEvent('click', {bubbles: true})); }); expect(container.firstChild).toBe(el); @@ -575,15 +575,15 @@ describe('ReactFresh', () => { // Perform top-down renders with both fresh and stale types. // Neither should change the state or color. // They should always resolve to the latest version. - render(() => OuterV1); - render(() => OuterV2); - render(() => OuterV1); + await render(() => OuterV1); + await render(() => OuterV2); + await render(() => OuterV1); expect(container.firstChild).toBe(el); expect(el.textContent).toBe('2'); expect(el.style.color).toBe('red'); // Finally, a render with incompatible type should reset it. - render(() => { + await render(() => { function Hello() { const [val, setVal] = React.useState(0); return ( @@ -605,9 +605,9 @@ describe('ReactFresh', () => { } }); - it('can preserve state for memo with custom comparison', () => { + it('can preserve state for memo with custom comparison', async () => { if (__DEV__) { - const OuterV1 = render(() => { + const OuterV1 = await render(() => { function Hello() { const [val, setVal] = React.useState(0); return ( @@ -626,7 +626,7 @@ describe('ReactFresh', () => { const el = container.firstChild; expect(el.textContent).toBe('0'); expect(el.style.color).toBe('blue'); - act(() => { + await act(() => { el.dispatchEvent(new MouseEvent('click', {bubbles: true})); }); expect(el.textContent).toBe('1'); @@ -653,7 +653,7 @@ describe('ReactFresh', () => { expect(el.style.color).toBe('red'); // Bump the state again. - act(() => { + await act(() => { el.dispatchEvent(new MouseEvent('click', {bubbles: true})); }); expect(container.firstChild).toBe(el); @@ -663,15 +663,15 @@ describe('ReactFresh', () => { // Perform top-down renders with both fresh and stale types. // Neither should change the state or color. // They should always resolve to the latest version. - render(() => OuterV1); - render(() => OuterV2); - render(() => OuterV1); + await render(() => OuterV1); + await render(() => OuterV2); + await render(() => OuterV1); expect(container.firstChild).toBe(el); expect(el.textContent).toBe('2'); expect(el.style.color).toBe('red'); // Finally, a render with incompatible type should reset it. - render(() => { + await render(() => { function Hello() { const [val, setVal] = React.useState(0); return ( @@ -693,9 +693,9 @@ describe('ReactFresh', () => { } }); - it('can update simple memo function in isolation', () => { + it('can update simple memo function in isolation', async () => { if (__DEV__) { - render(() => { + await render(() => { function Hello() { const [val, setVal] = React.useState(0); return ( @@ -713,7 +713,7 @@ describe('ReactFresh', () => { const el = container.firstChild; expect(el.textContent).toBe('0'); expect(el.style.color).toBe('blue'); - act(() => { + await act(() => { el.dispatchEvent(new MouseEvent('click', {bubbles: true})); }); expect(el.textContent).toBe('1'); @@ -740,9 +740,9 @@ describe('ReactFresh', () => { } }); - it('can preserve state for memo(forwardRef)', () => { + it('can preserve state for memo(forwardRef)', async () => { if (__DEV__) { - const OuterV1 = render(() => { + const OuterV1 = await render(() => { function Hello() { const [val, setVal] = React.useState(0); return ( @@ -762,7 +762,7 @@ describe('ReactFresh', () => { const el = container.firstChild; expect(el.textContent).toBe('0'); expect(el.style.color).toBe('blue'); - act(() => { + await act(() => { el.dispatchEvent(new MouseEvent('click', {bubbles: true})); }); expect(el.textContent).toBe('1'); @@ -790,7 +790,7 @@ describe('ReactFresh', () => { expect(el.style.color).toBe('red'); // Bump the state again. - act(() => { + await act(() => { el.dispatchEvent(new MouseEvent('click', {bubbles: true})); }); expect(container.firstChild).toBe(el); @@ -800,15 +800,15 @@ describe('ReactFresh', () => { // Perform top-down renders with both fresh and stale types. // Neither should change the state or color. // They should always resolve to the latest version. - render(() => OuterV1); - render(() => OuterV2); - render(() => OuterV1); + await render(() => OuterV1); + await render(() => OuterV2); + await render(() => OuterV1); expect(container.firstChild).toBe(el); expect(el.textContent).toBe('2'); expect(el.style.color).toBe('red'); // Finally, a render with incompatible type should reset it. - render(() => { + await render(() => { function Hello() { const [val, setVal] = React.useState(0); return ( @@ -832,7 +832,8 @@ describe('ReactFresh', () => { it('can preserve state for lazy after resolution', async () => { if (__DEV__) { - const AppV1 = render(() => { + let resolve; + const AppV1 = await render(() => { function Hello() { const [val, setVal] = React.useState(0); return ( @@ -845,8 +846,8 @@ describe('ReactFresh', () => { const Outer = React.lazy( () => - new Promise(resolve => { - setTimeout(() => resolve({default: Hello}), 100); + new Promise(_resolve => { + resolve = () => _resolve({default: Hello}); }), ); $RefreshReg$(Outer, 'Outer'); @@ -865,7 +866,7 @@ describe('ReactFresh', () => { expect(container.textContent).toBe('Loading'); await act(() => { - jest.runAllTimers(); + resolve(); }); expect(container.textContent).toBe('0'); @@ -873,7 +874,7 @@ describe('ReactFresh', () => { const el = container.firstChild; expect(el.textContent).toBe('0'); expect(el.style.color).toBe('blue'); - act(() => { + await act(() => { el.dispatchEvent(new MouseEvent('click', {bubbles: true})); }); expect(el.textContent).toBe('1'); @@ -892,8 +893,8 @@ describe('ReactFresh', () => { const Outer = React.lazy( () => - new Promise(resolve => { - setTimeout(() => resolve({default: Hello}), 100); + new Promise(_resolve => { + resolve = () => _resolve({default: Hello}); }), ); $RefreshReg$(Outer, 'Outer'); @@ -916,7 +917,7 @@ describe('ReactFresh', () => { expect(el.style.color).toBe('red'); // Bump the state again. - act(() => { + await act(() => { el.dispatchEvent(new MouseEvent('click', {bubbles: true})); }); expect(container.firstChild).toBe(el); @@ -926,15 +927,15 @@ describe('ReactFresh', () => { // Perform top-down renders with both fresh and stale types. // Neither should change the state or color. // They should always resolve to the latest version. - render(() => AppV1); - render(() => AppV2); - render(() => AppV1); + await render(() => AppV1); + await render(() => AppV2); + await render(() => AppV1); expect(container.firstChild).toBe(el); expect(el.textContent).toBe('2'); expect(el.style.color).toBe('red'); // Finally, a render with incompatible type should reset it. - render(() => { + await render(() => { function Hello() { const [val, setVal] = React.useState(0); return ( @@ -968,7 +969,8 @@ describe('ReactFresh', () => { it('can patch lazy before resolution', async () => { if (__DEV__) { - render(() => { + let resolve; + await render(() => { function Hello() { const [val, setVal] = React.useState(0); return ( @@ -981,8 +983,8 @@ describe('ReactFresh', () => { const Outer = React.lazy( () => - new Promise(resolve => { - setTimeout(() => resolve({default: Hello}), 100); + new Promise(_resolve => { + resolve = () => _resolve({default: Hello}); }), ); $RefreshReg$(Outer, 'Outer'); @@ -1014,7 +1016,7 @@ describe('ReactFresh', () => { }); await act(() => { - jest.runAllTimers(); + resolve(); }); // Expect different color on initial mount. @@ -1023,7 +1025,7 @@ describe('ReactFresh', () => { expect(el.style.color).toBe('red'); // Bump state. - act(() => { + await act(() => { el.dispatchEvent(new MouseEvent('click', {bubbles: true})); }); expect(container.firstChild).toBe(el); @@ -1050,7 +1052,8 @@ describe('ReactFresh', () => { it('can patch lazy(forwardRef) before resolution', async () => { if (__DEV__) { - render(() => { + let resolve; + await render(() => { function renderHello() { const [val, setVal] = React.useState(0); return ( @@ -1064,8 +1067,8 @@ describe('ReactFresh', () => { const Outer = React.lazy( () => - new Promise(resolve => { - setTimeout(() => resolve({default: Hello}), 100); + new Promise(_resolve => { + resolve = () => _resolve({default: Hello}); }), ); $RefreshReg$(Outer, 'Outer'); @@ -1098,7 +1101,7 @@ describe('ReactFresh', () => { }); await act(() => { - jest.runAllTimers(); + resolve(); }); // Expect different color on initial mount. @@ -1107,7 +1110,7 @@ describe('ReactFresh', () => { expect(el.style.color).toBe('red'); // Bump state. - act(() => { + await act(() => { el.dispatchEvent(new MouseEvent('click', {bubbles: true})); }); expect(container.firstChild).toBe(el); @@ -1135,7 +1138,8 @@ describe('ReactFresh', () => { it('can patch lazy(memo) before resolution', async () => { if (__DEV__) { - render(() => { + let resolve; + await render(() => { function renderHello() { const [val, setVal] = React.useState(0); return ( @@ -1149,8 +1153,8 @@ describe('ReactFresh', () => { const Outer = React.lazy( () => - new Promise(resolve => { - setTimeout(() => resolve({default: Hello}), 100); + new Promise(_resolve => { + resolve = () => _resolve({default: Hello}); }), ); $RefreshReg$(Outer, 'Outer'); @@ -1183,7 +1187,7 @@ describe('ReactFresh', () => { }); await act(() => { - jest.runAllTimers(); + resolve(); }); // Expect different color on initial mount. @@ -1192,7 +1196,7 @@ describe('ReactFresh', () => { expect(el.style.color).toBe('red'); // Bump state. - act(() => { + await act(() => { el.dispatchEvent(new MouseEvent('click', {bubbles: true})); }); expect(container.firstChild).toBe(el); @@ -1220,7 +1224,8 @@ describe('ReactFresh', () => { it('can patch lazy(memo(forwardRef)) before resolution', async () => { if (__DEV__) { - render(() => { + let resolve; + await render(() => { function renderHello() { const [val, setVal] = React.useState(0); return ( @@ -1234,8 +1239,8 @@ describe('ReactFresh', () => { const Outer = React.lazy( () => - new Promise(resolve => { - setTimeout(() => resolve({default: Hello}), 100); + new Promise(_resolve => { + resolve = () => _resolve({default: Hello}); }), ); $RefreshReg$(Outer, 'Outer'); @@ -1268,7 +1273,7 @@ describe('ReactFresh', () => { }); await act(() => { - jest.runAllTimers(); + resolve(); }); // Expect different color on initial mount. @@ -1277,7 +1282,7 @@ describe('ReactFresh', () => { expect(el.style.color).toBe('red'); // Bump state. - act(() => { + await act(() => { el.dispatchEvent(new MouseEvent('click', {bubbles: true})); }); expect(container.firstChild).toBe(el); @@ -1303,9 +1308,9 @@ describe('ReactFresh', () => { } }); - it('can patch both trees while suspense is displaying the fallback', async () => { + it('only patches the fallback tree while suspended', async () => { if (__DEV__) { - const AppV1 = render( + const AppV1 = await render( () => { function Hello({children}) { const [val, setVal] = React.useState(0); @@ -1343,7 +1348,7 @@ describe('ReactFresh', () => { expect(primaryChild.style.display).toBe(''); // Bump primary content state. - act(() => { + await act(() => { primaryChild.dispatchEvent(new MouseEvent('click', {bubbles: true})); }); expect(container.childNodes.length).toBe(1); @@ -1371,7 +1376,7 @@ describe('ReactFresh', () => { expect(primaryChild.style.display).toBe(''); // Now force the tree to suspend. - render(() => AppV1, {shouldSuspend: true}); + await render(() => AppV1, {shouldSuspend: true}); // Expect to see two trees, one of them is hidden. expect(container.childNodes.length).toBe(2); @@ -1385,7 +1390,7 @@ describe('ReactFresh', () => { expect(fallbackChild.style.display).toBe(''); // Bump fallback state. - act(() => { + await act(() => { fallbackChild.dispatchEvent(new MouseEvent('click', {bubbles: true})); }); expect(container.childNodes.length).toBe(2); @@ -1411,19 +1416,19 @@ describe('ReactFresh', () => { $RefreshReg$(Hello, 'Hello'); }); - // Colors inside both trees should change: + // Only update color in the visible child expect(container.childNodes.length).toBe(2); expect(container.childNodes[0]).toBe(primaryChild); expect(container.childNodes[1]).toBe(fallbackChild); expect(primaryChild.textContent).toBe('Content 1'); - expect(primaryChild.style.color).toBe('red'); + expect(primaryChild.style.color).toBe('green'); expect(primaryChild.style.display).toBe('none'); expect(fallbackChild.textContent).toBe('Fallback 1'); expect(fallbackChild.style.color).toBe('red'); expect(fallbackChild.style.display).toBe(''); // Only primary tree should exist now: - render(() => AppV1, {shouldSuspend: false}); + await render(() => AppV1, {shouldSuspend: false}); expect(container.childNodes.length).toBe(1); expect(container.childNodes[0]).toBe(primaryChild); expect(primaryChild.textContent).toBe('Content 1'); @@ -1450,11 +1455,11 @@ describe('ReactFresh', () => { } }); - it('does not re-render ancestor components unnecessarily during a hot update', () => { + it('does not re-render ancestor components unnecessarily during a hot update', async () => { if (__DEV__) { let appRenders = 0; - render(() => { + await render(() => { function Hello() { const [val, setVal] = React.useState(0); return ( @@ -1478,7 +1483,7 @@ describe('ReactFresh', () => { const el = container.firstChild; expect(el.textContent).toBe('0'); expect(el.style.color).toBe('blue'); - act(() => { + await act(() => { el.dispatchEvent(new MouseEvent('click', {bubbles: true})); }); expect(el.textContent).toBe('1'); @@ -1508,7 +1513,7 @@ describe('ReactFresh', () => { expect(appRenders).toBe(1); // Bump the state. - act(() => { + await act(() => { el.dispatchEvent(new MouseEvent('click', {bubbles: true})); }); expect(el.textContent).toBe('2'); @@ -1518,11 +1523,11 @@ describe('ReactFresh', () => { } }); - it('batches re-renders during a hot update', () => { + it('batches re-renders during a hot update', async () => { if (__DEV__) { let helloRenders = 0; - render(() => { + await render(() => { function Hello({children}) { helloRenders++; return
X{children}X
; @@ -1559,9 +1564,9 @@ describe('ReactFresh', () => { } }); - it('does not leak state between components', () => { + it('does not leak state between components', async () => { if (__DEV__) { - const AppV1 = render( + const AppV1 = await render( () => { function Hello1() { const [val, setVal] = React.useState(0); @@ -1594,21 +1599,21 @@ describe('ReactFresh', () => { const el = container.firstChild; expect(el.textContent).toBe('0'); expect(el.style.color).toBe('blue'); - act(() => { + await act(() => { el.dispatchEvent(new MouseEvent('click', {bubbles: true})); }); expect(el.textContent).toBe('1'); // Switch the condition, flipping inner content. // This should reset the state. - render(() => AppV1, {cond: true}); + await render(() => AppV1, {cond: true}); const el2 = container.firstChild; expect(el2).not.toBe(el); expect(el2.textContent).toBe('0'); expect(el2.style.color).toBe('blue'); // Bump it again. - act(() => { + await act(() => { el2.dispatchEvent(new MouseEvent('click', {bubbles: true})); }); expect(el2.textContent).toBe('1'); @@ -1641,7 +1646,7 @@ describe('ReactFresh', () => { expect(el2.style.color).toBe('red'); // Flip the condition again. - render(() => AppV1, {cond: false}); + await render(() => AppV1, {cond: false}); const el3 = container.firstChild; expect(el3).not.toBe(el2); expect(el3.textContent).toBe('0'); @@ -1649,9 +1654,9 @@ describe('ReactFresh', () => { } }); - it('can force remount by changing signature', () => { + it('can force remount by changing signature', async () => { if (__DEV__) { - const HelloV1 = render(() => { + const HelloV1 = await render(() => { function Hello() { const [val, setVal] = React.useState(0); return ( @@ -1670,7 +1675,7 @@ describe('ReactFresh', () => { const el = container.firstChild; expect(el.textContent).toBe('0'); expect(el.style.color).toBe('blue'); - act(() => { + await act(() => { el.dispatchEvent(new MouseEvent('click', {bubbles: true})); }); expect(el.textContent).toBe('1'); @@ -1719,7 +1724,7 @@ describe('ReactFresh', () => { expect(newEl.style.color).toBe('yellow'); // Bump state again. - act(() => { + await act(() => { newEl.dispatchEvent(new MouseEvent('click', {bubbles: true})); }); expect(newEl.textContent).toBe('1'); @@ -1728,11 +1733,11 @@ describe('ReactFresh', () => { // Perform top-down renders with both fresh and stale types. // Neither should change the state or color. // They should always resolve to the latest version. - render(() => HelloV1); - render(() => HelloV2); - render(() => HelloV3); - render(() => HelloV2); - render(() => HelloV1); + await render(() => HelloV1); + await render(() => HelloV2); + await render(() => HelloV3); + await render(() => HelloV2); + await render(() => HelloV1); expect(container.firstChild).toBe(newEl); expect(newEl.textContent).toBe('1'); expect(newEl.style.color).toBe('yellow'); @@ -1780,7 +1785,7 @@ describe('ReactFresh', () => { } }); - it('keeps a valid tree when forcing remount', () => { + it('keeps a valid tree when forcing remount', async () => { if (__DEV__) { const HelloV1 = prepare(() => { function Hello() { @@ -1910,26 +1915,31 @@ describe('ReactFresh', () => { , ]; - // First, check that each tree handles remounts in isolation. - ReactDOM.render(null, container); + await act(() => { + root.render(null); + }); + for (let i = 0; i < trees.length; i++) { - runRemountingStressTest(trees[i]); + await runRemountingStressTest(trees[i]); } // Then check that each tree is resilient to updates from another tree. for (let i = 0; i < trees.length; i++) { for (let j = 0; j < trees.length; j++) { - ReactDOM.render(null, container); + await act(() => { + root.render(null); + }); + // Intentionally don't clean up between the tests: - runRemountingStressTest(trees[i]); - runRemountingStressTest(trees[j]); - runRemountingStressTest(trees[i]); + await runRemountingStressTest(trees[i]); + await runRemountingStressTest(trees[j]); + await runRemountingStressTest(trees[i]); } } } - }); + }, 10000); - function runRemountingStressTest(tree) { + async function runRemountingStressTest(tree) { patch(() => { function Hello({children}) { return
{children}
; @@ -1939,7 +1949,10 @@ describe('ReactFresh', () => { return Hello; }); - ReactDOM.render(tree, container); + await act(() => { + root.render(tree); + }); + const elements = container.querySelectorAll('section'); // Each tree above produces exactly three
elements: expect(elements.length).toBe(3); @@ -2003,8 +2016,10 @@ describe('ReactFresh', () => { expect(el.dataset.color).toBe('black'); }); - // Do another render just in case. - ReactDOM.render(tree, container); + await act(() => { + root.render(tree); + }); + expect(container.querySelectorAll('section').length).toBe(3); container.querySelectorAll('section').forEach((el, index) => { expect(el).toBe(elementsAfterRemount[index]); @@ -2012,21 +2027,21 @@ describe('ReactFresh', () => { }); } - it('can remount on signature change within a wrapper', () => { + it('can remount on signature change within a wrapper', async () => { if (__DEV__) { - testRemountingWithWrapper(Hello => Hello); + await testRemountingWithWrapper(Hello => Hello); } }); - it('can remount on signature change within a simple memo wrapper', () => { + it('can remount on signature change within a simple memo wrapper', async () => { if (__DEV__) { - testRemountingWithWrapper(Hello => React.memo(Hello)); + await testRemountingWithWrapper(Hello => React.memo(Hello)); } }); - it('can remount on signature change within a lazy simple memo wrapper', () => { + it('can remount on signature change within a lazy simple memo wrapper', async () => { if (__DEV__) { - testRemountingWithWrapper(Hello => + await testRemountingWithWrapper(Hello => React.lazy(() => ({ then(cb) { cb({default: React.memo(Hello)}); @@ -2036,35 +2051,37 @@ describe('ReactFresh', () => { } }); - it('can remount on signature change within forwardRef', () => { + it('can remount on signature change within forwardRef', async () => { if (__DEV__) { - testRemountingWithWrapper(Hello => React.forwardRef(Hello)); + await testRemountingWithWrapper(Hello => React.forwardRef(Hello)); } }); - it('can remount on signature change within forwardRef render function', () => { + it('can remount on signature change within forwardRef render function', async () => { if (__DEV__) { - testRemountingWithWrapper(Hello => React.forwardRef(() => )); + await testRemountingWithWrapper(Hello => + React.forwardRef(() => ), + ); } }); - it('can remount on signature change within nested memo', () => { + it('can remount on signature change within nested memo', async () => { if (__DEV__) { - testRemountingWithWrapper(Hello => + await testRemountingWithWrapper(Hello => React.memo(React.memo(React.memo(Hello))), ); } }); - it('can remount on signature change within a memo wrapper and custom comparison', () => { + it('can remount on signature change within a memo wrapper and custom comparison', async () => { if (__DEV__) { - testRemountingWithWrapper(Hello => React.memo(Hello, () => true)); + await testRemountingWithWrapper(Hello => React.memo(Hello, () => true)); } }); - it('can remount on signature change within a class', () => { + it('can remount on signature change within a class', async () => { if (__DEV__) { - testRemountingWithWrapper(Hello => { + await testRemountingWithWrapper(Hello => { const child = ; return class Wrapper extends React.PureComponent { render() { @@ -2075,9 +2092,9 @@ describe('ReactFresh', () => { } }); - it('can remount on signature change within a context provider', () => { + it('can remount on signature change within a context provider', async () => { if (__DEV__) { - testRemountingWithWrapper(Hello => { + await testRemountingWithWrapper(Hello => { const Context = React.createContext(); const child = ( @@ -2091,9 +2108,9 @@ describe('ReactFresh', () => { } }); - it('can remount on signature change within a context consumer', () => { + it('can remount on signature change within a context consumer', async () => { if (__DEV__) { - testRemountingWithWrapper(Hello => { + await testRemountingWithWrapper(Hello => { const Context = React.createContext(); const child = {() => }; return function Wrapper() { @@ -2103,9 +2120,9 @@ describe('ReactFresh', () => { } }); - it('can remount on signature change within a suspense node', () => { + it('can remount on signature change within a suspense node', async () => { if (__DEV__) { - testRemountingWithWrapper(Hello => { + await testRemountingWithWrapper(Hello => { // TODO: we'll probably want to test fallback trees too. const child = ( @@ -2119,9 +2136,9 @@ describe('ReactFresh', () => { } }); - it('can remount on signature change within a mode node', () => { + it('can remount on signature change within a mode node', async () => { if (__DEV__) { - testRemountingWithWrapper(Hello => { + await testRemountingWithWrapper(Hello => { const child = ( @@ -2134,9 +2151,9 @@ describe('ReactFresh', () => { } }); - it('can remount on signature change within a fragment node', () => { + it('can remount on signature change within a fragment node', async () => { if (__DEV__) { - testRemountingWithWrapper(Hello => { + await testRemountingWithWrapper(Hello => { const child = ( <> @@ -2149,9 +2166,9 @@ describe('ReactFresh', () => { } }); - it('can remount on signature change within multiple siblings', () => { + it('can remount on signature change within multiple siblings', async () => { if (__DEV__) { - testRemountingWithWrapper(Hello => { + await testRemountingWithWrapper(Hello => { const child = ( <> <> @@ -2168,9 +2185,9 @@ describe('ReactFresh', () => { } }); - it('can remount on signature change within a profiler node', () => { + it('can remount on signature change within a profiler node', async () => { if (__DEV__) { - testRemountingWithWrapper(Hello => { + await testRemountingWithWrapper(Hello => { const child = ; return function Wrapper() { return ( @@ -2183,8 +2200,8 @@ describe('ReactFresh', () => { } }); - function testRemountingWithWrapper(wrap) { - render(() => { + async function testRemountingWithWrapper(wrap) { + await render(() => { function Hello() { const [val, setVal] = React.useState(0); return ( @@ -2206,7 +2223,7 @@ describe('ReactFresh', () => { const el = container.firstChild; expect(el.textContent).toBe('0'); expect(el.style.color).toBe('blue'); - act(() => { + await act(() => { el.dispatchEvent(new MouseEvent('click', {bubbles: true})); }); expect(el.textContent).toBe('1'); @@ -2255,7 +2272,7 @@ describe('ReactFresh', () => { expect(newEl.style.color).toBe('yellow'); // Bump state again. - act(() => { + await act(() => { newEl.dispatchEvent(new MouseEvent('click', {bubbles: true})); }); expect(newEl.textContent).toBe('1'); @@ -2303,11 +2320,11 @@ describe('ReactFresh', () => { expect(finalEl.style.color).toBe('orange'); } - it('resets hooks with dependencies on hot reload', () => { + it('resets hooks with dependencies on hot reload', async () => { if (__DEV__) { let useEffectWithEmptyArrayCalls = 0; - render(() => { + await render(() => { function Hello() { const [val, setVal] = React.useState(0); const tranformed = React.useMemo(() => val * 2, [val]); @@ -2332,33 +2349,31 @@ describe('ReactFresh', () => { expect(el.textContent).toBe('0'); expect(el.style.color).toBe('blue'); expect(useEffectWithEmptyArrayCalls).toBe(1); // useEffect ran - act(() => { + await act(() => { el.dispatchEvent(new MouseEvent('click', {bubbles: true})); }); expect(el.textContent).toBe('2'); // val * 2 expect(useEffectWithEmptyArrayCalls).toBe(1); // useEffect didn't re-run // Perform a hot update. - act(() => { - patch(() => { - function Hello() { - const [val, setVal] = React.useState(0); - const tranformed = React.useMemo(() => val * 10, [val]); - const handleClick = React.useCallback(() => setVal(v => v - 1), []); + patch(() => { + function Hello() { + const [val, setVal] = React.useState(0); + const tranformed = React.useMemo(() => val * 10, [val]); + const handleClick = React.useCallback(() => setVal(v => v - 1), []); - React.useEffect(() => { - useEffectWithEmptyArrayCalls++; - }, []); + React.useEffect(() => { + useEffectWithEmptyArrayCalls++; + }, []); - return ( -

- {tranformed} -

- ); - } - $RefreshReg$(Hello, 'Hello'); - return Hello; - }); + return ( +

+ {tranformed} +

+ ); + } + $RefreshReg$(Hello, 'Hello'); + return Hello; }); // Assert the state was preserved but memo was evicted. @@ -2368,7 +2383,7 @@ describe('ReactFresh', () => { expect(useEffectWithEmptyArrayCalls).toBe(2); // useEffect re-ran // This should fire the new callback which decreases the counter. - act(() => { + await act(() => { el.dispatchEvent(new MouseEvent('click', {bubbles: true})); }); expect(el.textContent).toBe('0'); @@ -2378,9 +2393,9 @@ describe('ReactFresh', () => { }); // This pattern is inspired by useSubscription and similar mechanisms. - it('does not get into infinite loops during render phase updates', () => { + it('does not get into infinite loops during render phase updates', async () => { if (__DEV__) { - render(() => { + await render(() => { function Hello() { const source = React.useMemo(() => ({value: 10}), []); const [state, setState] = React.useState({value: null}); @@ -2398,20 +2413,18 @@ describe('ReactFresh', () => { expect(el.style.color).toBe('blue'); // Perform a hot update. - act(() => { - patch(() => { - function Hello() { - const source = React.useMemo(() => ({value: 20}), []); - const [state, setState] = React.useState({value: null}); - if (state !== source) { - // This should perform a single render-phase update. - setState(source); - } - return

{state.value}

; + patch(() => { + function Hello() { + const source = React.useMemo(() => ({value: 20}), []); + const [state, setState] = React.useState({value: null}); + if (state !== source) { + // This should perform a single render-phase update. + setState(source); } - $RefreshReg$(Hello, 'Hello'); - return Hello; - }); + return

{state.value}

; + } + $RefreshReg$(Hello, 'Hello'); + return Hello; }); expect(container.firstChild).toBe(el); @@ -2448,7 +2461,6 @@ describe('ReactFresh', () => { }; }); - const root = ReactDOMClient.createRoot(container); root.render(); await waitFor(['App#layout']); const el = container.firstChild; @@ -2482,7 +2494,7 @@ describe('ReactFresh', () => { expect(el.firstChild.textContent).toBe('0'); expect(el.firstChild.style.color).toBe('red'); - await internalAct(() => { + await act(() => { el.firstChild.dispatchEvent( new MouseEvent('click', { bubbles: true, @@ -2522,9 +2534,9 @@ describe('ReactFresh', () => { expect(el.firstChild.style.color).toBe('orange'); }); - it('remounts failed error boundaries (componentDidCatch)', () => { + it('remounts failed error boundaries (componentDidCatch)', async () => { if (__DEV__) { - render(() => { + await render(() => { function Hello() { return

Hi

; } @@ -2600,9 +2612,9 @@ describe('ReactFresh', () => { } }); - it('remounts failed error boundaries (getDerivedStateFromError)', () => { + it('remounts failed error boundaries (getDerivedStateFromError)', async () => { if (__DEV__) { - render(() => { + await render(() => { function Hello() { return

Hi

; } @@ -2678,9 +2690,9 @@ describe('ReactFresh', () => { } }); - it('remounts error boundaries that failed asynchronously after hot update', () => { + it('remounts error boundaries that failed asynchronously after hot update', async () => { if (__DEV__) { - render(() => { + await render(() => { function Hello() { const [x] = React.useState(''); React.useEffect(() => {}, []); @@ -2722,26 +2734,25 @@ describe('ReactFresh', () => { const secondP = firstP.nextSibling.nextSibling; // Perform a hot update that fails. - act(() => { - patch(() => { - function Hello() { - const [x, setX] = React.useState(''); - React.useEffect(() => { - setTimeout(() => { - setX(42); // This will crash next render. - }, 1); - }, []); - x.slice(); - return

Hi

; - } - $RefreshReg$(Hello, 'Hello'); - }); + let crash; + patch(() => { + function Hello() { + const [x, setX] = React.useState(''); + React.useEffect(() => { + crash = () => { + setX(42); // This will crash next render. + }; + }, []); + x.slice(); + return

Hi

; + } + $RefreshReg$(Hello, 'Hello'); }); expect(container.innerHTML).toBe('

A

Hi

B

'); // Run timeout inside effect: - act(() => { - jest.runAllTimers(); + await act(() => { + crash(); }); expect(container.innerHTML).toBe( '

A

Oops: x.slice is not a function

B

', @@ -2750,16 +2761,14 @@ describe('ReactFresh', () => { expect(container.firstChild.nextSibling.nextSibling).toBe(secondP); // Perform a hot update that fixes the error. - act(() => { - patch(() => { - function Hello() { - const [x] = React.useState(''); - React.useEffect(() => {}, []); // Removes the bad effect code. - x.slice(); // Doesn't throw initially. - return

Fixed!

; - } - $RefreshReg$(Hello, 'Hello'); - }); + patch(() => { + function Hello() { + const [x] = React.useState(''); + React.useEffect(() => {}, []); // Removes the bad effect code. + x.slice(); // Doesn't throw initially. + return

Fixed!

; + } + $RefreshReg$(Hello, 'Hello'); }); // This should remount the error boundary (but not anything above it). @@ -2769,16 +2778,14 @@ describe('ReactFresh', () => { // Verify next hot reload doesn't remount anything. const helloNode = container.firstChild.nextSibling; - act(() => { - patch(() => { - function Hello() { - const [x] = React.useState(''); - React.useEffect(() => {}, []); - x.slice(); - return

Nice.

; - } - $RefreshReg$(Hello, 'Hello'); - }); + patch(() => { + function Hello() { + const [x] = React.useState(''); + React.useEffect(() => {}, []); + x.slice(); + return

Nice.

; + } + $RefreshReg$(Hello, 'Hello'); }); expect(container.firstChild.nextSibling).toBe(helloNode); @@ -2786,9 +2793,9 @@ describe('ReactFresh', () => { } }); - it('remounts a failed root on mount', () => { + it('remounts a failed root on mount', async () => { if (__DEV__) { - expect(() => { + await expect( render(() => { function Hello() { throw new Error('No'); @@ -2796,8 +2803,8 @@ describe('ReactFresh', () => { $RefreshReg$(Hello, 'Hello'); return Hello; - }); - }).toThrow('No'); + }), + ).rejects.toThrow('No'); expect(container.innerHTML).toBe(''); // A bad retry @@ -2849,7 +2856,9 @@ describe('ReactFresh', () => { expect(container.innerHTML).toBe('

Fixed 2!

'); // Updates after intentional unmount are ignored. - ReactDOM.unmountComponentAtNode(container); + await act(() => { + root.unmount(); + }); patch(() => { function Hello() { throw new Error('Ignored'); @@ -2867,9 +2876,9 @@ describe('ReactFresh', () => { } }); - it('does not retry an intentionally unmounted failed root', () => { + it('does not retry an intentionally unmounted failed root', async () => { if (__DEV__) { - expect(() => { + await expect( render(() => { function Hello() { throw new Error('No'); @@ -2877,12 +2886,14 @@ describe('ReactFresh', () => { $RefreshReg$(Hello, 'Hello'); return Hello; - }); - }).toThrow('No'); + }), + ).rejects.toThrow('No'); expect(container.innerHTML).toBe(''); // Intentional unmount. - ReactDOM.unmountComponentAtNode(container); + await act(() => { + root.unmount(); + }); // Perform a hot update that fixes the error. patch(() => { @@ -2896,9 +2907,9 @@ describe('ReactFresh', () => { } }); - it('remounts a failed root on update', () => { + it('remounts a failed root on update', async () => { if (__DEV__) { - render(() => { + await render(() => { function Hello() { return

Hi

; } @@ -2974,7 +2985,9 @@ describe('ReactFresh', () => { expect(container.innerHTML).toBe('

At last.

'); // Check we don't attempt to reverse an intentional unmount. - ReactDOM.unmountComponentAtNode(container); + await act(() => { + root.unmount(); + }); expect(container.innerHTML).toBe(''); patch(() => { function Hello() { @@ -2985,7 +2998,8 @@ describe('ReactFresh', () => { expect(container.innerHTML).toBe(''); // Mount a new container. - render(() => { + root = ReactDOMClient.createRoot(container); + await render(() => { function Hello() { return

Hi

; } @@ -3007,7 +3021,9 @@ describe('ReactFresh', () => { expect(container.innerHTML).toBe(''); // Check we don't attempt to reverse an intentional unmount, even after an error. - ReactDOM.unmountComponentAtNode(container); + await act(() => { + root.unmount(); + }); expect(container.innerHTML).toBe(''); patch(() => { function Hello() { @@ -3019,10 +3035,12 @@ describe('ReactFresh', () => { } }); - it('regression test: does not get into an infinite loop', () => { + it('regression test: does not get into an infinite loop', async () => { if (__DEV__) { const containerA = document.createElement('div'); const containerB = document.createElement('div'); + const rootA = ReactDOMClient.createRoot(containerA); + const rootB = ReactDOMClient.createRoot(containerB); // Initially, nothing interesting. const RootAV1 = () => { @@ -3034,9 +3052,9 @@ describe('ReactFresh', () => { }; $RefreshReg$(RootBV1, 'RootB'); - act(() => { - ReactDOM.render(, containerA); - ReactDOM.render(, containerB); + await act(() => { + rootA.render(); + rootB.render(); }); expect(containerA.innerHTML).toBe('A1'); expect(containerB.innerHTML).toBe('B1'); @@ -3046,7 +3064,11 @@ describe('ReactFresh', () => { throw new Error('A2!'); }; $RefreshReg$(RootAV2, 'RootA'); - expect(() => ReactFreshRuntime.performReactRefresh()).toThrow('A2!'); + await expect( + act(() => { + ReactFreshRuntime.performReactRefresh(); + }), + ).rejects.toThrow('A2!'); expect(containerA.innerHTML).toBe(''); expect(containerB.innerHTML).toBe('B1'); @@ -3060,7 +3082,11 @@ describe('ReactFresh', () => { return 'A3'; }; $RefreshReg$(RootAV3, 'RootA'); - expect(() => ReactFreshRuntime.performReactRefresh()).toThrow('A3!'); + await expect( + act(() => { + ReactFreshRuntime.performReactRefresh(); + }), + ).rejects.toThrow('A3!'); expect(containerA.innerHTML).toBe(''); expect(containerB.innerHTML).toBe('B1'); @@ -3068,15 +3094,17 @@ describe('ReactFresh', () => { return 'A4'; }; $RefreshReg$(RootAV4, 'RootA'); - ReactFreshRuntime.performReactRefresh(); + await act(() => { + ReactFreshRuntime.performReactRefresh(); + }); expect(containerA.innerHTML).toBe('A4'); expect(containerB.innerHTML).toBe('B1'); } }); - it('remounts classes on every edit', () => { + it('remounts classes on every edit', async () => { if (__DEV__) { - const HelloV1 = render(() => { + const HelloV1 = await render(() => { class Hello extends React.Component { state = {count: 0}; handleClick = () => { @@ -3105,7 +3133,7 @@ describe('ReactFresh', () => { const el = container.firstChild; expect(el.textContent).toBe('0'); expect(el.style.color).toBe('blue'); - act(() => { + await act(() => { el.dispatchEvent(new MouseEvent('click', {bubbles: true})); }); expect(el.textContent).toBe('1'); @@ -3136,14 +3164,14 @@ describe('ReactFresh', () => { const newEl = container.firstChild; expect(newEl.textContent).toBe('0'); expect(newEl.style.color).toBe('red'); - act(() => { + await act(() => { newEl.dispatchEvent(new MouseEvent('click', {bubbles: true})); }); expect(newEl.textContent).toBe('1'); // Now top-level renders of both types resolve to latest. - render(() => HelloV1); - render(() => HelloV2); + await render(() => HelloV1); + await render(() => HelloV2); expect(container.firstChild).toBe(newEl); expect(newEl.style.color).toBe('red'); expect(newEl.textContent).toBe('1'); @@ -3173,24 +3201,24 @@ describe('ReactFresh', () => { const finalEl = container.firstChild; expect(finalEl.textContent).toBe('0'); expect(finalEl.style.color).toBe('orange'); - act(() => { + await act(() => { finalEl.dispatchEvent(new MouseEvent('click', {bubbles: true})); }); expect(finalEl.textContent).toBe('1'); - render(() => HelloV3); - render(() => HelloV2); - render(() => HelloV1); + await render(() => HelloV3); + await render(() => HelloV2); + await render(() => HelloV1); expect(container.firstChild).toBe(finalEl); expect(finalEl.style.color).toBe('orange'); expect(finalEl.textContent).toBe('1'); } }); - it('updates refs when remounting', () => { + it('updates refs when remounting', async () => { if (__DEV__) { const testRef = React.createRef(); - render( + await render( () => { class Hello extends React.Component { getColor() { @@ -3261,9 +3289,9 @@ describe('ReactFresh', () => { } }); - it('remounts on conversion from class to function and back', () => { + it('remounts on conversion from class to function and back', async () => { if (__DEV__) { - const HelloV1 = render(() => { + const HelloV1 = await render(() => { function Hello() { const [val, setVal] = React.useState(0); return ( @@ -3280,7 +3308,7 @@ describe('ReactFresh', () => { const el = container.firstChild; expect(el.textContent).toBe('0'); expect(el.style.color).toBe('blue'); - act(() => { + await act(() => { el.dispatchEvent(new MouseEvent('click', {bubbles: true})); }); expect(el.textContent).toBe('1'); @@ -3311,14 +3339,14 @@ describe('ReactFresh', () => { const newEl = container.firstChild; expect(newEl.textContent).toBe('0'); expect(newEl.style.color).toBe('red'); - act(() => { + await act(() => { newEl.dispatchEvent(new MouseEvent('click', {bubbles: true})); }); expect(newEl.textContent).toBe('1'); // Now top-level renders of both types resolve to latest. - render(() => HelloV1); - render(() => HelloV2); + await render(() => HelloV1); + await render(() => HelloV2); expect(container.firstChild).toBe(newEl); expect(newEl.style.color).toBe('red'); expect(newEl.textContent).toBe('1'); @@ -3342,14 +3370,14 @@ describe('ReactFresh', () => { const finalEl = container.firstChild; expect(finalEl.textContent).toBe('0'); expect(finalEl.style.color).toBe('orange'); - act(() => { + await act(() => { finalEl.dispatchEvent(new MouseEvent('click', {bubbles: true})); }); expect(finalEl.textContent).toBe('1'); - render(() => HelloV3); - render(() => HelloV2); - render(() => HelloV1); + await render(() => HelloV3); + await render(() => HelloV2); + await render(() => HelloV1); expect(container.firstChild).toBe(finalEl); expect(finalEl.style.color).toBe('orange'); expect(finalEl.textContent).toBe('1'); @@ -3373,9 +3401,9 @@ describe('ReactFresh', () => { } }); - it('can find host instances for a family', () => { + it('can find host instances for a family', async () => { if (__DEV__) { - render(() => { + await render(() => { function Child({children}) { return
{children}
; } @@ -3481,7 +3509,7 @@ describe('ReactFresh', () => { }); } - it('can update multiple roots independently', () => { + it('can update multiple roots independently', async () => { if (__DEV__) { // Declare the first version. const HelloV1 = () => { @@ -3504,7 +3532,9 @@ describe('ReactFresh', () => { ); }; $RefreshReg$(HelloV2, 'Hello'); - ReactFreshRuntime.performReactRefresh(); + await act(() => { + ReactFreshRuntime.performReactRefresh(); + }); // Mount three roots. const cont1 = document.createElement('div'); @@ -3513,10 +3543,19 @@ describe('ReactFresh', () => { document.body.appendChild(cont1); document.body.appendChild(cont2); document.body.appendChild(cont3); + const root1 = ReactDOMClient.createRoot(cont1); + const root2 = ReactDOMClient.createRoot(cont2); + const root3 = ReactDOMClient.createRoot(cont3); try { - ReactDOM.render(, cont1); - ReactDOM.render(, cont2); - ReactDOM.render(, cont3); + await act(() => { + root1.render(); + }); + await act(() => { + root2.render(); + }); + await act(() => { + root3.render(); + }); // Expect we see the V2 color. expect(cont1.firstChild.style.color).toBe('red'); @@ -3527,7 +3566,7 @@ describe('ReactFresh', () => { expect(cont3.firstChild.textContent).toBe('0'); // Bump the state for each of them. - act(() => { + await act(() => { cont1.firstChild.dispatchEvent( new MouseEvent('click', {bubbles: true}), ); @@ -3555,7 +3594,9 @@ describe('ReactFresh', () => { ); }; $RefreshReg$(HelloV3, 'Hello'); - ReactFreshRuntime.performReactRefresh(); + await act(() => { + ReactFreshRuntime.performReactRefresh(); + }); // It should affect all roots. expect(cont1.firstChild.style.color).toBe('green'); @@ -3566,7 +3607,9 @@ describe('ReactFresh', () => { expect(cont3.firstChild.textContent).toBe('1'); // Unmount the second root. - ReactDOM.unmountComponentAtNode(cont2); + await act(() => { + root2.unmount(); + }); // Make the first root throw and unmount on hot update. const HelloV4 = ({id}) => { if (id === 1) { @@ -3580,9 +3623,11 @@ describe('ReactFresh', () => { ); }; $RefreshReg$(HelloV4, 'Hello'); - expect(() => { - ReactFreshRuntime.performReactRefresh(); - }).toThrow('Oops.'); + await expect( + act(() => { + ReactFreshRuntime.performReactRefresh(); + }), + ).rejects.toThrow('Oops.'); // Still, we expect the last root to be updated. expect(cont1.innerHTML).toBe(''); @@ -3791,18 +3836,20 @@ describe('ReactFresh', () => { jest.resetModules(); React = require('react'); ReactDOM = require('react-dom'); + ReactDOMClient = require('react-dom/client'); Scheduler = require('scheduler'); - act = React.act; - internalAct = require('internal-test-utils').act; + act = require('internal-test-utils').act; // Important! Inject into the global hook *after* ReactDOM runs: ReactFreshRuntime = require('react-refresh/runtime'); ReactFreshRuntime.injectIntoGlobalHook(global); + root = ReactDOMClient.createRoot(container); + // We're verifying that we're able to track roots mounted after this point. // The rest of this test is taken from the simplest first test case. - render(() => { + await render(() => { function Hello() { const [val, setVal] = React.useState(0); return ( @@ -3819,7 +3866,7 @@ describe('ReactFresh', () => { const el = container.firstChild; expect(el.textContent).toBe('0'); expect(el.style.color).toBe('blue'); - act(() => { + await act(() => { el.dispatchEvent(new MouseEvent('click', {bubbles: true})); }); expect(el.textContent).toBe('1'); @@ -3846,7 +3893,7 @@ describe('ReactFresh', () => { }); // This simulates the scenario in https://github.com/facebook/react/issues/20100 - it('does not block DevTools when an unsupported renderer is injected', () => { + it('does not block DevTools when an unsupported legacy renderer is injected', () => { if (__DEV__) { initFauxDevToolsHook(); @@ -3872,13 +3919,12 @@ describe('ReactFresh', () => { ReactFreshRuntime = require('react-refresh/runtime'); ReactFreshRuntime.injectIntoGlobalHook(global); - render(() => { - function Hello() { - return
Hi!
; - } - $RefreshReg$(Hello, 'Hello'); - return Hello; - }); + const Hello = () => { + return
Hi!
; + }; + $RefreshReg$(Hello, 'Hello'); + const Component = Hello; + ReactDOM.render(, container); expect(onCommitFiberRoot).toHaveBeenCalled(); }