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

Convert ReactSuspenseWithNoopRenderer tests to use built-in cache (#20601)

* Remove `ms` prop from SuspenseWithNoop tests Use `resolveText` instead. * Migrate SuspenseWithNoop tests to built-in cache

Andrew Clark committed Jan 19, 2021 at 16:38 UTC fb3e158a64dd9e0d777220ce0e4a3bdecff158dc
2 files changed +316 -349
packages/react-reconciler/src/__tests__/ReactSuspenseWithNoopRenderer-test.js
+311 -349
@@ -3,11 +3,10 @@ let Fragment;
3 let ReactNoop;
4 let Scheduler;
5 let Suspense;
6 -let textCache;
6 +let getCacheForType;
7
8 -let readText;
9 -let resolveText;
10 -let rejectText;
8 +let caches;
9 +let seededCache;
10
11 describe('ReactSuspenseWithNoopRenderer', () => {
12 beforeEach(() => {
@@ -19,80 +18,142 @@ describe('ReactSuspenseWithNoopRenderer', () => {
18 Scheduler = require('scheduler');
19 Suspense = React.Suspense;
20
22 - textCache = new Map();
23 -
24 - readText = text => {
25 - const record = textCache.get(text);
26 - if (record !== undefined) {
27 - switch (record.status) {
28 - case 'pending':
29 - throw record.promise;
30 - case 'rejected':
31 - throw Error('Failed to load: ' + text);
32 - case 'resolved':
33 - return text;
34 - }
35 - } else {
36 - let ping;
37 - const promise = new Promise(resolve => (ping = resolve));
38 - const newRecord = {
39 - status: 'pending',
40 - ping: ping,
41 - promise,
42 - };
43 - textCache.set(text, newRecord);
44 - throw promise;
45 - }
46 - };
21 + getCacheForType = React.unstable_getCacheForType;
22 +
23 + caches = [];
24 + seededCache = null;
25 + });
26
48 - resolveText = text => {
49 - const record = textCache.get(text);
50 - if (record !== undefined) {
51 - if (record.status === 'pending') {
52 - Scheduler.unstable_yieldValue(`Promise resolved [${text}]`);
53 - record.ping();
54 - record.ping = null;
27 + function createTextCache() {
28 + if (seededCache !== null) {
29 + // Trick to seed a cache before it exists.
30 + // TODO: Need a built-in API to seed data before the initial render (i.e.
31 + // not a refresh because nothing has mounted yet).
32 + const cache = seededCache;
33 + seededCache = null;
34 + return cache;
35 + }
36 +
37 + const data = new Map();
38 + const version = caches.length + 1;
39 + const cache = {
40 + version,
41 + data,
42 + resolve(text) {
43 + const record = data.get(text);
44 + if (record === undefined) {
45 + const newRecord = {
46 + status: 'resolved',
47 + value: text,
48 + };
49 + data.set(text, newRecord);
50 + } else if (record.status === 'pending') {
51 + const thenable = record.value;
52 record.status = 'resolved';
56 - clearTimeout(record.promise._timer);
57 - record.promise = null;
53 + record.value = text;
54 + thenable.pings.forEach(t => t());
55 }
59 - } else {
60 - const newRecord = {
61 - ping: null,
62 - status: 'resolved',
63 - promise: null,
64 - };
65 - textCache.set(text, newRecord);
66 - }
67 - };
68 -
69 - rejectText = text => {
70 - const record = textCache.get(text);
71 - if (record !== undefined) {
72 - if (record.status === 'pending') {
73 - Scheduler.unstable_yieldValue(`Promise rejected [${text}]`);
74 - record.ping();
56 + },
57 + reject(text, error) {
58 + const record = data.get(text);
59 + if (record === undefined) {
60 + const newRecord = {
61 + status: 'rejected',
62 + value: error,
63 + };
64 + data.set(text, newRecord);
65 + } else if (record.status === 'pending') {
66 + const thenable = record.value;
67 record.status = 'rejected';
76 - clearTimeout(record.promise._timer);
77 - record.promise = null;
68 + record.value = error;
69 + thenable.pings.forEach(t => t());
70 }
79 - } else {
80 - const newRecord = {
81 - ping: null,
82 - status: 'rejected',
83 - promise: null,
84 - };
85 - textCache.set(text, newRecord);
86 - }
71 + },
72 };
88 - });
73 + caches.push(cache);
74 + return cache;
75 + }
76 +
77 + function readText(text) {
78 + const textCache = getCacheForType(createTextCache);
79 + const record = textCache.data.get(text);
80 + if (record !== undefined) {
81 + switch (record.status) {
82 + case 'pending':
83 + Scheduler.unstable_yieldValue(`Suspend! [${text}]`);
84 + throw record.value;
85 + case 'rejected':
86 + Scheduler.unstable_yieldValue(`Error! [${text}]`);
87 + throw record.value;
88 + case 'resolved':
89 + return textCache.version;
90 + }
91 + } else {
92 + Scheduler.unstable_yieldValue(`Suspend! [${text}]`);
93 +
94 + const thenable = {
95 + pings: [],
96 + then(resolve) {
97 + if (newRecord.status === 'pending') {
98 + thenable.pings.push(resolve);
99 + } else {
100 + Promise.resolve().then(() => resolve(newRecord.value));
101 + }
102 + },
103 + };
104 +
105 + const newRecord = {
106 + status: 'pending',
107 + value: thenable,
108 + };
109 + textCache.data.set(text, newRecord);
110 +
111 + throw thenable;
112 + }
113 + }
114 +
115 + function Text({text}) {
116 + Scheduler.unstable_yieldValue(text);
117 + return <span prop={text} />;
118 + }
119 +
120 + function AsyncText({text, showVersion}) {
121 + const version = readText(text);
122 + const fullText = showVersion ? `${text} [v${version}]` : text;
123 + Scheduler.unstable_yieldValue(fullText);
124 + return <span prop={fullText} />;
125 + }
126 +
127 + function seedNextTextCache(text) {
128 + if (seededCache === null) {
129 + seededCache = createTextCache();
130 + }
131 + seededCache.resolve(text);
132 + }
133 +
134 + function resolveMostRecentTextCache(text) {
135 + if (caches.length === 0) {
136 + throw Error('Cache does not exist.');
137 + } else {
138 + // Resolve the most recently created cache. An older cache can by
139 + // resolved with `caches[index].resolve(text)`.
140 + caches[caches.length - 1].resolve(text);
141 + }
142 + }
143
90 - // function div(...children) {
91 - // children = children.map(
92 - // c => (typeof c === 'string' ? {text: c, hidden: false} : c),
93 - // );
94 - // return {type: 'div', children, prop: undefined, hidden: false};
95 - // }
144 + const resolveText = resolveMostRecentTextCache;
145 +
146 + function rejectMostRecentTextCache(text, error) {
147 + if (caches.length === 0) {
148 + throw Error('Cache does not exist.');
149 + } else {
150 + // Resolve the most recently created cache. An older cache can by
151 + // resolved with `caches[index].reject(text, error)`.
152 + caches[caches.length - 1].reject(text, error);
153 + }
154 + }
155 +
156 + const rejectText = rejectMostRecentTextCache;
157
158 function span(prop) {
159 return {type: 'span', children: [], prop, hidden: false};
@@ -114,32 +175,6 @@ describe('ReactSuspenseWithNoopRenderer', () => {
175 return Promise.resolve().then(() => {});
176 }
177
117 - function Text(props) {
118 - Scheduler.unstable_yieldValue(props.text);
119 - return <span prop={props.text} ref={props.hostRef} />;
120 - }
121 -
122 - function AsyncText(props) {
123 - const text = props.text;
124 - try {
125 - readText(text);
126 - Scheduler.unstable_yieldValue(text);
127 - return <span prop={text} />;
128 - } catch (promise) {
129 - if (typeof promise.then === 'function') {
130 - Scheduler.unstable_yieldValue(`Suspend! [${text}]`);
131 - if (typeof props.ms === 'number' && promise._timer === undefined) {
132 - promise._timer = setTimeout(() => {
133 - resolveText(text);
134 - }, props.ms);
135 - }
136 - } else {
137 - Scheduler.unstable_yieldValue(`Error! [${text}]`);
138 - }
139 - throw promise;
140 - }
141 - }
142 -
178 // Note: This is based on a similar component we use in www. We can delete
179 // once the extra div wrapper is no longer necessary.
180 function LegacyHiddenDiv({children, mode}) {
@@ -153,6 +188,7 @@ describe('ReactSuspenseWithNoopRenderer', () => {
188 );
189 }
190
191 + // @gate enableCache
192 it('does not restart rendering for initial render', async () => {
193 function Bar(props) {
194 Scheduler.unstable_yieldValue('Bar');
@@ -190,9 +226,7 @@ describe('ReactSuspenseWithNoopRenderer', () => {
226 expect(ReactNoop.getChildren()).toEqual([]);
227
228 // Flush the promise completely
193 - Scheduler.unstable_advanceTime(100);
194 - await advanceTimers(100);
195 - expect(Scheduler).toHaveYielded(['Promise resolved [A]']);
229 + await resolveText('A');
230
231 // Even though the promise has resolved, we should now flush
232 // and commit the in progress render instead of restarting.
@@ -217,6 +251,7 @@ describe('ReactSuspenseWithNoopRenderer', () => {
251 ]);
252 });
253
254 + // @gate enableCache
255 it('suspends rendering and continues later', async () => {
256 function Bar(props) {
257 Scheduler.unstable_yieldValue('Bar');
@@ -229,7 +264,7 @@ describe('ReactSuspenseWithNoopRenderer', () => {
264 <Suspense fallback={<Text text="Loading..." />}>
265 {renderBar ? (
266 <Bar>
232 - <AsyncText text="A" ms={100} />
267 + <AsyncText text="A" />
268 <Text text="B" />
269 </Bar>
270 ) : null}
@@ -254,29 +289,23 @@ describe('ReactSuspenseWithNoopRenderer', () => {
289 ]);
290 expect(ReactNoop.getChildren()).toEqual([]);
291
257 - // Flush some of the time
258 - await advanceTimers(50);
259 - // Still nothing...
260 - expect(Scheduler).toFlushWithoutYielding();
261 - expect(ReactNoop.getChildren()).toEqual([]);
262 -
263 - // Flush the promise completely
264 - await advanceTimers(50);
292 + // Resolve the data
293 + await resolveText('A');
294 // Renders successfully
266 - expect(Scheduler).toHaveYielded(['Promise resolved [A]']);
295 expect(Scheduler).toFlushAndYield(['Foo', 'Bar', 'A', 'B']);
296 expect(ReactNoop.getChildren()).toEqual([span('A'), span('B')]);
297 });
298
299 + // @gate enableCache
300 it('suspends siblings and later recovers each independently', async () => {
301 // Render two sibling Suspense components
302 ReactNoop.render(
303 <Fragment>
304 <Suspense fallback={<Text text="Loading A..." />}>
276 - <AsyncText text="A" ms={5000} />
305 + <AsyncText text="A" />
306 </Suspense>
307 <Suspense fallback={<Text text="Loading B..." />}>
279 - <AsyncText text="B" ms={6000} />
308 + <AsyncText text="B" />
309 </Suspense>
310 </Fragment>,
311 );
@@ -291,26 +320,22 @@ describe('ReactSuspenseWithNoopRenderer', () => {
320 span('Loading B...'),
321 ]);
322
294 - // Advance time by enough that the first Suspense's promise resolves and
295 - // switches back to the normal view. The second Suspense should still
296 - // show the placeholder
297 - ReactNoop.expire(5000);
298 - await advanceTimers(5000);
323 + // Resolve first Suspense's promise so that it switches switches back to the
324 + // normal view. The second Suspense should still show the placeholder.
325 + await resolveText('A');
326
300 - expect(Scheduler).toHaveYielded(['Promise resolved [A]']);
327 expect(Scheduler).toFlushAndYield(['A']);
328 expect(ReactNoop.getChildren()).toEqual([span('A'), span('Loading B...')]);
329
304 - // Advance time by enough that the second Suspense's promise resolves
305 - // and switches back to the normal view
306 - ReactNoop.expire(1000);
307 - await advanceTimers(1000);
330 + // Resolve the second Suspense's promise so that it switches back to the
331 + // normal view.
332 + await resolveText('B');
333
309 - expect(Scheduler).toHaveYielded(['Promise resolved [B]']);
334 expect(Scheduler).toFlushAndYield(['B']);
335 expect(ReactNoop.getChildren()).toEqual([span('A'), span('B')]);
336 });
337
338 + // @gate enableCache
339 it('continues rendering siblings after suspending', async () => {
340 // A shell is needed. The update cause it to suspend.
341 ReactNoop.render(<Suspense fallback={<Text text="Loading..." />} />);
@@ -338,7 +363,6 @@ describe('ReactSuspenseWithNoopRenderer', () => {
363 // Wait for data to resolve
364 await resolveText('B');
365 // Renders successfully
341 - expect(Scheduler).toHaveYielded(['Promise resolved [B]']);
366 expect(Scheduler).toFlushAndYield(['A', 'B', 'C', 'D']);
367 expect(ReactNoop.getChildren()).toEqual([
368 span('A'),
@@ -351,6 +375,7 @@ describe('ReactSuspenseWithNoopRenderer', () => {
375 // Second condition is redundant but guarantees that the test runs in prod.
376 // TODO: Delete this feature flag.
377 // @gate !replayFailedUnitOfWorkWithInvokeGuardedCallback || !__DEV__
378 + // @gate enableCache
379 it('retries on error', async () => {
380 class ErrorBoundary extends React.Component {
381 state = {error: null};
@@ -389,9 +414,7 @@ describe('ReactSuspenseWithNoopRenderer', () => {
414 expect(Scheduler).toFlushAndYield(['Suspend! [Result]', 'Loading...']);
415 expect(ReactNoop.getChildren()).toEqual([]);
416
392 - await rejectText('Result');
393 -
394 - expect(Scheduler).toHaveYielded(['Promise rejected [Result]']);
417 + await rejectText('Result', new Error('Failed to load: Result'));
418
419 expect(Scheduler).toFlushAndYield([
420 'Error! [Result]',
@@ -410,6 +433,7 @@ describe('ReactSuspenseWithNoopRenderer', () => {
433 // Second condition is redundant but guarantees that the test runs in prod.
434 // TODO: Delete this feature flag.
435 // @gate !replayFailedUnitOfWorkWithInvokeGuardedCallback || !__DEV__
436 + // @gate enableCache
437 it('retries on error after falling back to a placeholder', async () => {
438 class ErrorBoundary extends React.Component {
439 state = {error: null};
@@ -442,9 +466,8 @@ describe('ReactSuspenseWithNoopRenderer', () => {
466 expect(Scheduler).toFlushAndYield(['Suspend! [Result]', 'Loading...']);
467 expect(ReactNoop.getChildren()).toEqual([span('Loading...')]);
468
445 - await rejectText('Result');
469 + await rejectText('Result', new Error('Failed to load: Result'));
470
447 - expect(Scheduler).toHaveYielded(['Promise rejected [Result]']);
471 expect(Scheduler).toFlushAndYield([
472 'Error! [Result]',
473
@@ -460,6 +483,7 @@ describe('ReactSuspenseWithNoopRenderer', () => {
483 ]);
484 });
485
486 + // @gate enableCache
487 it('can update at a higher priority while in a suspended state', async () => {
488 function App(props) {
489 return (
@@ -474,7 +498,6 @@ describe('ReactSuspenseWithNoopRenderer', () => {
498 ReactNoop.render(<App highPri="A" lowPri="1" />);
499 expect(Scheduler).toFlushAndYield(['A', 'Suspend! [1]', 'Loading...']);
500 await resolveText('1');
477 - expect(Scheduler).toHaveYielded(['Promise resolved [1]']);
501 expect(Scheduler).toFlushAndYield(['A', '1']);
502 expect(ReactNoop.getChildren()).toEqual([span('A'), span('1')]);
503
@@ -497,10 +520,10 @@ describe('ReactSuspenseWithNoopRenderer', () => {
520
521 // Unblock the low-pri text and finish
522 await resolveText('2');
500 - expect(Scheduler).toHaveYielded(['Promise resolved [2]']);
523 expect(ReactNoop.getChildren()).toEqual([span('B'), span('1')]);
524 });
525
526 + // @gate enableCache
527 it('keeps working on lower priority work after being pinged', async () => {
528 // Advance the virtual time so that we're close to the edge of a bucket.
529 ReactNoop.expire(149);
@@ -530,11 +553,11 @@ describe('ReactSuspenseWithNoopRenderer', () => {
553 expect(ReactNoop.getChildren()).toEqual([]);
554
555 await resolveText('A');
533 - expect(Scheduler).toHaveYielded(['Promise resolved [A]']);
556 expect(Scheduler).toFlushAndYield(['A', 'B']);
557 expect(ReactNoop.getChildren()).toEqual([span('A'), span('B')]);
558 });
559
560 + // @gate enableCache
561 it('tries rendering a lower priority pending update even if a higher priority one suspends', async () => {
562 function App(props) {
563 if (props.hide) {
@@ -567,6 +590,7 @@ describe('ReactSuspenseWithNoopRenderer', () => {
590
591 // Note: This test was written to test a heuristic used in the expiration
592 // times model. Might not make sense in the new model.
593 + // @gate enableCache
594 it('tries each subsequent level after suspending', async () => {
595 const root = ReactNoop.createRoot();
596
@@ -638,6 +662,7 @@ describe('ReactSuspenseWithNoopRenderer', () => {
662 ]);
663 });
664
665 + // @gate enableCache
666 it('forces an expiration after an update times out', async () => {
667 ReactNoop.render(
668 <Fragment>
@@ -649,7 +674,7 @@ describe('ReactSuspenseWithNoopRenderer', () => {
674 ReactNoop.render(
675 <Fragment>
676 <Suspense fallback={<Text text="Loading..." />}>
652 - <AsyncText text="Async" ms={20000} />
677 + <AsyncText text="Async" />
678 </Suspense>
679 <Text text="Sync" />
680 </Fragment>,
@@ -667,7 +692,7 @@ describe('ReactSuspenseWithNoopRenderer', () => {
692 expect(ReactNoop.getChildren()).toEqual([]);
693
694 // Advance both React's virtual time and Jest's timers by enough to expire
670 - // the update, but not by enough to flush the suspending promise.
695 + // the update.
696 ReactNoop.expire(10000);
697 await advanceTimers(10000);
698 // No additional rendering work is required, since we already prepared
@@ -677,12 +702,12 @@ describe('ReactSuspenseWithNoopRenderer', () => {
702 expect(ReactNoop.getChildren()).toEqual([span('Loading...'), span('Sync')]);
703
704 // Once the promise resolves, we render the suspended view
680 - await advanceTimers(10000);
681 - expect(Scheduler).toHaveYielded(['Promise resolved [Async]']);
705 + await resolveText('Async');
706 expect(Scheduler).toFlushAndYield(['Async']);
707 expect(ReactNoop.getChildren()).toEqual([span('Async'), span('Sync')]);
708 });
709
710 + // @gate enableCache
711 it('switches to an inner fallback after suspending for a while', async () => {
712 // Advance the virtual time so that we're closer to the edge of a bucket.
713 ReactNoop.expire(200);
@@ -714,9 +739,7 @@ describe('ReactSuspenseWithNoopRenderer', () => {
739 ]);
740
741 // Resolve the outer promise.
717 - ReactNoop.expire(300);
718 - await advanceTimers(300);
719 - expect(Scheduler).toHaveYielded(['Promise resolved [Outer content]']);
742 + await resolveText('Outer content');
743 expect(Scheduler).toFlushAndYield([
744 'Outer content',
745 'Suspend! [Inner content]',
@@ -740,9 +763,7 @@ describe('ReactSuspenseWithNoopRenderer', () => {
763 ]);
764
765 // Finally, flush the inner promise. We should see the complete screen.
743 - ReactNoop.expire(1000);
744 - await advanceTimers(1000);
745 - expect(Scheduler).toHaveYielded(['Promise resolved [Inner content]']);
766 + await resolveText('Inner content');
767 expect(Scheduler).toFlushAndYield(['Inner content']);
768 expect(ReactNoop.getChildren()).toEqual([
769 span('Sync'),
@@ -751,6 +772,7 @@ describe('ReactSuspenseWithNoopRenderer', () => {
772 ]);
773 });
774
775 + // @gate enableCache
776 it('renders an expiration boundary synchronously', async () => {
777 spyOnDev(console, 'error');
778 // Synchronously render a tree that suspends
@@ -777,11 +799,11 @@ describe('ReactSuspenseWithNoopRenderer', () => {
799
800 // Once the promise resolves, we render the suspended view
801 await resolveText('Async');
780 - expect(Scheduler).toHaveYielded(['Promise resolved [Async]']);
802 expect(Scheduler).toFlushAndYield(['Async']);
803 expect(ReactNoop.getChildren()).toEqual([span('Async'), span('Sync')]);
804 });
805
806 + // @gate enableCache
807 it('suspending inside an expired expiration boundary will bubble to the next one', async () => {
808 ReactNoop.flushSync(() =>
809 ReactNoop.render(
@@ -805,6 +827,7 @@ describe('ReactSuspenseWithNoopRenderer', () => {
827 expect(ReactNoop.getChildren()).toEqual([span('Loading (outer)...')]);
828 });
829
830 + // @gate enableCache
831 it('expires early by default', async () => {
832 ReactNoop.render(
833 <Fragment>
@@ -816,7 +839,7 @@ describe('ReactSuspenseWithNoopRenderer', () => {
839 ReactNoop.render(
840 <Fragment>
841 <Suspense fallback={<Text text="Loading..." />}>
819 - <AsyncText text="Async" ms={3000} />
842 + <AsyncText text="Async" />
843 </Suspense>
844 <Text text="Sync" />
845 </Fragment>,
@@ -841,12 +864,12 @@ describe('ReactSuspenseWithNoopRenderer', () => {
864 expect(ReactNoop.getChildren()).toEqual([span('Loading...'), span('Sync')]);
865
866 // Once the promise resolves, we render the suspended view
844 - await advanceTimers(1000);
845 - expect(Scheduler).toHaveYielded(['Promise resolved [Async]']);
867 + await resolveText('Async');
868 expect(Scheduler).toFlushAndYield(['Async']);
869 expect(ReactNoop.getChildren()).toEqual([span('Async'), span('Sync')]);
870 });
871
872 + // @gate enableCache
873 it('resolves successfully even if fallback render is pending', async () => {
874 ReactNoop.render(
875 <>
@@ -858,17 +881,13 @@ describe('ReactSuspenseWithNoopRenderer', () => {
881 ReactNoop.render(
882 <>
883 <Suspense fallback={<Text text="Loading..." />}>
861 - <AsyncText text="Async" ms={3000} />
884 + <AsyncText text="Async" />
885 </Suspense>
886 </>,
887 );
888 expect(ReactNoop.flushNextYield()).toEqual(['Suspend! [Async]']);
866 - await advanceTimers(1500);
867 - expect(Scheduler).toHaveYielded([]);
868 - expect(ReactNoop.getChildren()).toEqual([]);
869 - // Before we have a chance to flush, the promise resolves.
870 - await advanceTimers(2000);
871 - expect(Scheduler).toHaveYielded(['Promise resolved [Async]']);
889 +
890 + await resolveText('Async');
891 expect(Scheduler).toFlushAndYield([
892 // We've now pinged the boundary but we don't know if we should restart yet,
893 // because we haven't completed the suspense boundary.
@@ -879,67 +898,58 @@ describe('ReactSuspenseWithNoopRenderer', () => {
898 expect(ReactNoop.getChildren()).toEqual([span('Async')]);
899 });
900
901 + // @gate enableCache
902 it('throws a helpful error when an update is suspends without a placeholder', () => {
883 - ReactNoop.render(<AsyncText ms={1000} text="Async" />);
903 + ReactNoop.render(<AsyncText text="Async" />);
904 expect(Scheduler).toFlushAndThrow(
905 'AsyncText suspended while rendering, but no fallback UI was specified.',
906 );
907 });
908
909 + // @gate enableCache
910 it('a Suspense component correctly handles more than one suspended child', async () => {
911 ReactNoop.render(
912 <Suspense fallback={<Text text="Loading..." />}>
892 - <AsyncText text="A" ms={100} />
893 - <AsyncText text="B" ms={100} />
913 + <AsyncText text="A" />
914 + <AsyncText text="B" />
915 </Suspense>,
916 );
896 - Scheduler.unstable_advanceTime(10000);
897 - expect(Scheduler).toFlushExpired([
917 + expect(Scheduler).toFlushAndYield([
918 'Suspend! [A]',
919 'Suspend! [B]',
920 'Loading...',
921 ]);
922 expect(ReactNoop.getChildren()).toEqual([span('Loading...')]);
923
904 - await advanceTimers(100);
924 + await resolveText('A');
925 + await resolveText('B');
926
906 - expect(Scheduler).toHaveYielded([
907 - 'Promise resolved [A]',
908 - 'Promise resolved [B]',
909 - ]);
927 expect(Scheduler).toFlushAndYield(['A', 'B']);
928 expect(ReactNoop.getChildren()).toEqual([span('A'), span('B')]);
929 });
930
931 + // @gate enableCache
932 it('can resume rendering earlier than a timeout', async () => {
933 ReactNoop.render(<Suspense fallback={<Text text="Loading..." />} />);
934 expect(Scheduler).toFlushAndYield([]);
935
936 ReactNoop.render(
937 <Suspense fallback={<Text text="Loading..." />}>
920 - <AsyncText text="Async" ms={100} />
938 + <AsyncText text="Async" />
939 </Suspense>,
940 );
941 expect(Scheduler).toFlushAndYield(['Suspend! [Async]', 'Loading...']);
942 expect(ReactNoop.getChildren()).toEqual([]);
943
926 - // Advance time by an amount slightly smaller than what's necessary to
927 - // resolve the promise
928 - await advanceTimers(99);
929 -
930 - // Nothing has rendered yet
931 - expect(Scheduler).toFlushWithoutYielding();
932 - expect(ReactNoop.getChildren()).toEqual([]);
933 -
944 // Resolve the promise
935 - await advanceTimers(1);
945 + await resolveText('Async');
946 // We can now resume rendering
937 - expect(Scheduler).toHaveYielded(['Promise resolved [Async]']);
947 expect(Scheduler).toFlushAndYield(['Async']);
948 expect(ReactNoop.getChildren()).toEqual([span('Async')]);
949 });
950
951 // @gate experimental
952 + // @gate enableCache
953 it('starts working on an update even if its priority falls between two suspended levels', async () => {
954 function App(props) {
955 return (
@@ -947,7 +957,7 @@ describe('ReactSuspenseWithNoopRenderer', () => {
957 {props.text === 'C' || props.text === 'S' ? (
958 <Text text={props.text} />
959 ) : (
950 - <AsyncText text={props.text} ms={10000} />
960 + <AsyncText text={props.text} />
961 )}
962 </Suspense>
963 );
@@ -982,17 +992,15 @@ describe('ReactSuspenseWithNoopRenderer', () => {
992 expect(Scheduler).toFlushAndYield(['C']);
993 expect(ReactNoop.getChildren()).toEqual([span('C')]);
994
985 - await advanceTimers(10000);
995 // Flush the remaining work.
987 - expect(Scheduler).toHaveYielded([
988 - 'Promise resolved [A]',
989 - 'Promise resolved [B]',
990 - ]);
996 + await resolveText('A');
997 + await resolveText('B');
998 // Nothing else to render.
999 expect(Scheduler).toFlushWithoutYielding();
1000 expect(ReactNoop.getChildren()).toEqual([span('C')]);
1001 });
1002
1003 + // @gate enableCache
1004 it('flushes all expired updates in a single batch', async () => {
1005 class Foo extends React.Component {
1006 componentDidUpdate() {
@@ -1004,7 +1012,7 @@ describe('ReactSuspenseWithNoopRenderer', () => {
1012 render() {
1013 return (
1014 <Suspense fallback={<Text text="Loading..." />}>
1007 - <AsyncText ms={20000} text={this.props.text} />
1015 + <AsyncText text={this.props.text} />
1016 </Suspense>
1017 );
1018 }
@@ -1031,22 +1039,21 @@ describe('ReactSuspenseWithNoopRenderer', () => {
1039 ]);
1040 expect(ReactNoop.getChildren()).toEqual([span('Loading...')]);
1041
1034 - Scheduler.unstable_advanceTime(20000);
1035 - await advanceTimers(20000);
1036 - expect(Scheduler).toHaveYielded(['Promise resolved [goodbye]']);
1042 + await resolveText('goodbye');
1043 expect(ReactNoop.getChildren()).toEqual([span('Loading...')]);
1044
1045 expect(Scheduler).toFlushAndYield(['goodbye']);
1046 expect(ReactNoop.getChildren()).toEqual([span('goodbye')]);
1047 });
1048
1049 + // @gate enableCache
1050 it('a suspended update that expires', async () => {
1051 // Regression test. This test used to fall into an infinite loop.
1052 function ExpensiveText({text}) {
1053 // This causes the update to expire.
1054 Scheduler.unstable_advanceTime(10000);
1055 // Then something suspends.
1049 - return <AsyncText text={text} ms={200000} />;
1056 + return <AsyncText text={text} />;
1057 }
1058
1059 function App() {
@@ -1067,12 +1074,9 @@ describe('ReactSuspenseWithNoopRenderer', () => {
1074 ]);
1075 expect(ReactNoop).toMatchRenderedOutput('Loading...');
1076
1070 - await advanceTimers(200000);
1071 - expect(Scheduler).toHaveYielded([
1072 - 'Promise resolved [A]',
1073 - 'Promise resolved [B]',
1074 - 'Promise resolved [C]',
1075 - ]);
1077 + await resolveText('A');
1078 + await resolveText('B');
1079 + await resolveText('C');
1080
1081 expect(Scheduler).toFlushAndYield(['A', 'B', 'C']);
1082 expect(ReactNoop).toMatchRenderedOutput(
@@ -1085,11 +1089,12 @@ describe('ReactSuspenseWithNoopRenderer', () => {
1089 });
1090
1091 describe('legacy mode mode', () => {
1092 + // @gate enableCache
1093 it('times out immediately', async () => {
1094 function App() {
1095 return (
1096 <Suspense fallback={<Text text="Loading..." />}>
1092 - <AsyncText ms={100} text="Result" />
1097 + <AsyncText text="Result" />
1098 </Suspense>
1099 );
1100 }
@@ -1099,19 +1104,18 @@ describe('ReactSuspenseWithNoopRenderer', () => {
1104 expect(Scheduler).toHaveYielded(['Suspend! [Result]', 'Loading...']);
1105 expect(ReactNoop.getChildren()).toEqual([span('Loading...')]);
1106
1102 - ReactNoop.expire(100);
1103 - await advanceTimers(100);
1107 + await resolveText('Result');
1108
1105 - expect(Scheduler).toHaveYielded(['Promise resolved [Result]']);
1109 expect(Scheduler).toFlushExpired(['Result']);
1110 expect(ReactNoop.getChildren()).toEqual([span('Result')]);
1111 });
1112
1113 + // @gate enableCache
1114 it('times out immediately when Suspense is in legacy mode', async () => {
1115 class UpdatingText extends React.Component {
1116 state = {step: 1};
1117 render() {
1114 - return <AsyncText ms={100} text={`Step: ${this.state.step}`} />;
1118 + return <AsyncText text={`Step: ${this.state.step}`} />;
1119 }
1120 }
1121
@@ -1136,17 +1140,9 @@ describe('ReactSuspenseWithNoopRenderer', () => {
1140 }
1141
1142 // Initial mount.
1143 + await seedNextTextCache('Step: 1');
1144 ReactNoop.renderLegacySyncRoot(<App />);
1140 - await advanceTimers(100);
1141 - expect(Scheduler).toHaveYielded([
1142 - 'Suspend! [Step: 1]',
1143 - 'Sibling',
1144 - 'Loading (1)',
1145 - 'Loading (2)',
1146 - 'Loading (3)',
1147 - 'Promise resolved [Step: 1]',
1148 - ]);
1149 - expect(Scheduler).toFlushExpired(['Step: 1']);
1145 + expect(Scheduler).toHaveYielded(['Step: 1', 'Sibling']);
1146 expect(ReactNoop).toMatchRenderedOutput(
1147 <>
1148 <span prop="Step: 1" />
@@ -1176,8 +1172,7 @@ describe('ReactSuspenseWithNoopRenderer', () => {
1172 </>,
1173 );
1174
1179 - await advanceTimers(100);
1180 - expect(Scheduler).toHaveYielded(['Promise resolved [Step: 2]']);
1175 + await resolveText('Step: 2');
1176 expect(Scheduler).toFlushExpired(['Step: 2']);
1177 expect(ReactNoop).toMatchRenderedOutput(
1178 <>
@@ -1187,6 +1182,7 @@ describe('ReactSuspenseWithNoopRenderer', () => {
1182 );
1183 });
1184
1185 + // @gate enableCache
1186 it('does not re-render siblings in loose mode', async () => {
1187 class TextWithLifecycle extends React.Component {
1188 componentDidMount() {
@@ -1216,7 +1212,7 @@ describe('ReactSuspenseWithNoopRenderer', () => {
1212 return (
1213 <Suspense fallback={<TextWithLifecycle text="Loading..." />}>
1214 <TextWithLifecycle text="A" />
1219 - <AsyncTextWithLifecycle ms={100} text="B" />
1215 + <AsyncTextWithLifecycle text="B" />
1216 <TextWithLifecycle text="C" />
1217 </Suspense>
1218 );
@@ -1247,10 +1243,8 @@ describe('ReactSuspenseWithNoopRenderer', () => {
1243 </>,
1244 );
1245
1250 - ReactNoop.expire(1000);
1251 - await advanceTimers(1000);
1246 + await resolveText('B');
1247
1253 - expect(Scheduler).toHaveYielded(['Promise resolved [B]']);
1248 expect(Scheduler).toFlushExpired(['B']);
1249 expect(ReactNoop).toMatchRenderedOutput(
1250 <>
@@ -1261,23 +1255,15 @@ describe('ReactSuspenseWithNoopRenderer', () => {
1255 );
1256 });
1257
1258 + // @gate enableCache
1259 it('suspends inside constructor', async () => {
1260 class AsyncTextInConstructor extends React.Component {
1261 constructor(props) {
1262 super(props);
1263 const text = props.text;
1264 Scheduler.unstable_yieldValue('constructor');
1270 - try {
1271 - readText(text);
1272 - this.state = {text};
1273 - } catch (promise) {
1274 - if (typeof promise.then === 'function') {
1275 - Scheduler.unstable_yieldValue(`Suspend! [${text}]`);
1276 - } else {
1277 - Scheduler.unstable_yieldValue(`Error! [${text}]`);
1278 - }
1279 - throw promise;
1280 - }
1265 + readText(text);
1266 + this.state = {text};
1267 }
1268 componentDidMount() {
1269 Scheduler.unstable_yieldValue('componentDidMount');
@@ -1290,7 +1276,7 @@ describe('ReactSuspenseWithNoopRenderer', () => {
1276
1277 ReactNoop.renderLegacySyncRoot(
1278 <Suspense fallback={<Text text="Loading..." />}>
1293 - <AsyncTextInConstructor ms={100} text="Hi" />
1279 + <AsyncTextInConstructor text="Hi" />
1280 </Suspense>,
1281 );
1282
@@ -1303,7 +1289,6 @@ describe('ReactSuspenseWithNoopRenderer', () => {
1289
1290 await resolveText('Hi');
1291
1306 - expect(Scheduler).toHaveYielded(['Promise resolved [Hi]']);
1292 expect(Scheduler).toFlushExpired([
1293 'constructor',
1294 'Hi',
@@ -1312,6 +1297,7 @@ describe('ReactSuspenseWithNoopRenderer', () => {
1297 expect(ReactNoop.getChildren()).toEqual([span('Hi')]);
1298 });
1299
1300 + // @gate enableCache
1301 it('does not infinite loop if fallback contains lifecycle method', async () => {
1302 class Fallback extends React.Component {
1303 state = {
@@ -1331,7 +1317,7 @@ describe('ReactSuspenseWithNoopRenderer', () => {
1317 render() {
1318 return (
1319 <Suspense fallback={<Fallback />}>
1334 - <AsyncText text="Hi" ms={100} />
1320 + <AsyncText text="Hi" />
1321 </Suspense>
1322 );
1323 }
@@ -1346,13 +1332,13 @@ describe('ReactSuspenseWithNoopRenderer', () => {
1332 'Loading...',
1333 ]);
1334 expect(ReactNoop.getChildren()).toEqual([span('Loading...')]);
1349 - await advanceTimers(100);
1350 - expect(Scheduler).toHaveYielded(['Promise resolved [Hi]']);
1335 + await resolveText('Hi');
1336 expect(Scheduler).toFlushExpired(['Hi']);
1337 expect(ReactNoop.getChildren()).toEqual([span('Hi')]);
1338 });
1339
1340 if (global.__PERSISTENT__) {
1341 + // @gate enableCache
1342 it('hides/unhides suspended children before layout effects fire (persistent)', async () => {
1343 const {useRef, useLayoutEffect} = React;
1344
@@ -1365,7 +1351,7 @@ describe('ReactSuspenseWithNoopRenderer', () => {
1351
1352 return (
1353 <span ref={child} hidden={false}>
1368 - <AsyncText ms={1000} text="Hi" />
1354 + <AsyncText text="Hi" />
1355 </span>
1356 );
1357 }
@@ -1390,12 +1376,11 @@ describe('ReactSuspenseWithNoopRenderer', () => {
1376 </>,
1377 ]);
1378
1393 - await advanceTimers(1000);
1394 -
1395 - expect(Scheduler).toHaveYielded(['Promise resolved [Hi]']);
1379 + await resolveText('Hi');
1380 expect(Scheduler).toFlushExpired(['Hi']);
1381 });
1382 } else {
1383 + // @gate enableCache
1384 it('hides/unhides suspended children before layout effects fire (mutation)', async () => {
1385 const {useRef, useLayoutEffect} = React;
1386
@@ -1410,7 +1395,7 @@ describe('ReactSuspenseWithNoopRenderer', () => {
1395
1396 return (
1397 <span ref={child} hidden={false}>
1413 - <AsyncText ms={1000} text="Hi" />
1398 + <AsyncText text="Hi" />
1399 </span>
1400 );
1401 }
@@ -1432,13 +1417,13 @@ describe('ReactSuspenseWithNoopRenderer', () => {
1417 'Child is hidden: true',
1418 ]);
1419
1435 - await advanceTimers(1000);
1420 + await resolveText('Hi');
1421
1437 - expect(Scheduler).toHaveYielded(['Promise resolved [Hi]']);
1422 expect(Scheduler).toFlushExpired(['Hi']);
1423 });
1424 }
1425
1426 + // @gate enableCache
1427 it('handles errors in the return path of a component that suspends', async () => {
1428 // Covers an edge case where an error is thrown inside the complete phase
1429 // of a component that is in the return path of a component that suspends.
@@ -1461,7 +1446,7 @@ describe('ReactSuspenseWithNoopRenderer', () => {
1446 <ErrorBoundary>
1447 <Suspense fallback="Loading...">
1448 <errorInCompletePhase>
1464 - <AsyncText ms={1000} text="Async" />
1449 + <AsyncText text="Async" />
1450 </errorInCompletePhase>
1451 </Suspense>
1452 </ErrorBoundary>,
@@ -1523,6 +1508,7 @@ describe('ReactSuspenseWithNoopRenderer', () => {
1508 });
1509 });
1510
1511 + // @gate enableCache
1512 it('does not call lifecycles of a suspended component', async () => {
1513 class TextWithLifecycle extends React.Component {
1514 componentDidMount() {
@@ -1551,18 +1537,9 @@ describe('ReactSuspenseWithNoopRenderer', () => {
1537 }
1538 render() {
1539 const text = this.props.text;
1554 - try {
1555 - readText(text);
1556 - Scheduler.unstable_yieldValue(text);
1557 - return <span prop={text} />;
1558 - } catch (promise) {
1559 - if (typeof promise.then === 'function') {
1560 - Scheduler.unstable_yieldValue(`Suspend! [${text}]`);
1561 - } else {
1562 - Scheduler.unstable_yieldValue(`Error! [${text}]`);
1563 - }
1564 - throw promise;
1565 - }
1540 + readText(text);
1541 + Scheduler.unstable_yieldValue(text);
1542 + return <span prop={text} />;
1543 }
1544 }
1545
@@ -1570,7 +1547,7 @@ describe('ReactSuspenseWithNoopRenderer', () => {
1547 return (
1548 <Suspense fallback={<TextWithLifecycle text="Loading..." />}>
1549 <TextWithLifecycle text="A" />
1573 - <AsyncTextWithLifecycle ms={100} text="B" />
1550 + <AsyncTextWithLifecycle text="B" />
1551 <TextWithLifecycle text="C" />
1552 </Suspense>
1553 );
@@ -1601,6 +1578,7 @@ describe('ReactSuspenseWithNoopRenderer', () => {
1578 );
1579 });
1580
1581 + // @gate enableCache
1582 it('does not call lifecycles of a suspended component (hooks)', async () => {
1583 function TextWithLifecycle(props) {
1584 React.useLayoutEffect(() => {
@@ -1636,25 +1614,16 @@ describe('ReactSuspenseWithNoopRenderer', () => {
1614 };
1615 }, [props.text]);
1616 const text = props.text;
1639 - try {
1640 - readText(text);
1641 - Scheduler.unstable_yieldValue(text);
1642 - return <span prop={text} />;
1643 - } catch (promise) {
1644 - if (typeof promise.then === 'function') {
1645 - Scheduler.unstable_yieldValue(`Suspend! [${text}]`);
1646 - } else {
1647 - Scheduler.unstable_yieldValue(`Error! [${text}]`);
1648 - }
1649 - throw promise;
1650 - }
1617 + readText(text);
1618 + Scheduler.unstable_yieldValue(text);
1619 + return <span prop={text} />;
1620 }
1621
1622 function App({text}) {
1623 return (
1624 <Suspense fallback={<TextWithLifecycle text="Loading..." />}>
1625 <TextWithLifecycle text="A" />
1657 - <AsyncTextWithLifecycle ms={100} text={text} />
1626 + <AsyncTextWithLifecycle text={text} />
1627 <TextWithLifecycle text="C" />
1628 </Suspense>
1629 );
@@ -1696,8 +1665,6 @@ describe('ReactSuspenseWithNoopRenderer', () => {
1665
1666 await resolveText('B');
1667
1699 - expect(Scheduler).toHaveYielded(['Promise resolved [B]']);
1700 -
1668 expect(Scheduler).toFlushAndYield([
1669 'B',
1670 'Destroy Layout Effect [Loading...]',
@@ -1732,7 +1699,6 @@ describe('ReactSuspenseWithNoopRenderer', () => {
1699
1700 await resolveText('B2');
1701
1735 - expect(Scheduler).toHaveYielded(['Promise resolved [B2]']);
1702 expect(Scheduler).toFlushAndYield([
1703 'B2',
1704 'Destroy Layout Effect [Loading...]',
@@ -1744,12 +1710,13 @@ describe('ReactSuspenseWithNoopRenderer', () => {
1710 ]);
1711 });
1712
1713 + // @gate enableCache
1714 it('suspends for longer if something took a long (CPU bound) time to render', async () => {
1715 function Foo({renderContent}) {
1716 Scheduler.unstable_yieldValue('Foo');
1717 return (
1718 <Suspense fallback={<Text text="Loading..." />}>
1752 - {renderContent ? <AsyncText text="A" ms={5000} /> : null}
1719 + {renderContent ? <AsyncText text="A" /> : null}
1720 </Suspense>
1721 );
1722 }
@@ -1789,22 +1756,21 @@ describe('ReactSuspenseWithNoopRenderer', () => {
1756 expect(ReactNoop.getChildren()).toEqual([span('Loading...')]);
1757
1758 // Flush the promise completely
1792 - Scheduler.unstable_advanceTime(4500);
1793 - await advanceTimers(4500);
1759 + await resolveText('A');
1760 // Renders successfully
1795 - expect(Scheduler).toHaveYielded(['Promise resolved [A]']);
1761 expect(Scheduler).toFlushAndYield(['A']);
1762 expect(ReactNoop.getChildren()).toEqual([span('A')]);
1763 });
1764
1765 + // @gate enableCache
1766 it('does not suspends if a fallback has been shown for a long time', async () => {
1767 function Foo() {
1768 Scheduler.unstable_yieldValue('Foo');
1769 return (
1770 <Suspense fallback={<Text text="Loading..." />}>
1805 - <AsyncText text="A" ms={5000} />
1771 + <AsyncText text="A" />
1772 <Suspense fallback={<Text text="Loading more..." />}>
1807 - <AsyncText text="B" ms={10000} />
1773 + <AsyncText text="B" />
1774 </Suspense>
1775 </Suspense>
1776 );
@@ -1823,10 +1789,10 @@ describe('ReactSuspenseWithNoopRenderer', () => {
1789 ]);
1790 expect(ReactNoop.getChildren()).toEqual([span('Loading...')]);
1791
1792 + await resolveText('A');
1793 // Wait a long time.
1794 Scheduler.unstable_advanceTime(5000);
1795 await advanceTimers(5000);
1829 - expect(Scheduler).toHaveYielded(['Promise resolved [A]']);
1796
1797 // Retry with the new content.
1798 expect(Scheduler).toFlushAndYield([
@@ -1843,22 +1809,21 @@ describe('ReactSuspenseWithNoopRenderer', () => {
1809 ]);
1810
1811 // Flush the last promise completely
1846 - Scheduler.unstable_advanceTime(5000);
1847 - await advanceTimers(5000);
1812 + await resolveText('B');
1813 // Renders successfully
1849 - expect(Scheduler).toHaveYielded(['Promise resolved [B]']);
1814 expect(Scheduler).toFlushAndYield(['B']);
1815 expect(ReactNoop.getChildren()).toEqual([span('A'), span('B')]);
1816 });
1817
1818 + // @gate enableCache
1819 it('does suspend if a fallback has been shown for a short time', async () => {
1820 function Foo() {
1821 Scheduler.unstable_yieldValue('Foo');
1822 return (
1823 <Suspense fallback={<Text text="Loading..." />}>
1859 - <AsyncText text="A" ms={200} />
1824 + <AsyncText text="A" />
1825 <Suspense fallback={<Text text="Loading more..." />}>
1861 - <AsyncText text="B" ms={450} />
1826 + <AsyncText text="B" />
1827 </Suspense>
1828 </Suspense>
1829 );
@@ -1877,10 +1842,7 @@ describe('ReactSuspenseWithNoopRenderer', () => {
1842 ]);
1843 expect(ReactNoop.getChildren()).toEqual([span('Loading...')]);
1844
1880 - // Wait a short time.
1881 - Scheduler.unstable_advanceTime(250);
1882 - await advanceTimers(250);
1883 - expect(Scheduler).toHaveYielded(['Promise resolved [A]']);
1845 + await resolveText('A');
1846
1847 // Retry with the new content.
1848 expect(Scheduler).toFlushAndYield([
@@ -1893,11 +1855,9 @@ describe('ReactSuspenseWithNoopRenderer', () => {
1855 // wait a bit longer. Still nothing...
1856 expect(ReactNoop.getChildren()).toEqual([span('Loading...')]);
1857
1896 - Scheduler.unstable_advanceTime(200);
1897 - await advanceTimers(200);
1858 + await resolveText('B');
1859
1860 // Before we commit another Promise resolves.
1900 - expect(Scheduler).toHaveYielded(['Promise resolved [B]']);
1861 // We're still showing the first loading state.
1862 expect(ReactNoop.getChildren()).toEqual([span('Loading...')]);
1863 // Restart and render the complete content.
@@ -1905,12 +1865,13 @@ describe('ReactSuspenseWithNoopRenderer', () => {
1865 expect(ReactNoop.getChildren()).toEqual([span('A'), span('B')]);
1866 });
1867
1868 + // @gate enableCache
1869 it('does not suspend for very long after a higher priority update', async () => {
1870 function Foo({renderContent}) {
1871 Scheduler.unstable_yieldValue('Foo');
1872 return (
1873 <Suspense fallback={<Text text="Loading..." />}>
1913 - {renderContent ? <AsyncText text="A" ms={5000} /> : null}
1874 + {renderContent ? <AsyncText text="A" /> : null}
1875 </Suspense>
1876 );
1877 }
@@ -1949,6 +1910,7 @@ describe('ReactSuspenseWithNoopRenderer', () => {
1910 });
1911
1912 // TODO: flip to "warns" when this is implemented again.
1913 + // @gate enableCache
1914 it('does not warn when a low priority update suspends inside a high priority update for functional components', async () => {
1915 let _setShow;
1916 function App() {
@@ -1975,6 +1937,7 @@ describe('ReactSuspenseWithNoopRenderer', () => {
1937 });
1938
1939 // TODO: flip to "warns" when this is implemented again.
1940 + // @gate enableCache
1941 it('does not warn when a low priority update suspends inside a high priority update for class components', async () => {
1942 let show;
1943 class App extends React.Component {
@@ -2003,6 +1966,7 @@ describe('ReactSuspenseWithNoopRenderer', () => {
1966 });
1967 });
1968
1969 + // @gate enableCache
1970 it('does not warn about wrong Suspense priority if no new fallbacks are shown', async () => {
1971 let showB;
1972 class App extends React.Component {
@@ -2037,6 +2001,7 @@ describe('ReactSuspenseWithNoopRenderer', () => {
2001 });
2002
2003 // TODO: flip to "warns" when this is implemented again.
2004 + // @gate enableCache
2005 it(
2006 'does not warn when component that triggered user-blocking update is between Suspense boundary ' +
2007 'and component that suspended',
@@ -2068,6 +2033,7 @@ describe('ReactSuspenseWithNoopRenderer', () => {
2033 },
2034 );
2035
2036 + // @gate enableCache
2037 it('normal priority updates suspending do not warn for class components', async () => {
2038 let show;
2039 class App extends React.Component {
@@ -2093,9 +2059,10 @@ describe('ReactSuspenseWithNoopRenderer', () => {
2059 expect(Scheduler).toHaveYielded(['Suspend! [A]']);
2060 await resolveText('A');
2061
2096 - expect(Scheduler).toHaveYielded(['Promise resolved [A]']);
2062 + expect(ReactNoop).toMatchRenderedOutput('Loading...');
2063 });
2064
2065 + // @gate enableCache
2066 it('normal priority updates suspending do not warn for functional components', async () => {
2067 let _setShow;
2068 function App() {
@@ -2118,9 +2085,10 @@ describe('ReactSuspenseWithNoopRenderer', () => {
2085 expect(Scheduler).toHaveYielded(['Suspend! [A]']);
2086 await resolveText('A');
2087
2121 - expect(Scheduler).toHaveYielded(['Promise resolved [A]']);
2088 + expect(ReactNoop).toMatchRenderedOutput('Loading...');
2089 });
2090
2091 + // @gate enableCache
2092 it('shows the parent fallback if the inner fallback should be avoided', async () => {
2093 function Foo({showC}) {
2094 Scheduler.unstable_yieldValue('Foo');
@@ -2129,8 +2097,8 @@ describe('ReactSuspenseWithNoopRenderer', () => {
2097 <Suspense
2098 unstable_avoidThisFallback={true}
2099 fallback={<Text text="Updating..." />}>
2132 - <AsyncText text="A" ms={5000} />
2133 - {showC ? <AsyncText text="C" ms={5000} /> : null}
2100 + <AsyncText text="A" />
2101 + {showC ? <AsyncText text="C" /> : null}
2102 </Suspense>
2103 <Text text="B" />
2104 </Suspense>
@@ -2147,9 +2115,7 @@ describe('ReactSuspenseWithNoopRenderer', () => {
2115 expect(ReactNoop.getChildren()).toEqual([span('Initial load...')]);
2116
2117 // Eventually we resolve and show the data.
2150 - Scheduler.unstable_advanceTime(5000);
2151 - await advanceTimers(5000);
2152 - expect(Scheduler).toHaveYielded(['Promise resolved [A]']);
2118 + await resolveText('A');
2119 expect(Scheduler).toFlushAndYield(['A', 'B']);
2120 expect(ReactNoop.getChildren()).toEqual([span('A'), span('B')]);
2121
@@ -2174,13 +2140,12 @@ describe('ReactSuspenseWithNoopRenderer', () => {
2140 ]);
2141
2142 // Later we load the data.
2177 - Scheduler.unstable_advanceTime(5000);
2178 - await advanceTimers(5000);
2179 - expect(Scheduler).toHaveYielded(['Promise resolved [C]']);
2143 + await resolveText('C');
2144 expect(Scheduler).toFlushAndYield(['A', 'C']);
2145 expect(ReactNoop.getChildren()).toEqual([span('A'), span('C'), span('B')]);
2146 });
2147
2148 + // @gate enableCache
2149 it('favors showing the inner fallback for nested top level avoided fallback', async () => {
2150 function Foo({showB}) {
2151 Scheduler.unstable_yieldValue('Foo');
@@ -2192,7 +2157,7 @@ describe('ReactSuspenseWithNoopRenderer', () => {
2157 <Suspense
2158 unstable_avoidThisFallback={true}
2159 fallback={<Text text="Loading B..." />}>
2195 - <AsyncText text="B" ms={5000} />
2160 + <AsyncText text="B" />
2161 </Suspense>
2162 </Suspense>
2163 );
@@ -2212,6 +2177,7 @@ describe('ReactSuspenseWithNoopRenderer', () => {
2177 expect(ReactNoop.getChildren()).toEqual([span('A'), span('Loading B...')]);
2178 });
2179
2180 + // @gate enableCache
2181 it('keeps showing an avoided parent fallback if it is already showing', async () => {
2182 function Foo({showB}) {
2183 Scheduler.unstable_yieldValue('Foo');
@@ -2225,7 +2191,7 @@ describe('ReactSuspenseWithNoopRenderer', () => {
2191 <Suspense
2192 unstable_avoidThisFallback={true}
2193 fallback={<Text text="Loading B..." />}>
2228 - <AsyncText text="B" ms={5000} />
2194 + <AsyncText text="B" />
2195 </Suspense>
2196 ) : null}
2197 </Suspense>
@@ -2255,12 +2221,13 @@ describe('ReactSuspenseWithNoopRenderer', () => {
2221 expect(ReactNoop.getChildren()).toEqual([span('A'), span('Loading B...')]);
2222 });
2223
2224 + // @gate enableCache
2225 it('commits a suspended idle pri render within a reasonable time', async () => {
2226 function Foo({renderContent}) {
2227 return (
2228 <Fragment>
2229 <Suspense fallback={<Text text="Loading A..." />}>
2263 - {renderContent ? <AsyncText text="A" ms={10000} /> : null}
2230 + {renderContent ? <AsyncText text="A" /> : null}
2231 </Suspense>
2232 </Fragment>
2233 );
@@ -2306,11 +2273,12 @@ describe('ReactSuspenseWithNoopRenderer', () => {
2273
2274 describe('startTransition', () => {
2275 // @gate experimental
2276 + // @gate enableCache
2277 it('top level render', async () => {
2278 function App({page}) {
2279 return (
2280 <Suspense fallback={<Text text="Loading..." />}>
2313 - <AsyncText text={page} ms={5000} />
2281 + <AsyncText text={page} />
2282 </Suspense>
2283 );
2284 }
@@ -2325,9 +2293,7 @@ describe('ReactSuspenseWithNoopRenderer', () => {
2293 expect(ReactNoop.getChildren()).toEqual([span('Loading...')]);
2294
2295 // Later we load the data.
2328 - Scheduler.unstable_advanceTime(5000);
2329 - await advanceTimers(5000);
2330 - expect(Scheduler).toHaveYielded(['Promise resolved [A]']);
2296 + await resolveText('A');
2297 expect(Scheduler).toFlushAndYield(['A']);
2298 expect(ReactNoop.getChildren()).toEqual([span('A')]);
2299
@@ -2341,14 +2307,13 @@ describe('ReactSuspenseWithNoopRenderer', () => {
2307 // loading state.
2308 expect(ReactNoop.getChildren()).toEqual([span('A')]);
2309 // Later we load the data.
2344 - Scheduler.unstable_advanceTime(3000);
2345 - await advanceTimers(3000);
2346 - expect(Scheduler).toHaveYielded(['Promise resolved [B]']);
2310 + await resolveText('B');
2311 expect(Scheduler).toFlushAndYield(['B']);
2312 expect(ReactNoop.getChildren()).toEqual([span('B')]);
2313 });
2314
2315 // @gate experimental
2316 + // @gate enableCache
2317 it('hooks', async () => {
2318 let transitionToPage;
2319 function App() {
@@ -2380,7 +2345,6 @@ describe('ReactSuspenseWithNoopRenderer', () => {
2345
2346 // Later we load the data.
2347 await resolveText('A');
2383 - expect(Scheduler).toHaveYielded(['Promise resolved [A]']);
2348 expect(Scheduler).toFlushAndYield(['A']);
2349 expect(ReactNoop.getChildren()).toEqual([span('A')]);
2350
@@ -2397,12 +2361,12 @@ describe('ReactSuspenseWithNoopRenderer', () => {
2361 });
2362 // Later we load the data.
2363 await resolveText('B');
2400 - expect(Scheduler).toHaveYielded(['Promise resolved [B]']);
2364 expect(Scheduler).toFlushAndYield(['B']);
2365 expect(ReactNoop.getChildren()).toEqual([span('B')]);
2366 });
2367
2368 // @gate experimental
2369 + // @gate enableCache
2370 it('classes', async () => {
2371 let transitionToPage;
2372 class App extends React.Component {
@@ -2437,7 +2401,6 @@ describe('ReactSuspenseWithNoopRenderer', () => {
2401
2402 // Later we load the data.
2403 await resolveText('A');
2440 - expect(Scheduler).toHaveYielded(['Promise resolved [A]']);
2404 expect(Scheduler).toFlushAndYield(['A']);
2405 expect(ReactNoop.getChildren()).toEqual([span('A')]);
2406
@@ -2454,7 +2417,6 @@ describe('ReactSuspenseWithNoopRenderer', () => {
2417 });
2418 // Later we load the data.
2419 await resolveText('B');
2457 - expect(Scheduler).toHaveYielded(['Promise resolved [B]']);
2420 expect(Scheduler).toFlushAndYield(['B']);
2421 expect(ReactNoop.getChildren()).toEqual([span('B')]);
2422 });
@@ -2462,6 +2424,7 @@ describe('ReactSuspenseWithNoopRenderer', () => {
2424
2425 describe('delays transitions when using React.startTranistion', () => {
2426 // @gate experimental
2427 + // @gate enableCache
2428 it('top level render', async () => {
2429 function App({page}) {
2430 return (
@@ -2482,7 +2445,6 @@ describe('ReactSuspenseWithNoopRenderer', () => {
2445
2446 // Later we load the data.
2447 await resolveText('A');
2485 - expect(Scheduler).toHaveYielded(['Promise resolved [A]']);
2448 expect(Scheduler).toFlushAndYield(['A']);
2449 expect(ReactNoop.getChildren()).toEqual([span('A')]);
2450
@@ -2498,7 +2460,6 @@ describe('ReactSuspenseWithNoopRenderer', () => {
2460
2461 // Later we load the data.
2462 await resolveText('B');
2501 - expect(Scheduler).toHaveYielded(['Promise resolved [B]']);
2463 expect(Scheduler).toFlushAndYield(['B']);
2464 expect(ReactNoop.getChildren()).toEqual([span('B')]);
2465
@@ -2514,6 +2475,7 @@ describe('ReactSuspenseWithNoopRenderer', () => {
2475 });
2476
2477 // @gate experimental
2478 + // @gate enableCache
2479 it('hooks', async () => {
2480 let transitionToPage;
2481 function App() {
@@ -2545,7 +2507,6 @@ describe('ReactSuspenseWithNoopRenderer', () => {
2507
2508 // Later we load the data.
2509 await resolveText('A');
2548 - expect(Scheduler).toHaveYielded(['Promise resolved [A]']);
2510 expect(Scheduler).toFlushAndYield(['A']);
2511 expect(ReactNoop.getChildren()).toEqual([span('A')]);
2512
@@ -2564,7 +2525,6 @@ describe('ReactSuspenseWithNoopRenderer', () => {
2525
2526 // Later we load the data.
2527 await resolveText('B');
2567 - expect(Scheduler).toHaveYielded(['Promise resolved [B]']);
2528 expect(Scheduler).toFlushAndYield(['B']);
2529 expect(ReactNoop.getChildren()).toEqual([span('B')]);
2530
@@ -2583,6 +2543,7 @@ describe('ReactSuspenseWithNoopRenderer', () => {
2543 });
2544
2545 // @gate experimental
2546 + // @gate enableCache
2547 it('classes', async () => {
2548 let transitionToPage;
2549 class App extends React.Component {
@@ -2617,7 +2578,6 @@ describe('ReactSuspenseWithNoopRenderer', () => {
2578
2579 // Later we load the data.
2580 await resolveText('A');
2620 - expect(Scheduler).toHaveYielded(['Promise resolved [A]']);
2581 expect(Scheduler).toFlushAndYield(['A']);
2582 expect(ReactNoop.getChildren()).toEqual([span('A')]);
2583
@@ -2635,7 +2595,6 @@ describe('ReactSuspenseWithNoopRenderer', () => {
2595
2596 // Later we load the data.
2597 await resolveText('B');
2638 - expect(Scheduler).toHaveYielded(['Promise resolved [B]']);
2598 expect(Scheduler).toFlushAndYield(['B']);
2599 expect(ReactNoop.getChildren()).toEqual([span('B')]);
2600
@@ -2655,6 +2614,7 @@ describe('ReactSuspenseWithNoopRenderer', () => {
2614 });
2615
2616 // @gate experimental
2617 + // @gate enableCache
2618 it('do not show placeholder when updating an avoided boundary with startTransition', async () => {
2619 function App({page}) {
2620 return (
@@ -2673,7 +2633,6 @@ describe('ReactSuspenseWithNoopRenderer', () => {
2633 ReactNoop.render(<App page="A" />);
2634 expect(Scheduler).toFlushAndYield(['Hi!', 'Suspend! [A]', 'Loading...']);
2635 await resolveText('A');
2676 - expect(Scheduler).toHaveYielded(['Promise resolved [A]']);
2636 expect(Scheduler).toFlushAndYield(['Hi!', 'A']);
2637 expect(ReactNoop.getChildren()).toEqual([span('Hi!'), span('A')]);
2638
@@ -2690,7 +2649,6 @@ describe('ReactSuspenseWithNoopRenderer', () => {
2649 // We should still be suspended here because this loading state should be avoided.
2650 expect(ReactNoop.getChildren()).toEqual([span('Hi!'), span('A')]);
2651 await resolveText('B');
2693 - expect(Scheduler).toHaveYielded(['Promise resolved [B]']);
2652 expect(Scheduler).toFlushAndYield(['Hi!', 'B']);
2653 expect(ReactNoop).toMatchRenderedOutput(
2654 <>
@@ -2701,6 +2659,7 @@ describe('ReactSuspenseWithNoopRenderer', () => {
2659 });
2660
2661 // @gate experimental
2662 + // @gate enableCache
2663 it('do not show placeholder when mounting an avoided boundary with startTransition', async () => {
2664 function App({page}) {
2665 return (
@@ -2742,7 +2701,6 @@ describe('ReactSuspenseWithNoopRenderer', () => {
2701 </>,
2702 );
2703 await resolveText('B');
2745 - expect(Scheduler).toHaveYielded(['Promise resolved [B]']);
2704 expect(Scheduler).toFlushAndYield(['Hi!', 'B']);
2705 expect(ReactNoop).toMatchRenderedOutput(
2706 <>
@@ -2754,6 +2712,7 @@ describe('ReactSuspenseWithNoopRenderer', () => {
2712
2713 // TODO: This test is specifically about avoided commits that suspend for a
2714 // JND. We may remove this behavior.
2715 + // @gate enableCache
2716 it("suspended commit remains suspended even if there's another update at same expiration", async () => {
2717 // Regression test
2718 function App({text}) {
@@ -2774,7 +2733,7 @@ describe('ReactSuspenseWithNoopRenderer', () => {
2733 await ReactNoop.act(async () => {
2734 await resolveText('Initial');
2735 });
2777 - expect(Scheduler).toHaveYielded(['Promise resolved [Initial]', 'Initial']);
2736 + expect(Scheduler).toHaveYielded(['Initial']);
2737 expect(root).toMatchRenderedOutput(<span prop="Initial" />);
2738
2739 await ReactNoop.act(async () => {
@@ -2853,6 +2812,7 @@ describe('ReactSuspenseWithNoopRenderer', () => {
2812 });
2813
2814 // @gate experimental
2815 + // @gate enableCache
2816 it('should not render hidden content while suspended on higher pri', async () => {
2817 function Offscreen() {
2818 Scheduler.unstable_yieldValue('Offscreen');
@@ -2885,9 +2845,7 @@ describe('ReactSuspenseWithNoopRenderer', () => {
2845 });
2846
2847 expect(Scheduler).toFlushAndYield(['Suspend! [A]', 'Loading...']);
2888 - Scheduler.unstable_advanceTime(2000);
2889 - await advanceTimers(2000);
2890 - expect(Scheduler).toHaveYielded(['Promise resolved [A]']);
2848 + await resolveText('A');
2849 expect(Scheduler).toFlushAndYieldThrough(['A', 'Commit']);
2850 expect(ReactNoop).toMatchRenderedOutput(
2851 <>
@@ -2905,6 +2863,7 @@ describe('ReactSuspenseWithNoopRenderer', () => {
2863 });
2864
2865 // @gate experimental
2866 + // @gate enableCache
2867 it('should be able to unblock higher pri content before suspended hidden', async () => {
2868 function Offscreen() {
2869 Scheduler.unstable_yieldValue('Offscreen');
@@ -2917,10 +2876,10 @@ describe('ReactSuspenseWithNoopRenderer', () => {
2876 return (
2877 <Suspense fallback={<Text text="Loading..." />}>
2878 <LegacyHiddenDiv mode="hidden">
2920 - <AsyncText text="A" ms={2000} />
2879 + <AsyncText text="A" />
2880 <Offscreen />
2881 </LegacyHiddenDiv>
2923 - {showContent ? <AsyncText text="A" ms={2000} /> : null}
2882 + {showContent ? <AsyncText text="A" /> : null}
2883 </Suspense>
2884 );
2885 }
@@ -2939,9 +2898,7 @@ describe('ReactSuspenseWithNoopRenderer', () => {
2898 });
2899
2900 expect(Scheduler).toFlushAndYield(['Suspend! [A]', 'Loading...']);
2942 - Scheduler.unstable_advanceTime(2000);
2943 - await advanceTimers(2000);
2944 - expect(Scheduler).toHaveYielded(['Promise resolved [A]']);
2901 + await resolveText('A');
2902 expect(Scheduler).toFlushAndYieldThrough(['A', 'Commit']);
2903 expect(ReactNoop).toMatchRenderedOutput(
2904 <>
@@ -2961,6 +2918,7 @@ describe('ReactSuspenseWithNoopRenderer', () => {
2918 );
2919 });
2920
2921 + // @gate enableCache
2922 it(
2923 'multiple updates originating inside a Suspense boundary at different ' +
2924 'priority levels are not dropped',
@@ -2985,7 +2943,7 @@ describe('ReactSuspenseWithNoopRenderer', () => {
2943 return <AsyncText text={text} />;
2944 }
2945
2988 - await resolveText('A');
2946 + await seedNextTextCache('A');
2947 await ReactNoop.act(async () => {
2948 root.render(<Parent />);
2949 });
@@ -3014,6 +2972,7 @@ describe('ReactSuspenseWithNoopRenderer', () => {
2972 },
2973 );
2974
2975 + // @gate enableCache
2976 it(
2977 'multiple updates originating inside a Suspense boundary at different ' +
2978 'priority levels are not dropped, including Idle updates',
@@ -3038,7 +2997,7 @@ describe('ReactSuspenseWithNoopRenderer', () => {
2997 return <AsyncText text={text} />;
2998 }
2999
3041 - await resolveText('A');
3000 + await seedNextTextCache('A');
3001 await ReactNoop.act(async () => {
3002 root.render(<Parent />);
3003 });
@@ -3081,6 +3040,7 @@ describe('ReactSuspenseWithNoopRenderer', () => {
3040 },
3041 );
3042
3043 + // @gate enableCache
3044 it(
3045 'fallback component can update itself even after a high pri update to ' +
3046 'the primary tree suspends',
@@ -3109,7 +3069,7 @@ describe('ReactSuspenseWithNoopRenderer', () => {
3069 }
3070
3071 // Resolve the initial tree
3112 - await resolveText('A');
3072 + await seedNextTextCache('A');
3073 await ReactNoop.act(async () => {
3074 root.render(<App />);
3075 });
@@ -3165,6 +3125,7 @@ describe('ReactSuspenseWithNoopRenderer', () => {
3125 },
3126 );
3127
3128 + // @gate enableCache
3129 it(
3130 'regression: primary fragment fiber is not always part of setState ' +
3131 'return path',
@@ -3193,7 +3154,7 @@ describe('ReactSuspenseWithNoopRenderer', () => {
3154 }
3155
3156 // Mount an initial tree. Resolve A so that it doesn't suspend.
3196 - await resolveText('A');
3157 + await seedNextTextCache('A');
3158 await ReactNoop.act(async () => {
3159 root.render(<Parent />);
3160 });
@@ -3243,6 +3204,7 @@ describe('ReactSuspenseWithNoopRenderer', () => {
3204 },
3205 );
3206
3207 + // @gate enableCache
3208 it(
3209 'regression: primary fragment fiber is not always part of setState ' +
3210 'return path (another case)',
@@ -3269,7 +3231,7 @@ describe('ReactSuspenseWithNoopRenderer', () => {
3231 }
3232
3233 // Mount an initial tree. Resolve A so that it doesn't suspend.
3272 - await resolveText('A');
3234 + await seedNextTextCache('A');
3235 await ReactNoop.act(async () => {
3236 root.render(<Parent />);
3237 });
@@ -3327,6 +3289,7 @@ describe('ReactSuspenseWithNoopRenderer', () => {
3289 },
3290 );
3291
3292 + // @gate enableCache
3293 it(
3294 'after showing fallback, should not flip back to primary content until ' +
3295 'the update that suspended finishes',
@@ -3380,7 +3343,7 @@ describe('ReactSuspenseWithNoopRenderer', () => {
3343 }
3344
3345 // Mount an initial tree. Resolve A so that it doesn't suspend.
3383 - await resolveText('Inner text: A');
3346 + await seedNextTextCache('Inner text: A');
3347 await ReactNoop.act(async () => {
3348 root.render(<Parent step={0} />);
3349 });
@@ -3455,7 +3418,6 @@ describe('ReactSuspenseWithNoopRenderer', () => {
3418 await resolveText('Inner text: B');
3419 });
3420 expect(Scheduler).toHaveYielded([
3458 - 'Promise resolved [Inner text: B]',
3421 'Inner text: B',
3422 'Inner step: 1',
3423 'Commit Child',
@@ -3471,6 +3433,7 @@ describe('ReactSuspenseWithNoopRenderer', () => {
3433 },
3434 );
3435
3436 + // @gate enableCache
3437 it('a high pri update can unhide a boundary that suspended at a different level', async () => {
3438 const {useState, useEffect} = React;
3439 const root = ReactNoop.createRoot();
@@ -3519,7 +3482,7 @@ describe('ReactSuspenseWithNoopRenderer', () => {
3482 }
3483
3484 // Mount an initial tree. Resolve A so that it doesn't suspend.
3522 - await resolveText('Inner: A0');
3485 + await seedNextTextCache('Inner: A0');
3486 await ReactNoop.act(async () => {
3487 root.render(<Parent step={0} />);
3488 });
@@ -3567,6 +3530,7 @@ describe('ReactSuspenseWithNoopRenderer', () => {
3530 );
3531 });
3532
3533 + // @gate enableCache
3534 it('regression: empty render at high priority causes update to be dropped', async () => {
3535 // Reproduces a bug where flushDiscreteUpdates starts a new (empty) render
3536 // pass which cancels a scheduled timeout and causes the fallback never to
@@ -3616,6 +3580,7 @@ describe('ReactSuspenseWithNoopRenderer', () => {
3580 });
3581
3582 // @gate experimental
3583 + // @gate enableCache
3584 it('regression: ping at high priority causes update to be dropped', async () => {
3585 const {useState, unstable_useTransition: useTransition} = React;
3586
@@ -3655,8 +3620,8 @@ describe('ReactSuspenseWithNoopRenderer', () => {
3620
3621 const root = ReactNoop.createRoot();
3622 await ReactNoop.act(async () => {
3658 - await resolveText('A');
3659 - await resolveText('B');
3623 + await seedNextTextCache('A');
3624 + await seedNextTextCache('B');
3625 root.render(<App />);
3626 });
3627 expect(Scheduler).toHaveYielded(['A', 'B']);
@@ -3698,7 +3663,6 @@ describe('ReactSuspenseWithNoopRenderer', () => {
3663 );
3664
3665 await resolveText('A1');
3701 - expect(Scheduler).toHaveYielded(['Promise resolved [A1]']);
3666 expect(Scheduler).toFlushAndYield([
3667 'A1',
3668 'Suspend! [A2]',
@@ -3716,12 +3680,7 @@ describe('ReactSuspenseWithNoopRenderer', () => {
3680 await resolveText('A2');
3681 await resolveText('B2');
3682 });
3719 - expect(Scheduler).toHaveYielded([
3720 - 'Promise resolved [A2]',
3721 - 'Promise resolved [B2]',
3722 - 'A2',
3723 - 'B2',
3724 - ]);
3683 + expect(Scheduler).toHaveYielded(['A2', 'B2']);
3684 expect(root).toMatchRenderedOutput(
3685 <>
3686 <span prop="A2" />
@@ -3732,6 +3691,7 @@ describe('ReactSuspenseWithNoopRenderer', () => {
3691
3692 // Regression: https://github.com/facebook/react/issues/18486
3693 // @gate experimental
3694 + // @gate enableCache
3695 it('does not get stuck in pending state with render phase updates', async () => {
3696 let setTextWithShortTransition;
3697 let setTextWithLongTransition;
@@ -3827,7 +3787,6 @@ describe('ReactSuspenseWithNoopRenderer', () => {
3787 await ReactNoop.act(async () => {
3788 await resolveText('a');
3789
3830 - expect(Scheduler).toHaveYielded(['Promise resolved [a]']);
3790 expect(Scheduler).toFlushAndYield(['Suspend! [b]', 'Loading...']);
3791 expect(root).toMatchRenderedOutput(
3792 <>
@@ -3840,12 +3799,13 @@ describe('ReactSuspenseWithNoopRenderer', () => {
3799 await ReactNoop.act(async () => {
3800 await resolveText('b');
3801 });
3843 - expect(Scheduler).toHaveYielded(['Promise resolved [b]', 'b']);
3802 + expect(Scheduler).toHaveYielded(['b']);
3803 // The bug was that the pending state got stuck forever.
3804 expect(root).toMatchRenderedOutput(<span prop="b" />);
3805 });
3806 });
3807
3808 + // @gate enableCache
3809 it('regression: #18657', async () => {
3810 const {useState} = React;
3811
@@ -3858,7 +3818,7 @@ describe('ReactSuspenseWithNoopRenderer', () => {
3818
3819 const root = ReactNoop.createRoot();
3820 await ReactNoop.act(async () => {
3861 - await resolveText('A');
3821 + await seedNextTextCache('A');
3822 root.render(
3823 <Suspense fallback={<Text text="Loading..." />}>
3824 <App />
@@ -3891,10 +3851,11 @@ describe('ReactSuspenseWithNoopRenderer', () => {
3851 setText('B');
3852 await resolveText('B');
3853 });
3894 - expect(Scheduler).toHaveYielded(['Promise resolved [B]', 'B']);
3854 + expect(Scheduler).toHaveYielded(['B']);
3855 expect(root).toMatchRenderedOutput(<span prop="B" />);
3856 });
3857
3858 + // @gate enableCache
3859 it('retries have lower priority than normal updates', async () => {
3860 const {useState} = React;
3861
@@ -3927,7 +3888,6 @@ describe('ReactSuspenseWithNoopRenderer', () => {
3888 await ReactNoop.act(async () => {
3889 // Resolve the promise. This will trigger a retry.
3890 await resolveText('Async');
3930 - expect(Scheduler).toHaveYielded(['Promise resolved [Async]']);
3891 // Before the retry happens, schedule a new update.
3892 setText('B');
3893
@@ -3950,6 +3910,7 @@ describe('ReactSuspenseWithNoopRenderer', () => {
3910 );
3911 });
3912
3913 + // @gate enableCache
3914 it('should fire effect clean-up when deleting suspended tree', async () => {
3915 const {useEffect} = React;
3916
@@ -3997,6 +3958,7 @@ describe('ReactSuspenseWithNoopRenderer', () => {
3958 expect(Scheduler).toHaveYielded(['Unmount Child']);
3959 });
3960
3961 + // @gate enableCache
3962 it('should fire effect clean-up when deleting suspended tree (legacy)', async () => {
3963 const {useEffect} = React;
3964
scripts/jest/TestFlags.js
+5
@@ -84,6 +84,11 @@ function getTestFlags() {
84
85 ...featureFlags,
86 ...environmentFlags,
87 +
88 + // FIXME: www-classic has enableCache on, but when running the source
89 + // tests, Jest doesn't expose the API correctly. Fix then remove
90 + // this override.
91 + enableCache: __EXPERIMENTAL__,
92 },
93 {
94 get(flags, flagName) {