@samitouri / QOS-React / commits / 8576c85b0d

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