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!" />