@samitouri / QOS-React / commits / 24309fde06

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 +});