0% found this document useful (0 votes)
27 views

parseHookNames Test

The document describes tests to parse React hook names from components using various source maps including inline, external, bundle and no source maps. It tests parsing hook names for components using useState, useReducer, useEffect, custom hooks and nested hooks.

Uploaded by

mahoraga
Copyright
© © All Rights Reserved
Available Formats
Download as TXT, PDF, TXT or read online on Scribd
0% found this document useful (0 votes)
27 views

parseHookNames Test

The document describes tests to parse React hook names from components using various source maps including inline, external, bundle and no source maps. It tests parsing hook names for components using useState, useReducer, useEffect, custom hooks and nested hooks.

Uploaded by

mahoraga
Copyright
© © All Rights Reserved
Available Formats
Download as TXT, PDF, TXT or read online on Scribd
You are on page 1/ 19

/**

* Copyright (c) Meta Platforms, Inc. and affiliates.


*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @flow
*/

// Note that this test uses React components declared in the "__source__"
directory.
// This is done to control if and how the code is transformed at runtime.
// Do not declare test components within this test file as it is very fragile.

function expectHookNamesToEqual(map, expectedNamesArray) {


// Slightly hacky since it relies on the iterable order of values()
expect(Array.from(map.values())).toEqual(expectedNamesArray);
}

function requireText(path, encoding) {


const {existsSync, readFileSync} = require('fs');
if (existsSync(path)) {
return Promise.resolve(readFileSync(path, encoding));
} else {
return Promise.reject(`File not found "${path}"`);
}
}

function initFetchMock() {
const fetchMock = require('jest-fetch-mock');
fetchMock.enableMocks();
fetchMock.mockIf(/.+$/, request => {
const url = request.url;
const isLoadingExternalSourceMap = /external\/.*\.map/.test(url);
if (isLoadingExternalSourceMap) {
// Assert that url contains correct query params
expect(url.includes('?foo=bar&param=some_value')).toBe(true);
const fileSystemPath = url.split('?')[0];
return requireText(fileSystemPath, 'utf8');
}
return requireText(url, 'utf8');
});
return fetchMock;
}

describe('parseHookNames', () => {
let fetchMock;
let inspectHooks;
let parseHookNames;

beforeEach(() => {
jest.resetModules();

jest.mock('source-map-support', () => {
console.trace('source-map-support');
});

fetchMock = initFetchMock();
inspectHooks =
require('react-debug-tools/src/ReactDebugHooks').inspectHooks;

// Jest can't run the workerized version of this module.


const {
flattenHooksList,
loadSourceAndMetadata,
} = require('../parseHookNames/loadSourceAndMetadata');
const parseSourceAndMetadata =
require('../parseHookNames/parseSourceAndMetadata').parseSourceAndMetadata;
parseHookNames = async hooksTree => {
const hooksList = flattenHooksList(hooksTree);

// Runs in the UI thread so it can share Network cache:


const locationKeyToHookSourceAndMetadata = await loadSourceAndMetadata(
hooksList,
);

// Runs in a Worker because it's CPU intensive:


return parseSourceAndMetadata(
hooksList,
locationKeyToHookSourceAndMetadata,
);
};

// Jest (jest-runner?) configures Errors to automatically account for source


maps.
// This changes behavior between our tests and the browser.
// Ideally we would clear the prepareStackTrace() method on the Error object,
// but Node falls back to looking for it on the main context's Error
constructor,
// which may still be patched.
// To ensure we get the default behavior, override prepareStackTrace ourselves.
// NOTE: prepareStackTrace is called from the error.stack getter, but the
getter
// has a recursion breaker which falls back to the default behavior.
Error.prepareStackTrace = (error, trace) => {
return error.stack;
};
});

afterEach(() => {
fetch.resetMocks();
});

async function getHookNamesForComponent(Component, props = {}) {


const hooksTree = inspectHooks(Component, props, undefined, true);
const hookNames = await parseHookNames(hooksTree);
return hookNames;
}

it('should parse names for useState()', async () => {


const Component =
require('./__source__/__untransformed__/ComponentWithUseState').Component;
const hookNames = await getHookNamesForComponent(Component);
expectHookNamesToEqual(hookNames, ['foo', 'bar', 'baz', null]);
});

it('should parse names for useReducer()', async () => {


const Component =
require('./__source__/__untransformed__/ComponentWithUseReducer').Component;
const hookNames = await getHookNamesForComponent(Component);
expectHookNamesToEqual(hookNames, ['foo', 'bar', 'baz']);
});

it('should skip loading source files for unnamed hooks like useEffect', async ()
=> {
const Component =
require('./__source__/__untransformed__/ComponentWithUseEffect').Component;

// Since this component contains only unnamed hooks, the source code should not
even be loaded.
fetchMock.mockIf(/.+$/, request => {
throw Error(`Unexpected file request for "${request.url}"`);
});

const hookNames = await getHookNamesForComponent(Component);


expectHookNamesToEqual(hookNames, []); // No hooks with names
});

it('should skip loading source files for unnamed hooks like useEffect
(alternate)', async () => {
const Component =

require('./__source__/__untransformed__/ComponentWithExternalUseEffect').Component;

fetchMock.mockIf(/.+$/, request => {


// Since the custom hook contains only unnamed hooks, the source code should
not be loaded.
if (request.url.endsWith('useCustom.js')) {
throw Error(`Unexpected file request for "${request.url}"`);
}
return requireText(request.url, 'utf8');
});

const hookNames = await getHookNamesForComponent(Component);


expectHookNamesToEqual(hookNames, ['count', null]); // No hooks with names
});

it('should parse names for custom hooks', async () => {


const Component =

require('./__source__/__untransformed__/ComponentWithNamedCustomHooks').Component;
const hookNames = await getHookNamesForComponent(Component);
expectHookNamesToEqual(hookNames, [
'foo',
null, // Custom hooks can have names, but not when using destructuring.
'baz',
]);
});

it('should parse names for code using hooks indirectly', async () => {
const Component =

require('./__source__/__untransformed__/ComponentUsingHooksIndirectly').Component;
const hookNames = await getHookNamesForComponent(Component);
expectHookNamesToEqual(hookNames, ['count', 'darkMode', 'isDarkMode']);
});
it('should parse names for code using nested hooks', async () => {
const Component =
require('./__source__/__untransformed__/ComponentWithNestedHooks').Component;
let InnerComponent;
const hookNames = await getHookNamesForComponent(Component, {
callback: innerComponent => {
InnerComponent = innerComponent;
},
});
const innerHookNames = await getHookNamesForComponent(InnerComponent);
expectHookNamesToEqual(hookNames, ['InnerComponent']);
expectHookNamesToEqual(innerHookNames, ['state']);
});

it('should return null for custom hooks without explicit names', async () => {
const Component =

require('./__source__/__untransformed__/ComponentWithUnnamedCustomHooks').Component
;
const hookNames = await getHookNamesForComponent(Component);
expectHookNamesToEqual(hookNames, [
null, // Custom hooks can have names, but this one does not even return a
value.
null, // Custom hooks can have names, but not when using destructuring.
null, // Custom hooks can have names, but not when using destructuring.
]);
});

// TODO Test that cache purge works

// TODO Test that cached metadata is purged when Fast Refresh scheduled

describe('inline, external and bundle source maps', () => {


it('should work for simple components', async () => {
async function test(path, name = 'Component') {
const Component = require(path)[name];
const hookNames = await getHookNamesForComponent(Component);
expectHookNamesToEqual(hookNames, [
'count', // useState
]);
}

await test('./__source__/Example'); // original source (uncompiled)


await test('./__source__/__compiled__/inline/Example'); // inline source map
await test('./__source__/__compiled__/external/Example'); // external source
map
await test('./__source__/__compiled__/inline/index-map/Example'); // inline
index map source map
await test('./__source__/__compiled__/external/index-map/Example'); //
external index map source map
await test('./__source__/__compiled__/bundle/index', 'Example'); // bundle
source map
await test('./__source__/__compiled__/no-columns/Example'); // simulated
Webpack 'cheap-module-source-map'
});

it('should work with more complex files and components', async () => {
async function test(path, name = undefined) {
const components = name != null ? require(path)[name] : require(path);

let hookNames = await getHookNamesForComponent(components.List);


expectHookNamesToEqual(hookNames, [
'newItemText', // useState
'items', // useState
'uid', // useState
'handleClick', // useCallback
'handleKeyPress', // useCallback
'handleChange', // useCallback
'removeItem', // useCallback
'toggleItem', // useCallback
]);

hookNames = await getHookNamesForComponent(components.ListItem, {


item: {},
});
expectHookNamesToEqual(hookNames, [
'handleDelete', // useCallback
'handleToggle', // useCallback
]);
}

await test('./__source__/ToDoList'); // original source (uncompiled)


await test('./__source__/__compiled__/inline/ToDoList'); // inline source map
await test('./__source__/__compiled__/external/ToDoList'); // external source
map
await test('./__source__/__compiled__/inline/index-map/ToDoList'); // inline
index map source map
await test('./__source__/__compiled__/external/index-map/ToDoList'); //
external index map source map
await test('./__source__/__compiled__/bundle', 'ToDoList'); // bundle source
map
await test('./__source__/__compiled__/no-columns/ToDoList'); // simulated
Webpack 'cheap-module-source-map'
});

it('should work for custom hook', async () => {


async function test(path, name = 'Component') {
const Component = require(path)[name];
const hookNames = await getHookNamesForComponent(Component);
expectHookNamesToEqual(hookNames, [
'count', // useState()
'isDarkMode', // useIsDarkMode()
'isDarkMode', // useIsDarkMode -> useState()
null, // useFoo()
]);
}

await test('./__source__/ComponentWithCustomHook'); // original source


(uncompiled)
await test('./__source__/__compiled__/inline/ComponentWithCustomHook'); //
inline source map
await test('./__source__/__compiled__/external/ComponentWithCustomHook'); //
external source map
await test(
'./__source__/__compiled__/inline/index-map/ComponentWithCustomHook',
); // inline index map source map
await test(
'./__source__/__compiled__/external/index-map/ComponentWithCustomHook',
); // external index map source map
await test('./__source__/__compiled__/bundle', 'ComponentWithCustomHook'); //
bundle source map
await test(
'./__source__/__compiled__/no-columns/ComponentWithCustomHook',
); // simulated Webpack 'cheap-module-source-map'
});

it('should work when code is using hooks indirectly', async () => {


async function test(path, name = 'Component') {
const Component = require(path)[name];
const hookNames = await getHookNamesForComponent(Component);
expectHookNamesToEqual(hookNames, [
'count', // useState()
'darkMode', // useDarkMode()
'isDarkMode', // useState()
]);
}

await test(
'./__source__/__compiled__/inline/ComponentUsingHooksIndirectly',
); // inline source map
await test(
'./__source__/__compiled__/external/ComponentUsingHooksIndirectly',
); // external source map
await test(
'./__source__/__compiled__/inline/index-map/ComponentUsingHooksIndirectly',
); // inline index map source map
await test(

'./__source__/__compiled__/external/index-map/ComponentUsingHooksIndirectly',
); // external index map source map
await test(
'./__source__/__compiled__/bundle',
'ComponentUsingHooksIndirectly',
); // bundle source map
await test(
'./__source__/__compiled__/no-columns/ComponentUsingHooksIndirectly',
); // simulated Webpack 'cheap-module-source-map'
});

it('should work when code is using nested hooks', async () => {


async function test(path, name = 'Component') {
const Component = require(path)[name];
let InnerComponent;
const hookNames = await getHookNamesForComponent(Component, {
callback: innerComponent => {
InnerComponent = innerComponent;
},
});
const innerHookNames = await getHookNamesForComponent(InnerComponent);
expectHookNamesToEqual(hookNames, [
'InnerComponent', // useMemo()
]);
expectHookNamesToEqual(innerHookNames, [
'state', // useState()
]);
}
await test('./__source__/__compiled__/inline/ComponentWithNestedHooks'); //
inline source map
await test('./__source__/__compiled__/external/ComponentWithNestedHooks'); //
external source map
await test(
'./__source__/__compiled__/inline/index-map/ComponentWithNestedHooks',
); // inline index map source map
await test(
'./__source__/__compiled__/external/index-map/ComponentWithNestedHooks',
); // external index map source map
await test(
'./__source__/__compiled__/bundle',
'ComponentWithNestedHooks',
); // bundle source map
await test(
'./__source__/__compiled__/no-columns/ComponentWithNestedHooks',
); // simulated Webpack 'cheap-module-source-map'
});

it('should work for external hooks', async () => {


async function test(path, name = 'Component') {
const Component = require(path)[name];
const hookNames = await getHookNamesForComponent(Component);
expectHookNamesToEqual(hookNames, [
'theme', // useTheme()
'theme', // useContext()
]);
}

// We can't test the uncompiled source here, because it either needs to get
transformed,
// which would break the source mapping, or the import statements will fail.

await test(
'./__source__/__compiled__/inline/ComponentWithExternalCustomHooks',
); // inline source map
await test(
'./__source__/__compiled__/external/ComponentWithExternalCustomHooks',
); // external source map
await test(

'./__source__/__compiled__/inline/index-map/ComponentWithExternalCustomHooks',
); // inline index map source map
await test(

'./__source__/__compiled__/external/index-map/ComponentWithExternalCustomHooks',
); // external index map source map
await test(
'./__source__/__compiled__/bundle',
'ComponentWithExternalCustomHooks',
); // bundle source map
await test(
'./__source__/__compiled__/no-columns/ComponentWithExternalCustomHooks',
); // simulated Webpack 'cheap-module-source-map'
});

it('should work when multiple hooks are on a line', async () => {


async function test(path, name = 'Component') {
const Component = require(path)[name];
const hookNames = await getHookNamesForComponent(Component);
expectHookNamesToEqual(hookNames, [
'a', // useContext()
'b', // useContext()
'c', // useContext()
'd', // useContext()
]);
}

await test(
'./__source__/__compiled__/inline/ComponentWithMultipleHooksPerLine',
); // inline source map
await test(
'./__source__/__compiled__/external/ComponentWithMultipleHooksPerLine',
); // external source map
await test(

'./__source__/__compiled__/inline/index-map/ComponentWithMultipleHooksPerLine',
); // inline index map source map
await test(

'./__source__/__compiled__/external/index-map/ComponentWithMultipleHooksPerLine',
); // external index map source map
await test(
'./__source__/__compiled__/bundle',
'ComponentWithMultipleHooksPerLine',
); // bundle source map

async function noColumnTest(path, name = 'Component') {


const Component = require(path)[name];
const hookNames = await getHookNamesForComponent(Component);
expectHookNamesToEqual(hookNames, [
'a', // useContext()
'b', // useContext()
null, // useContext()
null, // useContext()
]);
}

// Note that this test is expected to only match the first two hooks
// because the 3rd and 4th hook are on the same line,
// and this type of source map doesn't have column numbers.
await noColumnTest(
'./__source__/__compiled__/no-columns/ComponentWithMultipleHooksPerLine',
); // simulated Webpack 'cheap-module-source-map'
});

// TODO Inline require (e.g. require("react").useState()) isn't supported yet.


// Maybe this isn't an important use case to support,
// since inline requires are most likely to exist in compiled source (if at
all).
xit('should work for inline requires', async () => {
async function test(path, name = 'Component') {
const Component = require(path)[name];
const hookNames = await getHookNamesForComponent(Component);
expectHookNamesToEqual(hookNames, [
'count', // useState()
]);
}

await test('./__source__/InlineRequire'); // original source (uncompiled)


await test('./__source__/__compiled__/inline/InlineRequire'); // inline
source map
await test('./__source__/__compiled__/external/InlineRequire'); // external
source map
await test('./__source__/__compiled__/inline/index-map/InlineRequire'); //
inline index map source map
await test('./__source__/__compiled__/external/index-map/InlineRequire'); //
external index map source map
await test('./__source__/__compiled__/bundle', 'InlineRequire'); // bundle
source map
await test('./__source__/__compiled__/no-columns/InlineRequire'); //
simulated Webpack 'cheap-module-source-map'
});

it('should support sources that contain the string "sourceMappingURL="', async


() => {
async function test(path, name = 'Component') {
const Component = require(path)[name];
const hookNames = await getHookNamesForComponent(Component);
expectHookNamesToEqual(hookNames, [
'count', // useState()
]);
}

// We expect the inline sourceMappingURL to be invalid in this case; mute the


warning.
console.warn = () => {};

await test('./__source__/ContainingStringSourceMappingURL'); // original


source (uncompiled)
await test(
'./__source__/__compiled__/inline/ContainingStringSourceMappingURL',
); // inline source map
await test(
'./__source__/__compiled__/external/ContainingStringSourceMappingURL',
); // external source map
await test(

'./__source__/__compiled__/inline/index-map/ContainingStringSourceMappingURL',
); // inline index map source map
await test(

'./__source__/__compiled__/external/index-map/ContainingStringSourceMappingURL',
); // external index map source map
await test(
'./__source__/__compiled__/bundle',
'ContainingStringSourceMappingURL',
); // bundle source map
await test(
'./__source__/__compiled__/no-columns/ContainingStringSourceMappingURL',
); // simulated Webpack 'cheap-module-source-map'
});
});

describe('extended source maps', () => {


beforeEach(() => {
const babelParser = require('@babel/parser');
const generateHookMapModule = require('../generateHookMap');
jest.spyOn(babelParser, 'parse');
jest.spyOn(generateHookMapModule, 'decodeHookMap');
});

it('should work for simple components', async () => {


async function test(path, name = 'Component') {
const Component = require(path)[name];
const hookNames = await getHookNamesForComponent(Component);
expectHookNamesToEqual(hookNames, [
'count', // useState
]);
expect(require('@babel/parser').parse).toHaveBeenCalledTimes(0);
expect(require('../generateHookMap').decodeHookMap).toHaveBeenCalled();
}

await test(
'./__source__/__compiled__/inline/fb-sources-extended/Example',
); // x_facebook_sources extended inline source map
await test(
'./__source__/__compiled__/external/fb-sources-extended/Example',
); // x_facebook_sources extended external source map
await test(
'./__source__/__compiled__/inline/react-sources-extended/Example',
); // x_react_sources extended inline source map
await test(
'./__source__/__compiled__/external/react-sources-extended/Example',
); // x_react_sources extended external source map

// Using index map format for source maps


await test(
'./__source__/__compiled__/inline/fb-sources-extended/index-map/Example',
); // x_facebook_sources extended inline index map source map
await test(
'./__source__/__compiled__/external/fb-sources-extended/index-map/Example',
); // x_facebook_sources extended external index map source map
await test(
'./__source__/__compiled__/inline/react-sources-extended/index-map/
Example',
); // x_react_sources extended inline index map source map
await test(
'./__source__/__compiled__/external/react-sources-extended/index-map/
Example',
); // x_react_sources extended external index map source map

// TODO test no-columns and bundle cases with extended source maps
});

it('should work with more complex files and components', async () => {
async function test(path, name = undefined) {
const components = name != null ? require(path)[name] : require(path);

let hookNames = await getHookNamesForComponent(components.List);


expectHookNamesToEqual(hookNames, [
'newItemText', // useState
'items', // useState
'uid', // useState
'handleClick', // useCallback
'handleKeyPress', // useCallback
'handleChange', // useCallback
'removeItem', // useCallback
'toggleItem', // useCallback
]);

hookNames = await getHookNamesForComponent(components.ListItem, {


item: {},
});
expectHookNamesToEqual(hookNames, [
'handleDelete', // useCallback
'handleToggle', // useCallback
]);

expect(require('@babel/parser').parse).toHaveBeenCalledTimes(0);
expect(require('../generateHookMap').decodeHookMap).toHaveBeenCalled();
}

await test(
'./__source__/__compiled__/inline/fb-sources-extended/ToDoList',
); // x_facebook_sources extended inline source map
await test(
'./__source__/__compiled__/external/fb-sources-extended/ToDoList',
); // x_facebook_sources extended external source map
await test(
'./__source__/__compiled__/inline/react-sources-extended/ToDoList',
); // x_react_sources extended inline source map
await test(
'./__source__/__compiled__/external/react-sources-extended/ToDoList',
); // x_react_sources extended external source map

// Using index map format for source maps


await test(
'./__source__/__compiled__/inline/fb-sources-extended/index-map/ToDoList',
); // x_facebook_sources extended inline index map source map
await test(
'./__source__/__compiled__/external/fb-sources-extended/index-map/
ToDoList',
); // x_facebook_sources extended external index map source map
await test(
'./__source__/__compiled__/inline/react-sources-extended/index-map/
ToDoList',
); // x_react_sources extended inline index map source map
await test(
'./__source__/__compiled__/external/react-sources-extended/index-map/
ToDoList',
); // x_react_sources extended external index map source map

// TODO test no-columns and bundle cases with extended source maps
});

it('should work for custom hook', async () => {


async function test(path, name = 'Component') {
const Component = require(path)[name];
const hookNames = await getHookNamesForComponent(Component);
expectHookNamesToEqual(hookNames, [
'count', // useState()
'isDarkMode', // useIsDarkMode()
'isDarkMode', // useIsDarkMode -> useState()
null, // isFoo()
]);
expect(require('@babel/parser').parse).toHaveBeenCalledTimes(0);
expect(require('../generateHookMap').decodeHookMap).toHaveBeenCalled();
}

await test(

'./__source__/__compiled__/inline/fb-sources-extended/ComponentWithCustomHook',
); // x_facebook_sources extended inline source map
await test(

'./__source__/__compiled__/external/fb-sources-extended/ComponentWithCustomHook',
); // x_facebook_sources extended external source map
await test(

'./__source__/__compiled__/inline/react-sources-extended/ComponentWithCustomHook',
); // x_react_sources extended inline source map
await test(

'./__source__/__compiled__/external/react-sources-extended/ComponentWithCustomHook'
,
); // x_react_sources extended external source map

// Using index map format for source maps


await test(

'./__source__/__compiled__/inline/fb-sources-extended/index-map/ComponentWithCustom
Hook',
); // x_facebook_sources extended inline index map source map
await test(
'./__source__/__compiled__/external/fb-sources-extended/index-map/
ComponentWithCustomHook',
); // x_facebook_sources extended external index map source map
await test(
'./__source__/__compiled__/inline/react-sources-extended/index-map/
ComponentWithCustomHook',
); // x_react_sources extended inline index map source map
await test(
'./__source__/__compiled__/external/react-sources-extended/index-map/
ComponentWithCustomHook',
); // x_react_sources extended external index map source map

// TODO test no-columns and bundle cases with extended source maps
});

it('should work when code is using hooks indirectly', async () => {


async function test(path, name = 'Component') {
const Component = require(path)[name];
const hookNames = await getHookNamesForComponent(Component);
expectHookNamesToEqual(hookNames, [
'count', // useState()
'darkMode', // useDarkMode()
'isDarkMode', // useState()
]);
expect(require('@babel/parser').parse).toHaveBeenCalledTimes(0);
expect(require('../generateHookMap').decodeHookMap).toHaveBeenCalled();
}
await test(

'./__source__/__compiled__/inline/fb-sources-extended/ComponentUsingHooksIndirectly
',
); // x_facebook_sources extended inline source map
await test(

'./__source__/__compiled__/external/fb-sources-extended/ComponentUsingHooksIndirect
ly',
); // x_facebook_sources extended external source map
await test(

'./__source__/__compiled__/inline/react-sources-extended/ComponentUsingHooksIndirec
tly',
); // x_react_sources extended inline source map
await test(

'./__source__/__compiled__/external/react-sources-extended/ComponentUsingHooksIndir
ectly',
); // x_react_sources extended external source map

// Using index map format for source maps


await test(

'./__source__/__compiled__/inline/fb-sources-extended/index-map/ComponentUsingHooks
Indirectly',
); // x_facebook_sources extended inline index map source map
await test(
'./__source__/__compiled__/external/fb-sources-extended/index-map/
ComponentUsingHooksIndirectly',
); // x_facebook_sources extended external index map source map
await test(
'./__source__/__compiled__/inline/react-sources-extended/index-map/
ComponentUsingHooksIndirectly',
); // x_react_sources extended inline index map source map
await test(
'./__source__/__compiled__/external/react-sources-extended/index-map/
ComponentUsingHooksIndirectly',
); // x_react_sources extended external index map source map

// TODO test no-columns and bundle cases with extended source maps
});

it('should work when code is using nested hooks', async () => {


async function test(path, name = 'Component') {
const Component = require(path)[name];
let InnerComponent;
const hookNames = await getHookNamesForComponent(Component, {
callback: innerComponent => {
InnerComponent = innerComponent;
},
});
const innerHookNames = await getHookNamesForComponent(InnerComponent);
expectHookNamesToEqual(hookNames, [
'InnerComponent', // useMemo()
]);
expectHookNamesToEqual(innerHookNames, [
'state', // useState()
]);
expect(require('@babel/parser').parse).toHaveBeenCalledTimes(0);
expect(require('../generateHookMap').decodeHookMap).toHaveBeenCalled();
}

await test(

'./__source__/__compiled__/inline/fb-sources-extended/ComponentWithNestedHooks',
); // x_facebook_sources extended inline source map
await test(

'./__source__/__compiled__/external/fb-sources-extended/ComponentWithNestedHooks',
); // x_facebook_sources extended external source map
await test(

'./__source__/__compiled__/inline/react-sources-extended/ComponentWithNestedHooks',
); // x_react_sources extended inline source map
await test(

'./__source__/__compiled__/external/react-sources-extended/ComponentWithNestedHooks
',
); // x_react_sources extended external source map

// Using index map format for source maps


await test(

'./__source__/__compiled__/inline/fb-sources-extended/index-map/ComponentWithNested
Hooks',
); // x_facebook_sources extended inline index map source map
await test(
'./__source__/__compiled__/external/fb-sources-extended/index-map/
ComponentWithNestedHooks',
); // x_facebook_sources extended external index map source map
await test(
'./__source__/__compiled__/inline/react-sources-extended/index-map/
ComponentWithNestedHooks',
); // x_react_sources extended inline index map source map
await test(
'./__source__/__compiled__/external/react-sources-extended/index-map/
ComponentWithNestedHooks',
); // x_react_sources extended external index map source map

// TODO test no-columns and bundle cases with extended source maps
});

it('should work for external hooks', async () => {


async function test(path, name = 'Component') {
const Component = require(path)[name];
const hookNames = await getHookNamesForComponent(Component);
expectHookNamesToEqual(hookNames, [
'theme', // useTheme()
'theme', // useContext()
]);
expect(require('@babel/parser').parse).toHaveBeenCalledTimes(0);
expect(require('../generateHookMap').decodeHookMap).toHaveBeenCalled();
}

// We can't test the uncompiled source here, because it either needs to get
transformed,
// which would break the source mapping, or the import statements will fail.
await test(

'./__source__/__compiled__/inline/fb-sources-extended/ComponentWithExternalCustomHo
oks',
); // x_facebook_sources extended inline source map
await test(

'./__source__/__compiled__/external/fb-sources-extended/ComponentWithExternalCustom
Hooks',
); // x_facebook_sources extended external source map
await test(

'./__source__/__compiled__/inline/react-sources-extended/ComponentWithExternalCusto
mHooks',
); // x_react_sources extended inline source map
await test(

'./__source__/__compiled__/external/react-sources-extended/ComponentWithExternalCus
tomHooks',
); // x_react_sources extended external source map

// Using index map format for source maps


await test(

'./__source__/__compiled__/inline/fb-sources-extended/index-map/ComponentWithExtern
alCustomHooks',
); // x_facebook_sources extended inline index map source map
await test(
'./__source__/__compiled__/external/fb-sources-extended/index-map/
ComponentWithExternalCustomHooks',
); // x_facebook_sources extended external index map source map
await test(
'./__source__/__compiled__/inline/react-sources-extended/index-map/
ComponentWithExternalCustomHooks',
); // x_react_sources extended inline index map source map
await test(
'./__source__/__compiled__/external/react-sources-extended/index-map/
ComponentWithExternalCustomHooks',
); // x_react_sources extended external index map source map

// TODO test no-columns and bundle cases with extended source maps
});

it('should work when multiple hooks are on a line', async () => {


async function test(path, name = 'Component') {
const Component = require(path)[name];
const hookNames = await getHookNamesForComponent(Component);
expectHookNamesToEqual(hookNames, [
'a', // useContext()
'b', // useContext()
'c', // useContext()
'd', // useContext()
]);
expect(require('@babel/parser').parse).toHaveBeenCalledTimes(0);
expect(require('../generateHookMap').decodeHookMap).toHaveBeenCalled();
}

await test(
'./__source__/__compiled__/inline/fb-sources-extended/ComponentWithMultipleHooksPer
Line',
); // x_facebook_sources extended inline source map
await test(

'./__source__/__compiled__/external/fb-sources-extended/ComponentWithMultipleHooksP
erLine',
); // x_facebook_sources extended external source map
await test(

'./__source__/__compiled__/inline/react-sources-extended/ComponentWithMultipleHooks
PerLine',
); // x_react_sources extended inline source map
await test(

'./__source__/__compiled__/external/react-sources-extended/ComponentWithMultipleHoo
ksPerLine',
); // x_react_sources extended external source map

// Using index map format for source maps


await test(

'./__source__/__compiled__/inline/fb-sources-extended/index-map/ComponentWithMultip
leHooksPerLine',
); // x_facebook_sources extended inline index map source map
await test(
'./__source__/__compiled__/external/fb-sources-extended/index-map/
ComponentWithMultipleHooksPerLine',
); // x_facebook_sources extended external index map source map
await test(
'./__source__/__compiled__/inline/react-sources-extended/index-map/
ComponentWithMultipleHooksPerLine',
); // x_react_sources extended inline index map source map
await test(
'./__source__/__compiled__/external/react-sources-extended/index-map/
ComponentWithMultipleHooksPerLine',
); // x_react_sources extended external index map source map

// TODO test no-columns and bundle cases with extended source maps
});

// TODO Inline require (e.g. require("react").useState()) isn't supported yet.


// Maybe this isn't an important use case to support,
// since inline requires are most likely to exist in compiled source (if at
all).
xit('should work for inline requires', async () => {
async function test(path, name = 'Component') {
const Component = require(path)[name];
const hookNames = await getHookNamesForComponent(Component);
expectHookNamesToEqual(hookNames, [
'count', // useState()
]);
expect(require('@babel/parser').parse).toHaveBeenCalledTimes(0);
expect(require('../generateHookMap').decodeHookMap).toHaveBeenCalled();
}

await test(
'./__source__/__compiled__/inline/fb-sources-extended/InlineRequire',
); // x_facebook_sources extended inline source map
await test(
'./__source__/__compiled__/external/fb-sources-extended/InlineRequire',
); // x_facebook_sources extended external source map
await test(
'./__source__/__compiled__/inline/react-sources-extended/InlineRequire',
); // x_react_sources extended inline source map
await test(
'./__source__/__compiled__/external/react-sources-extended/InlineRequire',
); // x_react_sources extended external source map

// Using index map format for source maps


await test(

'./__source__/__compiled__/inline/fb-sources-extended/index-map/InlineRequire',
); // x_facebook_sources extended inline index map source map
await test(
'./__source__/__compiled__/external/fb-sources-extended/index-map/
InlineRequire',
); // x_facebook_sources extended external index map source map
await test(
'./__source__/__compiled__/inline/react-sources-extended/index-map/
InlineRequire',
); // x_react_sources extended inline index map source map
await test(
'./__source__/__compiled__/external/react-sources-extended/index-map/
InlineRequire',
); // x_react_sources extended external index map source map

// TODO test no-columns and bundle cases with extended source maps
});

it('should support sources that contain the string "sourceMappingURL="', async


() => {
async function test(path, name = 'Component') {
const Component = require(path)[name];
const hookNames = await getHookNamesForComponent(Component);
expectHookNamesToEqual(hookNames, [
'count', // useState()
]);
expect(require('@babel/parser').parse).toHaveBeenCalledTimes(0);
expect(require('../generateHookMap').decodeHookMap).toHaveBeenCalled();
}

// We expect the inline sourceMappingURL to be invalid in this case; mute the


warning.
console.warn = () => {};

await test(

'./__source__/__compiled__/inline/fb-sources-extended/ContainingStringSourceMapping
URL',
); // x_facebook_sources extended inline source map
await test(

'./__source__/__compiled__/external/fb-sources-extended/ContainingStringSourceMappi
ngURL',
); // x_facebook_sources extended external source map
await test(
'./__source__/__compiled__/inline/react-sources-extended/ContainingStringSourceMapp
ingURL',
); // x_react_sources extended inline source map
await test(

'./__source__/__compiled__/external/react-sources-extended/ContainingStringSourceMa
ppingURL',
); // x_react_sources extended external source map

// Using index map format for source maps


await test(

'./__source__/__compiled__/inline/fb-sources-extended/index-map/ContainingStringSou
rceMappingURL',
); // x_facebook_sources extended inline index map source map
await test(
'./__source__/__compiled__/external/fb-sources-extended/index-map/
ContainingStringSourceMappingURL',
); // x_facebook_sources extended external index map source map
await test(
'./__source__/__compiled__/inline/react-sources-extended/index-map/
ContainingStringSourceMappingURL',
); // x_react_sources extended inline index map source map
await test(
'./__source__/__compiled__/external/react-sources-extended/index-map/
ContainingStringSourceMappingURL',
); // x_react_sources extended external index map source map

// TODO test no-columns and bundle cases with extended source maps
});
});
});

describe('parseHookNames worker', () => {


let inspectHooks;
let parseHookNames;
let workerizedParseSourceAndMetadataMock;

beforeEach(() => {
window.Worker = undefined;

workerizedParseSourceAndMetadataMock = jest.fn();

initFetchMock();

jest.mock('../parseHookNames/parseSourceAndMetadata.worker.js', () => {
return {
__esModule: true,
default: () => ({
parseSourceAndMetadata: workerizedParseSourceAndMetadataMock,
}),
};
});

inspectHooks =
require('react-debug-tools/src/ReactDebugHooks').inspectHooks;
parseHookNames = require('../parseHookNames').parseHookNames;
});
async function getHookNamesForComponent(Component, props = {}) {
const hooksTree = inspectHooks(Component, props, undefined, true);
const hookNames = await parseHookNames(hooksTree);
return hookNames;
}

it('should use worker', async () => {


const Component =
require('./__source__/__untransformed__/ComponentWithUseState').Component;

window.Worker = true;

// Reset module so mocked worker instance can be updated.


jest.resetModules();
parseHookNames = require('../parseHookNames').parseHookNames;

await getHookNamesForComponent(Component);
expect(workerizedParseSourceAndMetadataMock).toHaveBeenCalledTimes(1);
});
});

You might also like