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.  * @emails react-core
    
  8.  */
    
  9. 
    
  10. 'use strict';
    
  11. 
    
  12. describe('DebugTracing', () => {
    
  13.   let React;
    
  14.   let ReactTestRenderer;
    
  15.   let waitForPaint;
    
  16.   let waitForAll;
    
  17. 
    
  18.   let logs;
    
  19. 
    
  20.   const SYNC_LANE_STRING = '0b0000000000000000000000000000010';
    
  21.   const DEFAULT_LANE_STRING = '0b0000000000000000000000000100000';
    
  22.   const RETRY_LANE_STRING = '0b0000000010000000000000000000000';
    
  23. 
    
  24.   global.IS_REACT_ACT_ENVIRONMENT = true;
    
  25. 
    
  26.   beforeEach(() => {
    
  27.     jest.resetModules();
    
  28. 
    
  29.     React = require('react');
    
  30.     ReactTestRenderer = require('react-test-renderer');
    
  31.     const InternalTestUtils = require('internal-test-utils');
    
  32.     waitForPaint = InternalTestUtils.waitForPaint;
    
  33.     waitForAll = InternalTestUtils.waitForAll;
    
  34. 
    
  35.     logs = [];
    
  36. 
    
  37.     const groups = [];
    
  38. 
    
  39.     spyOnDevAndProd(console, 'log').mockImplementation(message => {
    
  40.       logs.push(`log: ${message.replace(/%c/g, '')}`);
    
  41.     });
    
  42.     spyOnDevAndProd(console, 'group').mockImplementation(message => {
    
  43.       logs.push(`group: ${message.replace(/%c/g, '')}`);
    
  44.       groups.push(message);
    
  45.     });
    
  46.     spyOnDevAndProd(console, 'groupEnd').mockImplementation(() => {
    
  47.       const message = groups.pop();
    
  48.       logs.push(`groupEnd: ${message.replace(/%c/g, '')}`);
    
  49.     });
    
  50.   });
    
  51. 
    
  52.   // @gate enableDebugTracing
    
  53.   it('should not log anything for sync render without suspends or state updates', () => {
    
  54.     ReactTestRenderer.create(
    
  55.       <React.unstable_DebugTracingMode>
    
  56.         <div />
    
  57.       </React.unstable_DebugTracingMode>,
    
  58.     );
    
  59. 
    
  60.     expect(logs).toEqual([]);
    
  61.   });
    
  62. 
    
  63.   // @gate experimental && build === 'development' && enableDebugTracing
    
  64.   it('should not log anything for concurrent render without suspends or state updates', () => {
    
  65.     ReactTestRenderer.act(() =>
    
  66.       ReactTestRenderer.create(
    
  67.         <React.unstable_DebugTracingMode>
    
  68.           <div />
    
  69.         </React.unstable_DebugTracingMode>,
    
  70.         {unstable_isConcurrent: true},
    
  71.       ),
    
  72.     );
    
  73.     expect(logs).toEqual([]);
    
  74.   });
    
  75. 
    
  76.   // @gate experimental && build === 'development' && enableDebugTracing
    
  77.   it('should log sync render with suspense', async () => {
    
  78.     let resolveFakeSuspensePromise;
    
  79.     let didResolve = false;
    
  80.     const fakeSuspensePromise = new Promise(resolve => {
    
  81.       resolveFakeSuspensePromise = () => {
    
  82.         didResolve = true;
    
  83.         resolve();
    
  84.       };
    
  85.     });
    
  86. 
    
  87.     function Example() {
    
  88.       if (!didResolve) {
    
  89.         throw fakeSuspensePromise;
    
  90.       }
    
  91.       return null;
    
  92.     }
    
  93. 
    
  94.     ReactTestRenderer.act(() =>
    
  95.       ReactTestRenderer.create(
    
  96.         <React.unstable_DebugTracingMode>
    
  97.           <React.Suspense fallback={null}>
    
  98.             <Example />
    
  99.           </React.Suspense>
    
  100.         </React.unstable_DebugTracingMode>,
    
  101.       ),
    
  102.     );
    
  103. 
    
  104.     expect(logs).toEqual([
    
  105.       `group: ⚛️ render (${SYNC_LANE_STRING})`,
    
  106.       'log: ⚛️ Example suspended',
    
  107.       `groupEnd: ⚛️ render (${SYNC_LANE_STRING})`,
    
  108.     ]);
    
  109. 
    
  110.     logs.splice(0);
    
  111. 
    
  112.     resolveFakeSuspensePromise();
    
  113.     await waitForAll([]);
    
  114. 
    
  115.     expect(logs).toEqual(['log: ⚛️ Example resolved']);
    
  116.   });
    
  117. 
    
  118.   // @gate experimental && build === 'development' && enableDebugTracing && enableCPUSuspense
    
  119.   it('should log sync render with CPU suspense', async () => {
    
  120.     function Example() {
    
  121.       console.log('<Example/>');
    
  122.       return null;
    
  123.     }
    
  124. 
    
  125.     function Wrapper({children}) {
    
  126.       console.log('<Wrapper/>');
    
  127.       return children;
    
  128.     }
    
  129. 
    
  130.     ReactTestRenderer.create(
    
  131.       <React.unstable_DebugTracingMode>
    
  132.         <Wrapper>
    
  133.           <React.Suspense fallback={null} unstable_expectedLoadTime={1}>
    
  134.             <Example />
    
  135.           </React.Suspense>
    
  136.         </Wrapper>
    
  137.       </React.unstable_DebugTracingMode>,
    
  138.     );
    
  139. 
    
  140.     expect(logs).toEqual([
    
  141.       `group: ⚛️ render (${SYNC_LANE_STRING})`,
    
  142.       'log: <Wrapper/>',
    
  143.       `groupEnd: ⚛️ render (${SYNC_LANE_STRING})`,
    
  144.     ]);
    
  145. 
    
  146.     logs.splice(0);
    
  147. 
    
  148.     await waitForPaint([]);
    
  149. 
    
  150.     expect(logs).toEqual([
    
  151.       `group: ⚛️ render (${RETRY_LANE_STRING})`,
    
  152.       'log: <Example/>',
    
  153.       `groupEnd: ⚛️ render (${RETRY_LANE_STRING})`,
    
  154.     ]);
    
  155.   });
    
  156. 
    
  157.   // @gate experimental && build === 'development' && enableDebugTracing
    
  158.   it('should log concurrent render with suspense', async () => {
    
  159.     let isResolved = false;
    
  160.     let resolveFakeSuspensePromise;
    
  161.     const fakeSuspensePromise = new Promise(resolve => {
    
  162.       resolveFakeSuspensePromise = () => {
    
  163.         resolve();
    
  164.         isResolved = true;
    
  165.       };
    
  166.     });
    
  167. 
    
  168.     function Example() {
    
  169.       if (!isResolved) {
    
  170.         throw fakeSuspensePromise;
    
  171.       }
    
  172.       return null;
    
  173.     }
    
  174. 
    
  175.     ReactTestRenderer.act(() =>
    
  176.       ReactTestRenderer.create(
    
  177.         <React.unstable_DebugTracingMode>
    
  178.           <React.Suspense fallback={null}>
    
  179.             <Example />
    
  180.           </React.Suspense>
    
  181.         </React.unstable_DebugTracingMode>,
    
  182.         {unstable_isConcurrent: true},
    
  183.       ),
    
  184.     );
    
  185. 
    
  186.     expect(logs).toEqual([
    
  187.       `group: ⚛️ render (${DEFAULT_LANE_STRING})`,
    
  188.       'log: ⚛️ Example suspended',
    
  189.       `groupEnd: ⚛️ render (${DEFAULT_LANE_STRING})`,
    
  190.     ]);
    
  191. 
    
  192.     logs.splice(0);
    
  193. 
    
  194.     await ReactTestRenderer.act(async () => await resolveFakeSuspensePromise());
    
  195.     expect(logs).toEqual(['log: ⚛️ Example resolved']);
    
  196.   });
    
  197. 
    
  198.   // @gate experimental && build === 'development' && enableDebugTracing && enableCPUSuspense
    
  199.   it('should log concurrent render with CPU suspense', () => {
    
  200.     function Example() {
    
  201.       console.log('<Example/>');
    
  202.       return null;
    
  203.     }
    
  204. 
    
  205.     function Wrapper({children}) {
    
  206.       console.log('<Wrapper/>');
    
  207.       return children;
    
  208.     }
    
  209. 
    
  210.     ReactTestRenderer.act(() =>
    
  211.       ReactTestRenderer.create(
    
  212.         <React.unstable_DebugTracingMode>
    
  213.           <Wrapper>
    
  214.             <React.Suspense fallback={null} unstable_expectedLoadTime={1}>
    
  215.               <Example />
    
  216.             </React.Suspense>
    
  217.           </Wrapper>
    
  218.         </React.unstable_DebugTracingMode>,
    
  219.         {unstable_isConcurrent: true},
    
  220.       ),
    
  221.     );
    
  222. 
    
  223.     expect(logs).toEqual([
    
  224.       `group: ⚛️ render (${DEFAULT_LANE_STRING})`,
    
  225.       'log: <Wrapper/>',
    
  226.       `groupEnd: ⚛️ render (${DEFAULT_LANE_STRING})`,
    
  227.       `group: ⚛️ render (${RETRY_LANE_STRING})`,
    
  228.       'log: <Example/>',
    
  229.       `groupEnd: ⚛️ render (${RETRY_LANE_STRING})`,
    
  230.     ]);
    
  231.   });
    
  232. 
    
  233.   // @gate experimental && build === 'development' && enableDebugTracing
    
  234.   it('should log cascading class component updates', () => {
    
  235.     class Example extends React.Component {
    
  236.       state = {didMount: false};
    
  237.       componentDidMount() {
    
  238.         this.setState({didMount: true});
    
  239.       }
    
  240.       render() {
    
  241.         return null;
    
  242.       }
    
  243.     }
    
  244. 
    
  245.     ReactTestRenderer.act(() =>
    
  246.       ReactTestRenderer.create(
    
  247.         <React.unstable_DebugTracingMode>
    
  248.           <Example />
    
  249.         </React.unstable_DebugTracingMode>,
    
  250.         {unstable_isConcurrent: true},
    
  251.       ),
    
  252.     );
    
  253. 
    
  254.     expect(logs).toEqual([
    
  255.       `group: ⚛️ commit (${DEFAULT_LANE_STRING})`,
    
  256.       `group: ⚛️ layout effects (${DEFAULT_LANE_STRING})`,
    
  257.       `log: ⚛️ Example updated state (${SYNC_LANE_STRING})`,
    
  258.       `groupEnd: ⚛️ layout effects (${DEFAULT_LANE_STRING})`,
    
  259.       `groupEnd: ⚛️ commit (${DEFAULT_LANE_STRING})`,
    
  260.     ]);
    
  261.   });
    
  262. 
    
  263.   // @gate experimental && build === 'development' && enableDebugTracing
    
  264.   it('should log render phase state updates for class component', () => {
    
  265.     class Example extends React.Component {
    
  266.       state = {didRender: false};
    
  267.       render() {
    
  268.         if (this.state.didRender === false) {
    
  269.           this.setState({didRender: true});
    
  270.         }
    
  271.         return null;
    
  272.       }
    
  273.     }
    
  274. 
    
  275.     expect(() => {
    
  276.       ReactTestRenderer.act(() =>
    
  277.         ReactTestRenderer.create(
    
  278.           <React.unstable_DebugTracingMode>
    
  279.             <Example />
    
  280.           </React.unstable_DebugTracingMode>,
    
  281.           {unstable_isConcurrent: true},
    
  282.         ),
    
  283.       );
    
  284.     }).toErrorDev('Cannot update during an existing state transition');
    
  285. 
    
  286.     expect(logs).toEqual([
    
  287.       `group: ⚛️ render (${DEFAULT_LANE_STRING})`,
    
  288.       `log: ⚛️ Example updated state (${DEFAULT_LANE_STRING})`,
    
  289.       `groupEnd: ⚛️ render (${DEFAULT_LANE_STRING})`,
    
  290.     ]);
    
  291.   });
    
  292. 
    
  293.   // @gate experimental && build === 'development' && enableDebugTracing
    
  294.   it('should log cascading layout updates', () => {
    
  295.     function Example() {
    
  296.       const [didMount, setDidMount] = React.useState(false);
    
  297.       React.useLayoutEffect(() => {
    
  298.         setDidMount(true);
    
  299.       }, []);
    
  300.       return didMount;
    
  301.     }
    
  302. 
    
  303.     ReactTestRenderer.act(() =>
    
  304.       ReactTestRenderer.create(
    
  305.         <React.unstable_DebugTracingMode>
    
  306.           <Example />
    
  307.         </React.unstable_DebugTracingMode>,
    
  308.         {unstable_isConcurrent: true},
    
  309.       ),
    
  310.     );
    
  311. 
    
  312.     expect(logs).toEqual([
    
  313.       `group: ⚛️ commit (${DEFAULT_LANE_STRING})`,
    
  314.       `group: ⚛️ layout effects (${DEFAULT_LANE_STRING})`,
    
  315.       `log: ⚛️ Example updated state (${SYNC_LANE_STRING})`,
    
  316.       `groupEnd: ⚛️ layout effects (${DEFAULT_LANE_STRING})`,
    
  317.       `groupEnd: ⚛️ commit (${DEFAULT_LANE_STRING})`,
    
  318.     ]);
    
  319.   });
    
  320. 
    
  321.   // @gate experimental && build === 'development' && enableDebugTracing
    
  322.   it('should log cascading passive updates', () => {
    
  323.     function Example() {
    
  324.       const [didMount, setDidMount] = React.useState(false);
    
  325.       React.useEffect(() => {
    
  326.         setDidMount(true);
    
  327.       }, []);
    
  328.       return didMount;
    
  329.     }
    
  330. 
    
  331.     ReactTestRenderer.act(() => {
    
  332.       ReactTestRenderer.create(
    
  333.         <React.unstable_DebugTracingMode>
    
  334.           <Example />
    
  335.         </React.unstable_DebugTracingMode>,
    
  336.         {unstable_isConcurrent: true},
    
  337.       );
    
  338.     });
    
  339.     expect(logs).toEqual([
    
  340.       `group: ⚛️ passive effects (${DEFAULT_LANE_STRING})`,
    
  341.       `log: ⚛️ Example updated state (${DEFAULT_LANE_STRING})`,
    
  342.       `groupEnd: ⚛️ passive effects (${DEFAULT_LANE_STRING})`,
    
  343.     ]);
    
  344.   });
    
  345. 
    
  346.   // @gate experimental && build === 'development' && enableDebugTracing
    
  347.   it('should log render phase updates', () => {
    
  348.     function Example() {
    
  349.       const [didRender, setDidRender] = React.useState(false);
    
  350.       if (!didRender) {
    
  351.         setDidRender(true);
    
  352.       }
    
  353.       return didRender;
    
  354.     }
    
  355. 
    
  356.     ReactTestRenderer.act(() => {
    
  357.       ReactTestRenderer.create(
    
  358.         <React.unstable_DebugTracingMode>
    
  359.           <Example />
    
  360.         </React.unstable_DebugTracingMode>,
    
  361.         {unstable_isConcurrent: true},
    
  362.       );
    
  363.     });
    
  364. 
    
  365.     expect(logs).toEqual([
    
  366.       `group: ⚛️ render (${DEFAULT_LANE_STRING})`,
    
  367.       `log: ⚛️ Example updated state (${DEFAULT_LANE_STRING})`,
    
  368.       `groupEnd: ⚛️ render (${DEFAULT_LANE_STRING})`,
    
  369.     ]);
    
  370.   });
    
  371. 
    
  372.   // @gate experimental && build === 'development' && enableDebugTracing
    
  373.   it('should log when user code logs', () => {
    
  374.     function Example() {
    
  375.       console.log('Hello from user code');
    
  376.       return null;
    
  377.     }
    
  378. 
    
  379.     ReactTestRenderer.act(() =>
    
  380.       ReactTestRenderer.create(
    
  381.         <React.unstable_DebugTracingMode>
    
  382.           <Example />
    
  383.         </React.unstable_DebugTracingMode>,
    
  384.         {unstable_isConcurrent: true},
    
  385.       ),
    
  386.     );
    
  387. 
    
  388.     expect(logs).toEqual([
    
  389.       `group: ⚛️ render (${DEFAULT_LANE_STRING})`,
    
  390.       'log: Hello from user code',
    
  391.       `groupEnd: ⚛️ render (${DEFAULT_LANE_STRING})`,
    
  392.     ]);
    
  393.   });
    
  394. 
    
  395.   // @gate experimental && build === 'development' && enableDebugTracing
    
  396.   it('should not log anything outside of a unstable_DebugTracingMode subtree', () => {
    
  397.     function ExampleThatCascades() {
    
  398.       const [didMount, setDidMount] = React.useState(false);
    
  399.       React.useLayoutEffect(() => {
    
  400.         setDidMount(true);
    
  401.       }, []);
    
  402.       return didMount;
    
  403.     }
    
  404. 
    
  405.     const fakeSuspensePromise = {then() {}};
    
  406. 
    
  407.     function ExampleThatSuspends() {
    
  408.       throw fakeSuspensePromise;
    
  409.     }
    
  410. 
    
  411.     function Example() {
    
  412.       return null;
    
  413.     }
    
  414. 
    
  415.     ReactTestRenderer.act(() =>
    
  416.       ReactTestRenderer.create(
    
  417.         <React.Fragment>
    
  418.           <ExampleThatCascades />
    
  419.           <React.Suspense fallback={null}>
    
  420.             <ExampleThatSuspends />
    
  421.           </React.Suspense>
    
  422.           <React.unstable_DebugTracingMode>
    
  423.             <Example />
    
  424.           </React.unstable_DebugTracingMode>
    
  425.         </React.Fragment>,
    
  426.       ),
    
  427.     );
    
  428. 
    
  429.     expect(logs).toEqual([]);
    
  430.   });
    
  431. });