Add failing Concurrent Mode stress tests
Dan Abramov committed
Apr 18, 2019 at 16:10 UTC
24309fde06f0603f968532d4e41ec421e3434861
2 files changed
+910
src/__tests__/__snapshots__/storeStressTestConcurrent-test.js.snap
new
+239
@@ -0,0 +1,239 @@
1
+// Jest Snapshot v1, https://goo.gl/fbAQLP
2
+
3
+exports[`StoreStressConcurrent should handle a stress test for Suspense (Concurrent Mode) 1`] = `
4
+[root]
5
+ ▾ <Root>
6
+ <X>
7
+ ▾ <Suspense>
8
+ <A key="a">
9
+ <Y>
10
+`;
11
+
12
+exports[`StoreStressConcurrent should handle a stress test for Suspense (Concurrent Mode) 2`] = `
13
+[root]
14
+ ▾ <Root>
15
+ <X>
16
+ ▾ <Suspense>
17
+ <A key="a">
18
+ <Y>
19
+`;
20
+
21
+exports[`StoreStressConcurrent should handle a stress test for Suspense (Concurrent Mode) 3`] = `
22
+[root]
23
+ ▾ <Root>
24
+ <X>
25
+ ▾ <Suspense>
26
+ <A key="a">
27
+ <B key="b">
28
+ <C key="c">
29
+ <Y>
30
+`;
31
+
32
+exports[`StoreStressConcurrent should handle a stress test for Suspense (Concurrent Mode) 4`] = `
33
+[root]
34
+ ▾ <Root>
35
+ <X>
36
+ ▾ <Suspense>
37
+ <C key="c">
38
+ <B key="b">
39
+ <A key="a">
40
+ <Y>
41
+`;
42
+
43
+exports[`StoreStressConcurrent should handle a stress test for Suspense (Concurrent Mode) 5`] = `
44
+[root]
45
+ ▾ <Root>
46
+ <X>
47
+ ▾ <Suspense>
48
+ <C key="c">
49
+ <A key="a">
50
+ <Y>
51
+`;
52
+
53
+exports[`StoreStressConcurrent should handle a stress test for Suspense (Concurrent Mode) 6`] = `
54
+[root]
55
+ ▾ <Root>
56
+ <X>
57
+ ▾ <Suspense>
58
+ <C key="c">
59
+ <A key="a">
60
+ <Y>
61
+`;
62
+
63
+exports[`StoreStressConcurrent should handle a stress test for Suspense (Concurrent Mode) 7`] = `
64
+[root]
65
+ ▾ <Root>
66
+ <X>
67
+ ▾ <Suspense>
68
+ <C key="c">
69
+ <A key="a">
70
+ <Y>
71
+`;
72
+
73
+exports[`StoreStressConcurrent should handle a stress test for Suspense (Concurrent Mode) 8`] = `
74
+[root]
75
+ ▾ <Root>
76
+ <X>
77
+ ▾ <Suspense>
78
+ <A key="a">
79
+ <B key="b">
80
+ <Y>
81
+`;
82
+
83
+exports[`StoreStressConcurrent should handle a stress test for Suspense (Concurrent Mode) 9`] = `
84
+[root]
85
+ ▾ <Root>
86
+ <X>
87
+ ▾ <Suspense>
88
+ <A key="a">
89
+ <Y>
90
+`;
91
+
92
+exports[`StoreStressConcurrent should handle a stress test for Suspense (Concurrent Mode) 10`] = `
93
+[root]
94
+ ▾ <Root>
95
+ <X>
96
+ <Suspense>
97
+ <Y>
98
+`;
99
+
100
+exports[`StoreStressConcurrent should handle a stress test for Suspense (Concurrent Mode) 11`] = `
101
+[root]
102
+ ▾ <Root>
103
+ <X>
104
+ ▾ <Suspense>
105
+ <B key="b">
106
+ <Y>
107
+`;
108
+
109
+exports[`StoreStressConcurrent should handle a stress test for Suspense (Concurrent Mode) 12`] = `
110
+[root]
111
+ ▾ <Root>
112
+ <X>
113
+ ▾ <Suspense>
114
+ <A key="a">
115
+ <Y>
116
+`;
117
+
118
+exports[`StoreStressConcurrent should handle a stress test with different tree operations (Concurrent Mode): 1: abcde 1`] = `
119
+[root]
120
+ ▾ <Parent>
121
+ <A key="a">
122
+ <B key="b">
123
+ <C key="c">
124
+ <D key="d">
125
+ <E key="e">
126
+`;
127
+
128
+exports[`StoreStressConcurrent should handle a stress test with different tree operations (Concurrent Mode): 2: abxde 1`] = `
129
+[root]
130
+ ▾ <Parent>
131
+ <A key="a">
132
+ <B key="b">
133
+ ▾ <C key="c">
134
+ <X>
135
+ <D key="d">
136
+ <E key="e">
137
+`;
138
+
139
+exports[`StoreStressConcurrent should handle stress test with reordering (Concurrent Mode) 1`] = `
140
+[root]
141
+ ▾ <Root>
142
+ <A key="a">
143
+`;
144
+
145
+exports[`StoreStressConcurrent should handle stress test with reordering (Concurrent Mode) 2`] = `
146
+[root]
147
+ ▾ <Root>
148
+ <B key="b">
149
+`;
150
+
151
+exports[`StoreStressConcurrent should handle stress test with reordering (Concurrent Mode) 3`] = `
152
+[root]
153
+ ▾ <Root>
154
+ <C key="c">
155
+`;
156
+
157
+exports[`StoreStressConcurrent should handle stress test with reordering (Concurrent Mode) 4`] = `
158
+[root]
159
+ ▾ <Root>
160
+ <D key="d">
161
+`;
162
+
163
+exports[`StoreStressConcurrent should handle stress test with reordering (Concurrent Mode) 5`] = `
164
+[root]
165
+ ▾ <Root>
166
+ <E key="e">
167
+`;
168
+
169
+exports[`StoreStressConcurrent should handle stress test with reordering (Concurrent Mode) 6`] = `
170
+[root]
171
+ ▾ <Root>
172
+ <A key="a">
173
+`;
174
+
175
+exports[`StoreStressConcurrent should handle stress test with reordering (Concurrent Mode) 7`] = `
176
+[root]
177
+ ▾ <Root>
178
+ <B key="b">
179
+`;
180
+
181
+exports[`StoreStressConcurrent should handle stress test with reordering (Concurrent Mode) 8`] = `
182
+[root]
183
+ ▾ <Root>
184
+ <C key="c">
185
+`;
186
+
187
+exports[`StoreStressConcurrent should handle stress test with reordering (Concurrent Mode) 9`] = `
188
+[root]
189
+ ▾ <Root>
190
+ <D key="d">
191
+`;
192
+
193
+exports[`StoreStressConcurrent should handle stress test with reordering (Concurrent Mode) 10`] = `
194
+[root]
195
+ ▾ <Root>
196
+ <E key="e">
197
+`;
198
+
199
+exports[`StoreStressConcurrent should handle stress test with reordering (Concurrent Mode) 11`] = `
200
+[root]
201
+ ▾ <Root>
202
+ <A key="a">
203
+ <B key="b">
204
+`;
205
+
206
+exports[`StoreStressConcurrent should handle stress test with reordering (Concurrent Mode) 12`] = `
207
+[root]
208
+ ▾ <Root>
209
+ <B key="b">
210
+ <A key="a">
211
+`;
212
+
213
+exports[`StoreStressConcurrent should handle stress test with reordering (Concurrent Mode) 13`] = `
214
+[root]
215
+ ▾ <Root>
216
+ <B key="b">
217
+ <C key="c">
218
+`;
219
+
220
+exports[`StoreStressConcurrent should handle stress test with reordering (Concurrent Mode) 14`] = `
221
+[root]
222
+ ▾ <Root>
223
+ <C key="c">
224
+ <B key="b">
225
+`;
226
+
227
+exports[`StoreStressConcurrent should handle stress test with reordering (Concurrent Mode) 15`] = `
228
+[root]
229
+ ▾ <Root>
230
+ <A key="a">
231
+ <C key="c">
232
+`;
233
+
234
+exports[`StoreStressConcurrent should handle stress test with reordering (Concurrent Mode) 16`] = `
235
+[root]
236
+ ▾ <Root>
237
+ <C key="c">
238
+ <A key="a">
239
+`;
src/__tests__/storeStressTestConcurrent-test.js
new
+671
@@ -0,0 +1,671 @@
1
+// @flow
2
+
3
+describe('StoreStressConcurrent', () => {
4
+ let React;
5
+ let ReactDOM;
6
+ let TestUtils;
7
+ let bridge;
8
+ let store;
9
+ let print;
10
+
11
+ const act = (callback: Function) => {
12
+ TestUtils.act(() => {
13
+ callback();
14
+ });
15
+ jest.advanceTimersByTime(1000); // Flush rendering and Suspense
16
+ jest.runAllTimers(); // Flush Bridge operations
17
+ };
18
+
19
+ beforeEach(() => {
20
+ bridge = global.bridge;
21
+ store = global.store;
22
+ store.collapseNodesByDefault = false;
23
+
24
+ React = require('react');
25
+ ReactDOM = require('react-dom');
26
+ TestUtils = require('react-dom/test-utils');
27
+
28
+ print = require('./storeSerializer').print;
29
+ });
30
+
31
+ // This is a stress test for the tree mount/update/unmount traversal.
32
+ // It renders different trees that should produce the same output.
33
+ it('should handle a stress test with different tree operations (Concurrent Mode)', () => {
34
+ let setShowX;
35
+ const A = () => 'a';
36
+ const B = () => 'b';
37
+ const C = () => {
38
+ // We'll be manually flipping this component back and forth in the test.
39
+ // We only do this for a single node in order to verify that DevTools
40
+ // can handle a subtree switching alternates while other subtrees are memoized.
41
+ let [showX, _setShowX] = React.useState(false);
42
+ setShowX = _setShowX;
43
+ return showX ? <X /> : 'c';
44
+ };
45
+ const D = () => 'd';
46
+ const E = () => 'e';
47
+ const X = () => 'x';
48
+ const a = <A key="a" />;
49
+ const b = <B key="b" />;
50
+ const c = <C key="c" />;
51
+ const d = <D key="d" />;
52
+ const e = <E key="e" />;
53
+
54
+ function Parent({ children }) {
55
+ return children;
56
+ }
57
+
58
+ // 1. Render a normal version of [a, b, c, d, e].
59
+ let container = document.createElement('div');
60
+ // $FlowFixMe
61
+ let root = ReactDOM.unstable_createRoot(container);
62
+ act(() => root.render(<Parent>{[a, b, c, d, e]}</Parent>));
63
+ expect(store).toMatchSnapshot('1: abcde');
64
+ expect(container.textContent).toMatch('abcde');
65
+ const snapshotForABCDE = print(store);
66
+
67
+ // 2. Render a version where <C /> renders an <X /> child instead of 'c'.
68
+ // This is how we'll test an update to a single component.
69
+ act(() => {
70
+ setShowX(true);
71
+ });
72
+ expect(store).toMatchSnapshot('2: abxde');
73
+ expect(container.textContent).toMatch('abxde');
74
+ const snapshotForABXDE = print(store);
75
+
76
+ // 3. Verify flipping it back produces the original result.
77
+ act(() => {
78
+ setShowX(false);
79
+ });
80
+ expect(container.textContent).toMatch('abcde');
81
+ expect(print(store)).toBe(snapshotForABCDE);
82
+
83
+ // 4. Clean up.
84
+ act(() => root.unmount());
85
+ expect(print(store)).toBe('');
86
+
87
+ // Now comes the interesting part.
88
+ // All of these cases are equivalent to [a, b, c, d, e] in output.
89
+ // We'll verify that DevTools produces the same snapshots for them.
90
+ // These cases are picked so that rendering them sequentially in the same
91
+ // container results in a combination of mounts, updates, unmounts, and reorders.
92
+ // prettier-ignore
93
+ let cases = [
94
+ [a, b, c, d, e],
95
+ [[a], b, c, d, e],
96
+ [[a, b], c, d, e],
97
+ [[a, b], c, [d, e]],
98
+ [[a, b], c, [d, '', e]],
99
+ [[a], b, c, d, [e]],
100
+ [a, b, [[c]], d, e],
101
+ [[a, ''], [b], [c], [d], [e]],
102
+ [a, b, [c, [d, ['', e]]]],
103
+ [a, b, c, d, e],
104
+ [<div key="0">{a}</div>, b, c, d, e],
105
+ [<div key="0">{a}{b}</div>, c, d, e],
106
+ [<div key="0">{a}{b}</div>, c, <div key="1">{d}{e}</div>],
107
+ [<div key="1">{a}{b}</div>, c, <div key="0">{d}{e}</div>],
108
+ [<div key="0">{a}{b}</div>, c, <div key="1">{d}{e}</div>],
109
+ [<div key="2">{a}{b}</div>, c, <div key="3">{d}{e}</div>],
110
+ [<span key="0">{a}</span>, b, c, d, [e]],
111
+ [a, b, <span key="0"><span>{c}</span></span>, d, e],
112
+ [<div key="0">{a}</div>, [b], <span key="1">{c}</span>, [d], <div key="2">{e}</div>],
113
+ [a, b, [c, <div key="0">{d}<span>{e}</span></div>], ''],
114
+ [a, [[]], b, c, [d, [[]], e]],
115
+ [[[a, b, c, d], e]],
116
+ [a, b, c, d, e]
117
+ ];
118
+
119
+ // 5. Test fresh mount for each case.
120
+ for (let i = 0; i < cases.length; i++) {
121
+ // Ensure fresh mount.
122
+ container = document.createElement('div');
123
+ // $FlowFixMe
124
+ root = ReactDOM.unstable_createRoot(container);
125
+
126
+ // Verify mounting 'abcde'.
127
+ act(() => root.render(<Parent>{cases[i]}</Parent>));
128
+ expect(container.textContent).toMatch('abcde');
129
+ expect(print(store)).toEqual(snapshotForABCDE);
130
+
131
+ // Verify switching to 'abxde'.
132
+ act(() => {
133
+ setShowX(true);
134
+ });
135
+ expect(container.textContent).toMatch('abxde');
136
+ expect(print(store)).toBe(snapshotForABXDE);
137
+
138
+ // Verify switching back to 'abcde'.
139
+ act(() => {
140
+ setShowX(false);
141
+ });
142
+ expect(container.textContent).toMatch('abcde');
143
+ expect(print(store)).toBe(snapshotForABCDE);
144
+
145
+ // Clean up.
146
+ act(() => root.unmount());
147
+ expect(print(store)).toBe('');
148
+ }
149
+
150
+ // 6. Verify *updates* by reusing the container between iterations.
151
+ // There'll be no unmounting until the very end.
152
+ container = document.createElement('div');
153
+ // $FlowFixMe
154
+ root = ReactDOM.unstable_createRoot(container);
155
+ for (let i = 0; i < cases.length; i++) {
156
+ // Verify mounting 'abcde'.
157
+ act(() => root.render(<Parent>{cases[i]}</Parent>));
158
+ expect(container.textContent).toMatch('abcde');
159
+ expect(print(store)).toEqual(snapshotForABCDE);
160
+
161
+ // Verify switching to 'abxde'.
162
+ act(() => {
163
+ setShowX(true);
164
+ });
165
+ expect(container.textContent).toMatch('abxde');
166
+ expect(print(store)).toBe(snapshotForABXDE);
167
+
168
+ // Verify switching back to 'abcde'.
169
+ act(() => {
170
+ setShowX(false);
171
+ });
172
+ expect(container.textContent).toMatch('abcde');
173
+ expect(print(store)).toBe(snapshotForABCDE);
174
+ // Don't unmount. Reuse the container between iterations.
175
+ }
176
+ act(() => root.unmount());
177
+ expect(print(store)).toBe('');
178
+ });
179
+
180
+ it('should handle stress test with reordering (Concurrent Mode)', () => {
181
+ const A = () => 'a';
182
+ const B = () => 'b';
183
+ const C = () => 'c';
184
+ const D = () => 'd';
185
+ const E = () => 'e';
186
+ const a = <A key="a" />;
187
+ const b = <B key="b" />;
188
+ const c = <C key="c" />;
189
+ const d = <D key="d" />;
190
+ const e = <E key="e" />;
191
+
192
+ // prettier-ignore
193
+ let steps = [
194
+ a,
195
+ b,
196
+ c,
197
+ d,
198
+ e,
199
+ [a],
200
+ [b],
201
+ [c],
202
+ [d],
203
+ [e],
204
+ [a, b],
205
+ [b, a],
206
+ [b, c],
207
+ [c, b],
208
+ [a, c],
209
+ [c, a],
210
+ ];
211
+
212
+ const Root = ({ children }) => {
213
+ return children;
214
+ };
215
+
216
+ // 1. Capture the expected render result.
217
+ let snapshots = [];
218
+ let container = document.createElement('div');
219
+ // $FlowFixMe
220
+ let root = ReactDOM.unstable_createRoot(container);
221
+ for (let i = 0; i < steps.length; i++) {
222
+ act(() => root.render(<Root>{steps[i]}</Root>));
223
+ // We snapshot each step once so it doesn't regress.
224
+ expect(store).toMatchSnapshot();
225
+ snapshots.push(print(store));
226
+ act(() => root.unmount());
227
+ expect(print(store)).toBe('');
228
+ }
229
+
230
+ // 2. Verify that we can update from every step to every other step and back.
231
+ for (let i = 0; i < steps.length; i++) {
232
+ for (let j = 0; j < steps.length; j++) {
233
+ let container = document.createElement('div');
234
+ // $FlowFixMe
235
+ let root = ReactDOM.unstable_createRoot(container);
236
+ act(() => root.render(<Root>{steps[i]}</Root>));
237
+ expect(print(store)).toMatch(snapshots[i]);
238
+ act(() => root.render(<Root>{steps[j]}</Root>));
239
+ expect(print(store)).toMatch(snapshots[j]);
240
+ act(() => root.render(<Root>{steps[i]}</Root>));
241
+ expect(print(store)).toMatch(snapshots[i]);
242
+ act(() => root.unmount());
243
+ expect(print(store)).toBe('');
244
+ }
245
+ }
246
+
247
+ // 3. Same test as above, but this time we wrap children in a host component.
248
+ for (let i = 0; i < steps.length; i++) {
249
+ for (let j = 0; j < steps.length; j++) {
250
+ let container = document.createElement('div');
251
+ // $FlowFixMe
252
+ let root = ReactDOM.unstable_createRoot(container);
253
+ act(() =>
254
+ root.render(
255
+ <Root>
256
+ <div>{steps[i]}</div>
257
+ </Root>
258
+ )
259
+ );
260
+ expect(print(store)).toMatch(snapshots[i]);
261
+ act(() =>
262
+ root.render(
263
+ <Root>
264
+ <div>{steps[j]}</div>
265
+ </Root>
266
+ )
267
+ );
268
+ expect(print(store)).toMatch(snapshots[j]);
269
+ act(() =>
270
+ root.render(
271
+ <Root>
272
+ <div>{steps[i]}</div>
273
+ </Root>
274
+ )
275
+ );
276
+ expect(print(store)).toMatch(snapshots[i]);
277
+ act(() => root.unmount());
278
+ expect(print(store)).toBe('');
279
+ }
280
+ }
281
+ });
282
+
283
+ it('should handle a stress test for Suspense (Concurrent Mode)', async () => {
284
+ const A = () => 'a';
285
+ const B = () => 'b';
286
+ const C = () => 'c';
287
+ const X = () => 'x';
288
+ const Y = () => 'y';
289
+ const Z = () => 'z';
290
+ const a = <A key="a" />;
291
+ const b = <B key="b" />;
292
+ const c = <C key="c" />;
293
+ const z = <Z key="z" />;
294
+
295
+ // prettier-ignore
296
+ const steps = [
297
+ a,
298
+ [a],
299
+ [a, b, c],
300
+ [c, b, a],
301
+ [c, null, a],
302
+ <React.Fragment>{c}{a}</React.Fragment>,
303
+ <div>{c}{a}</div>,
304
+ <div><span>{a}</span>{b}</div>,
305
+ [[a]],
306
+ null,
307
+ b,
308
+ a
309
+ ];
310
+
311
+ const Never = () => {
312
+ throw new Promise(() => {});
313
+ };
314
+
315
+ const Root = ({ children }) => {
316
+ return children;
317
+ };
318
+
319
+ // 1. For each step, check Suspense can render them as initial primary content.
320
+ // This is the only step where we use Jest snapshots.
321
+ let snapshots = [];
322
+ let container = document.createElement('div');
323
+ // $FlowFixMe
324
+ let root = ReactDOM.unstable_createRoot(container);
325
+ for (let i = 0; i < steps.length; i++) {
326
+ act(() =>
327
+ root.render(
328
+ <Root>
329
+ <X />
330
+ <React.Suspense fallback={z}>{steps[i]}</React.Suspense>
331
+ <Y />
332
+ </Root>
333
+ )
334
+ );
335
+ // We snapshot each step once so it doesn't regress.
336
+ expect(store).toMatchSnapshot();
337
+ snapshots.push(print(store));
338
+ act(() => root.unmount());
339
+ expect(print(store)).toBe('');
340
+ }
341
+
342
+ // 2. Verify check Suspense can render same steps as initial fallback content.
343
+ for (let i = 0; i < steps.length; i++) {
344
+ act(() =>
345
+ root.render(
346
+ <Root>
347
+ <X />
348
+ <React.Suspense fallback={steps[i]}>
349
+ <Z />
350
+ <Never />
351
+ <Z />
352
+ </React.Suspense>
353
+ <Y />
354
+ </Root>
355
+ )
356
+ );
357
+ expect(print(store)).toEqual(snapshots[i]);
358
+ act(() => root.unmount());
359
+ expect(print(store)).toBe('');
360
+ }
361
+
362
+ // 3. Verify we can update from each step to each step in primary mode.
363
+ for (let i = 0; i < steps.length; i++) {
364
+ for (let j = 0; j < steps.length; j++) {
365
+ // Always start with a fresh container and steps[i].
366
+ container = document.createElement('div');
367
+ // $FlowFixMe
368
+ root = ReactDOM.unstable_createRoot(container);
369
+ act(() =>
370
+ root.render(
371
+ <Root>
372
+ <X />
373
+ <React.Suspense fallback={z}>{steps[i]}</React.Suspense>
374
+ <Y />
375
+ </Root>
376
+ )
377
+ );
378
+ expect(print(store)).toEqual(snapshots[i]);
379
+ // Re-render with steps[j].
380
+ act(() =>
381
+ root.render(
382
+ <Root>
383
+ <X />
384
+ <React.Suspense fallback={z}>{steps[j]}</React.Suspense>
385
+ <Y />
386
+ </Root>
387
+ )
388
+ );
389
+ // Verify the successful transition to steps[j].
390
+ expect(print(store)).toEqual(snapshots[j]);
391
+ // Check that we can transition back again.
392
+ act(() =>
393
+ root.render(
394
+ <Root>
395
+ <X />
396
+ <React.Suspense fallback={z}>{steps[i]}</React.Suspense>
397
+ <Y />
398
+ </Root>
399
+ )
400
+ );
401
+ expect(print(store)).toEqual(snapshots[i]);
402
+ // Clean up after every iteration.
403
+ act(() => root.unmount());
404
+ expect(print(store)).toBe('');
405
+ }
406
+ }
407
+
408
+ // 4. Verify we can update from each step to each step in fallback mode.
409
+ for (let i = 0; i < steps.length; i++) {
410
+ for (let j = 0; j < steps.length; j++) {
411
+ // Always start with a fresh container and steps[i].
412
+ container = document.createElement('div');
413
+ // $FlowFixMe
414
+ root = ReactDOM.unstable_createRoot(container);
415
+ act(() =>
416
+ root.render(
417
+ <Root>
418
+ <X />
419
+ <React.Suspense fallback={steps[i]}>
420
+ <Z />
421
+ <Never />
422
+ <Z />
423
+ </React.Suspense>
424
+ <Y />
425
+ </Root>
426
+ )
427
+ );
428
+ expect(print(store)).toEqual(snapshots[i]);
429
+ // Re-render with steps[j].
430
+ act(() =>
431
+ root.render(
432
+ <Root>
433
+ <X />
434
+ <React.Suspense fallback={steps[j]}>
435
+ <Z />
436
+ <Never />
437
+ <Z />
438
+ </React.Suspense>
439
+ <Y />
440
+ </Root>
441
+ )
442
+ );
443
+ // Verify the successful transition to steps[j].
444
+ expect(print(store)).toEqual(snapshots[j]);
445
+ // Check that we can transition back again.
446
+ act(() =>
447
+ root.render(
448
+ <Root>
449
+ <X />
450
+ <React.Suspense fallback={steps[i]}>
451
+ <Z />
452
+ <Never />
453
+ <Z />
454
+ </React.Suspense>
455
+ <Y />
456
+ </Root>
457
+ )
458
+ );
459
+ expect(print(store)).toEqual(snapshots[i]);
460
+ // Clean up after every iteration.
461
+ act(() => root.unmount());
462
+ expect(print(store)).toBe('');
463
+ }
464
+ }
465
+
466
+ // 5. Verify we can update from each step to each step when moving primary -> fallback.
467
+ for (let i = 0; i < steps.length; i++) {
468
+ for (let j = 0; j < steps.length; j++) {
469
+ // Always start with a fresh container and steps[i].
470
+ container = document.createElement('div');
471
+ // $FlowFixMe
472
+ root = ReactDOM.unstable_createRoot(container);
473
+ act(() =>
474
+ root.render(
475
+ <Root>
476
+ <X />
477
+ <React.Suspense fallback={z}>{steps[i]}</React.Suspense>
478
+ <Y />
479
+ </Root>
480
+ )
481
+ );
482
+ expect(print(store)).toEqual(snapshots[i]);
483
+ // Re-render with steps[j].
484
+ act(() =>
485
+ root.render(
486
+ <Root>
487
+ <X />
488
+ <React.Suspense fallback={steps[j]}>
489
+ <Z />
490
+ <Never />
491
+ <Z />
492
+ </React.Suspense>
493
+ <Y />
494
+ </Root>
495
+ )
496
+ );
497
+ // Verify the successful transition to steps[j].
498
+ expect(print(store)).toEqual(snapshots[j]);
499
+ // Check that we can transition back again.
500
+ act(() =>
501
+ root.render(
502
+ <Root>
503
+ <X />
504
+ <React.Suspense fallback={z}>{steps[i]}</React.Suspense>
505
+ <Y />
506
+ </Root>
507
+ )
508
+ );
509
+ expect(print(store)).toEqual(snapshots[i]);
510
+ // Clean up after every iteration.
511
+ act(() => root.unmount());
512
+ expect(print(store)).toBe('');
513
+ }
514
+ }
515
+
516
+ // 6. Verify we can update from each step to each step when moving fallback -> primary.
517
+ for (let i = 0; i < steps.length; i++) {
518
+ for (let j = 0; j < steps.length; j++) {
519
+ // Always start with a fresh container and steps[i].
520
+ container = document.createElement('div');
521
+ // $FlowFixMe
522
+ root = ReactDOM.unstable_createRoot(container);
523
+ act(() =>
524
+ root.render(
525
+ <Root>
526
+ <X />
527
+ <React.Suspense fallback={steps[i]}>
528
+ <Z />
529
+ <Never />
530
+ <Z />
531
+ </React.Suspense>
532
+ <Y />
533
+ </Root>
534
+ )
535
+ );
536
+ expect(print(store)).toEqual(snapshots[i]);
537
+ // Re-render with steps[j].
538
+ act(() =>
539
+ root.render(
540
+ <Root>
541
+ <X />
542
+ <React.Suspense fallback={z}>{steps[j]}</React.Suspense>
543
+ <Y />
544
+ </Root>
545
+ )
546
+ );
547
+ // Verify the successful transition to steps[j].
548
+ expect(print(store)).toEqual(snapshots[j]);
549
+ // Check that we can transition back again.
550
+ act(() =>
551
+ root.render(
552
+ <Root>
553
+ <X />
554
+ <React.Suspense fallback={steps[i]}>
555
+ <Z />
556
+ <Never />
557
+ <Z />
558
+ </React.Suspense>
559
+ <Y />
560
+ </Root>
561
+ )
562
+ );
563
+ expect(print(store)).toEqual(snapshots[i]);
564
+ // Clean up after every iteration.
565
+ act(() => root.unmount());
566
+ expect(print(store)).toBe('');
567
+ }
568
+ }
569
+
570
+ // 7. Verify we can update from each step to each step when toggling Suspense.
571
+ for (let i = 0; i < steps.length; i++) {
572
+ for (let j = 0; j < steps.length; j++) {
573
+ // Always start with a fresh container and steps[i].
574
+ container = document.createElement('div');
575
+ // $FlowFixMe
576
+ root = ReactDOM.unstable_createRoot(container);
577
+ act(() =>
578
+ root.render(
579
+ <Root>
580
+ <X />
581
+ <React.Suspense fallback={steps[j]}>{steps[i]}</React.Suspense>
582
+ <Y />
583
+ </Root>
584
+ )
585
+ );
586
+
587
+ // We get ID from the index in the tree above:
588
+ // Root, X, Suspense, ...
589
+ // ^ (index is 2)
590
+ const suspenseID = store.getElementIDAtIndex(2);
591
+
592
+ // Force fallback.
593
+ expect(print(store)).toEqual(snapshots[i]);
594
+ act(() => {
595
+ const suspenseID = store.getElementIDAtIndex(2);
596
+ bridge.send('overrideSuspense', {
597
+ id: suspenseID,
598
+ rendererID: store.getRendererIDForElement(suspenseID),
599
+ forceFallback: true,
600
+ });
601
+ });
602
+ expect(print(store)).toEqual(snapshots[j]);
603
+
604
+ // Stop forcing fallback.
605
+ act(() => {
606
+ bridge.send('overrideSuspense', {
607
+ id: suspenseID,
608
+ rendererID: store.getRendererIDForElement(suspenseID),
609
+ forceFallback: false,
610
+ });
611
+ });
612
+ expect(print(store)).toEqual(snapshots[i]);
613
+
614
+ // Trigger actual fallback.
615
+ act(() =>
616
+ root.render(
617
+ <Root>
618
+ <X />
619
+ <React.Suspense fallback={steps[j]}>
620
+ <Z />
621
+ <Never />
622
+ <Z />
623
+ </React.Suspense>
624
+ <Y />
625
+ </Root>
626
+ )
627
+ );
628
+ expect(print(store)).toEqual(snapshots[j]);
629
+
630
+ // Force fallback while we're in fallback mode.
631
+ act(() => {
632
+ bridge.send('overrideSuspense', {
633
+ id: suspenseID,
634
+ rendererID: store.getRendererIDForElement(suspenseID),
635
+ forceFallback: true,
636
+ });
637
+ });
638
+ // Keep seeing fallback content.
639
+ expect(print(store)).toEqual(snapshots[j]);
640
+
641
+ // Switch to primary mode.
642
+ act(() =>
643
+ root.render(
644
+ <Root>
645
+ <X />
646
+ <React.Suspense fallback={steps[j]}>{steps[i]}</React.Suspense>
647
+ <Y />
648
+ </Root>
649
+ )
650
+ );
651
+ // Fallback is still forced though.
652
+ expect(print(store)).toEqual(snapshots[j]);
653
+
654
+ // Stop forcing fallback. This reverts to primary content.
655
+ act(() => {
656
+ bridge.send('overrideSuspense', {
657
+ id: suspenseID,
658
+ rendererID: store.getRendererIDForElement(suspenseID),
659
+ forceFallback: false,
660
+ });
661
+ });
662
+ // Now we see primary content.
663
+ expect(print(store)).toEqual(snapshots[i]);
664
+
665
+ // Clean up after every iteration.
666
+ act(() => root.unmount());
667
+ expect(print(store)).toBe('');
668
+ }
669
+ }
670
+ });
671
+});