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