diff --git a/packages/react-dom/src/__tests__/ReactDOMEventListener-test.js b/packages/react-dom/src/__tests__/ReactDOMEventListener-test.js index e4412632e17ee..d884b92d7fd49 100644 --- a/packages/react-dom/src/__tests__/ReactDOMEventListener-test.js +++ b/packages/react-dom/src/__tests__/ReactDOMEventListener-test.js @@ -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( -
Child
, - childContainer, - ); - const parentNode = ReactDOM.render( -
div
, - parentContainer, - ); + + const childRoot = ReactDOMClient.createRoot(childContainer); + const parentRoot = ReactDOMClient.createRoot(parentContainer); + + await act(() => { + childRoot.render(
Child
); + parentRoot.render(
Parent
); + }); + 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( -
Child
, - childContainer, - ); - const parentNode = ReactDOM.render( -
Parent
, - parentContainer, - ); - const grandParentNode = ReactDOM.render( -
Parent
, - grandParentContainer, - ); + + const childRoot = ReactDOMClient.createRoot(childContainer); + const parentRoot = ReactDOMClient.createRoot(parentContainer); + const grandParentRoot = ReactDOMClient.createRoot(grandParentContainer); + + await act(() => { + childRoot.render(
Child
); + parentRoot.render(
Parent
); + grandParentRoot.render(
Grandparent
); + }); + 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(, container); - container.firstChild.dispatchEvent( - new MouseEvent('click', { - bubbles: true, - }), - ); + const root = ReactDOMClient.createRoot(container); + await act(() => { + root.render(); + }); + await act(() => { + container.firstChild.dispatchEvent( + new MouseEvent('click', { + bubbles: true, + }), + ); + }); expect(container.firstChild.textContent).toBe('clicked!'); } finally { document.body.removeChild(container); @@ -188,22 +200,17 @@ 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 =
Inner
; return (
- {inner} +
Inner
); @@ -211,25 +218,28 @@ describe('ReactDOMEventListener', () => { } const container = document.createElement('div'); - const instance = ReactDOM.render(, container); + const root = ReactDOMClient.createRoot(container); + await act(() => { + root.render(); + }); 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( -
- -
, - 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( +
+ +
, + ); + }); + + 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( -
-
-
, - container, - ); + const root = ReactDOMClient.createRoot(container); + await act(() => { + root.render( +
+ +
, + ); + }); 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( -
- -
, - 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( +
+ +
, + ); + }); + + 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( -
-
, - 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( +
+
, + ); + }); + 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( -
- -
, - container, - ); - - ref.current.dispatchEvent( - new ProgressEvent('load', { - bubbles: false, - }), - ); + const root = ReactDOMClient.createRoot(container); + await act(() => { + root.render( +
+ +
, + ); + }); + 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( -
- {/* Intentionally no handler on the target: */} -
, - container, - ); - ref.current.dispatchEvent( - new Event('play', { - bubbles: false, - }), - ); + const root = ReactDOMClient.createRoot(container); + await act(() => { + root.render( +
+ {/* Intentionally no handler on the target: */} +
, + ); + }); + + 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( -
- {/* Intentionally no handler on the target: */} - -
, - 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( +
+ {/* Intentionally no handler on the target: */} + +
, + ); + }); + 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,52 +611,60 @@ 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( -
-
-
, - container, - ); - ref.current.dispatchEvent( - new Event('toggle', { - bubbles: false, - }), - ); + const root = ReactDOMClient.createRoot(container); + await act(() => { + root.render( +
+
+
, + ); + }); + 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( -
- -
, - 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( +
+ +
, + ); + }); + 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 { @@ -619,53 +672,62 @@ describe('ReactDOMEventListener', () => { } }); - 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( -
-
, - container, - ); - ref.current.dispatchEvent( - new Event('play', { - bubbles: false, - }), - ); + const root = ReactDOMClient.createRoot(container); + await act(() => { + root.render( +
+
, + ); + }); + 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( - - - , - container, - ); - ref.current.dispatchEvent( - new Event('invalid', { - bubbles: false, - }), - ); + const root = ReactDOMClient.createRoot(container); + await act(() => { + root.render( +
+ +
, + ); + }); + + 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(); @@ -673,30 +735,36 @@ describe('ReactDOMEventListener', () => { const log = []; document.body.appendChild(container); try { - ReactDOM.render( -
-
-
-
-
, - container, - ); - innerRef.current.dispatchEvent( - new Event('play', { - bubbles: false, - }), - ); + const root = ReactDOMClient.createRoot(container); + await act(() => { + root.render( +
+
+
+
+
, + ); + }); + 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( -
+ const root = ReactDOMClient.createRoot(container); + await act(() => { + root.render(
-
-
, - container, - ); - ref.current.dispatchEvent( - new Event('scroll', { - bubbles: false, - }), - ); - ref.current.dispatchEvent( - new Event('scrollend', { - bubbles: false, - }), - ); + onScrollEndCapture={onScrollEndCapture}> +
+
+
, + ); + }); + + 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( -
+ const root = ReactDOMClient.createRoot(container); + await act(() => { + root.render(
- {/* Intentionally no handler on the child: */} -
-
-
, - container, - ); - ref.current.dispatchEvent( - new Event('scroll', { - bubbles: false, - }), - ); - ref.current.dispatchEvent( - new Event('scrollend', { - bubbles: false, - }), - ); +
+ {/* Intentionally no handler on the child: */} +
+
+
, + ); + }); + 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( -
+ const root = ReactDOMClient.createRoot(container); + await act(() => { + root.render(
-
-
-
, - container, - ); +
+
+
+
, + ); + }); - // Update to attach. - ReactDOM.render( -
onScroll(e)} - onScrollCapture={e => onScrollCapture(e)} - onScrollEnd={e => onScrollEnd(e)} - onScrollEndCapture={e => onScrollEndCapture(e)}> + await act(() => { + // Update to attach. + root.render(
onScroll(e)} onScrollCapture={e => onScrollCapture(e)} onScrollEnd={e => onScrollEnd(e)} onScrollEndCapture={e => onScrollEndCapture(e)}>
onScroll(e)} onScrollCapture={e => onScrollCapture(e)} onScrollEnd={e => onScrollEnd(e)} - onScrollEndCapture={e => onScrollEndCapture(e)} - ref={ref} - /> -
-
, - container, - ); - ref.current.dispatchEvent( - new Event('scroll', { - bubbles: false, - }), - ); - ref.current.dispatchEvent( - new Event('scrollend', { - bubbles: false, - }), - ); + onScrollEndCapture={e => onScrollEndCapture(e)}> +
onScroll(e)} + onScrollCapture={e => onScrollCapture(e)} + onScrollEnd={e => onScrollEnd(e)} + onScrollEndCapture={e => onScrollEndCapture(e)} + ref={ref} + /> +
+
, + ); + }); + + 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( -
onScroll(e)} - onScrollCapture={e => onScrollCapture(e)} - onScrollEnd={e => onScrollEnd(e)} - onScrollEndCapture={e => onScrollEndCapture(e)}> + await act(() => { + root.render(
onScroll(e)} onScrollCapture={e => onScrollCapture(e)} onScrollEnd={e => onScrollEnd(e)} onScrollEndCapture={e => onScrollEndCapture(e)}>
onScroll(e)} onScrollCapture={e => onScrollCapture(e)} onScrollEnd={e => onScrollEnd(e)} - onScrollEndCapture={e => onScrollEndCapture(e)} - ref={ref} - /> -
-
, - container, - ); - ref.current.dispatchEvent( - new Event('scroll', { - bubbles: false, - }), - ); - ref.current.dispatchEvent( - new Event('scrollend', { - bubbles: false, - }), - ); + onScrollEndCapture={e => onScrollEndCapture(e)}> +
onScroll(e)} + onScrollCapture={e => onScrollCapture(e)} + onScrollEnd={e => onScrollEnd(e)} + onScrollEndCapture={e => onScrollEndCapture(e)} + ref={ref} + /> +
+
, + ); + }); + 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,24 +1053,27 @@ describe('ReactDOMEventListener', () => { // Update to detach. log.length = 0; - ReactDOM.render( -
+ await act(() => { + root.render(
-
-
-
, - container, - ); - ref.current.dispatchEvent( - new Event('scroll', { - bubbles: false, - }), - ); - ref.current.dispatchEvent( - new Event('scrollend', { - bubbles: false, - }), - ); +
+
+
+
, + ); + }); + 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); @@ -992,7 +1081,7 @@ describe('ReactDOMEventListener', () => { }); // 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( -
+ await act(() => { + root.render(
-
-
-
, - container, - ); - ref.current.dispatchEvent( - new Event('scroll', { - bubbles: false, - }), - ); - ref.current.dispatchEvent( - new Event('scrollend', { - bubbles: false, - }), - ); +
+
+
+
, + ); + }); + 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(, document.createElement('div')); - ReactDOM.render(, document.createElement('div')); + const rootOne = ReactDOMClient.createRoot(document.createElement('div')); + const rootTwo = ReactDOMClient.createRoot(document.createElement('div')); + + await act(() => { + rootOne.render(); + rootTwo.render(); + }); } finally { document.addEventListener = originalDocAddEventListener; }