Add better test coverage for hooks in DevTools Profiler's change-detection (#17816)
Brian Vaughn committed
Jan 9, 2020 at 13:15 UTC
435259f189526fa32b8c9cdab7d7b2c1dc260bc8
2 files changed
+555
packages/react-devtools-shared/src/__tests__/__snapshots__/profilingCache-test.js.snap
+431
@@ -2121,6 +2121,437 @@ Object {
2121
}
2122
`;
2123
2124
+exports[`ProfilingCache should properly detect changed hooks: CommitDetails commitIndex: 0 1`] = `
2125
+Object {
2126
+ "changeDescriptions": Map {
2127
+ 3 => Object {
2128
+ "context": null,
2129
+ "didHooksChange": false,
2130
+ "isFirstMount": true,
2131
+ "props": null,
2132
+ "state": null,
2133
+ },
2134
+ },
2135
+ "duration": 0,
2136
+ "fiberActualDurations": Map {
2137
+ 1 => 0,
2138
+ 2 => 0,
2139
+ 3 => 0,
2140
+ },
2141
+ "fiberSelfDurations": Map {
2142
+ 1 => 0,
2143
+ 2 => 0,
2144
+ 3 => 0,
2145
+ },
2146
+ "interactionIDs": Array [],
2147
+ "priorityLevel": "Immediate",
2148
+ "timestamp": 0,
2149
+}
2150
+`;
2151
+
2152
+exports[`ProfilingCache should properly detect changed hooks: CommitDetails commitIndex: 1 1`] = `
2153
+Object {
2154
+ "changeDescriptions": Map {
2155
+ 3 => Object {
2156
+ "context": null,
2157
+ "didHooksChange": false,
2158
+ "isFirstMount": false,
2159
+ "props": Array [
2160
+ "count",
2161
+ ],
2162
+ "state": null,
2163
+ },
2164
+ },
2165
+ "duration": 0,
2166
+ "fiberActualDurations": Map {
2167
+ 3 => 0,
2168
+ 2 => 0,
2169
+ 1 => 0,
2170
+ },
2171
+ "fiberSelfDurations": Map {
2172
+ 3 => 0,
2173
+ 2 => 0,
2174
+ 1 => 0,
2175
+ },
2176
+ "interactionIDs": Array [],
2177
+ "priorityLevel": "Immediate",
2178
+ "timestamp": 0,
2179
+}
2180
+`;
2181
+
2182
+exports[`ProfilingCache should properly detect changed hooks: CommitDetails commitIndex: 2 1`] = `
2183
+Object {
2184
+ "changeDescriptions": Map {
2185
+ 3 => Object {
2186
+ "context": null,
2187
+ "didHooksChange": true,
2188
+ "isFirstMount": false,
2189
+ "props": Array [],
2190
+ "state": null,
2191
+ },
2192
+ },
2193
+ "duration": 0,
2194
+ "fiberActualDurations": Map {
2195
+ 3 => 0,
2196
+ },
2197
+ "fiberSelfDurations": Map {
2198
+ 3 => 0,
2199
+ },
2200
+ "interactionIDs": Array [],
2201
+ "priorityLevel": "Immediate",
2202
+ "timestamp": 0,
2203
+}
2204
+`;
2205
+
2206
+exports[`ProfilingCache should properly detect changed hooks: CommitDetails commitIndex: 3 1`] = `
2207
+Object {
2208
+ "changeDescriptions": Map {
2209
+ 3 => Object {
2210
+ "context": null,
2211
+ "didHooksChange": true,
2212
+ "isFirstMount": false,
2213
+ "props": Array [],
2214
+ "state": null,
2215
+ },
2216
+ },
2217
+ "duration": 0,
2218
+ "fiberActualDurations": Map {
2219
+ 3 => 0,
2220
+ },
2221
+ "fiberSelfDurations": Map {
2222
+ 3 => 0,
2223
+ },
2224
+ "interactionIDs": Array [],
2225
+ "priorityLevel": "Immediate",
2226
+ "timestamp": 0,
2227
+}
2228
+`;
2229
+
2230
+exports[`ProfilingCache should properly detect changed hooks: CommitDetails commitIndex: 4 1`] = `
2231
+Object {
2232
+ "changeDescriptions": Map {
2233
+ 3 => Object {
2234
+ "context": null,
2235
+ "didHooksChange": false,
2236
+ "isFirstMount": false,
2237
+ "props": Array [],
2238
+ "state": null,
2239
+ },
2240
+ },
2241
+ "duration": 0,
2242
+ "fiberActualDurations": Map {
2243
+ 3 => 0,
2244
+ 2 => 0,
2245
+ 1 => 0,
2246
+ },
2247
+ "fiberSelfDurations": Map {
2248
+ 3 => 0,
2249
+ 2 => 0,
2250
+ 1 => 0,
2251
+ },
2252
+ "interactionIDs": Array [],
2253
+ "priorityLevel": "Immediate",
2254
+ "timestamp": 0,
2255
+}
2256
+`;
2257
+
2258
+exports[`ProfilingCache should properly detect changed hooks: imported data 1`] = `
2259
+Object {
2260
+ "dataForRoots": Array [
2261
+ Object {
2262
+ "commitData": Array [
2263
+ Object {
2264
+ "changeDescriptions": Array [
2265
+ Array [
2266
+ 3,
2267
+ Object {
2268
+ "context": null,
2269
+ "didHooksChange": false,
2270
+ "isFirstMount": true,
2271
+ "props": null,
2272
+ "state": null,
2273
+ },
2274
+ ],
2275
+ ],
2276
+ "duration": 0,
2277
+ "fiberActualDurations": Array [
2278
+ Array [
2279
+ 1,
2280
+ 0,
2281
+ ],
2282
+ Array [
2283
+ 2,
2284
+ 0,
2285
+ ],
2286
+ Array [
2287
+ 3,
2288
+ 0,
2289
+ ],
2290
+ ],
2291
+ "fiberSelfDurations": Array [
2292
+ Array [
2293
+ 1,
2294
+ 0,
2295
+ ],
2296
+ Array [
2297
+ 2,
2298
+ 0,
2299
+ ],
2300
+ Array [
2301
+ 3,
2302
+ 0,
2303
+ ],
2304
+ ],
2305
+ "interactionIDs": Array [],
2306
+ "priorityLevel": "Immediate",
2307
+ "timestamp": 0,
2308
+ },
2309
+ Object {
2310
+ "changeDescriptions": Array [
2311
+ Array [
2312
+ 3,
2313
+ Object {
2314
+ "context": null,
2315
+ "didHooksChange": false,
2316
+ "isFirstMount": false,
2317
+ "props": Array [
2318
+ "count",
2319
+ ],
2320
+ "state": null,
2321
+ },
2322
+ ],
2323
+ ],
2324
+ "duration": 0,
2325
+ "fiberActualDurations": Array [
2326
+ Array [
2327
+ 3,
2328
+ 0,
2329
+ ],
2330
+ Array [
2331
+ 2,
2332
+ 0,
2333
+ ],
2334
+ Array [
2335
+ 1,
2336
+ 0,
2337
+ ],
2338
+ ],
2339
+ "fiberSelfDurations": Array [
2340
+ Array [
2341
+ 3,
2342
+ 0,
2343
+ ],
2344
+ Array [
2345
+ 2,
2346
+ 0,
2347
+ ],
2348
+ Array [
2349
+ 1,
2350
+ 0,
2351
+ ],
2352
+ ],
2353
+ "interactionIDs": Array [],
2354
+ "priorityLevel": "Immediate",
2355
+ "timestamp": 0,
2356
+ },
2357
+ Object {
2358
+ "changeDescriptions": Array [
2359
+ Array [
2360
+ 3,
2361
+ Object {
2362
+ "context": null,
2363
+ "didHooksChange": true,
2364
+ "isFirstMount": false,
2365
+ "props": Array [],
2366
+ "state": null,
2367
+ },
2368
+ ],
2369
+ ],
2370
+ "duration": 0,
2371
+ "fiberActualDurations": Array [
2372
+ Array [
2373
+ 3,
2374
+ 0,
2375
+ ],
2376
+ ],
2377
+ "fiberSelfDurations": Array [
2378
+ Array [
2379
+ 3,
2380
+ 0,
2381
+ ],
2382
+ ],
2383
+ "interactionIDs": Array [],
2384
+ "priorityLevel": "Immediate",
2385
+ "timestamp": 0,
2386
+ },
2387
+ Object {
2388
+ "changeDescriptions": Array [
2389
+ Array [
2390
+ 3,
2391
+ Object {
2392
+ "context": null,
2393
+ "didHooksChange": true,
2394
+ "isFirstMount": false,
2395
+ "props": Array [],
2396
+ "state": null,
2397
+ },
2398
+ ],
2399
+ ],
2400
+ "duration": 0,
2401
+ "fiberActualDurations": Array [
2402
+ Array [
2403
+ 3,
2404
+ 0,
2405
+ ],
2406
+ ],
2407
+ "fiberSelfDurations": Array [
2408
+ Array [
2409
+ 3,
2410
+ 0,
2411
+ ],
2412
+ ],
2413
+ "interactionIDs": Array [],
2414
+ "priorityLevel": "Immediate",
2415
+ "timestamp": 0,
2416
+ },
2417
+ Object {
2418
+ "changeDescriptions": Array [
2419
+ Array [
2420
+ 3,
2421
+ Object {
2422
+ "context": null,
2423
+ "didHooksChange": false,
2424
+ "isFirstMount": false,
2425
+ "props": Array [],
2426
+ "state": null,
2427
+ },
2428
+ ],
2429
+ ],
2430
+ "duration": 0,
2431
+ "fiberActualDurations": Array [
2432
+ Array [
2433
+ 3,
2434
+ 0,
2435
+ ],
2436
+ Array [
2437
+ 2,
2438
+ 0,
2439
+ ],
2440
+ Array [
2441
+ 1,
2442
+ 0,
2443
+ ],
2444
+ ],
2445
+ "fiberSelfDurations": Array [
2446
+ Array [
2447
+ 3,
2448
+ 0,
2449
+ ],
2450
+ Array [
2451
+ 2,
2452
+ 0,
2453
+ ],
2454
+ Array [
2455
+ 1,
2456
+ 0,
2457
+ ],
2458
+ ],
2459
+ "interactionIDs": Array [],
2460
+ "priorityLevel": "Immediate",
2461
+ "timestamp": 0,
2462
+ },
2463
+ ],
2464
+ "displayName": "Component",
2465
+ "initialTreeBaseDurations": Array [],
2466
+ "interactionCommits": Array [],
2467
+ "interactions": Array [],
2468
+ "operations": Array [
2469
+ Array [
2470
+ 1,
2471
+ 1,
2472
+ 27,
2473
+ 16,
2474
+ 67,
2475
+ 111,
2476
+ 110,
2477
+ 116,
2478
+ 101,
2479
+ 120,
2480
+ 116,
2481
+ 46,
2482
+ 80,
2483
+ 114,
2484
+ 111,
2485
+ 118,
2486
+ 105,
2487
+ 100,
2488
+ 101,
2489
+ 114,
2490
+ 9,
2491
+ 67,
2492
+ 111,
2493
+ 109,
2494
+ 112,
2495
+ 111,
2496
+ 110,
2497
+ 101,
2498
+ 110,
2499
+ 116,
2500
+ 1,
2501
+ 1,
2502
+ 11,
2503
+ 1,
2504
+ 1,
2505
+ 1,
2506
+ 2,
2507
+ 2,
2508
+ 1,
2509
+ 0,
2510
+ 1,
2511
+ 0,
2512
+ 4,
2513
+ 2,
2514
+ 0,
2515
+ 1,
2516
+ 3,
2517
+ 5,
2518
+ 2,
2519
+ 0,
2520
+ 2,
2521
+ 0,
2522
+ 4,
2523
+ 3,
2524
+ 0,
2525
+ ],
2526
+ Array [
2527
+ 1,
2528
+ 1,
2529
+ 0,
2530
+ ],
2531
+ Array [
2532
+ 1,
2533
+ 1,
2534
+ 0,
2535
+ ],
2536
+ Array [
2537
+ 1,
2538
+ 1,
2539
+ 0,
2540
+ ],
2541
+ Array [
2542
+ 1,
2543
+ 1,
2544
+ 0,
2545
+ ],
2546
+ ],
2547
+ "rootID": 1,
2548
+ "snapshots": Array [],
2549
+ },
2550
+ ],
2551
+ "version": 4,
2552
+}
2553
+`;
2554
+
2555
exports[`ProfilingCache should record changed props/state/context/hooks: CommitDetails commitIndex: 0 1`] = `
2556
Object {
2557
"changeDescriptions": Map {
packages/react-devtools-shared/src/__tests__/profilingCache-test.js
+124
@@ -308,6 +308,130 @@ describe('ProfilingCache', () => {
308
}
309
});
310
311
+ it('should properly detect changed hooks', () => {
312
+ const Context = React.createContext(0);
313
+
314
+ function reducer(state, action) {
315
+ switch (action.type) {
316
+ case 'invert':
317
+ return {value: !state.value};
318
+ default:
319
+ throw new Error();
320
+ }
321
+ }
322
+
323
+ let dispatch = null;
324
+ let setState = null;
325
+
326
+ const Component = ({count, string}) => {
327
+ // These hooks may change and initiate re-renders.
328
+ setState = React.useState('abc')[1];
329
+ dispatch = React.useReducer(reducer, {value: true})[1];
330
+
331
+ // This hook's return value may change between renders,
332
+ // but the hook itself isn't stateful.
333
+ React.useContext(Context);
334
+
335
+ // These hooks and their dependencies may not change between renders.
336
+ // We're using them to ensure that they don't trigger false positives.
337
+ React.useCallback(() => () => {}, [string]);
338
+ React.useMemo(() => string, [string]);
339
+
340
+ // These hooks never "change".
341
+ React.useEffect(() => {}, [string]);
342
+ React.useLayoutEffect(() => {}, [string]);
343
+
344
+ return null;
345
+ };
346
+
347
+ const container = document.createElement('div');
348
+
349
+ utils.act(() => store.profilerStore.startProfiling());
350
+ utils.act(() =>
351
+ ReactDOM.render(
352
+ <Context.Provider value={true}>
353
+ <Component count={1} />
354
+ </Context.Provider>,
355
+ container,
356
+ ),
357
+ );
358
+
359
+ // Second render has no changed hooks, only changed props.
360
+ utils.act(() =>
361
+ ReactDOM.render(
362
+ <Context.Provider value={true}>
363
+ <Component count={2} />
364
+ </Context.Provider>,
365
+ container,
366
+ ),
367
+ );
368
+
369
+ // Third render has a changed reducer hook
370
+ utils.act(() => dispatch({type: 'invert'}));
371
+
372
+ // Fourth render has a changed state hook
373
+ utils.act(() => setState('def'));
374
+
375
+ // Fifth render has a changed context value, but no changed hook.
376
+ // Technically, DevTools will miss this "context" change since it only tracks legacy context.
377
+ utils.act(() =>
378
+ ReactDOM.render(
379
+ <Context.Provider value={false}>
380
+ <Component count={2} />
381
+ </Context.Provider>,
382
+ container,
383
+ ),
384
+ );
385
+
386
+ utils.act(() => store.profilerStore.stopProfiling());
387
+
388
+ const allCommitData = [];
389
+
390
+ function Validator({commitIndex, previousCommitDetails, rootID}) {
391
+ const commitData = store.profilerStore.getCommitData(rootID, commitIndex);
392
+ if (previousCommitDetails != null) {
393
+ expect(commitData).toEqual(previousCommitDetails);
394
+ } else {
395
+ allCommitData.push(commitData);
396
+ expect(commitData).toMatchSnapshot(
397
+ `CommitDetails commitIndex: ${commitIndex}`,
398
+ );
399
+ }
400
+ return null;
401
+ }
402
+
403
+ const rootID = store.roots[0];
404
+
405
+ for (let commitIndex = 0; commitIndex < 5; commitIndex++) {
406
+ utils.act(() => {
407
+ TestRenderer.create(
408
+ <Validator
409
+ commitIndex={commitIndex}
410
+ previousCommitDetails={null}
411
+ rootID={rootID}
412
+ />,
413
+ );
414
+ });
415
+ }
416
+
417
+ expect(allCommitData).toHaveLength(5);
418
+
419
+ // Export and re-import profile data and make sure it is retained.
420
+ utils.exportImportHelper(bridge, store);
421
+
422
+ for (let commitIndex = 0; commitIndex < 5; commitIndex++) {
423
+ utils.act(() => {
424
+ TestRenderer.create(
425
+ <Validator
426
+ commitIndex={commitIndex}
427
+ previousCommitDetails={allCommitData[commitIndex]}
428
+ rootID={rootID}
429
+ />,
430
+ );
431
+ });
432
+ }
433
+ });
434
+
435
it('should calculate a self duration based on actual children (not filtered children)', () => {
436
store.componentFilters = [utils.createDisplayNameFilter('^Parent$')];
437