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