@samitouri / QOS-React-2 / commits / f569d27f86

Add a stress test for tree traversal

Dan committed Apr 15, 2019 at 01:45 UTC f569d27f86e19f11a9fca809198c0fb70397dddb
2 files changed +194 -1
src/__tests__/__snapshots__/store-test.js.snap
+21
@@ -25,6 +25,27 @@ 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 with different tree operations: 1: abcde 1`] = `
29 +[root]
30 + ▾ <Parent>
31 + <A key="a">
32 + <B key="b">
33 + <C key="c">
34 + <D key="d">
35 + <E key="e">
36 +`;
37 +
38 +exports[`Store should handle a stress test with different tree operations: 2: abxde 1`] = `
39 +[root]
40 + ▾ <Parent>
41 + <A key="a">
42 + <B key="b">
43 + ▾ <C key="c">
44 + <X>
45 + <D key="d">
46 + <E key="e">
47 +`;
48 +
49 exports[`Store should support collapsing parts of the tree: 1: mount 1`] = `
50 [root]
51 ▾ <Grandparent>
src/__tests__/store-test.js
+173 -1
@@ -5,12 +5,12 @@ describe('Store', () => {
5 let ReactDOM;
6 let TestUtils;
7 let store;
8 + let print;
9
10 const act = (callback: Function) => {
11 TestUtils.act(() => {
12 callback();
13 });
13 -
14 jest.runAllTimers(); // Flush Bridge operations
15 };
16
@@ -20,6 +20,8 @@ describe('Store', () => {
20 React = require('react');
21 ReactDOM = require('react-dom');
22 TestUtils = require('react-dom/test-utils');
23 +
24 + print = require('./storeSerializer').print;
25 });
26
27 it('should support mount and update operations', () => {
@@ -163,4 +165,174 @@ describe('Store', () => {
165 act(() => store.toggleIsCollapsed(grandparentID, false));
166 expect(store).toMatchSnapshot('6: expand Grandparent');
167 });
168 +
169 + // This is a stress test for the tree mount/update/unmount traversal.
170 + // It renders different trees that should produce the same output.
171 + it('should handle a stress test with different tree operations', () => {
172 + let setShowX;
173 + const A = () => 'a';
174 + const B = () => 'b';
175 + const C = () => {
176 + // We'll be manually flipping this component back and forth in the test.
177 + // We only do this for a single node in order to verify that DevTools
178 + // can handle a subtree switching alternates while other subtrees are memoized.
179 + let [showX, _setShowX] = React.useState(false);
180 + setShowX = _setShowX;
181 + return showX ? <X /> : 'c';
182 + };
183 + const D = () => 'd';
184 + const E = () => 'e';
185 + const X = () => 'x';
186 + const a = <A key="a" />;
187 + const b = <B key="b" />;
188 + const c = <C key="c" />;
189 + const d = <D key="d" />;
190 + const e = <E key="e" />;
191 +
192 + function Parent({ children }) {
193 + return children;
194 + }
195 +
196 + // 1. Render a normal version of [a, b, c, d, e].
197 + let container = document.createElement('div');
198 + act(() => ReactDOM.render(<Parent>{[a, b, c, d, e]}</Parent>, container));
199 + expect(store).toMatchSnapshot('1: abcde');
200 + expect(container.textContent).toMatch('abcde');
201 + const snapshotForABCDE = print(store);
202 +
203 + // 2. Render a version where <C /> renders an <X /> child instead of 'c'.
204 + // This is how we'll test an update to a single component.
205 + act(() => {
206 + setShowX(true);
207 + });
208 + expect(store).toMatchSnapshot('2: abxde');
209 + expect(container.textContent).toMatch('abxde');
210 + const snapshotForABXDE = print(store);
211 +
212 + // 3. Verify flipping it back produces the original result.
213 + act(() => {
214 + setShowX(false);
215 + });
216 + expect(container.textContent).toMatch('abcde');
217 + expect(print(store)).toBe(snapshotForABCDE);
218 +
219 + // 4. Clean up.
220 + act(() => ReactDOM.unmountComponentAtNode(container));
221 + expect(print(store)).toBe('');
222 +
223 + // Now comes the interesting part.
224 + // All of these cases are equivalent to [a, b, c, d, e] in output.
225 + // We'll verify that DevTools produces the same snapshots for them.
226 + // These cases are picked so that rendering them sequentially in the same
227 + // container results in a combination of mounts, updates, unmounts, and reorders.
228 + // prettier-ignore
229 + let cases = [
230 + [a, b, c, d, e],
231 + [[a], b, c, d, e],
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 + [<div key="0">{a}</div>, b, c, d, e],
241 + [<div key="0">{a}{b}</div>, c, d, e],
242 + [<div key="0">{a}{b}</div>, c, <div key="1">{d}{e}</div>],
243 + [<div key="1">{a}{b}</div>, c, <div key="0">{d}{e}</div>],
244 + [<div key="0">{a}{b}</div>, c, <div key="1">{d}{e}</div>],
245 + [<div key="2">{a}{b}</div>, c, <div key="3">{d}{e}</div>],
246 + [<span key="0">{a}</span>, b, c, d, [e]],
247 + [a, b, <span key="0"><span>{c}</span></span>, d, e],
248 + [<div key="0">{a}</div>, [b], <span key="1">{c}</span>, [d], <div key="2">{e}</div>],
249 + [a, b, [c, <div key="0">{d}<span>{e}</span></div>], ''],
250 + [a, [[]], b, c, [d, [[]], e]],
251 + [[[a, b, c, d], e]],
252 + [a, b, c, d, e]
253 + ];
254 +
255 + // 5. Test fresh mount for each case.
256 + for (let i = 0; i < cases.length; i++) {
257 + // Ensure fresh mount.
258 + container = document.createElement('div');
259 +
260 + // Verify mounting 'abcde'.
261 + act(() => ReactDOM.render(<Parent>{cases[i]}</Parent>, container));
262 + expect(container.textContent).toMatch('abcde');
263 + expect(print(store)).toEqual(snapshotForABCDE);
264 +
265 + // Verify switching to 'abxde'.
266 + act(() => {
267 + setShowX(true);
268 + });
269 + expect(container.textContent).toMatch('abxde');
270 + expect(print(store)).toBe(snapshotForABXDE);
271 +
272 + // Verify switching back to 'abcde'.
273 + act(() => {
274 + setShowX(false);
275 + });
276 + expect(container.textContent).toMatch('abcde');
277 + expect(print(store)).toBe(snapshotForABCDE);
278 +
279 + // Clean up.
280 + act(() => ReactDOM.unmountComponentAtNode(container));
281 + expect(print(store)).toBe('');
282 + }
283 +
284 + // 6. Verify *updates* by reusing the container between iterations.
285 + // There'll be no unmounting until the very end.
286 + container = document.createElement('div');
287 + for (let i = 0; i < cases.length; i++) {
288 + // Verify mounting 'abcde'.
289 + act(() => ReactDOM.render(<Parent>{cases[i]}</Parent>, container));
290 + expect(container.textContent).toMatch('abcde');
291 + expect(print(store)).toEqual(snapshotForABCDE);
292 +
293 + // Verify switching to 'abxde'.
294 + act(() => {
295 + setShowX(true);
296 + });
297 + expect(container.textContent).toMatch('abxde');
298 + expect(print(store)).toBe(snapshotForABXDE);
299 +
300 + // Verify switching back to 'abcde'.
301 + act(() => {
302 + setShowX(false);
303 + });
304 + expect(container.textContent).toMatch('abcde');
305 + expect(print(store)).toBe(snapshotForABCDE);
306 + // Don't unmount. Reuse the container between iterations.
307 + }
308 + act(() => ReactDOM.unmountComponentAtNode(container));
309 + expect(print(store)).toBe('');
310 +
311 + // 7. Same as the previous step, but for Concurrent Mode.
312 + container = document.createElement('div');
313 + let root = ReactDOM.unstable_createRoot(container);
314 + for (let i = 0; i < cases.length; i++) {
315 + // Verify mounting 'abcde'.
316 + act(() => root.render(<Parent>{cases[i]}</Parent>));
317 + expect(container.textContent).toMatch('abcde');
318 + expect(print(store)).toEqual(snapshotForABCDE);
319 +
320 + // Verify switching to 'abxde'.
321 + act(() => {
322 + setShowX(true);
323 + });
324 + expect(container.textContent).toMatch('abxde');
325 + expect(print(store)).toBe(snapshotForABXDE);
326 +
327 + // Verify switching back to 'abcde'.
328 + act(() => {
329 + setShowX(false);
330 + });
331 + expect(container.textContent).toMatch('abcde');
332 + expect(print(store)).toBe(snapshotForABCDE);
333 + // Don't unmount. Reuse the container between iterations.
334 + }
335 + act(() => root.unmount());
336 + expect(print(store)).toBe('');
337 + });
338 });