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. // Note that this test uses React components declared in the "__source__" directory.
    
  11. // This is done to control if and how the code is transformed at runtime.
    
  12. // Do not declare test components within this test file as it is very fragile.
    
  13. 
    
  14. function expectHookNamesToEqual(map, expectedNamesArray) {
    
  15.   // Slightly hacky since it relies on the iterable order of values()
    
  16.   expect(Array.from(map.values())).toEqual(expectedNamesArray);
    
  17. }
    
  18. 
    
  19. function requireText(path, encoding) {
    
  20.   const {existsSync, readFileSync} = require('fs');
    
  21.   if (existsSync(path)) {
    
  22.     return Promise.resolve(readFileSync(path, encoding));
    
  23.   } else {
    
  24.     return Promise.reject(`File not found "${path}"`);
    
  25.   }
    
  26. }
    
  27. 
    
  28. function initFetchMock() {
    
  29.   const fetchMock = require('jest-fetch-mock');
    
  30.   fetchMock.enableMocks();
    
  31.   fetchMock.mockIf(/.+$/, request => {
    
  32.     const url = request.url;
    
  33.     const isLoadingExternalSourceMap = /external\/.*\.map/.test(url);
    
  34.     if (isLoadingExternalSourceMap) {
    
  35.       // Assert that url contains correct query params
    
  36.       expect(url.includes('?foo=bar&param=some_value')).toBe(true);
    
  37.       const fileSystemPath = url.split('?')[0];
    
  38.       return requireText(fileSystemPath, 'utf8');
    
  39.     }
    
  40.     return requireText(url, 'utf8');
    
  41.   });
    
  42.   return fetchMock;
    
  43. }
    
  44. 
    
  45. describe('parseHookNames', () => {
    
  46.   let fetchMock;
    
  47.   let inspectHooks;
    
  48.   let parseHookNames;
    
  49. 
    
  50.   beforeEach(() => {
    
  51.     jest.resetModules();
    
  52. 
    
  53.     jest.mock('source-map-support', () => {
    
  54.       console.trace('source-map-support');
    
  55.     });
    
  56. 
    
  57.     fetchMock = initFetchMock();
    
  58. 
    
  59.     inspectHooks =
    
  60.       require('react-debug-tools/src/ReactDebugHooks').inspectHooks;
    
  61. 
    
  62.     // Jest can't run the workerized version of this module.
    
  63.     const {
    
  64.       flattenHooksList,
    
  65.       loadSourceAndMetadata,
    
  66.     } = require('../parseHookNames/loadSourceAndMetadata');
    
  67.     const parseSourceAndMetadata =
    
  68.       require('../parseHookNames/parseSourceAndMetadata').parseSourceAndMetadata;
    
  69.     parseHookNames = async hooksTree => {
    
  70.       const hooksList = flattenHooksList(hooksTree);
    
  71. 
    
  72.       // Runs in the UI thread so it can share Network cache:
    
  73.       const locationKeyToHookSourceAndMetadata =
    
  74.         await loadSourceAndMetadata(hooksList);
    
  75. 
    
  76.       // Runs in a Worker because it's CPU intensive:
    
  77.       return parseSourceAndMetadata(
    
  78.         hooksList,
    
  79.         locationKeyToHookSourceAndMetadata,
    
  80.       );
    
  81.     };
    
  82. 
    
  83.     // Jest (jest-runner?) configures Errors to automatically account for source maps.
    
  84.     // This changes behavior between our tests and the browser.
    
  85.     // Ideally we would clear the prepareStackTrace() method on the Error object,
    
  86.     // but Node falls back to looking for it on the main context's Error constructor,
    
  87.     // which may still be patched.
    
  88.     // To ensure we get the default behavior, override prepareStackTrace ourselves.
    
  89.     // NOTE: prepareStackTrace is called from the error.stack getter, but the getter
    
  90.     // has a recursion breaker which falls back to the default behavior.
    
  91.     Error.prepareStackTrace = (error, trace) => {
    
  92.       return error.stack;
    
  93.     };
    
  94.   });
    
  95. 
    
  96.   afterEach(() => {
    
  97.     fetch.resetMocks();
    
  98.   });
    
  99. 
    
  100.   async function getHookNamesForComponent(Component, props = {}) {
    
  101.     const hooksTree = inspectHooks(Component, props, undefined, true);
    
  102.     const hookNames = await parseHookNames(hooksTree);
    
  103.     return hookNames;
    
  104.   }
    
  105. 
    
  106.   it('should parse names for useState()', async () => {
    
  107.     const Component =
    
  108.       require('./__source__/__untransformed__/ComponentWithUseState').Component;
    
  109.     const hookNames = await getHookNamesForComponent(Component);
    
  110.     expectHookNamesToEqual(hookNames, ['foo', 'bar', 'baz', null]);
    
  111.   });
    
  112. 
    
  113.   it('should parse names for useReducer()', async () => {
    
  114.     const Component =
    
  115.       require('./__source__/__untransformed__/ComponentWithUseReducer').Component;
    
  116.     const hookNames = await getHookNamesForComponent(Component);
    
  117.     expectHookNamesToEqual(hookNames, ['foo', 'bar', 'baz']);
    
  118.   });
    
  119. 
    
  120.   it('should skip loading source files for unnamed hooks like useEffect', async () => {
    
  121.     const Component =
    
  122.       require('./__source__/__untransformed__/ComponentWithUseEffect').Component;
    
  123. 
    
  124.     // Since this component contains only unnamed hooks, the source code should not even be loaded.
    
  125.     fetchMock.mockIf(/.+$/, request => {
    
  126.       throw Error(`Unexpected file request for "${request.url}"`);
    
  127.     });
    
  128. 
    
  129.     const hookNames = await getHookNamesForComponent(Component);
    
  130.     expectHookNamesToEqual(hookNames, []); // No hooks with names
    
  131.   });
    
  132. 
    
  133.   it('should skip loading source files for unnamed hooks like useEffect (alternate)', async () => {
    
  134.     const Component =
    
  135.       require('./__source__/__untransformed__/ComponentWithExternalUseEffect').Component;
    
  136. 
    
  137.     fetchMock.mockIf(/.+$/, request => {
    
  138.       // Since the custom hook contains only unnamed hooks, the source code should not be loaded.
    
  139.       if (request.url.endsWith('useCustom.js')) {
    
  140.         throw Error(`Unexpected file request for "${request.url}"`);
    
  141.       }
    
  142.       return requireText(request.url, 'utf8');
    
  143.     });
    
  144. 
    
  145.     const hookNames = await getHookNamesForComponent(Component);
    
  146.     expectHookNamesToEqual(hookNames, ['count', null]); // No hooks with names
    
  147.   });
    
  148. 
    
  149.   it('should parse names for custom hooks', async () => {
    
  150.     const Component =
    
  151.       require('./__source__/__untransformed__/ComponentWithNamedCustomHooks').Component;
    
  152.     const hookNames = await getHookNamesForComponent(Component);
    
  153.     expectHookNamesToEqual(hookNames, [
    
  154.       'foo',
    
  155.       null, // Custom hooks can have names, but not when using destructuring.
    
  156.       'baz',
    
  157.     ]);
    
  158.   });
    
  159. 
    
  160.   it('should parse names for code using hooks indirectly', async () => {
    
  161.     const Component =
    
  162.       require('./__source__/__untransformed__/ComponentUsingHooksIndirectly').Component;
    
  163.     const hookNames = await getHookNamesForComponent(Component);
    
  164.     expectHookNamesToEqual(hookNames, ['count', 'darkMode', 'isDarkMode']);
    
  165.   });
    
  166. 
    
  167.   it('should parse names for code using nested hooks', async () => {
    
  168.     const Component =
    
  169.       require('./__source__/__untransformed__/ComponentWithNestedHooks').Component;
    
  170.     let InnerComponent;
    
  171.     const hookNames = await getHookNamesForComponent(Component, {
    
  172.       callback: innerComponent => {
    
  173.         InnerComponent = innerComponent;
    
  174.       },
    
  175.     });
    
  176.     const innerHookNames = await getHookNamesForComponent(InnerComponent);
    
  177.     expectHookNamesToEqual(hookNames, ['InnerComponent']);
    
  178.     expectHookNamesToEqual(innerHookNames, ['state']);
    
  179.   });
    
  180. 
    
  181.   it('should return null for custom hooks without explicit names', async () => {
    
  182.     const Component =
    
  183.       require('./__source__/__untransformed__/ComponentWithUnnamedCustomHooks').Component;
    
  184.     const hookNames = await getHookNamesForComponent(Component);
    
  185.     expectHookNamesToEqual(hookNames, [
    
  186.       null, // Custom hooks can have names, but this one does not even return a value.
    
  187.       null, // Custom hooks can have names, but not when using destructuring.
    
  188.       null, // Custom hooks can have names, but not when using destructuring.
    
  189.     ]);
    
  190.   });
    
  191. 
    
  192.   // TODO Test that cache purge works
    
  193. 
    
  194.   // TODO Test that cached metadata is purged when Fast Refresh scheduled
    
  195. 
    
  196.   describe('inline, external and bundle source maps', () => {
    
  197.     it('should work for simple components', async () => {
    
  198.       async function test(path, name = 'Component') {
    
  199.         const Component = require(path)[name];
    
  200.         const hookNames = await getHookNamesForComponent(Component);
    
  201.         expectHookNamesToEqual(hookNames, [
    
  202.           'count', // useState
    
  203.         ]);
    
  204.       }
    
  205. 
    
  206.       await test('./__source__/Example'); // original source (uncompiled)
    
  207.       await test('./__source__/__compiled__/inline/Example'); // inline source map
    
  208.       await test('./__source__/__compiled__/external/Example'); // external source map
    
  209.       await test('./__source__/__compiled__/inline/index-map/Example'); // inline index map source map
    
  210.       await test('./__source__/__compiled__/external/index-map/Example'); // external index map source map
    
  211.       await test('./__source__/__compiled__/bundle/index', 'Example'); // bundle source map
    
  212.       await test('./__source__/__compiled__/no-columns/Example'); // simulated Webpack 'cheap-module-source-map'
    
  213.     });
    
  214. 
    
  215.     it('should work with more complex files and components', async () => {
    
  216.       async function test(path, name = undefined) {
    
  217.         const components = name != null ? require(path)[name] : require(path);
    
  218. 
    
  219.         let hookNames = await getHookNamesForComponent(components.List);
    
  220.         expectHookNamesToEqual(hookNames, [
    
  221.           'newItemText', // useState
    
  222.           'items', // useState
    
  223.           'uid', // useState
    
  224.           'handleClick', // useCallback
    
  225.           'handleKeyPress', // useCallback
    
  226.           'handleChange', // useCallback
    
  227.           'removeItem', // useCallback
    
  228.           'toggleItem', // useCallback
    
  229.         ]);
    
  230. 
    
  231.         hookNames = await getHookNamesForComponent(components.ListItem, {
    
  232.           item: {},
    
  233.         });
    
  234.         expectHookNamesToEqual(hookNames, [
    
  235.           'handleDelete', // useCallback
    
  236.           'handleToggle', // useCallback
    
  237.         ]);
    
  238.       }
    
  239. 
    
  240.       await test('./__source__/ToDoList'); // original source (uncompiled)
    
  241.       await test('./__source__/__compiled__/inline/ToDoList'); // inline source map
    
  242.       await test('./__source__/__compiled__/external/ToDoList'); // external source map
    
  243.       await test('./__source__/__compiled__/inline/index-map/ToDoList'); // inline index map source map
    
  244.       await test('./__source__/__compiled__/external/index-map/ToDoList'); // external index map source map
    
  245.       await test('./__source__/__compiled__/bundle', 'ToDoList'); // bundle source map
    
  246.       await test('./__source__/__compiled__/no-columns/ToDoList'); // simulated Webpack 'cheap-module-source-map'
    
  247.     });
    
  248. 
    
  249.     it('should work for custom hook', async () => {
    
  250.       async function test(path, name = 'Component') {
    
  251.         const Component = require(path)[name];
    
  252.         const hookNames = await getHookNamesForComponent(Component);
    
  253.         expectHookNamesToEqual(hookNames, [
    
  254.           'count', // useState()
    
  255.           'isDarkMode', // useIsDarkMode()
    
  256.           'isDarkMode', // useIsDarkMode -> useState()
    
  257.           null, // useFoo()
    
  258.         ]);
    
  259.       }
    
  260. 
    
  261.       await test('./__source__/ComponentWithCustomHook'); // original source (uncompiled)
    
  262.       await test('./__source__/__compiled__/inline/ComponentWithCustomHook'); // inline source map
    
  263.       await test('./__source__/__compiled__/external/ComponentWithCustomHook'); // external source map
    
  264.       await test(
    
  265.         './__source__/__compiled__/inline/index-map/ComponentWithCustomHook',
    
  266.       ); // inline index map source map
    
  267.       await test(
    
  268.         './__source__/__compiled__/external/index-map/ComponentWithCustomHook',
    
  269.       ); // external index map source map
    
  270.       await test('./__source__/__compiled__/bundle', 'ComponentWithCustomHook'); // bundle source map
    
  271.       await test(
    
  272.         './__source__/__compiled__/no-columns/ComponentWithCustomHook',
    
  273.       ); // simulated Webpack 'cheap-module-source-map'
    
  274.     });
    
  275. 
    
  276.     it('should work when code is using hooks indirectly', async () => {
    
  277.       async function test(path, name = 'Component') {
    
  278.         const Component = require(path)[name];
    
  279.         const hookNames = await getHookNamesForComponent(Component);
    
  280.         expectHookNamesToEqual(hookNames, [
    
  281.           'count', // useState()
    
  282.           'darkMode', // useDarkMode()
    
  283.           'isDarkMode', // useState()
    
  284.         ]);
    
  285.       }
    
  286. 
    
  287.       await test(
    
  288.         './__source__/__compiled__/inline/ComponentUsingHooksIndirectly',
    
  289.       ); // inline source map
    
  290.       await test(
    
  291.         './__source__/__compiled__/external/ComponentUsingHooksIndirectly',
    
  292.       ); // external source map
    
  293.       await test(
    
  294.         './__source__/__compiled__/inline/index-map/ComponentUsingHooksIndirectly',
    
  295.       ); // inline index map source map
    
  296.       await test(
    
  297.         './__source__/__compiled__/external/index-map/ComponentUsingHooksIndirectly',
    
  298.       ); // external index map source map
    
  299.       await test(
    
  300.         './__source__/__compiled__/bundle',
    
  301.         'ComponentUsingHooksIndirectly',
    
  302.       ); // bundle source map
    
  303.       await test(
    
  304.         './__source__/__compiled__/no-columns/ComponentUsingHooksIndirectly',
    
  305.       ); // simulated Webpack 'cheap-module-source-map'
    
  306.     });
    
  307. 
    
  308.     it('should work when code is using nested hooks', async () => {
    
  309.       async function test(path, name = 'Component') {
    
  310.         const Component = require(path)[name];
    
  311.         let InnerComponent;
    
  312.         const hookNames = await getHookNamesForComponent(Component, {
    
  313.           callback: innerComponent => {
    
  314.             InnerComponent = innerComponent;
    
  315.           },
    
  316.         });
    
  317.         const innerHookNames = await getHookNamesForComponent(InnerComponent);
    
  318.         expectHookNamesToEqual(hookNames, [
    
  319.           'InnerComponent', // useMemo()
    
  320.         ]);
    
  321.         expectHookNamesToEqual(innerHookNames, [
    
  322.           'state', // useState()
    
  323.         ]);
    
  324.       }
    
  325. 
    
  326.       await test('./__source__/__compiled__/inline/ComponentWithNestedHooks'); // inline source map
    
  327.       await test('./__source__/__compiled__/external/ComponentWithNestedHooks'); // external source map
    
  328.       await test(
    
  329.         './__source__/__compiled__/inline/index-map/ComponentWithNestedHooks',
    
  330.       ); // inline index map source map
    
  331.       await test(
    
  332.         './__source__/__compiled__/external/index-map/ComponentWithNestedHooks',
    
  333.       ); // external index map source map
    
  334.       await test(
    
  335.         './__source__/__compiled__/bundle',
    
  336.         'ComponentWithNestedHooks',
    
  337.       ); // bundle source map
    
  338.       await test(
    
  339.         './__source__/__compiled__/no-columns/ComponentWithNestedHooks',
    
  340.       ); // simulated Webpack 'cheap-module-source-map'
    
  341.     });
    
  342. 
    
  343.     it('should work for external hooks', async () => {
    
  344.       async function test(path, name = 'Component') {
    
  345.         const Component = require(path)[name];
    
  346.         const hookNames = await getHookNamesForComponent(Component);
    
  347.         expectHookNamesToEqual(hookNames, [
    
  348.           'theme', // useTheme()
    
  349.           'theme', // useContext()
    
  350.         ]);
    
  351.       }
    
  352. 
    
  353.       // We can't test the uncompiled source here, because it either needs to get transformed,
    
  354.       // which would break the source mapping, or the import statements will fail.
    
  355. 
    
  356.       await test(
    
  357.         './__source__/__compiled__/inline/ComponentWithExternalCustomHooks',
    
  358.       ); // inline source map
    
  359.       await test(
    
  360.         './__source__/__compiled__/external/ComponentWithExternalCustomHooks',
    
  361.       ); // external source map
    
  362.       await test(
    
  363.         './__source__/__compiled__/inline/index-map/ComponentWithExternalCustomHooks',
    
  364.       ); // inline index map source map
    
  365.       await test(
    
  366.         './__source__/__compiled__/external/index-map/ComponentWithExternalCustomHooks',
    
  367.       ); // external index map source map
    
  368.       await test(
    
  369.         './__source__/__compiled__/bundle',
    
  370.         'ComponentWithExternalCustomHooks',
    
  371.       ); // bundle source map
    
  372.       await test(
    
  373.         './__source__/__compiled__/no-columns/ComponentWithExternalCustomHooks',
    
  374.       ); // simulated Webpack 'cheap-module-source-map'
    
  375.     });
    
  376. 
    
  377.     it('should work when multiple hooks are on a line', async () => {
    
  378.       async function test(path, name = 'Component') {
    
  379.         const Component = require(path)[name];
    
  380.         const hookNames = await getHookNamesForComponent(Component);
    
  381.         expectHookNamesToEqual(hookNames, [
    
  382.           'a', // useContext()
    
  383.           'b', // useContext()
    
  384.           'c', // useContext()
    
  385.           'd', // useContext()
    
  386.         ]);
    
  387.       }
    
  388. 
    
  389.       await test(
    
  390.         './__source__/__compiled__/inline/ComponentWithMultipleHooksPerLine',
    
  391.       ); // inline source map
    
  392.       await test(
    
  393.         './__source__/__compiled__/external/ComponentWithMultipleHooksPerLine',
    
  394.       ); // external source map
    
  395.       await test(
    
  396.         './__source__/__compiled__/inline/index-map/ComponentWithMultipleHooksPerLine',
    
  397.       ); // inline index map source map
    
  398.       await test(
    
  399.         './__source__/__compiled__/external/index-map/ComponentWithMultipleHooksPerLine',
    
  400.       ); // external index map source map
    
  401.       await test(
    
  402.         './__source__/__compiled__/bundle',
    
  403.         'ComponentWithMultipleHooksPerLine',
    
  404.       ); // bundle source map
    
  405. 
    
  406.       async function noColumnTest(path, name = 'Component') {
    
  407.         const Component = require(path)[name];
    
  408.         const hookNames = await getHookNamesForComponent(Component);
    
  409.         expectHookNamesToEqual(hookNames, [
    
  410.           'a', // useContext()
    
  411.           'b', // useContext()
    
  412.           null, // useContext()
    
  413.           null, // useContext()
    
  414.         ]);
    
  415.       }
    
  416. 
    
  417.       // Note that this test is expected to only match the first two hooks
    
  418.       // because the 3rd and 4th hook are on the same line,
    
  419.       // and this type of source map doesn't have column numbers.
    
  420.       await noColumnTest(
    
  421.         './__source__/__compiled__/no-columns/ComponentWithMultipleHooksPerLine',
    
  422.       ); // simulated Webpack 'cheap-module-source-map'
    
  423.     });
    
  424. 
    
  425.     // TODO Inline require (e.g. require("react").useState()) isn't supported yet.
    
  426.     // Maybe this isn't an important use case to support,
    
  427.     // since inline requires are most likely to exist in compiled source (if at all).
    
  428.     xit('should work for inline requires', async () => {
    
  429.       async function test(path, name = 'Component') {
    
  430.         const Component = require(path)[name];
    
  431.         const hookNames = await getHookNamesForComponent(Component);
    
  432.         expectHookNamesToEqual(hookNames, [
    
  433.           'count', // useState()
    
  434.         ]);
    
  435.       }
    
  436. 
    
  437.       await test('./__source__/InlineRequire'); // original source (uncompiled)
    
  438.       await test('./__source__/__compiled__/inline/InlineRequire'); // inline source map
    
  439.       await test('./__source__/__compiled__/external/InlineRequire'); // external source map
    
  440.       await test('./__source__/__compiled__/inline/index-map/InlineRequire'); // inline index map source map
    
  441.       await test('./__source__/__compiled__/external/index-map/InlineRequire'); // external index map source map
    
  442.       await test('./__source__/__compiled__/bundle', 'InlineRequire'); // bundle source map
    
  443.       await test('./__source__/__compiled__/no-columns/InlineRequire'); // simulated Webpack 'cheap-module-source-map'
    
  444.     });
    
  445. 
    
  446.     it('should support sources that contain the string "sourceMappingURL="', async () => {
    
  447.       async function test(path, name = 'Component') {
    
  448.         const Component = require(path)[name];
    
  449.         const hookNames = await getHookNamesForComponent(Component);
    
  450.         expectHookNamesToEqual(hookNames, [
    
  451.           'count', // useState()
    
  452.         ]);
    
  453.       }
    
  454. 
    
  455.       // We expect the inline sourceMappingURL to be invalid in this case; mute the warning.
    
  456.       console.warn = () => {};
    
  457. 
    
  458.       await test('./__source__/ContainingStringSourceMappingURL'); // original source (uncompiled)
    
  459.       await test(
    
  460.         './__source__/__compiled__/inline/ContainingStringSourceMappingURL',
    
  461.       ); // inline source map
    
  462.       await test(
    
  463.         './__source__/__compiled__/external/ContainingStringSourceMappingURL',
    
  464.       ); // external source map
    
  465.       await test(
    
  466.         './__source__/__compiled__/inline/index-map/ContainingStringSourceMappingURL',
    
  467.       ); // inline index map source map
    
  468.       await test(
    
  469.         './__source__/__compiled__/external/index-map/ContainingStringSourceMappingURL',
    
  470.       ); // external index map source map
    
  471.       await test(
    
  472.         './__source__/__compiled__/bundle',
    
  473.         'ContainingStringSourceMappingURL',
    
  474.       ); // bundle source map
    
  475.       await test(
    
  476.         './__source__/__compiled__/no-columns/ContainingStringSourceMappingURL',
    
  477.       ); // simulated Webpack 'cheap-module-source-map'
    
  478.     });
    
  479.   });
    
  480. 
    
  481.   describe('extended source maps', () => {
    
  482.     beforeEach(() => {
    
  483.       const babelParser = require('@babel/parser');
    
  484.       const generateHookMapModule = require('../generateHookMap');
    
  485.       jest.spyOn(babelParser, 'parse');
    
  486.       jest.spyOn(generateHookMapModule, 'decodeHookMap');
    
  487.     });
    
  488. 
    
  489.     it('should work for simple components', async () => {
    
  490.       async function test(path, name = 'Component') {
    
  491.         const Component = require(path)[name];
    
  492.         const hookNames = await getHookNamesForComponent(Component);
    
  493.         expectHookNamesToEqual(hookNames, [
    
  494.           'count', // useState
    
  495.         ]);
    
  496.         expect(require('@babel/parser').parse).toHaveBeenCalledTimes(0);
    
  497.         expect(require('../generateHookMap').decodeHookMap).toHaveBeenCalled();
    
  498.       }
    
  499. 
    
  500.       await test(
    
  501.         './__source__/__compiled__/inline/fb-sources-extended/Example',
    
  502.       ); // x_facebook_sources extended inline source map
    
  503.       await test(
    
  504.         './__source__/__compiled__/external/fb-sources-extended/Example',
    
  505.       ); // x_facebook_sources extended external source map
    
  506.       await test(
    
  507.         './__source__/__compiled__/inline/react-sources-extended/Example',
    
  508.       ); // x_react_sources extended inline source map
    
  509.       await test(
    
  510.         './__source__/__compiled__/external/react-sources-extended/Example',
    
  511.       ); // x_react_sources extended external source map
    
  512. 
    
  513.       // Using index map format for source maps
    
  514.       await test(
    
  515.         './__source__/__compiled__/inline/fb-sources-extended/index-map/Example',
    
  516.       ); // x_facebook_sources extended inline index map source map
    
  517.       await test(
    
  518.         './__source__/__compiled__/external/fb-sources-extended/index-map/Example',
    
  519.       ); // x_facebook_sources extended external index map source map
    
  520.       await test(
    
  521.         './__source__/__compiled__/inline/react-sources-extended/index-map/Example',
    
  522.       ); // x_react_sources extended inline index map source map
    
  523.       await test(
    
  524.         './__source__/__compiled__/external/react-sources-extended/index-map/Example',
    
  525.       ); // x_react_sources extended external index map source map
    
  526. 
    
  527.       // TODO test no-columns and bundle cases with extended source maps
    
  528.     });
    
  529. 
    
  530.     it('should work with more complex files and components', async () => {
    
  531.       async function test(path, name = undefined) {
    
  532.         const components = name != null ? require(path)[name] : require(path);
    
  533. 
    
  534.         let hookNames = await getHookNamesForComponent(components.List);
    
  535.         expectHookNamesToEqual(hookNames, [
    
  536.           'newItemText', // useState
    
  537.           'items', // useState
    
  538.           'uid', // useState
    
  539.           'handleClick', // useCallback
    
  540.           'handleKeyPress', // useCallback
    
  541.           'handleChange', // useCallback
    
  542.           'removeItem', // useCallback
    
  543.           'toggleItem', // useCallback
    
  544.         ]);
    
  545. 
    
  546.         hookNames = await getHookNamesForComponent(components.ListItem, {
    
  547.           item: {},
    
  548.         });
    
  549.         expectHookNamesToEqual(hookNames, [
    
  550.           'handleDelete', // useCallback
    
  551.           'handleToggle', // useCallback
    
  552.         ]);
    
  553. 
    
  554.         expect(require('@babel/parser').parse).toHaveBeenCalledTimes(0);
    
  555.         expect(require('../generateHookMap').decodeHookMap).toHaveBeenCalled();
    
  556.       }
    
  557. 
    
  558.       await test(
    
  559.         './__source__/__compiled__/inline/fb-sources-extended/ToDoList',
    
  560.       ); // x_facebook_sources extended inline source map
    
  561.       await test(
    
  562.         './__source__/__compiled__/external/fb-sources-extended/ToDoList',
    
  563.       ); // x_facebook_sources extended external source map
    
  564.       await test(
    
  565.         './__source__/__compiled__/inline/react-sources-extended/ToDoList',
    
  566.       ); // x_react_sources extended inline source map
    
  567.       await test(
    
  568.         './__source__/__compiled__/external/react-sources-extended/ToDoList',
    
  569.       ); // x_react_sources extended external source map
    
  570. 
    
  571.       // Using index map format for source maps
    
  572.       await test(
    
  573.         './__source__/__compiled__/inline/fb-sources-extended/index-map/ToDoList',
    
  574.       ); // x_facebook_sources extended inline index map source map
    
  575.       await test(
    
  576.         './__source__/__compiled__/external/fb-sources-extended/index-map/ToDoList',
    
  577.       ); // x_facebook_sources extended external index map source map
    
  578.       await test(
    
  579.         './__source__/__compiled__/inline/react-sources-extended/index-map/ToDoList',
    
  580.       ); // x_react_sources extended inline index map source map
    
  581.       await test(
    
  582.         './__source__/__compiled__/external/react-sources-extended/index-map/ToDoList',
    
  583.       ); // x_react_sources extended external index map source map
    
  584. 
    
  585.       // TODO test no-columns and bundle cases with extended source maps
    
  586.     });
    
  587. 
    
  588.     it('should work for custom hook', async () => {
    
  589.       async function test(path, name = 'Component') {
    
  590.         const Component = require(path)[name];
    
  591.         const hookNames = await getHookNamesForComponent(Component);
    
  592.         expectHookNamesToEqual(hookNames, [
    
  593.           'count', // useState()
    
  594.           'isDarkMode', // useIsDarkMode()
    
  595.           'isDarkMode', // useIsDarkMode -> useState()
    
  596.           null, // isFoo()
    
  597.         ]);
    
  598.         expect(require('@babel/parser').parse).toHaveBeenCalledTimes(0);
    
  599.         expect(require('../generateHookMap').decodeHookMap).toHaveBeenCalled();
    
  600.       }
    
  601. 
    
  602.       await test(
    
  603.         './__source__/__compiled__/inline/fb-sources-extended/ComponentWithCustomHook',
    
  604.       ); // x_facebook_sources extended inline source map
    
  605.       await test(
    
  606.         './__source__/__compiled__/external/fb-sources-extended/ComponentWithCustomHook',
    
  607.       ); // x_facebook_sources extended external source map
    
  608.       await test(
    
  609.         './__source__/__compiled__/inline/react-sources-extended/ComponentWithCustomHook',
    
  610.       ); // x_react_sources extended inline source map
    
  611.       await test(
    
  612.         './__source__/__compiled__/external/react-sources-extended/ComponentWithCustomHook',
    
  613.       ); // x_react_sources extended external source map
    
  614. 
    
  615.       // Using index map format for source maps
    
  616.       await test(
    
  617.         './__source__/__compiled__/inline/fb-sources-extended/index-map/ComponentWithCustomHook',
    
  618.       ); // x_facebook_sources extended inline index map source map
    
  619.       await test(
    
  620.         './__source__/__compiled__/external/fb-sources-extended/index-map/ComponentWithCustomHook',
    
  621.       ); // x_facebook_sources extended external index map source map
    
  622.       await test(
    
  623.         './__source__/__compiled__/inline/react-sources-extended/index-map/ComponentWithCustomHook',
    
  624.       ); // x_react_sources extended inline index map source map
    
  625.       await test(
    
  626.         './__source__/__compiled__/external/react-sources-extended/index-map/ComponentWithCustomHook',
    
  627.       ); // x_react_sources extended external index map source map
    
  628. 
    
  629.       // TODO test no-columns and bundle cases with extended source maps
    
  630.     });
    
  631. 
    
  632.     it('should work when code is using hooks indirectly', async () => {
    
  633.       async function test(path, name = 'Component') {
    
  634.         const Component = require(path)[name];
    
  635.         const hookNames = await getHookNamesForComponent(Component);
    
  636.         expectHookNamesToEqual(hookNames, [
    
  637.           'count', // useState()
    
  638.           'darkMode', // useDarkMode()
    
  639.           'isDarkMode', // useState()
    
  640.         ]);
    
  641.         expect(require('@babel/parser').parse).toHaveBeenCalledTimes(0);
    
  642.         expect(require('../generateHookMap').decodeHookMap).toHaveBeenCalled();
    
  643.       }
    
  644. 
    
  645.       await test(
    
  646.         './__source__/__compiled__/inline/fb-sources-extended/ComponentUsingHooksIndirectly',
    
  647.       ); // x_facebook_sources extended inline source map
    
  648.       await test(
    
  649.         './__source__/__compiled__/external/fb-sources-extended/ComponentUsingHooksIndirectly',
    
  650.       ); // x_facebook_sources extended external source map
    
  651.       await test(
    
  652.         './__source__/__compiled__/inline/react-sources-extended/ComponentUsingHooksIndirectly',
    
  653.       ); // x_react_sources extended inline source map
    
  654.       await test(
    
  655.         './__source__/__compiled__/external/react-sources-extended/ComponentUsingHooksIndirectly',
    
  656.       ); // x_react_sources extended external source map
    
  657. 
    
  658.       // Using index map format for source maps
    
  659.       await test(
    
  660.         './__source__/__compiled__/inline/fb-sources-extended/index-map/ComponentUsingHooksIndirectly',
    
  661.       ); // x_facebook_sources extended inline index map source map
    
  662.       await test(
    
  663.         './__source__/__compiled__/external/fb-sources-extended/index-map/ComponentUsingHooksIndirectly',
    
  664.       ); // x_facebook_sources extended external index map source map
    
  665.       await test(
    
  666.         './__source__/__compiled__/inline/react-sources-extended/index-map/ComponentUsingHooksIndirectly',
    
  667.       ); // x_react_sources extended inline index map source map
    
  668.       await test(
    
  669.         './__source__/__compiled__/external/react-sources-extended/index-map/ComponentUsingHooksIndirectly',
    
  670.       ); // x_react_sources extended external index map source map
    
  671. 
    
  672.       // TODO test no-columns and bundle cases with extended source maps
    
  673.     });
    
  674. 
    
  675.     it('should work when code is using nested hooks', async () => {
    
  676.       async function test(path, name = 'Component') {
    
  677.         const Component = require(path)[name];
    
  678.         let InnerComponent;
    
  679.         const hookNames = await getHookNamesForComponent(Component, {
    
  680.           callback: innerComponent => {
    
  681.             InnerComponent = innerComponent;
    
  682.           },
    
  683.         });
    
  684.         const innerHookNames = await getHookNamesForComponent(InnerComponent);
    
  685.         expectHookNamesToEqual(hookNames, [
    
  686.           'InnerComponent', // useMemo()
    
  687.         ]);
    
  688.         expectHookNamesToEqual(innerHookNames, [
    
  689.           'state', // useState()
    
  690.         ]);
    
  691.         expect(require('@babel/parser').parse).toHaveBeenCalledTimes(0);
    
  692.         expect(require('../generateHookMap').decodeHookMap).toHaveBeenCalled();
    
  693.       }
    
  694. 
    
  695.       await test(
    
  696.         './__source__/__compiled__/inline/fb-sources-extended/ComponentWithNestedHooks',
    
  697.       ); // x_facebook_sources extended inline source map
    
  698.       await test(
    
  699.         './__source__/__compiled__/external/fb-sources-extended/ComponentWithNestedHooks',
    
  700.       ); // x_facebook_sources extended external source map
    
  701.       await test(
    
  702.         './__source__/__compiled__/inline/react-sources-extended/ComponentWithNestedHooks',
    
  703.       ); // x_react_sources extended inline source map
    
  704.       await test(
    
  705.         './__source__/__compiled__/external/react-sources-extended/ComponentWithNestedHooks',
    
  706.       ); // x_react_sources extended external source map
    
  707. 
    
  708.       // Using index map format for source maps
    
  709.       await test(
    
  710.         './__source__/__compiled__/inline/fb-sources-extended/index-map/ComponentWithNestedHooks',
    
  711.       ); // x_facebook_sources extended inline index map source map
    
  712.       await test(
    
  713.         './__source__/__compiled__/external/fb-sources-extended/index-map/ComponentWithNestedHooks',
    
  714.       ); // x_facebook_sources extended external index map source map
    
  715.       await test(
    
  716.         './__source__/__compiled__/inline/react-sources-extended/index-map/ComponentWithNestedHooks',
    
  717.       ); // x_react_sources extended inline index map source map
    
  718.       await test(
    
  719.         './__source__/__compiled__/external/react-sources-extended/index-map/ComponentWithNestedHooks',
    
  720.       ); // x_react_sources extended external index map source map
    
  721. 
    
  722.       // TODO test no-columns and bundle cases with extended source maps
    
  723.     });
    
  724. 
    
  725.     it('should work for external hooks', async () => {
    
  726.       async function test(path, name = 'Component') {
    
  727.         const Component = require(path)[name];
    
  728.         const hookNames = await getHookNamesForComponent(Component);
    
  729.         expectHookNamesToEqual(hookNames, [
    
  730.           'theme', // useTheme()
    
  731.           'theme', // useContext()
    
  732.         ]);
    
  733.         expect(require('@babel/parser').parse).toHaveBeenCalledTimes(0);
    
  734.         expect(require('../generateHookMap').decodeHookMap).toHaveBeenCalled();
    
  735.       }
    
  736. 
    
  737.       // We can't test the uncompiled source here, because it either needs to get transformed,
    
  738.       // which would break the source mapping, or the import statements will fail.
    
  739. 
    
  740.       await test(
    
  741.         './__source__/__compiled__/inline/fb-sources-extended/ComponentWithExternalCustomHooks',
    
  742.       ); // x_facebook_sources extended inline source map
    
  743.       await test(
    
  744.         './__source__/__compiled__/external/fb-sources-extended/ComponentWithExternalCustomHooks',
    
  745.       ); // x_facebook_sources extended external source map
    
  746.       await test(
    
  747.         './__source__/__compiled__/inline/react-sources-extended/ComponentWithExternalCustomHooks',
    
  748.       ); // x_react_sources extended inline source map
    
  749.       await test(
    
  750.         './__source__/__compiled__/external/react-sources-extended/ComponentWithExternalCustomHooks',
    
  751.       ); // x_react_sources extended external source map
    
  752. 
    
  753.       // Using index map format for source maps
    
  754.       await test(
    
  755.         './__source__/__compiled__/inline/fb-sources-extended/index-map/ComponentWithExternalCustomHooks',
    
  756.       ); // x_facebook_sources extended inline index map source map
    
  757.       await test(
    
  758.         './__source__/__compiled__/external/fb-sources-extended/index-map/ComponentWithExternalCustomHooks',
    
  759.       ); // x_facebook_sources extended external index map source map
    
  760.       await test(
    
  761.         './__source__/__compiled__/inline/react-sources-extended/index-map/ComponentWithExternalCustomHooks',
    
  762.       ); // x_react_sources extended inline index map source map
    
  763.       await test(
    
  764.         './__source__/__compiled__/external/react-sources-extended/index-map/ComponentWithExternalCustomHooks',
    
  765.       ); // x_react_sources extended external index map source map
    
  766. 
    
  767.       // TODO test no-columns and bundle cases with extended source maps
    
  768.     });
    
  769. 
    
  770.     it('should work when multiple hooks are on a line', async () => {
    
  771.       async function test(path, name = 'Component') {
    
  772.         const Component = require(path)[name];
    
  773.         const hookNames = await getHookNamesForComponent(Component);
    
  774.         expectHookNamesToEqual(hookNames, [
    
  775.           'a', // useContext()
    
  776.           'b', // useContext()
    
  777.           'c', // useContext()
    
  778.           'd', // useContext()
    
  779.         ]);
    
  780.         expect(require('@babel/parser').parse).toHaveBeenCalledTimes(0);
    
  781.         expect(require('../generateHookMap').decodeHookMap).toHaveBeenCalled();
    
  782.       }
    
  783. 
    
  784.       await test(
    
  785.         './__source__/__compiled__/inline/fb-sources-extended/ComponentWithMultipleHooksPerLine',
    
  786.       ); // x_facebook_sources extended inline source map
    
  787.       await test(
    
  788.         './__source__/__compiled__/external/fb-sources-extended/ComponentWithMultipleHooksPerLine',
    
  789.       ); // x_facebook_sources extended external source map
    
  790.       await test(
    
  791.         './__source__/__compiled__/inline/react-sources-extended/ComponentWithMultipleHooksPerLine',
    
  792.       ); // x_react_sources extended inline source map
    
  793.       await test(
    
  794.         './__source__/__compiled__/external/react-sources-extended/ComponentWithMultipleHooksPerLine',
    
  795.       ); // x_react_sources extended external source map
    
  796. 
    
  797.       // Using index map format for source maps
    
  798.       await test(
    
  799.         './__source__/__compiled__/inline/fb-sources-extended/index-map/ComponentWithMultipleHooksPerLine',
    
  800.       ); // x_facebook_sources extended inline index map source map
    
  801.       await test(
    
  802.         './__source__/__compiled__/external/fb-sources-extended/index-map/ComponentWithMultipleHooksPerLine',
    
  803.       ); // x_facebook_sources extended external index map source map
    
  804.       await test(
    
  805.         './__source__/__compiled__/inline/react-sources-extended/index-map/ComponentWithMultipleHooksPerLine',
    
  806.       ); // x_react_sources extended inline index map source map
    
  807.       await test(
    
  808.         './__source__/__compiled__/external/react-sources-extended/index-map/ComponentWithMultipleHooksPerLine',
    
  809.       ); // x_react_sources extended external index map source map
    
  810. 
    
  811.       // TODO test no-columns and bundle cases with extended source maps
    
  812.     });
    
  813. 
    
  814.     // TODO Inline require (e.g. require("react").useState()) isn't supported yet.
    
  815.     // Maybe this isn't an important use case to support,
    
  816.     // since inline requires are most likely to exist in compiled source (if at all).
    
  817.     xit('should work for inline requires', async () => {
    
  818.       async function test(path, name = 'Component') {
    
  819.         const Component = require(path)[name];
    
  820.         const hookNames = await getHookNamesForComponent(Component);
    
  821.         expectHookNamesToEqual(hookNames, [
    
  822.           'count', // useState()
    
  823.         ]);
    
  824.         expect(require('@babel/parser').parse).toHaveBeenCalledTimes(0);
    
  825.         expect(require('../generateHookMap').decodeHookMap).toHaveBeenCalled();
    
  826.       }
    
  827. 
    
  828.       await test(
    
  829.         './__source__/__compiled__/inline/fb-sources-extended/InlineRequire',
    
  830.       ); // x_facebook_sources extended inline source map
    
  831.       await test(
    
  832.         './__source__/__compiled__/external/fb-sources-extended/InlineRequire',
    
  833.       ); // x_facebook_sources extended external source map
    
  834.       await test(
    
  835.         './__source__/__compiled__/inline/react-sources-extended/InlineRequire',
    
  836.       ); // x_react_sources extended inline source map
    
  837.       await test(
    
  838.         './__source__/__compiled__/external/react-sources-extended/InlineRequire',
    
  839.       ); // x_react_sources extended external source map
    
  840. 
    
  841.       // Using index map format for source maps
    
  842.       await test(
    
  843.         './__source__/__compiled__/inline/fb-sources-extended/index-map/InlineRequire',
    
  844.       ); // x_facebook_sources extended inline index map source map
    
  845.       await test(
    
  846.         './__source__/__compiled__/external/fb-sources-extended/index-map/InlineRequire',
    
  847.       ); // x_facebook_sources extended external index map source map
    
  848.       await test(
    
  849.         './__source__/__compiled__/inline/react-sources-extended/index-map/InlineRequire',
    
  850.       ); // x_react_sources extended inline index map source map
    
  851.       await test(
    
  852.         './__source__/__compiled__/external/react-sources-extended/index-map/InlineRequire',
    
  853.       ); // x_react_sources extended external index map source map
    
  854. 
    
  855.       // TODO test no-columns and bundle cases with extended source maps
    
  856.     });
    
  857. 
    
  858.     it('should support sources that contain the string "sourceMappingURL="', async () => {
    
  859.       async function test(path, name = 'Component') {
    
  860.         const Component = require(path)[name];
    
  861.         const hookNames = await getHookNamesForComponent(Component);
    
  862.         expectHookNamesToEqual(hookNames, [
    
  863.           'count', // useState()
    
  864.         ]);
    
  865.         expect(require('@babel/parser').parse).toHaveBeenCalledTimes(0);
    
  866.         expect(require('../generateHookMap').decodeHookMap).toHaveBeenCalled();
    
  867.       }
    
  868. 
    
  869.       // We expect the inline sourceMappingURL to be invalid in this case; mute the warning.
    
  870.       console.warn = () => {};
    
  871. 
    
  872.       await test(
    
  873.         './__source__/__compiled__/inline/fb-sources-extended/ContainingStringSourceMappingURL',
    
  874.       ); // x_facebook_sources extended inline source map
    
  875.       await test(
    
  876.         './__source__/__compiled__/external/fb-sources-extended/ContainingStringSourceMappingURL',
    
  877.       ); // x_facebook_sources extended external source map
    
  878.       await test(
    
  879.         './__source__/__compiled__/inline/react-sources-extended/ContainingStringSourceMappingURL',
    
  880.       ); // x_react_sources extended inline source map
    
  881.       await test(
    
  882.         './__source__/__compiled__/external/react-sources-extended/ContainingStringSourceMappingURL',
    
  883.       ); // x_react_sources extended external source map
    
  884. 
    
  885.       // Using index map format for source maps
    
  886.       await test(
    
  887.         './__source__/__compiled__/inline/fb-sources-extended/index-map/ContainingStringSourceMappingURL',
    
  888.       ); // x_facebook_sources extended inline index map source map
    
  889.       await test(
    
  890.         './__source__/__compiled__/external/fb-sources-extended/index-map/ContainingStringSourceMappingURL',
    
  891.       ); // x_facebook_sources extended external index map source map
    
  892.       await test(
    
  893.         './__source__/__compiled__/inline/react-sources-extended/index-map/ContainingStringSourceMappingURL',
    
  894.       ); // x_react_sources extended inline index map source map
    
  895.       await test(
    
  896.         './__source__/__compiled__/external/react-sources-extended/index-map/ContainingStringSourceMappingURL',
    
  897.       ); // x_react_sources extended external index map source map
    
  898. 
    
  899.       // TODO test no-columns and bundle cases with extended source maps
    
  900.     });
    
  901.   });
    
  902. });
    
  903. 
    
  904. describe('parseHookNames worker', () => {
    
  905.   let inspectHooks;
    
  906.   let parseHookNames;
    
  907.   let workerizedParseSourceAndMetadataMock;
    
  908. 
    
  909.   beforeEach(() => {
    
  910.     window.Worker = undefined;
    
  911. 
    
  912.     workerizedParseSourceAndMetadataMock = jest.fn();
    
  913. 
    
  914.     initFetchMock();
    
  915. 
    
  916.     jest.mock('../parseHookNames/parseSourceAndMetadata.worker.js', () => {
    
  917.       return {
    
  918.         __esModule: true,
    
  919.         default: () => ({
    
  920.           parseSourceAndMetadata: workerizedParseSourceAndMetadataMock,
    
  921.         }),
    
  922.       };
    
  923.     });
    
  924. 
    
  925.     inspectHooks =
    
  926.       require('react-debug-tools/src/ReactDebugHooks').inspectHooks;
    
  927.     parseHookNames = require('../parseHookNames').parseHookNames;
    
  928.   });
    
  929. 
    
  930.   async function getHookNamesForComponent(Component, props = {}) {
    
  931.     const hooksTree = inspectHooks(Component, props, undefined, true);
    
  932.     const hookNames = await parseHookNames(hooksTree);
    
  933.     return hookNames;
    
  934.   }
    
  935. 
    
  936.   it('should use worker', async () => {
    
  937.     const Component =
    
  938.       require('./__source__/__untransformed__/ComponentWithUseState').Component;
    
  939. 
    
  940.     window.Worker = true;
    
  941. 
    
  942.     // Reset module so mocked worker instance can be updated.
    
  943.     jest.resetModules();
    
  944.     parseHookNames = require('../parseHookNames').parseHookNames;
    
  945. 
    
  946.     await getHookNamesForComponent(Component);
    
  947.     expect(workerizedParseSourceAndMetadataMock).toHaveBeenCalledTimes(1);
    
  948.   });
    
  949. });