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 {FrontendBridge} from 'react-devtools-shared/src/bridge';
    
  11. import type Store from 'react-devtools-shared/src/devtools/store';
    
  12. 
    
  13. describe('ProfilingCache', () => {
    
  14.   let PropTypes;
    
  15.   let React;
    
  16.   let ReactDOM;
    
  17.   let ReactDOMClient;
    
  18.   let Scheduler;
    
  19.   let bridge: FrontendBridge;
    
  20.   let legacyRender;
    
  21.   let store: Store;
    
  22.   let utils;
    
  23. 
    
  24.   beforeEach(() => {
    
  25.     utils = require('./utils');
    
  26.     utils.beforeEachProfiling();
    
  27. 
    
  28.     legacyRender = utils.legacyRender;
    
  29. 
    
  30.     bridge = global.bridge;
    
  31.     store = global.store;
    
  32.     store.collapseNodesByDefault = false;
    
  33.     store.recordChangeDescriptions = true;
    
  34. 
    
  35.     PropTypes = require('prop-types');
    
  36.     React = require('react');
    
  37.     ReactDOM = require('react-dom');
    
  38.     ReactDOMClient = require('react-dom/client');
    
  39.     Scheduler = require('scheduler');
    
  40.   });
    
  41. 
    
  42.   // @reactVersion >= 16.9
    
  43.   it('should collect data for each root (including ones added or mounted after profiling started)', () => {
    
  44.     const Parent = ({count}) => {
    
  45.       Scheduler.unstable_advanceTime(10);
    
  46.       const children = new Array(count)
    
  47.         .fill(true)
    
  48.         .map((_, index) => <Child key={index} duration={index} />);
    
  49.       return (
    
  50.         <React.Fragment>
    
  51.           {children}
    
  52.           <MemoizedChild duration={1} />
    
  53.         </React.Fragment>
    
  54.       );
    
  55.     };
    
  56.     const Child = ({duration}) => {
    
  57.       Scheduler.unstable_advanceTime(duration);
    
  58.       return null;
    
  59.     };
    
  60.     const MemoizedChild = React.memo(Child);
    
  61. 
    
  62.     const RootA = ({children}) => children;
    
  63.     const RootB = ({children}) => children;
    
  64.     const RootC = ({children}) => children;
    
  65. 
    
  66.     const containerA = document.createElement('div');
    
  67.     const containerB = document.createElement('div');
    
  68.     const containerC = document.createElement('div');
    
  69. 
    
  70.     utils.act(() =>
    
  71.       legacyRender(
    
  72.         <RootA>
    
  73.           <Parent count={2} />
    
  74.         </RootA>,
    
  75.         containerA,
    
  76.       ),
    
  77.     );
    
  78.     utils.act(() =>
    
  79.       legacyRender(
    
  80.         <RootB>
    
  81.           <Parent count={1} />
    
  82.         </RootB>,
    
  83.         containerB,
    
  84.       ),
    
  85.     );
    
  86.     utils.act(() => store.profilerStore.startProfiling());
    
  87.     utils.act(() =>
    
  88.       legacyRender(
    
  89.         <RootA>
    
  90.           <Parent count={3} />
    
  91.         </RootA>,
    
  92.         containerA,
    
  93.       ),
    
  94.     );
    
  95.     utils.act(() =>
    
  96.       legacyRender(
    
  97.         <RootC>
    
  98.           <Parent count={1} />
    
  99.         </RootC>,
    
  100.         containerC,
    
  101.       ),
    
  102.     );
    
  103.     utils.act(() =>
    
  104.       legacyRender(
    
  105.         <RootA>
    
  106.           <Parent count={1} />
    
  107.         </RootA>,
    
  108.         containerA,
    
  109.       ),
    
  110.     );
    
  111.     utils.act(() => ReactDOM.unmountComponentAtNode(containerB));
    
  112.     utils.act(() =>
    
  113.       legacyRender(
    
  114.         <RootA>
    
  115.           <Parent count={0} />
    
  116.         </RootA>,
    
  117.         containerA,
    
  118.       ),
    
  119.     );
    
  120.     utils.act(() => store.profilerStore.stopProfiling());
    
  121.     utils.act(() => ReactDOM.unmountComponentAtNode(containerA));
    
  122. 
    
  123.     const rootIDs = Array.from(
    
  124.       store.profilerStore.profilingData.dataForRoots.values(),
    
  125.     ).map(({rootID}) => rootID);
    
  126.     expect(rootIDs).toHaveLength(3);
    
  127. 
    
  128.     const originalProfilingDataForRoot = [];
    
  129. 
    
  130.     let data = store.profilerStore.getDataForRoot(rootIDs[0]);
    
  131.     expect(data.displayName).toMatchInlineSnapshot(`"RootA"`);
    
  132.     expect(data.commitData).toHaveLength(3);
    
  133.     originalProfilingDataForRoot.push(data);
    
  134. 
    
  135.     data = store.profilerStore.getDataForRoot(rootIDs[1]);
    
  136.     expect(data.displayName).toMatchInlineSnapshot(`"RootC"`);
    
  137.     expect(data.commitData).toHaveLength(1);
    
  138.     originalProfilingDataForRoot.push(data);
    
  139. 
    
  140.     data = store.profilerStore.getDataForRoot(rootIDs[2]);
    
  141.     expect(data.displayName).toMatchInlineSnapshot(`"RootB"`);
    
  142.     expect(data.commitData).toHaveLength(1);
    
  143.     originalProfilingDataForRoot.push(data);
    
  144. 
    
  145.     utils.exportImportHelper(bridge, store);
    
  146. 
    
  147.     rootIDs.forEach((rootID, index) => {
    
  148.       const current = store.profilerStore.getDataForRoot(rootID);
    
  149.       const prev = originalProfilingDataForRoot[index];
    
  150.       expect(current).toEqual(prev);
    
  151.     });
    
  152.   });
    
  153. 
    
  154.   // @reactVersion >= 16.9
    
  155.   it('should collect data for each commit', () => {
    
  156.     const Parent = ({count}) => {
    
  157.       Scheduler.unstable_advanceTime(10);
    
  158.       const children = new Array(count)
    
  159.         .fill(true)
    
  160.         .map((_, index) => <Child key={index} duration={index} />);
    
  161.       return (
    
  162.         <React.Fragment>
    
  163.           {children}
    
  164.           <MemoizedChild duration={1} />
    
  165.         </React.Fragment>
    
  166.       );
    
  167.     };
    
  168.     const Child = ({duration}) => {
    
  169.       Scheduler.unstable_advanceTime(duration);
    
  170.       return null;
    
  171.     };
    
  172.     const MemoizedChild = React.memo(Child);
    
  173. 
    
  174.     const container = document.createElement('div');
    
  175. 
    
  176.     utils.act(() => store.profilerStore.startProfiling());
    
  177.     utils.act(() => legacyRender(<Parent count={2} />, container));
    
  178.     utils.act(() => legacyRender(<Parent count={3} />, container));
    
  179.     utils.act(() => legacyRender(<Parent count={1} />, container));
    
  180.     utils.act(() => legacyRender(<Parent count={0} />, container));
    
  181.     utils.act(() => store.profilerStore.stopProfiling());
    
  182. 
    
  183.     const rootID = store.roots[0];
    
  184. 
    
  185.     const prevCommitData =
    
  186.       store.profilerStore.getDataForRoot(rootID).commitData;
    
  187.     expect(prevCommitData).toHaveLength(4);
    
  188. 
    
  189.     utils.exportImportHelper(bridge, store);
    
  190. 
    
  191.     const nextCommitData =
    
  192.       store.profilerStore.getDataForRoot(rootID).commitData;
    
  193.     expect(nextCommitData).toHaveLength(4);
    
  194.     nextCommitData.forEach((commitData, index) => {
    
  195.       expect(commitData).toEqual(prevCommitData[index]);
    
  196.     });
    
  197.   });
    
  198. 
    
  199.   // @reactVersion >= 16.9
    
  200.   it('should record changed props/state/context/hooks', () => {
    
  201.     let instance = null;
    
  202. 
    
  203.     const ModernContext = React.createContext(0);
    
  204. 
    
  205.     class LegacyContextProvider extends React.Component<any, {count: number}> {
    
  206.       static childContextTypes = {
    
  207.         count: PropTypes.number,
    
  208.       };
    
  209.       state = {count: 0};
    
  210.       getChildContext() {
    
  211.         return this.state;
    
  212.       }
    
  213.       render() {
    
  214.         instance = this;
    
  215.         return (
    
  216.           <ModernContext.Provider value={this.state.count}>
    
  217.             <React.Fragment>
    
  218.               <ModernContextConsumer />
    
  219.               <LegacyContextConsumer />
    
  220.             </React.Fragment>
    
  221.           </ModernContext.Provider>
    
  222.         );
    
  223.       }
    
  224.     }
    
  225. 
    
  226.     const FunctionComponentWithHooks = ({count}) => {
    
  227.       React.useMemo(() => count, [count]);
    
  228.       return null;
    
  229.     };
    
  230. 
    
  231.     class ModernContextConsumer extends React.Component<any> {
    
  232.       static contextType = ModernContext;
    
  233.       render() {
    
  234.         return <FunctionComponentWithHooks count={this.context} />;
    
  235.       }
    
  236.     }
    
  237. 
    
  238.     class LegacyContextConsumer extends React.Component<any> {
    
  239.       static contextTypes = {
    
  240.         count: PropTypes.number,
    
  241.       };
    
  242.       render() {
    
  243.         return <FunctionComponentWithHooks count={this.context.count} />;
    
  244.       }
    
  245.     }
    
  246. 
    
  247.     const container = document.createElement('div');
    
  248. 
    
  249.     utils.act(() => store.profilerStore.startProfiling());
    
  250.     utils.act(() => legacyRender(<LegacyContextProvider />, container));
    
  251.     expect(instance).not.toBeNull();
    
  252.     utils.act(() => (instance: any).setState({count: 1}));
    
  253.     utils.act(() =>
    
  254.       legacyRender(<LegacyContextProvider foo={123} />, container),
    
  255.     );
    
  256.     utils.act(() =>
    
  257.       legacyRender(<LegacyContextProvider bar="abc" />, container),
    
  258.     );
    
  259.     utils.act(() => legacyRender(<LegacyContextProvider />, container));
    
  260.     utils.act(() => store.profilerStore.stopProfiling());
    
  261. 
    
  262.     const rootID = store.roots[0];
    
  263. 
    
  264.     let changeDescriptions = store.profilerStore
    
  265.       .getDataForRoot(rootID)
    
  266.       .commitData.map(commitData => commitData.changeDescriptions);
    
  267.     expect(changeDescriptions).toHaveLength(5);
    
  268.     expect(changeDescriptions[0]).toMatchInlineSnapshot(`
    
  269.       Map {
    
  270.         2 => {
    
  271.           "context": null,
    
  272.           "didHooksChange": false,
    
  273.           "isFirstMount": true,
    
  274.           "props": null,
    
  275.           "state": null,
    
  276.         },
    
  277.         4 => {
    
  278.           "context": null,
    
  279.           "didHooksChange": false,
    
  280.           "isFirstMount": true,
    
  281.           "props": null,
    
  282.           "state": null,
    
  283.         },
    
  284.         5 => {
    
  285.           "context": null,
    
  286.           "didHooksChange": false,
    
  287.           "isFirstMount": true,
    
  288.           "props": null,
    
  289.           "state": null,
    
  290.         },
    
  291.         6 => {
    
  292.           "context": null,
    
  293.           "didHooksChange": false,
    
  294.           "isFirstMount": true,
    
  295.           "props": null,
    
  296.           "state": null,
    
  297.         },
    
  298.         7 => {
    
  299.           "context": null,
    
  300.           "didHooksChange": false,
    
  301.           "isFirstMount": true,
    
  302.           "props": null,
    
  303.           "state": null,
    
  304.         },
    
  305.       }
    
  306.     `);
    
  307.     expect(changeDescriptions[1]).toMatchInlineSnapshot(`
    
  308.       Map {
    
  309.         5 => {
    
  310.           "context": null,
    
  311.           "didHooksChange": false,
    
  312.           "hooks": [],
    
  313.           "isFirstMount": false,
    
  314.           "props": [
    
  315.             "count",
    
  316.           ],
    
  317.           "state": null,
    
  318.         },
    
  319.         4 => {
    
  320.           "context": true,
    
  321.           "didHooksChange": false,
    
  322.           "hooks": null,
    
  323.           "isFirstMount": false,
    
  324.           "props": [],
    
  325.           "state": null,
    
  326.         },
    
  327.         7 => {
    
  328.           "context": null,
    
  329.           "didHooksChange": false,
    
  330.           "hooks": [],
    
  331.           "isFirstMount": false,
    
  332.           "props": [
    
  333.             "count",
    
  334.           ],
    
  335.           "state": null,
    
  336.         },
    
  337.         6 => {
    
  338.           "context": [
    
  339.             "count",
    
  340.           ],
    
  341.           "didHooksChange": false,
    
  342.           "hooks": null,
    
  343.           "isFirstMount": false,
    
  344.           "props": [],
    
  345.           "state": null,
    
  346.         },
    
  347.         2 => {
    
  348.           "context": null,
    
  349.           "didHooksChange": false,
    
  350.           "hooks": [],
    
  351.           "isFirstMount": false,
    
  352.           "props": [],
    
  353.           "state": [
    
  354.             "count",
    
  355.           ],
    
  356.         },
    
  357.       }
    
  358.     `);
    
  359.     expect(changeDescriptions[2]).toMatchInlineSnapshot(`
    
  360.       Map {
    
  361.         5 => {
    
  362.           "context": null,
    
  363.           "didHooksChange": false,
    
  364.           "hooks": [],
    
  365.           "isFirstMount": false,
    
  366.           "props": [],
    
  367.           "state": null,
    
  368.         },
    
  369.         4 => {
    
  370.           "context": false,
    
  371.           "didHooksChange": false,
    
  372.           "hooks": null,
    
  373.           "isFirstMount": false,
    
  374.           "props": [],
    
  375.           "state": null,
    
  376.         },
    
  377.         7 => {
    
  378.           "context": null,
    
  379.           "didHooksChange": false,
    
  380.           "hooks": [],
    
  381.           "isFirstMount": false,
    
  382.           "props": [],
    
  383.           "state": null,
    
  384.         },
    
  385.         6 => {
    
  386.           "context": [],
    
  387.           "didHooksChange": false,
    
  388.           "hooks": null,
    
  389.           "isFirstMount": false,
    
  390.           "props": [],
    
  391.           "state": null,
    
  392.         },
    
  393.         2 => {
    
  394.           "context": null,
    
  395.           "didHooksChange": false,
    
  396.           "hooks": [],
    
  397.           "isFirstMount": false,
    
  398.           "props": [
    
  399.             "foo",
    
  400.           ],
    
  401.           "state": [],
    
  402.         },
    
  403.       }
    
  404.     `);
    
  405.     expect(changeDescriptions[3]).toMatchInlineSnapshot(`
    
  406.       Map {
    
  407.         5 => {
    
  408.           "context": null,
    
  409.           "didHooksChange": false,
    
  410.           "hooks": [],
    
  411.           "isFirstMount": false,
    
  412.           "props": [],
    
  413.           "state": null,
    
  414.         },
    
  415.         4 => {
    
  416.           "context": false,
    
  417.           "didHooksChange": false,
    
  418.           "hooks": null,
    
  419.           "isFirstMount": false,
    
  420.           "props": [],
    
  421.           "state": null,
    
  422.         },
    
  423.         7 => {
    
  424.           "context": null,
    
  425.           "didHooksChange": false,
    
  426.           "hooks": [],
    
  427.           "isFirstMount": false,
    
  428.           "props": [],
    
  429.           "state": null,
    
  430.         },
    
  431.         6 => {
    
  432.           "context": [],
    
  433.           "didHooksChange": false,
    
  434.           "hooks": null,
    
  435.           "isFirstMount": false,
    
  436.           "props": [],
    
  437.           "state": null,
    
  438.         },
    
  439.         2 => {
    
  440.           "context": null,
    
  441.           "didHooksChange": false,
    
  442.           "hooks": [],
    
  443.           "isFirstMount": false,
    
  444.           "props": [
    
  445.             "foo",
    
  446.             "bar",
    
  447.           ],
    
  448.           "state": [],
    
  449.         },
    
  450.       }
    
  451.     `);
    
  452.     expect(changeDescriptions[4]).toMatchInlineSnapshot(`
    
  453.       Map {
    
  454.         5 => {
    
  455.           "context": null,
    
  456.           "didHooksChange": false,
    
  457.           "hooks": [],
    
  458.           "isFirstMount": false,
    
  459.           "props": [],
    
  460.           "state": null,
    
  461.         },
    
  462.         4 => {
    
  463.           "context": false,
    
  464.           "didHooksChange": false,
    
  465.           "hooks": null,
    
  466.           "isFirstMount": false,
    
  467.           "props": [],
    
  468.           "state": null,
    
  469.         },
    
  470.         7 => {
    
  471.           "context": null,
    
  472.           "didHooksChange": false,
    
  473.           "hooks": [],
    
  474.           "isFirstMount": false,
    
  475.           "props": [],
    
  476.           "state": null,
    
  477.         },
    
  478.         6 => {
    
  479.           "context": [],
    
  480.           "didHooksChange": false,
    
  481.           "hooks": null,
    
  482.           "isFirstMount": false,
    
  483.           "props": [],
    
  484.           "state": null,
    
  485.         },
    
  486.         2 => {
    
  487.           "context": null,
    
  488.           "didHooksChange": false,
    
  489.           "hooks": [],
    
  490.           "isFirstMount": false,
    
  491.           "props": [
    
  492.             "bar",
    
  493.           ],
    
  494.           "state": [],
    
  495.         },
    
  496.       }
    
  497.     `);
    
  498. 
    
  499.     utils.exportImportHelper(bridge, store);
    
  500. 
    
  501.     const prevChangeDescriptions = [...changeDescriptions];
    
  502. 
    
  503.     changeDescriptions = store.profilerStore
    
  504.       .getDataForRoot(rootID)
    
  505.       .commitData.map(commitData => commitData.changeDescriptions);
    
  506.     expect(changeDescriptions).toHaveLength(5);
    
  507. 
    
  508.     for (let commitIndex = 0; commitIndex < 5; commitIndex++) {
    
  509.       expect(changeDescriptions[commitIndex]).toEqual(
    
  510.         prevChangeDescriptions[commitIndex],
    
  511.       );
    
  512.     }
    
  513.   });
    
  514. 
    
  515.   // @reactVersion >= 18.0
    
  516.   it('should properly detect changed hooks', () => {
    
  517.     const Context = React.createContext(0);
    
  518. 
    
  519.     function reducer(state, action) {
    
  520.       switch (action.type) {
    
  521.         case 'invert':
    
  522.           return {value: !state.value};
    
  523.         default:
    
  524.           throw new Error();
    
  525.       }
    
  526.     }
    
  527. 
    
  528.     let snapshot = 0;
    
  529.     function getServerSnapshot() {
    
  530.       return snapshot;
    
  531.     }
    
  532.     function getClientSnapshot() {
    
  533.       return snapshot;
    
  534.     }
    
  535. 
    
  536.     let syncExternalStoreCallback;
    
  537.     function subscribe(callback) {
    
  538.       syncExternalStoreCallback = callback;
    
  539.     }
    
  540. 
    
  541.     let dispatch = null;
    
  542.     let setState = null;
    
  543. 
    
  544.     const Component = ({count, string}) => {
    
  545.       // These hooks may change and initiate re-renders.
    
  546.       setState = React.useState('abc')[1];
    
  547.       dispatch = React.useReducer(reducer, {value: true})[1];
    
  548.       React.useSyncExternalStore(
    
  549.         subscribe,
    
  550.         getClientSnapshot,
    
  551.         getServerSnapshot,
    
  552.       );
    
  553. 
    
  554.       // This hook's return value may change between renders,
    
  555.       // but the hook itself isn't stateful.
    
  556.       React.useContext(Context);
    
  557. 
    
  558.       // These hooks never change in a way that schedules an update.
    
  559.       React.useCallback(() => () => {}, [string]);
    
  560.       React.useMemo(() => string, [string]);
    
  561.       React.useCallback(() => () => {}, [count]);
    
  562.       React.useMemo(() => count, [count]);
    
  563.       React.useCallback(() => () => {});
    
  564.       React.useMemo(() => string);
    
  565. 
    
  566.       // These hooks never change in a way that schedules an update.
    
  567.       React.useEffect(() => {}, [string]);
    
  568.       React.useLayoutEffect(() => {}, [string]);
    
  569.       React.useEffect(() => {}, [count]);
    
  570.       React.useLayoutEffect(() => {}, [count]);
    
  571.       React.useEffect(() => {});
    
  572.       React.useLayoutEffect(() => {});
    
  573. 
    
  574.       return null;
    
  575.     };
    
  576. 
    
  577.     const container = document.createElement('div');
    
  578. 
    
  579.     utils.act(() => store.profilerStore.startProfiling());
    
  580.     utils.act(() =>
    
  581.       legacyRender(
    
  582.         <Context.Provider value={true}>
    
  583.           <Component count={1} />
    
  584.         </Context.Provider>,
    
  585.         container,
    
  586.       ),
    
  587.     );
    
  588. 
    
  589.     // Second render has no changed hooks, only changed props.
    
  590.     utils.act(() =>
    
  591.       legacyRender(
    
  592.         <Context.Provider value={true}>
    
  593.           <Component count={2} />
    
  594.         </Context.Provider>,
    
  595.         container,
    
  596.       ),
    
  597.     );
    
  598. 
    
  599.     // Third render has a changed reducer hook.
    
  600.     utils.act(() => dispatch({type: 'invert'}));
    
  601. 
    
  602.     // Fourth render has a changed state hook.
    
  603.     utils.act(() => setState('def'));
    
  604. 
    
  605.     // Fifth render has a changed context value, but no changed hook.
    
  606.     utils.act(() =>
    
  607.       legacyRender(
    
  608.         <Context.Provider value={false}>
    
  609.           <Component count={2} />
    
  610.         </Context.Provider>,
    
  611.         container,
    
  612.       ),
    
  613.     );
    
  614. 
    
  615.     // 6th renderer is triggered by a sync external store change.
    
  616.     utils.act(() => {
    
  617.       snapshot++;
    
  618.       syncExternalStoreCallback();
    
  619.     });
    
  620. 
    
  621.     utils.act(() => store.profilerStore.stopProfiling());
    
  622. 
    
  623.     const rootID = store.roots[0];
    
  624. 
    
  625.     const changeDescriptions = store.profilerStore
    
  626.       .getDataForRoot(rootID)
    
  627.       .commitData.map(commitData => commitData.changeDescriptions);
    
  628.     expect(changeDescriptions).toHaveLength(6);
    
  629. 
    
  630.     // 1st render: No change
    
  631.     expect(changeDescriptions[0]).toMatchInlineSnapshot(`
    
  632.       Map {
    
  633.         3 => {
    
  634.           "context": null,
    
  635.           "didHooksChange": false,
    
  636.           "isFirstMount": true,
    
  637.           "props": null,
    
  638.           "state": null,
    
  639.         },
    
  640.       }
    
  641.     `);
    
  642. 
    
  643.     // 2nd render: Changed props
    
  644.     expect(changeDescriptions[1]).toMatchInlineSnapshot(`
    
  645.       Map {
    
  646.         3 => {
    
  647.           "context": false,
    
  648.           "didHooksChange": false,
    
  649.           "hooks": [],
    
  650.           "isFirstMount": false,
    
  651.           "props": [
    
  652.             "count",
    
  653.           ],
    
  654.           "state": null,
    
  655.         },
    
  656.       }
    
  657.     `);
    
  658. 
    
  659.     // 3rd render: Changed useReducer
    
  660.     expect(changeDescriptions[2]).toMatchInlineSnapshot(`
    
  661.       Map {
    
  662.         3 => {
    
  663.           "context": false,
    
  664.           "didHooksChange": true,
    
  665.           "hooks": [
    
  666.             1,
    
  667.           ],
    
  668.           "isFirstMount": false,
    
  669.           "props": [],
    
  670.           "state": null,
    
  671.         },
    
  672.       }
    
  673.     `);
    
  674. 
    
  675.     // 4th render: Changed useState
    
  676.     expect(changeDescriptions[3]).toMatchInlineSnapshot(`
    
  677.       Map {
    
  678.         3 => {
    
  679.           "context": false,
    
  680.           "didHooksChange": true,
    
  681.           "hooks": [
    
  682.             0,
    
  683.           ],
    
  684.           "isFirstMount": false,
    
  685.           "props": [],
    
  686.           "state": null,
    
  687.         },
    
  688.       }
    
  689.     `);
    
  690. 
    
  691.     // 5th render: Changed context
    
  692.     expect(changeDescriptions[4]).toMatchInlineSnapshot(`
    
  693.       Map {
    
  694.         3 => {
    
  695.           "context": true,
    
  696.           "didHooksChange": false,
    
  697.           "hooks": [],
    
  698.           "isFirstMount": false,
    
  699.           "props": [],
    
  700.           "state": null,
    
  701.         },
    
  702.       }
    
  703.     `);
    
  704. 
    
  705.     // 6th render: Sync external store
    
  706.     expect(changeDescriptions[5]).toMatchInlineSnapshot(`
    
  707.       Map {
    
  708.         3 => {
    
  709.           "context": false,
    
  710.           "didHooksChange": true,
    
  711.           "hooks": [
    
  712.             2,
    
  713.           ],
    
  714.           "isFirstMount": false,
    
  715.           "props": [],
    
  716.           "state": null,
    
  717.         },
    
  718.       }
    
  719.     `);
    
  720. 
    
  721.     expect(changeDescriptions).toHaveLength(6);
    
  722. 
    
  723.     // Export and re-import profile data and make sure it is retained.
    
  724.     utils.exportImportHelper(bridge, store);
    
  725. 
    
  726.     for (let commitIndex = 0; commitIndex < 6; commitIndex++) {
    
  727.       const commitData = store.profilerStore.getCommitData(rootID, commitIndex);
    
  728.       expect(commitData.changeDescriptions).toEqual(
    
  729.         changeDescriptions[commitIndex],
    
  730.       );
    
  731.     }
    
  732.   });
    
  733. 
    
  734.   // @reactVersion >= 18.0
    
  735.   it('should calculate durations based on actual children (not filtered children)', () => {
    
  736.     store.componentFilters = [utils.createDisplayNameFilter('^Parent$')];
    
  737. 
    
  738.     const Grandparent = () => {
    
  739.       Scheduler.unstable_advanceTime(10);
    
  740.       return (
    
  741.         <React.Fragment>
    
  742.           <Parent key="one" />
    
  743.           <Parent key="two" />
    
  744.         </React.Fragment>
    
  745.       );
    
  746.     };
    
  747.     const Parent = () => {
    
  748.       Scheduler.unstable_advanceTime(2);
    
  749.       return <Child />;
    
  750.     };
    
  751.     const Child = () => {
    
  752.       Scheduler.unstable_advanceTime(1);
    
  753.       return null;
    
  754.     };
    
  755. 
    
  756.     utils.act(() => store.profilerStore.startProfiling());
    
  757.     utils.act(() =>
    
  758.       legacyRender(<Grandparent />, document.createElement('div')),
    
  759.     );
    
  760.     utils.act(() => store.profilerStore.stopProfiling());
    
  761. 
    
  762.     expect(store).toMatchInlineSnapshot(`
    
  763.       [root]
    
  764.         â–¾ <Grandparent>
    
  765.             <Child>
    
  766.             <Child>
    
  767.     `);
    
  768. 
    
  769.     const rootID = store.roots[0];
    
  770.     const commitData = store.profilerStore.getDataForRoot(rootID).commitData;
    
  771.     expect(commitData).toHaveLength(1);
    
  772. 
    
  773.     // Actual duration should also include both filtered <Parent> components.
    
  774.     expect(commitData[0].fiberActualDurations).toMatchInlineSnapshot(`
    
  775.       Map {
    
  776.         1 => 16,
    
  777.         2 => 16,
    
  778.         4 => 1,
    
  779.         6 => 1,
    
  780.       }
    
  781.     `);
    
  782. 
    
  783.     expect(commitData[0].fiberSelfDurations).toMatchInlineSnapshot(`
    
  784.       Map {
    
  785.         1 => 0,
    
  786.         2 => 10,
    
  787.         4 => 1,
    
  788.         6 => 1,
    
  789.       }
    
  790.     `);
    
  791.   });
    
  792. 
    
  793.   // @reactVersion >= 17.0
    
  794.   it('should calculate durations correctly for suspended views', async () => {
    
  795.     let data;
    
  796.     const getData = () => {
    
  797.       if (data) {
    
  798.         return data;
    
  799.       } else {
    
  800.         throw new Promise(resolve => {
    
  801.           data = 'abc';
    
  802.           resolve(data);
    
  803.         });
    
  804.       }
    
  805.     };
    
  806. 
    
  807.     const Parent = () => {
    
  808.       Scheduler.unstable_advanceTime(10);
    
  809.       return (
    
  810.         <React.Suspense fallback={<Fallback />}>
    
  811.           <Async />
    
  812.         </React.Suspense>
    
  813.       );
    
  814.     };
    
  815.     const Fallback = () => {
    
  816.       Scheduler.unstable_advanceTime(2);
    
  817.       return 'Fallback...';
    
  818.     };
    
  819.     const Async = () => {
    
  820.       Scheduler.unstable_advanceTime(3);
    
  821.       return getData();
    
  822.     };
    
  823. 
    
  824.     utils.act(() => store.profilerStore.startProfiling());
    
  825.     await utils.actAsync(() =>
    
  826.       legacyRender(<Parent />, document.createElement('div')),
    
  827.     );
    
  828.     utils.act(() => store.profilerStore.stopProfiling());
    
  829. 
    
  830.     const rootID = store.roots[0];
    
  831.     const commitData = store.profilerStore.getDataForRoot(rootID).commitData;
    
  832.     expect(commitData).toHaveLength(2);
    
  833.     expect(commitData[0].fiberActualDurations).toMatchInlineSnapshot(`
    
  834.       Map {
    
  835.         1 => 15,
    
  836.         2 => 15,
    
  837.         3 => 5,
    
  838.         4 => 2,
    
  839.       }
    
  840.     `);
    
  841.     expect(commitData[0].fiberSelfDurations).toMatchInlineSnapshot(`
    
  842.       Map {
    
  843.         1 => 0,
    
  844.         2 => 10,
    
  845.         3 => 3,
    
  846.         4 => 2,
    
  847.       }
    
  848.     `);
    
  849.     expect(commitData[1].fiberActualDurations).toMatchInlineSnapshot(`
    
  850.       Map {
    
  851.         7 => 3,
    
  852.         3 => 3,
    
  853.       }
    
  854.     `);
    
  855.     expect(commitData[1].fiberSelfDurations).toMatchInlineSnapshot(`
    
  856.       Map {
    
  857.         7 => 3,
    
  858.         3 => 0,
    
  859.       }
    
  860.     `);
    
  861.   });
    
  862. 
    
  863.   // @reactVersion >= 16.9
    
  864.   it('should collect data for each rendered fiber', () => {
    
  865.     const Parent = ({count}) => {
    
  866.       Scheduler.unstable_advanceTime(10);
    
  867.       const children = new Array(count)
    
  868.         .fill(true)
    
  869.         .map((_, index) => <Child key={index} duration={index} />);
    
  870.       return (
    
  871.         <React.Fragment>
    
  872.           {children}
    
  873.           <MemoizedChild duration={1} />
    
  874.         </React.Fragment>
    
  875.       );
    
  876.     };
    
  877.     const Child = ({duration}) => {
    
  878.       Scheduler.unstable_advanceTime(duration);
    
  879.       return null;
    
  880.     };
    
  881.     const MemoizedChild = React.memo(Child);
    
  882. 
    
  883.     const container = document.createElement('div');
    
  884. 
    
  885.     utils.act(() => store.profilerStore.startProfiling());
    
  886.     utils.act(() => legacyRender(<Parent count={1} />, container));
    
  887.     utils.act(() => legacyRender(<Parent count={2} />, container));
    
  888.     utils.act(() => legacyRender(<Parent count={3} />, container));
    
  889.     utils.act(() => store.profilerStore.stopProfiling());
    
  890. 
    
  891.     const rootID = store.roots[0];
    
  892.     const allFiberCommits = [];
    
  893.     for (let index = 0; index < store.numElements; index++) {
    
  894.       const fiberID = store.getElementIDAtIndex(index);
    
  895.       const fiberCommits = store.profilerStore.profilingCache.getFiberCommits({
    
  896.         fiberID,
    
  897.         rootID,
    
  898.       });
    
  899. 
    
  900.       allFiberCommits.push(fiberCommits);
    
  901.     }
    
  902. 
    
  903.     expect(allFiberCommits).toMatchInlineSnapshot(`
    
  904.       [
    
  905.         [
    
  906.           0,
    
  907.           1,
    
  908.           2,
    
  909.         ],
    
  910.         [
    
  911.           0,
    
  912.           1,
    
  913.           2,
    
  914.         ],
    
  915.         [
    
  916.           1,
    
  917.           2,
    
  918.         ],
    
  919.         [
    
  920.           2,
    
  921.         ],
    
  922.         [
    
  923.           0,
    
  924.         ],
    
  925.       ]
    
  926.     `);
    
  927. 
    
  928.     utils.exportImportHelper(bridge, store);
    
  929. 
    
  930.     for (let index = 0; index < store.numElements; index++) {
    
  931.       const fiberID = store.getElementIDAtIndex(index);
    
  932.       const fiberCommits = store.profilerStore.profilingCache.getFiberCommits({
    
  933.         fiberID,
    
  934.         rootID,
    
  935.       });
    
  936. 
    
  937.       expect(fiberCommits).toEqual(allFiberCommits[index]);
    
  938.     }
    
  939.   });
    
  940. 
    
  941.   // @reactVersion >= 18.0.0
    
  942.   // @reactVersion <= 18.2.0
    
  943.   it('should handle unexpectedly shallow suspense trees for react v[18.0.0 - 18.2.0]', () => {
    
  944.     const container = document.createElement('div');
    
  945. 
    
  946.     utils.act(() => store.profilerStore.startProfiling());
    
  947.     utils.act(() => legacyRender(<React.Suspense />, container));
    
  948.     utils.act(() => store.profilerStore.stopProfiling());
    
  949. 
    
  950.     const rootID = store.roots[0];
    
  951.     const commitData = store.profilerStore.getDataForRoot(rootID).commitData;
    
  952.     expect(commitData).toMatchInlineSnapshot(`
    
  953.       [
    
  954.         {
    
  955.           "changeDescriptions": Map {},
    
  956.           "duration": 0,
    
  957.           "effectDuration": null,
    
  958.           "fiberActualDurations": Map {
    
  959.             1 => 0,
    
  960.             2 => 0,
    
  961.           },
    
  962.           "fiberSelfDurations": Map {
    
  963.             1 => 0,
    
  964.             2 => 0,
    
  965.           },
    
  966.           "passiveEffectDuration": null,
    
  967.           "priorityLevel": "Immediate",
    
  968.           "timestamp": 0,
    
  969.           "updaters": [
    
  970.             {
    
  971.               "displayName": "render()",
    
  972.               "hocDisplayNames": null,
    
  973.               "id": 1,
    
  974.               "key": null,
    
  975.               "type": 11,
    
  976.             },
    
  977.           ],
    
  978.         },
    
  979.       ]
    
  980.     `);
    
  981.   });
    
  982. 
    
  983.   // This test is not gated.
    
  984.   // For this test we use the current version of react, built from source.
    
  985.   it('should handle unexpectedly shallow suspense trees', () => {
    
  986.     const container = document.createElement('div');
    
  987. 
    
  988.     utils.act(() => store.profilerStore.startProfiling());
    
  989.     utils.act(() => legacyRender(<React.Suspense />, container));
    
  990.     utils.act(() => store.profilerStore.stopProfiling());
    
  991. 
    
  992.     const rootID = store.roots[0];
    
  993.     const commitData = store.profilerStore.getDataForRoot(rootID).commitData;
    
  994.     expect(commitData).toMatchInlineSnapshot(`
    
  995.       [
    
  996.         {
    
  997.           "changeDescriptions": Map {},
    
  998.           "duration": 0,
    
  999.           "effectDuration": null,
    
  1000.           "fiberActualDurations": Map {
    
  1001.             1 => 0,
    
  1002.             2 => 0,
    
  1003.           },
    
  1004.           "fiberSelfDurations": Map {
    
  1005.             1 => 0,
    
  1006.             2 => 0,
    
  1007.           },
    
  1008.           "passiveEffectDuration": null,
    
  1009.           "priorityLevel": "Normal",
    
  1010.           "timestamp": 0,
    
  1011.           "updaters": [
    
  1012.             {
    
  1013.               "displayName": "render()",
    
  1014.               "hocDisplayNames": null,
    
  1015.               "id": 1,
    
  1016.               "key": null,
    
  1017.               "type": 11,
    
  1018.             },
    
  1019.           ],
    
  1020.         },
    
  1021.       ]
    
  1022.     `);
    
  1023.   });
    
  1024. 
    
  1025.   // See https://github.com/facebook/react/issues/18831
    
  1026.   // @reactVersion >= 16.9
    
  1027.   it('should not crash during route transitions with Suspense', () => {
    
  1028.     const RouterContext = React.createContext();
    
  1029. 
    
  1030.     function App() {
    
  1031.       return (
    
  1032.         <Router>
    
  1033.           <Switch>
    
  1034.             <Route path="/">
    
  1035.               <Home />
    
  1036.             </Route>
    
  1037.             <Route path="/about">
    
  1038.               <About />
    
  1039.             </Route>
    
  1040.           </Switch>
    
  1041.         </Router>
    
  1042.       );
    
  1043.     }
    
  1044. 
    
  1045.     const Home = () => {
    
  1046.       return (
    
  1047.         <React.Suspense>
    
  1048.           <Link path="/about">Home</Link>
    
  1049.         </React.Suspense>
    
  1050.       );
    
  1051.     };
    
  1052. 
    
  1053.     const About = () => <div>About</div>;
    
  1054. 
    
  1055.     // Mimics https://github.com/ReactTraining/react-router/blob/master/packages/react-router/modules/Router.js
    
  1056.     function Router({children}) {
    
  1057.       const [path, setPath] = React.useState('/');
    
  1058.       return (
    
  1059.         <RouterContext.Provider value={{path, setPath}}>
    
  1060.           {children}
    
  1061.         </RouterContext.Provider>
    
  1062.       );
    
  1063.     }
    
  1064. 
    
  1065.     // Mimics https://github.com/ReactTraining/react-router/blob/master/packages/react-router/modules/Switch.js
    
  1066.     function Switch({children}) {
    
  1067.       return (
    
  1068.         <RouterContext.Consumer>
    
  1069.           {context => {
    
  1070.             let element = null;
    
  1071.             React.Children.forEach(children, child => {
    
  1072.               if (context.path === child.props.path) {
    
  1073.                 element = child.props.children;
    
  1074.               }
    
  1075.             });
    
  1076.             return element ? React.cloneElement(element) : null;
    
  1077.           }}
    
  1078.         </RouterContext.Consumer>
    
  1079.       );
    
  1080.     }
    
  1081. 
    
  1082.     // Mimics https://github.com/ReactTraining/react-router/blob/master/packages/react-router/modules/Route.js
    
  1083.     function Route({children, path}) {
    
  1084.       return null;
    
  1085.     }
    
  1086. 
    
  1087.     const linkRef = React.createRef();
    
  1088. 
    
  1089.     // Mimics https://github.com/ReactTraining/react-router/blob/master/packages/react-router-dom/modules/Link.js
    
  1090.     function Link({children, path}) {
    
  1091.       return (
    
  1092.         <RouterContext.Consumer>
    
  1093.           {context => {
    
  1094.             return (
    
  1095.               <button ref={linkRef} onClick={() => context.setPath(path)}>
    
  1096.                 {children}
    
  1097.               </button>
    
  1098.             );
    
  1099.           }}
    
  1100.         </RouterContext.Consumer>
    
  1101.       );
    
  1102.     }
    
  1103. 
    
  1104.     const {Simulate} = require('react-dom/test-utils');
    
  1105. 
    
  1106.     const container = document.createElement('div');
    
  1107.     utils.act(() => legacyRender(<App />, container));
    
  1108.     expect(container.textContent).toBe('Home');
    
  1109.     utils.act(() => store.profilerStore.startProfiling());
    
  1110.     utils.act(() => Simulate.click(linkRef.current));
    
  1111.     utils.act(() => store.profilerStore.stopProfiling());
    
  1112.     expect(container.textContent).toBe('About');
    
  1113.   });
    
  1114. 
    
  1115.   // @reactVersion >= 18.0
    
  1116.   it('components that were deleted and added to updaters during the layout phase should not crash', () => {
    
  1117.     let setChildUnmounted;
    
  1118.     function Child() {
    
  1119.       const [, setState] = React.useState(false);
    
  1120. 
    
  1121.       React.useLayoutEffect(() => {
    
  1122.         return () => setState(true);
    
  1123.       });
    
  1124. 
    
  1125.       return null;
    
  1126.     }
    
  1127. 
    
  1128.     function App() {
    
  1129.       const [childUnmounted, _setChildUnmounted] = React.useState(false);
    
  1130.       setChildUnmounted = _setChildUnmounted;
    
  1131.       return <>{!childUnmounted && <Child />}</>;
    
  1132.     }
    
  1133. 
    
  1134.     const root = ReactDOMClient.createRoot(document.createElement('div'));
    
  1135.     utils.act(() => root.render(<App />));
    
  1136.     utils.act(() => store.profilerStore.startProfiling());
    
  1137.     utils.act(() => setChildUnmounted(true));
    
  1138.     utils.act(() => store.profilerStore.stopProfiling());
    
  1139. 
    
  1140.     const updaters = store.profilerStore.getCommitData(
    
  1141.       store.roots[0],
    
  1142.       0,
    
  1143.     ).updaters;
    
  1144.     expect(updaters.length).toEqual(1);
    
  1145.     expect(updaters[0].displayName).toEqual('App');
    
  1146.   });
    
  1147. 
    
  1148.   // @reactVersion >= 18.0
    
  1149.   it('components in a deleted subtree and added to updaters during the layout phase should not crash', () => {
    
  1150.     let setChildUnmounted;
    
  1151.     function Child() {
    
  1152.       return <GrandChild />;
    
  1153.     }
    
  1154. 
    
  1155.     function GrandChild() {
    
  1156.       const [, setState] = React.useState(false);
    
  1157. 
    
  1158.       React.useLayoutEffect(() => {
    
  1159.         return () => setState(true);
    
  1160.       });
    
  1161. 
    
  1162.       return null;
    
  1163.     }
    
  1164. 
    
  1165.     function App() {
    
  1166.       const [childUnmounted, _setChildUnmounted] = React.useState(false);
    
  1167.       setChildUnmounted = _setChildUnmounted;
    
  1168.       return <>{!childUnmounted && <Child />}</>;
    
  1169.     }
    
  1170. 
    
  1171.     const root = ReactDOMClient.createRoot(document.createElement('div'));
    
  1172.     utils.act(() => root.render(<App />));
    
  1173.     utils.act(() => store.profilerStore.startProfiling());
    
  1174.     utils.act(() => setChildUnmounted(true));
    
  1175.     utils.act(() => store.profilerStore.stopProfiling());
    
  1176. 
    
  1177.     const updaters = store.profilerStore.getCommitData(
    
  1178.       store.roots[0],
    
  1179.       0,
    
  1180.     ).updaters;
    
  1181.     expect(updaters.length).toEqual(1);
    
  1182.     expect(updaters[0].displayName).toEqual('App');
    
  1183.   });
    
  1184. 
    
  1185.   // @reactVersion >= 18.0
    
  1186.   it('components that were deleted should not be added to updaters during the passive phase', () => {
    
  1187.     let setChildUnmounted;
    
  1188.     function Child() {
    
  1189.       const [, setState] = React.useState(false);
    
  1190.       React.useEffect(() => {
    
  1191.         return () => setState(true);
    
  1192.       });
    
  1193. 
    
  1194.       return null;
    
  1195.     }
    
  1196. 
    
  1197.     function App() {
    
  1198.       const [childUnmounted, _setChildUnmounted] = React.useState(false);
    
  1199.       setChildUnmounted = _setChildUnmounted;
    
  1200.       return <>{!childUnmounted && <Child />}</>;
    
  1201.     }
    
  1202. 
    
  1203.     const root = ReactDOMClient.createRoot(document.createElement('div'));
    
  1204.     utils.act(() => root.render(<App />));
    
  1205.     utils.act(() => store.profilerStore.startProfiling());
    
  1206.     utils.act(() => setChildUnmounted(true));
    
  1207.     utils.act(() => store.profilerStore.stopProfiling());
    
  1208. 
    
  1209.     const updaters = store.profilerStore.getCommitData(
    
  1210.       store.roots[0],
    
  1211.       0,
    
  1212.     ).updaters;
    
  1213.     expect(updaters.length).toEqual(1);
    
  1214.     expect(updaters[0].displayName).toEqual('App');
    
  1215.   });
    
  1216. });