@samitouri / QOS-React-2 / commits / f9de24a26a

Convert more Suspense tests to use `act` (#26602)

Many of our Suspense-related tests were written before the `act` API was introduced, and use the lower level `waitFor` helpers instead. So they are less resilient to changes in implementation details than they could be. This converts some of our test suite to use `act` in more places. I found these while working on a PR to expand our fallback throttling mechanism to include all renders that result from a promise resolving, even if there are no more fallbacks in the tree. This isn't all the affected tests, just some of them — I'll be sharding the changes across multiple PRs.

Andrew Clark committed Apr 11, 2023 at 13:47 UTC f9de24a26af23ce63b6f0474670870c86e1a631c
1 file changed +140 -112
packages/react-reconciler/src/__tests__/ReactSuspenseWithNoopRenderer-test.js
+140 -112
@@ -194,7 +194,7 @@ describe('ReactSuspenseWithNoopRenderer', () => {
194 }
195
196 // @gate enableLegacyCache
197 - it('does not restart rendering for initial render', async () => {
197 + it("does not restart if there's a ping during initial render", async () => {
198 function Bar(props) {
199 Scheduler.log('Bar');
200 return props.children;
@@ -216,13 +216,9 @@ describe('ReactSuspenseWithNoopRenderer', () => {
216 );
217 }
218
219 - if (gate(flags => flags.enableSyncDefaultUpdates)) {
220 - React.startTransition(() => {
221 - ReactNoop.render(<Foo />);
222 - });
223 - } else {
219 + React.startTransition(() => {
220 ReactNoop.render(<Foo />);
225 - }
221 + });
222 await waitFor([
223 'Foo',
224 'Bar',
@@ -237,21 +233,21 @@ describe('ReactSuspenseWithNoopRenderer', () => {
233 expect(ReactNoop).toMatchRenderedOutput(null);
234
235 // Flush the promise completely
240 - await resolveText('A');
241 -
242 - // Even though the promise has resolved, we should now flush
243 - // and commit the in progress render instead of restarting.
244 - await waitForPaint(['D']);
245 - expect(ReactNoop).toMatchRenderedOutput(
246 - <>
247 - <span prop="Loading..." />
248 - <span prop="C" />
249 - <span prop="D" />
250 - </>,
251 - );
252 -
253 - // Next, we'll flush the complete content.
254 - await waitForAll(['Bar', 'A', 'B']);
236 + await act(async () => {
237 + await resolveText('A');
238 + // Even though the promise has resolved, we should now flush
239 + // and commit the in progress render instead of restarting.
240 + await waitForPaint(['D']);
241 + expect(ReactNoop).toMatchRenderedOutput(
242 + <>
243 + <span prop="Loading..." />
244 + <span prop="C" />
245 + <span prop="D" />
246 + </>,
247 + );
248 + // Next, we'll flush the complete content.
249 + await waitForAll(['Bar', 'A', 'B']);
250 + });
251
252 expect(ReactNoop).toMatchRenderedOutput(
253 <>
@@ -343,9 +339,8 @@ describe('ReactSuspenseWithNoopRenderer', () => {
339
340 // Resolve first Suspense's promise so that it switches switches back to the
341 // normal view. The second Suspense should still show the placeholder.
346 - await resolveText('A');
347 -
348 - await waitForAll(['A']);
342 + await act(() => resolveText('A'));
343 + assertLog(['A']);
344 expect(ReactNoop).toMatchRenderedOutput(
345 <>
346 <span prop="A" />
@@ -355,9 +350,8 @@ describe('ReactSuspenseWithNoopRenderer', () => {
350
351 // Resolve the second Suspense's promise so that it switches back to the
352 // normal view.
358 - await resolveText('B');
359 -
360 - await waitForAll(['B']);
353 + await act(() => resolveText('B'));
354 + assertLog(['B']);
355 expect(ReactNoop).toMatchRenderedOutput(
356 <>
357 <span prop="A" />
@@ -515,20 +509,37 @@ describe('ReactSuspenseWithNoopRenderer', () => {
509
510 // @gate enableLegacyCache
511 it('can update at a higher priority while in a suspended state', async () => {
518 - function App(props) {
512 + let setHighPri;
513 + function HighPri() {
514 + const [text, setText] = React.useState('A');
515 + setHighPri = setText;
516 + return <Text text={text} />;
517 + }
518 +
519 + let setLowPri;
520 + function LowPri() {
521 + const [text, setText] = React.useState('1');
522 + setLowPri = setText;
523 + return <AsyncText text={text} />;
524 + }
525 +
526 + function App() {
527 return (
520 - <Suspense fallback={<Text text="Loading..." />}>
521 - <Text text={props.highPri} />
522 - <AsyncText text={props.lowPri} />
523 - </Suspense>
528 + <>
529 + <HighPri />
530 + <Suspense fallback={<Text text="Loading..." />}>
531 + <LowPri />
532 + </Suspense>
533 + </>
534 );
535 }
536
537 // Initial mount
528 - ReactNoop.render(<App highPri="A" lowPri="1" />);
529 - await waitForAll(['A', 'Suspend! [1]', 'Loading...']);
530 - await resolveText('1');
531 - await waitForAll(['A', '1']);
538 + await act(() => ReactNoop.render(<App />));
539 + assertLog(['A', 'Suspend! [1]', 'Loading...']);
540 +
541 + await act(() => resolveText('1'));
542 + assertLog(['1']);
543 expect(ReactNoop).toMatchRenderedOutput(
544 <>
545 <span prop="A" />
@@ -537,20 +548,16 @@ describe('ReactSuspenseWithNoopRenderer', () => {
548 );
549
550 // Update the low-pri text
540 - ReactNoop.render(<App highPri="A" lowPri="2" />);
541 - await waitForAll([
542 - 'A',
543 - // Suspends
544 - 'Suspend! [2]',
545 - 'Loading...',
546 - ]);
551 + await act(() => startTransition(() => setLowPri('2')));
552 + // Suspends
553 + assertLog(['Suspend! [2]', 'Loading...']);
554
555 // While we're still waiting for the low-pri update to complete, update the
556 // high-pri text at high priority.
557 ReactNoop.flushSync(() => {
551 - ReactNoop.render(<App highPri="B" lowPri="1" />);
558 + setHighPri('B');
559 });
553 - assertLog(['B', '1']);
560 + assertLog(['B']);
561 expect(ReactNoop).toMatchRenderedOutput(
562 <>
563 <span prop="B" />
@@ -558,12 +565,14 @@ describe('ReactSuspenseWithNoopRenderer', () => {
565 </>,
566 );
567
561 - // Unblock the low-pri text and finish
562 - await resolveText('2');
568 + // Unblock the low-pri text and finish. Nothing in the UI changes because
569 + // the update was overriden
570 + await act(() => resolveText('2'));
571 + assertLog(['2']);
572 expect(ReactNoop).toMatchRenderedOutput(
573 <>
574 <span prop="B" />
566 - <span prop="1" />
575 + <span prop="2" />
576 </>,
577 );
578 });
@@ -772,8 +781,8 @@ describe('ReactSuspenseWithNoopRenderer', () => {
781 );
782
783 // Finally, flush the inner promise. We should see the complete screen.
775 - await resolveText('Inner content');
776 - await waitForAll(['Inner content']);
784 + await act(() => resolveText('Inner content'));
785 + assertLog(['Inner content']);
786 expect(ReactNoop).toMatchRenderedOutput(
787 <>
788 <span prop="Sync" />
@@ -784,7 +793,7 @@ describe('ReactSuspenseWithNoopRenderer', () => {
793 });
794
795 // @gate enableLegacyCache
787 - it('renders an expiration boundary synchronously', async () => {
796 + it('renders an Suspense boundary synchronously', async () => {
797 spyOnDev(console, 'error');
798 // Synchronously render a tree that suspends
799 ReactNoop.flushSync(() =>
@@ -814,8 +823,8 @@ describe('ReactSuspenseWithNoopRenderer', () => {
823 );
824
825 // Once the promise resolves, we render the suspended view
817 - await resolveText('Async');
818 - await waitForAll(['Async']);
826 + await act(() => resolveText('Async'));
827 + assertLog(['Async']);
828 expect(ReactNoop).toMatchRenderedOutput(
829 <>
830 <span prop="Async" />
@@ -922,10 +931,11 @@ describe('ReactSuspenseWithNoopRenderer', () => {
931 await waitForAll(['Suspend! [A]', 'Loading...']);
932 expect(ReactNoop).toMatchRenderedOutput(<span prop="Loading..." />);
933
925 - await resolveText('A');
926 - await resolveText('B');
927 -
928 - await waitForAll(['A', 'B']);
934 + await act(() => {
935 + resolveText('A');
936 + resolveText('B');
937 + });
938 + assertLog(['A', 'B']);
939 expect(ReactNoop).toMatchRenderedOutput(
940 <>
941 <span prop="A" />
@@ -1736,12 +1746,12 @@ describe('ReactSuspenseWithNoopRenderer', () => {
1746 ]);
1747 expect(ReactNoop).toMatchRenderedOutput(<span prop="Loading..." />);
1748
1739 - await resolveText('A');
1749 // Wait a long time.
1750 Scheduler.unstable_advanceTime(5000);
1751 await advanceTimers(5000);
1752
1753 // Retry with the new content.
1754 + await resolveText('A');
1755 await waitForAll([
1756 'A',
1757 // B suspends
@@ -1759,9 +1769,9 @@ describe('ReactSuspenseWithNoopRenderer', () => {
1769 );
1770
1771 // Flush the last promise completely
1762 - await resolveText('B');
1772 + await act(() => resolveText('B'));
1773 // Renders successfully
1764 - await waitForAll(['B']);
1774 + assertLog(['B']);
1775 expect(ReactNoop).toMatchRenderedOutput(
1776 <>
1777 <span prop="A" />
@@ -1794,26 +1804,44 @@ describe('ReactSuspenseWithNoopRenderer', () => {
1804 ]);
1805 expect(ReactNoop).toMatchRenderedOutput(<span prop="Loading..." />);
1806
1797 - await resolveText('A');
1807 + await act(async () => {
1808 + await resolveText('A');
1809
1799 - // Retry with the new content.
1800 - await waitForAll([
1801 - 'A',
1802 - // B suspends
1803 - 'Suspend! [B]',
1804 - 'Loading more...',
1805 - ]);
1806 - // Because we've already been waiting for so long we can
1807 - // wait a bit longer. Still nothing...
1808 - expect(ReactNoop).toMatchRenderedOutput(<span prop="Loading..." />);
1810 + // Retry with the new content.
1811 + await waitForAll([
1812 + 'A',
1813 + // B suspends
1814 + 'Suspend! [B]',
1815 + 'Loading more...',
1816 + ]);
1817 + // Because we've already been waiting for so long we can
1818 + // wait a bit longer. Still nothing...
1819 + expect(ReactNoop).toMatchRenderedOutput(<span prop="Loading..." />);
1820
1810 - await resolveText('B');
1821 + // Before we commit another Promise resolves.
1822 + // We're still showing the first loading state.
1823 + await resolveText('B');
1824 + expect(ReactNoop).toMatchRenderedOutput(<span prop="Loading..." />);
1825
1812 - // Before we commit another Promise resolves.
1813 - // We're still showing the first loading state.
1814 - expect(ReactNoop).toMatchRenderedOutput(<span prop="Loading..." />);
1815 - // Restart and render the complete content.
1816 - await waitForAll(['A', 'B']);
1826 + // Restart and render the complete content.
1827 + await waitForAll(['A', 'B']);
1828 + // TODO: Because this render was the result of a retry, and a fallback
1829 + // was shown recently, we should suspend and remain on the fallback
1830 + // for little bit longer. We currently only do this if there's still
1831 + // remaining fallbacks in the tree, but we should do it for all retries.
1832 + //
1833 + // Correct output:
1834 + // expect(ReactNoop).toMatchRenderedOutput(<span prop="Loading..." />);
1835 + //
1836 + // Actual output:
1837 + expect(ReactNoop).toMatchRenderedOutput(
1838 + <>
1839 + <span prop="A" />
1840 + <span prop="B" />
1841 + </>,
1842 + );
1843 + });
1844 + assertLog([]);
1845 expect(ReactNoop).toMatchRenderedOutput(
1846 <>
1847 <span prop="A" />
@@ -2011,8 +2039,8 @@ describe('ReactSuspenseWithNoopRenderer', () => {
2039 expect(ReactNoop).toMatchRenderedOutput(<span prop="Initial load..." />);
2040
2041 // Eventually we resolve and show the data.
2014 - await resolveText('A');
2015 - await waitForAll(['A', 'B']);
2042 + await act(() => resolveText('A'));
2043 + assertLog(['A', 'B']);
2044 expect(ReactNoop).toMatchRenderedOutput(
2045 <>
2046 <span prop="A" />
@@ -2037,8 +2065,8 @@ describe('ReactSuspenseWithNoopRenderer', () => {
2065 );
2066
2067 // Later we load the data.
2040 - await resolveText('C');
2041 - await waitForAll(['A', 'C']);
2068 + await act(() => resolveText('C'));
2069 + assertLog(['A', 'C']);
2070 expect(ReactNoop).toMatchRenderedOutput(
2071 <>
2072 <span prop="A" />
@@ -2073,8 +2101,8 @@ describe('ReactSuspenseWithNoopRenderer', () => {
2101 expect(ReactNoop).toMatchRenderedOutput(<span prop="B" />);
2102
2103 // Eventually we resolve and show the data.
2076 - await resolveText('A');
2077 - await waitForAll(['A']);
2104 + await act(() => resolveText('A'));
2105 + assertLog(['A']);
2106 expect(ReactNoop).toMatchRenderedOutput(
2107 <>
2108 <span prop="A" />
@@ -2102,8 +2130,8 @@ describe('ReactSuspenseWithNoopRenderer', () => {
2130 );
2131
2132 // Later we load the data.
2105 - await resolveText('C');
2106 - await waitForAll(['A', 'C']);
2133 + await act(() => resolveText('C'));
2134 + assertLog(['A', 'C']);
2135 expect(ReactNoop).toMatchRenderedOutput(
2136 <>
2137 <span prop="A" />
@@ -2266,8 +2294,8 @@ describe('ReactSuspenseWithNoopRenderer', () => {
2294 expect(ReactNoop).toMatchRenderedOutput(<span prop="Loading..." />);
2295
2296 // Later we load the data.
2269 - await resolveText('A');
2270 - await waitForAll(['A']);
2297 + await act(() => resolveText('A'));
2298 + assertLog(['A']);
2299 expect(ReactNoop).toMatchRenderedOutput(<span prop="A" />);
2300
2301 // Start transition.
@@ -2280,8 +2308,8 @@ describe('ReactSuspenseWithNoopRenderer', () => {
2308 // loading state.
2309 expect(ReactNoop).toMatchRenderedOutput(<span prop="A" />);
2310 // Later we load the data.
2283 - await resolveText('B');
2284 - await waitForAll(['B']);
2311 + await act(() => resolveText('B'));
2312 + assertLog(['B']);
2313 expect(ReactNoop).toMatchRenderedOutput(<span prop="B" />);
2314 });
2315
@@ -2316,8 +2344,8 @@ describe('ReactSuspenseWithNoopRenderer', () => {
2344 });
2345
2346 // Later we load the data.
2319 - await resolveText('A');
2320 - await waitForAll(['A']);
2347 + await act(() => resolveText('A'));
2348 + assertLog(['A']);
2349 expect(ReactNoop).toMatchRenderedOutput(<span prop="A" />);
2350
2351 // Start transition.
@@ -2332,8 +2360,8 @@ describe('ReactSuspenseWithNoopRenderer', () => {
2360 expect(ReactNoop).toMatchRenderedOutput(<span prop="A" />);
2361 });
2362 // Later we load the data.
2335 - await resolveText('B');
2336 - await waitForAll(['B']);
2363 + await act(() => resolveText('B'));
2364 + assertLog(['B']);
2365 expect(ReactNoop).toMatchRenderedOutput(<span prop="B" />);
2366 });
2367
@@ -2371,8 +2399,8 @@ describe('ReactSuspenseWithNoopRenderer', () => {
2399 });
2400
2401 // Later we load the data.
2374 - await resolveText('A');
2375 - await waitForAll(['A']);
2402 + await act(() => resolveText('A'));
2403 + assertLog(['A']);
2404 expect(ReactNoop).toMatchRenderedOutput(<span prop="A" />);
2405
2406 // Start transition.
@@ -2387,8 +2415,8 @@ describe('ReactSuspenseWithNoopRenderer', () => {
2415 expect(ReactNoop).toMatchRenderedOutput(<span prop="A" />);
2416 });
2417 // Later we load the data.
2390 - await resolveText('B');
2391 - await waitForAll(['B']);
2418 + await act(() => resolveText('B'));
2419 + assertLog(['B']);
2420 expect(ReactNoop).toMatchRenderedOutput(<span prop="B" />);
2421 });
2422 });
@@ -2414,8 +2442,8 @@ describe('ReactSuspenseWithNoopRenderer', () => {
2442 expect(ReactNoop).toMatchRenderedOutput(<span prop="Loading..." />);
2443
2444 // Later we load the data.
2417 - await resolveText('A');
2418 - await waitForAll(['A']);
2445 + await act(() => resolveText('A'));
2446 + assertLog(['A']);
2447 expect(ReactNoop).toMatchRenderedOutput(<span prop="A" />);
2448
2449 // Start transition.
@@ -2429,8 +2457,8 @@ describe('ReactSuspenseWithNoopRenderer', () => {
2457 expect(ReactNoop).toMatchRenderedOutput(<span prop="A" />);
2458
2459 // Later we load the data.
2432 - await resolveText('B');
2433 - await waitForAll(['B']);
2460 + await act(() => resolveText('B'));
2461 + assertLog(['B']);
2462 expect(ReactNoop).toMatchRenderedOutput(<span prop="B" />);
2463
2464 // Start a long (infinite) transition.
@@ -2475,8 +2503,8 @@ describe('ReactSuspenseWithNoopRenderer', () => {
2503 });
2504
2505 // Later we load the data.
2478 - await resolveText('A');
2479 - await waitForAll(['A']);
2506 + await act(() => resolveText('A'));
2507 + assertLog(['A']);
2508 expect(ReactNoop).toMatchRenderedOutput(<span prop="A" />);
2509
2510 // Start transition.
@@ -2493,8 +2521,8 @@ describe('ReactSuspenseWithNoopRenderer', () => {
2521 });
2522
2523 // Later we load the data.
2496 - await resolveText('B');
2497 - await waitForAll(['B']);
2524 + await act(() => resolveText('B'));
2525 + assertLog(['B']);
2526 expect(ReactNoop).toMatchRenderedOutput(<span prop="B" />);
2527
2528 // Start a long (infinite) transition.
@@ -2545,8 +2573,8 @@ describe('ReactSuspenseWithNoopRenderer', () => {
2573 });
2574
2575 // Later we load the data.
2548 - await resolveText('A');
2549 - await waitForAll(['A']);
2576 + await act(() => resolveText('A'));
2577 + assertLog(['A']);
2578 expect(ReactNoop).toMatchRenderedOutput(<span prop="A" />);
2579
2580 // Start transition.
@@ -2562,8 +2590,8 @@ describe('ReactSuspenseWithNoopRenderer', () => {
2590 });
2591
2592 // Later we load the data.
2565 - await resolveText('B');
2566 - await waitForAll(['B']);
2593 + await act(() => resolveText('B'));
2594 + assertLog(['B']);
2595 expect(ReactNoop).toMatchRenderedOutput(<span prop="B" />);
2596
2597 // Start a long (infinite) transition.
@@ -2599,8 +2627,8 @@ describe('ReactSuspenseWithNoopRenderer', () => {
2627 // Initial render.
2628 ReactNoop.render(<App page="A" />);
2629 await waitForAll(['Hi!', 'Suspend! [A]', 'Loading...']);
2602 - await resolveText('A');
2603 - await waitForAll(['Hi!', 'A']);
2630 + await act(() => resolveText('A'));
2631 + assertLog(['Hi!', 'A']);
2632 expect(ReactNoop).toMatchRenderedOutput(
2633 <>
2634 <span prop="Hi!" />