diff --git a/packages/react-dom/src/__tests__/ReactDOMImageLoad-test.internal.js b/packages/react-dom/src/__tests__/ReactDOMImageLoad-test.internal.js index 717d795d99884..304e1f5cb65bb 100644 --- a/packages/react-dom/src/__tests__/ReactDOMImageLoad-test.internal.js +++ b/packages/react-dom/src/__tests__/ReactDOMImageLoad-test.internal.js @@ -24,6 +24,10 @@ let images = []; let onLoadSpy = null; let actualLoadSpy = null; +let waitForAll; +let waitFor; +let assertLog; + function PhaseMarkers({children}) { Scheduler.unstable_yieldValue('render start'); React.useLayoutEffect(() => { @@ -94,6 +98,11 @@ describe('ReactDOMImageLoad', () => { ReactDOMClient = require('react-dom/client'); // Suspense = React.Suspense; + const InternalTestUtils = require('internal-test-utils'); + waitForAll = InternalTestUtils.waitForAll; + waitFor = InternalTestUtils.waitFor; + assertLog = InternalTestUtils.assertLog; + onLoadSpy = jest.fn(reactEvent => { const src = reactEvent.target.getAttribute('src'); Scheduler.unstable_yieldValue('onLoadSpy [' + src + ']'); @@ -206,26 +215,17 @@ describe('ReactDOMImageLoad', () => { ), ); - expect(Scheduler).toFlushAndYieldThrough([ - 'render start', - 'Img default', - 'Yield', - ]); + await waitFor(['render start', 'Img default', 'Yield']); const img = last(images); loadImage(img); - expect(Scheduler).toHaveYielded([ + assertLog([ 'actualLoadSpy [default]', // no onLoadSpy since we have not completed render ]); - expect(Scheduler).toFlushAndYield([ - 'a', - 'load triggered', - 'last layout', - 'last passive', - ]); + await waitForAll(['a', 'load triggered', 'last layout', 'last passive']); expect(img.__needsDispatch).toBe(true); loadImage(img); - expect(Scheduler).toHaveYielded([ + assertLog([ 'actualLoadSpy [default]', // the browser reloading of the image causes this to yield again 'onLoadSpy [default]', ]); @@ -244,7 +244,7 @@ describe('ReactDOMImageLoad', () => { ), ); - expect(Scheduler).toFlushAndYieldThrough([ + await waitFor([ 'render start', 'Img default', 'load triggered', @@ -253,11 +253,8 @@ describe('ReactDOMImageLoad', () => { Scheduler.unstable_requestPaint(); const img = last(images); loadImage(img); - expect(Scheduler).toHaveYielded([ - 'actualLoadSpy [default]', - 'onLoadSpy [default]', - ]); - expect(Scheduler).toFlushAndYield(['last passive']); + assertLog(['actualLoadSpy [default]', 'onLoadSpy [default]']); + await waitForAll(['last passive']); expect(img.__needsDispatch).toBe(false); expect(onLoadSpy).toHaveBeenCalledTimes(1); }); @@ -286,16 +283,12 @@ describe('ReactDOMImageLoad', () => { React.startTransition(() => root.render()); - expect(Scheduler).toFlushAndYieldThrough([ - 'render start', - 'Img a', - 'Yield', - ]); + await waitFor(['render start', 'Img a', 'Yield']); const img = last(images); loadImage(img); - expect(Scheduler).toHaveYielded(['actualLoadSpy [a]']); + assertLog(['actualLoadSpy [a]']); - expect(Scheduler).toFlushAndYieldThrough([ + await waitFor([ 'load triggered', 'last layout', // the update in layout causes a passive effects flush before a sync render @@ -309,7 +302,7 @@ describe('ReactDOMImageLoad', () => { ]); expect(images.length).toBe(1); loadImage(img); - expect(Scheduler).toHaveYielded(['actualLoadSpy [b]', 'onLoadSpy [b]']); + assertLog(['actualLoadSpy [b]', 'onLoadSpy [b]']); expect(onLoadSpy).toHaveBeenCalledTimes(1); }); @@ -323,7 +316,7 @@ describe('ReactDOMImageLoad', () => { , ); - expect(Scheduler).toFlushAndYield([ + await waitForAll([ 'render start', 'Img default', 'load triggered', @@ -332,10 +325,7 @@ describe('ReactDOMImageLoad', () => { ]); const img = last(images); loadImage(img); - expect(Scheduler).toHaveYielded([ - 'actualLoadSpy [default]', - 'onLoadSpy [default]', - ]); + assertLog(['actualLoadSpy [default]', 'onLoadSpy [default]']); expect(onLoadSpy).toHaveBeenCalledTimes(1); }); @@ -365,26 +355,17 @@ describe('ReactDOMImageLoad', () => { ), ); - expect(Scheduler).toFlushAndYieldThrough([ - 'render start', - 'Img default', - 'Yield', - ]); + await waitFor(['render start', 'Img default', 'Yield']); const img = last(images); loadImage(img); - expect(Scheduler).toHaveYielded(['actualLoadSpy [default]']); - expect(Scheduler).toFlushAndYield([ - 'a', - 'load triggered', - 'last layout', - 'last passive', - ]); + assertLog(['actualLoadSpy [default]']); + await waitForAll(['a', 'load triggered', 'last layout', 'last passive']); expect(img.__needsDispatch).toBe(true); loadImage(img); // we expect the browser to load the image again but since we are no longer rendering // the img there will be no onLoad called - expect(Scheduler).toHaveYielded(['actualLoadSpy [default]']); - expect(Scheduler).toFlushWithoutYielding(); + assertLog(['actualLoadSpy [default]']); + await waitForAll([]); expect(onLoadSpy).not.toHaveBeenCalled(); }); @@ -426,7 +407,7 @@ describe('ReactDOMImageLoad', () => { ), ); - expect(Scheduler).toFlushAndYieldThrough([ + await waitFor([ // initial render 'render start', 'Img default', @@ -434,8 +415,8 @@ describe('ReactDOMImageLoad', () => { ]); const img = last(images); loadImage(img); - expect(Scheduler).toHaveYielded(['actualLoadSpy [default]']); - expect(Scheduler).toFlushAndYield([ + assertLog(['actualLoadSpy [default]']); + await waitForAll([ 'a', 'load triggered', // img is present at first @@ -450,8 +431,8 @@ describe('ReactDOMImageLoad', () => { loadImage(img); // we expect the browser to load the image again but since we are no longer rendering // the img there will be no onLoad called - expect(Scheduler).toHaveYielded(['actualLoadSpy [default]']); - expect(Scheduler).toFlushWithoutYielding(); + assertLog(['actualLoadSpy [default]']); + await waitForAll([]); expect(onLoadSpy).not.toHaveBeenCalled(); }); @@ -548,22 +529,18 @@ describe('ReactDOMImageLoad', () => { root.render(); - expect(Scheduler).toFlushWithoutYielding(); + await waitForAll([]); React.startTransition(() => externalSetSrc('a')); - expect(Scheduler).toFlushAndYieldThrough([ - 'YieldingWithImage', - 'Img a', - 'Yield', - ]); + await waitFor(['YieldingWithImage', 'Img a', 'Yield']); let img = last(images); loadImage(img); - expect(Scheduler).toHaveYielded(['actualLoadSpy [a]']); + assertLog(['actualLoadSpy [a]']); ReactDOM.flushSync(() => externalSetSrcAlt('b')); - expect(Scheduler).toHaveYielded([ + assertLog([ 'YieldingWithImage', 'Img b', 'Yield', @@ -576,18 +553,12 @@ describe('ReactDOMImageLoad', () => { expect(img.__needsDispatch).toBe(true); loadImage(img); - expect(Scheduler).toHaveYielded(['actualLoadSpy [b]', 'onLoadSpy [b]']); + assertLog(['actualLoadSpy [b]', 'onLoadSpy [b]']); // why is there another update here? - expect(Scheduler).toFlushAndYield([ - 'YieldingWithImage', - 'Img b', - 'Yield', - 'b', - 'Committed', - ]); + await waitForAll(['YieldingWithImage', 'Img b', 'Yield', 'b', 'Committed']); }); - it('preserves the src property / attribute when triggering a potential new load event', () => { + it('preserves the src property / attribute when triggering a potential new load event', async () => { // this test covers a regression identified in https://github.com/mui/material-ui/pull/31263 // where the resetting of the src property caused the property to change from relative to fully qualified @@ -612,17 +583,13 @@ describe('ReactDOMImageLoad', () => { ); // render to yield to capture state of img src attribute and property before commit - expect(Scheduler).toFlushAndYieldThrough([ - 'render start', - 'Img default', - 'Yield', - ]); + await waitFor(['render start', 'Img default', 'Yield']); const img = last(images); const renderSrcProperty = img.src; const renderSrcAttr = img.getAttribute('src'); // finish render and commit causing the src property to be rewritten - expect(Scheduler).toFlushAndYield(['a', 'last layout', 'last passive']); + await waitForAll(['a', 'last layout', 'last passive']); const commitSrcProperty = img.src; const commitSrcAttr = img.getAttribute('src'); diff --git a/packages/react-dom/src/__tests__/ReactDOMNativeEventHeuristic-test.js b/packages/react-dom/src/__tests__/ReactDOMNativeEventHeuristic-test.js index cfdeecf718f03..609eca887dde2 100644 --- a/packages/react-dom/src/__tests__/ReactDOMNativeEventHeuristic-test.js +++ b/packages/react-dom/src/__tests__/ReactDOMNativeEventHeuristic-test.js @@ -15,6 +15,8 @@ let ReactDOM; let ReactDOMClient; let Scheduler; let act; +let assertLog; +let waitFor; describe('ReactDOMNativeEventHeuristic-test', () => { let container; @@ -28,6 +30,10 @@ describe('ReactDOMNativeEventHeuristic-test', () => { Scheduler = require('scheduler'); act = require('jest-react').act; + const InternalTestUtils = require('internal-test-utils'); + assertLog = InternalTestUtils.assertLog; + waitFor = InternalTestUtils.waitFor; + document.body.appendChild(container); }); @@ -301,10 +307,10 @@ describe('ReactDOMNativeEventHeuristic-test', () => { dispatchAndSetCurrentEvent(target.current, mouseEnterEvent); // Since mouse end is not discrete, should not have updated yet - expect(Scheduler).toHaveYielded(['not hovered']); + assertLog(['not hovered']); expect(container.textContent).toEqual('not hovered'); - expect(Scheduler).toFlushAndYieldThrough(['hovered']); + await waitFor(['hovered']); expect(container.textContent).toEqual('hovered'); }); expect(container.textContent).toEqual('hovered'); @@ -381,7 +387,7 @@ describe('ReactDOMNativeEventHeuristic-test', () => { pressEvent.initEvent('click', true, true); dispatchAndSetCurrentEvent(target, pressEvent); - expect(Scheduler).toHaveYielded(['Count: 0 [after batchedUpdates]']); + assertLog(['Count: 0 [after batchedUpdates]']); expect(container.textContent).toEqual('Count: 0'); // Intentionally not using `act` so we can observe in between the click diff --git a/packages/react-dom/src/__tests__/ReactDOMNestedEvents-test.js b/packages/react-dom/src/__tests__/ReactDOMNestedEvents-test.js index 6c632cd31ead5..ee6abf41da8d7 100644 --- a/packages/react-dom/src/__tests__/ReactDOMNestedEvents-test.js +++ b/packages/react-dom/src/__tests__/ReactDOMNestedEvents-test.js @@ -15,6 +15,7 @@ describe('ReactDOMNestedEvents', () => { let Scheduler; let act; let useState; + let assertLog; beforeEach(() => { jest.resetModules(); @@ -23,6 +24,9 @@ describe('ReactDOMNestedEvents', () => { Scheduler = require('scheduler'); act = require('jest-react').act; useState = React.useState; + + const InternalTestUtils = require('internal-test-utils'); + assertLog = InternalTestUtils.assertLog; }); test('nested event dispatches should not cause updates to flush', async () => { @@ -67,9 +71,7 @@ describe('ReactDOMNestedEvents', () => { await act(async () => { buttonRef.current.click(); }); - expect(Scheduler).toHaveYielded([ - 'Value right after focus call: Clicked: false, Focused: false', - ]); + assertLog(['Value right after focus call: Clicked: false, Focused: false']); expect(buttonRef.current.innerHTML).toEqual('Clicked: true, Focused: true'); }); }); diff --git a/packages/react-dom/src/__tests__/ReactDOMRoot-test.js b/packages/react-dom/src/__tests__/ReactDOMRoot-test.js index 9d51ba40c3796..6d1db3fdee419 100644 --- a/packages/react-dom/src/__tests__/ReactDOMRoot-test.js +++ b/packages/react-dom/src/__tests__/ReactDOMRoot-test.js @@ -16,6 +16,8 @@ let ReactDOMServer = require('react-dom/server'); let Scheduler = require('scheduler'); let act; let useEffect; +let assertLog; +let waitFor; describe('ReactDOMRoot', () => { let container; @@ -30,6 +32,10 @@ describe('ReactDOMRoot', () => { Scheduler = require('scheduler'); act = require('jest-react').act; useEffect = React.useEffect; + + const InternalTestUtils = require('internal-test-utils'); + assertLog = InternalTestUtils.assertLog; + waitFor = InternalTestUtils.waitFor; }); it('renders children', () => { @@ -255,7 +261,7 @@ describe('ReactDOMRoot', () => { Scheduler.unstable_yieldValue('callback'); }); expect(container.textContent).toEqual('Hi'); - expect(Scheduler).toHaveYielded(['callback']); + assertLog(['callback']); }); it('warns when unmounting with legacy API (no previous content)', () => { @@ -401,10 +407,10 @@ describe('ReactDOMRoot', () => { await act(async () => { root.render(); - expect(Scheduler).toHaveYielded(['a']); + assertLog(['a']); expect(container.textContent).toEqual('a'); - expect(Scheduler).toFlushAndYieldThrough(['b']); + await waitFor(['b']); if (gate(flags => flags.allowConcurrentByDefault)) { expect(container.textContent).toEqual('a'); } else { diff --git a/packages/react-dom/src/__tests__/ReactDOMServerPartialHydration-test.internal.js b/packages/react-dom/src/__tests__/ReactDOMServerPartialHydration-test.internal.js index 0862865f76004..7148c8163262e 100644 --- a/packages/react-dom/src/__tests__/ReactDOMServerPartialHydration-test.internal.js +++ b/packages/react-dom/src/__tests__/ReactDOMServerPartialHydration-test.internal.js @@ -21,6 +21,10 @@ let SuspenseList; let Offscreen; let act; let IdleEventPriority; +let waitForAll; +let waitFor; +let waitForPaint; +let assertLog; function normalizeCodeLocInfo(strOrErr) { if (strOrErr && strOrErr.replace) { @@ -113,6 +117,12 @@ describe('ReactDOMServerPartialHydration', () => { SuspenseList = React.SuspenseList; } + const InternalTestUtils = require('internal-test-utils'); + waitForAll = InternalTestUtils.waitForAll; + assertLog = InternalTestUtils.assertLog; + waitForPaint = InternalTestUtils.waitForPaint; + waitFor = InternalTestUtils.waitFor; + IdleEventPriority = require('react-reconciler/constants').IdleEventPriority; }); @@ -290,13 +300,7 @@ describe('ReactDOMServerPartialHydration', () => { const finalHTML = ReactDOMServer.renderToString(); const container = document.createElement('section'); container.innerHTML = finalHTML; - expect(Scheduler).toHaveYielded([ - 'Hello', - 'Component', - 'Component', - 'Component', - 'Component', - ]); + assertLog(['Hello', 'Component', 'Component', 'Component', 'Component']); expect(container.innerHTML).toBe( 'Hello
Component
Component
Component
Component
', @@ -310,7 +314,7 @@ describe('ReactDOMServerPartialHydration', () => { Scheduler.unstable_yieldValue(error.message); }, }); - expect(Scheduler).toFlushAndYield([ + await waitForAll([ 'Suspend', 'Component', 'Component', @@ -327,7 +331,7 @@ describe('ReactDOMServerPartialHydration', () => { suspend = false; resolve(); await promise; - expect(Scheduler).toFlushAndYield([ + await waitForAll([ // first pass, mismatches at end 'Hello', 'Component', @@ -434,7 +438,7 @@ describe('ReactDOMServerPartialHydration', () => { Scheduler.unstable_yieldValue(error.message); }, }); - expect(Scheduler).toFlushAndYield([]); + await waitForAll([]); expect(hydrated.length).toBe(0); expect(deleted.length).toBe(0); @@ -520,7 +524,7 @@ describe('ReactDOMServerPartialHydration', () => { expect(container.innerHTML).toContain('A'); expect(container.innerHTML).not.toContain('B'); - expect(Scheduler).toHaveYielded([ + assertLog([ 'There was an error while hydrating this Suspense boundary. ' + 'Switched to client rendering.', ]); @@ -640,7 +644,7 @@ describe('ReactDOMServerPartialHydration', () => { }); }); }).toErrorDev('Did not expect server HTML to contain a in
'); - expect(Scheduler).toHaveYielded([ + assertLog([ 'Hydration failed because the initial UI does not match what was rendered on the server.', 'There was an error while hydrating this Suspense boundary. Switched to client rendering.', ]); @@ -1389,7 +1393,7 @@ describe('ReactDOMServerPartialHydration', () => { suspend = false; const finalHTML = ReactDOMServer.renderToString(); - expect(Scheduler).toHaveYielded(['Child', 'Sibling']); + assertLog(['Child', 'Sibling']); const container = document.createElement('div'); container.innerHTML = finalHTML; @@ -1401,7 +1405,7 @@ describe('ReactDOMServerPartialHydration', () => { await act(async () => { suspend = true; - expect(Scheduler).toFlushAndYieldThrough(['Child']); + await waitFor(['Child']); // While we're part way through the hydration, we update the state. // This will schedule an update on the children of the suspense boundary. @@ -1410,7 +1414,7 @@ describe('ReactDOMServerPartialHydration', () => { ); // This will throw it away and rerender. - expect(Scheduler).toFlushAndYield(['Child', 'Sibling']); + await waitForAll(['Child', 'Sibling']); expect(container.textContent).toBe('Hello'); @@ -1418,7 +1422,7 @@ describe('ReactDOMServerPartialHydration', () => { resolve(); await promise; }); - expect(Scheduler).toHaveYielded(['Child', 'Sibling']); + assertLog(['Child', 'Sibling']); expect(container.textContent).toBe('Hello'); }); @@ -1635,7 +1639,7 @@ describe('ReactDOMServerPartialHydration', () => { }, }); if (__DEV__) { - expect(Scheduler).toFlushAndYield([ + await waitForAll([ 'The server did not finish this Suspense boundary: The server used' + ' "renderToString" which does not support Suspense. If you intended' + ' for this Suspense boundary to render the fallback content on the' + @@ -1644,7 +1648,7 @@ describe('ReactDOMServerPartialHydration', () => { ' please switch to "renderToPipeableStream" which supports Suspense on the server', ]); } else { - expect(Scheduler).toFlushAndYield([ + await waitForAll([ 'The server could not finish this Suspense boundary, likely due to ' + 'an error during server rendering. Switched to client rendering.', ]); @@ -1708,7 +1712,7 @@ describe('ReactDOMServerPartialHydration', () => { }, }); if (__DEV__) { - expect(Scheduler).toFlushAndYield([ + await waitForAll([ 'The server did not finish this Suspense boundary: The server used' + ' "renderToString" which does not support Suspense. If you intended' + ' for this Suspense boundary to render the fallback content on the' + @@ -1717,7 +1721,7 @@ describe('ReactDOMServerPartialHydration', () => { ' please switch to "renderToPipeableStream" which supports Suspense on the server', ]); } else { - expect(Scheduler).toFlushAndYield([ + await waitForAll([ 'The server could not finish this Suspense boundary, likely due to ' + 'an error during server rendering. Switched to client rendering.', ]); @@ -1786,7 +1790,7 @@ describe('ReactDOMServerPartialHydration', () => { }, }); if (__DEV__) { - expect(Scheduler).toFlushAndYield([ + await waitForAll([ 'The server did not finish this Suspense boundary: The server used' + ' "renderToString" which does not support Suspense. If you intended' + ' for this Suspense boundary to render the fallback content on the' + @@ -1795,7 +1799,7 @@ describe('ReactDOMServerPartialHydration', () => { ' please switch to "renderToPipeableStream" which supports Suspense on the server', ]); } else { - expect(Scheduler).toFlushAndYield([ + await waitForAll([ 'The server could not finish this Suspense boundary, likely due to ' + 'an error during server rendering. Switched to client rendering.', ]); @@ -2028,7 +2032,7 @@ describe('ReactDOMServerPartialHydration', () => { suspend = false; const html = ReactDOMServer.renderToString(); - expect(Scheduler).toHaveYielded(['Before', 'After']); + assertLog(['Before', 'After']); const container = document.createElement('div'); container.innerHTML = html; @@ -2044,7 +2048,7 @@ describe('ReactDOMServerPartialHydration', () => { suspend = true; await act(async () => { - expect(Scheduler).toFlushAndYieldThrough(['Before', 'After']); + await waitFor(['Before', 'After']); // This will cause us to skip the second row completely. }); @@ -2108,7 +2112,7 @@ describe('ReactDOMServerPartialHydration', () => { suspend = true; if (__DEV__) { - expect(Scheduler).toFlushAndYield([ + await waitForAll([ 'The server did not finish this Suspense boundary: The server used' + ' "renderToString" which does not support Suspense. If you intended' + ' for this Suspense boundary to render the fallback content on the' + @@ -2117,7 +2121,7 @@ describe('ReactDOMServerPartialHydration', () => { ' please switch to "renderToPipeableStream" which supports Suspense on the server', ]); } else { - expect(Scheduler).toFlushAndYield([ + await waitForAll([ 'The server could not finish this Suspense boundary, likely due to ' + 'an error during server rendering. Switched to client rendering.', ]); @@ -2182,7 +2186,7 @@ describe('ReactDOMServerPartialHydration', () => { }, }); if (__DEV__) { - expect(Scheduler).toFlushAndYield([ + await waitForAll([ 'The server did not finish this Suspense boundary: The server used' + ' "renderToString" which does not support Suspense. If you intended' + ' for this Suspense boundary to render the fallback content on the' + @@ -2191,7 +2195,7 @@ describe('ReactDOMServerPartialHydration', () => { ' please switch to "renderToPipeableStream" which supports Suspense on the server', ]); } else { - expect(Scheduler).toFlushAndYield([ + await waitForAll([ 'The server could not finish this Suspense boundary, likely due to ' + 'an error during server rendering. Switched to client rendering.', ]); @@ -3009,7 +3013,7 @@ describe('ReactDOMServerPartialHydration', () => { suspend = false; const finalHTML = ReactDOMServer.renderToString(); - expect(Scheduler).toHaveYielded(['Child']); + assertLog(['Child']); const container = document.createElement('div'); container.innerHTML = finalHTML; @@ -3019,7 +3023,7 @@ describe('ReactDOMServerPartialHydration', () => { container, , ); - expect(Scheduler).toFlushAndYield([]); + await waitForAll([]); expect(ref.current).toBe(null); expect(container.textContent).toBe('Hello'); @@ -3036,14 +3040,14 @@ describe('ReactDOMServerPartialHydration', () => { // When we flush we expect the Normal pri render to take priority // over hydration. - expect(Scheduler).toFlushAndYieldThrough(['Sibling', 'Commit Sibling']); + await waitFor(['Sibling', 'Commit Sibling']); // We shouldn't have hydrated the child yet. expect(ref.current).toBe(null); // But we did have a chance to update the content. expect(container.textContent).toBe('HelloWorld'); - expect(Scheduler).toFlushAndYield(['Child']); + await waitForAll(['Child']); // Now we're hydrated. expect(ref.current).not.toBe(null); @@ -3248,7 +3252,7 @@ describe('ReactDOMServerPartialHydration', () => { } const finalHTML = ReactDOMServer.renderToString(); - expect(Scheduler).toHaveYielded([]); + assertLog([]); const container = document.createElement('div'); container.innerHTML = finalHTML; @@ -3267,7 +3271,7 @@ describe('ReactDOMServerPartialHydration', () => { // The tree successfully hydrates ReactDOMClient.hydrateRoot(container, ); - expect(Scheduler).toFlushAndYield([]); + await waitForAll([]); expect(ref.current).toBe(span); }); @@ -3295,7 +3299,7 @@ describe('ReactDOMServerPartialHydration', () => { // During server rendering, the Child component should not be evaluated, // because it's inside a hidden tree. const finalHTML = ReactDOMServer.renderToString(); - expect(Scheduler).toHaveYielded(['App']); + assertLog(['App']); const container = document.createElement('div'); container.innerHTML = finalHTML; @@ -3313,11 +3317,11 @@ describe('ReactDOMServerPartialHydration', () => { // The visible span successfully hydrates ReactDOMClient.hydrateRoot(container, ); - expect(Scheduler).toFlushUntilNextPaint(['App']); + await waitForPaint(['App']); expect(visibleRef.current).toBe(visibleSpan); // Subsequently, the hidden child is prerendered on the client - expect(Scheduler).toFlushUntilNextPaint(['HiddenChild']); + await waitForPaint(['HiddenChild']); expect(container).toMatchInlineSnapshot(`
@@ -3434,7 +3438,7 @@ describe('ReactDOMServerPartialHydration', () => { ], {withoutStack: 1}, ); - expect(Scheduler).toHaveYielded([ + assertLog([ 'Log recoverable error: Hydration failed because the initial UI does not match what was rendered on the server.', // TODO: There were multiple mismatches in a single container. Should // we attempt to de-dupe them? @@ -3482,7 +3486,7 @@ describe('ReactDOMServerPartialHydration', () => { ], {withoutStack: 1}, ); - expect(Scheduler).toHaveYielded([ + assertLog([ 'Text content does not match server-rendered HTML.', 'There was an error while hydrating. Because the error happened outside ' + 'of a Suspense boundary, the entire root will switch to client rendering.', @@ -3527,7 +3531,7 @@ describe('ReactDOMServerPartialHydration', () => { ], {withoutStack: 1}, ); - expect(Scheduler).toHaveYielded([ + assertLog([ 'Text content does not match server-rendered HTML.', 'There was an error while hydrating. Because the error happened outside ' + 'of a Suspense boundary, the entire root will switch to client rendering.', diff --git a/packages/react-dom/src/__tests__/ReactDOMServerSelectiveHydration-test.internal.js b/packages/react-dom/src/__tests__/ReactDOMServerSelectiveHydration-test.internal.js index cac14620e7971..bbcbc8c275164 100644 --- a/packages/react-dom/src/__tests__/ReactDOMServerSelectiveHydration-test.internal.js +++ b/packages/react-dom/src/__tests__/ReactDOMServerSelectiveHydration-test.internal.js @@ -19,6 +19,10 @@ let ReactFeatureFlags; let Scheduler; let Suspense; let act; +let assertLog; +let waitForAll; +let waitFor; +let waitForPaint; let IdleEventPriority; let ContinuousEventPriority; @@ -137,6 +141,12 @@ describe('ReactDOMServerSelectiveHydration', () => { Scheduler = require('scheduler'); Suspense = React.Suspense; + const InternalTestUtils = require('internal-test-utils'); + assertLog = InternalTestUtils.assertLog; + waitForAll = InternalTestUtils.waitForAll; + waitFor = InternalTestUtils.waitFor; + waitForPaint = InternalTestUtils.waitForPaint; + IdleEventPriority = require('react-reconciler/constants').IdleEventPriority; ContinuousEventPriority = require('react-reconciler/constants').ContinuousEventPriority; @@ -172,7 +182,7 @@ describe('ReactDOMServerSelectiveHydration', () => { const finalHTML = ReactDOMServer.renderToString(); - expect(Scheduler).toHaveYielded(['App', 'A', 'B']); + assertLog(['App', 'A', 'B']); const container = document.createElement('div'); // We need this to be in the document since we'll dispatch events on it. @@ -185,7 +195,7 @@ describe('ReactDOMServerSelectiveHydration', () => { ReactDOMClient.hydrateRoot(container, ); // Nothing has been hydrated so far. - expect(Scheduler).toHaveYielded([]); + assertLog([]); // This should synchronously hydrate the root App and the second suspense // boundary. @@ -195,10 +205,10 @@ describe('ReactDOMServerSelectiveHydration', () => { expect(result).toBe(false); // We rendered App, B and then invoked the event without rendering A. - expect(Scheduler).toHaveYielded(['App', 'B', 'Clicked B']); + assertLog(['App', 'B', 'Clicked B']); // After continuing the scheduler, we finally hydrate A. - expect(Scheduler).toFlushAndYield(['A']); + await waitForAll(['A']); document.body.removeChild(container); }); @@ -246,7 +256,7 @@ describe('ReactDOMServerSelectiveHydration', () => { const finalHTML = ReactDOMServer.renderToString(); - expect(Scheduler).toHaveYielded(['App', 'A', 'B', 'C', 'D']); + assertLog(['App', 'A', 'B', 'C', 'D']); const container = document.createElement('div'); // We need this to be in the document since we'll dispatch events on it. @@ -263,14 +273,14 @@ describe('ReactDOMServerSelectiveHydration', () => { ReactDOMClient.hydrateRoot(container, ); // Nothing has been hydrated so far. - expect(Scheduler).toHaveYielded([]); + assertLog([]); // This click target cannot be hydrated yet because it's suspended. await act(async () => { const result = dispatchClickEvent(spanD); expect(result).toBe(true); }); - expect(Scheduler).toHaveYielded([ + assertLog([ 'App', // Continuing rendering will render B next. 'B', @@ -289,11 +299,11 @@ describe('ReactDOMServerSelectiveHydration', () => { flags.enableCapturePhaseSelectiveHydrationWithoutDiscreteEventReplay, ) ) { - expect(Scheduler).toHaveYielded(['D', 'A']); + assertLog(['D', 'A']); } else { // After the click, we should prioritize D and the Click first, // and only after that render A and C. - expect(Scheduler).toHaveYielded(['D', 'Clicked D', 'A']); + assertLog(['D', 'Clicked D', 'A']); } document.body.removeChild(container); @@ -342,7 +352,7 @@ describe('ReactDOMServerSelectiveHydration', () => { const finalHTML = ReactDOMServer.renderToString(); - expect(Scheduler).toHaveYielded(['App', 'A', 'B', 'C', 'D']); + assertLog(['App', 'A', 'B', 'C', 'D']); const container = document.createElement('div'); // We need this to be in the document since we'll dispatch events on it. @@ -361,7 +371,7 @@ describe('ReactDOMServerSelectiveHydration', () => { ReactDOMClient.hydrateRoot(container, ); // Nothing has been hydrated so far. - expect(Scheduler).toHaveYielded([]); + assertLog([]); // This click target cannot be hydrated yet because the first is Suspended. dispatchClickEvent(spanA); @@ -374,9 +384,9 @@ describe('ReactDOMServerSelectiveHydration', () => { flags.enableCapturePhaseSelectiveHydrationWithoutDiscreteEventReplay, ) ) { - expect(Scheduler).toHaveYielded(['App', 'C', 'Clicked C']); + assertLog(['App', 'C', 'Clicked C']); } else { - expect(Scheduler).toHaveYielded(['App']); + assertLog(['App']); } await act(async () => { @@ -388,7 +398,7 @@ describe('ReactDOMServerSelectiveHydration', () => { if ( ReactFeatureFlags.enableCapturePhaseSelectiveHydrationWithoutDiscreteEventReplay ) { - expect(Scheduler).toHaveYielded([ + assertLog([ 'A', 'D', // B should render last since it wasn't clicked. @@ -397,7 +407,7 @@ describe('ReactDOMServerSelectiveHydration', () => { } else { // We should prioritize hydrating A, C and D first since we clicked in // them. Only after they're done will we hydrate B. - expect(Scheduler).toHaveYielded([ + assertLog([ 'A', 'Clicked A', 'C', @@ -447,7 +457,7 @@ describe('ReactDOMServerSelectiveHydration', () => { const finalHTML = ReactDOMServer.renderToString(); - expect(Scheduler).toHaveYielded(['App', 'A', 'B']); + assertLog(['App', 'A', 'B']); const container = document.createElement('div'); // We need this to be in the document since we'll dispatch events on it. @@ -460,7 +470,7 @@ describe('ReactDOMServerSelectiveHydration', () => { ReactDOMClient.hydrateRoot(container, ); // Nothing has been hydrated so far. - expect(Scheduler).toHaveYielded([]); + assertLog([]); const span = container.getElementsByTagName('span')[1]; @@ -471,10 +481,10 @@ describe('ReactDOMServerSelectiveHydration', () => { target.virtualclick(); // We rendered App, B and then invoked the event without rendering A. - expect(Scheduler).toHaveYielded(['App', 'B', 'Clicked B']); + assertLog(['App', 'B', 'Clicked B']); // After continuing the scheduler, we finally hydrate A. - expect(Scheduler).toFlushAndYield(['A']); + await waitForAll(['A']); document.body.removeChild(container); }); @@ -527,7 +537,7 @@ describe('ReactDOMServerSelectiveHydration', () => { const finalHTML = ReactDOMServer.renderToString(); - expect(Scheduler).toHaveYielded(['App', 'A', 'B', 'C', 'D']); + assertLog(['App', 'A', 'B', 'C', 'D']); const container = document.createElement('div'); // We need this to be in the document since we'll dispatch events on it. @@ -545,14 +555,14 @@ describe('ReactDOMServerSelectiveHydration', () => { ReactDOMClient.hydrateRoot(container, ); // Nothing has been hydrated so far. - expect(Scheduler).toHaveYielded([]); + assertLog([]); // Continuing rendering will render B next. await act(async () => { const target = createEventTarget(spanD); target.virtualclick(); }); - expect(Scheduler).toHaveYielded(['App', 'B', 'C']); + assertLog(['App', 'B', 'C']); // After the click, we should prioritize D and the Click first, // and only after that render A and C. @@ -568,9 +578,9 @@ describe('ReactDOMServerSelectiveHydration', () => { ) ) { // no replay - expect(Scheduler).toHaveYielded(['D', 'A']); + assertLog(['D', 'A']); } else { - expect(Scheduler).toHaveYielded(['D', 'Clicked D', 'A']); + assertLog(['D', 'Clicked D', 'A']); } document.body.removeChild(container); @@ -623,7 +633,7 @@ describe('ReactDOMServerSelectiveHydration', () => { const finalHTML = ReactDOMServer.renderToString(); - expect(Scheduler).toHaveYielded(['App', 'A', 'B', 'C', 'D']); + assertLog(['App', 'A', 'B', 'C', 'D']); const container = document.createElement('div'); // We need this to be in the document since we'll dispatch events on it. @@ -643,7 +653,7 @@ describe('ReactDOMServerSelectiveHydration', () => { ReactDOMClient.hydrateRoot(container, ); // Nothing has been hydrated so far. - expect(Scheduler).toHaveYielded([]); + assertLog([]); // This click target cannot be hydrated yet because the first is Suspended. createEventTarget(spanA).virtualclick(); @@ -653,9 +663,9 @@ describe('ReactDOMServerSelectiveHydration', () => { if ( ReactFeatureFlags.enableCapturePhaseSelectiveHydrationWithoutDiscreteEventReplay ) { - expect(Scheduler).toHaveYielded(['App', 'C', 'Clicked C']); + assertLog(['App', 'C', 'Clicked C']); } else { - expect(Scheduler).toHaveYielded(['App']); + assertLog(['App']); } await act(async () => { suspend = false; @@ -666,7 +676,7 @@ describe('ReactDOMServerSelectiveHydration', () => { if ( ReactFeatureFlags.enableCapturePhaseSelectiveHydrationWithoutDiscreteEventReplay ) { - expect(Scheduler).toHaveYielded([ + assertLog([ 'A', 'D', // B should render last since it wasn't clicked. @@ -675,7 +685,7 @@ describe('ReactDOMServerSelectiveHydration', () => { } else { // We should prioritize hydrating A, C and D first since we clicked in // them. Only after they're done will we hydrate B. - expect(Scheduler).toHaveYielded([ + assertLog([ 'A', 'Clicked A', 'C', @@ -734,7 +744,7 @@ describe('ReactDOMServerSelectiveHydration', () => { ); } const finalHTML = ReactDOMServer.renderToString(); - expect(Scheduler).toHaveYielded(['App', 'A', 'B', 'C', 'D']); + assertLog(['App', 'A', 'B', 'C', 'D']); const container = document.createElement('div'); // We need this to be in the document since we'll dispatch events on it. document.body.appendChild(container); @@ -752,14 +762,14 @@ describe('ReactDOMServerSelectiveHydration', () => { ReactDOMClient.hydrateRoot(container, ); // Nothing has been hydrated so far. - expect(Scheduler).toHaveYielded([]); + assertLog([]); // Click D dispatchMouseHoverEvent(spanD, null); dispatchClickEvent(spanD); // Hover over B and then C. dispatchMouseHoverEvent(spanB, spanD); dispatchMouseHoverEvent(spanC, spanB); - expect(Scheduler).toHaveYielded(['App']); + assertLog(['App']); await act(async () => { suspend = false; resolve(); @@ -773,7 +783,7 @@ describe('ReactDOMServerSelectiveHydration', () => { ) { // We should prioritize hydrating D first because we clicked it. // but event isnt replayed - expect(Scheduler).toHaveYielded([ + assertLog([ 'D', 'B', // Ideally this should be later. 'C', @@ -787,7 +797,7 @@ describe('ReactDOMServerSelectiveHydration', () => { // the same time since B was already scheduled. // This is ok because it will at least not continue for nested // boundary. See the next test below. - expect(Scheduler).toHaveYielded([ + assertLog([ 'D', 'Clicked D', 'B', // Ideally this should be later. @@ -883,7 +893,7 @@ describe('ReactDOMServerSelectiveHydration', () => { const finalHTML = ReactDOMServer.renderToString(); - expect(Scheduler).toHaveYielded(['App', 'A', 'B', 'C', 'D']); + assertLog(['App', 'A', 'B', 'C', 'D']); const container = document.createElement('div'); // We need this to be in the document since we'll dispatch events on it. @@ -902,7 +912,7 @@ describe('ReactDOMServerSelectiveHydration', () => { ReactDOMClient.hydrateRoot(container, ); // Nothing has been hydrated so far. - expect(Scheduler).toHaveYielded([]); + assertLog([]); // Click D dispatchMouseHoverEvent(spanD, null); @@ -911,7 +921,7 @@ describe('ReactDOMServerSelectiveHydration', () => { dispatchMouseHoverEvent(spanB, spanD); dispatchMouseHoverEvent(spanC, spanB); - expect(Scheduler).toHaveYielded(['App']); + assertLog(['App']); await act(async () => { suspend = false; @@ -927,7 +937,7 @@ describe('ReactDOMServerSelectiveHydration', () => { ) { // We should prioritize hydrating D first because we clicked it. // but event isnt replayed - expect(Scheduler).toHaveYielded([ + assertLog([ 'D', 'B', // Ideally this should be later. 'C', @@ -948,7 +958,7 @@ describe('ReactDOMServerSelectiveHydration', () => { // the same time since B was already scheduled. // This is ok because it will at least not continue for nested // boundary. See the next test below. - expect(Scheduler).toHaveYielded([ + assertLog([ 'D', 'Clicked D', 'B', // Ideally this should be later. @@ -964,7 +974,7 @@ describe('ReactDOMServerSelectiveHydration', () => { // This test shows existing quirk where stopPropagation on mouseout // prevents mouseEnter from firing dispatchMouseHoverEvent(spanC, spanB); - expect(Scheduler).toHaveYielded([ + assertLog([ 'Mouse Out Capture B', // stopPropagation stops these // 'Mouse Out B', @@ -1120,7 +1130,7 @@ describe('ReactDOMServerSelectiveHydration', () => { expect(InnerScheduler).toHaveYielded(['Suspend Inner']); } - expect(Scheduler).toHaveYielded([]); + assertLog([]); }); afterEach(async () => { document.body.innerHTML = ''; @@ -1143,13 +1153,13 @@ describe('ReactDOMServerSelectiveHydration', () => { // Inner App renders because it is unblocked expect(InnerScheduler).toHaveYielded(['Inner']); // No event is replayed yet - expect(Scheduler).toHaveYielded([]); + assertLog([]); dispatchMouseHoverEvent(innerDiv); expect(OuterScheduler).toHaveYielded([]); expect(InnerScheduler).toHaveYielded([]); // No event is replayed yet - expect(Scheduler).toHaveYielded([]); + assertLog([]); await act(async () => { resolveOuter(); @@ -1166,7 +1176,7 @@ describe('ReactDOMServerSelectiveHydration', () => { // Outer hydrates and schedules Replay expect(OuterScheduler).toHaveYielded(['Outer']); // No event is replayed yet - expect(Scheduler).toHaveYielded([]); + assertLog([]); // fire scheduled Replay await act(async () => { @@ -1177,10 +1187,7 @@ describe('ReactDOMServerSelectiveHydration', () => { }); // First Inner Mouse Enter fires then Outer Mouse Enter - expect(Scheduler).toHaveYielded([ - 'Inner Mouse Enter', - 'Outer Mouse Enter', - ]); + assertLog(['Inner Mouse Enter', 'Outer Mouse Enter']); }); // @gate enableCapturePhaseSelectiveHydrationWithoutDiscreteEventReplay @@ -1209,14 +1216,14 @@ describe('ReactDOMServerSelectiveHydration', () => { // Inner is still blocked so when Outer replays the event in capture phase // inner ends up caling stopPropagation - expect(Scheduler).toHaveYielded([]); + assertLog([]); expect(OuterScheduler).toHaveYielded([]); expect(InnerScheduler).toHaveYielded(['Suspend Inner']); dispatchMouseHoverEvent(innerDiv); expect(OuterScheduler).toHaveYielded([]); expect(InnerScheduler).toHaveYielded([]); - expect(Scheduler).toHaveYielded([]); + assertLog([]); await act(async () => { resolveInner(); @@ -1238,10 +1245,7 @@ describe('ReactDOMServerSelectiveHydration', () => { }); // First Inner Mouse Enter fires then Outer Mouse Enter - expect(Scheduler).toHaveYielded([ - 'Inner Mouse Enter', - 'Outer Mouse Enter', - ]); + assertLog(['Inner Mouse Enter', 'Outer Mouse Enter']); }); }); @@ -1280,7 +1284,7 @@ describe('ReactDOMServerSelectiveHydration', () => { } const finalHTML = ReactDOMServer.renderToString(); - expect(Scheduler).toHaveYielded(['Child']); + assertLog(['Child']); const container = document.createElement('div'); @@ -1294,11 +1298,11 @@ describe('ReactDOMServerSelectiveHydration', () => { dispatchMouseHoverEvent(childDiv); // Not hydrated so event is saved for replay and stopPropagation is called - expect(Scheduler).toHaveYielded([]); + assertLog([]); resolve(); Scheduler.unstable_flushNumberOfYields(1); - expect(Scheduler).toHaveYielded(['Child']); + assertLog(['Child']); Scheduler.unstable_scheduleCallback( Scheduler.unstable_ImmediatePriority, @@ -1316,7 +1320,7 @@ describe('ReactDOMServerSelectiveHydration', () => { // Even though the tree is remove the event is still dispatched with native event handler // on the container firing. - expect(Scheduler).toHaveYielded(['container2 mouse over']); + assertLog(['container2 mouse over']); document.body.removeChild(container); }); @@ -1366,7 +1370,7 @@ describe('ReactDOMServerSelectiveHydration', () => { const finalHTML = ReactDOMServer.renderToString(); - expect(Scheduler).toHaveYielded(['App', 'A', 'B', 'C', 'D']); + assertLog(['App', 'A', 'B', 'C', 'D']); const container = document.createElement('div'); // We need this to be in the document since we'll dispatch events on it. @@ -1385,7 +1389,7 @@ describe('ReactDOMServerSelectiveHydration', () => { ReactDOMClient.hydrateRoot(container, ); // Nothing has been hydrated so far. - expect(Scheduler).toHaveYielded([]); + assertLog([]); // Hover over B and then C. dispatchMouseHoverEvent(spanB, spanD); @@ -1402,7 +1406,7 @@ describe('ReactDOMServerSelectiveHydration', () => { // Next it doesn't matter if we hydrate A or B first but as an // implementation detail we're currently hydrating B first since // we at one point hovered over it and we never deprioritized it. - expect(Scheduler).toHaveYielded(['App', 'C', 'Hover C', 'A', 'B', 'D']); + assertLog(['App', 'C', 'Hover C', 'A', 'B', 'D']); document.body.removeChild(container); }); @@ -1432,7 +1436,7 @@ describe('ReactDOMServerSelectiveHydration', () => { const finalHTML = ReactDOMServer.renderToString(); - expect(Scheduler).toHaveYielded(['App', 'A', 'B', 'C']); + assertLog(['App', 'A', 'B', 'C']); const container = document.createElement('div'); container.innerHTML = finalHTML; @@ -1443,7 +1447,7 @@ describe('ReactDOMServerSelectiveHydration', () => { const root = ReactDOMClient.hydrateRoot(container, ); // Nothing has been hydrated so far. - expect(Scheduler).toHaveYielded([]); + assertLog([]); // Increase priority of B and then C. root.unstable_scheduleHydration(spanB); @@ -1451,7 +1455,7 @@ describe('ReactDOMServerSelectiveHydration', () => { // We should prioritize hydrating C first because the last added // gets highest priority followed by the next added. - expect(Scheduler).toFlushAndYield(['App', 'C', 'B', 'A']); + await waitForAll(['App', 'C', 'B', 'A']); }); // @gate experimental || www @@ -1485,7 +1489,7 @@ describe('ReactDOMServerSelectiveHydration', () => { const finalHTML = ReactDOMServer.renderToString(); - expect(Scheduler).toHaveYielded(['App', 'A', 'a', 'B', 'b', 'C', 'c']); + assertLog(['App', 'A', 'a', 'B', 'b', 'C', 'c']); const container = document.createElement('div'); container.innerHTML = finalHTML; @@ -1497,10 +1501,10 @@ describe('ReactDOMServerSelectiveHydration', () => { const spanB = container.getElementsByTagName('span')[2]; const spanC = container.getElementsByTagName('span')[4]; - act(() => { + await act(async () => { const root = ReactDOMClient.hydrateRoot(container, ); // Hydrate the shell. - expect(Scheduler).toFlushAndYieldThrough(['App', 'Commit']); + await waitFor(['App', 'Commit']); // Render an update at Idle priority that needs to update A. @@ -1510,7 +1514,7 @@ describe('ReactDOMServerSelectiveHydration', () => { // Start rendering. This will force the first boundary to hydrate // by scheduling it at one higher pri than Idle. - expect(Scheduler).toFlushAndYieldThrough([ + await waitFor([ 'App', // Start hydrating A @@ -1532,13 +1536,13 @@ describe('ReactDOMServerSelectiveHydration', () => { // priority levels. dispatchClickEvent(spanC); - expect(Scheduler).toHaveYielded([ + assertLog([ // Hydrate C first since we clicked it. 'C', 'c', ]); - expect(Scheduler).toFlushAndYield([ + await waitForAll([ // Finish hydration of A since we forced it to hydrate. 'A', 'a', @@ -1612,7 +1616,7 @@ describe('ReactDOMServerSelectiveHydration', () => { 'useLayoutEffect does nothing on the server', ]); - expect(Scheduler).toHaveYielded(['App', 'A', 'B']); + assertLog(['App', 'A', 'B']); const container = document.createElement('div'); // We need this to be in the document since we'll dispatch events on it. @@ -1625,23 +1629,17 @@ describe('ReactDOMServerSelectiveHydration', () => { ReactDOMClient.hydrateRoot(container, ); // Nothing has been hydrated so far. - expect(Scheduler).toHaveYielded([]); + assertLog([]); // This should synchronously hydrate the root App and the second suspense // boundary. dispatchClickEvent(span); // We rendered App, B and then invoked the event without rendering A. - expect(Scheduler).toHaveYielded([ - 'App', - 'B', - 'Capture Clicked B', - 'Native Click B', - 'Clicked B', - ]); + assertLog(['App', 'B', 'Capture Clicked B', 'Native Click B', 'Clicked B']); // After continuing the scheduler, we finally hydrate A. - expect(Scheduler).toFlushAndYield(['A']); + await waitForAll(['A']); document.body.removeChild(container); }); @@ -1686,7 +1684,7 @@ describe('ReactDOMServerSelectiveHydration', () => { } const finalHTML = ReactDOMServer.renderToString(); - expect(Scheduler).toHaveYielded(['App', 'Child']); + assertLog(['App', 'Child']); const container = document.createElement('div'); document.body.appendChild(container); @@ -1696,12 +1694,12 @@ describe('ReactDOMServerSelectiveHydration', () => { ReactDOMClient.hydrateRoot(container, ); // Nothing has been hydrated so far. - expect(Scheduler).toHaveYielded([]); + assertLog([]); const span = container.getElementsByTagName('span')[0]; dispatchClickEvent(span); - expect(Scheduler).toHaveYielded(['App']); + assertLog(['App']); dispatchClickEvent(span); @@ -1740,7 +1738,7 @@ describe('ReactDOMServerSelectiveHydration', () => { const finalHTML = ReactDOMServer.renderToString(); - expect(Scheduler).toHaveYielded(['App', 'A']); + assertLog(['App', 'A']); const container = document.createElement('div'); // We need this to be in the document since we'll dispatch events on it. @@ -1756,12 +1754,12 @@ describe('ReactDOMServerSelectiveHydration', () => { React.startTransition(() => { ReactDOMClient.hydrateRoot(container, ); }); - expect(Scheduler).toFlushAndYieldThrough(['App']); + await waitFor(['App']); // This should attempt to synchronously hydrate the root, then pause // because it still suspended const result = dispatchClickEvent(span); - expect(Scheduler).toHaveYielded(['App']); + assertLog(['App']); // The event should not have been cancelled because we didn't hydrate. expect(result).toBe(true); @@ -1778,9 +1776,9 @@ describe('ReactDOMServerSelectiveHydration', () => { flags.enableCapturePhaseSelectiveHydrationWithoutDiscreteEventReplay, ) ) { - expect(Scheduler).toHaveYielded(['App', 'A']); + assertLog(['App', 'A']); } else { - expect(Scheduler).toHaveYielded(['App', 'A', 'Clicked A']); + assertLog(['App', 'A', 'Clicked A']); } document.body.removeChild(container); @@ -1804,20 +1802,20 @@ describe('ReactDOMServerSelectiveHydration', () => { let spanRef; const finalHTML = ReactDOMServer.renderToString(); - expect(Scheduler).toHaveYielded(['App A', 'Child A']); + assertLog(['App A', 'Child A']); const container = document.createElement('div'); document.body.appendChild(container); container.innerHTML = finalHTML; const initialSpan = container.getElementsByTagName('span')[0]; const root = ReactDOMClient.hydrateRoot(container, ); - expect(Scheduler).toFlushUntilNextPaint(['App A']); + await waitForPaint(['App A']); await act(async () => { ReactDOM.flushSync(() => { root.render(); }); }); - expect(Scheduler).toHaveYielded(['App B', 'Child A', 'App B', 'Child B']); + assertLog(['App B', 'Child A', 'App B', 'Child B']); expect(initialSpan).toBe(spanRef); }); @@ -1840,13 +1838,13 @@ describe('ReactDOMServerSelectiveHydration', () => { let spanRef; const finalHTML = ReactDOMServer.renderToString(); - expect(Scheduler).toHaveYielded(['App A', 'Child A']); + assertLog(['App A', 'Child A']); const container = document.createElement('div'); document.body.appendChild(container); container.innerHTML = finalHTML; const initialSpan = container.getElementsByTagName('span')[0]; const root = ReactDOMClient.hydrateRoot(container, ); - expect(Scheduler).toFlushUntilNextPaint(['App A']); + await waitForPaint(['App A']); await act(async () => { TODO_scheduleContinuousSchedulerTask(() => { @@ -1854,7 +1852,7 @@ describe('ReactDOMServerSelectiveHydration', () => { }); }); - expect(Scheduler).toHaveYielded(['App B', 'Child A', 'App B', 'Child B']); + assertLog(['App B', 'Child A', 'App B', 'Child B']); expect(initialSpan).toBe(spanRef); }); @@ -1876,17 +1874,17 @@ describe('ReactDOMServerSelectiveHydration', () => { let spanRef; const finalHTML = ReactDOMServer.renderToString(); - expect(Scheduler).toHaveYielded(['App A', 'Child A']); + assertLog(['App A', 'Child A']); const container = document.createElement('div'); document.body.appendChild(container); container.innerHTML = finalHTML; const initialSpan = container.getElementsByTagName('span')[0]; const root = ReactDOMClient.hydrateRoot(container, ); - expect(Scheduler).toFlushUntilNextPaint(['App A']); + await waitForPaint(['App A']); await act(async () => { root.render(); }); - expect(Scheduler).toHaveYielded(['App B', 'Child A', 'App B', 'Child B']); + assertLog(['App B', 'Child A', 'App B', 'Child B']); expect(initialSpan).toBe(spanRef); }); @@ -1927,7 +1925,7 @@ describe('ReactDOMServerSelectiveHydration', () => { ); } const finalHTML = ReactDOMServer.renderToString(); - expect(Scheduler).toHaveYielded(['App', 'A', 'DefaultContext']); + assertLog(['App', 'A', 'DefaultContext']); const container = document.createElement('div'); container.innerHTML = finalHTML; document.body.appendChild(container); @@ -1936,25 +1934,16 @@ describe('ReactDOMServerSelectiveHydration', () => { await act(async () => { const root = ReactDOMClient.hydrateRoot(container, ); - expect(Scheduler).toFlushAndYieldThrough([ - 'App', - 'DefaultContext', - 'Commit', - ]); + await waitFor(['App', 'DefaultContext', 'Commit']); TODO_scheduleIdleDOMSchedulerTask(() => { root.render(); }); - expect(Scheduler).toFlushAndYieldThrough(['App', 'A']); + await waitFor(['App', 'A']); dispatchClickEvent(spanA); - expect(Scheduler).toHaveYielded(['A']); - expect(Scheduler).toFlushAndYield([ - 'App', - 'AA', - 'DefaultContext', - 'Commit', - ]); + assertLog(['A']); + await waitForAll(['App', 'AA', 'DefaultContext', 'Commit']); }); }); @@ -1994,29 +1983,18 @@ describe('ReactDOMServerSelectiveHydration', () => { ); } const finalHTML = ReactDOMServer.renderToString(); - expect(Scheduler).toHaveYielded(['App', 'A', 'DefaultContext']); + assertLog(['App', 'A', 'DefaultContext']); const container = document.createElement('div'); container.innerHTML = finalHTML; await act(async () => { const root = ReactDOMClient.hydrateRoot(container, ); - expect(Scheduler).toFlushAndYieldThrough([ - 'App', - 'DefaultContext', - 'Commit', - ]); + await waitFor(['App', 'DefaultContext', 'Commit']); ReactDOM.flushSync(() => { root.render(); }); - expect(Scheduler).toHaveYielded([ - 'App', - 'A', - 'App', - 'AA', - 'DefaultContext', - 'Commit', - ]); + assertLog(['App', 'A', 'App', 'AA', 'DefaultContext', 'Commit']); }); }); }); diff --git a/packages/react-dom/src/__tests__/ReactDOMSingletonComponents-test.js b/packages/react-dom/src/__tests__/ReactDOMSingletonComponents-test.js index 26d50204a619f..f8056fd6042e7 100644 --- a/packages/react-dom/src/__tests__/ReactDOMSingletonComponents-test.js +++ b/packages/react-dom/src/__tests__/ReactDOMSingletonComponents-test.js @@ -12,7 +12,6 @@ let JSDOM; let Stream; -let Scheduler; let React; let ReactDOM; let ReactDOMClient; @@ -23,18 +22,21 @@ let container; let buffer = ''; let hasErrored = false; let fatalError = undefined; +let waitForAll; describe('ReactDOM HostSingleton', () => { beforeEach(() => { jest.resetModules(); JSDOM = require('jsdom').JSDOM; - Scheduler = require('scheduler'); React = require('react'); ReactDOM = require('react-dom'); ReactDOMClient = require('react-dom/client'); ReactDOMFizzServer = require('react-dom/server'); Stream = require('stream'); + const InternalTestUtils = require('internal-test-utils'); + waitForAll = InternalTestUtils.waitForAll; + // Test Environment const jsdom = new JSDOM( '
', @@ -130,7 +132,7 @@ describe('ReactDOM HostSingleton', () => { , ); - expect(Scheduler).toFlushWithoutYielding(); + await waitForAll([]); expect(getVisibleChildren(document)).toEqual( @@ -150,8 +152,8 @@ describe('ReactDOM HostSingleton', () => { , ); - expect(() => { - expect(Scheduler).toFlushWithoutYielding(); + await expect(async () => { + await waitForAll([]); }).toErrorDev( 'Warning: You are mounting a new head component when a previous one has not first unmounted. It is an error to render more than one head component at a time and attributes and children of these components will likely fail in unpredictable ways. Please only render a single instance of and if you need to mount a new one, ensure any previous ones have unmounted first', ); @@ -175,7 +177,7 @@ describe('ReactDOM HostSingleton', () => { , ); - expect(Scheduler).toFlushWithoutYielding(); + await waitForAll([]); expect(getVisibleChildren(document)).toEqual( @@ -193,7 +195,7 @@ describe('ReactDOM HostSingleton', () => { , ); - expect(Scheduler).toFlushWithoutYielding(); + await waitForAll([]); expect(getVisibleChildren(document)).toEqual( @@ -280,7 +282,7 @@ describe('ReactDOM HostSingleton', () => { , ); - expect(Scheduler).toFlushWithoutYielding(); + await waitForAll([]); expect(persistentElements).toEqual([ document.documentElement, document.head, @@ -320,7 +322,7 @@ describe('ReactDOM HostSingleton', () => { , ); - expect(Scheduler).toFlushWithoutYielding(); + await waitForAll([]); expect(persistentElements).toEqual([ document.documentElement, document.head, @@ -359,7 +361,7 @@ describe('ReactDOM HostSingleton', () => { , ); - expect(Scheduler).toFlushWithoutYielding(); + await waitForAll([]); expect(persistentElements).toEqual([ document.documentElement, document.head, @@ -395,7 +397,7 @@ describe('ReactDOM HostSingleton', () => { , ); - expect(Scheduler).toFlushWithoutYielding(); + await waitForAll([]); expect(persistentElements).toEqual([ document.documentElement, document.head, @@ -422,7 +424,7 @@ describe('ReactDOM HostSingleton', () => { // unmount the root root.unmount(); - expect(Scheduler).toFlushWithoutYielding(); + await waitForAll([]); expect(persistentElements).toEqual([ document.documentElement, document.head, @@ -471,8 +473,8 @@ describe('ReactDOM HostSingleton', () => { }, }, ); - expect(() => { - expect(Scheduler).toFlushWithoutYielding(); + await expect(async () => { + await waitForAll([]); }).toErrorDev( [ `Warning: Expected server HTML to contain a matching
in . @@ -555,7 +557,7 @@ describe('ReactDOM HostSingleton', () => { }, ); expect(hydrationErrors).toEqual([]); - expect(Scheduler).toFlushWithoutYielding(); + await waitForAll([]); expect(persistentElements).toEqual([ document.documentElement, document.head, @@ -627,7 +629,7 @@ describe('ReactDOM HostSingleton', () => { , ); - expect(Scheduler).toFlushWithoutYielding(); + await waitForAll([]); // We construct and insert some artificial stylesheets mimicing what a 3rd party script might do // In the future we could hydrate with these already in the document but the rules are restrictive @@ -683,7 +685,7 @@ describe('ReactDOM HostSingleton', () => { , ); - expect(Scheduler).toFlushWithoutYielding(); + await waitForAll([]); expect(getVisibleChildren(document)).toEqual( @@ -711,7 +713,7 @@ describe('ReactDOM HostSingleton', () => { , ); - expect(Scheduler).toFlushWithoutYielding(); + await waitForAll([]); expect(getVisibleChildren(document)).toEqual( @@ -763,7 +765,7 @@ describe('ReactDOM HostSingleton', () => { , ); - expect(Scheduler).toFlushWithoutYielding(); + await waitForAll([]); expect(getVisibleChildren(document)).toEqual( @@ -799,7 +801,7 @@ describe('ReactDOM HostSingleton', () => { const root = ReactDOMClient.createRoot(container); root.render(something new); - expect(Scheduler).toFlushWithoutYielding(); + await waitForAll([]); expect(getVisibleChildren(document)).toEqual( @@ -831,7 +833,7 @@ describe('ReactDOM HostSingleton', () => { const root = ReactDOMClient.createRoot(container); root.render(
something new
); - expect(Scheduler).toFlushWithoutYielding(); + await waitForAll([]); expect(getVisibleChildren(document)).toEqual( @@ -865,7 +867,7 @@ describe('ReactDOM HostSingleton', () => { foo , ); - expect(Scheduler).toFlushWithoutYielding(); + await waitForAll([]); expect(getVisibleChildren(document)).toEqual( @@ -879,7 +881,7 @@ describe('ReactDOM HostSingleton', () => { bar , ); - expect(Scheduler).toFlushWithoutYielding(); + await waitForAll([]); expect(getVisibleChildren(document)).toEqual( @@ -896,7 +898,7 @@ describe('ReactDOM HostSingleton', () => { baz , ); - expect(Scheduler).toFlushWithoutYielding(); + await waitForAll([]); expect(getVisibleChildren(document)).toEqual( @@ -913,7 +915,7 @@ describe('ReactDOM HostSingleton', () => { foo , ); - expect(Scheduler).toFlushWithoutYielding(); + await waitForAll([]); expect(getVisibleChildren(document)).toEqual( @@ -929,7 +931,7 @@ describe('ReactDOM HostSingleton', () => { , ); - expect(Scheduler).toFlushWithoutYielding(); + await waitForAll([]); expect(getVisibleChildren(document)).toEqual( @@ -945,7 +947,7 @@ describe('ReactDOM HostSingleton', () => { foo , ); - expect(Scheduler).toFlushWithoutYielding(); + await waitForAll([]); expect(getVisibleChildren(document)).toEqual( @@ -961,7 +963,7 @@ describe('ReactDOM HostSingleton', () => { , ); - expect(Scheduler).toFlushWithoutYielding(); + await waitForAll([]); expect(getVisibleChildren(document)).toEqual( @@ -996,7 +998,7 @@ describe('ReactDOM HostSingleton', () => { foo , ); - expect(Scheduler).toFlushWithoutYielding(); + await waitForAll([]); expect(getVisibleChildren(document)).toEqual( diff --git a/packages/react-dom/src/__tests__/ReactDOMUseId-test.js b/packages/react-dom/src/__tests__/ReactDOMUseId-test.js index c7c401d4abb8e..97502104dda20 100644 --- a/packages/react-dom/src/__tests__/ReactDOMUseId-test.js +++ b/packages/react-dom/src/__tests__/ReactDOMUseId-test.js @@ -11,7 +11,6 @@ let JSDOM; let React; let ReactDOMClient; -let Scheduler; let clientAct; let ReactDOMFizzServer; let Stream; @@ -24,6 +23,7 @@ let container; let buffer = ''; let hasErrored = false; let fatalError = undefined; +let waitForPaint; describe('useId', () => { beforeEach(() => { @@ -31,7 +31,6 @@ describe('useId', () => { JSDOM = require('jsdom').JSDOM; React = require('react'); ReactDOMClient = require('react-dom/client'); - Scheduler = require('scheduler'); clientAct = require('jest-react').act; ReactDOMFizzServer = require('react-dom/server'); Stream = require('stream'); @@ -39,6 +38,9 @@ describe('useId', () => { useId = React.useId; useState = React.useState; + const InternalTestUtils = require('internal-test-utils'); + waitForPaint = InternalTestUtils.waitForPaint; + // Test Environment const jsdom = new JSDOM( '
', @@ -443,7 +445,7 @@ describe('useId', () => { const dehydratedSpan = container.getElementsByTagName('span')[0]; await clientAct(async () => { const root = ReactDOMClient.hydrateRoot(container, ); - expect(Scheduler).toFlushUntilNextPaint([]); + await waitForPaint([]); expect(container).toMatchInlineSnapshot(`
{ const dehydratedSpan = container.getElementsByTagName('span')[0]; await clientAct(async () => { const root = ReactDOMClient.hydrateRoot(container, ); - expect(Scheduler).toFlushUntilNextPaint([]); + await waitForPaint([]); expect(container).toMatchInlineSnapshot(`
{ let NoopErrorBoundary; let RetryErrorBoundary; let Normal; + let assertLog; beforeEach(() => { jest.useFakeTimers(); @@ -48,6 +49,9 @@ describe('ReactErrorBoundaries', () => { act = require('jest-react').act; Scheduler = require('scheduler'); + const InternalTestUtils = require('internal-test-utils'); + assertLog = InternalTestUtils.assertLog; + BrokenConstructor = class extends React.Component { constructor(props) { super(props); @@ -805,7 +809,7 @@ describe('ReactErrorBoundaries', () => { } expect(container.firstChild.textContent).toBe('Caught an error: Hello.'); - expect(Scheduler).toHaveYielded([ + assertLog([ 'ErrorBoundary constructor', 'ErrorBoundary componentWillMount', 'ErrorBoundary render success', @@ -820,7 +824,7 @@ describe('ReactErrorBoundaries', () => { ]); ReactDOM.unmountComponentAtNode(container); - expect(Scheduler).toHaveYielded(['ErrorBoundary componentWillUnmount']); + assertLog(['ErrorBoundary componentWillUnmount']); }); it('renders an error state if child throws in render', () => { @@ -832,7 +836,7 @@ describe('ReactErrorBoundaries', () => { container, ); expect(container.firstChild.textContent).toBe('Caught an error: Hello.'); - expect(Scheduler).toHaveYielded([ + assertLog([ 'ErrorBoundary constructor', 'ErrorBoundary componentWillMount', 'ErrorBoundary render success', @@ -847,7 +851,7 @@ describe('ReactErrorBoundaries', () => { ]); ReactDOM.unmountComponentAtNode(container); - expect(Scheduler).toHaveYielded(['ErrorBoundary componentWillUnmount']); + assertLog(['ErrorBoundary componentWillUnmount']); }); it('renders an error state if child throws in constructor', () => { @@ -859,7 +863,7 @@ describe('ReactErrorBoundaries', () => { container, ); expect(container.firstChild.textContent).toBe('Caught an error: Hello.'); - expect(Scheduler).toHaveYielded([ + assertLog([ 'ErrorBoundary constructor', 'ErrorBoundary componentWillMount', 'ErrorBoundary render success', @@ -872,7 +876,7 @@ describe('ReactErrorBoundaries', () => { ]); ReactDOM.unmountComponentAtNode(container); - expect(Scheduler).toHaveYielded(['ErrorBoundary componentWillUnmount']); + assertLog(['ErrorBoundary componentWillUnmount']); }); it('renders an error state if child throws in componentWillMount', () => { @@ -884,7 +888,7 @@ describe('ReactErrorBoundaries', () => { container, ); expect(container.firstChild.textContent).toBe('Caught an error: Hello.'); - expect(Scheduler).toHaveYielded([ + assertLog([ 'ErrorBoundary constructor', 'ErrorBoundary componentWillMount', 'ErrorBoundary render success', @@ -898,7 +902,7 @@ describe('ReactErrorBoundaries', () => { ]); ReactDOM.unmountComponentAtNode(container); - expect(Scheduler).toHaveYielded(['ErrorBoundary componentWillUnmount']); + assertLog(['ErrorBoundary componentWillUnmount']); }); it('renders an error state if context provider throws in componentWillMount', () => { @@ -977,7 +981,7 @@ describe('ReactErrorBoundaries', () => { , container, ); - expect(Scheduler).toHaveYielded([ + assertLog([ 'ErrorBoundary constructor', 'ErrorBoundary componentWillMount', 'ErrorBoundary render success', @@ -995,7 +999,7 @@ describe('ReactErrorBoundaries', () => { ]); ReactDOM.unmountComponentAtNode(container); - expect(Scheduler).toHaveYielded([ + assertLog([ 'ErrorBoundary componentWillUnmount', 'ErrorMessage componentWillUnmount', ]); @@ -1012,7 +1016,7 @@ describe('ReactErrorBoundaries', () => { container, ); expect(container.firstChild.textContent).toBe('Caught an error: Hello.'); - expect(Scheduler).toHaveYielded([ + assertLog([ 'ErrorBoundary constructor', 'ErrorBoundary componentWillMount', 'ErrorBoundary render success', @@ -1037,7 +1041,7 @@ describe('ReactErrorBoundaries', () => { ]); ReactDOM.unmountComponentAtNode(container); - expect(Scheduler).toHaveYielded(['ErrorBoundary componentWillUnmount']); + assertLog(['ErrorBoundary componentWillUnmount']); }); it('propagates errors inside boundary during componentWillMount', () => { @@ -1049,7 +1053,7 @@ describe('ReactErrorBoundaries', () => { container, ); expect(container.firstChild.textContent).toBe('Caught an error: Hello.'); - expect(Scheduler).toHaveYielded([ + assertLog([ 'ErrorBoundary constructor', 'ErrorBoundary componentWillMount', 'ErrorBoundary render success', @@ -1063,7 +1067,7 @@ describe('ReactErrorBoundaries', () => { ]); ReactDOM.unmountComponentAtNode(container); - expect(Scheduler).toHaveYielded(['ErrorBoundary componentWillUnmount']); + assertLog(['ErrorBoundary componentWillUnmount']); }); it('propagates errors inside boundary while rendering error state', () => { @@ -1077,7 +1081,7 @@ describe('ReactErrorBoundaries', () => { container, ); expect(container.firstChild.textContent).toBe('Caught an error: Hello.'); - expect(Scheduler).toHaveYielded([ + assertLog([ 'ErrorBoundary constructor', 'ErrorBoundary componentWillMount', 'ErrorBoundary render success', @@ -1099,7 +1103,7 @@ describe('ReactErrorBoundaries', () => { ]); ReactDOM.unmountComponentAtNode(container); - expect(Scheduler).toHaveYielded(['ErrorBoundary componentWillUnmount']); + assertLog(['ErrorBoundary componentWillUnmount']); }); it('does not call componentWillUnmount when aborting initial mount', () => { @@ -1113,7 +1117,7 @@ describe('ReactErrorBoundaries', () => { container, ); expect(container.firstChild.textContent).toBe('Caught an error: Hello.'); - expect(Scheduler).toHaveYielded([ + assertLog([ 'ErrorBoundary constructor', 'ErrorBoundary componentWillMount', 'ErrorBoundary render success', @@ -1137,7 +1141,7 @@ describe('ReactErrorBoundaries', () => { ]); ReactDOM.unmountComponentAtNode(container); - expect(Scheduler).toHaveYielded(['ErrorBoundary componentWillUnmount']); + assertLog(['ErrorBoundary componentWillUnmount']); }); it('resets callback refs if mounting aborts', () => { @@ -1157,7 +1161,7 @@ describe('ReactErrorBoundaries', () => { container, ); expect(container.textContent).toBe('Caught an error: Hello.'); - expect(Scheduler).toHaveYielded([ + assertLog([ 'ErrorBoundary constructor', 'ErrorBoundary componentWillMount', 'ErrorBoundary render success', @@ -1173,7 +1177,7 @@ describe('ReactErrorBoundaries', () => { ]); ReactDOM.unmountComponentAtNode(container); - expect(Scheduler).toHaveYielded([ + assertLog([ 'ErrorBoundary componentWillUnmount', 'Error message ref is set to null', ]); @@ -1192,7 +1196,7 @@ describe('ReactErrorBoundaries', () => { container, ); expect(container.textContent).toBe('Caught an error: Hello.'); - expect(Scheduler).toHaveYielded([ + assertLog([ 'ErrorBoundary constructor', 'ErrorBoundary componentWillMount', 'ErrorBoundary render success', @@ -1210,7 +1214,7 @@ describe('ReactErrorBoundaries', () => { ); ReactDOM.unmountComponentAtNode(container); - expect(Scheduler).toHaveYielded(['ErrorBoundary componentWillUnmount']); + assertLog(['ErrorBoundary componentWillUnmount']); expect(errorMessageRef.current).toEqual(null); }); @@ -1223,7 +1227,7 @@ describe('ReactErrorBoundaries', () => { container, ); expect(container.firstChild.textContent).toBe('Mounted successfully.'); - expect(Scheduler).toHaveYielded([ + assertLog([ 'ErrorBoundary constructor', 'ErrorBoundary componentWillMount', 'ErrorBoundary render success', @@ -1231,7 +1235,7 @@ describe('ReactErrorBoundaries', () => { ]); ReactDOM.unmountComponentAtNode(container); - expect(Scheduler).toHaveYielded(['ErrorBoundary componentWillUnmount']); + assertLog(['ErrorBoundary componentWillUnmount']); }); it('catches if child throws in constructor during update', () => { @@ -1252,7 +1256,7 @@ describe('ReactErrorBoundaries', () => { container, ); expect(container.textContent).toBe('Caught an error: Hello.'); - expect(Scheduler).toHaveYielded([ + assertLog([ 'ErrorBoundary componentWillReceiveProps', 'ErrorBoundary componentWillUpdate', 'ErrorBoundary render success', @@ -1275,7 +1279,7 @@ describe('ReactErrorBoundaries', () => { ]); ReactDOM.unmountComponentAtNode(container); - expect(Scheduler).toHaveYielded(['ErrorBoundary componentWillUnmount']); + assertLog(['ErrorBoundary componentWillUnmount']); }); it('catches if child throws in componentWillMount during update', () => { @@ -1297,7 +1301,7 @@ describe('ReactErrorBoundaries', () => { container, ); expect(container.textContent).toBe('Caught an error: Hello.'); - expect(Scheduler).toHaveYielded([ + assertLog([ 'ErrorBoundary componentWillReceiveProps', 'ErrorBoundary componentWillUpdate', 'ErrorBoundary render success', @@ -1321,7 +1325,7 @@ describe('ReactErrorBoundaries', () => { ]); ReactDOM.unmountComponentAtNode(container); - expect(Scheduler).toHaveYielded(['ErrorBoundary componentWillUnmount']); + assertLog(['ErrorBoundary componentWillUnmount']); }); it('catches if child throws in componentWillReceiveProps during update', () => { @@ -1343,7 +1347,7 @@ describe('ReactErrorBoundaries', () => { container, ); expect(container.textContent).toBe('Caught an error: Hello.'); - expect(Scheduler).toHaveYielded([ + assertLog([ 'ErrorBoundary componentWillReceiveProps', 'ErrorBoundary componentWillUpdate', 'ErrorBoundary render success', @@ -1363,7 +1367,7 @@ describe('ReactErrorBoundaries', () => { ]); ReactDOM.unmountComponentAtNode(container); - expect(Scheduler).toHaveYielded(['ErrorBoundary componentWillUnmount']); + assertLog(['ErrorBoundary componentWillUnmount']); }); it('catches if child throws in componentWillUpdate during update', () => { @@ -1385,7 +1389,7 @@ describe('ReactErrorBoundaries', () => { container, ); expect(container.textContent).toBe('Caught an error: Hello.'); - expect(Scheduler).toHaveYielded([ + assertLog([ 'ErrorBoundary componentWillReceiveProps', 'ErrorBoundary componentWillUpdate', 'ErrorBoundary render success', @@ -1405,7 +1409,7 @@ describe('ReactErrorBoundaries', () => { ]); ReactDOM.unmountComponentAtNode(container); - expect(Scheduler).toHaveYielded(['ErrorBoundary componentWillUnmount']); + assertLog(['ErrorBoundary componentWillUnmount']); }); it('catches if child throws in render during update', () => { @@ -1427,7 +1431,7 @@ describe('ReactErrorBoundaries', () => { container, ); expect(container.textContent).toBe('Caught an error: Hello.'); - expect(Scheduler).toHaveYielded([ + assertLog([ 'ErrorBoundary componentWillReceiveProps', 'ErrorBoundary componentWillUpdate', 'ErrorBoundary render success', @@ -1451,7 +1455,7 @@ describe('ReactErrorBoundaries', () => { ]); ReactDOM.unmountComponentAtNode(container); - expect(Scheduler).toHaveYielded(['ErrorBoundary componentWillUnmount']); + assertLog(['ErrorBoundary componentWillUnmount']); }); it('keeps refs up-to-date during updates', () => { @@ -1472,7 +1476,7 @@ describe('ReactErrorBoundaries', () => { , container, ); - expect(Scheduler).toHaveYielded([ + assertLog([ 'ErrorBoundary constructor', 'ErrorBoundary componentWillMount', 'ErrorBoundary render success', @@ -1489,7 +1493,7 @@ describe('ReactErrorBoundaries', () => { container, ); expect(container.textContent).toBe('Caught an error: Hello.'); - expect(Scheduler).toHaveYielded([ + assertLog([ 'ErrorBoundary componentWillReceiveProps', 'ErrorBoundary componentWillUpdate', 'ErrorBoundary render success', @@ -1509,7 +1513,7 @@ describe('ReactErrorBoundaries', () => { ]); ReactDOM.unmountComponentAtNode(container); - expect(Scheduler).toHaveYielded([ + assertLog([ 'ErrorBoundary componentWillUnmount', 'Error message ref is set to null', ]); @@ -1534,7 +1538,7 @@ describe('ReactErrorBoundaries', () => { container, ); expect(container.textContent).toBe('Caught an error: Hello.'); - expect(Scheduler).toHaveYielded([ + assertLog([ 'ErrorBoundary componentWillReceiveProps', 'ErrorBoundary componentWillUpdate', 'ErrorBoundary render success', @@ -1567,7 +1571,7 @@ describe('ReactErrorBoundaries', () => { ]); ReactDOM.unmountComponentAtNode(container); - expect(Scheduler).toHaveYielded(['ErrorBoundary componentWillUnmount']); + assertLog(['ErrorBoundary componentWillUnmount']); }); it('recovers from nested componentWillUnmount errors on update', () => { @@ -1592,7 +1596,7 @@ describe('ReactErrorBoundaries', () => { container, ); expect(container.textContent).toBe('Caught an error: Hello.'); - expect(Scheduler).toHaveYielded([ + assertLog([ 'ErrorBoundary componentWillReceiveProps', 'ErrorBoundary componentWillUpdate', 'ErrorBoundary render success', @@ -1626,7 +1630,7 @@ describe('ReactErrorBoundaries', () => { ]); ReactDOM.unmountComponentAtNode(container); - expect(Scheduler).toHaveYielded(['ErrorBoundary componentWillUnmount']); + assertLog(['ErrorBoundary componentWillUnmount']); }); it('picks the right boundary when handling unmounting errors', () => { @@ -1664,7 +1668,7 @@ describe('ReactErrorBoundaries', () => { container, ); expect(container.textContent).toBe('Caught an inner error: Hello.'); - expect(Scheduler).toHaveYielded([ + assertLog([ // Update outer boundary 'OuterErrorBoundary componentWillReceiveProps', 'OuterErrorBoundary componentWillUpdate', @@ -1689,7 +1693,7 @@ describe('ReactErrorBoundaries', () => { ]); ReactDOM.unmountComponentAtNode(container); - expect(Scheduler).toHaveYielded([ + assertLog([ 'OuterErrorBoundary componentWillUnmount', 'InnerErrorBoundary componentWillUnmount', ]); @@ -1722,7 +1726,7 @@ describe('ReactErrorBoundaries', () => { container, ); expect(container.textContent).not.toContain('Caught an error'); - expect(Scheduler).toHaveYielded([ + assertLog([ 'ErrorBoundary componentWillReceiveProps', 'ErrorBoundary componentWillUpdate', 'ErrorBoundary render success', @@ -1736,7 +1740,7 @@ describe('ReactErrorBoundaries', () => { ]); ReactDOM.unmountComponentAtNode(container); - expect(Scheduler).toHaveYielded([ + assertLog([ 'ErrorBoundary componentWillUnmount', 'Normal componentWillUnmount', ]); @@ -1782,7 +1786,7 @@ describe('ReactErrorBoundaries', () => { Scheduler.unstable_clearYields(); ReactDOM.unmountComponentAtNode(container); - expect(Scheduler).toHaveYielded(['ErrorBoundary componentWillUnmount']); + assertLog(['ErrorBoundary componentWillUnmount']); }); it("doesn't get into inconsistent state during additions", () => { @@ -1800,7 +1804,7 @@ describe('ReactErrorBoundaries', () => { Scheduler.unstable_clearYields(); ReactDOM.unmountComponentAtNode(container); - expect(Scheduler).toHaveYielded(['ErrorBoundary componentWillUnmount']); + assertLog(['ErrorBoundary componentWillUnmount']); }); it("doesn't get into inconsistent state during reorders", () => { @@ -1848,7 +1852,7 @@ describe('ReactErrorBoundaries', () => { Scheduler.unstable_clearYields(); ReactDOM.unmountComponentAtNode(container); - expect(Scheduler).toHaveYielded(['ErrorBoundary componentWillUnmount']); + assertLog(['ErrorBoundary componentWillUnmount']); }); it('catches errors originating downstream', () => { @@ -1880,7 +1884,7 @@ describe('ReactErrorBoundaries', () => { statefulInst.forceUpdate(); }).not.toThrow(); - expect(Scheduler).toHaveYielded([ + assertLog([ 'Stateful render [!]', 'ErrorBoundary static getDerivedStateFromError', 'ErrorBoundary componentWillUpdate', @@ -1889,7 +1893,7 @@ describe('ReactErrorBoundaries', () => { ]); ReactDOM.unmountComponentAtNode(container); - expect(Scheduler).toHaveYielded(['ErrorBoundary componentWillUnmount']); + assertLog(['ErrorBoundary componentWillUnmount']); }); it('catches errors in componentDidMount', () => { @@ -1904,7 +1908,7 @@ describe('ReactErrorBoundaries', () => { , container, ); - expect(Scheduler).toHaveYielded([ + assertLog([ 'ErrorBoundary constructor', 'ErrorBoundary componentWillMount', 'ErrorBoundary render success', @@ -1947,7 +1951,7 @@ describe('ReactErrorBoundaries', () => { ]); ReactDOM.unmountComponentAtNode(container); - expect(Scheduler).toHaveYielded(['ErrorBoundary componentWillUnmount']); + assertLog(['ErrorBoundary componentWillUnmount']); }); it('catches errors in componentDidUpdate', () => { @@ -1966,7 +1970,7 @@ describe('ReactErrorBoundaries', () => { , container, ); - expect(Scheduler).toHaveYielded([ + assertLog([ 'ErrorBoundary componentWillReceiveProps', 'ErrorBoundary componentWillUpdate', 'ErrorBoundary render success', @@ -1985,7 +1989,7 @@ describe('ReactErrorBoundaries', () => { ]); ReactDOM.unmountComponentAtNode(container); - expect(Scheduler).toHaveYielded(['ErrorBoundary componentWillUnmount']); + assertLog(['ErrorBoundary componentWillUnmount']); }); it('catches errors in useEffect', () => { @@ -1997,7 +2001,7 @@ describe('ReactErrorBoundaries', () => { , container, ); - expect(Scheduler).toHaveYielded([ + assertLog([ 'ErrorBoundary constructor', 'ErrorBoundary componentWillMount', 'ErrorBoundary render success', @@ -2010,7 +2014,7 @@ describe('ReactErrorBoundaries', () => { }); // verify flushed passive effects and handle the error - expect(Scheduler).toHaveYielded([ + assertLog([ 'BrokenUseEffect useEffect [!]', // Handle the error 'ErrorBoundary static getDerivedStateFromError', @@ -2030,7 +2034,7 @@ describe('ReactErrorBoundaries', () => { , container, ); - expect(Scheduler).toHaveYielded([ + assertLog([ 'ErrorBoundary constructor', 'ErrorBoundary componentWillMount', 'ErrorBoundary render success', @@ -2062,7 +2066,7 @@ describe('ReactErrorBoundaries', () => { container, ); expect(container.firstChild.textContent).toBe('Caught an error: Hello.'); - expect(Scheduler).toHaveYielded([ + assertLog([ 'ErrorBoundary constructor', 'ErrorBoundary componentWillMount', 'ErrorBoundary render success', @@ -2082,7 +2086,7 @@ describe('ReactErrorBoundaries', () => { ]); ReactDOM.unmountComponentAtNode(container); - expect(Scheduler).toHaveYielded(['ErrorBoundary componentWillUnmount']); + assertLog(['ErrorBoundary componentWillUnmount']); }); it('calls static getDerivedStateFromError for each error that is captured', () => { @@ -2132,7 +2136,7 @@ describe('ReactErrorBoundaries', () => { expect(container.firstChild.textContent).toBe( 'Caught an unmounting error: E2.' + 'Caught an updating error: E4.', ); - expect(Scheduler).toHaveYielded([ + assertLog([ // Begin update phase 'OuterErrorBoundary componentWillReceiveProps', 'OuterErrorBoundary componentWillUpdate', @@ -2178,7 +2182,7 @@ describe('ReactErrorBoundaries', () => { ]); ReactDOM.unmountComponentAtNode(container); - expect(Scheduler).toHaveYielded([ + assertLog([ 'OuterErrorBoundary componentWillUnmount', 'InnerUnmountBoundary componentWillUnmount', 'InnerUpdateBoundary componentWillUnmount', @@ -2230,7 +2234,7 @@ describe('ReactErrorBoundaries', () => { ), ).toThrow('Hello'); expect(container.innerHTML).toBe(''); - expect(Scheduler).toHaveYielded([ + assertLog([ 'NoopErrorBoundary constructor', 'NoopErrorBoundary componentWillMount', 'NoopErrorBoundary render', @@ -2538,7 +2542,7 @@ describe('ReactErrorBoundaries', () => { expect(container.firstChild.textContent).toBe('sibling'); expect(container.lastChild.textContent).toBe('broken'); - expect(Scheduler).toHaveYielded([ + assertLog([ 'OuterBoundary render success', 'Component render sibling', 'InnerBoundary render success', @@ -2555,7 +2559,7 @@ describe('ReactErrorBoundaries', () => { // React should skip over the unmounting boundary and find the nearest still-mounted boundary. expect(container.firstChild.textContent).toBe('OuterFallback'); expect(container.lastChild.textContent).toBe('OuterFallback'); - expect(Scheduler).toHaveYielded([ + assertLog([ 'OuterBoundary render success', 'Component render sibling', 'BrokenComponentWillUnmount componentWillUnmount', @@ -2622,7 +2626,7 @@ describe('ReactErrorBoundaries', () => { expect(container.firstChild.textContent).toBe('sibling'); expect(container.lastChild.textContent).toBe('ref'); - expect(Scheduler).toHaveYielded([ + assertLog([ 'OuterBoundary render success', 'Component render sibling', 'InnerBoundary render success', @@ -2640,7 +2644,7 @@ describe('ReactErrorBoundaries', () => { // React should skip over the unmounting boundary and find the nearest still-mounted boundary. expect(container.firstChild.textContent).toBe('OuterFallback'); expect(container.lastChild.textContent).toBe('OuterFallback'); - expect(Scheduler).toHaveYielded([ + assertLog([ 'OuterBoundary render success', 'Component render sibling', 'LocalBrokenCallbackRef ref false', diff --git a/packages/react-dom/src/__tests__/ReactServerRenderingHydration-test.js b/packages/react-dom/src/__tests__/ReactServerRenderingHydration-test.js index 4dabcee403333..03e53fc21ae63 100644 --- a/packages/react-dom/src/__tests__/ReactServerRenderingHydration-test.js +++ b/packages/react-dom/src/__tests__/ReactServerRenderingHydration-test.js @@ -16,6 +16,7 @@ let ReactDOMClient; let ReactDOMServer; let ReactDOMServerBrowser; let Scheduler; +let waitForAll; // These tests rely both on ReactDOMServer and ReactDOM. // If a test only needs ReactDOMServer, put it in ReactServerRendering-test instead. @@ -28,6 +29,9 @@ describe('ReactDOMServerHydration', () => { ReactDOMServer = require('react-dom/server'); ReactDOMServerBrowser = require('react-dom/server.browser'); Scheduler = require('scheduler'); + + const InternalTestUtils = require('internal-test-utils'); + waitForAll = InternalTestUtils.waitForAll; }); it('should have the correct mounting behavior (new hydrate API)', () => { @@ -604,7 +608,7 @@ describe('ReactDOMServerHydration', () => { expect(customElement.obj).toBe(undefined); }); - it('refers users to apis that support Suspense when something suspends', () => { + it('refers users to apis that support Suspense when something suspends', async () => { const theInfinitePromise = new Promise(() => {}); function InfiniteSuspend() { throw theInfinitePromise; @@ -631,7 +635,7 @@ describe('ReactDOMServerHydration', () => { }, }); - expect(Scheduler).toFlushAndYield([]); + await waitForAll([]); expect(errors.length).toBe(1); if (__DEV__) { expect(errors[0]).toBe( @@ -649,7 +653,7 @@ describe('ReactDOMServerHydration', () => { } }); - it('refers users to apis that support Suspense when something suspends (browser)', () => { + it('refers users to apis that support Suspense when something suspends (browser)', async () => { const theInfinitePromise = new Promise(() => {}); function InfiniteSuspend() { throw theInfinitePromise; @@ -676,7 +680,7 @@ describe('ReactDOMServerHydration', () => { }, }); - expect(Scheduler).toFlushAndYield([]); + await waitForAll([]); expect(errors.length).toBe(1); if (__DEV__) { expect(errors[0]).toBe( diff --git a/packages/react-dom/src/__tests__/ReactTestUtilsAct-test.js b/packages/react-dom/src/__tests__/ReactTestUtilsAct-test.js index 3cd2e5712fb8d..81a9f4570b7f3 100644 --- a/packages/react-dom/src/__tests__/ReactTestUtilsAct-test.js +++ b/packages/react-dom/src/__tests__/ReactTestUtilsAct-test.js @@ -14,6 +14,7 @@ let ReactTestUtils; let Scheduler; let act; let container; +let assertLog; jest.useRealTimers(); @@ -122,6 +123,10 @@ function runActTests(label, render, unmount, rerender) { ReactTestUtils = require('react-dom/test-utils'); Scheduler = require('scheduler'); act = ReactTestUtils.act; + + const InternalTestUtils = require('internal-test-utils'); + assertLog = InternalTestUtils.assertLog; + container = document.createElement('div'); document.body.appendChild(container); }); @@ -145,7 +150,7 @@ function runActTests(label, render, unmount, rerender) { render(, container); }); - expect(Scheduler).toHaveYielded([100]); + assertLog([100]); }); // @gate __DEV__ @@ -165,7 +170,7 @@ function runActTests(label, render, unmount, rerender) { act(() => { render(, container); }); - expect(Scheduler).toHaveYielded([0]); + assertLog([0]); const button = container.querySelector('#button'); function click() { button.dispatchEvent(new MouseEvent('click', {bubbles: true})); @@ -177,11 +182,11 @@ function runActTests(label, render, unmount, rerender) { click(); }); // it consolidates the 3 updates, then fires the effect - expect(Scheduler).toHaveYielded([3]); + assertLog([3]); await act(async () => click()); - expect(Scheduler).toHaveYielded([4]); + assertLog([4]); await act(async () => click()); - expect(Scheduler).toHaveYielded([5]); + assertLog([5]); expect(button.innerHTML).toBe('5'); }); @@ -219,10 +224,10 @@ function runActTests(label, render, unmount, rerender) { }); // the effect wouldn't have yielded yet because // we're still inside an act() scope - expect(Scheduler).toHaveYielded([]); + assertLog([]); }); // but after exiting the last one, effects get flushed - expect(Scheduler).toHaveYielded([0]); + assertLog([0]); }); // @gate __DEV__ @@ -551,7 +556,7 @@ function runActTests(label, render, unmount, rerender) { }); // exiting act() drains effects and microtasks - expect(Scheduler).toHaveYielded([0, 1]); + assertLog([0, 1]); expect(container.innerHTML).toBe('1'); }); @@ -576,7 +581,7 @@ function runActTests(label, render, unmount, rerender) { render(, container); }); // all 5 ticks present and accounted for - expect(Scheduler).toHaveYielded([0, 1, 2, 3, 4]); + assertLog([0, 1, 2, 3, 4]); expect(container.innerHTML).toBe('5'); }); }); @@ -657,7 +662,7 @@ function runActTests(label, render, unmount, rerender) { act(() => { render(, container); }); - expect(Scheduler).toHaveYielded(['oh yes']); + assertLog(['oh yes']); } }); @@ -688,7 +693,7 @@ function runActTests(label, render, unmount, rerender) { await act(async () => { render(, container); }); - expect(Scheduler).toHaveYielded(['oh yes']); + assertLog(['oh yes']); } }); }); diff --git a/packages/react-dom/src/__tests__/ReactUpdates-test.js b/packages/react-dom/src/__tests__/ReactUpdates-test.js index 9da24ed44f0dc..87913c63df8dd 100644 --- a/packages/react-dom/src/__tests__/ReactUpdates-test.js +++ b/packages/react-dom/src/__tests__/ReactUpdates-test.js @@ -15,6 +15,9 @@ let ReactDOMClient; let ReactTestUtils; let act; let Scheduler; +let waitForAll; +let waitFor; +let assertLog; describe('ReactUpdates', () => { beforeEach(() => { @@ -25,6 +28,11 @@ describe('ReactUpdates', () => { ReactTestUtils = require('react-dom/test-utils'); act = require('jest-react').act; Scheduler = require('scheduler'); + + const InternalTestUtils = require('internal-test-utils'); + waitForAll = InternalTestUtils.waitForAll; + waitFor = InternalTestUtils.waitFor; + assertLog = InternalTestUtils.assertLog; }); // Note: This is based on a similar component we use in www. We can delete @@ -1319,7 +1327,7 @@ describe('ReactUpdates', () => { }); // @gate www - it('delays sync updates inside hidden subtrees in Concurrent Mode', () => { + it('delays sync updates inside hidden subtrees in Concurrent Mode', async () => { const container = document.createElement('div'); function Baz() { @@ -1352,14 +1360,14 @@ describe('ReactUpdates', () => { const root = ReactDOMClient.createRoot(container); let hiddenDiv; - act(() => { + await act(async () => { root.render(); - expect(Scheduler).toFlushAndYieldThrough(['Foo', 'Baz', 'Foo#effect']); + await waitFor(['Foo', 'Baz', 'Foo#effect']); hiddenDiv = container.firstChild.firstChild; expect(hiddenDiv.hidden).toBe(true); expect(hiddenDiv.innerHTML).toBe(''); // Run offscreen update - expect(Scheduler).toFlushAndYield(['Bar']); + await waitForAll(['Bar']); expect(hiddenDiv.hidden).toBe(true); expect(hiddenDiv.innerHTML).toBe('

bar 0

'); }); @@ -1371,7 +1379,7 @@ describe('ReactUpdates', () => { expect(hiddenDiv.innerHTML).toBe('

bar 0

'); // Run offscreen update - expect(Scheduler).toFlushAndYield(['Bar']); + await waitForAll(['Bar']); expect(hiddenDiv.innerHTML).toBe('

bar 1

'); }); @@ -1699,7 +1707,7 @@ describe('ReactUpdates', () => { ReactDOM.render(, container); }); - expect(Scheduler).toHaveYielded(['Done']); + assertLog(['Done']); expect(container.textContent).toBe('1000'); }); } diff --git a/packages/react-dom/src/__tests__/ReactWrongReturnPointer-test.js b/packages/react-dom/src/__tests__/ReactWrongReturnPointer-test.js index d5f6f2fc5db1f..126095bb120c8 100644 --- a/packages/react-dom/src/__tests__/ReactWrongReturnPointer-test.js +++ b/packages/react-dom/src/__tests__/ReactWrongReturnPointer-test.js @@ -16,6 +16,7 @@ let SuspenseList; let getCacheForType; let caches; let seededCache; +let assertLog; beforeEach(() => { React = require('react'); @@ -23,6 +24,9 @@ beforeEach(() => { Scheduler = require('scheduler'); act = require('jest-react').act; + const InternalTestUtils = require('internal-test-utils'); + assertLog = InternalTestUtils.assertLog; + Suspense = React.Suspense; if (gate(flags => flags.enableSuspenseList)) { SuspenseList = React.SuspenseList; @@ -188,19 +192,15 @@ test('regression (#20932): return pointer is correct before entering deleted tre await act(async () => { root.render(); }); - expect(Scheduler).toHaveYielded([ - 'Suspend! [0]', - 'Loading Async...', - 'Loading Tail...', - ]); + assertLog(['Suspend! [0]', 'Loading Async...', 'Loading Tail...']); await act(async () => { resolveText(0); }); - expect(Scheduler).toHaveYielded([0, 'Tail']); + assertLog([0, 'Tail']); await act(async () => { setAsyncText(x => x + 1); }); - expect(Scheduler).toHaveYielded([ + assertLog([ 'Suspend! [1]', 'Loading Async...', 'Suspend! [1]', diff --git a/packages/react-dom/src/events/__tests__/DOMPluginEventSystem-test.internal.js b/packages/react-dom/src/events/__tests__/DOMPluginEventSystem-test.internal.js index 6202eec7f3574..8a894dcc4e1e1 100644 --- a/packages/react-dom/src/events/__tests__/DOMPluginEventSystem-test.internal.js +++ b/packages/react-dom/src/events/__tests__/DOMPluginEventSystem-test.internal.js @@ -18,6 +18,8 @@ let ReactDOMClient; let ReactDOMServer; let Scheduler; let act; +let waitForAll; +let waitFor; function dispatchEvent(element, type) { const event = document.createEvent('Event'); @@ -79,6 +81,11 @@ describe('DOMPluginEventSystem', () => { Scheduler = require('scheduler'); ReactDOMServer = require('react-dom/server'); act = require('jest-react').act; + + const InternalTestUtils = require('internal-test-utils'); + waitForAll = InternalTestUtils.waitForAll; + waitFor = InternalTestUtils.waitFor; + container = document.createElement('div'); document.body.appendChild(container); startNativeEventListenerClearDown(); @@ -1273,6 +1280,10 @@ describe('DOMPluginEventSystem', () => { Scheduler = require('scheduler'); ReactDOMServer = require('react-dom/server'); act = require('jest-react').act; + + const InternalTestUtils = require('internal-test-utils'); + waitForAll = InternalTestUtils.waitForAll; + waitFor = InternalTestUtils.waitFor; }); // @gate www @@ -1955,7 +1966,7 @@ describe('DOMPluginEventSystem', () => { const root = ReactDOMClient.createRoot(container); root.render(); - expect(Scheduler).toFlushAndYield(['Test']); + await waitForAll(['Test']); // Click the button dispatchClickEvent(ref.current); @@ -1969,7 +1980,7 @@ describe('DOMPluginEventSystem', () => { root.render(); }); // Yield before committing - expect(Scheduler).toFlushAndYieldThrough(['Test']); + await waitFor(['Test']); // Click the button again dispatchClickEvent(ref.current); @@ -1979,7 +1990,7 @@ describe('DOMPluginEventSystem', () => { log.length = 0; // Commit - expect(Scheduler).toFlushAndYield([]); + await waitForAll([]); dispatchClickEvent(ref.current); expect(log).toEqual([{counter: 1}]); }); diff --git a/packages/react-dom/src/events/plugins/__tests__/ChangeEventPlugin-test.js b/packages/react-dom/src/events/plugins/__tests__/ChangeEventPlugin-test.js index 51fb21b696415..4255f13f8ad78 100644 --- a/packages/react-dom/src/events/plugins/__tests__/ChangeEventPlugin-test.js +++ b/packages/react-dom/src/events/plugins/__tests__/ChangeEventPlugin-test.js @@ -15,6 +15,8 @@ let ReactDOMClient; let ReactFeatureFlags; let Scheduler; let act; +let waitForAll; +let assertLog; const setUntrackedChecked = Object.getOwnPropertyDescriptor( HTMLInputElement.prototype, @@ -60,6 +62,11 @@ describe('ChangeEventPlugin', () => { ReactDOMClient = require('react-dom/client'); act = require('jest-react').act; Scheduler = require('scheduler'); + + const InternalTestUtils = require('internal-test-utils'); + waitForAll = InternalTestUtils.waitForAll; + assertLog = InternalTestUtils.assertLog; + container = document.createElement('div'); document.body.appendChild(container); }); @@ -497,7 +504,7 @@ describe('ChangeEventPlugin', () => { }); describe('concurrent mode', () => { - it('text input', () => { + it('text input', async () => { const root = ReactDOMClient.createRoot(container); let input; @@ -522,10 +529,10 @@ describe('ChangeEventPlugin', () => { // Initial mount. Test that this is async. root.render(); // Should not have flushed yet. - expect(Scheduler).toHaveYielded([]); + assertLog([]); expect(input).toBe(undefined); // Flush callbacks. - expect(Scheduler).toFlushAndYield(['render: initial']); + await waitForAll(['render: initial']); expect(input.value).toBe('initial'); // Trigger a change event. @@ -534,12 +541,12 @@ describe('ChangeEventPlugin', () => { new Event('input', {bubbles: true, cancelable: true}), ); // Change should synchronously flush - expect(Scheduler).toHaveYielded(['render: changed']); + assertLog(['render: changed']); // Value should be the controlled value, not the original one expect(input.value).toBe('changed [!]'); }); - it('checkbox input', () => { + it('checkbox input', async () => { const root = ReactDOMClient.createRoot(container); let input; @@ -567,10 +574,10 @@ describe('ChangeEventPlugin', () => { // Initial mount. Test that this is async. root.render(); // Should not have flushed yet. - expect(Scheduler).toHaveYielded([]); + assertLog([]); expect(input).toBe(undefined); // Flush callbacks. - expect(Scheduler).toFlushAndYield(['render: false']); + await waitForAll(['render: false']); expect(input.checked).toBe(false); // Trigger a change event. @@ -578,23 +585,23 @@ describe('ChangeEventPlugin', () => { new MouseEvent('click', {bubbles: true, cancelable: true}), ); // Change should synchronously flush - expect(Scheduler).toHaveYielded(['render: true']); + assertLog(['render: true']); expect(input.checked).toBe(true); // Now let's make sure we're using the controlled value. root.render(); - expect(Scheduler).toFlushAndYield(['render: true']); + await waitForAll(['render: true']); // Trigger another change event. input.dispatchEvent( new MouseEvent('click', {bubbles: true, cancelable: true}), ); // Change should synchronously flush - expect(Scheduler).toHaveYielded(['render: true']); + assertLog(['render: true']); expect(input.checked).toBe(false); }); - it('textarea', () => { + it('textarea', async () => { const root = ReactDOMClient.createRoot(container); let textarea; @@ -619,10 +626,10 @@ describe('ChangeEventPlugin', () => { // Initial mount. Test that this is async. root.render(); // Should not have flushed yet. - expect(Scheduler).toHaveYielded([]); + assertLog([]); expect(textarea).toBe(undefined); // Flush callbacks. - expect(Scheduler).toFlushAndYield(['render: initial']); + await waitForAll(['render: initial']); expect(textarea.value).toBe('initial'); // Trigger a change event. @@ -631,12 +638,12 @@ describe('ChangeEventPlugin', () => { new Event('input', {bubbles: true, cancelable: true}), ); // Change should synchronously flush - expect(Scheduler).toHaveYielded(['render: changed']); + assertLog(['render: changed']); // Value should be the controlled value, not the original one expect(textarea.value).toBe('changed [!]'); }); - it('parent of input', () => { + it('parent of input', async () => { const root = ReactDOMClient.createRoot(container); let input; @@ -665,10 +672,10 @@ describe('ChangeEventPlugin', () => { // Initial mount. Test that this is async. root.render(); // Should not have flushed yet. - expect(Scheduler).toHaveYielded([]); + assertLog([]); expect(input).toBe(undefined); // Flush callbacks. - expect(Scheduler).toFlushAndYield(['render: initial']); + await waitForAll(['render: initial']); expect(input.value).toBe('initial'); // Trigger a change event. @@ -677,7 +684,7 @@ describe('ChangeEventPlugin', () => { new Event('input', {bubbles: true, cancelable: true}), ); // Change should synchronously flush - expect(Scheduler).toHaveYielded(['render: changed']); + assertLog(['render: changed']); // Value should be the controlled value, not the original one expect(input.value).toBe('changed [!]'); }); @@ -711,10 +718,10 @@ describe('ChangeEventPlugin', () => { // Initial mount. Test that this is async. root.render(); // Should not have flushed yet. - expect(Scheduler).toHaveYielded([]); + assertLog([]); expect(input).toBe(undefined); // Flush callbacks. - expect(Scheduler).toFlushAndYield(['render: initial']); + await waitForAll(['render: initial']); expect(input.value).toBe('initial'); // Trigger a click event @@ -724,7 +731,7 @@ describe('ChangeEventPlugin', () => { // Flush microtask queue. await null; - expect(Scheduler).toHaveYielded(['render: ']); + assertLog(['render: ']); expect(input.value).toBe(''); });