diff --git a/packages/react-reconciler/src/__tests__/ReactBatching-test.internal.js b/packages/react-reconciler/src/__tests__/ReactBatching-test.internal.js index 55c3b2162a57c..8d7bb3e75668c 100644 --- a/packages/react-reconciler/src/__tests__/ReactBatching-test.internal.js +++ b/packages/react-reconciler/src/__tests__/ReactBatching-test.internal.js @@ -166,9 +166,11 @@ describe('ReactBlockingMode', () => { assertLog(['A1', 'B1']); expect(root).toMatchRenderedOutput('A1B1'); } else { + // Only the second update should have flushed synchronously assertLog(['B1']); expect(root).toMatchRenderedOutput('A0B1'); + // Now flush the first update 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 ec6d9d619078f..3444f3f2a406a 100644 --- a/packages/react-reconciler/src/__tests__/ReactCPUSuspense-test.js +++ b/packages/react-reconciler/src/__tests__/ReactCPUSuspense-test.js @@ -143,6 +143,7 @@ describe('ReactSuspenseWithNoopRenderer', () => { , ); }); + // Inner contents finish in separate commit from outer assertLog(['Inner']); expect(root).toMatchRenderedOutput( <> @@ -178,6 +179,7 @@ describe('ReactSuspenseWithNoopRenderer', () => { await act(async () => { root.render(); }); + // Inner contents finish in separate commit from outer assertLog(['Outer', 'Loading...', 'Inner [0]']); expect(root).toMatchRenderedOutput( <> @@ -190,6 +192,7 @@ describe('ReactSuspenseWithNoopRenderer', () => { await act(async () => { setCount(1); }); + // Entire update finishes in a single commit assertLog(['Outer', 'Inner [1]']); expect(root).toMatchRenderedOutput( <> @@ -227,6 +230,7 @@ describe('ReactSuspenseWithNoopRenderer', () => { , ); }); + // Inner contents suspended, so we continue showing a fallback. assertLog(['Suspend! [Inner]']); expect(root).toMatchRenderedOutput( <> @@ -276,6 +280,7 @@ describe('ReactSuspenseWithNoopRenderer', () => { await act(async () => { root.render(); }); + // Each level commits separately assertLog(['A', 'Loading B...', 'B', 'Loading C...', 'C']); expect(root).toMatchRenderedOutput( <> diff --git a/packages/react-reconciler/src/__tests__/ReactCache-test.js b/packages/react-reconciler/src/__tests__/ReactCache-test.js index 47a96971261fd..25e0dbebb00b3 100644 --- a/packages/react-reconciler/src/__tests__/ReactCache-test.js +++ b/packages/react-reconciler/src/__tests__/ReactCache-test.js @@ -219,6 +219,7 @@ describe('ReactCache', () => { await act(async () => { root.render('Bye'); }); + // no cleanup: cache is still retained at the root assertLog([]); expect(root).toMatchRenderedOutput('Bye'); }); @@ -245,6 +246,7 @@ describe('ReactCache', () => { await act(async () => { root.render('Bye'); }); + // no cleanup: cache is still retained at the root assertLog([]); expect(root).toMatchRenderedOutput('Bye'); }); @@ -273,6 +275,8 @@ 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. assertLog(['Cache miss! [A]', 'Loading...', 'Loading...']); expect(root).toMatchRenderedOutput('Loading...Loading...'); @@ -285,6 +289,7 @@ describe('ReactCache', () => { await act(async () => { root.render('Bye'); }); + // no cleanup: cache is still retained at the root assertLog([]); expect(root).toMatchRenderedOutput('Bye'); }); @@ -320,6 +325,8 @@ describe('ReactCache', () => { 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. assertLog(['Cache miss! [A]', 'Loading...', 'Loading...']); expect(root).toMatchRenderedOutput('Loading...Loading...'); @@ -332,6 +339,9 @@ describe('ReactCache', () => { 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 assertLog(['Cache cleanup: A [v1]']); expect(root).toMatchRenderedOutput('Bye'); }); @@ -356,6 +366,8 @@ 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. assertLog(['Cache miss! [A]', 'Loading...']); expect(root).toMatchRenderedOutput('Loading...'); @@ -368,6 +380,7 @@ describe('ReactCache', () => { await act(async () => { root.render('Bye'); }); + // no cleanup: cache is still retained at the root assertLog([]); expect(root).toMatchRenderedOutput('Bye'); }, @@ -412,6 +425,7 @@ describe('ReactCache', () => { await act(async () => { root.render('Bye'); }); + // no cleanup: cache is still retained at the root assertLog([]); expect(root).toMatchRenderedOutput('Bye'); }); @@ -468,6 +482,8 @@ describe('ReactCache', () => { await act(async () => { root.render('Bye!'); }); + // Cleanup occurs for the *second* cache instance: the first is still + // referenced by the root assertLog(['Cache cleanup: A [v2]']); expect(root).toMatchRenderedOutput('Bye!'); }); @@ -549,6 +565,7 @@ describe('ReactCache', () => { await act(async () => { root.render('Bye'); }); + // no cleanup: cache is still retained at the root assertLog([]); expect(root).toMatchRenderedOutput('Bye'); }); @@ -728,12 +745,14 @@ describe('ReactCache', () => { await act(async () => { resolveMostRecentTextCache('A'); }); + // Note that the version has updated, and the previous cache is cleared 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 assertLog([]); expect(root).toMatchRenderedOutput('Bye'); }); @@ -781,12 +800,14 @@ describe('ReactCache', () => { await act(async () => { resolveMostRecentTextCache('A'); }); + // Note that the version has updated, and the previous cache is cleared 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 assertLog([]); expect(root).toMatchRenderedOutput('Bye'); }); @@ -841,12 +862,15 @@ 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 assertLog(['A [v2]']); expect(root).toMatchRenderedOutput('A [v2]'); await act(async () => { root.render('Bye'); }); + // the refreshed cache boundary is unmounted and cleans up assertLog(['Cache cleanup: A [v2]']); expect(root).toMatchRenderedOutput('Bye'); }); @@ -920,6 +944,8 @@ 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 assertLog(['Cache cleanup: A [v3]']); expect(root).toMatchRenderedOutput('Bye!'); }); @@ -967,6 +993,8 @@ 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. assertLog(['Cache miss! [A]', 'Loading...', 'Loading...']); expect(root).toMatchRenderedOutput('Loading...Loading...'); @@ -1064,6 +1092,9 @@ describe('ReactCache', () => { 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. assertLog(['Cache cleanup: A [v2]']); expect(root).toMatchRenderedOutput('Bye!'); }, diff --git a/packages/react-reconciler/src/__tests__/ReactContextPropagation-test.js b/packages/react-reconciler/src/__tests__/ReactContextPropagation-test.js index 65eccd01dddd6..49db1ea0fe159 100644 --- a/packages/react-reconciler/src/__tests__/ReactContextPropagation-test.js +++ b/packages/react-reconciler/src/__tests__/ReactContextPropagation-test.js @@ -339,6 +339,11 @@ describe('ReactLazyContextPropagation', () => { setOtherValue(1); setOtherValue(0); }); + // NOTE: If this didn't yield anything, that indicates that we never visited + // the consumer during the render phase, which probably means the eager + // bailout mechanism kicked in. Because we're testing the _lazy_ bailout + // mechanism, update this test to foil the _eager_ bailout, somehow. Perhaps + // by switching to useReducer. assertLog(['Consumer']); expect(root).toMatchRenderedOutput('0'); }); diff --git a/packages/react-reconciler/src/__tests__/ReactDeferredValue-test.js b/packages/react-reconciler/src/__tests__/ReactDeferredValue-test.js index dd9053afc1c3e..328e77b1850d8 100644 --- a/packages/react-reconciler/src/__tests__/ReactDeferredValue-test.js +++ b/packages/react-reconciler/src/__tests__/ReactDeferredValue-test.js @@ -78,6 +78,7 @@ describe('ReactDeferredValue', () => { root.render(); await waitForPaint(['Original: 2']); + // The deferred value updates in a separate render await waitForPaint(['Deferred: 2']); }); expect(root).toMatchRenderedOutput( @@ -92,6 +93,7 @@ describe('ReactDeferredValue', () => { startTransition(() => { root.render(); }); + // The deferred value updates in the same render as the original await waitForPaint(['Original: 3', 'Deferred: 3']); }); expect(root).toMatchRenderedOutput( @@ -137,6 +139,7 @@ describe('ReactDeferredValue', () => { root.render(); await waitForPaint(['Original: 2']); + // The deferred value updates in a separate render await waitForPaint(['Deferred: 2']); }); expect(root).toMatchRenderedOutput( @@ -151,6 +154,7 @@ describe('ReactDeferredValue', () => { startTransition(() => { root.render(); }); + // The deferred value updates in the same render as the original await waitForPaint(['Original: 3', 'Deferred: 3']); }); expect(root).toMatchRenderedOutput( @@ -201,6 +205,7 @@ describe('ReactDeferredValue', () => { root.render(); await waitForPaint(['Original: 2']); + // The deferred value updates in a separate render await waitForPaint(['Deferred: 2']); }); expect(root).toMatchRenderedOutput( @@ -215,6 +220,7 @@ describe('ReactDeferredValue', () => { startTransition(() => { root.render(); }); + // The deferred value updates in the same render as the original await waitForPaint(['Original: 3', 'Deferred: 3']); }); expect(root).toMatchRenderedOutput( @@ -270,6 +276,9 @@ describe('ReactDeferredValue', () => { startTransition(() => { root.render(); }); + // In the regression, the memoized value was not updated during non-urgent + // updates, so this would flip the deferred value back to the initial + // value (1) instead of reusing the current one (2). await waitForPaint(['Original: 2', 'Deferred: 2']); expect(root).toMatchRenderedOutput(
diff --git a/packages/react-reconciler/src/__tests__/ReactExpiration-test.js b/packages/react-reconciler/src/__tests__/ReactExpiration-test.js index 2556a16ac3b21..1903ab2eee25f 100644 --- a/packages/react-reconciler/src/__tests__/ReactExpiration-test.js +++ b/packages/react-reconciler/src/__tests__/ReactExpiration-test.js @@ -171,14 +171,17 @@ describe('ReactExpiration', () => { }); // Advance the timer. Scheduler.unstable_advanceTime(2000); + // Partially flush the first update, then interrupt it. await waitFor(['A [render]']); interrupt(); + // Don't advance time by enough to expire the first update. assertLog([]); expect(ReactNoop).toMatchRenderedOutput(null); // Schedule another update. ReactNoop.render(); + // Both updates are batched await waitForAll(['B [render]', 'B [commit]']); expect(ReactNoop).toMatchRenderedOutput(); @@ -190,6 +193,8 @@ describe('ReactExpiration', () => { expect(ReactNoop).toMatchRenderedOutput(); // Schedule another update. ReactNoop.render(); + // The updates should flush in the same batch, since as far as the scheduler + // knows, they may have occurred inside the same event. await waitForAll(['B [render]', 'B [commit]']); }); @@ -223,14 +228,17 @@ describe('ReactExpiration', () => { }); // Advance the timer. Scheduler.unstable_advanceTime(2000); + // Partially flush the first update, then interrupt it. await waitFor(['A [render]']); interrupt(); + // Don't advance time by enough to expire the first update. assertLog([]); expect(ReactNoop).toMatchRenderedOutput(null); // Schedule another update. ReactNoop.render(); + // Both updates are batched await waitForAll(['B [render]', 'B [commit]']); expect(ReactNoop).toMatchRenderedOutput(); @@ -247,6 +255,8 @@ describe('ReactExpiration', () => { // Schedule another update. ReactNoop.render(); + // The updates should flush in the same batch, since as far as the scheduler + // knows, they may have occurred inside the same event. await waitForAll(['B [render]', 'B [commit]']); }, ); @@ -471,7 +481,9 @@ describe('ReactExpiration', () => { // In other words, we can flush just the first child without flushing // the rest. Scheduler.unstable_flushNumberOfYields(1); + // Yield right after first child. assertLog(['Sync pri: 1']); + // Now do the rest. await waitForAll(['Normal pri: 1']); }); expect(root).toMatchRenderedOutput('Sync pri: 1, Normal pri: 1'); @@ -533,6 +545,7 @@ describe('ReactExpiration', () => { await waitFor(['Sync pri: 0']); updateSyncPri(); }); + // Same thing should happen as last time assertLog([ // Interrupt idle update to render sync update 'Sync pri: 1', @@ -733,6 +746,7 @@ describe('ReactExpiration', () => { Scheduler.unstable_flushNumberOfYields(1); assertLog(['A1', 'B1', 'C1']); }); + // The effect flushes after paint. assertLog(['Effect: 1']); }); }); diff --git a/packages/react-reconciler/src/__tests__/ReactFlushSync-test.js b/packages/react-reconciler/src/__tests__/ReactFlushSync-test.js index 4e3e948c46268..9b9d2abe35e78 100644 --- a/packages/react-reconciler/src/__tests__/ReactFlushSync-test.js +++ b/packages/react-reconciler/src/__tests__/ReactFlushSync-test.js @@ -52,6 +52,7 @@ describe('ReactFlushSync', () => { React.startTransition(() => { root.render(); }); + // This will yield right before the passive effect fires await waitForPaint(['0, 0']); // The passive effect will schedule a sync update and a normal update. @@ -67,6 +68,7 @@ describe('ReactFlushSync', () => { if (gate(flags => flags.enableUnifiedSyncLane)) { await waitForPaint([]); } else { + // Now flush it. await waitForPaint(['1, 1']); } }); @@ -112,9 +114,11 @@ describe('ReactFlushSync', () => { }); }); }); + // Only the sync update should have flushed assertLog(['1, 0']); expect(root).toMatchRenderedOutput('1, 0'); }); + // Now the async update has flushed, too. assertLog(['1, 1']); expect(root).toMatchRenderedOutput('1, 1'); }); @@ -162,6 +166,7 @@ describe('ReactFlushSync', () => { ]); expect(root).toMatchRenderedOutput('Child'); }); + // Effect flushes after paint. assertLog(['Effect']); }); @@ -217,6 +222,7 @@ describe('ReactFlushSync', () => { ]); expect(root).toMatchRenderedOutput('Child'); }); + // Effect flushes after paint. assertLog(['Effect']); }); @@ -236,8 +242,10 @@ describe('ReactFlushSync', () => { // Passive effects are pending. Calling flushSync should not affect them. ReactNoop.flushSync(); + // Effects still haven't fired. assertLog([]); }); + // Now the effects have fired. assertLog(['Effect']); }); }); diff --git a/packages/react-reconciler/src/__tests__/ReactFragment-test.js b/packages/react-reconciler/src/__tests__/ReactFragment-test.js index 7de3300bf33b7..e6b9c185ca40e 100644 --- a/packages/react-reconciler/src/__tests__/ReactFragment-test.js +++ b/packages/react-reconciler/src/__tests__/ReactFragment-test.js @@ -944,6 +944,7 @@ describe('ReactFragment', () => { ); ReactNoop.render(); + // The key warning gets deduped because it's in the same component. await waitForAll([]); expect(ops).toEqual(['Update Stateful']); @@ -955,6 +956,7 @@ describe('ReactFragment', () => { ); ReactNoop.render(); + // The key warning gets deduped because it's in the same component. await waitForAll([]); expect(ops).toEqual(['Update Stateful', 'Update Stateful']); diff --git a/packages/react-reconciler/src/__tests__/ReactSuspenseWithNoopRenderer-test.js b/packages/react-reconciler/src/__tests__/ReactSuspenseWithNoopRenderer-test.js index 3a268504136df..0c857c232424b 100644 --- a/packages/react-reconciler/src/__tests__/ReactSuspenseWithNoopRenderer-test.js +++ b/packages/react-reconciler/src/__tests__/ReactSuspenseWithNoopRenderer-test.js @@ -299,6 +299,7 @@ describe('ReactSuspenseWithNoopRenderer', () => { // Resolve the data await resolveText('A'); + // Renders successfully await waitForAll(['Foo', 'Bar', 'A', 'B']); expect(ReactNoop).toMatchRenderedOutput( <> @@ -375,6 +376,7 @@ describe('ReactSuspenseWithNoopRenderer', () => { , ); }); + // B suspends. Continue rendering the remaining siblings. await waitForAll(['A', 'Suspend! [B]', 'C', 'D', 'Loading...']); // Did not commit yet. expect(ReactNoop).toMatchRenderedOutput(null); @@ -382,6 +384,7 @@ describe('ReactSuspenseWithNoopRenderer', () => { // Wait for data to resolve await resolveText('B'); await waitForAll(['A', 'B', 'C', 'D']); + // Renders successfully expect(ReactNoop).toMatchRenderedOutput( <> @@ -730,6 +733,8 @@ describe('ReactSuspenseWithNoopRenderer', () => { // the update. ReactNoop.expire(10000); await advanceTimers(10000); + // No additional rendering work is required, since we already prepared + // the placeholder. assertLog([]); // Should have committed the placeholder. expect(ReactNoop).toMatchRenderedOutput( @@ -974,6 +979,7 @@ describe('ReactSuspenseWithNoopRenderer', () => { // but not by enough to flush the promise or reach the true expiration time. ReactNoop.expire(2000); await advanceTimers(2000); + // Even flushing won't yield a fallback in a transition. expect(ReactNoop).toMatchRenderedOutput(null); await waitForAll([]); @@ -1014,6 +1020,8 @@ describe('ReactSuspenseWithNoopRenderer', () => { await resolveText('Async'); + // Because we're already showing a fallback, interrupt the current render + // and restart immediately. await waitForAll(['Async', 'Sibling']); expect(root).toMatchRenderedOutput( <> @@ -1081,6 +1089,7 @@ describe('ReactSuspenseWithNoopRenderer', () => { // Resolve the promise await resolveText('Async'); + // We can now resume rendering await waitForAll(['Async']); expect(ReactNoop).toMatchRenderedOutput(); }); @@ -1106,6 +1115,7 @@ describe('ReactSuspenseWithNoopRenderer', () => { // Schedule an update, and suspend for up to 5 seconds. React.startTransition(() => ReactNoop.render()); + // The update should suspend. await waitForAll(['Suspend! [A]', 'Loading...']); expect(ReactNoop).toMatchRenderedOutput(); @@ -1117,6 +1127,7 @@ describe('ReactSuspenseWithNoopRenderer', () => { // Schedule another low priority update. React.startTransition(() => ReactNoop.render()); + // This update should also suspend. await waitForAll(['Suspend! [B]', 'Loading...']); expect(ReactNoop).toMatchRenderedOutput(); @@ -1129,6 +1140,7 @@ describe('ReactSuspenseWithNoopRenderer', () => { // Flush the remaining work. await resolveText('A'); await resolveText('B'); + // Nothing else to render. await waitForAll([]); expect(ReactNoop).toMatchRenderedOutput(); }); @@ -1778,6 +1790,7 @@ describe('ReactSuspenseWithNoopRenderer', () => { 'Commit root', ]); + // Flush passive effects. await waitForAll([ 'Effect [A]', // B's effect should not fire because it suspended @@ -1823,6 +1836,7 @@ describe('ReactSuspenseWithNoopRenderer', () => { 'Commit root', ]); + // Flush passive effects. await waitForAll([ // B2's effect should not fire because it suspended // 'Effect [B2]', @@ -1863,6 +1877,7 @@ describe('ReactSuspenseWithNoopRenderer', () => { }); Scheduler.unstable_advanceTime(100); await advanceTimers(100); + // Start rendering await waitFor(['Foo']); // For some reason it took a long time to render Foo. Scheduler.unstable_advanceTime(1250); @@ -1878,12 +1893,15 @@ describe('ReactSuspenseWithNoopRenderer', () => { // Flush some of the time Scheduler.unstable_advanceTime(450); await advanceTimers(450); + // Because we've already been waiting for so long we can + // wait a bit longer. Still nothing... await waitForAll([]); expect(ReactNoop).toMatchRenderedOutput(null); // Eventually we'll show the fallback. Scheduler.unstable_advanceTime(500); await advanceTimers(500); + // No need to rerender. await waitForAll([]); // Since this is a transition, we never fallback. expect(ReactNoop).toMatchRenderedOutput(null); @@ -1891,6 +1909,8 @@ describe('ReactSuspenseWithNoopRenderer', () => { // Flush the promise completely await resolveText('A'); await waitForAll(['Foo', 'A']); + // Renders successfully + // TODO: Why does this render Foo expect(ReactNoop).toMatchRenderedOutput(); }); @@ -1909,6 +1929,7 @@ describe('ReactSuspenseWithNoopRenderer', () => { } ReactNoop.render(); + // Start rendering await waitForAll([ 'Foo', // A suspends @@ -1925,6 +1946,7 @@ describe('ReactSuspenseWithNoopRenderer', () => { Scheduler.unstable_advanceTime(5000); await advanceTimers(5000); + // Retry with the new content. await waitForAll([ 'A', // B still suspends @@ -1943,6 +1965,7 @@ describe('ReactSuspenseWithNoopRenderer', () => { // Flush the last promise completely await resolveText('B'); + // Renders successfully await waitForAll(['B']); expect(ReactNoop).toMatchRenderedOutput( <> @@ -1967,6 +1990,7 @@ describe('ReactSuspenseWithNoopRenderer', () => { } ReactNoop.render(); + // Start rendering await waitForAll([ 'Foo', // A suspends @@ -1980,6 +2004,7 @@ describe('ReactSuspenseWithNoopRenderer', () => { await resolveText('A'); + // Retry with the new content. await waitForAll([ 'A', // B still suspends @@ -1995,6 +2020,7 @@ describe('ReactSuspenseWithNoopRenderer', () => { // Before we commit another Promise resolves. // We're still showing the first loading state. expect(ReactNoop).toMatchRenderedOutput(); + // Restart and render the complete content. await waitForAll(['A', 'B']); expect(ReactNoop).toMatchRenderedOutput( <> @@ -2040,6 +2066,10 @@ describe('ReactSuspenseWithNoopRenderer', () => { Scheduler.unstable_advanceTime(500); jest.advanceTimersByTime(500); + // We should have already shown the fallback. + // When we wrote this test, we inferred the start time of high priority + // updates as way earlier in the past. This test ensures that we don't + // use this assumption to add a very long JND. await waitForAll([]); // Transitions never fallback. expect(ReactNoop).toMatchRenderedOutput(null); @@ -2481,6 +2511,7 @@ describe('ReactSuspenseWithNoopRenderer', () => { // Schedule an update at idle pri. ReactNoop.idleUpdates(() => ReactNoop.render()); + // We won't even work on Idle priority. await waitForAll([]); // We're still suspended. @@ -3040,6 +3071,9 @@ describe('ReactSuspenseWithNoopRenderer', () => { await act(async () => { foo.setState({suspend: true}); + // In the regression that this covers, we would neglect to reset the + // current debug phase after suspending (in the catch block), so React + // thinks we're still inside the render phase. await waitFor(['Suspend!']); // Then when this setState happens, React would incorrectly fire a warning @@ -3130,6 +3164,7 @@ describe('ReactSuspenseWithNoopRenderer', () => { await waitFor(['Commit']); expect(ReactNoop).toMatchRenderedOutput(