From cce00bcd855612e164bcb10aa633edda8763c72f Mon Sep 17 00:00:00 2001 From: Sebastian Silbermann Date: Wed, 31 Jan 2024 22:36:48 +0100 Subject: [PATCH 1/6] Convert ReactFresh to createRoot --- .../src/__tests__/ReactFresh-test.js | 881 +++++++++--------- 1 file changed, 466 insertions(+), 415 deletions(-) diff --git a/packages/react-refresh/src/__tests__/ReactFresh-test.js b/packages/react-refresh/src/__tests__/ReactFresh-test.js index 94e170e3346e6..2f0f51851f663 100644 --- a/packages/react-refresh/src/__tests__/ReactFresh-test.js +++ b/packages/react-refresh/src/__tests__/ReactFresh-test.js @@ -17,25 +17,25 @@ 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__) { jest.resetModules(); + global.IS_REACT_ACT_ENVIRONMENT = true; React = require('react'); ReactFreshRuntime = require('react-refresh/runtime'); ReactFreshRuntime.injectIntoGlobalHook(global); 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 +47,7 @@ describe('ReactFresh', () => { new React.Component().updater, ); container = document.createElement('div'); + root = ReactDOMClient.createRoot(container); document.body.appendChild(container); } }); @@ -63,17 +64,19 @@ 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; } - function patch(version) { + async function patch(version) { const Component = version(); - ReactFreshRuntime.performReactRefresh(); + await act(() => { + ReactFreshRuntime.performReactRefresh(); + }); return Component; } @@ -99,9 +102,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,13 +121,13 @@ 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'); // Perform a hot update. - const HelloV2 = patch(() => { + const HelloV2 = await patch(() => { function Hello() { const [val, setVal] = React.useState(0); return ( @@ -143,7 +146,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 +156,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 +172,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 +192,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,13 +214,13 @@ 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'); // Perform a hot update. - const OuterV2 = patch(() => { + const OuterV2 = await patch(() => { function Hello() { const [val, setVal] = React.useState(0); return ( @@ -239,7 +242,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 +252,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 +282,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,38 +320,38 @@ 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'); // Patch to change the color. - const ParentV2 = patch(() => { + const ParentV2 = await patch(() => { function Hello() { const [val, setVal] = React.useState(0); return ( @@ -383,14 +386,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 +401,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,13 +432,13 @@ 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'); // Perform a hot update. - patch(() => { + await patch(() => { function Hello({color}) { const [val, setVal] = React.useState(0); return ( @@ -458,9 +461,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,13 +486,13 @@ 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'); // Perform a hot update of just the rendering function. - patch(() => { + await patch(() => { function Hello({color}) { const [val, setVal] = React.useState(0); return ( @@ -515,9 +518,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,13 +540,13 @@ 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'); // Perform a hot update. - const OuterV2 = patch(() => { + const OuterV2 = await patch(() => { function Hello() { const [val, setVal] = React.useState(0); return ( @@ -565,7 +568,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 +578,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 +608,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,13 +629,13 @@ 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'); // Perform a hot update. - const OuterV2 = patch(() => { + const OuterV2 = await patch(() => { function Hello() { const [val, setVal] = React.useState(0); return ( @@ -653,7 +656,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 +666,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 +696,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,13 +716,13 @@ 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'); // Perform a hot update of just the rendering function. - patch(() => { + await patch(() => { function Hello() { const [val, setVal] = React.useState(0); return ( @@ -740,9 +743,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,13 +765,13 @@ 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'); // Perform a hot update. - const OuterV2 = patch(() => { + const OuterV2 = await patch(() => { function Hello() { const [val, setVal] = React.useState(0); return ( @@ -790,7 +793,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 +803,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 +835,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 +849,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 +869,7 @@ describe('ReactFresh', () => { expect(container.textContent).toBe('Loading'); await act(() => { - jest.runAllTimers(); + resolve(); }); expect(container.textContent).toBe('0'); @@ -873,13 +877,13 @@ 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'); // Perform a hot update. - const AppV2 = patch(() => { + const AppV2 = await patch(() => { function Hello() { const [val, setVal] = React.useState(0); return ( @@ -892,8 +896,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 +920,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 +930,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 +972,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 +986,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'); @@ -1001,7 +1006,7 @@ describe('ReactFresh', () => { expect(container.textContent).toBe('Loading'); // Perform a hot update. - patch(() => { + await patch(() => { function Hello() { const [val, setVal] = React.useState(0); return ( @@ -1014,7 +1019,7 @@ describe('ReactFresh', () => { }); await act(() => { - jest.runAllTimers(); + resolve(); }); // Expect different color on initial mount. @@ -1023,7 +1028,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); @@ -1031,7 +1036,7 @@ describe('ReactFresh', () => { expect(el.style.color).toBe('red'); // Test another reload. - patch(() => { + await patch(() => { function Hello() { const [val, setVal] = React.useState(0); return ( @@ -1050,7 +1055,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 +1070,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'); @@ -1084,7 +1090,7 @@ describe('ReactFresh', () => { expect(container.textContent).toBe('Loading'); // Perform a hot update. - patch(() => { + await patch(() => { function renderHello() { const [val, setVal] = React.useState(0); return ( @@ -1098,7 +1104,7 @@ describe('ReactFresh', () => { }); await act(() => { - jest.runAllTimers(); + resolve(); }); // Expect different color on initial mount. @@ -1107,7 +1113,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); @@ -1115,7 +1121,7 @@ describe('ReactFresh', () => { expect(el.style.color).toBe('red'); // Test another reload. - patch(() => { + await patch(() => { function renderHello() { const [val, setVal] = React.useState(0); return ( @@ -1135,7 +1141,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 +1156,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'); @@ -1169,7 +1176,7 @@ describe('ReactFresh', () => { expect(container.textContent).toBe('Loading'); // Perform a hot update. - patch(() => { + await patch(() => { function renderHello() { const [val, setVal] = React.useState(0); return ( @@ -1183,7 +1190,7 @@ describe('ReactFresh', () => { }); await act(() => { - jest.runAllTimers(); + resolve(); }); // Expect different color on initial mount. @@ -1192,7 +1199,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); @@ -1200,7 +1207,7 @@ describe('ReactFresh', () => { expect(el.style.color).toBe('red'); // Test another reload. - patch(() => { + await patch(() => { function renderHello() { const [val, setVal] = React.useState(0); return ( @@ -1220,7 +1227,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 +1242,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'); @@ -1254,7 +1262,7 @@ describe('ReactFresh', () => { expect(container.textContent).toBe('Loading'); // Perform a hot update. - patch(() => { + await patch(() => { function renderHello() { const [val, setVal] = React.useState(0); return ( @@ -1268,7 +1276,7 @@ describe('ReactFresh', () => { }); await act(() => { - jest.runAllTimers(); + resolve(); }); // Expect different color on initial mount. @@ -1277,7 +1285,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); @@ -1285,7 +1293,7 @@ describe('ReactFresh', () => { expect(el.style.color).toBe('red'); // Test another reload. - patch(() => { + await patch(() => { function renderHello() { const [val, setVal] = React.useState(0); return ( @@ -1305,7 +1313,7 @@ describe('ReactFresh', () => { it('can patch both trees while suspense is displaying the fallback', async () => { if (__DEV__) { - const AppV1 = render( + const AppV1 = await render( () => { function Hello({children}) { const [val, setVal] = React.useState(0); @@ -1343,7 +1351,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); @@ -1353,7 +1361,7 @@ describe('ReactFresh', () => { expect(primaryChild.style.display).toBe(''); // Perform a hot update. - patch(() => { + await patch(() => { function Hello({children}) { const [val, setVal] = React.useState(0); return ( @@ -1371,7 +1379,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 +1393,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); @@ -1399,7 +1407,7 @@ describe('ReactFresh', () => { expect(fallbackChild.style.display).toBe(''); // Perform a hot update. - patch(() => { + await patch(() => { function Hello({children}) { const [val, setVal] = React.useState(0); return ( @@ -1423,7 +1431,7 @@ describe('ReactFresh', () => { 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'); @@ -1431,7 +1439,7 @@ describe('ReactFresh', () => { expect(primaryChild.style.display).toBe(''); // Perform a hot update. - patch(() => { + await patch(() => { function Hello({children}) { const [val, setVal] = React.useState(0); return ( @@ -1450,11 +1458,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 +1486,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'); @@ -1487,7 +1495,7 @@ describe('ReactFresh', () => { expect(appRenders).toBe(1); // Perform a hot update for Hello only. - patch(() => { + await patch(() => { function Hello() { const [val, setVal] = React.useState(0); return ( @@ -1508,7 +1516,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 +1526,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
; @@ -1547,7 +1555,7 @@ describe('ReactFresh', () => { expect(container.textContent).toBe('XXXXXXXXXX'); helloRenders = 0; - patch(() => { + await patch(() => { function Hello({children}) { helloRenders++; return
O{children}O
; @@ -1559,9 +1567,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,27 +1602,27 @@ 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'); // Perform a hot update for both inner components. - patch(() => { + await patch(() => { function Hello1() { const [val, setVal] = React.useState(0); return ( @@ -1641,7 +1649,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 +1657,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,13 +1678,13 @@ 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'); // Perform a hot update. - const HelloV2 = patch(() => { + const HelloV2 = await patch(() => { function Hello() { const [val, setVal] = React.useState(0); return ( @@ -1697,7 +1705,7 @@ describe('ReactFresh', () => { expect(el.style.color).toBe('red'); // Perform a hot update. - const HelloV3 = patch(() => { + const HelloV3 = await patch(() => { function Hello() { const [val, setVal] = React.useState(0); return ( @@ -1719,7 +1727,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,17 +1736,17 @@ 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'); // Verify we can patch again while preserving the signature. - patch(() => { + await patch(() => { function Hello() { const [val, setVal] = React.useState(0); return ( @@ -1758,7 +1766,7 @@ describe('ReactFresh', () => { expect(newEl.style.color).toBe('purple'); // Check removing the signature also causes a remount. - patch(() => { + await patch(() => { function Hello() { const [val, setVal] = React.useState(0); return ( @@ -1780,7 +1788,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,27 +1918,32 @@ 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]); } } } }); - function runRemountingStressTest(tree) { - patch(() => { + async function runRemountingStressTest(tree) { + await patch(() => { function Hello({children}) { return
{children}
; } @@ -1939,7 +1952,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); @@ -1948,7 +1964,7 @@ describe('ReactFresh', () => { }); // Patch color without changing the signature. - patch(() => { + await patch(() => { function Hello({children}) { return
{children}
; } @@ -1967,7 +1983,7 @@ describe('ReactFresh', () => { }); // Patch color *and* change the signature. - patch(() => { + await patch(() => { function Hello({children}) { return
{children}
; } @@ -1986,7 +2002,7 @@ describe('ReactFresh', () => { }); // Now patch color but *don't* change the signature. - patch(() => { + await patch(() => { function Hello({children}) { return
{children}
; } @@ -2003,8 +2019,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 +2030,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 +2054,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 +2095,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 +2111,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 +2123,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 +2139,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 +2154,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 +2169,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 +2188,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 +2203,8 @@ describe('ReactFresh', () => { } }); - function testRemountingWithWrapper(wrap) { - render(() => { + async function testRemountingWithWrapper(wrap) { + await render(() => { function Hello() { const [val, setVal] = React.useState(0); return ( @@ -2206,13 +2226,13 @@ 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'); // Perform a hot update that doesn't remount. - patch(() => { + await patch(() => { function Hello() { const [val, setVal] = React.useState(0); return ( @@ -2233,7 +2253,7 @@ describe('ReactFresh', () => { expect(el.style.color).toBe('red'); // Perform a hot update that remounts. - patch(() => { + await patch(() => { function Hello() { const [val, setVal] = React.useState(0); return ( @@ -2255,14 +2275,14 @@ 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'); expect(newEl.style.color).toBe('yellow'); // Verify we can patch again while preserving the signature. - patch(() => { + await patch(() => { function Hello() { const [val, setVal] = React.useState(0); return ( @@ -2282,7 +2302,7 @@ describe('ReactFresh', () => { expect(newEl.style.color).toBe('purple'); // Check removing the signature also causes a remount. - patch(() => { + await patch(() => { function Hello() { const [val, setVal] = React.useState(0); return ( @@ -2303,11 +2323,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 +2352,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), []); + await 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 +2386,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 +2396,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 +2416,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}

; + await 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 +2464,6 @@ describe('ReactFresh', () => { }; }); - const root = ReactDOMClient.createRoot(container); root.render(); await waitFor(['App#layout']); const el = container.firstChild; @@ -2456,8 +2471,8 @@ describe('ReactFresh', () => { expect(el.firstChild).toBe(null); // Offscreen content not flushed yet. // Perform a hot update. - patch(() => { - function Hello() { + { + const Hello = () => { React.useLayoutEffect(() => { Scheduler.log('Hello#layout'); }); @@ -2467,9 +2482,10 @@ describe('ReactFresh', () => { {val}

); - } + }; $RefreshReg$(Hello, 'Hello'); - }); + ReactFreshRuntime.performReactRefresh(); + } // It's still offscreen so we don't see anything. expect(container.firstChild).toBe(el); @@ -2482,7 +2498,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, @@ -2495,8 +2511,8 @@ describe('ReactFresh', () => { expect(el.firstChild.style.color).toBe('red'); // Hot reload while we're offscreen. - patch(() => { - function Hello() { + { + const Hello = () => { React.useLayoutEffect(() => { Scheduler.log('Hello#layout'); }); @@ -2506,9 +2522,10 @@ describe('ReactFresh', () => { {val}

); - } + }; $RefreshReg$(Hello, 'Hello'); - }); + ReactFreshRuntime.performReactRefresh(); + } // It's still offscreen so we don't see the updates. expect(container.firstChild).toBe(el); @@ -2522,9 +2539,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

; } @@ -2563,7 +2580,7 @@ describe('ReactFresh', () => { const secondP = firstP.nextSibling.nextSibling; // Perform a hot update that fails. - patch(() => { + await patch(() => { function Hello() { throw new Error('No'); } @@ -2575,7 +2592,7 @@ describe('ReactFresh', () => { expect(container.firstChild.nextSibling.nextSibling).toBe(secondP); // Perform a hot update that fixes the error. - patch(() => { + await patch(() => { function Hello() { return

Fixed!

; } @@ -2589,7 +2606,7 @@ describe('ReactFresh', () => { // Verify next hot reload doesn't remount anything. const helloNode = container.firstChild.nextSibling; - patch(() => { + await patch(() => { function Hello() { return

Nice.

; } @@ -2600,9 +2617,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

; } @@ -2641,7 +2658,7 @@ describe('ReactFresh', () => { const secondP = firstP.nextSibling.nextSibling; // Perform a hot update that fails. - patch(() => { + await patch(() => { function Hello() { throw new Error('No'); } @@ -2653,7 +2670,7 @@ describe('ReactFresh', () => { expect(container.firstChild.nextSibling.nextSibling).toBe(secondP); // Perform a hot update that fixes the error. - patch(() => { + await patch(() => { function Hello() { return

Fixed!

; } @@ -2667,7 +2684,7 @@ describe('ReactFresh', () => { // Verify next hot reload doesn't remount anything. const helloNode = container.firstChild.nextSibling; - patch(() => { + await patch(() => { function Hello() { return

Nice.

; } @@ -2678,9 +2695,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 +2739,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; + await 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 +2766,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'); - }); + await 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 +2783,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'); - }); + await patch(() => { + function Hello() { + const [x] = React.useState(''); + React.useEffect(() => {}, []); + x.slice(); + return

Nice.

; + } + $RefreshReg$(Hello, 'Hello'); }); expect(container.firstChild.nextSibling).toBe(helloNode); @@ -2786,9 +2798,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,23 +2808,23 @@ describe('ReactFresh', () => { $RefreshReg$(Hello, 'Hello'); return Hello; - }); - }).toThrow('No'); + }), + ).rejects.toThrow('No'); expect(container.innerHTML).toBe(''); // A bad retry - expect(() => { + await expect( patch(() => { function Hello() { throw new Error('Not yet'); } $RefreshReg$(Hello, 'Hello'); - }); - }).toThrow('Not yet'); + }), + ).rejects.toThrow('Not yet'); expect(container.innerHTML).toBe(''); // Perform a hot update that fixes the error. - patch(() => { + await patch(() => { function Hello() { return

Fixed!

; } @@ -2822,25 +2834,25 @@ describe('ReactFresh', () => { expect(container.innerHTML).toBe('

Fixed!

'); // Ensure we can keep failing and recovering later. - expect(() => { + await expect( patch(() => { function Hello() { throw new Error('No 2'); } $RefreshReg$(Hello, 'Hello'); - }); - }).toThrow('No 2'); + }), + ).rejects.toThrow('No 2'); expect(container.innerHTML).toBe(''); - expect(() => { + await expect( patch(() => { function Hello() { throw new Error('Not yet 2'); } $RefreshReg$(Hello, 'Hello'); - }); - }).toThrow('Not yet 2'); + }), + ).rejects.toThrow('Not yet 2'); expect(container.innerHTML).toBe(''); - patch(() => { + await patch(() => { function Hello() { return

Fixed 2!

; } @@ -2849,15 +2861,17 @@ describe('ReactFresh', () => { expect(container.innerHTML).toBe('

Fixed 2!

'); // Updates after intentional unmount are ignored. - ReactDOM.unmountComponentAtNode(container); - patch(() => { + await act(() => { + root.unmount(); + }); + await patch(() => { function Hello() { throw new Error('Ignored'); } $RefreshReg$(Hello, 'Hello'); }); expect(container.innerHTML).toBe(''); - patch(() => { + await patch(() => { function Hello() { return

Ignored

; } @@ -2867,9 +2881,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,15 +2891,17 @@ 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(() => { + await patch(() => { function Hello() { return

Fixed!

; } @@ -2896,9 +2912,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

; } @@ -2910,29 +2926,29 @@ describe('ReactFresh', () => { // Perform a hot update that fails. // This removes the root. - expect(() => { + await expect( patch(() => { function Hello() { throw new Error('No'); } $RefreshReg$(Hello, 'Hello'); - }); - }).toThrow('No'); + }), + ).rejects.toThrow('No'); expect(container.innerHTML).toBe(''); // A bad retry - expect(() => { + await expect( patch(() => { function Hello() { throw new Error('Not yet'); } $RefreshReg$(Hello, 'Hello'); - }); - }).toThrow('Not yet'); + }), + ).rejects.toThrow('Not yet'); expect(container.innerHTML).toBe(''); // Perform a hot update that fixes the error. - patch(() => { + await patch(() => { function Hello() { return

Fixed!

; } @@ -2943,7 +2959,7 @@ describe('ReactFresh', () => { // Verify next hot reload doesn't remount anything. const helloNode = container.firstChild; - patch(() => { + await patch(() => { function Hello() { return

Nice.

; } @@ -2953,18 +2969,18 @@ describe('ReactFresh', () => { expect(helloNode.textContent).toBe('Nice.'); // Break again. - expect(() => { + await expect( patch(() => { function Hello() { throw new Error('Oops'); } $RefreshReg$(Hello, 'Hello'); - }); - }).toThrow('Oops'); + }), + ).rejects.toThrow('Oops'); expect(container.innerHTML).toBe(''); // Perform a hot update that fixes the error. - patch(() => { + await patch(() => { function Hello() { return

At last.

; } @@ -2974,9 +2990,11 @@ 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(() => { + await patch(() => { function Hello() { return

Never mind me!

; } @@ -2985,7 +3003,8 @@ describe('ReactFresh', () => { expect(container.innerHTML).toBe(''); // Mount a new container. - render(() => { + root = ReactDOMClient.createRoot(container); + await render(() => { function Hello() { return

Hi

; } @@ -2996,20 +3015,22 @@ describe('ReactFresh', () => { expect(container.innerHTML).toBe('

Hi

'); // Break again. - expect(() => { + await expect( patch(() => { function Hello() { throw new Error('Oops'); } $RefreshReg$(Hello, 'Hello'); - }); - }).toThrow('Oops'); + }), + ).rejects.toThrow('Oops'); 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(() => { + await patch(() => { function Hello() { return

Never mind me!

; } @@ -3019,10 +3040,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 +3057,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 +3069,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 +3087,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 +3099,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,13 +3138,13 @@ 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'); // Perform a hot update. - const HelloV2 = patch(() => { + const HelloV2 = await patch(() => { class Hello extends React.Component { state = {count: 0}; handleClick = () => { @@ -3136,19 +3169,19 @@ 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'); - const HelloV3 = patch(() => { + const HelloV3 = await patch(() => { class Hello extends React.Component { state = {count: 0}; handleClick = () => { @@ -3173,24 +3206,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() { @@ -3207,7 +3240,7 @@ describe('ReactFresh', () => { ); expect(testRef.current.getColor()).toBe('green'); - patch(() => { + await patch(() => { class Hello extends React.Component { getColor() { return 'orange'; @@ -3220,7 +3253,7 @@ describe('ReactFresh', () => { }); expect(testRef.current.getColor()).toBe('orange'); - patch(() => { + await patch(() => { const Hello = React.forwardRef((props, ref) => { React.useImperativeHandle(ref, () => ({ getColor() { @@ -3233,7 +3266,7 @@ describe('ReactFresh', () => { }); expect(testRef.current.getColor()).toBe('pink'); - patch(() => { + await patch(() => { const Hello = React.forwardRef((props, ref) => { React.useImperativeHandle(ref, () => ({ getColor() { @@ -3246,7 +3279,7 @@ describe('ReactFresh', () => { }); expect(testRef.current.getColor()).toBe('yellow'); - patch(() => { + await patch(() => { const Hello = React.forwardRef((props, ref) => { React.useImperativeHandle(ref, () => ({ getColor() { @@ -3261,9 +3294,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,13 +3313,13 @@ 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'); // Perform a hot update that turns it into a class. - const HelloV2 = patch(() => { + const HelloV2 = await patch(() => { class Hello extends React.Component { state = {count: 0}; handleClick = () => { @@ -3311,20 +3344,20 @@ 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'); // Now convert it back to a function. - const HelloV3 = patch(() => { + const HelloV3 = await patch(() => { function Hello() { const [val, setVal] = React.useState(0); return ( @@ -3342,20 +3375,20 @@ 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'); // Now that it's a function, verify edits keep state. - patch(() => { + await patch(() => { function Hello() { const [val, setVal] = React.useState(0); return ( @@ -3373,9 +3406,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 +3514,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 +3537,9 @@ describe('ReactFresh', () => { ); }; $RefreshReg$(HelloV2, 'Hello'); - ReactFreshRuntime.performReactRefresh(); + await act(() => { + ReactFreshRuntime.performReactRefresh(); + }); // Mount three roots. const cont1 = document.createElement('div'); @@ -3513,10 +3548,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 +3571,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 +3599,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 +3612,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 +3628,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 +3841,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,13 +3871,13 @@ 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'); // Perform a hot update. - patch(() => { + await patch(() => { function Hello() { const [val, setVal] = React.useState(0); return ( @@ -3846,7 +3898,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 +3924,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(); } From 39f95f45a0daf993aebe163ed20dc383fe899565 Mon Sep 17 00:00:00 2001 From: Sebastian Silbermann Date: Fri, 2 Feb 2024 19:18:24 +0100 Subject: [PATCH 2/6] Amend assertions --- packages/react-refresh/src/__tests__/ReactFresh-test.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/react-refresh/src/__tests__/ReactFresh-test.js b/packages/react-refresh/src/__tests__/ReactFresh-test.js index 2f0f51851f663..47cf32f9e64d0 100644 --- a/packages/react-refresh/src/__tests__/ReactFresh-test.js +++ b/packages/react-refresh/src/__tests__/ReactFresh-test.js @@ -1419,12 +1419,12 @@ 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'); From c52fc26c5fd15da72aa2dd0491d62f1bb38d66d2 Mon Sep 17 00:00:00 2001 From: Sebastian Silbermann Date: Fri, 2 Feb 2024 19:18:43 +0100 Subject: [PATCH 3/6] Silly mistake --- packages/react-refresh/src/__tests__/ReactFresh-test.js | 1 - 1 file changed, 1 deletion(-) diff --git a/packages/react-refresh/src/__tests__/ReactFresh-test.js b/packages/react-refresh/src/__tests__/ReactFresh-test.js index 47cf32f9e64d0..0e344756ce621 100644 --- a/packages/react-refresh/src/__tests__/ReactFresh-test.js +++ b/packages/react-refresh/src/__tests__/ReactFresh-test.js @@ -28,7 +28,6 @@ describe('ReactFresh', () => { beforeEach(() => { if (__DEV__) { jest.resetModules(); - global.IS_REACT_ACT_ENVIRONMENT = true; React = require('react'); ReactFreshRuntime = require('react-refresh/runtime'); ReactFreshRuntime.injectIntoGlobalHook(global); From 6462aaf53c8c514ae339deed9c0a2bf24151cf2a Mon Sep 17 00:00:00 2001 From: Sebastian Silbermann Date: Fri, 2 Feb 2024 19:27:54 +0100 Subject: [PATCH 4/6] Don't need to wrap performReactRefresh in act --- .../src/__tests__/ReactFresh-test.js | 200 +++++++++--------- 1 file changed, 98 insertions(+), 102 deletions(-) diff --git a/packages/react-refresh/src/__tests__/ReactFresh-test.js b/packages/react-refresh/src/__tests__/ReactFresh-test.js index 0e344756ce621..50e72cae5ae3c 100644 --- a/packages/react-refresh/src/__tests__/ReactFresh-test.js +++ b/packages/react-refresh/src/__tests__/ReactFresh-test.js @@ -71,11 +71,9 @@ describe('ReactFresh', () => { return Component; } - async function patch(version) { + function patch(version) { const Component = version(); - await act(() => { - ReactFreshRuntime.performReactRefresh(); - }); + ReactFreshRuntime.performReactRefresh(); return Component; } @@ -126,7 +124,7 @@ describe('ReactFresh', () => { expect(el.textContent).toBe('1'); // Perform a hot update. - const HelloV2 = await patch(() => { + const HelloV2 = patch(() => { function Hello() { const [val, setVal] = React.useState(0); return ( @@ -219,7 +217,7 @@ describe('ReactFresh', () => { expect(el.textContent).toBe('1'); // Perform a hot update. - const OuterV2 = await patch(() => { + const OuterV2 = patch(() => { function Hello() { const [val, setVal] = React.useState(0); return ( @@ -350,7 +348,7 @@ describe('ReactFresh', () => { expect(el.textContent).toBe('1'); // Patch to change the color. - const ParentV2 = await patch(() => { + const ParentV2 = patch(() => { function Hello() { const [val, setVal] = React.useState(0); return ( @@ -437,7 +435,7 @@ describe('ReactFresh', () => { expect(el.textContent).toBe('1'); // Perform a hot update. - await patch(() => { + patch(() => { function Hello({color}) { const [val, setVal] = React.useState(0); return ( @@ -491,7 +489,7 @@ describe('ReactFresh', () => { expect(el.textContent).toBe('1'); // Perform a hot update of just the rendering function. - await patch(() => { + patch(() => { function Hello({color}) { const [val, setVal] = React.useState(0); return ( @@ -545,7 +543,7 @@ describe('ReactFresh', () => { expect(el.textContent).toBe('1'); // Perform a hot update. - const OuterV2 = await patch(() => { + const OuterV2 = patch(() => { function Hello() { const [val, setVal] = React.useState(0); return ( @@ -634,7 +632,7 @@ describe('ReactFresh', () => { expect(el.textContent).toBe('1'); // Perform a hot update. - const OuterV2 = await patch(() => { + const OuterV2 = patch(() => { function Hello() { const [val, setVal] = React.useState(0); return ( @@ -721,7 +719,7 @@ describe('ReactFresh', () => { expect(el.textContent).toBe('1'); // Perform a hot update of just the rendering function. - await patch(() => { + patch(() => { function Hello() { const [val, setVal] = React.useState(0); return ( @@ -770,7 +768,7 @@ describe('ReactFresh', () => { expect(el.textContent).toBe('1'); // Perform a hot update. - const OuterV2 = await patch(() => { + const OuterV2 = patch(() => { function Hello() { const [val, setVal] = React.useState(0); return ( @@ -882,7 +880,7 @@ describe('ReactFresh', () => { expect(el.textContent).toBe('1'); // Perform a hot update. - const AppV2 = await patch(() => { + const AppV2 = patch(() => { function Hello() { const [val, setVal] = React.useState(0); return ( @@ -1005,7 +1003,7 @@ describe('ReactFresh', () => { expect(container.textContent).toBe('Loading'); // Perform a hot update. - await patch(() => { + patch(() => { function Hello() { const [val, setVal] = React.useState(0); return ( @@ -1035,7 +1033,7 @@ describe('ReactFresh', () => { expect(el.style.color).toBe('red'); // Test another reload. - await patch(() => { + patch(() => { function Hello() { const [val, setVal] = React.useState(0); return ( @@ -1089,7 +1087,7 @@ describe('ReactFresh', () => { expect(container.textContent).toBe('Loading'); // Perform a hot update. - await patch(() => { + patch(() => { function renderHello() { const [val, setVal] = React.useState(0); return ( @@ -1120,7 +1118,7 @@ describe('ReactFresh', () => { expect(el.style.color).toBe('red'); // Test another reload. - await patch(() => { + patch(() => { function renderHello() { const [val, setVal] = React.useState(0); return ( @@ -1175,7 +1173,7 @@ describe('ReactFresh', () => { expect(container.textContent).toBe('Loading'); // Perform a hot update. - await patch(() => { + patch(() => { function renderHello() { const [val, setVal] = React.useState(0); return ( @@ -1206,7 +1204,7 @@ describe('ReactFresh', () => { expect(el.style.color).toBe('red'); // Test another reload. - await patch(() => { + patch(() => { function renderHello() { const [val, setVal] = React.useState(0); return ( @@ -1261,7 +1259,7 @@ describe('ReactFresh', () => { expect(container.textContent).toBe('Loading'); // Perform a hot update. - await patch(() => { + patch(() => { function renderHello() { const [val, setVal] = React.useState(0); return ( @@ -1292,7 +1290,7 @@ describe('ReactFresh', () => { expect(el.style.color).toBe('red'); // Test another reload. - await patch(() => { + patch(() => { function renderHello() { const [val, setVal] = React.useState(0); return ( @@ -1360,7 +1358,7 @@ describe('ReactFresh', () => { expect(primaryChild.style.display).toBe(''); // Perform a hot update. - await patch(() => { + patch(() => { function Hello({children}) { const [val, setVal] = React.useState(0); return ( @@ -1406,7 +1404,7 @@ describe('ReactFresh', () => { expect(fallbackChild.style.display).toBe(''); // Perform a hot update. - await patch(() => { + patch(() => { function Hello({children}) { const [val, setVal] = React.useState(0); return ( @@ -1438,7 +1436,7 @@ describe('ReactFresh', () => { expect(primaryChild.style.display).toBe(''); // Perform a hot update. - await patch(() => { + patch(() => { function Hello({children}) { const [val, setVal] = React.useState(0); return ( @@ -1494,7 +1492,7 @@ describe('ReactFresh', () => { expect(appRenders).toBe(1); // Perform a hot update for Hello only. - await patch(() => { + patch(() => { function Hello() { const [val, setVal] = React.useState(0); return ( @@ -1554,7 +1552,7 @@ describe('ReactFresh', () => { expect(container.textContent).toBe('XXXXXXXXXX'); helloRenders = 0; - await patch(() => { + patch(() => { function Hello({children}) { helloRenders++; return
O{children}O
; @@ -1621,7 +1619,7 @@ describe('ReactFresh', () => { expect(el2.textContent).toBe('1'); // Perform a hot update for both inner components. - await patch(() => { + patch(() => { function Hello1() { const [val, setVal] = React.useState(0); return ( @@ -1683,7 +1681,7 @@ describe('ReactFresh', () => { expect(el.textContent).toBe('1'); // Perform a hot update. - const HelloV2 = await patch(() => { + const HelloV2 = patch(() => { function Hello() { const [val, setVal] = React.useState(0); return ( @@ -1704,7 +1702,7 @@ describe('ReactFresh', () => { expect(el.style.color).toBe('red'); // Perform a hot update. - const HelloV3 = await patch(() => { + const HelloV3 = patch(() => { function Hello() { const [val, setVal] = React.useState(0); return ( @@ -1745,7 +1743,7 @@ describe('ReactFresh', () => { expect(newEl.style.color).toBe('yellow'); // Verify we can patch again while preserving the signature. - await patch(() => { + patch(() => { function Hello() { const [val, setVal] = React.useState(0); return ( @@ -1765,7 +1763,7 @@ describe('ReactFresh', () => { expect(newEl.style.color).toBe('purple'); // Check removing the signature also causes a remount. - await patch(() => { + patch(() => { function Hello() { const [val, setVal] = React.useState(0); return ( @@ -1942,7 +1940,7 @@ describe('ReactFresh', () => { }); async function runRemountingStressTest(tree) { - await patch(() => { + patch(() => { function Hello({children}) { return
{children}
; } @@ -1963,7 +1961,7 @@ describe('ReactFresh', () => { }); // Patch color without changing the signature. - await patch(() => { + patch(() => { function Hello({children}) { return
{children}
; } @@ -1982,7 +1980,7 @@ describe('ReactFresh', () => { }); // Patch color *and* change the signature. - await patch(() => { + patch(() => { function Hello({children}) { return
{children}
; } @@ -2001,7 +1999,7 @@ describe('ReactFresh', () => { }); // Now patch color but *don't* change the signature. - await patch(() => { + patch(() => { function Hello({children}) { return
{children}
; } @@ -2231,7 +2229,7 @@ describe('ReactFresh', () => { expect(el.textContent).toBe('1'); // Perform a hot update that doesn't remount. - await patch(() => { + patch(() => { function Hello() { const [val, setVal] = React.useState(0); return ( @@ -2252,7 +2250,7 @@ describe('ReactFresh', () => { expect(el.style.color).toBe('red'); // Perform a hot update that remounts. - await patch(() => { + patch(() => { function Hello() { const [val, setVal] = React.useState(0); return ( @@ -2281,7 +2279,7 @@ describe('ReactFresh', () => { expect(newEl.style.color).toBe('yellow'); // Verify we can patch again while preserving the signature. - await patch(() => { + patch(() => { function Hello() { const [val, setVal] = React.useState(0); return ( @@ -2301,7 +2299,7 @@ describe('ReactFresh', () => { expect(newEl.style.color).toBe('purple'); // Check removing the signature also causes a remount. - await patch(() => { + patch(() => { function Hello() { const [val, setVal] = React.useState(0); return ( @@ -2358,7 +2356,7 @@ describe('ReactFresh', () => { expect(useEffectWithEmptyArrayCalls).toBe(1); // useEffect didn't re-run // Perform a hot update. - await patch(() => { + patch(() => { function Hello() { const [val, setVal] = React.useState(0); const tranformed = React.useMemo(() => val * 10, [val]); @@ -2415,7 +2413,7 @@ describe('ReactFresh', () => { expect(el.style.color).toBe('blue'); // Perform a hot update. - await patch(() => { + patch(() => { function Hello() { const source = React.useMemo(() => ({value: 20}), []); const [state, setState] = React.useState({value: null}); @@ -2470,8 +2468,8 @@ describe('ReactFresh', () => { expect(el.firstChild).toBe(null); // Offscreen content not flushed yet. // Perform a hot update. - { - const Hello = () => { + patch(() => { + function Hello() { React.useLayoutEffect(() => { Scheduler.log('Hello#layout'); }); @@ -2481,10 +2479,9 @@ describe('ReactFresh', () => { {val}

); - }; + } $RefreshReg$(Hello, 'Hello'); - ReactFreshRuntime.performReactRefresh(); - } + }); // It's still offscreen so we don't see anything. expect(container.firstChild).toBe(el); @@ -2510,8 +2507,8 @@ describe('ReactFresh', () => { expect(el.firstChild.style.color).toBe('red'); // Hot reload while we're offscreen. - { - const Hello = () => { + patch(() => { + function Hello() { React.useLayoutEffect(() => { Scheduler.log('Hello#layout'); }); @@ -2521,10 +2518,9 @@ describe('ReactFresh', () => { {val}

); - }; + } $RefreshReg$(Hello, 'Hello'); - ReactFreshRuntime.performReactRefresh(); - } + }); // It's still offscreen so we don't see the updates. expect(container.firstChild).toBe(el); @@ -2579,7 +2575,7 @@ describe('ReactFresh', () => { const secondP = firstP.nextSibling.nextSibling; // Perform a hot update that fails. - await patch(() => { + patch(() => { function Hello() { throw new Error('No'); } @@ -2591,7 +2587,7 @@ describe('ReactFresh', () => { expect(container.firstChild.nextSibling.nextSibling).toBe(secondP); // Perform a hot update that fixes the error. - await patch(() => { + patch(() => { function Hello() { return

Fixed!

; } @@ -2605,7 +2601,7 @@ describe('ReactFresh', () => { // Verify next hot reload doesn't remount anything. const helloNode = container.firstChild.nextSibling; - await patch(() => { + patch(() => { function Hello() { return

Nice.

; } @@ -2657,7 +2653,7 @@ describe('ReactFresh', () => { const secondP = firstP.nextSibling.nextSibling; // Perform a hot update that fails. - await patch(() => { + patch(() => { function Hello() { throw new Error('No'); } @@ -2669,7 +2665,7 @@ describe('ReactFresh', () => { expect(container.firstChild.nextSibling.nextSibling).toBe(secondP); // Perform a hot update that fixes the error. - await patch(() => { + patch(() => { function Hello() { return

Fixed!

; } @@ -2683,7 +2679,7 @@ describe('ReactFresh', () => { // Verify next hot reload doesn't remount anything. const helloNode = container.firstChild.nextSibling; - await patch(() => { + patch(() => { function Hello() { return

Nice.

; } @@ -2739,7 +2735,7 @@ describe('ReactFresh', () => { // Perform a hot update that fails. let crash; - await patch(() => { + patch(() => { function Hello() { const [x, setX] = React.useState(''); React.useEffect(() => { @@ -2765,7 +2761,7 @@ describe('ReactFresh', () => { expect(container.firstChild.nextSibling.nextSibling).toBe(secondP); // Perform a hot update that fixes the error. - await patch(() => { + patch(() => { function Hello() { const [x] = React.useState(''); React.useEffect(() => {}, []); // Removes the bad effect code. @@ -2782,7 +2778,7 @@ describe('ReactFresh', () => { // Verify next hot reload doesn't remount anything. const helloNode = container.firstChild.nextSibling; - await patch(() => { + patch(() => { function Hello() { const [x] = React.useState(''); React.useEffect(() => {}, []); @@ -2812,18 +2808,18 @@ describe('ReactFresh', () => { expect(container.innerHTML).toBe(''); // A bad retry - await expect( + expect(() => { patch(() => { function Hello() { throw new Error('Not yet'); } $RefreshReg$(Hello, 'Hello'); - }), - ).rejects.toThrow('Not yet'); + }); + }).toThrow('Not yet'); expect(container.innerHTML).toBe(''); // Perform a hot update that fixes the error. - await patch(() => { + patch(() => { function Hello() { return

Fixed!

; } @@ -2833,25 +2829,25 @@ describe('ReactFresh', () => { expect(container.innerHTML).toBe('

Fixed!

'); // Ensure we can keep failing and recovering later. - await expect( + expect(() => { patch(() => { function Hello() { throw new Error('No 2'); } $RefreshReg$(Hello, 'Hello'); - }), - ).rejects.toThrow('No 2'); + }); + }).toThrow('No 2'); expect(container.innerHTML).toBe(''); - await expect( + expect(() => { patch(() => { function Hello() { throw new Error('Not yet 2'); } $RefreshReg$(Hello, 'Hello'); - }), - ).rejects.toThrow('Not yet 2'); + }); + }).toThrow('Not yet 2'); expect(container.innerHTML).toBe(''); - await patch(() => { + patch(() => { function Hello() { return

Fixed 2!

; } @@ -2863,14 +2859,14 @@ describe('ReactFresh', () => { await act(() => { root.unmount(); }); - await patch(() => { + patch(() => { function Hello() { throw new Error('Ignored'); } $RefreshReg$(Hello, 'Hello'); }); expect(container.innerHTML).toBe(''); - await patch(() => { + patch(() => { function Hello() { return

Ignored

; } @@ -2900,7 +2896,7 @@ describe('ReactFresh', () => { }); // Perform a hot update that fixes the error. - await patch(() => { + patch(() => { function Hello() { return

Fixed!

; } @@ -2925,29 +2921,29 @@ describe('ReactFresh', () => { // Perform a hot update that fails. // This removes the root. - await expect( + expect(() => { patch(() => { function Hello() { throw new Error('No'); } $RefreshReg$(Hello, 'Hello'); - }), - ).rejects.toThrow('No'); + }); + }).toThrow('No'); expect(container.innerHTML).toBe(''); // A bad retry - await expect( + expect(() => { patch(() => { function Hello() { throw new Error('Not yet'); } $RefreshReg$(Hello, 'Hello'); - }), - ).rejects.toThrow('Not yet'); + }); + }).toThrow('Not yet'); expect(container.innerHTML).toBe(''); // Perform a hot update that fixes the error. - await patch(() => { + patch(() => { function Hello() { return

Fixed!

; } @@ -2958,7 +2954,7 @@ describe('ReactFresh', () => { // Verify next hot reload doesn't remount anything. const helloNode = container.firstChild; - await patch(() => { + patch(() => { function Hello() { return

Nice.

; } @@ -2968,18 +2964,18 @@ describe('ReactFresh', () => { expect(helloNode.textContent).toBe('Nice.'); // Break again. - await expect( + expect(() => { patch(() => { function Hello() { throw new Error('Oops'); } $RefreshReg$(Hello, 'Hello'); - }), - ).rejects.toThrow('Oops'); + }); + }).toThrow('Oops'); expect(container.innerHTML).toBe(''); // Perform a hot update that fixes the error. - await patch(() => { + patch(() => { function Hello() { return

At last.

; } @@ -2993,7 +2989,7 @@ describe('ReactFresh', () => { root.unmount(); }); expect(container.innerHTML).toBe(''); - await patch(() => { + patch(() => { function Hello() { return

Never mind me!

; } @@ -3014,14 +3010,14 @@ describe('ReactFresh', () => { expect(container.innerHTML).toBe('

Hi

'); // Break again. - await expect( + expect(() => { patch(() => { function Hello() { throw new Error('Oops'); } $RefreshReg$(Hello, 'Hello'); - }), - ).rejects.toThrow('Oops'); + }); + }).toThrow('Oops'); expect(container.innerHTML).toBe(''); // Check we don't attempt to reverse an intentional unmount, even after an error. @@ -3029,7 +3025,7 @@ describe('ReactFresh', () => { root.unmount(); }); expect(container.innerHTML).toBe(''); - await patch(() => { + patch(() => { function Hello() { return

Never mind me!

; } @@ -3143,7 +3139,7 @@ describe('ReactFresh', () => { expect(el.textContent).toBe('1'); // Perform a hot update. - const HelloV2 = await patch(() => { + const HelloV2 = patch(() => { class Hello extends React.Component { state = {count: 0}; handleClick = () => { @@ -3180,7 +3176,7 @@ describe('ReactFresh', () => { expect(newEl.style.color).toBe('red'); expect(newEl.textContent).toBe('1'); - const HelloV3 = await patch(() => { + const HelloV3 = patch(() => { class Hello extends React.Component { state = {count: 0}; handleClick = () => { @@ -3239,7 +3235,7 @@ describe('ReactFresh', () => { ); expect(testRef.current.getColor()).toBe('green'); - await patch(() => { + patch(() => { class Hello extends React.Component { getColor() { return 'orange'; @@ -3252,7 +3248,7 @@ describe('ReactFresh', () => { }); expect(testRef.current.getColor()).toBe('orange'); - await patch(() => { + patch(() => { const Hello = React.forwardRef((props, ref) => { React.useImperativeHandle(ref, () => ({ getColor() { @@ -3265,7 +3261,7 @@ describe('ReactFresh', () => { }); expect(testRef.current.getColor()).toBe('pink'); - await patch(() => { + patch(() => { const Hello = React.forwardRef((props, ref) => { React.useImperativeHandle(ref, () => ({ getColor() { @@ -3278,7 +3274,7 @@ describe('ReactFresh', () => { }); expect(testRef.current.getColor()).toBe('yellow'); - await patch(() => { + patch(() => { const Hello = React.forwardRef((props, ref) => { React.useImperativeHandle(ref, () => ({ getColor() { @@ -3318,7 +3314,7 @@ describe('ReactFresh', () => { expect(el.textContent).toBe('1'); // Perform a hot update that turns it into a class. - const HelloV2 = await patch(() => { + const HelloV2 = patch(() => { class Hello extends React.Component { state = {count: 0}; handleClick = () => { @@ -3356,7 +3352,7 @@ describe('ReactFresh', () => { expect(newEl.textContent).toBe('1'); // Now convert it back to a function. - const HelloV3 = await patch(() => { + const HelloV3 = patch(() => { function Hello() { const [val, setVal] = React.useState(0); return ( @@ -3387,7 +3383,7 @@ describe('ReactFresh', () => { expect(finalEl.textContent).toBe('1'); // Now that it's a function, verify edits keep state. - await patch(() => { + patch(() => { function Hello() { const [val, setVal] = React.useState(0); return ( @@ -3876,7 +3872,7 @@ describe('ReactFresh', () => { expect(el.textContent).toBe('1'); // Perform a hot update. - await patch(() => { + patch(() => { function Hello() { const [val, setVal] = React.useState(0); return ( From caac07853f3c906d4a903e685c3efc9b67c92ca7 Mon Sep 17 00:00:00 2001 From: Sebastian Silbermann Date: Fri, 2 Feb 2024 19:48:39 +0100 Subject: [PATCH 5/6] Update test name to reflect new behavior --- packages/react-refresh/src/__tests__/ReactFresh-test.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/react-refresh/src/__tests__/ReactFresh-test.js b/packages/react-refresh/src/__tests__/ReactFresh-test.js index 50e72cae5ae3c..b3464765c5529 100644 --- a/packages/react-refresh/src/__tests__/ReactFresh-test.js +++ b/packages/react-refresh/src/__tests__/ReactFresh-test.js @@ -1308,7 +1308,7 @@ 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 = await render( () => { From 6aea7f81fe7ce8590f348bc1d3b085ce42590b08 Mon Sep 17 00:00:00 2001 From: Sebastian Silbermann Date: Fri, 2 Feb 2024 20:04:45 +0100 Subject: [PATCH 6/6] Increase timeout for slow tests --- packages/react-refresh/src/__tests__/ReactFresh-test.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/react-refresh/src/__tests__/ReactFresh-test.js b/packages/react-refresh/src/__tests__/ReactFresh-test.js index b3464765c5529..38b74563c30a7 100644 --- a/packages/react-refresh/src/__tests__/ReactFresh-test.js +++ b/packages/react-refresh/src/__tests__/ReactFresh-test.js @@ -1937,7 +1937,7 @@ describe('ReactFresh', () => { } } } - }); + }, 10000); async function runRemountingStressTest(tree) { patch(() => {