mirror of
https://github.com/facebook/react.git
synced 2026-02-26 18:58:05 +00:00
951 lines
40 KiB
JavaScript
951 lines
40 KiB
JavaScript
/**
|
|
* Copyright (c) Facebook, Inc. and its 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¶m=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/ComponentWithCustomHook',
|
|
); // 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/ComponentUsingHooksIndirectly',
|
|
); // x_facebook_sources extended external source map
|
|
await test(
|
|
'./__source__/__compiled__/inline/react-sources-extended/ComponentUsingHooksIndirectly',
|
|
); // x_react_sources extended inline source map
|
|
await test(
|
|
'./__source__/__compiled__/external/react-sources-extended/ComponentUsingHooksIndirectly',
|
|
); // x_react_sources extended external source map
|
|
|
|
// Using index map format for source maps
|
|
await test(
|
|
'./__source__/__compiled__/inline/fb-sources-extended/index-map/ComponentUsingHooksIndirectly',
|
|
); // 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/ComponentWithNestedHooks',
|
|
); // 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/ComponentWithExternalCustomHooks',
|
|
); // x_facebook_sources extended inline source map
|
|
await test(
|
|
'./__source__/__compiled__/external/fb-sources-extended/ComponentWithExternalCustomHooks',
|
|
); // x_facebook_sources extended external source map
|
|
await test(
|
|
'./__source__/__compiled__/inline/react-sources-extended/ComponentWithExternalCustomHooks',
|
|
); // x_react_sources extended inline source map
|
|
await test(
|
|
'./__source__/__compiled__/external/react-sources-extended/ComponentWithExternalCustomHooks',
|
|
); // x_react_sources extended external source map
|
|
|
|
// Using index map format for source maps
|
|
await test(
|
|
'./__source__/__compiled__/inline/fb-sources-extended/index-map/ComponentWithExternalCustomHooks',
|
|
); // 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/ComponentWithMultipleHooksPerLine',
|
|
); // x_facebook_sources extended inline source map
|
|
await test(
|
|
'./__source__/__compiled__/external/fb-sources-extended/ComponentWithMultipleHooksPerLine',
|
|
); // x_facebook_sources extended external source map
|
|
await test(
|
|
'./__source__/__compiled__/inline/react-sources-extended/ComponentWithMultipleHooksPerLine',
|
|
); // x_react_sources extended inline source map
|
|
await test(
|
|
'./__source__/__compiled__/external/react-sources-extended/ComponentWithMultipleHooksPerLine',
|
|
); // x_react_sources extended external source map
|
|
|
|
// Using index map format for source maps
|
|
await test(
|
|
'./__source__/__compiled__/inline/fb-sources-extended/index-map/ComponentWithMultipleHooksPerLine',
|
|
); // 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/ContainingStringSourceMappingURL',
|
|
); // x_facebook_sources extended inline source map
|
|
await test(
|
|
'./__source__/__compiled__/external/fb-sources-extended/ContainingStringSourceMappingURL',
|
|
); // x_facebook_sources extended external source map
|
|
await test(
|
|
'./__source__/__compiled__/inline/react-sources-extended/ContainingStringSourceMappingURL',
|
|
); // x_react_sources extended inline source map
|
|
await test(
|
|
'./__source__/__compiled__/external/react-sources-extended/ContainingStringSourceMappingURL',
|
|
); // x_react_sources extended external source map
|
|
|
|
// Using index map format for source maps
|
|
await test(
|
|
'./__source__/__compiled__/inline/fb-sources-extended/index-map/ContainingStringSourceMappingURL',
|
|
); // 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);
|
|
});
|
|
});
|