Repository: facebook/react
Pull request: Convert ReactFresh to createRoot


Changed files:
filename: packages/react-refresh/src/__tests__/ReactFresh-test.js
patch: @@ -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(<Component {...props} />, container);
+    await act(() => {
+      root.render(<Component {...props} />);
     });
     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,23 +153,23 @@ 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);
       expect(el.textContent).toBe('3');
       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,33 +383,33 @@ 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);
       expect(el.textContent).toBe('1');
       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,15 +866,15 @@ describe('ReactFresh', () => {
 
       expect(container.textContent).toBe('Loading');
       await act(() => {
-        jest.runAllTimers();
+        resolve();
       });
       expect(container.textContent).toBe('0');
 
       // Bump the state before patching.
       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 <div>X{children}X</div>;
@@ -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,17 +1646,17 @@ 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');
       expect(el3.style.color).toBe('red');
     }
   });
 
-  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', () => {
         </HelloV1>,
       ];
 
-      // 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 <section data-color="blue">{children}</section>;
@@ -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 <section> elements:
     expect(elements.length).toBe(3);
@@ -2003,30 +2016,32 @@ 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]);
       expect(el.dataset.color).toBe('black');
     });
   }
 
-  it('can remount on signature change within a <root> wrapper', () => {
+  it('can remount on signature change within a <root> 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(() => <Hello />));
+      await testRemountingWithWrapper(Hello =>
+        React.forwardRef(() => <Hello />),
+      );
     }
   });
 
-  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 = <Hello />;
         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 = (
           <Context.Provider value="constant">
@@ -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 = <Context.Consumer>{() => <Hello />}</Context.Consumer>;
         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 = (
           <React.Suspense>
@@ -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 = (
           <React.StrictMode>
             <Hello />
@@ -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 = (
           <>
             <Hello />
@@ -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 = <Hello />;
         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 (
-              <p style={{color: 'red'}} onClick={handleClick}>
-                {tranformed}
-              </p>
-            );
-          }
-          $RefreshReg$(Hello, 'Hello');
-          return Hello;
-        });
+          return (
+            <p style={{color: 'red'}} onClick={handleClick}>
+              {tranformed}
+            </p>
+          );
+        }
+        $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 <p style={{color: 'red'}}>{state.value}</p>;
+      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 <p style={{color: 'red'}}>{state.value}</p>;
+        }
+        $RefreshReg$(Hello, 'Hello');
+        return Hello;
       });
 
       expect(container.firstChild).toBe(el);
@@ -2448,7 +2461,6 @@ describe('ReactFresh', () => {
       };
     });
 
-    const root = ReactDOMClient.createRoot(container);
     root.render(<AppV1 offscreen={true} />);
     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 <h1>Hi</h1>;
         }
@@ -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 <h1>Hi</h1>;
         }
@@ -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 <h1>Hi</h1>;
-          }
-          $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 <h1>Hi</h1>;
+        }
+        $RefreshReg$(Hello, 'Hello');
       });
 
       expect(container.innerHTML).toBe('<p>A</p><h1>Hi</h1><p>B</p>');
       // Run timeout inside effect:
-      act(() => {
-        jest.runAllTimers();
+      await act(() => {
+        crash();
       });
       expect(container.innerHTML).toBe(
         '<p>A</p><h1>Oops: x.slice is not a function</h1><p>B</p>',
@@ -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 <h1>Fixed!</h1>;
-          }
-          $RefreshReg$(Hello, 'Hello');
-        });
+      patch(() => {
+        function Hello() {
+          const [x] = React.useState('');
+          React.useEffect(() => {}, []); // Removes the bad effect code.
+          x.slice(); // Doesn't throw initially.
+          return <h1>Fixed!</h1>;
+        }
+        $RefreshReg$(Hello, 'Hello');
       });
 
       // This should remount the error boundary (but not anything above it).
@@ -2769,35 +2778,33 @@ 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 <h1>Nice.</h1>;
-          }
-          $RefreshReg$(Hello, 'Hello');
-        });
+      patch(() => {
+        function Hello() {
+          const [x] = React.useState('');
+          React.useEffect(() => {}, []);
+          x.slice();
+          return <h1>Nice.</h1>;
+        }
+        $RefreshReg$(Hello, 'Hello');
       });
 
       expect(container.firstChild.nextSibling).toBe(helloNode);
       expect(helloNode.textContent).toBe('Nice.');
     }
   });
 
-  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');
           }
           $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('<h1>Fixed 2!</h1>');
 
       // Updates after intentional unmount are ignored.
-      ReactDOM.unmountComponentAtNode(container);
+      await act(() => {
+        root.unmount();
+      });
       patch(() => {
         function Hello() {
           throw new Error('Ignored');
@@ -2867,22 +2876,24 @@ 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');
           }
           $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 <h1>Hi</h1>;
         }
@@ -2974,7 +2985,9 @@ describe('ReactFresh', () => {
       expect(container.innerHTML).toBe('<h1>At last.</h1>');
 
       // 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 <h1>Hi</h1>;
         }
@@ -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(<RootAV1 />, containerA);
-        ReactDOM.render(<RootBV1 />, containerB);
+      await act(() => {
+        rootA.render(<RootAV1 />);
+        rootB.render(<RootBV1 />);
       });
       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,23 +3082,29 @@ 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');
 
       const RootAV4 = () => {
         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 <div className="Child">{children}</div>;
         }
@@ -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(<HelloV1 id={1} />, cont1);
-        ReactDOM.render(<HelloV2 id={2} />, cont2);
-        ReactDOM.render(<HelloV1 id={3} />, cont3);
+        await act(() => {
+          root1.render(<HelloV1 id={1} />);
+        });
+        await act(() => {
+          root2.render(<HelloV2 id={2} />);
+        });
+        await act(() => {
+          root3.render(<HelloV1 id={3} />);
+        });
 
         // 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 <div>Hi!</div>;
-        }
-        $RefreshReg$(Hello, 'Hello');
-        return Hello;
-      });
+      const Hello = () => {
+        return <div>Hi!</div>;
+      };
+      $RefreshReg$(Hello, 'Hello');
+      const Component = Hello;
+      ReactDOM.render(<Component />, container);
 
       expect(onCommitFiberRoot).toHaveBeenCalled();
     }