<|start_filename|>packages/react-devtools-shared/src/__tests__/profilingCache-test.js<|end_filename|>
/**
 * Copyright (c) Facebook, Inc. and its affiliates.
 *
 * This source code is licensed under the MIT license found in the
 * LICENSE file in the root directory of this source tree.
 *
 * @flow
 */

import type {FrontendBridge} from 'react-devtools-shared/src/bridge';
import type Store from 'react-devtools-shared/src/devtools/store';

describe('ProfilingCache', () => {
  let PropTypes;
  let React;
  let ReactDOM;
  let ReactDOMClient;
  let Scheduler;
  let bridge: FrontendBridge;
  let legacyRender;
  let store: Store;
  let utils;

  beforeEach(() => {
    utils = require('./utils');
    utils.beforeEachProfiling();

    legacyRender = utils.legacyRender;

    bridge = global.bridge;
    store = global.store;
    store.collapseNodesByDefault = false;
    store.recordChangeDescriptions = true;

    PropTypes = require('prop-types');
    React = require('react');
    ReactDOM = require('react-dom');
    ReactDOMClient = require('react-dom/client');
    Scheduler = require('scheduler');
  });

  it('should collect data for each root (including ones added or mounted after profiling started)', () => {
    const Parent = ({count}) => {
      Scheduler.unstable_advanceTime(10);
      const children = new Array(count)
        .fill(true)
        .map((_, index) => <Child key={index} duration={index} />);
      return (
        <React.Fragment>
          {children}
          <MemoizedChild duration={1} />
        </React.Fragment>
      );
    };
    const Child = ({duration}) => {
      Scheduler.unstable_advanceTime(duration);
      return null;
    };
    const MemoizedChild = React.memo(Child);

    const RootA = ({children}) => children;
    const RootB = ({children}) => children;
    const RootC = ({children}) => children;

    const containerA = document.createElement('div');
    const containerB = document.createElement('div');
    const containerC = document.createElement('div');

    utils.act(() =>
      legacyRender(
        <RootA>
          <Parent count={2} />
        </RootA>,
        containerA,
      ),
    );
    utils.act(() =>
      legacyRender(
        <RootB>
          <Parent count={1} />
        </RootB>,
        containerB,
      ),
    );
    utils.act(() => store.profilerStore.startProfiling());
    utils.act(() =>
      legacyRender(
        <RootA>
          <Parent count={3} />
        </RootA>,
        containerA,
      ),
    );
    utils.act(() =>
      legacyRender(
        <RootC>
          <Parent count={1} />
        </RootC>,
        containerC,
      ),
    );
    utils.act(() =>
      legacyRender(
        <RootA>
          <Parent count={1} />
        </RootA>,
        containerA,
      ),
    );
    utils.act(() => ReactDOM.unmountComponentAtNode(containerB));
    utils.act(() =>
      legacyRender(
        <RootA>
          <Parent count={0} />
        </RootA>,
        containerA,
      ),
    );
    utils.act(() => store.profilerStore.stopProfiling());
    utils.act(() => ReactDOM.unmountComponentAtNode(containerA));

    const rootIDs = Array.from(
      store.profilerStore.profilingData.dataForRoots.values(),
    ).map(({rootID}) => rootID);
    expect(rootIDs).toHaveLength(3);

    const originalProfilingDataForRoot = [];

    let data = store.profilerStore.getDataForRoot(rootIDs[0]);
    expect(data.displayName).toMatchInlineSnapshot(`"RootA"`);
    expect(data.commitData).toHaveLength(3);
    originalProfilingDataForRoot.push(data);

    data = store.profilerStore.getDataForRoot(rootIDs[1]);
    expect(data.displayName).toMatchInlineSnapshot(`"RootC"`);
    expect(data.commitData).toHaveLength(1);
    originalProfilingDataForRoot.push(data);

    data = store.profilerStore.getDataForRoot(rootIDs[2]);
    expect(data.displayName).toMatchInlineSnapshot(`"RootB"`);
    expect(data.commitData).toHaveLength(1);
    originalProfilingDataForRoot.push(data);

    utils.exportImportHelper(bridge, store);

    rootIDs.forEach((rootID, index) => {
      const current = store.profilerStore.getDataForRoot(rootID);
      const prev = originalProfilingDataForRoot[index];
      expect(current).toEqual(prev);
    });
  });

  it('should collect data for each commit', () => {
    const Parent = ({count}) => {
      Scheduler.unstable_advanceTime(10);
      const children = new Array(count)
        .fill(true)
        .map((_, index) => <Child key={index} duration={index} />);
      return (
        <React.Fragment>
          {children}
          <MemoizedChild duration={1} />
        </React.Fragment>
      );
    };
    const Child = ({duration}) => {
      Scheduler.unstable_advanceTime(duration);
      return null;
    };
    const MemoizedChild = React.memo(Child);

    const container = document.createElement('div');

    utils.act(() => store.profilerStore.startProfiling());
    utils.act(() => legacyRender(<Parent count={2} />, container));
    utils.act(() => legacyRender(<Parent count={3} />, container));
    utils.act(() => legacyRender(<Parent count={1} />, container));
    utils.act(() => legacyRender(<Parent count={0} />, container));
    utils.act(() => store.profilerStore.stopProfiling());

    const rootID = store.roots[0];

    const prevCommitData = store.profilerStore.getDataForRoot(rootID)
      .commitData;
    expect(prevCommitData).toHaveLength(4);

    utils.exportImportHelper(bridge, store);

    const nextCommitData = store.profilerStore.getDataForRoot(rootID)
      .commitData;
    expect(nextCommitData).toHaveLength(4);
    nextCommitData.forEach((commitData, index) => {
      expect(commitData).toEqual(prevCommitData[index]);
    });
  });

  it('should record changed props/state/context/hooks', () => {
    let instance = null;

    const ModernContext = React.createContext(0);

    class LegacyContextProvider extends React.Component<
      any,
      {|count: number|},
    > {
      static childContextTypes = {
        count: PropTypes.number,
      };
      state = {count: 0};
      getChildContext() {
        return this.state;
      }
      render() {
        instance = this;
        return (
          <ModernContext.Provider value={this.state.count}>
            <React.Fragment>
              <ModernContextConsumer />
              <LegacyContextConsumer />
            </React.Fragment>
          </ModernContext.Provider>
        );
      }
    }

    const FunctionComponentWithHooks = ({count}) => {
      React.useMemo(() => count, [count]);
      return null;
    };

    class ModernContextConsumer extends React.Component<any> {
      static contextType = ModernContext;
      render() {
        return <FunctionComponentWithHooks count={this.context} />;
      }
    }

    class LegacyContextConsumer extends React.Component<any> {
      static contextTypes = {
        count: PropTypes.number,
      };
      render() {
        return <FunctionComponentWithHooks count={this.context.count} />;
      }
    }

    const container = document.createElement('div');

    utils.act(() => store.profilerStore.startProfiling());
    utils.act(() => legacyRender(<LegacyContextProvider />, container));
    expect(instance).not.toBeNull();
    utils.act(() => (instance: any).setState({count: 1}));
    utils.act(() =>
      legacyRender(<LegacyContextProvider foo={123} />, container),
    );
    utils.act(() =>
      legacyRender(<LegacyContextProvider bar="abc" />, container),
    );
    utils.act(() => legacyRender(<LegacyContextProvider />, container));
    utils.act(() => store.profilerStore.stopProfiling());

    const rootID = store.roots[0];

    let changeDescriptions = store.profilerStore
      .getDataForRoot(rootID)
      .commitData.map(commitData => commitData.changeDescriptions);
    expect(changeDescriptions).toHaveLength(5);
    expect(changeDescriptions[0]).toMatchInlineSnapshot(`
      Map {
        2 => Object {
          "context": null,
          "didHooksChange": false,
          "isFirstMount": true,
          "props": null,
          "state": null,
        },
        4 => Object {
          "context": null,
          "didHooksChange": false,
          "isFirstMount": true,
          "props": null,
          "state": null,
        },
        5 => Object {
          "context": null,
          "didHooksChange": false,
          "isFirstMount": true,
          "props": null,
          "state": null,
        },
        6 => Object {
          "context": null,
          "didHooksChange": false,
          "isFirstMount": true,
          "props": null,
          "state": null,
        },
        7 => Object {
          "context": null,
          "didHooksChange": false,
          "isFirstMount": true,
          "props": null,
          "state": null,
        },
      }
    `);
    expect(changeDescriptions[1]).toMatchInlineSnapshot(`
      Map {
        5 => Object {
          "context": null,
          "didHooksChange": false,
          "hooks": Array [],
          "isFirstMount": false,
          "props": Array [
            "count",
          ],
          "state": null,
        },
        4 => Object {
          "context": true,
          "didHooksChange": false,
          "hooks": null,
          "isFirstMount": false,
          "props": Array [],
          "state": null,
        },
        7 => Object {
          "context": null,
          "didHooksChange": false,
          "hooks": Array [],
          "isFirstMount": false,
          "props": Array [
            "count",
          ],
          "state": null,
        },
        6 => Object {
          "context": Array [
            "count",
          ],
          "didHooksChange": false,
          "hooks": null,
          "isFirstMount": false,
          "props": Array [],
          "state": null,
        },
        2 => Object {
          "context": null,
          "didHooksChange": false,
          "hooks": Array [],
          "isFirstMount": false,
          "props": Array [],
          "state": Array [
            "count",
          ],
        },
      }
    `);
    expect(changeDescriptions[2]).toMatchInlineSnapshot(`
      Map {
        5 => Object {
          "context": null,
          "didHooksChange": false,
          "hooks": Array [],
          "isFirstMount": false,
          "props": Array [],
          "state": null,
        },
        4 => Object {
          "context": false,
          "didHooksChange": false,
          "hooks": null,
          "isFirstMount": false,
          "props": Array [],
          "state": null,
        },
        7 => Object {
          "context": null,
          "didHooksChange": false,
          "hooks": Array [],
          "isFirstMount": false,
          "props": Array [],
          "state": null,
        },
        6 => Object {
          "context": Array [],
          "didHooksChange": false,
          "hooks": null,
          "isFirstMount": false,
          "props": Array [],
          "state": null,
        },
        2 => Object {
          "context": null,
          "didHooksChange": false,
          "hooks": Array [],
          "isFirstMount": false,
          "props": Array [
            "foo",
          ],
          "state": Array [],
        },
      }
    `);
    expect(changeDescriptions[3]).toMatchInlineSnapshot(`
      Map {
        5 => Object {
          "context": null,
          "didHooksChange": false,
          "hooks": Array [],
          "isFirstMount": false,
          "props": Array [],
          "state": null,
        },
        4 => Object {
          "context": false,
          "didHooksChange": false,
          "hooks": null,
          "isFirstMount": false,
          "props": Array [],
          "state": null,
        },
        7 => Object {
          "context": null,
          "didHooksChange": false,
          "hooks": Array [],
          "isFirstMount": false,
          "props": Array [],
          "state": null,
        },
        6 => Object {
          "context": Array [],
          "didHooksChange": false,
          "hooks": null,
          "isFirstMount": false,
          "props": Array [],
          "state": null,
        },
        2 => Object {
          "context": null,
          "didHooksChange": false,
          "hooks": Array [],
          "isFirstMount": false,
          "props": Array [
            "foo",
            "bar",
          ],
          "state": Array [],
        },
      }
    `);
    expect(changeDescriptions[4]).toMatchInlineSnapshot(`
      Map {
        5 => Object {
          "context": null,
          "didHooksChange": false,
          "hooks": Array [],
          "isFirstMount": false,
          "props": Array [],
          "state": null,
        },
        4 => Object {
          "context": false,
          "didHooksChange": false,
          "hooks": null,
          "isFirstMount": false,
          "props": Array [],
          "state": null,
        },
        7 => Object {
          "context": null,
          "didHooksChange": false,
          "hooks": Array [],
          "isFirstMount": false,
          "props": Array [],
          "state": null,
        },
        6 => Object {
          "context": Array [],
          "didHooksChange": false,
          "hooks": null,
          "isFirstMount": false,
          "props": Array [],
          "state": null,
        },
        2 => Object {
          "context": null,
          "didHooksChange": false,
          "hooks": Array [],
          "isFirstMount": false,
          "props": Array [
            "bar",
          ],
          "state": Array [],
        },
      }
    `);

    utils.exportImportHelper(bridge, store);

    const prevChangeDescriptions = [...changeDescriptions];

    changeDescriptions = store.profilerStore
      .getDataForRoot(rootID)
      .commitData.map(commitData => commitData.changeDescriptions);
    expect(changeDescriptions).toHaveLength(5);

    for (let commitIndex = 0; commitIndex < 5; commitIndex++) {
      expect(changeDescriptions[commitIndex]).toEqual(
        prevChangeDescriptions[commitIndex],
      );
    }
  });

  it('should properly detect changed hooks', () => {
    const Context = React.createContext(0);

    function reducer(state, action) {
      switch (action.type) {
        case 'invert':
          return {value: !state.value};
        default:
          throw new Error();
      }
    }

    let snapshot = 0;
    function getServerSnapshot() {
      return snapshot;
    }
    function getClientSnapshot() {
      return snapshot;
    }

    let syncExternalStoreCallback;
    function subscribe(callback) {
      syncExternalStoreCallback = callback;
    }

    let dispatch = null;
    let setState = null;

    const Component = ({count, string}) => {
      // These hooks may change and initiate re-renders.
      setState = React.useState('abc')[1];
      dispatch = React.useReducer(reducer, {value: true})[1];
      React.useSyncExternalStore(
        subscribe,
        getClientSnapshot,
        getServerSnapshot,
      );

      // This hook's return value may change between renders,
      // but the hook itself isn't stateful.
      React.useContext(Context);

      // These hooks never change in a way that schedules an update.
      React.useCallback(() => () => {}, [string]);
      React.useMemo(() => string, [string]);
      React.useCallback(() => () => {}, [count]);
      React.useMemo(() => count, [count]);
      React.useCallback(() => () => {});
      React.useMemo(() => string);

      // These hooks never change in a way that schedules an update.
      React.useEffect(() => {}, [string]);
      React.useLayoutEffect(() => {}, [string]);
      React.useEffect(() => {}, [count]);
      React.useLayoutEffect(() => {}, [count]);
      React.useEffect(() => {});
      React.useLayoutEffect(() => {});

      return null;
    };

    const container = document.createElement('div');

    utils.act(() => store.profilerStore.startProfiling());
    utils.act(() =>
      legacyRender(
        <Context.Provider value={true}>
          <Component count={1} />
        </Context.Provider>,
        container,
      ),
    );

    // Second render has no changed hooks, only changed props.
    utils.act(() =>
      legacyRender(
        <Context.Provider value={true}>
          <Component count={2} />
        </Context.Provider>,
        container,
      ),
    );

    // Third render has a changed reducer hook.
    utils.act(() => dispatch({type: 'invert'}));

    // Fourth render has a changed state hook.
    utils.act(() => setState('def'));

    // Fifth render has a changed context value, but no changed hook.
    utils.act(() =>
      legacyRender(
        <Context.Provider value={false}>
          <Component count={2} />
        </Context.Provider>,
        container,
      ),
    );

    // 6th renderer is triggered by a sync external store change.
    utils.act(() => {
      snapshot++;
      syncExternalStoreCallback();
    });

    utils.act(() => store.profilerStore.stopProfiling());

    const rootID = store.roots[0];

    const changeDescriptions = store.profilerStore
      .getDataForRoot(rootID)
      .commitData.map(commitData => commitData.changeDescriptions);
    expect(changeDescriptions).toHaveLength(6);

    // 1st render: No change
    expect(changeDescriptions[0]).toMatchInlineSnapshot(`
      Map {
        3 => Object {
          "context": null,
          "didHooksChange": false,
          "isFirstMount": true,
          "props": null,
          "state": null,
        },
      }
    `);

    // 2nd render: Changed props
    expect(changeDescriptions[1]).toMatchInlineSnapshot(`
      Map {
        3 => Object {
          "context": false,
          "didHooksChange": false,
          "hooks": Array [],
          "isFirstMount": false,
          "props": Array [
            "count",
          ],
          "state": null,
        },
      }
    `);

    // 3rd render: Changed useReducer
    expect(changeDescriptions[2]).toMatchInlineSnapshot(`
      Map {
        3 => Object {
          "context": false,
          "didHooksChange": true,
          "hooks": Array [
            1,
          ],
          "isFirstMount": false,
          "props": Array [],
          "state": null,
        },
      }
    `);

    // 4th render: Changed useState
    expect(changeDescriptions[3]).toMatchInlineSnapshot(`
      Map {
        3 => Object {
          "context": false,
          "didHooksChange": true,
          "hooks": Array [
            0,
          ],
          "isFirstMount": false,
          "props": Array [],
          "state": null,
        },
      }
    `);

    // 5th render: Changed context
    expect(changeDescriptions[4]).toMatchInlineSnapshot(`
      Map {
        3 => Object {
          "context": true,
          "didHooksChange": false,
          "hooks": Array [],
          "isFirstMount": false,
          "props": Array [],
          "state": null,
        },
      }
    `);

    // 6th render: Sync external store
    expect(changeDescriptions[5]).toMatchInlineSnapshot(`
      Map {
        3 => Object {
          "context": false,
          "didHooksChange": true,
          "hooks": Array [
            2,
          ],
          "isFirstMount": false,
          "props": Array [],
          "state": null,
        },
      }
    `);

    expect(changeDescriptions).toHaveLength(6);

    // Export and re-import profile data and make sure it is retained.
    utils.exportImportHelper(bridge, store);

    for (let commitIndex = 0; commitIndex < 6; commitIndex++) {
      const commitData = store.profilerStore.getCommitData(rootID, commitIndex);
      expect(commitData.changeDescriptions).toEqual(
        changeDescriptions[commitIndex],
      );
    }
  });

  it('should calculate durations based on actual children (not filtered children)', () => {
    store.componentFilters = [utils.createDisplayNameFilter('^Parent$')];

    const Grandparent = () => {
      Scheduler.unstable_advanceTime(10);
      return (
        <React.Fragment>
          <Parent key="one" />
          <Parent key="two" />
        </React.Fragment>
      );
    };
    const Parent = () => {
      Scheduler.unstable_advanceTime(2);
      return <Child />;
    };
    const Child = () => {
      Scheduler.unstable_advanceTime(1);
      return null;
    };

    utils.act(() => store.profilerStore.startProfiling());
    utils.act(() =>
      legacyRender(<Grandparent />, document.createElement('div')),
    );
    utils.act(() => store.profilerStore.stopProfiling());

    expect(store).toMatchInlineSnapshot(`
      [root]
        ▾ <Grandparent>
            <Child>
            <Child>
    `);

    const rootID = store.roots[0];
    const commitData = store.profilerStore.getDataForRoot(rootID).commitData;
    expect(commitData).toHaveLength(1);

    // Actual duration should also include both filtered <Parent> components.
    expect(commitData[0].fiberActualDurations).toMatchInlineSnapshot(`
      Map {
        1 => 16,
        2 => 16,
        4 => 1,
        6 => 1,
      }
    `);

    expect(commitData[0].fiberSelfDurations).toMatchInlineSnapshot(`
      Map {
        1 => 0,
        2 => 10,
        4 => 1,
        6 => 1,
      }
    `);
  });

  it('should calculate durations correctly for suspended views', async () => {
    let data;
    const getData = () => {
      if (data) {
        return data;
      } else {
        throw new Promise(resolve => {
          data = 'abc';
          resolve(data);
        });
      }
    };

    const Parent = () => {
      Scheduler.unstable_advanceTime(10);
      return (
        <React.Suspense fallback={<Fallback />}>
          <Async />
        </React.Suspense>
      );
    };
    const Fallback = () => {
      Scheduler.unstable_advanceTime(2);
      return 'Fallback...';
    };
    const Async = () => {
      Scheduler.unstable_advanceTime(3);
      return getData();
    };

    utils.act(() => store.profilerStore.startProfiling());
    await utils.actAsync(() =>
      legacyRender(<Parent />, document.createElement('div')),
    );
    utils.act(() => store.profilerStore.stopProfiling());

    const rootID = store.roots[0];
    const commitData = store.profilerStore.getDataForRoot(rootID).commitData;
    expect(commitData).toHaveLength(2);
    expect(commitData[0].fiberActualDurations).toMatchInlineSnapshot(`
      Map {
        1 => 15,
        2 => 15,
        3 => 5,
        4 => 2,
      }
    `);
    expect(commitData[0].fiberSelfDurations).toMatchInlineSnapshot(`
      Map {
        1 => 0,
        2 => 10,
        3 => 3,
        4 => 2,
      }
    `);
    expect(commitData[1].fiberActualDurations).toMatchInlineSnapshot(`
      Map {
        7 => 3,
        3 => 3,
      }
    `);
    expect(commitData[1].fiberSelfDurations).toMatchInlineSnapshot(`
      Map {
        7 => 3,
        3 => 0,
      }
    `);
  });

  it('should collect data for each rendered fiber', () => {
    const Parent = ({count}) => {
      Scheduler.unstable_advanceTime(10);
      const children = new Array(count)
        .fill(true)
        .map((_, index) => <Child key={index} duration={index} />);
      return (
        <React.Fragment>
          {children}
          <MemoizedChild duration={1} />
        </React.Fragment>
      );
    };
    const Child = ({duration}) => {
      Scheduler.unstable_advanceTime(duration);
      return null;
    };
    const MemoizedChild = React.memo(Child);

    const container = document.createElement('div');

    utils.act(() => store.profilerStore.startProfiling());
    utils.act(() => legacyRender(<Parent count={1} />, container));
    utils.act(() => legacyRender(<Parent count={2} />, container));
    utils.act(() => legacyRender(<Parent count={3} />, container));
    utils.act(() => store.profilerStore.stopProfiling());

    const rootID = store.roots[0];
    const allFiberCommits = [];
    for (let index = 0; index < store.numElements; index++) {
      const fiberID = store.getElementIDAtIndex(index);
      const fiberCommits = store.profilerStore.profilingCache.getFiberCommits({
        fiberID,
        rootID,
      });

      allFiberCommits.push(fiberCommits);
    }

    expect(allFiberCommits).toMatchInlineSnapshot(`
      Array [
        Array [
          0,
          1,
          2,
        ],
        Array [
          0,
          1,
          2,
        ],
        Array [
          1,
          2,
        ],
        Array [
          2,
        ],
        Array [
          0,
        ],
      ]
    `);

    utils.exportImportHelper(bridge, store);

    for (let index = 0; index < store.numElements; index++) {
      const fiberID = store.getElementIDAtIndex(index);
      const fiberCommits = store.profilerStore.profilingCache.getFiberCommits({
        fiberID,
        rootID,
      });

      expect(fiberCommits).toEqual(allFiberCommits[index]);
    }
  });

  it('should handle unexpectedly shallow suspense trees', () => {
    const container = document.createElement('div');

    utils.act(() => store.profilerStore.startProfiling());
    utils.act(() => legacyRender(<React.Suspense />, container));
    utils.act(() => store.profilerStore.stopProfiling());

    const rootID = store.roots[0];
    const commitData = store.profilerStore.getDataForRoot(rootID).commitData;
    expect(commitData).toMatchInlineSnapshot(`
      Array [
        Object {
          "changeDescriptions": Map {},
          "duration": 0,
          "effectDuration": null,
          "fiberActualDurations": Map {
            1 => 0,
            2 => 0,
          },
          "fiberSelfDurations": Map {
            1 => 0,
            2 => 0,
          },
          "passiveEffectDuration": null,
          "priorityLevel": "Immediate",
          "timestamp": 0,
          "updaters": Array [
            Object {
              "displayName": "render()",
              "hocDisplayNames": null,
              "id": 1,
              "key": null,
              "type": 11,
            },
          ],
        },
      ]
    `);
  });

  // See https://github.com/facebook/react/issues/18831
  it('should not crash during route transitions with Suspense', () => {
    const RouterContext = React.createContext();

    function App() {
      return (
        <Router>
          <Switch>
            <Route path="/">
              <Home />
            </Route>
            <Route path="/about">
              <About />
            </Route>
          </Switch>
        </Router>
      );
    }

    const Home = () => {
      return (
        <React.Suspense>
          <Link path="/about">Home</Link>
        </React.Suspense>
      );
    };

    const About = () => <div>About</div>;

    // Mimics https://github.com/ReactTraining/react-router/blob/master/packages/react-router/modules/Router.js
    function Router({children}) {
      const [path, setPath] = React.useState('/');
      return (
        <RouterContext.Provider value={{path, setPath}}>
          {children}
        </RouterContext.Provider>
      );
    }

    // Mimics https://github.com/ReactTraining/react-router/blob/master/packages/react-router/modules/Switch.js
    function Switch({children}) {
      return (
        <RouterContext.Consumer>
          {context => {
            let element = null;
            React.Children.forEach(children, child => {
              if (context.path === child.props.path) {
                element = child.props.children;
              }
            });
            return element ? React.cloneElement(element) : null;
          }}
        </RouterContext.Consumer>
      );
    }

    // Mimics https://github.com/ReactTraining/react-router/blob/master/packages/react-router/modules/Route.js
    function Route({children, path}) {
      return null;
    }

    const linkRef = React.createRef();

    // Mimics https://github.com/ReactTraining/react-router/blob/master/packages/react-router-dom/modules/Link.js
    function Link({children, path}) {
      return (
        <RouterContext.Consumer>
          {context => {
            return (
              <button ref={linkRef} onClick={() => context.setPath(path)}>
                {children}
              </button>
            );
          }}
        </RouterContext.Consumer>
      );
    }

    const {Simulate} = require('react-dom/test-utils');

    const container = document.createElement('div');
    utils.act(() => legacyRender(<App />, container));
    expect(container.textContent).toBe('Home');
    utils.act(() => store.profilerStore.startProfiling());
    utils.act(() => Simulate.click(linkRef.current));
    utils.act(() => store.profilerStore.stopProfiling());
    expect(container.textContent).toBe('About');
  });

  it('components that were deleted and added to updaters during the layout phase should not crash', () => {
    let setChildUnmounted;
    function Child() {
      const [, setState] = React.useState(false);

      React.useLayoutEffect(() => {
        return () => setState(true);
      });

      return null;
    }

    function App() {
      const [childUnmounted, _setChildUnmounted] = React.useState(false);
      setChildUnmounted = _setChildUnmounted;
      return <>{!childUnmounted && <Child />}</>;
    }

    const root = ReactDOMClient.createRoot(document.createElement('div'));
    utils.act(() => root.render(<App />));
    utils.act(() => store.profilerStore.startProfiling());
    utils.act(() => setChildUnmounted(true));
    utils.act(() => store.profilerStore.stopProfiling());

    const updaters = store.profilerStore.getCommitData(store.roots[0], 0)
      .updaters;
    expect(updaters.length).toEqual(1);
    expect(updaters[0].displayName).toEqual('App');
  });

  it('components in a deleted subtree and added to updaters during the layout phase should not crash', () => {
    let setChildUnmounted;
    function Child() {
      return <GrandChild />;
    }

    function GrandChild() {
      const [, setState] = React.useState(false);

      React.useLayoutEffect(() => {
        return () => setState(true);
      });

      return null;
    }

    function App() {
      const [childUnmounted, _setChildUnmounted] = React.useState(false);
      setChildUnmounted = _setChildUnmounted;
      return <>{!childUnmounted && <Child />}</>;
    }

    const root = ReactDOMClient.createRoot(document.createElement('div'));
    utils.act(() => root.render(<App />));
    utils.act(() => store.profilerStore.startProfiling());
    utils.act(() => setChildUnmounted(true));
    utils.act(() => store.profilerStore.stopProfiling());

    const updaters = store.profilerStore.getCommitData(store.roots[0], 0)
      .updaters;
    expect(updaters.length).toEqual(1);
    expect(updaters[0].displayName).toEqual('App');
  });

  it('components that were deleted should not be added to updaters during the passive phase', () => {
    let setChildUnmounted;
    function Child() {
      const [, setState] = React.useState(false);
      React.useEffect(() => {
        return () => setState(true);
      });

      return null;
    }

    function App() {
      const [childUnmounted, _setChildUnmounted] = React.useState(false);
      setChildUnmounted = _setChildUnmounted;
      return <>{!childUnmounted && <Child />}</>;
    }

    const root = ReactDOMClient.createRoot(document.createElement('div'));
    utils.act(() => root.render(<App />));
    utils.act(() => store.profilerStore.startProfiling());
    utils.act(() => setChildUnmounted(true));
    utils.act(() => store.profilerStore.stopProfiling());

    const updaters = store.profilerStore.getCommitData(store.roots[0], 0)
      .updaters;
    expect(updaters.length).toEqual(1);
    expect(updaters[0].displayName).toEqual('App');
  });
});


<|start_filename|>packages/react-devtools-shared/src/__tests__/storeOwners-test.js<|end_filename|>
/**
 * Copyright (c) Facebook, Inc. and its affiliates.
 *
 * This source code is licensed under the MIT license found in the
 * LICENSE file in the root directory of this source tree.
 *
 * @flow
 */

const {printOwnersList} = require('../devtools/utils');

describe('Store owners list', () => {
  let React;
  let act;
  let legacyRender;
  let store;

  beforeEach(() => {
    store = global.store;
    store.collapseNodesByDefault = false;

    React = require('react');

    const utils = require('./utils');
    act = utils.act;
    legacyRender = utils.legacyRender;
  });

  function getFormattedOwnersList(elementID) {
    const ownersList = store.getOwnersListForElement(elementID);
    return printOwnersList(ownersList);
  }

  it('should drill through intermediate components', () => {
    const Root = () => (
      <Intermediate>
        <div>
          <Leaf />
        </div>
      </Intermediate>
    );
    const Wrapper = ({children}) => children;
    const Leaf = () => <div>Leaf</div>;
    const Intermediate = ({children}) => <Wrapper>{children}</Wrapper>;

    act(() => legacyRender(<Root />, document.createElement('div')));
    expect(store).toMatchInlineSnapshot(`
      [root]
        ▾ <Root>
          ▾ <Intermediate>
            ▾ <Wrapper>
                <Leaf>
    `);

    const rootID = store.getElementIDAtIndex(0);
    expect(getFormattedOwnersList(rootID)).toMatchInlineSnapshot(`
      "  ▾ <Root>
          ▾ <Intermediate>
              <Leaf>"
    `);

    const intermediateID = store.getElementIDAtIndex(1);
    expect(getFormattedOwnersList(intermediateID)).toMatchInlineSnapshot(`
      "  ▾ <Intermediate>
          ▾ <Wrapper>"
    `);
  });

  it('should drill through interleaved intermediate components', () => {
    const Root = () => [
      <Intermediate key="intermediate">
        <Leaf />
      </Intermediate>,
      <Leaf key="leaf" />,
    ];
    const Wrapper = ({children}) => children;
    const Leaf = () => <div>Leaf</div>;
    const Intermediate = ({children}) => [
      <Leaf key="leaf" />,
      <Wrapper key="wrapper">{children}</Wrapper>,
    ];

    act(() => legacyRender(<Root />, document.createElement('div')));
    expect(store).toMatchInlineSnapshot(`
      [root]
        ▾ <Root>
          ▾ <Intermediate key="intermediate">
              <Leaf key="leaf">
            ▾ <Wrapper key="wrapper">
                <Leaf>
            <Leaf key="leaf">
    `);

    const rootID = store.getElementIDAtIndex(0);
    expect(getFormattedOwnersList(rootID)).toMatchInlineSnapshot(`
      "  ▾ <Root>
          ▾ <Intermediate key=\\"intermediate\\">
              <Leaf>
            <Leaf key=\\"leaf\\">"
    `);

    const intermediateID = store.getElementIDAtIndex(1);
    expect(getFormattedOwnersList(intermediateID)).toMatchInlineSnapshot(`
      "  ▾ <Intermediate key=\\"intermediate\\">
            <Leaf key=\\"leaf\\">
          ▾ <Wrapper key=\\"wrapper\\">"
    `);
  });

  it('should show the proper owners list order and contents after insertions and deletions', () => {
    const Root = ({includeDirect, includeIndirect}) => (
      <div>
        {includeDirect ? <Leaf /> : null}
        {includeIndirect ? (
          <Intermediate>
            <Leaf />
          </Intermediate>
        ) : null}
      </div>
    );
    const Wrapper = ({children}) => children;
    const Leaf = () => <div>Leaf</div>;
    const Intermediate = ({children}) => <Wrapper>{children}</Wrapper>;

    const container = document.createElement('div');

    act(() =>
      legacyRender(
        <Root includeDirect={false} includeIndirect={true} />,
        container,
      ),
    );

    const rootID = store.getElementIDAtIndex(0);
    expect(store).toMatchInlineSnapshot(`
      [root]
        ▾ <Root>
          ▾ <Intermediate>
            ▾ <Wrapper>
                <Leaf>
    `);
    expect(getFormattedOwnersList(rootID)).toMatchInlineSnapshot(`
      "  ▾ <Root>
          ▾ <Intermediate>
              <Leaf>"
    `);

    act(() =>
      legacyRender(
        <Root includeDirect={true} includeIndirect={true} />,
        container,
      ),
    );
    expect(store).toMatchInlineSnapshot(`
      [root]
        ▾ <Root>
            <Leaf>
          ▾ <Intermediate>
            ▾ <Wrapper>
                <Leaf>
    `);
    expect(getFormattedOwnersList(rootID)).toMatchInlineSnapshot(`
      "  ▾ <Root>
            <Leaf>
          ▾ <Intermediate>
              <Leaf>"
    `);

    act(() =>
      legacyRender(
        <Root includeDirect={true} includeIndirect={false} />,
        container,
      ),
    );
    expect(store).toMatchInlineSnapshot(`
      [root]
        ▾ <Root>
            <Leaf>
    `);
    expect(getFormattedOwnersList(rootID)).toMatchInlineSnapshot(`
      "  ▾ <Root>
            <Leaf>"
    `);

    act(() =>
      legacyRender(
        <Root includeDirect={false} includeIndirect={false} />,
        container,
      ),
    );
    expect(store).toMatchInlineSnapshot(`
      [root]
          <Root>
    `);
    expect(getFormattedOwnersList(rootID)).toMatchInlineSnapshot(
      `"    <Root>"`,
    );
  });

  it('should show the proper owners list ordering after reordered children', () => {
    const Root = ({ascending}) =>
      ascending
        ? [<Leaf key="A" />, <Leaf key="B" />, <Leaf key="C" />]
        : [<Leaf key="C" />, <Leaf key="B" />, <Leaf key="A" />];
    const Leaf = () => <div>Leaf</div>;

    const container = document.createElement('div');
    act(() => legacyRender(<Root ascending={true} />, container));

    const rootID = store.getElementIDAtIndex(0);
    expect(store).toMatchInlineSnapshot(`
      [root]
        ▾ <Root>
            <Leaf key="A">
            <Leaf key="B">
            <Leaf key="C">
    `);
    expect(getFormattedOwnersList(rootID)).toMatchInlineSnapshot(`
      "  ▾ <Root>
            <Leaf key=\\"A\\">
            <Leaf key=\\"B\\">
            <Leaf key=\\"C\\">"
    `);

    act(() => legacyRender(<Root ascending={false} />, container));
    expect(store).toMatchInlineSnapshot(`
      [root]
        ▾ <Root>
            <Leaf key="C">
            <Leaf key="B">
            <Leaf key="A">
    `);
    expect(getFormattedOwnersList(rootID)).toMatchInlineSnapshot(`
      "  ▾ <Root>
            <Leaf key=\\"C\\">
            <Leaf key=\\"B\\">
            <Leaf key=\\"A\\">"
    `);
  });
});


<|start_filename|>packages/react-reconciler/src/ReactFiberInterleavedUpdates.new.js<|end_filename|>
/**
 * Copyright (c) Facebook, Inc. and its affiliates.
 *
 * This source code is licensed under the MIT license found in the
 * LICENSE file in the root directory of this source tree.
 *
 * @flow
 */

import type {UpdateQueue as HookQueue} from './ReactFiberHooks.new';
import type {SharedQueue as ClassQueue} from './ReactUpdateQueue.new';

// An array of all update queues that received updates during the current
// render. When this render exits, either because it finishes or because it is
// interrupted, the interleaved updates will be transferred onto the main part
// of the queue.
let interleavedQueues: Array<
  HookQueue<any, any> | ClassQueue<any>,
> | null = null;

export function pushInterleavedQueue(
  queue: HookQueue<any, any> | ClassQueue<any>,
) {
  if (interleavedQueues === null) {
    interleavedQueues = [queue];
  } else {
    interleavedQueues.push(queue);
  }
}

export function hasInterleavedUpdates() {
  return interleavedQueues !== null;
}

export function enqueueInterleavedUpdates() {
  // Transfer the interleaved updates onto the main queue. Each queue has a
  // `pending` field and an `interleaved` field. When they are not null, they
  // point to the last node in a circular linked list. We need to append the
  // interleaved list to the end of the pending list by joining them into a
  // single, circular list.
  if (interleavedQueues !== null) {
    for (let i = 0; i < interleavedQueues.length; i++) {
      const queue = interleavedQueues[i];
      const lastInterleavedUpdate = queue.interleaved;
      if (lastInterleavedUpdate !== null) {
        queue.interleaved = null;
        const firstInterleavedUpdate = lastInterleavedUpdate.next;
        const lastPendingUpdate = queue.pending;
        if (lastPendingUpdate !== null) {
          const firstPendingUpdate = lastPendingUpdate.next;
          lastPendingUpdate.next = (firstInterleavedUpdate: any);
          lastInterleavedUpdate.next = (firstPendingUpdate: any);
        }
        queue.pending = (lastInterleavedUpdate: any);
      }
    }
    interleavedQueues = null;
  }
}


<|start_filename|>packages/react-reconciler/src/ReactFiberCacheComponent.old.js<|end_filename|>
/**
 * Copyright (c) Facebook, Inc. and its affiliates.
 *
 * This source code is licensed under the MIT license found in the
 * LICENSE file in the root directory of this source tree.
 *
 * @flow
 */

import type {ReactContext} from 'shared/ReactTypes';

import {enableCache} from 'shared/ReactFeatureFlags';
import {REACT_CONTEXT_TYPE} from 'shared/ReactSymbols';

import {pushProvider, popProvider} from './ReactFiberNewContext.old';
import * as Scheduler from 'scheduler';

// In environments without AbortController (e.g. tests)
// replace it with a lightweight shim that only has the features we use.
const AbortControllerLocal = enableCache
  ? typeof AbortController !== 'undefined'
    ? AbortController
    : (function AbortControllerShim() {
        const listeners = [];
        const signal = (this.signal = {
          aborted: false,
          addEventListener: (type, listener) => {
            listeners.push(listener);
          },
        });

        this.abort = () => {
          signal.aborted = true;
          listeners.forEach(listener => listener());
        };
      }: AbortController)
  : (null: any);

export type Cache = {|
  controller: AbortControllerLocal,
  data: Map<() => mixed, mixed>,
  refCount: number,
|};

export type CacheComponentState = {|
  +parent: Cache,
  +cache: Cache,
|};

export type SpawnedCachePool = {|
  +parent: Cache,
  +pool: Cache,
|};

// Intentionally not named imports because Rollup would
// use dynamic dispatch for CommonJS interop named imports.
const {
  unstable_scheduleCallback: scheduleCallback,
  unstable_NormalPriority: NormalPriority,
} = Scheduler;

export const CacheContext: ReactContext<Cache> = enableCache
  ? {
      $$typeof: REACT_CONTEXT_TYPE,
      // We don't use Consumer/Provider for Cache components. So we'll cheat.
      Consumer: (null: any),
      Provider: (null: any),
      // We'll initialize these at the root.
      _currentValue: (null: any),
      _currentValue2: (null: any),
      _threadCount: 0,
      _defaultValue: (null: any),
      _globalName: (null: any),
    }
  : (null: any);

if (__DEV__ && enableCache) {
  CacheContext._currentRenderer = null;
  CacheContext._currentRenderer2 = null;
}

// Creates a new empty Cache instance with a ref-count of 0. The caller is responsible
// for retaining the cache once it is in use (retainCache), and releasing the cache
// once it is no longer needed (releaseCache).
export function createCache(): Cache {
  if (!enableCache) {
    return (null: any);
  }
  const cache: Cache = {
    controller: new AbortControllerLocal(),
    data: new Map(),
    refCount: 0,
  };

  return cache;
}

export function retainCache(cache: Cache) {
  if (!enableCache) {
    return;
  }
  if (__DEV__) {
    if (cache.controller.signal.aborted) {
      console.warn(
        'A cache instance was retained after it was already freed. ' +
          'This likely indicates a bug in React.',
      );
    }
  }
  cache.refCount++;
}

// Cleanup a cache instance, potentially freeing it if there are no more references
export function releaseCache(cache: Cache) {
  if (!enableCache) {
    return;
  }
  cache.refCount--;
  if (__DEV__) {
    if (cache.refCount < 0) {
      console.warn(
        'A cache instance was released after it was already freed. ' +
          'This likely indicates a bug in React.',
      );
    }
  }
  if (cache.refCount === 0) {
    scheduleCallback(NormalPriority, () => {
      cache.controller.abort();
    });
  }
}

export function pushCacheProvider(workInProgress: Fiber, cache: Cache) {
  if (!enableCache) {
    return;
  }
  pushProvider(workInProgress, CacheContext, cache);
}

export function popCacheProvider(workInProgress: Fiber, cache: Cache) {
  if (!enableCache) {
    return;
  }
  popProvider(CacheContext, workInProgress);
}


<|start_filename|>packages/react-server/src/ReactServerStreamConfigNode.js<|end_filename|>
/**
 * Copyright (c) Facebook, Inc. and its affiliates.
 *
 * This source code is licensed under the MIT license found in the
 * LICENSE file in the root directory of this source tree.
 *
 * @flow
 */

import type {Writable} from 'stream';
import {TextEncoder} from 'util';

type MightBeFlushable = {
  flush?: () => void,
  ...
};

export type Destination = Writable & MightBeFlushable;

export type PrecomputedChunk = Uint8Array;
export type Chunk = string;

export function scheduleWork(callback: () => void) {
  setImmediate(callback);
}

export function flushBuffered(destination: Destination) {
  // If we don't have any more data to send right now.
  // Flush whatever is in the buffer to the wire.
  if (typeof destination.flush === 'function') {
    // By convention the Zlib streams provide a flush function for this purpose.
    // For Express, compression middleware adds this method.
    destination.flush();
  }
}

const VIEW_SIZE = 2048;
let currentView = null;
let writtenBytes = 0;
let destinationHasCapacity = true;

export function beginWriting(destination: Destination) {
  currentView = new Uint8Array(VIEW_SIZE);
  writtenBytes = 0;
  destinationHasCapacity = true;
}

function writeStringChunk(destination: Destination, stringChunk: string) {
  if (stringChunk.length === 0) {
    return;
  }
  // maximum possible view needed to encode entire string
  if (stringChunk.length * 3 > VIEW_SIZE) {
    if (writtenBytes > 0) {
      writeToDestination(
        destination,
        ((currentView: any): Uint8Array).subarray(0, writtenBytes),
      );
      currentView = new Uint8Array(VIEW_SIZE);
      writtenBytes = 0;
    }
    writeToDestination(destination, textEncoder.encode(stringChunk));
    return;
  }

  let target: Uint8Array = (currentView: any);
  if (writtenBytes > 0) {
    target = ((currentView: any): Uint8Array).subarray(writtenBytes);
  }
  const {read, written} = textEncoder.encodeInto(stringChunk, target);
  writtenBytes += written;

  if (read < stringChunk.length) {
    writeToDestination(destination, (currentView: any));
    currentView = new Uint8Array(VIEW_SIZE);
    writtenBytes = textEncoder.encodeInto(stringChunk.slice(read), currentView)
      .written;
  }

  if (writtenBytes === VIEW_SIZE) {
    writeToDestination(destination, (currentView: any));
    currentView = new Uint8Array(VIEW_SIZE);
    writtenBytes = 0;
  }
}

function writeViewChunk(destination: Destination, chunk: PrecomputedChunk) {
  if (chunk.byteLength === 0) {
    return;
  }
  if (chunk.byteLength > VIEW_SIZE) {
    // this chunk may overflow a single view which implies it was not
    // one that is cached by the streaming renderer. We will enqueu
    // it directly and expect it is not re-used
    if (writtenBytes > 0) {
      writeToDestination(
        destination,
        ((currentView: any): Uint8Array).subarray(0, writtenBytes),
      );
      currentView = new Uint8Array(VIEW_SIZE);
      writtenBytes = 0;
    }
    writeToDestination(destination, chunk);
    return;
  }

  let bytesToWrite = chunk;
  const allowableBytes = ((currentView: any): Uint8Array).length - writtenBytes;
  if (allowableBytes < bytesToWrite.byteLength) {
    // this chunk would overflow the current view. We enqueue a full view
    // and start a new view with the remaining chunk
    if (allowableBytes === 0) {
      // the current view is already full, send it
      writeToDestination(destination, (currentView: any));
    } else {
      // fill up the current view and apply the remaining chunk bytes
      // to a new view.
      ((currentView: any): Uint8Array).set(
        bytesToWrite.subarray(0, allowableBytes),
        writtenBytes,
      );
      writtenBytes += allowableBytes;
      writeToDestination(destination, (currentView: any));
      bytesToWrite = bytesToWrite.subarray(allowableBytes);
    }
    currentView = new Uint8Array(VIEW_SIZE);
    writtenBytes = 0;
  }
  ((currentView: any): Uint8Array).set(bytesToWrite, writtenBytes);
  writtenBytes += bytesToWrite.byteLength;

  if (writtenBytes === VIEW_SIZE) {
    writeToDestination(destination, (currentView: any));
    currentView = new Uint8Array(VIEW_SIZE);
    writtenBytes = 0;
  }
}

export function writeChunk(
  destination: Destination,
  chunk: PrecomputedChunk | Chunk,
): void {
  if (typeof chunk === 'string') {
    writeStringChunk(destination, chunk);
  } else {
    writeViewChunk(destination, ((chunk: any): PrecomputedChunk));
  }
}

function writeToDestination(destination: Destination, view: Uint8Array) {
  const currentHasCapacity = destination.write(view);
  destinationHasCapacity = destinationHasCapacity && currentHasCapacity;
}

export function writeChunkAndReturn(
  destination: Destination,
  chunk: PrecomputedChunk | Chunk,
): boolean {
  writeChunk(destination, chunk);
  return destinationHasCapacity;
}

export function completeWriting(destination: Destination) {
  if (currentView && writtenBytes > 0) {
    destination.write(currentView.subarray(0, writtenBytes));
  }
  currentView = null;
  writtenBytes = 0;
  destinationHasCapacity = true;
}

export function close(destination: Destination) {
  destination.end();
}

const textEncoder = new TextEncoder();

export function stringToChunk(content: string): Chunk {
  return content;
}

export function stringToPrecomputedChunk(content: string): PrecomputedChunk {
  return textEncoder.encode(content);
}

export function closeWithError(destination: Destination, error: mixed): void {
  // $FlowFixMe: This is an Error object or the destination accepts other types.
  destination.destroy(error);
}


<|start_filename|>packages/react-reconciler/src/ReactFiberTransition.old.js<|end_filename|>
/**
 * Copyright (c) Facebook, Inc. and its affiliates.
 *
 * This source code is licensed under the MIT license found in the
 * LICENSE file in the root directory of this source tree.
 *
 * @flow
 */
import type {FiberRoot} from './ReactInternalTypes';
import type {Lanes} from './ReactFiberLane.old';
import type {StackCursor} from './ReactFiberStack.old';
import type {Cache, SpawnedCachePool} from './ReactFiberCacheComponent.old';
import type {Transition} from './ReactFiberTracingMarkerComponent.old';

import {enableCache, enableTransitionTracing} from 'shared/ReactFeatureFlags';
import {isPrimaryRenderer} from './ReactFiberHostConfig';
import {createCursor, push, pop} from './ReactFiberStack.old';
import {
  getWorkInProgressRoot,
  getWorkInProgressTransitions,
} from './ReactFiberWorkLoop.old';
import {
  createCache,
  retainCache,
  CacheContext,
} from './ReactFiberCacheComponent.old';

// When retrying a Suspense/Offscreen boundary, we restore the cache that was
// used during the previous render by placing it here, on the stack.
const resumedCache: StackCursor<Cache | null> = createCursor(null);

// During the render/synchronous commit phase, we don't actually process the
// transitions. Therefore, we want to lazily combine transitions. Instead of
// comparing the arrays of transitions when we combine them and storing them
// and filtering out the duplicates, we will instead store the unprocessed transitions
// in an array and actually filter them in the passive phase.
const transitionStack: StackCursor<Array<Transition> | null> = createCursor(
  null,
);

function peekCacheFromPool(): Cache | null {
  if (!enableCache) {
    return (null: any);
  }

  // Check if the cache pool already has a cache we can use.

  // If we're rendering inside a Suspense boundary that is currently hidden,
  // we should use the same cache that we used during the previous render, if
  // one exists.
  const cacheResumedFromPreviousRender = resumedCache.current;
  if (cacheResumedFromPreviousRender !== null) {
    return cacheResumedFromPreviousRender;
  }

  // Otherwise, check the root's cache pool.
  const root = (getWorkInProgressRoot(): any);
  const cacheFromRootCachePool = root.pooledCache;

  return cacheFromRootCachePool;
}

export function requestCacheFromPool(renderLanes: Lanes): Cache {
  // Similar to previous function, except if there's not already a cache in the
  // pool, we allocate a new one.
  const cacheFromPool = peekCacheFromPool();
  if (cacheFromPool !== null) {
    return cacheFromPool;
  }

  // Create a fresh cache and add it to the root cache pool. A cache can have
  // multiple owners:
  // - A cache pool that lives on the FiberRoot. This is where all fresh caches
  //   are originally created (TODO: except during refreshes, until we implement
  //   this correctly). The root takes ownership immediately when the cache is
  //   created. Conceptually, root.pooledCache is an Option<Arc<Cache>> (owned),
  //   and the return value of this function is a &Arc<Cache> (borrowed).
  // - One of several fiber types: host root, cache boundary, suspense
  //   component. These retain and release in the commit phase.

  const root = (getWorkInProgressRoot(): any);
  const freshCache = createCache();
  root.pooledCache = freshCache;
  retainCache(freshCache);
  if (freshCache !== null) {
    root.pooledCacheLanes |= renderLanes;
  }
  return freshCache;
}

export function pushRootTransition(
  workInProgress: Fiber,
  root: FiberRoot,
  renderLanes: Lanes,
) {
  if (enableTransitionTracing) {
    const rootTransitions = getWorkInProgressTransitions();
    push(transitionStack, rootTransitions, workInProgress);
  }
}

export function popRootTransition(
  workInProgress: Fiber,
  root: FiberRoot,
  renderLanes: Lanes,
) {
  if (enableTransitionTracing) {
    pop(transitionStack, workInProgress);
  }
}

export function pushTransition(
  offscreenWorkInProgress: Fiber,
  prevCachePool: SpawnedCachePool | null,
  newTransitions: Array<Transition> | null,
): void {
  if (enableCache) {
    if (prevCachePool === null) {
      push(resumedCache, resumedCache.current, offscreenWorkInProgress);
    } else {
      push(resumedCache, prevCachePool.pool, offscreenWorkInProgress);
    }
  }

  if (enableTransitionTracing) {
    if (transitionStack.current === null) {
      push(transitionStack, newTransitions, offscreenWorkInProgress);
    } else if (newTransitions === null) {
      push(transitionStack, transitionStack.current, offscreenWorkInProgress);
    } else {
      push(
        transitionStack,
        transitionStack.current.concat(newTransitions),
        offscreenWorkInProgress,
      );
    }
  }
}

export function popTransition(workInProgress: Fiber, current: Fiber | null) {
  if (current !== null) {
    if (enableCache) {
      pop(resumedCache, workInProgress);
    }

    if (enableTransitionTracing) {
      pop(transitionStack, workInProgress);
    }
  }
}

export function getSuspendedTransitions(): Array<Transition> | null {
  if (!enableTransitionTracing) {
    return null;
  }

  return transitionStack.current;
}

export function getSuspendedCache(): SpawnedCachePool | null {
  if (!enableCache) {
    return null;
  }
  // This function is called when a Suspense boundary suspends. It returns the
  // cache that would have been used to render fresh data during this render,
  // if there was any, so that we can resume rendering with the same cache when
  // we receive more data.
  const cacheFromPool = peekCacheFromPool();
  if (cacheFromPool === null) {
    return null;
  }

  return {
    // We must also save the parent, so that when we resume we can detect
    // a refresh.
    parent: isPrimaryRenderer
      ? CacheContext._currentValue
      : CacheContext._currentValue2,
    pool: cacheFromPool,
  };
}

export function getOffscreenDeferredCache(): SpawnedCachePool | null {
  if (!enableCache) {
    return null;
  }

  const cacheFromPool = peekCacheFromPool();
  if (cacheFromPool === null) {
    return null;
  }

  return {
    // We must also store the parent, so that when we resume we can detect
    // a refresh.
    parent: isPrimaryRenderer
      ? CacheContext._currentValue
      : CacheContext._currentValue2,
    pool: cacheFromPool,
  };
}


<|start_filename|>packages/react-devtools-shared/src/__tests__/profilingCharts-test.js<|end_filename|>
/**
 * Copyright (c) Facebook, Inc. and its affiliates.
 *
 * This source code is licensed under the MIT license found in the
 * LICENSE file in the root directory of this source tree.
 *
 * @flow
 */

import type Store from 'react-devtools-shared/src/devtools/store';

describe('profiling charts', () => {
  let React;
  let Scheduler;
  let legacyRender;
  let store: Store;
  let utils;

  beforeEach(() => {
    utils = require('./utils');
    utils.beforeEachProfiling();

    legacyRender = utils.legacyRender;

    store = global.store;
    store.collapseNodesByDefault = false;
    store.recordChangeDescriptions = true;

    React = require('react');
    Scheduler = require('scheduler');
  });

  function getFlamegraphChartData(rootID, commitIndex) {
    const commitTree = store.profilerStore.profilingCache.getCommitTree({
      commitIndex,
      rootID,
    });
    const chartData = store.profilerStore.profilingCache.getFlamegraphChartData(
      {
        commitIndex,
        commitTree,
        rootID,
      },
    );
    return {commitTree, chartData};
  }

  function getRankedChartData(rootID, commitIndex) {
    const commitTree = store.profilerStore.profilingCache.getCommitTree({
      commitIndex,
      rootID,
    });
    const chartData = store.profilerStore.profilingCache.getRankedChartData({
      commitIndex,
      commitTree,
      rootID,
    });
    return {commitTree, chartData};
  }

  describe('flamegraph chart', () => {
    it('should contain valid data', () => {
      const Parent = (_: {||}) => {
        Scheduler.unstable_advanceTime(10);
        return (
          <React.Fragment>
            <Child key="first" duration={3} />
            <Child key="second" duration={2} />
            <Child key="third" duration={0} />
          </React.Fragment>
        );
      };

      // Memoize children to verify that chart doesn't include in the update.
      const Child = React.memo(function Child({duration}) {
        Scheduler.unstable_advanceTime(duration);
        return null;
      });

      const container = document.createElement('div');

      utils.act(() => store.profilerStore.startProfiling());

      utils.act(() => legacyRender(<Parent />, container));
      expect(store).toMatchInlineSnapshot(`
        [root]
          ▾ <Parent>
              <Child key="first"> [Memo]
              <Child key="second"> [Memo]
              <Child key="third"> [Memo]
      `);

      utils.act(() => legacyRender(<Parent />, container));
      expect(store).toMatchInlineSnapshot(`
        [root]
          ▾ <Parent>
              <Child key="first"> [Memo]
              <Child key="second"> [Memo]
              <Child key="third"> [Memo]
      `);

      utils.act(() => store.profilerStore.stopProfiling());

      const rootID = store.roots[0];

      const firstCommitData = getFlamegraphChartData(rootID, 0);
      expect(firstCommitData.commitTree.nodes.size).toBe(5);
      expect(firstCommitData.chartData.rows).toMatchInlineSnapshot(`
        Array [
          Array [
            Object {
              "actualDuration": 15,
              "didRender": true,
              "id": 2,
              "label": "Parent (10ms of 15ms)",
              "name": "Parent",
              "offset": 0,
              "selfDuration": 10,
              "treeBaseDuration": 15,
            },
          ],
          Array [
            Object {
              "actualDuration": 0,
              "didRender": true,
              "id": 5,
              "label": "Child key=\\"third\\" (<0.1ms of <0.1ms)",
              "name": "Child",
              "offset": 15,
              "selfDuration": 0,
              "treeBaseDuration": 0,
            },
            Object {
              "actualDuration": 2,
              "didRender": true,
              "id": 4,
              "label": "Child key=\\"second\\" (2ms of 2ms)",
              "name": "Child",
              "offset": 13,
              "selfDuration": 2,
              "treeBaseDuration": 2,
            },
            Object {
              "actualDuration": 3,
              "didRender": true,
              "id": 3,
              "label": "Child key=\\"first\\" (3ms of 3ms)",
              "name": "Child",
              "offset": 10,
              "selfDuration": 3,
              "treeBaseDuration": 3,
            },
          ],
        ]
      `);

      const secondCommitData = getFlamegraphChartData(rootID, 1);
      expect(secondCommitData.commitTree.nodes.size).toBe(5);
      expect(secondCommitData.chartData.rows).toMatchInlineSnapshot(`
        Array [
          Array [
            Object {
              "actualDuration": 10,
              "didRender": true,
              "id": 2,
              "label": "Parent (10ms of 10ms)",
              "name": "Parent",
              "offset": 0,
              "selfDuration": 10,
              "treeBaseDuration": 15,
            },
          ],
          Array [
            Object {
              "actualDuration": 0,
              "didRender": false,
              "id": 5,
              "label": "Child key=\\"third\\"",
              "name": "Child",
              "offset": 15,
              "selfDuration": 0,
              "treeBaseDuration": 0,
            },
            Object {
              "actualDuration": 0,
              "didRender": false,
              "id": 4,
              "label": "Child key=\\"second\\"",
              "name": "Child",
              "offset": 13,
              "selfDuration": 0,
              "treeBaseDuration": 2,
            },
            Object {
              "actualDuration": 0,
              "didRender": false,
              "id": 3,
              "label": "Child key=\\"first\\"",
              "name": "Child",
              "offset": 10,
              "selfDuration": 0,
              "treeBaseDuration": 3,
            },
          ],
        ]
      `);
    });
  });

  describe('ranked chart', () => {
    it('should contain valid data', () => {
      const Parent = (_: {||}) => {
        Scheduler.unstable_advanceTime(10);
        return (
          <React.Fragment>
            <Child key="first" duration={3} />
            <Child key="second" duration={2} />
            <Child key="third" duration={0} />
          </React.Fragment>
        );
      };

      // Memoize children to verify that chart doesn't include in the update.
      const Child = React.memo(function Child({duration}) {
        Scheduler.unstable_advanceTime(duration);
        return null;
      });

      const container = document.createElement('div');

      utils.act(() => store.profilerStore.startProfiling());

      utils.act(() => legacyRender(<Parent />, container));
      expect(store).toMatchInlineSnapshot(`
        [root]
          ▾ <Parent>
              <Child key="first"> [Memo]
              <Child key="second"> [Memo]
              <Child key="third"> [Memo]
      `);

      utils.act(() => legacyRender(<Parent />, container));
      expect(store).toMatchInlineSnapshot(`
        [root]
          ▾ <Parent>
              <Child key="first"> [Memo]
              <Child key="second"> [Memo]
              <Child key="third"> [Memo]
      `);

      utils.act(() => store.profilerStore.stopProfiling());

      const rootID = store.roots[0];

      // Everything should render the first time.
      const firstCommitData = getRankedChartData(rootID, 0);
      expect(firstCommitData.commitTree.nodes.size).toBe(5);
      expect(firstCommitData.chartData.nodes).toMatchInlineSnapshot(`
        Array [
          Object {
            "id": 2,
            "label": "Parent (10ms)",
            "name": "Parent",
            "value": 10,
          },
          Object {
            "id": 3,
            "label": "Child (Memo) key=\\"first\\" (3ms)",
            "name": "Child",
            "value": 3,
          },
          Object {
            "id": 4,
            "label": "Child (Memo) key=\\"second\\" (2ms)",
            "name": "Child",
            "value": 2,
          },
          Object {
            "id": 5,
            "label": "Child (Memo) key=\\"third\\" (<0.1ms)",
            "name": "Child",
            "value": 0,
          },
        ]
      `);

      // Only parent should render the second time, since child props have not changed.
      const secondCommitData = getRankedChartData(rootID, 1);
      expect(secondCommitData.commitTree.nodes.size).toBe(5);
      expect(secondCommitData.chartData.nodes).toMatchInlineSnapshot(`
        Array [
          Object {
            "id": 2,
            "label": "Parent (10ms)",
            "name": "Parent",
            "value": 10,
          },
        ]
      `);
    });
  });
});


<|start_filename|>fixtures/fizz/src/BigComponent.js<|end_filename|>
export default function BigComponent() {
  return (
    <article>
      <section>
        <h2>Description</h2>
        <p>
          This page has repeating sections purposefully to create very large
          trees that stress the rendering and streaming capabilities of Fizz
        </p>
      </section>
      <section>
        <h2>Another Section</h2>
        <p>this section has a list</p>
        <ul>
          <li>item one</li>
          <li>item two</li>
          <li>item three</li>
          <li>item four</li>
          <li>item five</li>
        </ul>
        <p>it isn't a very interesting list</p>
      </section>
      <section>
        <h2>Smiley Section</h2>
        <p>here is a list of smiley emojis</p>
        <ol>
          <li>😀</li>
          <li>😃</li>
          <li>😄</li>
          <li>😁</li>
          <li>😆</li>
          <li>😅</li>
          <li>😂</li>
          <li>🤣</li>
          <li>🥲</li>
          <li>☺️</li>
          <li>😊</li>
          <li>😇</li>
          <li>🙂</li>
          <li>🙃</li>
          <li>😉</li>
          <li>😌</li>
          <li>😍</li>
          <li>🥰</li>
          <li>😘</li>
          <li>😗</li>
          <li>😙</li>
          <li>😚</li>
          <li>😋</li>
          <li>😛</li>
          <li>😝</li>
          <li>😜</li>
          <li>🤪</li>
          <li>🤨</li>
          <li>🧐</li>
          <li>🤓</li>
          <li>😎</li>
          <li>🥸</li>
          <li>🤩</li>
          <li>🥳</li>
          <li>😏</li>
          <li>😒</li>
          <li>😞</li>
          <li>😔</li>
          <li>😟</li>
          <li>😕</li>
          <li>🙁</li>
          <li>☹️</li>
          <li>😣</li>
          <li>😖</li>
          <li>😫</li>
          <li>😩</li>
          <li>🥺</li>
          <li>😢</li>
          <li>😭</li>
          <li>😤</li>
          <li>😠</li>
          <li>😡</li>
          <li>🤬</li>
          <li>🤯</li>
          <li>😳</li>
          <li>🥵</li>
          <li>🥶</li>
          <li>😱</li>
          <li>😨</li>
          <li>😰</li>
          <li>😥</li>
          <li>😓</li>
          <li>🤗</li>
          <li>🤔</li>
          <li>🤭</li>
          <li>🤫</li>
          <li>🤥</li>
          <li>😶</li>
          <li>😐</li>
          <li>😑</li>
          <li>😬</li>
          <li>🙄</li>
          <li>😯</li>
          <li>😦</li>
          <li>😧</li>
          <li>😮</li>
          <li>😲</li>
          <li>🥱</li>
          <li>😴</li>
          <li>🤤</li>
          <li>😪</li>
          <li>😵</li>
          <li>🤐</li>
          <li>🥴</li>
          <li>🤢</li>
          <li>🤮</li>
          <li>🤧</li>
          <li>😷</li>
          <li>🤒</li>
          <li>🤕</li>
          <li>🤑</li>
          <li>🤠</li>
          <li>😈</li>
          <li>👿</li>
          <li>👹</li>
          <li>👺</li>
          <li>🤡</li>
          <li>💩</li>
          <li>👻</li>
          <li>💀</li>
          <li>☠️</li>
          <li>👽</li>
          <li>👾</li>
          <li>🤖</li>
          <li>🎃</li>
          <li>😺</li>
          <li>😸</li>
          <li>😹</li>
          <li>😻</li>
          <li>😼</li>
          <li>😽</li>
          <li>🙀</li>
          <li>😿</li>
          <li>😾</li>
        </ol>
      </section>
      <section>
        <h2>Translation Section</h2>
        <p>This is the final section you will see before the sections repeat</p>
        <p>
          English: This is a text block translated from English to another
          language in Google Translate.
        </p>
        <p>
          Korean: 이것은 Google 번역에서 영어에서 다른 언어로 번역된 텍스트
          블록입니다.
        </p>
        <p>
          Hindi: यह Google अनुवाद में अंग्रेज़ी से दूसरी भाषा में अनुवादित
          टेक्स्ट ब्लॉक है।
        </p>
        <p>
          Lithuanian: Tai teksto blokas, išverstas iš anglų kalbos į kitą
          „Google“ vertėjo kalbą.
        </p>
        <div>
          <div>
            <div>
              <div>
                <div>
                  <div>
                    <span>
                      we're deep in some nested divs here, not that you can tell
                      visually
                    </span>
                  </div>
                </div>
              </div>
            </div>
          </div>
        </div>
      </section>
    </article>
  );
}


<|start_filename|>scripts/devtools/prepare-release.js<|end_filename|>
#!/usr/bin/env node

'use strict';

const chalk = require('chalk');
const {exec} = require('child-process-promise');
const {readFileSync, writeFileSync} = require('fs');
const {readJsonSync, writeJsonSync} = require('fs-extra');
const inquirer = require('inquirer');
const {join, relative} = require('path');
const semver = require('semver');
const {
  CHANGELOG_PATH,
  DRY_RUN,
  MANIFEST_PATHS,
  PACKAGE_PATHS,
  PULL_REQUEST_BASE_URL,
  RELEASE_SCRIPT_TOKEN,
  ROOT_PATH,
} = require('./configuration');
const {
  checkNPMPermissions,
  clear,
  confirmContinue,
  execRead,
} = require('./utils');

// This is the primary control function for this script.
async function main() {
  clear();

  await checkNPMPermissions();

  const sha = await getPreviousCommitSha();
  const [shortCommitLog, formattedCommitLog] = await getCommitLog(sha);

  console.log('');
  console.log(
    'This release includes the following commits:',
    chalk.gray(shortCommitLog)
  );
  console.log('');

  const releaseType = await getReleaseType();

  const path = join(ROOT_PATH, PACKAGE_PATHS[0]);
  const previousVersion = readJsonSync(path).version;
  const {major, minor, patch} = semver(previousVersion);
  const nextVersion =
    releaseType === 'minor'
      ? `${major}.${minor + 1}.0`
      : `${major}.${minor}.${patch + 1}`;

  updateChangelog(nextVersion, formattedCommitLog);

  await reviewChangelogPrompt();

  updatePackageVersions(previousVersion, nextVersion);
  updateManifestVersions(previousVersion, nextVersion);

  console.log('');
  console.log(
    `Packages and manifests have been updated from version ${chalk.bold(
      previousVersion
    )} to ${chalk.bold(nextVersion)}`
  );
  console.log('');

  await commitPendingChanges(previousVersion, nextVersion);

  printFinalInstructions();
}

async function commitPendingChanges(previousVersion, nextVersion) {
  console.log('');
  console.log('Committing revision and changelog.');
  console.log(chalk.dim('  git add .'));
  console.log(
    chalk.dim(
      `  git commit -m "React DevTools ${previousVersion} -> ${nextVersion}"`
    )
  );

  if (!DRY_RUN) {
    await exec(`
      git add .
      git commit -m "React DevTools ${previousVersion} -> ${nextVersion}"
    `);
  }

  console.log('');
  console.log(`Please push this commit before continuing:`);
  console.log(`  ${chalk.bold.green('git push')}`);

  await confirmContinue();
}

async function getCommitLog(sha) {
  let shortLog = '';
  let formattedLog = '';

  const rawLog = await execRead(`
    git log --topo-order --pretty=format:'%s' ${sha}...HEAD -- packages/react-devtools*
  `);
  rawLog.split('\n').forEach(line => {
    line = line.replace('[DevTools] ', '');

    const match = line.match(/(.+) \(#([0-9]+)\)/);
    if (match !== null) {
      const title = match[1];
      const pr = match[2];

      formattedLog += `\n* ${title} ([USERNAME](https://github.com/USERNAME) in [#${pr}](${PULL_REQUEST_BASE_URL}${pr}))`;
      shortLog += `\n* ${title}`;
    } else {
      formattedLog += `\n* ${line}`;
      shortLog += `\n* ${line}`;
    }
  });

  return [shortLog, formattedLog];
}

async function getPreviousCommitSha() {
  const choices = [];

  const lines = await execRead(`
    git log --max-count=5 --topo-order --pretty=format:'%H:::%s:::%as' HEAD -- ${join(
      ROOT_PATH,
      PACKAGE_PATHS[0]
    )}
  `);

  lines.split('\n').forEach((line, index) => {
    const [hash, message, date] = line.split(':::');
    choices.push({
      name: `${chalk.bold(hash)} ${chalk.dim(date)} ${message}`,
      value: hash,
      short: date,
    });
  });

  const {sha} = await inquirer.prompt([
    {
      type: 'list',
      name: 'sha',
      message: 'Which of the commits above marks the last DevTools release?',
      choices,
      default: choices[0].value,
    },
  ]);

  return sha;
}

async function getReleaseType() {
  const {releaseType} = await inquirer.prompt([
    {
      type: 'list',
      name: 'releaseType',
      message: 'Which type of release is this?',
      choices: [
        {
          name: 'Minor (new user facing functionality)',
          value: 'minor',
          short: 'Minor',
        },
        {name: 'Patch (bug fixes only)', value: 'patch', short: 'Patch'},
      ],
      default: 'patch',
    },
  ]);

  return releaseType;
}

function printFinalInstructions() {
  const buildAndTestcriptPath = join(__dirname, 'build-and-test.js');
  const pathToPrint = relative(process.cwd(), buildAndTestcriptPath);

  console.log('');
  console.log('Continue by running the build-and-test script:');
  console.log(chalk.bold.green('  ' + pathToPrint));
}

async function reviewChangelogPrompt() {
  console.log('');
  console.log(
    'The changelog has been updated with commits since the previous release:'
  );
  console.log(`  ${chalk.bold(CHANGELOG_PATH)}`);
  console.log('');
  console.log('Please review the new changelog text for the following:');
  console.log('  1. Organize the list into Features vs Bugfixes');
  console.log('  1. Filter out any non-user-visible changes (e.g. typo fixes)');
  console.log('  1. Combine related PRs into a single bullet list.');
  console.log(
    '  1. Replacing the "USERNAME" placeholder text with the GitHub username(s)'
  );
  console.log('');
  console.log(`  ${chalk.bold.green(`open ${CHANGELOG_PATH}`)}`);

  await confirmContinue();
}

function updateChangelog(nextVersion, commitLog) {
  const path = join(ROOT_PATH, CHANGELOG_PATH);
  const oldChangelog = readFileSync(path, 'utf8');

  const [beginning, end] = oldChangelog.split(RELEASE_SCRIPT_TOKEN);

  const dateString = new Date().toLocaleDateString('en-us', {
    year: 'numeric',
    month: 'long',
    day: 'numeric',
  });
  const header = `---\n\n### ${nextVersion}\n${dateString}`;

  const newChangelog = `${beginning}${RELEASE_SCRIPT_TOKEN}\n\n${header}\n${commitLog}${end}`;

  console.log(chalk.dim('  Updating changelog: ' + CHANGELOG_PATH));

  if (!DRY_RUN) {
    writeFileSync(path, newChangelog);
  }
}

function updateManifestVersions(previousVersion, nextVersion) {
  MANIFEST_PATHS.forEach(partialPath => {
    const path = join(ROOT_PATH, partialPath);
    const json = readJsonSync(path);
    json.version = nextVersion;

    if (json.hasOwnProperty('version_name')) {
      json.version_name = nextVersion;
    }

    console.log(chalk.dim('  Updating manifest JSON: ' + partialPath));

    if (!DRY_RUN) {
      writeJsonSync(path, json, {spaces: 2});
    }
  });
}

function updatePackageVersions(previousVersion, nextVersion) {
  PACKAGE_PATHS.forEach(partialPath => {
    const path = join(ROOT_PATH, partialPath);
    const json = readJsonSync(path);
    json.version = nextVersion;

    for (let key in json.dependencies) {
      if (key.startsWith('react-devtools')) {
        const version = json.dependencies[key];

        json.dependencies[key] = version.replace(previousVersion, nextVersion);
      }
    }

    console.log(chalk.dim('  Updating package JSON: ' + partialPath));

    if (!DRY_RUN) {
      writeJsonSync(path, json, {spaces: 2});
    }
  });
}

main();
