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) {