main
js 263 lines 7.6 KB
Raw
1 /**
2 * Copyright (c) Meta Platforms, Inc. and affiliates.
3 *
4 * This source code is licensed under the MIT license found in the
5 * LICENSE file in the root directory of this source tree.
6 *
7 * @flow
8 */
9
10 import type Store from 'react-devtools-shared/src/devtools/store';
11
12 import {getVersionedRenderImplementation} from './utils';
13
14 describe('commit tree', () => {
15 let React = require('react');
16 let Scheduler;
17 let store: Store;
18 let utils;
19
20 beforeEach(() => {
21 utils = require('./utils');
22 utils.beforeEachProfiling();
23
24 store = global.store;
25 store.collapseNodesByDefault = false;
26 store.recordChangeDescriptions = true;
27
28 React = require('react');
29 Scheduler = require('scheduler');
30 });
31
32 const {render} = getVersionedRenderImplementation();
33
34 // @reactVersion >= 16.9
35 it('should be able to rebuild the store tree for each commit', () => {
36 const Parent = ({count}) => {
37 Scheduler.unstable_advanceTime(10);
38 return new Array(count)
39 .fill(true)
40 .map((_, index) => <Child key={index} />);
41 };
42 const Child = React.memo(function Child() {
43 Scheduler.unstable_advanceTime(2);
44 return null;
45 });
46
47 utils.act(() => store.profilerStore.startProfiling());
48 utils.act(() => render(<Parent count={1} />));
49 expect(store).toMatchInlineSnapshot(`
50 [root]
51 ▾ <Parent>
52 <Child key="0"> [Memo]
53 `);
54 utils.act(() => render(<Parent count={3} />));
55 expect(store).toMatchInlineSnapshot(`
56 [root]
57 ▾ <Parent>
58 <Child key="0"> [Memo]
59 <Child key="1"> [Memo]
60 <Child key="2"> [Memo]
61 `);
62 utils.act(() => render(<Parent count={2} />));
63 expect(store).toMatchInlineSnapshot(`
64 [root]
65 ▾ <Parent>
66 <Child key="0"> [Memo]
67 <Child key="1"> [Memo]
68 `);
69 utils.act(() => render(<Parent count={0} />));
70 expect(store).toMatchInlineSnapshot(`
71 [root]
72 <Parent>
73 `);
74 utils.act(() => store.profilerStore.stopProfiling());
75
76 const rootID = store.roots[0];
77 const commitTrees = [];
78 for (let commitIndex = 0; commitIndex < 4; commitIndex++) {
79 commitTrees.push(
80 store.profilerStore.profilingCache.getCommitTree({
81 commitIndex,
82 rootID,
83 }),
84 );
85 }
86
87 expect(commitTrees[0].nodes.size).toBe(3); // <Root> + <Parent> + <Child>
88 expect(commitTrees[1].nodes.size).toBe(5); // <Root> + <Parent> + <Child> x 3
89 expect(commitTrees[2].nodes.size).toBe(4); // <Root> + <Parent> + <Child> x 2
90 expect(commitTrees[3].nodes.size).toBe(2); // <Root> + <Parent>
91 });
92
93 describe('Lazy', () => {
94 async function fakeImport(result) {
95 return {default: result};
96 }
97
98 const LazyInnerComponent = () => null;
99
100 const App = ({renderChildren}) => {
101 if (renderChildren) {
102 return (
103 <React.Suspense fallback="Loading...">
104 <LazyComponent />
105 </React.Suspense>
106 );
107 } else {
108 return null;
109 }
110 };
111
112 let LazyComponent;
113 beforeEach(() => {
114 LazyComponent = React.lazy(() => fakeImport(LazyInnerComponent));
115 });
116
117 // @reactVersion >= 16.9
118 it('should support Lazy components', async () => {
119 utils.act(() => store.profilerStore.startProfiling());
120 utils.act(() => render(<App renderChildren={true} />));
121 await Promise.resolve();
122 expect(store).toMatchInlineSnapshot(`
123 [root]
124 ▾ <App>
125 <Suspense>
126 [suspense-root] rects={null}
127 <Suspense name="App" uniqueSuspenders={true} rects={null}>
128 `);
129 utils.act(() => render(<App renderChildren={true} />));
130 expect(store).toMatchInlineSnapshot(`
131 [root]
132 ▾ <App>
133 ▾ <Suspense>
134 <LazyInnerComponent>
135 [suspense-root] rects={null}
136 <Suspense name="App" uniqueSuspenders={true} rects={null}>
137 `);
138 utils.act(() => render(<App renderChildren={false} />));
139 expect(store).toMatchInlineSnapshot(`
140 [root]
141 <App>
142 `);
143 utils.act(() => store.profilerStore.stopProfiling());
144
145 const rootID = store.roots[0];
146 const commitTrees = [];
147 for (let commitIndex = 0; commitIndex < 3; commitIndex++) {
148 commitTrees.push(
149 store.profilerStore.profilingCache.getCommitTree({
150 commitIndex,
151 rootID,
152 }),
153 );
154 }
155
156 expect(commitTrees[0].nodes.size).toBe(3);
157 expect(commitTrees[1].nodes.size).toBe(4); // <Root> + <App> + <Suspense> + <LazyInnerComponent>
158 expect(commitTrees[2].nodes.size).toBe(2); // <Root> + <App>
159 });
160
161 // @reactVersion >= 16.9
162 it('should support Lazy components that are unmounted before resolving', async () => {
163 utils.act(() => store.profilerStore.startProfiling());
164 utils.act(() => render(<App renderChildren={true} />));
165 expect(store).toMatchInlineSnapshot(`
166 [root]
167 ▾ <App>
168 <Suspense>
169 [suspense-root] rects={null}
170 <Suspense name="App" uniqueSuspenders={true} rects={null}>
171 `);
172 utils.act(() => render(<App renderChildren={false} />));
173 expect(store).toMatchInlineSnapshot(`
174 [root]
175 <App>
176 `);
177 utils.act(() => store.profilerStore.stopProfiling());
178
179 const rootID = store.roots[0];
180 const commitTrees = [];
181 for (let commitIndex = 0; commitIndex < 2; commitIndex++) {
182 commitTrees.push(
183 store.profilerStore.profilingCache.getCommitTree({
184 commitIndex,
185 rootID,
186 }),
187 );
188 }
189
190 expect(commitTrees[0].nodes.size).toBe(3);
191 expect(commitTrees[1].nodes.size).toBe(2); // <Root> + <App>
192 });
193 });
194
195 describe('Suspense', () => {
196 it('should handle transitioning from fallback back to content during profiling', async () => {
197 let resolvePromise;
198 let promise = null;
199 let childTreeBaseDuration = 10;
200
201 function Wrapper({children}) {
202 Scheduler.unstable_advanceTime(2);
203 return children;
204 }
205
206 function Child() {
207 Scheduler.unstable_advanceTime(childTreeBaseDuration);
208 if (promise !== null) {
209 React.use(promise);
210 }
211 return <GrandChild />;
212 }
213
214 function GrandChild() {
215 Scheduler.unstable_advanceTime(5);
216 return null;
217 }
218
219 function Fallback() {
220 Scheduler.unstable_advanceTime(2);
221 return null;
222 }
223
224 function App() {
225 Scheduler.unstable_advanceTime(1);
226 return (
227 <React.Suspense fallback={<Fallback />}>
228 <Wrapper>
229 <Child />
230 </Wrapper>
231 </React.Suspense>
232 );
233 }
234
235 utils.act(() => store.profilerStore.startProfiling());
236
237 // Commit 1: Mount with primary
238 utils.act(() => render(<App step={1} />));
239
240 // Commit 2: Suspend, show fallback
241 promise = new Promise(resolve => (resolvePromise = resolve));
242 await utils.actAsync(() => render(<App step={2} />));
243
244 // Commit 3: Resolve suspended promise, show primary content with a different duration.
245 childTreeBaseDuration = 20;
246 promise = null;
247 await utils.actAsync(resolvePromise);
248 utils.act(() => render(<App step={3} />));
249
250 utils.act(() => store.profilerStore.stopProfiling());
251
252 const rootID = store.roots[0];
253 const dataForRoot = store.profilerStore.getDataForRoot(rootID);
254 const numCommits = dataForRoot.commitData.length;
255 for (let commitIndex = 0; commitIndex < numCommits; commitIndex++) {
256 store.profilerStore.profilingCache.getCommitTree({
257 commitIndex,
258 rootID,
259 });
260 }
261 });
262 });
263 });