diff --git a/packages/react-reconciler/src/__tests__/DebugTracing-test.internal.js b/packages/react-reconciler/src/__tests__/DebugTracing-test.internal.js index 5c84c79b7b1d8..a5b20ada90283 100644 --- a/packages/react-reconciler/src/__tests__/DebugTracing-test.internal.js +++ b/packages/react-reconciler/src/__tests__/DebugTracing-test.internal.js @@ -12,7 +12,7 @@ describe('DebugTracing', () => { let React; let ReactTestRenderer; - let Scheduler; + let waitForPaint; let logs; @@ -27,7 +27,8 @@ describe('DebugTracing', () => { React = require('react'); ReactTestRenderer = require('react-test-renderer'); - Scheduler = require('scheduler'); + const InternalTestUtils = require('internal-test-utils'); + waitForPaint = InternalTestUtils.waitForPaint; logs = []; @@ -100,7 +101,7 @@ describe('DebugTracing', () => { }); // @gate experimental && build === 'development' && enableDebugTracing && enableCPUSuspense - it('should log sync render with CPU suspense', () => { + it('should log sync render with CPU suspense', async () => { function Example() { console.log(''); return null; @@ -129,7 +130,7 @@ describe('DebugTracing', () => { logs.splice(0); - expect(Scheduler).toFlushUntilNextPaint([]); + await waitForPaint([]); expect(logs).toEqual([ `group: ⚛️ render (${RETRY_LANE_STRING})`, diff --git a/packages/react-reconciler/src/__tests__/ReactActWarnings-test.js b/packages/react-reconciler/src/__tests__/ReactActWarnings-test.js index 57c290981eb15..15248976ca1bf 100644 --- a/packages/react-reconciler/src/__tests__/ReactActWarnings-test.js +++ b/packages/react-reconciler/src/__tests__/ReactActWarnings-test.js @@ -9,6 +9,8 @@ let React; let Scheduler; +let waitForAll; +let assertLog; let ReactNoop; let useState; let act; @@ -32,6 +34,10 @@ describe('act warnings', () => { startTransition = React.startTransition; getCacheForType = React.unstable_getCacheForType; caches = []; + + const InternalTestUtils = require('internal-test-utils'); + waitForAll = InternalTestUtils.waitForAll; + assertLog = InternalTestUtils.assertLog; }); function createTextCache() { @@ -134,17 +140,17 @@ describe('act warnings', () => { } } - function withActEnvironment(value, scope) { + async function withActEnvironment(value, scope) { const prevValue = global.IS_REACT_ACT_ENVIRONMENT; global.IS_REACT_ACT_ENVIRONMENT = value; try { - return scope(); + return await scope(); } finally { global.IS_REACT_ACT_ENVIRONMENT = prevValue; } } - test('warns about unwrapped updates only if environment flag is enabled', () => { + test('warns about unwrapped updates only if environment flag is enabled', async () => { let setState; function App() { const [state, _setState] = useState(0); @@ -154,34 +160,34 @@ describe('act warnings', () => { const root = ReactNoop.createRoot(); root.render(); - expect(Scheduler).toFlushAndYield([0]); + await waitForAll([0]); expect(root).toMatchRenderedOutput('0'); // Default behavior. Flag is undefined. No warning. expect(global.IS_REACT_ACT_ENVIRONMENT).toBe(undefined); setState(1); - expect(Scheduler).toFlushAndYield([1]); + await waitForAll([1]); expect(root).toMatchRenderedOutput('1'); // Flag is true. Warn. - withActEnvironment(true, () => { + await withActEnvironment(true, async () => { expect(() => setState(2)).toErrorDev( 'An update to App inside a test was not wrapped in act', ); - expect(Scheduler).toFlushAndYield([2]); + await waitForAll([2]); expect(root).toMatchRenderedOutput('2'); }); // Flag is false. No warning. - withActEnvironment(false, () => { + await withActEnvironment(false, async () => { setState(3); - expect(Scheduler).toFlushAndYield([3]); + await waitForAll([3]); expect(root).toMatchRenderedOutput('3'); }); }); // @gate __DEV__ - test('act warns if the environment flag is not enabled', () => { + test('act warns if the environment flag is not enabled', async () => { let setState; function App() { const [state, _setState] = useState(0); @@ -191,7 +197,7 @@ describe('act warnings', () => { const root = ReactNoop.createRoot(); root.render(); - expect(Scheduler).toFlushAndYield([0]); + await waitForAll([0]); expect(root).toMatchRenderedOutput('0'); // Default behavior. Flag is undefined. Warn. @@ -204,20 +210,20 @@ describe('act warnings', () => { 'The current testing environment is not configured to support act(...)', {withoutStack: true}, ); - expect(Scheduler).toHaveYielded([1]); + assertLog([1]); expect(root).toMatchRenderedOutput('1'); // Flag is true. Don't warn. - withActEnvironment(true, () => { + await withActEnvironment(true, () => { act(() => { setState(2); }); - expect(Scheduler).toHaveYielded([2]); + assertLog([2]); expect(root).toMatchRenderedOutput('2'); }); // Flag is false. Warn. - withActEnvironment(false, () => { + await withActEnvironment(false, () => { expect(() => { act(() => { setState(1); @@ -226,13 +232,13 @@ describe('act warnings', () => { 'The current testing environment is not configured to support act(...)', {withoutStack: true}, ); - expect(Scheduler).toHaveYielded([1]); + assertLog([1]); expect(root).toMatchRenderedOutput('1'); }); }); - test('warns if root update is not wrapped', () => { - withActEnvironment(true, () => { + test('warns if root update is not wrapped', async () => { + await withActEnvironment(true, () => { const root = ReactNoop.createRoot(); expect(() => root.render('Hi')).toErrorDev( // TODO: Better error message that doesn't make it look like "Root" is @@ -244,7 +250,7 @@ describe('act warnings', () => { }); // @gate __DEV__ - test('warns if class update is not wrapped', () => { + test('warns if class update is not wrapped', async () => { let app; class App extends React.Component { state = {count: 0}; @@ -254,7 +260,7 @@ describe('act warnings', () => { } } - withActEnvironment(true, () => { + await withActEnvironment(true, () => { const root = ReactNoop.createRoot(); act(() => { root.render(); @@ -266,7 +272,7 @@ describe('act warnings', () => { }); // @gate __DEV__ - test('warns even if update is synchronous', () => { + test('warns even if update is synchronous', async () => { let setState; function App() { const [state, _setState] = useState(0); @@ -274,10 +280,10 @@ describe('act warnings', () => { return ; } - withActEnvironment(true, () => { + await withActEnvironment(true, () => { const root = ReactNoop.createRoot(); act(() => root.render()); - expect(Scheduler).toHaveYielded([0]); + assertLog([0]); expect(root).toMatchRenderedOutput('0'); // Even though this update is synchronous, we should still fire a warning, @@ -286,14 +292,14 @@ describe('act warnings', () => { 'An update to App inside a test was not wrapped in act(...)', ); - expect(Scheduler).toHaveYielded([1]); + assertLog([1]); expect(root).toMatchRenderedOutput('1'); }); }); // @gate __DEV__ // @gate enableLegacyCache - test('warns if Suspense retry is not wrapped', () => { + test('warns if Suspense retry is not wrapped', async () => { function App() { return ( }> @@ -302,12 +308,12 @@ describe('act warnings', () => { ); } - withActEnvironment(true, () => { + await withActEnvironment(true, () => { const root = ReactNoop.createRoot(); act(() => { root.render(); }); - expect(Scheduler).toHaveYielded(['Suspend! [Async]', 'Loading...']); + assertLog(['Suspend! [Async]', 'Loading...']); expect(root).toMatchRenderedOutput('Loading...'); // This is a retry, not a ping, because we already showed a fallback. @@ -321,7 +327,7 @@ describe('act warnings', () => { // @gate __DEV__ // @gate enableLegacyCache - test('warns if Suspense ping is not wrapped', () => { + test('warns if Suspense ping is not wrapped', async () => { function App({showMore}) { return ( }> @@ -330,12 +336,12 @@ describe('act warnings', () => { ); } - withActEnvironment(true, () => { + await withActEnvironment(true, () => { const root = ReactNoop.createRoot(); act(() => { root.render(); }); - expect(Scheduler).toHaveYielded(['(empty)']); + assertLog(['(empty)']); expect(root).toMatchRenderedOutput('(empty)'); act(() => { @@ -343,7 +349,7 @@ describe('act warnings', () => { root.render(); }); }); - expect(Scheduler).toHaveYielded(['Suspend! [Async]', 'Loading...']); + assertLog(['Suspend! [Async]', 'Loading...']); expect(root).toMatchRenderedOutput('(empty)'); // This is a ping, not a retry, because no fallback is showing. diff --git a/packages/react-reconciler/src/__tests__/ReactBatching-test.internal.js b/packages/react-reconciler/src/__tests__/ReactBatching-test.internal.js index f9938a79b53d9..55c3b2162a57c 100644 --- a/packages/react-reconciler/src/__tests__/ReactBatching-test.internal.js +++ b/packages/react-reconciler/src/__tests__/ReactBatching-test.internal.js @@ -2,6 +2,8 @@ let React; let ReactFeatureFlags; let ReactNoop; let Scheduler; +let waitForAll; +let assertLog; let ReactCache; let Suspense; let TextResource; @@ -18,6 +20,10 @@ describe('ReactBlockingMode', () => { ReactCache = require('react-cache'); Suspense = React.Suspense; + const InternalTestUtils = require('internal-test-utils'); + waitForAll = InternalTestUtils.waitForAll; + assertLog = InternalTestUtils.assertLog; + TextResource = ReactCache.unstable_createResource( ([text, ms = 0]) => { return new Promise((resolve, reject) => @@ -52,7 +58,7 @@ describe('ReactBlockingMode', () => { } } - it('updates flush without yielding in the next event', () => { + it('updates flush without yielding in the next event', async () => { const root = ReactNoop.createRoot(); root.render( @@ -66,12 +72,11 @@ describe('ReactBlockingMode', () => { // Nothing should have rendered yet expect(root).toMatchRenderedOutput(null); - // Everything should render immediately in the next event - expect(Scheduler).toFlushAndYield(['A', 'B', 'C']); + await waitForAll(['A', 'B', 'C']); expect(root).toMatchRenderedOutput('ABC'); }); - it('layout updates flush synchronously in same event', () => { + it('layout updates flush synchronously in same event', async () => { const {useLayoutEffect} = React; function App() { @@ -84,9 +89,9 @@ describe('ReactBlockingMode', () => { const root = ReactNoop.createRoot(); root.render(); expect(root).toMatchRenderedOutput(null); - expect(Scheduler).toHaveYielded([]); + assertLog([]); - expect(Scheduler).toFlushAndYield(['Hi', 'Layout effect']); + await waitForAll(['Hi', 'Layout effect']); expect(root).toMatchRenderedOutput('Hi'); }); @@ -106,15 +111,15 @@ describe('ReactBlockingMode', () => { , ); - expect(Scheduler).toFlushAndYield(['A', 'Suspend! [B]', 'C', 'Loading...']); + await waitForAll(['A', 'Suspend! [B]', 'C', 'Loading...']); // In Legacy Mode, A and B would mount in a hidden primary tree. In // Concurrent Mode, nothing in the primary tree should mount. But the // fallback should mount immediately. expect(root).toMatchRenderedOutput('Loading...'); await jest.advanceTimersByTime(1000); - expect(Scheduler).toHaveYielded(['Promise resolved [B]']); - expect(Scheduler).toFlushAndYield(['A', 'B', 'C']); + assertLog(['Promise resolved [B]']); + await waitForAll(['A', 'B', 'C']); expect(root).toMatchRenderedOutput( <> A @@ -124,7 +129,7 @@ describe('ReactBlockingMode', () => { ); }); - it('flushSync does not flush batched work', () => { + it('flushSync does not flush batched work', async () => { const {useState, forwardRef, useImperativeHandle} = React; const root = ReactNoop.createRoot(); @@ -143,8 +148,7 @@ describe('ReactBlockingMode', () => { , ); - // Mount - expect(Scheduler).toFlushAndYield(['A0', 'B0']); + await waitForAll(['A0', 'B0']); expect(root).toMatchRenderedOutput('A0B0'); // Schedule a batched update to the first sibling @@ -159,15 +163,13 @@ describe('ReactBlockingMode', () => { // Now flush the first update if (gate(flags => flags.enableUnifiedSyncLane)) { - expect(Scheduler).toHaveYielded(['A1', 'B1']); + assertLog(['A1', 'B1']); expect(root).toMatchRenderedOutput('A1B1'); } else { - // Only the second update should have flushed synchronously - expect(Scheduler).toHaveYielded(['B1']); + assertLog(['B1']); expect(root).toMatchRenderedOutput('A0B1'); - // Now flush the first update - expect(Scheduler).toFlushAndYield(['A1']); + await waitForAll(['A1']); expect(root).toMatchRenderedOutput('A1B1'); } }); diff --git a/packages/react-reconciler/src/__tests__/ReactCPUSuspense-test.js b/packages/react-reconciler/src/__tests__/ReactCPUSuspense-test.js index 0130fa87d6180..ec6d9d619078f 100644 --- a/packages/react-reconciler/src/__tests__/ReactCPUSuspense-test.js +++ b/packages/react-reconciler/src/__tests__/ReactCPUSuspense-test.js @@ -10,6 +10,9 @@ let readText; let resolveText; // let rejectText; +let assertLog; +let waitForPaint; + describe('ReactSuspenseWithNoopRenderer', () => { beforeEach(() => { jest.resetModules(); @@ -21,6 +24,10 @@ describe('ReactSuspenseWithNoopRenderer', () => { Suspense = React.Suspense; useState = React.useState; + const InternalTestUtils = require('internal-test-utils'); + assertLog = InternalTestUtils.assertLog; + waitForPaint = InternalTestUtils.waitForPaint; + textCache = new Map(); readText = text => { @@ -128,7 +135,7 @@ describe('ReactSuspenseWithNoopRenderer', () => { const root = ReactNoop.createRoot(); await act(async () => { root.render(); - expect(Scheduler).toFlushUntilNextPaint(['Outer', 'Loading...']); + await waitForPaint(['Outer', 'Loading...']); expect(root).toMatchRenderedOutput( <> Outer @@ -136,8 +143,7 @@ describe('ReactSuspenseWithNoopRenderer', () => { , ); }); - // Inner contents finish in separate commit from outer - expect(Scheduler).toHaveYielded(['Inner']); + assertLog(['Inner']); expect(root).toMatchRenderedOutput( <> Outer @@ -172,8 +178,7 @@ describe('ReactSuspenseWithNoopRenderer', () => { await act(async () => { root.render(); }); - // Inner contents finish in separate commit from outer - expect(Scheduler).toHaveYielded(['Outer', 'Loading...', 'Inner [0]']); + assertLog(['Outer', 'Loading...', 'Inner [0]']); expect(root).toMatchRenderedOutput( <> Outer @@ -185,8 +190,7 @@ describe('ReactSuspenseWithNoopRenderer', () => { await act(async () => { setCount(1); }); - // Entire update finishes in a single commit - expect(Scheduler).toHaveYielded(['Outer', 'Inner [1]']); + assertLog(['Outer', 'Inner [1]']); expect(root).toMatchRenderedOutput( <> Outer @@ -215,7 +219,7 @@ describe('ReactSuspenseWithNoopRenderer', () => { const root = ReactNoop.createRoot(); await act(async () => { root.render(); - expect(Scheduler).toFlushUntilNextPaint(['Outer', 'Loading...']); + await waitForPaint(['Outer', 'Loading...']); expect(root).toMatchRenderedOutput( <> Outer @@ -223,8 +227,7 @@ describe('ReactSuspenseWithNoopRenderer', () => { , ); }); - // Inner contents suspended, so we continue showing a fallback. - expect(Scheduler).toHaveYielded(['Suspend! [Inner]']); + assertLog(['Suspend! [Inner]']); expect(root).toMatchRenderedOutput( <> Outer @@ -236,7 +239,7 @@ describe('ReactSuspenseWithNoopRenderer', () => { await act(async () => { await resolveText('Inner'); }); - expect(Scheduler).toHaveYielded(['Inner']); + assertLog(['Inner']); expect(root).toMatchRenderedOutput( <> Outer @@ -273,14 +276,7 @@ describe('ReactSuspenseWithNoopRenderer', () => { await act(async () => { root.render(); }); - // Each level commits separately - expect(Scheduler).toHaveYielded([ - 'A', - 'Loading B...', - 'B', - 'Loading C...', - 'C', - ]); + assertLog(['A', 'Loading B...', 'B', 'Loading C...', 'C']); expect(root).toMatchRenderedOutput( <> A diff --git a/packages/react-reconciler/src/__tests__/ReactCache-test.js b/packages/react-reconciler/src/__tests__/ReactCache-test.js index 7b1eda50ff2bf..47a96971261fd 100644 --- a/packages/react-reconciler/src/__tests__/ReactCache-test.js +++ b/packages/react-reconciler/src/__tests__/ReactCache-test.js @@ -3,6 +3,7 @@ let ReactNoop; let Cache; let getCacheSignal; let Scheduler; +let assertLog; let act; let Suspense; let Offscreen; @@ -32,6 +33,9 @@ describe('ReactCache', () => { startTransition = React.startTransition; useState = React.useState; + const InternalTestUtils = require('internal-test-utils'); + assertLog = InternalTestUtils.assertLog; + textCaches = []; seededCache = null; @@ -203,20 +207,19 @@ describe('ReactCache', () => { , ); }); - expect(Scheduler).toHaveYielded(['Cache miss! [A]', 'Loading...']); + assertLog(['Cache miss! [A]', 'Loading...']); expect(root).toMatchRenderedOutput('Loading...'); await act(async () => { resolveMostRecentTextCache('A'); }); - expect(Scheduler).toHaveYielded(['A']); + assertLog(['A']); expect(root).toMatchRenderedOutput('A'); await act(async () => { root.render('Bye'); }); - // no cleanup: cache is still retained at the root - expect(Scheduler).toHaveYielded([]); + assertLog([]); expect(root).toMatchRenderedOutput('Bye'); }); @@ -230,20 +233,19 @@ describe('ReactCache', () => { , ); }); - expect(Scheduler).toHaveYielded(['Cache miss! [A]', 'Loading...']); + assertLog(['Cache miss! [A]', 'Loading...']); expect(root).toMatchRenderedOutput('Loading...'); await act(async () => { resolveMostRecentTextCache('A'); }); - expect(Scheduler).toHaveYielded(['A']); + assertLog(['A']); expect(root).toMatchRenderedOutput('A'); await act(async () => { root.render('Bye'); }); - // no cleanup: cache is still retained at the root - expect(Scheduler).toHaveYielded([]); + assertLog([]); expect(root).toMatchRenderedOutput('Bye'); }); @@ -271,26 +273,19 @@ describe('ReactCache', () => { root.render(); }); - // Even though there are two new trees, they should share the same - // data cache. So there should be only a single cache miss for A. - expect(Scheduler).toHaveYielded([ - 'Cache miss! [A]', - 'Loading...', - 'Loading...', - ]); + assertLog(['Cache miss! [A]', 'Loading...', 'Loading...']); expect(root).toMatchRenderedOutput('Loading...Loading...'); await act(async () => { resolveMostRecentTextCache('A'); }); - expect(Scheduler).toHaveYielded(['A', 'A']); + assertLog(['A', 'A']); expect(root).toMatchRenderedOutput('AA'); await act(async () => { root.render('Bye'); }); - // no cleanup: cache is still retained at the root - expect(Scheduler).toHaveYielded([]); + assertLog([]); expect(root).toMatchRenderedOutput('Bye'); }); @@ -319,34 +314,25 @@ describe('ReactCache', () => { await act(async () => { root.render(); }); - expect(Scheduler).toHaveYielded([]); + assertLog([]); expect(root).toMatchRenderedOutput('(empty)'); await act(async () => { root.render(); }); - // Even though there are two new trees, they should share the same - // data cache. So there should be only a single cache miss for A. - expect(Scheduler).toHaveYielded([ - 'Cache miss! [A]', - 'Loading...', - 'Loading...', - ]); + assertLog(['Cache miss! [A]', 'Loading...', 'Loading...']); expect(root).toMatchRenderedOutput('Loading...Loading...'); await act(async () => { resolveMostRecentTextCache('A'); }); - expect(Scheduler).toHaveYielded(['A', 'A']); + assertLog(['A', 'A']); expect(root).toMatchRenderedOutput('AA'); await act(async () => { root.render('Bye'); }); - // cleanup occurs for the cache shared by the inner cache boundaries (which - // are not shared w the root because they were added in an update) - // note that no cache is created for the root since the cache is never accessed - expect(Scheduler).toHaveYielded(['Cache cleanup: A [v1]']); + assertLog(['Cache cleanup: A [v1]']); expect(root).toMatchRenderedOutput('Bye'); }); @@ -370,22 +356,19 @@ describe('ReactCache', () => { await act(async () => { root.render(); }); - // Even though there is a nested boundary, it should share the same - // data cache as the root. So there should be only a single cache miss for A. - expect(Scheduler).toHaveYielded(['Cache miss! [A]', 'Loading...']); + assertLog(['Cache miss! [A]', 'Loading...']); expect(root).toMatchRenderedOutput('Loading...'); await act(async () => { resolveMostRecentTextCache('A'); }); - expect(Scheduler).toHaveYielded(['A', 'A']); + assertLog(['A', 'A']); expect(root).toMatchRenderedOutput('AA'); await act(async () => { root.render('Bye'); }); - // no cleanup: cache is still retained at the root - expect(Scheduler).toHaveYielded([]); + assertLog([]); expect(root).toMatchRenderedOutput('Bye'); }, ); @@ -412,14 +395,14 @@ describe('ReactCache', () => { seedNextTextCache('A'); root.render(); }); - expect(Scheduler).toHaveYielded(['A [v1]']); + assertLog(['A [v1]']); expect(root).toMatchRenderedOutput('A [v1]'); // Add a new cache boundary await act(async () => { root.render(); }); - expect(Scheduler).toHaveYielded([ + assertLog([ 'A [v1]', // New tree should use already cached data 'A [v1]', @@ -429,8 +412,7 @@ describe('ReactCache', () => { await act(async () => { root.render('Bye'); }); - // no cleanup: cache is still retained at the root - expect(Scheduler).toHaveYielded([]); + assertLog([]); expect(root).toMatchRenderedOutput('Bye'); }); @@ -460,14 +442,14 @@ describe('ReactCache', () => { seedNextTextCache('A'); root.render(); }); - expect(Scheduler).toHaveYielded(['A [v1]']); + assertLog(['A [v1]']); expect(root).toMatchRenderedOutput('A [v1]'); // Add a new cache boundary await act(async () => { root.render(); }); - expect(Scheduler).toHaveYielded([ + assertLog([ 'A [v1]', // New tree should load fresh data. 'Cache miss! [A]', @@ -477,7 +459,7 @@ describe('ReactCache', () => { await act(async () => { resolveMostRecentTextCache('A'); }); - expect(Scheduler).toHaveYielded(['A [v2]']); + assertLog(['A [v2]']); expect(root).toMatchRenderedOutput('A [v1]A [v2]'); // Replace all the children: this should retain the root Cache instance, @@ -486,9 +468,7 @@ describe('ReactCache', () => { await act(async () => { root.render('Bye!'); }); - // Cleanup occurs for the *second* cache instance: the first is still - // referenced by the root - expect(Scheduler).toHaveYielded(['Cache cleanup: A [v2]']); + assertLog(['Cache cleanup: A [v2]']); expect(root).toMatchRenderedOutput('Bye!'); }); @@ -535,13 +515,13 @@ describe('ReactCache', () => { await act(async () => { root.render(); }); - expect(Scheduler).toHaveYielded(['Cache miss! [A]', 'Loading shell...']); + assertLog(['Cache miss! [A]', 'Loading shell...']); expect(root).toMatchRenderedOutput('Loading shell...'); await act(async () => { resolveMostRecentTextCache('A'); }); - expect(Scheduler).toHaveYielded([ + assertLog([ 'Shell', // There's a cache miss for B, because it hasn't been read yet. But not // A, because it was cached when we rendered the shell. @@ -558,7 +538,7 @@ describe('ReactCache', () => { await act(async () => { resolveMostRecentTextCache('B'); }); - expect(Scheduler).toHaveYielded(['Content']); + assertLog(['Content']); expect(root).toMatchRenderedOutput( <>
Shell
@@ -569,8 +549,7 @@ describe('ReactCache', () => { await act(async () => { root.render('Bye'); }); - // no cleanup: cache is still retained at the root - expect(Scheduler).toHaveYielded([]); + assertLog([]); expect(root).toMatchRenderedOutput('Bye'); }); @@ -619,19 +598,19 @@ describe('ReactCache', () => { await act(async () => { root.render(); }); - expect(Scheduler).toHaveYielded([]); + assertLog([]); expect(root).toMatchRenderedOutput('(empty)'); await act(async () => { root.render(); }); - expect(Scheduler).toHaveYielded(['Cache miss! [A]', 'Loading shell...']); + assertLog(['Cache miss! [A]', 'Loading shell...']); expect(root).toMatchRenderedOutput('Loading shell...'); await act(async () => { resolveMostRecentTextCache('A'); }); - expect(Scheduler).toHaveYielded([ + assertLog([ 'Shell', // There's a cache miss for B, because it hasn't been read yet. But not // A, because it was cached when we rendered the shell. @@ -648,7 +627,7 @@ describe('ReactCache', () => { await act(async () => { resolveMostRecentTextCache('B'); }); - expect(Scheduler).toHaveYielded(['Content']); + assertLog(['Content']); expect(root).toMatchRenderedOutput( <>
Shell
@@ -659,10 +638,7 @@ describe('ReactCache', () => { await act(async () => { root.render('Bye'); }); - expect(Scheduler).toHaveYielded([ - 'Cache cleanup: A [v1]', - 'Cache cleanup: B [v1]', - ]); + assertLog(['Cache cleanup: A [v1]', 'Cache cleanup: B [v1]']); expect(root).toMatchRenderedOutput('Bye'); }); @@ -683,20 +659,20 @@ describe('ReactCache', () => { , ); }); - expect(Scheduler).toHaveYielded(['Cache miss! [A]', 'Loading...']); + assertLog(['Cache miss! [A]', 'Loading...']); expect(root).toMatchRenderedOutput('Loading...'); await act(async () => { resolveMostRecentTextCache('A'); }); - expect(Scheduler).toHaveYielded(['A [v1]']); + assertLog(['A [v1]']); expect(root).toMatchRenderedOutput('A [v1]'); // Refresh for new data. await act(async () => { startTransition(() => refresh()); }); - expect(Scheduler).toHaveYielded(['Cache miss! [A]', 'Loading...']); + assertLog(['Cache miss! [A]', 'Loading...']); expect(root).toMatchRenderedOutput('A [v1]'); await act(async () => { @@ -704,9 +680,9 @@ describe('ReactCache', () => { }); // Note that the version has updated if (getCacheSignal) { - expect(Scheduler).toHaveYielded(['A [v2]', 'Cache cleanup: A [v1]']); + assertLog(['A [v2]', 'Cache cleanup: A [v1]']); } else { - expect(Scheduler).toHaveYielded(['A [v2]']); + assertLog(['A [v2]']); } expect(root).toMatchRenderedOutput('A [v2]'); @@ -733,34 +709,32 @@ describe('ReactCache', () => { , ); }); - expect(Scheduler).toHaveYielded(['Cache miss! [A]', 'Loading...']); + assertLog(['Cache miss! [A]', 'Loading...']); expect(root).toMatchRenderedOutput('Loading...'); await act(async () => { resolveMostRecentTextCache('A'); }); - expect(Scheduler).toHaveYielded(['A [v1]']); + assertLog(['A [v1]']); expect(root).toMatchRenderedOutput('A [v1]'); // Refresh for new data. await act(async () => { startTransition(() => refresh()); }); - expect(Scheduler).toHaveYielded(['Cache miss! [A]', 'Loading...']); + assertLog(['Cache miss! [A]', 'Loading...']); expect(root).toMatchRenderedOutput('A [v1]'); await act(async () => { resolveMostRecentTextCache('A'); }); - // Note that the version has updated, and the previous cache is cleared - expect(Scheduler).toHaveYielded(['A [v2]', 'Cache cleanup: A [v1]']); + assertLog(['A [v2]', 'Cache cleanup: A [v1]']); expect(root).toMatchRenderedOutput('A [v2]'); await act(async () => { root.render('Bye'); }); - // the original root cache already cleaned up when the refresh completed - expect(Scheduler).toHaveYielded([]); + assertLog([]); expect(root).toMatchRenderedOutput('Bye'); }); @@ -781,20 +755,20 @@ describe('ReactCache', () => { , ); }); - expect(Scheduler).toHaveYielded(['Cache miss! [A]', 'Loading...']); + assertLog(['Cache miss! [A]', 'Loading...']); expect(root).toMatchRenderedOutput('Loading...'); await act(async () => { resolveMostRecentTextCache('A'); }); - expect(Scheduler).toHaveYielded(['A [v1]']); + assertLog(['A [v1]']); expect(root).toMatchRenderedOutput('A [v1]'); // Refresh for new data. await act(async () => { refresh(); }); - expect(Scheduler).toHaveYielded([ + assertLog([ 'Cache miss! [A]', 'Loading...', // The v1 cache can be cleaned up since everything that references it has @@ -807,15 +781,13 @@ describe('ReactCache', () => { await act(async () => { resolveMostRecentTextCache('A'); }); - // Note that the version has updated, and the previous cache is cleared - expect(Scheduler).toHaveYielded(['A [v2]']); + assertLog(['A [v2]']); expect(root).toMatchRenderedOutput('A [v2]'); await act(async () => { root.render('Bye'); }); - // the original root cache already cleaned up when the refresh completed - expect(Scheduler).toHaveYielded([]); + assertLog([]); expect(root).toMatchRenderedOutput('Bye'); }); @@ -847,13 +819,13 @@ describe('ReactCache', () => {
, ); }); - expect(Scheduler).toHaveYielded(['Cache miss! [A]', 'Loading...']); + assertLog(['Cache miss! [A]', 'Loading...']); expect(root).toMatchRenderedOutput('Loading...'); await act(async () => { resolveMostRecentTextCache('A'); }); - expect(Scheduler).toHaveYielded(['A [v1]']); + assertLog(['A [v1]']); expect(root).toMatchRenderedOutput('A [v1]'); // Refresh for new data. @@ -869,16 +841,13 @@ describe('ReactCache', () => { }), ); }); - // The root should re-render without a cache miss. - // The cache is not cleared up yet, since it's still reference by the root - expect(Scheduler).toHaveYielded(['A [v2]']); + assertLog(['A [v2]']); expect(root).toMatchRenderedOutput('A [v2]'); await act(async () => { root.render('Bye'); }); - // the refreshed cache boundary is unmounted and cleans up - expect(Scheduler).toHaveYielded(['Cache cleanup: A [v2]']); + assertLog(['Cache cleanup: A [v2]']); expect(root).toMatchRenderedOutput('Bye'); }); @@ -913,7 +882,7 @@ describe('ReactCache', () => { seedNextTextCache('A'); root.render(); }); - expect(Scheduler).toHaveYielded(['A [v1]']); + assertLog(['A [v1]']); expect(root).toMatchRenderedOutput('A [v1]'); // Add a new cache boundary @@ -921,7 +890,7 @@ describe('ReactCache', () => { seedNextTextCache('A'); root.render(); }); - expect(Scheduler).toHaveYielded([ + assertLog([ 'A [v1]', // New tree should load fresh data. 'A [v2]', @@ -933,17 +902,13 @@ describe('ReactCache', () => { await act(async () => { startTransition(() => refreshShell()); }); - expect(Scheduler).toHaveYielded([ - 'Cache miss! [A]', - 'Loading...', - 'Loading...', - ]); + assertLog(['Cache miss! [A]', 'Loading...', 'Loading...']); expect(root).toMatchRenderedOutput('A [v1]A [v2]'); await act(async () => { resolveMostRecentTextCache('A'); }); - expect(Scheduler).toHaveYielded([ + assertLog([ 'A [v3]', 'A [v3]', // once the refresh completes the inner showMore boundary frees its previous @@ -955,9 +920,7 @@ describe('ReactCache', () => { await act(async () => { root.render('Bye!'); }); - // Unmounting children releases the refreshed cache instance only; the root - // still retains the original cache instance used for the first render - expect(Scheduler).toHaveYielded(['Cache cleanup: A [v3]']); + assertLog(['Cache cleanup: A [v3]']); expect(root).toMatchRenderedOutput('Bye!'); }); @@ -1004,19 +967,13 @@ describe('ReactCache', () => { root.render(); }); - // Even though there are two new trees, they should share the same - // data cache. So there should be only a single cache miss for A. - expect(Scheduler).toHaveYielded([ - 'Cache miss! [A]', - 'Loading...', - 'Loading...', - ]); + assertLog(['Cache miss! [A]', 'Loading...', 'Loading...']); expect(root).toMatchRenderedOutput('Loading...Loading...'); await act(async () => { resolveMostRecentTextCache('A'); }); - expect(Scheduler).toHaveYielded(['A [v1]', 'A [v1]']); + assertLog(['A [v1]', 'A [v1]']); expect(root).toMatchRenderedOutput('A [v1]A [v1]'); // Refresh the first boundary. It should not refresh the second boundary, @@ -1024,12 +981,12 @@ describe('ReactCache', () => { await act(async () => { await refreshFirstBoundary(); }); - expect(Scheduler).toHaveYielded(['Cache miss! [A]', 'Loading...']); + assertLog(['Cache miss! [A]', 'Loading...']); await act(async () => { resolveMostRecentTextCache('A'); }); - expect(Scheduler).toHaveYielded(['A [v2]']); + assertLog(['A [v2]']); expect(root).toMatchRenderedOutput('A [v2]A [v1]'); // Unmount children: this should clear *both* cache instances: @@ -1041,10 +998,7 @@ describe('ReactCache', () => { await act(async () => { root.render('Bye!'); }); - expect(Scheduler).toHaveYielded([ - 'Cache cleanup: A [v2]', - 'Cache cleanup: A [v1]', - ]); + assertLog(['Cache cleanup: A [v2]', 'Cache cleanup: A [v1]']); expect(root).toMatchRenderedOutput('Bye!'); }, ); @@ -1079,11 +1033,7 @@ describe('ReactCache', () => { await act(async () => { root.render(); }); - expect(Scheduler).toHaveYielded([ - 'Cache miss! [A]', - 'Cache miss! [B]', - 'Loading...', - ]); + assertLog(['Cache miss! [A]', 'Cache miss! [B]', 'Loading...']); expect(root).toMatchRenderedOutput('Loading...'); await act(async () => { @@ -1093,7 +1043,7 @@ describe('ReactCache', () => { // And mount the second tree, which includes new content root.render(); }); - expect(Scheduler).toHaveYielded([ + assertLog([ // The new tree should use a fresh cache 'Cache miss! [A]', 'Loading...', @@ -1108,16 +1058,13 @@ describe('ReactCache', () => { await act(async () => { resolveMostRecentTextCache('A'); }); - expect(Scheduler).toHaveYielded(['A [v2]']); + assertLog(['A [v2]']); expect(root).toMatchRenderedOutput('A [v2] A [v1] B [v1]'); await act(async () => { root.render('Bye!'); }); - // Unmounting children releases both cache boundaries, but the original - // cache instance (used by second boundary) is still referenced by the root. - // only the second cache instance is freed. - expect(Scheduler).toHaveYielded(['Cache cleanup: A [v2]']); + assertLog(['Cache cleanup: A [v2]']); expect(root).toMatchRenderedOutput('Bye!'); }, ); @@ -1138,7 +1085,7 @@ describe('ReactCache', () => { }>(empty), ); }); - expect(Scheduler).toHaveYielded([]); + assertLog([]); expect(root).toMatchRenderedOutput('(empty)'); await act(async () => { @@ -1150,7 +1097,7 @@ describe('ReactCache', () => { ); }); }); - expect(Scheduler).toHaveYielded(['Cache miss! [A]', 'Loading...']); + assertLog(['Cache miss! [A]', 'Loading...']); expect(root).toMatchRenderedOutput('(empty)'); await act(async () => { @@ -1163,7 +1110,7 @@ describe('ReactCache', () => { ); }); }); - expect(Scheduler).toHaveYielded([ + assertLog([ // No cache miss, because it uses the pooled cache 'Loading...', ]); @@ -1173,7 +1120,7 @@ describe('ReactCache', () => { await act(async () => { resolveMostRecentTextCache('A'); }); - expect(Scheduler).toHaveYielded(['A [v1]', 'A [v1]']); + assertLog(['A [v1]', 'A [v1]']); expect(root).toMatchRenderedOutput('A [v1]A [v1]'); // Now do another transition @@ -1188,7 +1135,7 @@ describe('ReactCache', () => { ); }); }); - expect(Scheduler).toHaveYielded([ + assertLog([ // First two children use the old cache because they already finished 'A [v1]', 'A [v1]', @@ -1201,7 +1148,7 @@ describe('ReactCache', () => { await act(async () => { resolveMostRecentTextCache('A'); }); - expect(Scheduler).toHaveYielded(['A [v1]', 'A [v1]', 'A [v2]']); + assertLog(['A [v1]', 'A [v1]', 'A [v2]']); expect(root).toMatchRenderedOutput('A [v1]A [v1]A [v2]'); // Unmount children: the first text cache instance is created only after the root @@ -1211,10 +1158,7 @@ describe('ReactCache', () => { await act(async () => { root.render('Bye!'); }); - expect(Scheduler).toHaveYielded([ - 'Cache cleanup: A [v1]', - 'Cache cleanup: A [v2]', - ]); + assertLog(['Cache cleanup: A [v1]', 'Cache cleanup: A [v2]']); expect(root).toMatchRenderedOutput('Bye!'); }); @@ -1257,7 +1201,7 @@ describe('ReactCache', () => { await act(async () => { root.render(); }); - expect(Scheduler).toHaveYielded(['0']); + assertLog(['0']); expect(root).toMatchRenderedOutput('0'); await act(async () => { @@ -1265,13 +1209,13 @@ describe('ReactCache', () => { showMore(); }); }); - expect(Scheduler).toHaveYielded(['Cache miss! [A]', 'Loading...']); + assertLog(['Cache miss! [A]', 'Loading...']); expect(root).toMatchRenderedOutput('0'); await act(async () => { updateUnrelated(1); }); - expect(Scheduler).toHaveYielded([ + assertLog([ '1', // Happens to re-render the fallback. Doesn't need to, but not relevant @@ -1283,7 +1227,7 @@ describe('ReactCache', () => { await act(async () => { resolveMostRecentTextCache('A'); }); - expect(Scheduler).toHaveYielded(['A [v1]']); + assertLog(['A [v1]']); expect(root).toMatchRenderedOutput('A [v1]1'); // Unmount children: the first text cache instance is created only after initial @@ -1293,7 +1237,7 @@ describe('ReactCache', () => { await act(async () => { root.render('Bye!'); }); - expect(Scheduler).toHaveYielded(['Cache cleanup: A [v1]']); + assertLog(['Cache cleanup: A [v1]']); expect(root).toMatchRenderedOutput('Bye!'); }); @@ -1310,7 +1254,7 @@ describe('ReactCache', () => { , ); }); - expect(Scheduler).toHaveYielded(['A [v1]']); + assertLog(['A [v1]']); expect(root).toMatchRenderedOutput('A [v1]'); seedNextTextCache('B'); @@ -1323,7 +1267,7 @@ describe('ReactCache', () => { , ); }); - expect(Scheduler).toHaveYielded(['B [v2]']); + assertLog(['B [v2]']); expect(root).toMatchRenderedOutput('B [v2]'); // Unmount children: the fresh cache instance for B cleans up since the cache boundary @@ -1332,7 +1276,7 @@ describe('ReactCache', () => { await act(async () => { root.render('Bye!'); }); - expect(Scheduler).toHaveYielded(['Cache cleanup: B [v2]']); + assertLog(['Cache cleanup: B [v2]']); expect(root).toMatchRenderedOutput('Bye!'); }); @@ -1348,13 +1292,13 @@ describe('ReactCache', () => { , ); }); - expect(Scheduler).toHaveYielded(['Cache miss! [A]']); + assertLog(['Cache miss! [A]']); expect(root).toMatchRenderedOutput('Loading...'); await act(async () => { resolveMostRecentTextCache('A'); }); - expect(Scheduler).toHaveYielded(['A [v1]']); + assertLog(['A [v1]']); expect(root).toMatchRenderedOutput('A [v1]'); // After a mount, subsequent transitions use a fresh cache @@ -1369,7 +1313,7 @@ describe('ReactCache', () => { ); }); }); - expect(Scheduler).toHaveYielded(['Cache miss! [B]']); + assertLog(['Cache miss! [B]']); expect(root).toMatchRenderedOutput('A [v1]'); // Update to a different text and with a different key for the cache @@ -1386,13 +1330,13 @@ describe('ReactCache', () => { ); }); }); - expect(Scheduler).toHaveYielded(['Cache miss! [C]']); + assertLog(['Cache miss! [C]']); expect(root).toMatchRenderedOutput('A [v1]'); await act(async () => { resolveMostRecentTextCache('C'); }); - expect(Scheduler).toHaveYielded(['C [v2]']); + assertLog(['C [v2]']); expect(root).toMatchRenderedOutput('C [v2]'); // Unmount children: the fresh cache used for the updates is freed, while the @@ -1400,10 +1344,7 @@ describe('ReactCache', () => { await act(async () => { root.render('Bye!'); }); - expect(Scheduler).toHaveYielded([ - 'Cache cleanup: B [v2]', - 'Cache cleanup: C [v2]', - ]); + assertLog(['Cache cleanup: B [v2]', 'Cache cleanup: C [v2]']); expect(root).toMatchRenderedOutput('Bye!'); }); @@ -1419,13 +1360,13 @@ describe('ReactCache', () => { , ); }); - expect(Scheduler).toHaveYielded(['Cache miss! [A]']); + assertLog(['Cache miss! [A]']); expect(root).toMatchRenderedOutput('Loading...'); await act(async () => { resolveMostRecentTextCache('A'); }); - expect(Scheduler).toHaveYielded(['A [v1]']); + assertLog(['A [v1]']); expect(root).toMatchRenderedOutput('A [v1]'); // After a mount, subsequent updates use a fresh cache @@ -1438,7 +1379,7 @@ describe('ReactCache', () => { , ); }); - expect(Scheduler).toHaveYielded(['Cache miss! [B]']); + assertLog(['Cache miss! [B]']); expect(root).toMatchRenderedOutput('Loading...'); // A second update uses the same fresh cache: even though this is a new @@ -1452,13 +1393,13 @@ describe('ReactCache', () => { , ); }); - expect(Scheduler).toHaveYielded(['Cache miss! [C]']); + assertLog(['Cache miss! [C]']); expect(root).toMatchRenderedOutput('Loading...'); await act(async () => { resolveMostRecentTextCache('C'); }); - expect(Scheduler).toHaveYielded(['C [v2]']); + assertLog(['C [v2]']); expect(root).toMatchRenderedOutput('C [v2]'); // Unmount children: the fresh cache used for the updates is freed, while the @@ -1466,10 +1407,7 @@ describe('ReactCache', () => { await act(async () => { root.render('Bye!'); }); - expect(Scheduler).toHaveYielded([ - 'Cache cleanup: B [v2]', - 'Cache cleanup: C [v2]', - ]); + assertLog(['Cache cleanup: B [v2]', 'Cache cleanup: C [v2]']); expect(root).toMatchRenderedOutput('Bye!'); }); @@ -1486,7 +1424,7 @@ describe('ReactCache', () => { , ); }); - expect(Scheduler).toHaveYielded(['A [v1]']); + assertLog(['A [v1]']); expect(root).toMatchRenderedOutput('A [v1]'); // Start a transition from A -> B..., which should create a fresh cache @@ -1502,7 +1440,7 @@ describe('ReactCache', () => { ); }); }); - expect(Scheduler).toHaveYielded(['Cache miss! [B]']); + assertLog(['Cache miss! [B]']); expect(root).toMatchRenderedOutput('A [v1]'); // ...but cancel by transitioning "back" to A (which we never really left) @@ -1517,14 +1455,14 @@ describe('ReactCache', () => { ); }); }); - expect(Scheduler).toHaveYielded(['A [v1]', 'Cache cleanup: B [v2]']); + assertLog(['A [v1]', 'Cache cleanup: B [v2]']); expect(root).toMatchRenderedOutput('A [v1]'); // Unmount children: ... await act(async () => { root.render('Bye!'); }); - expect(Scheduler).toHaveYielded([]); + assertLog([]); expect(root).toMatchRenderedOutput('Bye!'); }); @@ -1544,7 +1482,7 @@ describe('ReactCache', () => { , ); }); - expect(Scheduler).toHaveYielded(['A [v1]']); + assertLog(['A [v1]']); expect(root).toMatchRenderedOutput('A [v1]'); await act(async () => { @@ -1552,13 +1490,13 @@ describe('ReactCache', () => { refresh(); }); }); - expect(Scheduler).toHaveYielded(['Cache miss! [A]']); + assertLog(['Cache miss! [A]']); expect(root).toMatchRenderedOutput('A [v1]'); await act(async () => { root.render('Bye!'); }); - expect(Scheduler).toHaveYielded([ + assertLog([ // TODO: the v1 cache should *not* be cleaned up, it is still retained by the root // The following line is presently yielded but should not be: // 'Cache cleanup: A [v1]', @@ -1588,7 +1526,7 @@ describe('ReactCache', () => { , ); }); - expect(Scheduler).toHaveYielded(['A [v1]']); + assertLog(['A [v1]']); expect(root).toMatchRenderedOutput('A [v1]'); await act(async () => { @@ -1596,14 +1534,14 @@ describe('ReactCache', () => { refresh(); }); }); - expect(Scheduler).toHaveYielded(['Cache miss! [A]']); + assertLog(['Cache miss! [A]']); expect(root).toMatchRenderedOutput('A [v1]'); // Unmount the boundary before the refresh can complete await act(async () => { root.render('Bye!'); }); - expect(Scheduler).toHaveYielded([ + assertLog([ // TODO: the v2 cache *should* be cleaned up, it was created for the abandoned refresh // The following line is presently not yielded but should be: 'Cache cleanup: A [v2]', @@ -1632,14 +1570,14 @@ describe('ReactCache', () => { await act(async () => { root.render(); }); - expect(Scheduler).toHaveYielded([]); + assertLog([]); expect(root).toMatchRenderedOutput(