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


Changed files:
filename: packages/react-dom/src/__tests__/ReactDOMEventListener-test.js
patch: @@ -12,30 +12,37 @@
 describe('ReactDOMEventListener', () => {
   let React;
   let ReactDOM;
+  let ReactDOMClient;
   let ReactDOMServer;
+  let act;
 
   beforeEach(() => {
     jest.resetModules();
     React = require('react');
     ReactDOM = require('react-dom');
+    ReactDOMClient = require('react-dom/client');
     ReactDOMServer = require('react-dom/server');
+    act = require('internal-test-utils').act;
   });
 
   describe('Propagation', () => {
-    it('should propagate events one level down', () => {
+    it('should propagate events one level down', async () => {
       const mouseOut = jest.fn();
       const onMouseOut = event => mouseOut(event.currentTarget);
 
       const childContainer = document.createElement('div');
       const parentContainer = document.createElement('div');
-      const childNode = ReactDOM.render(
-        <div onMouseOut={onMouseOut}>Child</div>,
-        childContainer,
-      );
-      const parentNode = ReactDOM.render(
-        <div onMouseOut={onMouseOut}>div</div>,
-        parentContainer,
-      );
+
+      const childRoot = ReactDOMClient.createRoot(childContainer);
+      const parentRoot = ReactDOMClient.createRoot(parentContainer);
+
+      await act(() => {
+        childRoot.render(<div onMouseOut={onMouseOut}>Child</div>);
+        parentRoot.render(<div onMouseOut={onMouseOut}>Parent</div>);
+      });
+      const parentNode = parentContainer.firstChild;
+      const childNode = childContainer.firstChild;
+
       parentNode.appendChild(childContainer);
       document.body.appendChild(parentContainer);
 
@@ -44,34 +51,35 @@ describe('ReactDOMEventListener', () => {
         nativeEvent.initEvent('mouseout', true, true);
         childNode.dispatchEvent(nativeEvent);
 
-        expect(mouseOut).toBeCalled();
         expect(mouseOut).toHaveBeenCalledTimes(2);
-        expect(mouseOut.mock.calls[0][0]).toEqual(childNode);
-        expect(mouseOut.mock.calls[1][0]).toEqual(parentNode);
+        expect(mouseOut).toHaveBeenNthCalledWith(1, childNode);
+        expect(mouseOut).toHaveBeenNthCalledWith(2, parentNode);
       } finally {
         document.body.removeChild(parentContainer);
       }
     });
 
-    it('should propagate events two levels down', () => {
+    it('should propagate events two levels down', async () => {
       const mouseOut = jest.fn();
       const onMouseOut = event => mouseOut(event.currentTarget);
 
       const childContainer = document.createElement('div');
       const parentContainer = document.createElement('div');
       const grandParentContainer = document.createElement('div');
-      const childNode = ReactDOM.render(
-        <div onMouseOut={onMouseOut}>Child</div>,
-        childContainer,
-      );
-      const parentNode = ReactDOM.render(
-        <div onMouseOut={onMouseOut}>Parent</div>,
-        parentContainer,
-      );
-      const grandParentNode = ReactDOM.render(
-        <div onMouseOut={onMouseOut}>Parent</div>,
-        grandParentContainer,
-      );
+
+      const childRoot = ReactDOMClient.createRoot(childContainer);
+      const parentRoot = ReactDOMClient.createRoot(parentContainer);
+      const grandParentRoot = ReactDOMClient.createRoot(grandParentContainer);
+
+      await act(() => {
+        childRoot.render(<div onMouseOut={onMouseOut}>Child</div>);
+        parentRoot.render(<div onMouseOut={onMouseOut}>Parent</div>);
+        grandParentRoot.render(<div onMouseOut={onMouseOut}>Grandparent</div>);
+      });
+      const childNode = childContainer.firstChild;
+      const parentNode = parentContainer.firstChild;
+      const grandParentNode = grandParentContainer.firstChild;
+
       parentNode.appendChild(childContainer);
       grandParentNode.appendChild(parentContainer);
 
@@ -82,18 +90,17 @@ describe('ReactDOMEventListener', () => {
         nativeEvent.initEvent('mouseout', true, true);
         childNode.dispatchEvent(nativeEvent);
 
-        expect(mouseOut).toBeCalled();
         expect(mouseOut).toHaveBeenCalledTimes(3);
-        expect(mouseOut.mock.calls[0][0]).toEqual(childNode);
-        expect(mouseOut.mock.calls[1][0]).toEqual(parentNode);
-        expect(mouseOut.mock.calls[2][0]).toEqual(grandParentNode);
+        expect(mouseOut).toHaveBeenNthCalledWith(1, childNode);
+        expect(mouseOut).toHaveBeenNthCalledWith(2, parentNode);
+        expect(mouseOut).toHaveBeenNthCalledWith(3, grandParentNode);
       } finally {
         document.body.removeChild(grandParentContainer);
       }
     });
 
     // Regression test for https://github.com/facebook/react/issues/1105
-    it('should not get confused by disappearing elements', () => {
+    it('should not get confused by disappearing elements', async () => {
       const container = document.createElement('div');
       document.body.appendChild(container);
 
@@ -119,12 +126,17 @@ describe('ReactDOMEventListener', () => {
             }
           }
         }
-        ReactDOM.render(<MyComponent />, container);
-        container.firstChild.dispatchEvent(
-          new MouseEvent('click', {
-            bubbles: true,
-          }),
-        );
+        const root = ReactDOMClient.createRoot(container);
+        await act(() => {
+          root.render(<MyComponent />);
+        });
+        await act(() => {
+          container.firstChild.dispatchEvent(
+            new MouseEvent('click', {
+              bubbles: true,
+            }),
+          );
+        });
         expect(container.firstChild.textContent).toBe('clicked!');
       } finally {
         document.body.removeChild(container);
@@ -188,48 +200,46 @@ describe('ReactDOMEventListener', () => {
     });
   });
 
-  it('should not fire duplicate events for a React DOM tree', () => {
+  it('should not fire duplicate events for a React DOM tree', async () => {
     const mouseOut = jest.fn();
     const onMouseOut = event => mouseOut(event.target);
 
+    const innerRef = React.createRef();
     class Wrapper extends React.Component {
-      innerRef = React.createRef();
-      getInner = () => {
-        return this.innerRef.current;
-      };
-
       render() {
-        const inner = <div ref={this.innerRef}>Inner</div>;
         return (
           <div>
             <div onMouseOut={onMouseOut} id="outer">
-              {inner}
+              <div ref={innerRef}>Inner</div>
             </div>
           </div>
         );
       }
     }
 
     const container = document.createElement('div');
-    const instance = ReactDOM.render(<Wrapper />, container);
+    const root = ReactDOMClient.createRoot(container);
+    await act(() => {
+      root.render(<Wrapper />);
+    });
 
     document.body.appendChild(container);
 
     try {
       const nativeEvent = document.createEvent('Event');
       nativeEvent.initEvent('mouseout', true, true);
-      instance.getInner().dispatchEvent(nativeEvent);
+      await act(() => {
+        innerRef.current.dispatchEvent(nativeEvent);
+      });
 
-      expect(mouseOut).toBeCalled();
-      expect(mouseOut).toHaveBeenCalledTimes(1);
-      expect(mouseOut.mock.calls[0][0]).toEqual(instance.getInner());
+      expect(mouseOut).toBeCalledWith(innerRef.current);
     } finally {
       document.body.removeChild(container);
     }
   });
 
   // Regression test for https://github.com/facebook/react/pull/12877
-  it('should not fire form events twice', () => {
+  it('should not fire form events twice', async () => {
     const container = document.createElement('div');
     document.body.appendChild(container);
 
@@ -239,43 +249,54 @@ describe('ReactDOMEventListener', () => {
     const handleInvalid = jest.fn();
     const handleReset = jest.fn();
     const handleSubmit = jest.fn();
-    ReactDOM.render(
-      <form ref={formRef} onReset={handleReset} onSubmit={handleSubmit}>
-        <input ref={inputRef} onInvalid={handleInvalid} />
-      </form>,
-      container,
-    );
 
-    inputRef.current.dispatchEvent(
-      new Event('invalid', {
-        // https://developer.mozilla.org/en-US/docs/Web/Events/invalid
-        bubbles: false,
-      }),
-    );
+    const root = ReactDOMClient.createRoot(container);
+    await act(() => {
+      root.render(
+        <form ref={formRef} onReset={handleReset} onSubmit={handleSubmit}>
+          <input ref={inputRef} onInvalid={handleInvalid} />
+        </form>,
+      );
+    });
+
+    await act(() => {
+      inputRef.current.dispatchEvent(
+        new Event('invalid', {
+          // https://developer.mozilla.org/en-US/docs/Web/Events/invalid
+          bubbles: false,
+        }),
+      );
+    });
     expect(handleInvalid).toHaveBeenCalledTimes(1);
 
-    formRef.current.dispatchEvent(
-      new Event('reset', {
-        // https://developer.mozilla.org/en-US/docs/Web/Events/reset
-        bubbles: true,
-      }),
-    );
+    await act(() => {
+      formRef.current.dispatchEvent(
+        new Event('reset', {
+          // https://developer.mozilla.org/en-US/docs/Web/Events/reset
+          bubbles: true,
+        }),
+      );
+    });
     expect(handleReset).toHaveBeenCalledTimes(1);
 
-    formRef.current.dispatchEvent(
-      new Event('submit', {
-        // https://developer.mozilla.org/en-US/docs/Web/Events/submit
-        bubbles: true,
-      }),
-    );
+    await act(() => {
+      formRef.current.dispatchEvent(
+        new Event('submit', {
+          // https://developer.mozilla.org/en-US/docs/Web/Events/submit
+          bubbles: true,
+        }),
+      );
+    });
     expect(handleSubmit).toHaveBeenCalledTimes(1);
 
-    formRef.current.dispatchEvent(
-      new Event('submit', {
-        // Might happen on older browsers.
-        bubbles: true,
-      }),
-    );
+    await act(() => {
+      formRef.current.dispatchEvent(
+        new Event('submit', {
+          // Might happen on older browsers.
+          bubbles: true,
+        }),
+      );
+    });
     expect(handleSubmit).toHaveBeenCalledTimes(2); // It already fired in this test.
 
     document.body.removeChild(container);
@@ -284,7 +305,7 @@ describe('ReactDOMEventListener', () => {
   // This tests an implementation detail that submit/reset events are listened to
   // at the document level, which is necessary for event replaying to work.
   // They bubble in all modern browsers.
-  it('should not receive submit events if native, interim DOM handler prevents it', () => {
+  it('should not receive submit events if native, interim DOM handler prevents it', async () => {
     const container = document.createElement('div');
     document.body.appendChild(container);
 
@@ -294,30 +315,34 @@ describe('ReactDOMEventListener', () => {
 
       const handleSubmit = jest.fn();
       const handleReset = jest.fn();
-      ReactDOM.render(
-        <div ref={interimRef}>
-          <form ref={formRef} onSubmit={handleSubmit} onReset={handleReset} />
-        </div>,
-        container,
-      );
+      const root = ReactDOMClient.createRoot(container);
+      await act(() => {
+        root.render(
+          <div ref={interimRef}>
+            <form ref={formRef} onSubmit={handleSubmit} onReset={handleReset} />
+          </div>,
+        );
+      });
 
       interimRef.current.onsubmit = nativeEvent =>
         nativeEvent.stopPropagation();
       interimRef.current.onreset = nativeEvent => nativeEvent.stopPropagation();
 
-      formRef.current.dispatchEvent(
-        new Event('submit', {
-          // https://developer.mozilla.org/en-US/docs/Web/Events/submit
-          bubbles: true,
-        }),
-      );
+      await act(() => {
+        formRef.current.dispatchEvent(
+          new Event('submit', {
+            // https://developer.mozilla.org/en-US/docs/Web/Events/submit
+            bubbles: true,
+          }),
+        );
 
-      formRef.current.dispatchEvent(
-        new Event('reset', {
-          // https://developer.mozilla.org/en-US/docs/Web/Events/reset
-          bubbles: true,
-        }),
-      );
+        formRef.current.dispatchEvent(
+          new Event('reset', {
+            // https://developer.mozilla.org/en-US/docs/Web/Events/reset
+            bubbles: true,
+          }),
+        );
+      });
 
       expect(handleSubmit).not.toHaveBeenCalled();
       expect(handleReset).not.toHaveBeenCalled();
@@ -326,7 +351,7 @@ describe('ReactDOMEventListener', () => {
     }
   });
 
-  it('should dispatch loadstart only for media elements', () => {
+  it('should dispatch loadstart only for media elements', async () => {
     const container = document.createElement('div');
     document.body.appendChild(container);
 
@@ -336,35 +361,41 @@ describe('ReactDOMEventListener', () => {
 
       const handleImgLoadStart = jest.fn();
       const handleVideoLoadStart = jest.fn();
-      ReactDOM.render(
-        <div>
-          <img ref={imgRef} onLoadStart={handleImgLoadStart} />
-          <video ref={videoRef} onLoadStart={handleVideoLoadStart} />
-        </div>,
-        container,
-      );
-
-      // Note for debugging: loadstart currently doesn't fire in Chrome.
-      // https://bugs.chromium.org/p/chromium/issues/detail?id=458851
-      imgRef.current.dispatchEvent(
-        new ProgressEvent('loadstart', {
-          bubbles: false,
-        }),
-      );
+      const root = ReactDOMClient.createRoot(container);
+      await act(() => {
+        root.render(
+          <div>
+            <img ref={imgRef} onLoadStart={handleImgLoadStart} />
+            <video ref={videoRef} onLoadStart={handleVideoLoadStart} />
+          </div>,
+        );
+      });
+
+      await act(() => {
+        // Note for debugging: loadstart currently doesn't fire in Chrome.
+        // https://bugs.chromium.org/p/chromium/issues/detail?id=458851
+        imgRef.current.dispatchEvent(
+          new ProgressEvent('loadstart', {
+            bubbles: false,
+          }),
+        );
+      });
       expect(handleImgLoadStart).toHaveBeenCalledTimes(0);
 
-      videoRef.current.dispatchEvent(
-        new ProgressEvent('loadstart', {
-          bubbles: false,
-        }),
-      );
+      await act(() => {
+        videoRef.current.dispatchEvent(
+          new ProgressEvent('loadstart', {
+            bubbles: false,
+          }),
+        );
+      });
       expect(handleVideoLoadStart).toHaveBeenCalledTimes(1);
     } finally {
       document.body.removeChild(container);
     }
   });
 
-  it('should not attempt to listen to unnecessary events on the top level', () => {
+  it('should not attempt to listen to unnecessary events on the top level', async () => {
     const container = document.createElement('div');
     document.body.appendChild(container);
 
@@ -451,22 +482,25 @@ describe('ReactDOMEventListener', () => {
     try {
       // We expect that mounting this tree will
       // *not* attach handlers for any top-level events.
-      ReactDOM.render(
-        <div onPlay={handleVideoPlayDelegated}>
-          <video ref={videoRef} {...mediaEvents} onPlay={handleVideoPlay} />
-          <audio {...mediaEvents}>
-            <source {...mediaEvents} />
-          </audio>
-        </div>,
-        container,
-      );
-
-      // Also verify dispatching one of them works
-      videoRef.current.dispatchEvent(
-        new Event('play', {
-          bubbles: false,
-        }),
-      );
+      const root = ReactDOMClient.createRoot(container);
+      await act(() => {
+        root.render(
+          <div onPlay={handleVideoPlayDelegated}>
+            <video ref={videoRef} {...mediaEvents} onPlay={handleVideoPlay} />
+            <audio {...mediaEvents}>
+              <source {...mediaEvents} />
+            </audio>
+          </div>,
+        );
+      });
+      await act(() => {
+        // Also verify dispatching one of them works
+        videoRef.current.dispatchEvent(
+          new Event('play', {
+            bubbles: false,
+          }),
+        );
+      });
       expect(handleVideoPlay).toHaveBeenCalledTimes(1);
       // Unlike browsers, we delegate media events.
       // (This doesn't make a lot of sense but it would be a breaking change not to.)
@@ -478,26 +512,28 @@ describe('ReactDOMEventListener', () => {
     }
   });
 
-  it('should dispatch load for embed elements', () => {
+  it('should dispatch load for embed elements', async () => {
     const container = document.createElement('div');
     document.body.appendChild(container);
 
     try {
       const ref = React.createRef();
       const handleLoad = jest.fn();
-
-      ReactDOM.render(
-        <div>
-          <embed ref={ref} onLoad={handleLoad} />
-        </div>,
-        container,
-      );
-
-      ref.current.dispatchEvent(
-        new ProgressEvent('load', {
-          bubbles: false,
-        }),
-      );
+      const root = ReactDOMClient.createRoot(container);
+      await act(() => {
+        root.render(
+          <div>
+            <embed ref={ref} onLoad={handleLoad} />
+          </div>,
+        );
+      });
+      await act(() => {
+        ref.current.dispatchEvent(
+          new ProgressEvent('load', {
+            bubbles: false,
+          }),
+        );
+      });
 
       expect(handleLoad).toHaveBeenCalledTimes(1);
     } finally {
@@ -507,24 +543,29 @@ describe('ReactDOMEventListener', () => {
 
   // Unlike browsers, we delegate media events.
   // (This doesn't make a lot of sense but it would be a breaking change not to.)
-  it('should delegate media events even without a direct listener', () => {
+  it('should delegate media events even without a direct listener', async () => {
     const container = document.createElement('div');
     const ref = React.createRef();
     const handleVideoPlayDelegated = jest.fn();
     document.body.appendChild(container);
     try {
-      ReactDOM.render(
-        <div onPlay={handleVideoPlayDelegated}>
-          {/* Intentionally no handler on the target: */}
-          <video ref={ref} />
-        </div>,
-        container,
-      );
-      ref.current.dispatchEvent(
-        new Event('play', {
-          bubbles: false,
-        }),
-      );
+      const root = ReactDOMClient.createRoot(container);
+      await act(() => {
+        root.render(
+          <div onPlay={handleVideoPlayDelegated}>
+            {/* Intentionally no handler on the target: */}
+            <video ref={ref} />
+          </div>,
+        );
+      });
+
+      await act(() => {
+        ref.current.dispatchEvent(
+          new Event('play', {
+            bubbles: false,
+          }),
+        );
+      });
       // Regression test: ensure React tree delegation still works
       // even if the actual DOM element did not have a handler.
       expect(handleVideoPlayDelegated).toHaveBeenCalledTimes(1);
@@ -533,30 +574,34 @@ describe('ReactDOMEventListener', () => {
     }
   });
 
-  it('should delegate dialog events even without a direct listener', () => {
+  it('should delegate dialog events even without a direct listener', async () => {
     const container = document.createElement('div');
     const ref = React.createRef();
     const onCancel = jest.fn();
     const onClose = jest.fn();
     document.body.appendChild(container);
     try {
-      ReactDOM.render(
-        <div onCancel={onCancel} onClose={onClose}>
-          {/* Intentionally no handler on the target: */}
-          <dialog ref={ref} />
-        </div>,
-        container,
-      );
-      ref.current.dispatchEvent(
-        new Event('close', {
-          bubbles: false,
-        }),
-      );
-      ref.current.dispatchEvent(
-        new Event('cancel', {
-          bubbles: false,
-        }),
-      );
+      const root = ReactDOMClient.createRoot(container);
+      await act(() => {
+        root.render(
+          <div onCancel={onCancel} onClose={onClose}>
+            {/* Intentionally no handler on the target: */}
+            <dialog ref={ref} />
+          </div>,
+        );
+      });
+      await act(() => {
+        ref.current.dispatchEvent(
+          new Event('close', {
+            bubbles: false,
+          }),
+        );
+        ref.current.dispatchEvent(
+          new Event('cancel', {
+            bubbles: false,
+          }),
+        );
+      });
       // Regression test: ensure React tree delegation still works
       // even if the actual DOM element did not have a handler.
       expect(onCancel).toHaveBeenCalledTimes(1);
@@ -566,137 +611,160 @@ describe('ReactDOMEventListener', () => {
     }
   });
 
-  it('should bubble non-native bubbling toggle events', () => {
+  it('should bubble non-native bubbling toggle events', async () => {
     const container = document.createElement('div');
     const ref = React.createRef();
     const onToggle = jest.fn();
     document.body.appendChild(container);
     try {
-      ReactDOM.render(
-        <div onToggle={onToggle}>
-          <details ref={ref} onToggle={onToggle} />
-        </div>,
-        container,
-      );
-      ref.current.dispatchEvent(
-        new Event('toggle', {
-          bubbles: false,
-        }),
-      );
+      const root = ReactDOMClient.createRoot(container);
+      await act(() => {
+        root.render(
+          <div onToggle={onToggle}>
+            <details ref={ref} onToggle={onToggle} />
+          </div>,
+        );
+      });
+      await act(() => {
+        ref.current.dispatchEvent(
+          new Event('toggle', {
+            bubbles: false,
+          }),
+        );
+      });
       expect(onToggle).toHaveBeenCalledTimes(2);
     } finally {
       document.body.removeChild(container);
     }
   });
 
-  it('should bubble non-native bubbling cancel/close events', () => {
+  it('should bubble non-native bubbling cancel/close events', async () => {
     const container = document.createElement('div');
     const ref = React.createRef();
     const onCancel = jest.fn();
     const onClose = jest.fn();
     document.body.appendChild(container);
     try {
-      ReactDOM.render(
-        <div onCancel={onCancel} onClose={onClose}>
-          <dialog ref={ref} onCancel={onCancel} onClose={onClose} />
-        </div>,
-        container,
-      );
-      ref.current.dispatchEvent(
-        new Event('cancel', {
-          bubbles: false,
-        }),
-      );
-      ref.current.dispatchEvent(
-        new Event('close', {
-          bubbles: false,
-        }),
-      );
+      const root = ReactDOMClient.createRoot(container);
+      await act(() => {
+        root.render(
+          <div onCancel={onCancel} onClose={onClose}>
+            <dialog ref={ref} onCancel={onCancel} onClose={onClose} />
+          </div>,
+        );
+      });
+      await act(() => {
+        ref.current.dispatchEvent(
+          new Event('cancel', {
+            bubbles: false,
+          }),
+        );
+        ref.current.dispatchEvent(
+          new Event('close', {
+            bubbles: false,
+          }),
+        );
+      });
       expect(onCancel).toHaveBeenCalledTimes(2);
       expect(onClose).toHaveBeenCalledTimes(2);
     } finally {
       document.body.removeChild(container);
     }
   });
 
-  it('should bubble non-native bubbling media events events', () => {
+  it('should bubble non-native bubbling media events events', async () => {
     const container = document.createElement('div');
     const ref = React.createRef();
     const onPlay = jest.fn();
     document.body.appendChild(container);
     try {
-      ReactDOM.render(
-        <div onPlay={onPlay}>
-          <video ref={ref} onPlay={onPlay} />
-        </div>,
-        container,
-      );
-      ref.current.dispatchEvent(
-        new Event('play', {
-          bubbles: false,
-        }),
-      );
+      const root = ReactDOMClient.createRoot(container);
+      await act(() => {
+        root.render(
+          <div onPlay={onPlay}>
+            <video ref={ref} onPlay={onPlay} />
+          </div>,
+        );
+      });
+      await act(() => {
+        ref.current.dispatchEvent(
+          new Event('play', {
+            bubbles: false,
+          }),
+        );
+      });
       expect(onPlay).toHaveBeenCalledTimes(2);
     } finally {
       document.body.removeChild(container);
     }
   });
 
-  it('should bubble non-native bubbling invalid events', () => {
+  it('should bubble non-native bubbling invalid events', async () => {
     const container = document.createElement('div');
     const ref = React.createRef();
     const onInvalid = jest.fn();
     document.body.appendChild(container);
     try {
-      ReactDOM.render(
-        <form onInvalid={onInvalid}>
-          <input ref={ref} onInvalid={onInvalid} />
-        </form>,
-        container,
-      );
-      ref.current.dispatchEvent(
-        new Event('invalid', {
-          bubbles: false,
-        }),
-      );
+      const root = ReactDOMClient.createRoot(container);
+      await act(() => {
+        root.render(
+          <form onInvalid={onInvalid}>
+            <input ref={ref} onInvalid={onInvalid} />
+          </form>,
+        );
+      });
+
+      await act(() => {
+        ref.current.dispatchEvent(
+          new Event('invalid', {
+            bubbles: false,
+          }),
+        );
+      });
       expect(onInvalid).toHaveBeenCalledTimes(2);
     } finally {
       document.body.removeChild(container);
     }
   });
 
-  it('should handle non-bubbling capture events correctly', () => {
+  it('should handle non-bubbling capture events correctly', async () => {
     const container = document.createElement('div');
     const innerRef = React.createRef();
     const outerRef = React.createRef();
     const onPlayCapture = jest.fn(e => log.push(e.currentTarget));
     const log = [];
     document.body.appendChild(container);
     try {
-      ReactDOM.render(
-        <div ref={outerRef} onPlayCapture={onPlayCapture}>
-          <div onPlayCapture={onPlayCapture}>
-            <div ref={innerRef} onPlayCapture={onPlayCapture} />
-          </div>
-        </div>,
-        container,
-      );
-      innerRef.current.dispatchEvent(
-        new Event('play', {
-          bubbles: false,
-        }),
-      );
+      const root = ReactDOMClient.createRoot(container);
+      await act(() => {
+        root.render(
+          <div ref={outerRef} onPlayCapture={onPlayCapture}>
+            <div onPlayCapture={onPlayCapture}>
+              <div ref={innerRef} onPlayCapture={onPlayCapture} />
+            </div>
+          </div>,
+        );
+      });
+      await act(() => {
+        innerRef.current.dispatchEvent(
+          new Event('play', {
+            bubbles: false,
+          }),
+        );
+      });
       expect(onPlayCapture).toHaveBeenCalledTimes(3);
       expect(log).toEqual([
         outerRef.current,
         outerRef.current.firstChild,
         innerRef.current,
       ]);
-      outerRef.current.dispatchEvent(
-        new Event('play', {
-          bubbles: false,
-        }),
-      );
+      await act(() => {
+        outerRef.current.dispatchEvent(
+          new Event('play', {
+            bubbles: false,
+          }),
+        );
+      });
       expect(onPlayCapture).toHaveBeenCalledTimes(4);
       expect(log).toEqual([
         outerRef.current,
@@ -712,7 +780,7 @@ describe('ReactDOMEventListener', () => {
   // We're moving towards aligning more closely with the browser.
   // Currently we emulate bubbling for all non-bubbling events except scroll.
   // We may expand this list in the future, removing emulated bubbling altogether.
-  it('should not emulate bubbling of scroll events', () => {
+  it('should not emulate bubbling of scroll events', async () => {
     const container = document.createElement('div');
     const ref = React.createRef();
     const log = [];
@@ -730,41 +798,46 @@ describe('ReactDOMEventListener', () => {
     );
     document.body.appendChild(container);
     try {
-      ReactDOM.render(
-        <div
-          className="grand"
-          onScroll={onScroll}
-          onScrollCapture={onScrollCapture}
-          onScrollEnd={onScrollEnd}
-          onScrollEndCapture={onScrollEndCapture}>
+      const root = ReactDOMClient.createRoot(container);
+      await act(() => {
+        root.render(
           <div
-            className="parent"
+            className="grand"
             onScroll={onScroll}
             onScrollCapture={onScrollCapture}
             onScrollEnd={onScrollEnd}
             onScrollEndCapture={onScrollEndCapture}>
             <div
-              className="child"
+              className="parent"
               onScroll={onScroll}
               onScrollCapture={onScrollCapture}
               onScrollEnd={onScrollEnd}
-              onScrollEndCapture={onScrollEndCapture}
-              ref={ref}
-            />
-          </div>
-        </div>,
-        container,
-      );
-      ref.current.dispatchEvent(
-        new Event('scroll', {
-          bubbles: false,
-        }),
-      );
-      ref.current.dispatchEvent(
-        new Event('scrollend', {
-          bubbles: false,
-        }),
-      );
+              onScrollEndCapture={onScrollEndCapture}>
+              <div
+                className="child"
+                onScroll={onScroll}
+                onScrollCapture={onScrollCapture}
+                onScrollEnd={onScrollEnd}
+                onScrollEndCapture={onScrollEndCapture}
+                ref={ref}
+              />
+            </div>
+          </div>,
+        );
+      });
+
+      await act(() => {
+        ref.current.dispatchEvent(
+          new Event('scroll', {
+            bubbles: false,
+          }),
+        );
+        ref.current.dispatchEvent(
+          new Event('scrollend', {
+            bubbles: false,
+          }),
+        );
+      });
       expect(log).toEqual([
         ['onScroll', 'capture', 'grand'],
         ['onScroll', 'capture', 'parent'],
@@ -783,7 +856,7 @@ describe('ReactDOMEventListener', () => {
   // We're moving towards aligning more closely with the browser.
   // Currently we emulate bubbling for all non-bubbling events except scroll.
   // We may expand this list in the future, removing emulated bubbling altogether.
-  it('should not emulate bubbling of scroll events (no own handler)', () => {
+  it('should not emulate bubbling of scroll events (no own handler)', async () => {
     const container = document.createElement('div');
     const ref = React.createRef();
     const log = [];
@@ -801,35 +874,39 @@ describe('ReactDOMEventListener', () => {
     );
     document.body.appendChild(container);
     try {
-      ReactDOM.render(
-        <div
-          className="grand"
-          onScroll={onScroll}
-          onScrollCapture={onScrollCapture}
-          onScrollEnd={onScrollEnd}
-          onScrollEndCapture={onScrollEndCapture}>
+      const root = ReactDOMClient.createRoot(container);
+      await act(() => {
+        root.render(
           <div
-            className="parent"
+            className="grand"
             onScroll={onScroll}
             onScrollCapture={onScrollCapture}
             onScrollEnd={onScrollEnd}
             onScrollEndCapture={onScrollEndCapture}>
-            {/* Intentionally no handler on the child: */}
-            <div className="child" ref={ref} />
-          </div>
-        </div>,
-        container,
-      );
-      ref.current.dispatchEvent(
-        new Event('scroll', {
-          bubbles: false,
-        }),
-      );
-      ref.current.dispatchEvent(
-        new Event('scrollend', {
-          bubbles: false,
-        }),
-      );
+            <div
+              className="parent"
+              onScroll={onScroll}
+              onScrollCapture={onScrollCapture}
+              onScrollEnd={onScrollEnd}
+              onScrollEndCapture={onScrollEndCapture}>
+              {/* Intentionally no handler on the child: */}
+              <div className="child" ref={ref} />
+            </div>
+          </div>,
+        );
+      });
+      await act(() => {
+        ref.current.dispatchEvent(
+          new Event('scroll', {
+            bubbles: false,
+          }),
+        );
+        ref.current.dispatchEvent(
+          new Event('scrollend', {
+            bubbles: false,
+          }),
+        );
+      });
       expect(log).toEqual([
         ['onScroll', 'capture', 'grand'],
         ['onScroll', 'capture', 'parent'],
@@ -841,7 +918,7 @@ describe('ReactDOMEventListener', () => {
     }
   });
 
-  it('should subscribe to scroll during updates', () => {
+  it('should subscribe to scroll during updates', async () => {
     const container = document.createElement('div');
     const ref = React.createRef();
     const log = [];
@@ -859,51 +936,57 @@ describe('ReactDOMEventListener', () => {
     );
     document.body.appendChild(container);
     try {
-      ReactDOM.render(
-        <div>
+      const root = ReactDOMClient.createRoot(container);
+      await act(() => {
+        root.render(
           <div>
-            <div />
-          </div>
-        </div>,
-        container,
-      );
+            <div>
+              <div />
+            </div>
+          </div>,
+        );
+      });
 
-      // Update to attach.
-      ReactDOM.render(
-        <div
-          className="grand"
-          onScroll={e => onScroll(e)}
-          onScrollCapture={e => onScrollCapture(e)}
-          onScrollEnd={e => onScrollEnd(e)}
-          onScrollEndCapture={e => onScrollEndCapture(e)}>
+      await act(() => {
+        // Update to attach.
+        root.render(
           <div
-            className="parent"
+            className="grand"
             onScroll={e => onScroll(e)}
             onScrollCapture={e => onScrollCapture(e)}
             onScrollEnd={e => onScrollEnd(e)}
             onScrollEndCapture={e => onScrollEndCapture(e)}>
             <div
-              className="child"
+              className="parent"
               onScroll={e => onScroll(e)}
               onScrollCapture={e => onScrollCapture(e)}
               onScrollEnd={e => onScrollEnd(e)}
-              onScrollEndCapture={e => onScrollEndCapture(e)}
-              ref={ref}
-            />
-          </div>
-        </div>,
-        container,
-      );
-      ref.current.dispatchEvent(
-        new Event('scroll', {
-          bubbles: false,
-        }),
-      );
-      ref.current.dispatchEvent(
-        new Event('scrollend', {
-          bubbles: false,
-        }),
-      );
+              onScrollEndCapture={e => onScrollEndCapture(e)}>
+              <div
+                className="child"
+                onScroll={e => onScroll(e)}
+                onScrollCapture={e => onScrollCapture(e)}
+                onScrollEnd={e => onScrollEnd(e)}
+                onScrollEndCapture={e => onScrollEndCapture(e)}
+                ref={ref}
+              />
+            </div>
+          </div>,
+        );
+      });
+
+      await act(() => {
+        ref.current.dispatchEvent(
+          new Event('scroll', {
+            bubbles: false,
+          }),
+        );
+        ref.current.dispatchEvent(
+          new Event('scrollend', {
+            bubbles: false,
+          }),
+        );
+      });
       expect(log).toEqual([
         ['onScroll', 'capture', 'grand'],
         ['onScroll', 'capture', 'parent'],
@@ -917,43 +1000,46 @@ describe('ReactDOMEventListener', () => {
 
       // Update to verify deduplication.
       log.length = 0;
-      ReactDOM.render(
-        <div
-          className="grand"
-          // Note: these are intentionally inline functions so that
-          // we hit the reattachment codepath instead of bailing out.
-          onScroll={e => onScroll(e)}
-          onScrollCapture={e => onScrollCapture(e)}
-          onScrollEnd={e => onScrollEnd(e)}
-          onScrollEndCapture={e => onScrollEndCapture(e)}>
+      await act(() => {
+        root.render(
           <div
-            className="parent"
+            className="grand"
+            // Note: these are intentionally inline functions so that
+            // we hit the reattachment codepath instead of bailing out.
             onScroll={e => onScroll(e)}
             onScrollCapture={e => onScrollCapture(e)}
             onScrollEnd={e => onScrollEnd(e)}
             onScrollEndCapture={e => onScrollEndCapture(e)}>
             <div
-              className="child"
+              className="parent"
               onScroll={e => onScroll(e)}
               onScrollCapture={e => onScrollCapture(e)}
               onScrollEnd={e => onScrollEnd(e)}
-              onScrollEndCapture={e => onScrollEndCapture(e)}
-              ref={ref}
-            />
-          </div>
-        </div>,
-        container,
-      );
-      ref.current.dispatchEvent(
-        new Event('scroll', {
-          bubbles: false,
-        }),
-      );
-      ref.current.dispatchEvent(
-        new Event('scrollend', {
-          bubbles: false,
-        }),
-      );
+              onScrollEndCapture={e => onScrollEndCapture(e)}>
+              <div
+                className="child"
+                onScroll={e => onScroll(e)}
+                onScrollCapture={e => onScrollCapture(e)}
+                onScrollEnd={e => onScrollEnd(e)}
+                onScrollEndCapture={e => onScrollEndCapture(e)}
+                ref={ref}
+              />
+            </div>
+          </div>,
+        );
+      });
+      await act(() => {
+        ref.current.dispatchEvent(
+          new Event('scroll', {
+            bubbles: false,
+          }),
+        );
+        ref.current.dispatchEvent(
+          new Event('scrollend', {
+            bubbles: false,
+          }),
+        );
+      });
       expect(log).toEqual([
         ['onScroll', 'capture', 'grand'],
         ['onScroll', 'capture', 'parent'],
@@ -967,32 +1053,35 @@ describe('ReactDOMEventListener', () => {
 
       // Update to detach.
       log.length = 0;
-      ReactDOM.render(
-        <div>
+      await act(() => {
+        root.render(
           <div>
-            <div ref={ref} />
-          </div>
-        </div>,
-        container,
-      );
-      ref.current.dispatchEvent(
-        new Event('scroll', {
-          bubbles: false,
-        }),
-      );
-      ref.current.dispatchEvent(
-        new Event('scrollend', {
-          bubbles: false,
-        }),
-      );
+            <div>
+              <div ref={ref} />
+            </div>
+          </div>,
+        );
+      });
+      await act(() => {
+        ref.current.dispatchEvent(
+          new Event('scroll', {
+            bubbles: false,
+          }),
+        );
+        ref.current.dispatchEvent(
+          new Event('scrollend', {
+            bubbles: false,
+          }),
+        );
+      });
       expect(log).toEqual([]);
     } finally {
       document.body.removeChild(container);
     }
   });
 
   // Regression test.
-  it('should subscribe to scroll during hydration', () => {
+  it('should subscribe to scroll during hydration', async () => {
     const container = document.createElement('div');
     const ref = React.createRef();
     const log = [];
@@ -1036,17 +1125,22 @@ describe('ReactDOMEventListener', () => {
     document.body.appendChild(container);
     try {
       container.innerHTML = ReactDOMServer.renderToString(tree);
-      ReactDOM.hydrate(tree, container);
-      ref.current.dispatchEvent(
-        new Event('scroll', {
-          bubbles: false,
-        }),
-      );
-      ref.current.dispatchEvent(
-        new Event('scrollend', {
-          bubbles: false,
-        }),
-      );
+      let root;
+      await act(() => {
+        root = ReactDOMClient.hydrateRoot(container, tree);
+      });
+      await act(() => {
+        ref.current.dispatchEvent(
+          new Event('scroll', {
+            bubbles: false,
+          }),
+        );
+        ref.current.dispatchEvent(
+          new Event('scrollend', {
+            bubbles: false,
+          }),
+        );
+      });
       expect(log).toEqual([
         ['onScroll', 'capture', 'grand'],
         ['onScroll', 'capture', 'parent'],
@@ -1059,31 +1153,34 @@ describe('ReactDOMEventListener', () => {
       ]);
 
       log.length = 0;
-      ReactDOM.render(
-        <div>
+      await act(() => {
+        root.render(
           <div>
-            <div ref={ref} />
-          </div>
-        </div>,
-        container,
-      );
-      ref.current.dispatchEvent(
-        new Event('scroll', {
-          bubbles: false,
-        }),
-      );
-      ref.current.dispatchEvent(
-        new Event('scrollend', {
-          bubbles: false,
-        }),
-      );
+            <div>
+              <div ref={ref} />
+            </div>
+          </div>,
+        );
+      });
+      await act(() => {
+        ref.current.dispatchEvent(
+          new Event('scroll', {
+            bubbles: false,
+          }),
+        );
+        ref.current.dispatchEvent(
+          new Event('scrollend', {
+            bubbles: false,
+          }),
+        );
+      });
       expect(log).toEqual([]);
     } finally {
       document.body.removeChild(container);
     }
   });
 
-  it('should not subscribe to selectionchange twice', () => {
+  it('should not subscribe to selectionchange twice', async () => {
     const log = [];
 
     const originalDocAddEventListener = document.addEventListener;
@@ -1099,8 +1196,13 @@ describe('ReactDOMEventListener', () => {
       }
     };
     try {
-      ReactDOM.render(<input />, document.createElement('div'));
-      ReactDOM.render(<input />, document.createElement('div'));
+      const rootOne = ReactDOMClient.createRoot(document.createElement('div'));
+      const rootTwo = ReactDOMClient.createRoot(document.createElement('div'));
+
+      await act(() => {
+        rootOne.render(<input />);
+        rootTwo.render(<input />);
+      });
     } finally {
       document.addEventListener = originalDocAddEventListener;
     }