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('ProfilerStore', () => {
    
  13.   let React;
    
  14.   let ReactDOM;
    
  15.   let legacyRender;
    
  16.   let store: Store;
    
  17.   let utils;
    
  18. 
    
  19.   beforeEach(() => {
    
  20.     utils = require('./utils');
    
  21.     utils.beforeEachProfiling();
    
  22. 
    
  23.     legacyRender = utils.legacyRender;
    
  24. 
    
  25.     store = global.store;
    
  26.     store.collapseNodesByDefault = false;
    
  27.     store.recordChangeDescriptions = true;
    
  28. 
    
  29.     React = require('react');
    
  30.     ReactDOM = require('react-dom');
    
  31.   });
    
  32. 
    
  33.   // @reactVersion >= 16.9
    
  34.   it('should not remove profiling data when roots are unmounted', async () => {
    
  35.     const Parent = ({count}) =>
    
  36.       new Array(count)
    
  37.         .fill(true)
    
  38.         .map((_, index) => <Child key={index} duration={index} />);
    
  39.     const Child = () => <div>Hi!</div>;
    
  40. 
    
  41.     const containerA = document.createElement('div');
    
  42.     const containerB = document.createElement('div');
    
  43. 
    
  44.     utils.act(() => {
    
  45.       legacyRender(<Parent key="A" count={3} />, containerA);
    
  46.       legacyRender(<Parent key="B" count={2} />, containerB);
    
  47.     });
    
  48. 
    
  49.     utils.act(() => store.profilerStore.startProfiling());
    
  50. 
    
  51.     utils.act(() => {
    
  52.       legacyRender(<Parent key="A" count={4} />, containerA);
    
  53.       legacyRender(<Parent key="B" count={1} />, containerB);
    
  54.     });
    
  55. 
    
  56.     utils.act(() => store.profilerStore.stopProfiling());
    
  57. 
    
  58.     const rootA = store.roots[0];
    
  59.     const rootB = store.roots[1];
    
  60. 
    
  61.     utils.act(() => ReactDOM.unmountComponentAtNode(containerB));
    
  62. 
    
  63.     expect(store.profilerStore.getDataForRoot(rootA)).not.toBeNull();
    
  64. 
    
  65.     utils.act(() => ReactDOM.unmountComponentAtNode(containerA));
    
  66. 
    
  67.     expect(store.profilerStore.getDataForRoot(rootB)).not.toBeNull();
    
  68.   });
    
  69. 
    
  70.   // @reactVersion >= 16.9
    
  71.   it('should not allow new/saved profiling data to be set while profiling is in progress', () => {
    
  72.     utils.act(() => store.profilerStore.startProfiling());
    
  73.     const fauxProfilingData = {
    
  74.       dataForRoots: new Map(),
    
  75.     };
    
  76.     jest.spyOn(console, 'warn').mockImplementation(() => {});
    
  77.     store.profilerStore.profilingData = fauxProfilingData;
    
  78.     expect(store.profilerStore.profilingData).not.toBe(fauxProfilingData);
    
  79.     expect(console.warn).toHaveBeenCalledTimes(1);
    
  80.     expect(console.warn).toHaveBeenCalledWith(
    
  81.       'Profiling data cannot be updated while profiling is in progress.',
    
  82.     );
    
  83.     utils.act(() => store.profilerStore.stopProfiling());
    
  84.     store.profilerStore.profilingData = fauxProfilingData;
    
  85.     expect(store.profilerStore.profilingData).toBe(fauxProfilingData);
    
  86.   });
    
  87. 
    
  88.   // @reactVersion >= 16.9
    
  89.   // This test covers current broken behavior (arguably) with the synthetic event system.
    
  90.   it('should filter empty commits', () => {
    
  91.     const inputRef = React.createRef();
    
  92.     const ControlledInput = () => {
    
  93.       const [name, setName] = React.useState('foo');
    
  94.       const handleChange = event => setName(event.target.value);
    
  95.       return <input ref={inputRef} value={name} onChange={handleChange} />;
    
  96.     };
    
  97. 
    
  98.     const container = document.createElement('div');
    
  99. 
    
  100.     // This element has to be in the <body> for the event system to work.
    
  101.     document.body.appendChild(container);
    
  102. 
    
  103.     // It's important that this test uses legacy sync mode.
    
  104.     // The root API does not trigger this particular failing case.
    
  105.     legacyRender(<ControlledInput />, container);
    
  106. 
    
  107.     utils.act(() => store.profilerStore.startProfiling());
    
  108. 
    
  109.     // Sets a value in a way that React doesn't see,
    
  110.     // so that a subsequent "change" event will trigger the event handler.
    
  111.     const setUntrackedValue = Object.getOwnPropertyDescriptor(
    
  112.       HTMLInputElement.prototype,
    
  113.       'value',
    
  114.     ).set;
    
  115. 
    
  116.     const target = inputRef.current;
    
  117.     setUntrackedValue.call(target, 'bar');
    
  118.     target.dispatchEvent(new Event('input', {bubbles: true, cancelable: true}));
    
  119.     expect(target.value).toBe('bar');
    
  120. 
    
  121.     utils.act(() => store.profilerStore.stopProfiling());
    
  122. 
    
  123.     // Only one commit should have been recorded (in response to the "change" event).
    
  124.     const root = store.roots[0];
    
  125.     const data = store.profilerStore.getDataForRoot(root);
    
  126.     expect(data.commitData).toHaveLength(1);
    
  127.     expect(data.operations).toHaveLength(1);
    
  128.   });
    
  129. 
    
  130.   // @reactVersion >= 16.9
    
  131.   it('should filter empty commits alt', () => {
    
  132.     let commitCount = 0;
    
  133. 
    
  134.     const inputRef = React.createRef();
    
  135.     const Example = () => {
    
  136.       const [, setTouched] = React.useState(false);
    
  137. 
    
  138.       const handleBlur = () => {
    
  139.         setTouched(true);
    
  140.       };
    
  141. 
    
  142.       require('scheduler').unstable_advanceTime(1);
    
  143. 
    
  144.       React.useLayoutEffect(() => {
    
  145.         commitCount++;
    
  146.       });
    
  147. 
    
  148.       return <input ref={inputRef} onBlur={handleBlur} />;
    
  149.     };
    
  150. 
    
  151.     const container = document.createElement('div');
    
  152. 
    
  153.     // This element has to be in the <body> for the event system to work.
    
  154.     document.body.appendChild(container);
    
  155. 
    
  156.     // It's important that this test uses legacy sync mode.
    
  157.     // The root API does not trigger this particular failing case.
    
  158.     legacyRender(<Example />, container);
    
  159. 
    
  160.     expect(commitCount).toBe(1);
    
  161.     commitCount = 0;
    
  162. 
    
  163.     utils.act(() => store.profilerStore.startProfiling());
    
  164. 
    
  165.     // Focus and blur.
    
  166.     const target = inputRef.current;
    
  167.     target.focus();
    
  168.     target.blur();
    
  169.     target.focus();
    
  170.     target.blur();
    
  171.     expect(commitCount).toBe(1);
    
  172. 
    
  173.     utils.act(() => store.profilerStore.stopProfiling());
    
  174. 
    
  175.     // Only one commit should have been recorded (in response to the "change" event).
    
  176.     const root = store.roots[0];
    
  177.     const data = store.profilerStore.getDataForRoot(root);
    
  178.     expect(data.commitData).toHaveLength(1);
    
  179.     expect(data.operations).toHaveLength(1);
    
  180.   });
    
  181. 
    
  182.   // @reactVersion >= 16.9
    
  183.   it('should throw if component filters are modified while profiling', () => {
    
  184.     utils.act(() => store.profilerStore.startProfiling());
    
  185. 
    
  186.     expect(() => {
    
  187.       utils.act(() => {
    
  188.         const {
    
  189.           ElementTypeHostComponent,
    
  190.         } = require('react-devtools-shared/src/frontend/types');
    
  191.         store.componentFilters = [
    
  192.           utils.createElementTypeFilter(ElementTypeHostComponent),
    
  193.         ];
    
  194.       });
    
  195.     }).toThrow('Cannot modify filter preferences while profiling');
    
  196.   });
    
  197. 
    
  198.   // @reactVersion >= 16.9
    
  199.   it('should not throw if state contains a property hasOwnProperty ', () => {
    
  200.     let setStateCallback;
    
  201.     const ControlledInput = () => {
    
  202.       const [state, setState] = React.useState({hasOwnProperty: true});
    
  203.       setStateCallback = setState;
    
  204.       return state.hasOwnProperty;
    
  205.     };
    
  206. 
    
  207.     const container = document.createElement('div');
    
  208. 
    
  209.     // This element has to be in the <body> for the event system to work.
    
  210.     document.body.appendChild(container);
    
  211. 
    
  212.     // It's important that this test uses legacy sync mode.
    
  213.     // The root API does not trigger this particular failing case.
    
  214.     legacyRender(<ControlledInput />, container);
    
  215. 
    
  216.     utils.act(() => store.profilerStore.startProfiling());
    
  217.     utils.act(() =>
    
  218.       setStateCallback({
    
  219.         hasOwnProperty: false,
    
  220.       }),
    
  221.     );
    
  222.     utils.act(() => store.profilerStore.stopProfiling());
    
  223. 
    
  224.     // Only one commit should have been recorded (in response to the "change" event).
    
  225.     const root = store.roots[0];
    
  226.     const data = store.profilerStore.getDataForRoot(root);
    
  227.     expect(data.commitData).toHaveLength(1);
    
  228.     expect(data.operations).toHaveLength(1);
    
  229.   });
    
  230. 
    
  231.   // @reactVersion >= 18.0
    
  232.   it('should not throw while initializing context values for Fibers within a not-yet-mounted subtree', () => {
    
  233.     const promise = new Promise(resolve => {});
    
  234.     const SuspendingView = () => {
    
  235.       throw promise;
    
  236.     };
    
  237. 
    
  238.     const App = () => {
    
  239.       return (
    
  240.         <React.Suspense fallback="Fallback">
    
  241.           <SuspendingView />
    
  242.         </React.Suspense>
    
  243.       );
    
  244.     };
    
  245. 
    
  246.     const container = document.createElement('div');
    
  247. 
    
  248.     utils.act(() => legacyRender(<App />, container));
    
  249.     utils.act(() => store.profilerStore.startProfiling());
    
  250.   });
    
  251. });