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.  * @jest-environment node
    
  9.  */
    
  10. 
    
  11. 'use strict';
    
  12. 
    
  13. let React;
    
  14. let ReactDebugTools;
    
  15. 
    
  16. describe('ReactHooksInspection', () => {
    
  17.   beforeEach(() => {
    
  18.     jest.resetModules();
    
  19.     React = require('react');
    
  20.     ReactDebugTools = require('react-debug-tools');
    
  21.   });
    
  22. 
    
  23.   it('should inspect a simple useState hook', () => {
    
  24.     function Foo(props) {
    
  25.       const [state] = React.useState('hello world');
    
  26.       return <div>{state}</div>;
    
  27.     }
    
  28.     const tree = ReactDebugTools.inspectHooks(Foo, {});
    
  29.     expect(tree).toEqual([
    
  30.       {
    
  31.         isStateEditable: true,
    
  32.         id: 0,
    
  33.         name: 'State',
    
  34.         value: 'hello world',
    
  35.         subHooks: [],
    
  36.       },
    
  37.     ]);
    
  38.   });
    
  39. 
    
  40.   it('should inspect a simple custom hook', () => {
    
  41.     function useCustom(value) {
    
  42.       const [state] = React.useState(value);
    
  43.       React.useDebugValue('custom hook label');
    
  44.       return state;
    
  45.     }
    
  46.     function Foo(props) {
    
  47.       const value = useCustom('hello world');
    
  48.       return <div>{value}</div>;
    
  49.     }
    
  50.     const tree = ReactDebugTools.inspectHooks(Foo, {});
    
  51.     expect(tree).toEqual([
    
  52.       {
    
  53.         isStateEditable: false,
    
  54.         id: null,
    
  55.         name: 'Custom',
    
  56.         value: __DEV__ ? 'custom hook label' : undefined,
    
  57.         subHooks: [
    
  58.           {
    
  59.             isStateEditable: true,
    
  60.             id: 0,
    
  61.             name: 'State',
    
  62.             value: 'hello world',
    
  63.             subHooks: [],
    
  64.           },
    
  65.         ],
    
  66.       },
    
  67.     ]);
    
  68.   });
    
  69. 
    
  70.   it('should inspect a tree of multiple hooks', () => {
    
  71.     function effect() {}
    
  72.     function useCustom(value) {
    
  73.       const [state] = React.useState(value);
    
  74.       React.useEffect(effect);
    
  75.       return state;
    
  76.     }
    
  77.     function Foo(props) {
    
  78.       const value1 = useCustom('hello');
    
  79.       const value2 = useCustom('world');
    
  80.       return (
    
  81.         <div>
    
  82.           {value1} {value2}
    
  83.         </div>
    
  84.       );
    
  85.     }
    
  86.     const tree = ReactDebugTools.inspectHooks(Foo, {});
    
  87.     expect(tree).toEqual([
    
  88.       {
    
  89.         isStateEditable: false,
    
  90.         id: null,
    
  91.         name: 'Custom',
    
  92.         value: undefined,
    
  93.         subHooks: [
    
  94.           {
    
  95.             isStateEditable: true,
    
  96.             id: 0,
    
  97.             name: 'State',
    
  98.             subHooks: [],
    
  99.             value: 'hello',
    
  100.           },
    
  101.           {
    
  102.             isStateEditable: false,
    
  103.             id: 1,
    
  104.             name: 'Effect',
    
  105.             subHooks: [],
    
  106.             value: effect,
    
  107.           },
    
  108.         ],
    
  109.       },
    
  110.       {
    
  111.         isStateEditable: false,
    
  112.         id: null,
    
  113.         name: 'Custom',
    
  114.         value: undefined,
    
  115.         subHooks: [
    
  116.           {
    
  117.             isStateEditable: true,
    
  118.             id: 2,
    
  119.             name: 'State',
    
  120.             value: 'world',
    
  121.             subHooks: [],
    
  122.           },
    
  123.           {
    
  124.             isStateEditable: false,
    
  125.             id: 3,
    
  126.             name: 'Effect',
    
  127.             value: effect,
    
  128.             subHooks: [],
    
  129.           },
    
  130.         ],
    
  131.       },
    
  132.     ]);
    
  133.   });
    
  134. 
    
  135.   it('should inspect a tree of multiple levels of hooks', () => {
    
  136.     function effect() {}
    
  137.     function useCustom(value) {
    
  138.       const [state] = React.useReducer((s, a) => s, value);
    
  139.       React.useEffect(effect);
    
  140.       return state;
    
  141.     }
    
  142.     function useBar(value) {
    
  143.       const result = useCustom(value);
    
  144.       React.useLayoutEffect(effect);
    
  145.       return result;
    
  146.     }
    
  147.     function useBaz(value) {
    
  148.       React.useLayoutEffect(effect);
    
  149.       const result = useCustom(value);
    
  150.       return result;
    
  151.     }
    
  152.     function Foo(props) {
    
  153.       const value1 = useBar('hello');
    
  154.       const value2 = useBaz('world');
    
  155.       return (
    
  156.         <div>
    
  157.           {value1} {value2}
    
  158.         </div>
    
  159.       );
    
  160.     }
    
  161.     const tree = ReactDebugTools.inspectHooks(Foo, {});
    
  162.     expect(tree).toEqual([
    
  163.       {
    
  164.         isStateEditable: false,
    
  165.         id: null,
    
  166.         name: 'Bar',
    
  167.         value: undefined,
    
  168.         subHooks: [
    
  169.           {
    
  170.             isStateEditable: false,
    
  171.             id: null,
    
  172.             name: 'Custom',
    
  173.             value: undefined,
    
  174.             subHooks: [
    
  175.               {
    
  176.                 isStateEditable: true,
    
  177.                 id: 0,
    
  178.                 name: 'Reducer',
    
  179.                 value: 'hello',
    
  180.                 subHooks: [],
    
  181.               },
    
  182.               {
    
  183.                 isStateEditable: false,
    
  184.                 id: 1,
    
  185.                 name: 'Effect',
    
  186.                 value: effect,
    
  187.                 subHooks: [],
    
  188.               },
    
  189.             ],
    
  190.           },
    
  191.           {
    
  192.             isStateEditable: false,
    
  193.             id: 2,
    
  194.             name: 'LayoutEffect',
    
  195.             value: effect,
    
  196.             subHooks: [],
    
  197.           },
    
  198.         ],
    
  199.       },
    
  200.       {
    
  201.         isStateEditable: false,
    
  202.         id: null,
    
  203.         name: 'Baz',
    
  204.         value: undefined,
    
  205.         subHooks: [
    
  206.           {
    
  207.             isStateEditable: false,
    
  208.             id: 3,
    
  209.             name: 'LayoutEffect',
    
  210.             value: effect,
    
  211.             subHooks: [],
    
  212.           },
    
  213.           {
    
  214.             isStateEditable: false,
    
  215.             id: null,
    
  216.             name: 'Custom',
    
  217.             subHooks: [
    
  218.               {
    
  219.                 isStateEditable: true,
    
  220.                 id: 4,
    
  221.                 name: 'Reducer',
    
  222.                 subHooks: [],
    
  223.                 value: 'world',
    
  224.               },
    
  225.               {
    
  226.                 isStateEditable: false,
    
  227.                 id: 5,
    
  228.                 name: 'Effect',
    
  229.                 subHooks: [],
    
  230.                 value: effect,
    
  231.               },
    
  232.             ],
    
  233.             value: undefined,
    
  234.           },
    
  235.         ],
    
  236.       },
    
  237.     ]);
    
  238.   });
    
  239. 
    
  240.   it('should inspect the default value using the useContext hook', () => {
    
  241.     const MyContext = React.createContext('default');
    
  242.     function Foo(props) {
    
  243.       const value = React.useContext(MyContext);
    
  244.       return <div>{value}</div>;
    
  245.     }
    
  246.     const tree = ReactDebugTools.inspectHooks(Foo, {});
    
  247.     expect(tree).toEqual([
    
  248.       {
    
  249.         isStateEditable: false,
    
  250.         id: null,
    
  251.         name: 'Context',
    
  252.         value: 'default',
    
  253.         subHooks: [],
    
  254.       },
    
  255.     ]);
    
  256.   });
    
  257. 
    
  258.   it('should support an injected dispatcher', () => {
    
  259.     const initial = {
    
  260.       useState() {
    
  261.         throw new Error("Should've been proxied");
    
  262.       },
    
  263.     };
    
  264.     let current = initial;
    
  265.     let getterCalls = 0;
    
  266.     const setterCalls = [];
    
  267.     const FakeDispatcherRef = {
    
  268.       get current() {
    
  269.         getterCalls++;
    
  270.         return current;
    
  271.       },
    
  272.       set current(value) {
    
  273.         setterCalls.push(value);
    
  274.         current = value;
    
  275.       },
    
  276.     };
    
  277. 
    
  278.     function Foo(props) {
    
  279.       const [state] = FakeDispatcherRef.current.useState('hello world');
    
  280.       return <div>{state}</div>;
    
  281.     }
    
  282. 
    
  283.     ReactDebugTools.inspectHooks(Foo, {}, FakeDispatcherRef);
    
  284. 
    
  285.     expect(getterCalls).toBe(2);
    
  286.     expect(setterCalls).toHaveLength(2);
    
  287.     expect(setterCalls[0]).not.toBe(initial);
    
  288.     expect(setterCalls[1]).toBe(initial);
    
  289.   });
    
  290. 
    
  291.   describe('useDebugValue', () => {
    
  292.     it('should be ignored when called outside of a custom hook', () => {
    
  293.       function Foo(props) {
    
  294.         React.useDebugValue('this is invalid');
    
  295.         return null;
    
  296.       }
    
  297.       const tree = ReactDebugTools.inspectHooks(Foo, {});
    
  298.       expect(tree).toHaveLength(0);
    
  299.     });
    
  300. 
    
  301.     it('should support an optional formatter function param', () => {
    
  302.       function useCustom() {
    
  303.         React.useDebugValue({bar: 123}, object => `bar:${object.bar}`);
    
  304.         React.useState(0);
    
  305.       }
    
  306.       function Foo(props) {
    
  307.         useCustom();
    
  308.         return null;
    
  309.       }
    
  310.       const tree = ReactDebugTools.inspectHooks(Foo, {});
    
  311.       expect(tree).toEqual([
    
  312.         {
    
  313.           isStateEditable: false,
    
  314.           id: null,
    
  315.           name: 'Custom',
    
  316.           value: __DEV__ ? 'bar:123' : undefined,
    
  317.           subHooks: [
    
  318.             {
    
  319.               isStateEditable: true,
    
  320.               id: 0,
    
  321.               name: 'State',
    
  322.               subHooks: [],
    
  323.               value: 0,
    
  324.             },
    
  325.           ],
    
  326.         },
    
  327.       ]);
    
  328.     });
    
  329.   });
    
  330. });