669 lines
41 KiB
JavaScript
669 lines
41 KiB
JavaScript
|
/**
|
||
|
* 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()
|
||
|
expectedNamesArray |> (map.values() |> Array.from(%) |> expect(%)).toEqual(%);
|
||
|
}
|
||
|
function requireText(path, encoding) {
|
||
|
const {
|
||
|
existsSync,
|
||
|
readFileSync
|
||
|
} = 'fs' |> require(%);
|
||
|
if (path |> existsSync(%)) {
|
||
|
return path |> readFileSync(%, encoding) |> Promise.resolve(%);
|
||
|
} else {
|
||
|
return `File not found "${path}"` |> Promise.reject(%);
|
||
|
}
|
||
|
}
|
||
|
function initFetchMock() {
|
||
|
const fetchMock = 'jest-fetch-mock' |> require(%);
|
||
|
fetchMock.enableMocks();
|
||
|
/.+$/ |> fetchMock.mockIf(%, request => {
|
||
|
const url = request.url;
|
||
|
const isLoadingExternalSourceMap = url |> /external\/.*\.map/.test(%);
|
||
|
if (isLoadingExternalSourceMap) {
|
||
|
// Assert that url contains correct query params
|
||
|
true |> ('?foo=bar¶m=some_value' |> url.includes(%) |> expect(%)).toBe(%);
|
||
|
const fileSystemPath = ('?' |> url.split(%))[0];
|
||
|
return fileSystemPath |> requireText(%, 'utf8');
|
||
|
}
|
||
|
return url |> requireText(%, 'utf8');
|
||
|
});
|
||
|
return fetchMock;
|
||
|
}
|
||
|
'parseHookNames' |> describe(%, () => {
|
||
|
let fetchMock;
|
||
|
let inspectHooks;
|
||
|
let parseHookNames;
|
||
|
(() => {
|
||
|
jest.resetModules();
|
||
|
'source-map-support' |> jest.mock(%, () => {
|
||
|
'source-map-support' |> console.trace(%);
|
||
|
});
|
||
|
fetchMock = initFetchMock();
|
||
|
inspectHooks = ('react-debug-tools/src/ReactDebugHooks' |> require(%)).inspectHooks;
|
||
|
|
||
|
// Jest can't run the workerized version of this module.
|
||
|
const {
|
||
|
flattenHooksList,
|
||
|
loadSourceAndMetadata
|
||
|
} = '../parseHookNames/loadSourceAndMetadata' |> require(%);
|
||
|
const parseSourceAndMetadata = ('../parseHookNames/parseSourceAndMetadata' |> require(%)).parseSourceAndMetadata;
|
||
|
parseHookNames = async hooksTree => {
|
||
|
const hooksList = hooksTree |> flattenHooksList(%);
|
||
|
|
||
|
// Runs in the UI thread so it can share Network cache:
|
||
|
const locationKeyToHookSourceAndMetadata = await (hooksList |> loadSourceAndMetadata(%));
|
||
|
|
||
|
// Runs in a Worker because it's CPU intensive:
|
||
|
return hooksList |> parseSourceAndMetadata(%, 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;
|
||
|
};
|
||
|
}) |> beforeEach(%);
|
||
|
(() => {
|
||
|
fetch.resetMocks();
|
||
|
}) |> afterEach(%);
|
||
|
async function getHookNamesForComponent(Component, props = {}) {
|
||
|
const hooksTree = inspectHooks(Component, props, undefined);
|
||
|
const hookNames = await (hooksTree |> parseHookNames(%));
|
||
|
return hookNames;
|
||
|
}
|
||
|
'should parse names for useState()' |> it(%, async () => {
|
||
|
const Component = ('./__source__/__untransformed__/ComponentWithUseState' |> require(%)).Component;
|
||
|
const hookNames = await (Component |> getHookNamesForComponent(%));
|
||
|
hookNames |> expectHookNamesToEqual(%, ['foo', 'bar', 'baz', null]);
|
||
|
});
|
||
|
'should parse names for useReducer()' |> it(%, async () => {
|
||
|
const Component = ('./__source__/__untransformed__/ComponentWithUseReducer' |> require(%)).Component;
|
||
|
const hookNames = await (Component |> getHookNamesForComponent(%));
|
||
|
hookNames |> expectHookNamesToEqual(%, ['foo', 'bar', 'baz']);
|
||
|
});
|
||
|
'should skip loading source files for unnamed hooks like useEffect' |> it(%, async () => {
|
||
|
const Component = ('./__source__/__untransformed__/ComponentWithUseEffect' |> require(%)).Component;
|
||
|
|
||
|
// Since this component contains only unnamed hooks, the source code should not even be loaded.
|
||
|
/.+$/ |> fetchMock.mockIf(%, request => {
|
||
|
throw `Unexpected file request for "${request.url}"` |> Error(%);
|
||
|
});
|
||
|
const hookNames = await (Component |> getHookNamesForComponent(%));
|
||
|
// No hooks with names
|
||
|
hookNames |> expectHookNamesToEqual(%, []);
|
||
|
});
|
||
|
'should skip loading source files for unnamed hooks like useEffect (alternate)' |> it(%, async () => {
|
||
|
const Component = ('./__source__/__untransformed__/ComponentWithExternalUseEffect' |> require(%)).Component;
|
||
|
/.+$/ |> fetchMock.mockIf(%, request => {
|
||
|
// Since the custom hook contains only unnamed hooks, the source code should not be loaded.
|
||
|
if ('useCustom.js' |> request.url.endsWith(%)) {
|
||
|
throw `Unexpected file request for "${request.url}"` |> Error(%);
|
||
|
}
|
||
|
return request.url |> requireText(%, 'utf8');
|
||
|
});
|
||
|
const hookNames = await (Component |> getHookNamesForComponent(%));
|
||
|
// No hooks with names
|
||
|
hookNames |> expectHookNamesToEqual(%, ['count', null]);
|
||
|
});
|
||
|
'should parse names for custom hooks' |> it(%, async () => {
|
||
|
const Component = ('./__source__/__untransformed__/ComponentWithNamedCustomHooks' |> require(%)).Component;
|
||
|
const hookNames = await (Component |> getHookNamesForComponent(%));
|
||
|
hookNames |> expectHookNamesToEqual(%, ['foo', null,
|
||
|
// Custom hooks can have names, but not when using destructuring.
|
||
|
'baz']);
|
||
|
});
|
||
|
'should parse names for code using hooks indirectly' |> it(%, async () => {
|
||
|
const Component = ('./__source__/__untransformed__/ComponentUsingHooksIndirectly' |> require(%)).Component;
|
||
|
const hookNames = await (Component |> getHookNamesForComponent(%));
|
||
|
hookNames |> expectHookNamesToEqual(%, ['count', 'darkMode', 'isDarkMode']);
|
||
|
});
|
||
|
'should parse names for code using nested hooks' |> it(%, async () => {
|
||
|
const Component = ('./__source__/__untransformed__/ComponentWithNestedHooks' |> require(%)).Component;
|
||
|
let InnerComponent;
|
||
|
const hookNames = await (Component |> getHookNamesForComponent(%, {
|
||
|
callback: innerComponent => {
|
||
|
InnerComponent = innerComponent;
|
||
|
}
|
||
|
}));
|
||
|
const innerHookNames = await (InnerComponent |> getHookNamesForComponent(%));
|
||
|
hookNames |> expectHookNamesToEqual(%, ['InnerComponent']);
|
||
|
innerHookNames |> expectHookNamesToEqual(%, ['state']);
|
||
|
});
|
||
|
// TODO Test that cache purge works
|
||
|
|
||
|
// TODO Test that cached metadata is purged when Fast Refresh scheduled
|
||
|
'should return null for custom hooks without explicit names' |> it(%, async () => {
|
||
|
const Component = ('./__source__/__untransformed__/ComponentWithUnnamedCustomHooks' |> require(%)).Component;
|
||
|
const hookNames = await (Component |> getHookNamesForComponent(%));
|
||
|
hookNames |> expectHookNamesToEqual(%, [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.
|
||
|
]);
|
||
|
});
|
||
|
'inline, external and bundle source maps' |> describe(%, () => {
|
||
|
'should work for simple components' |> it(%, async () => {
|
||
|
async function test(path, name = 'Component') {
|
||
|
const Component = (path |> require(%))[name];
|
||
|
const hookNames = await (Component |> getHookNamesForComponent(%));
|
||
|
hookNames |> expectHookNamesToEqual(%, ['count' // useState
|
||
|
]);
|
||
|
}
|
||
|
await ('./__source__/Example' |> test(%)); // original source (uncompiled)
|
||
|
await ('./__source__/__compiled__/inline/Example' |> test(%)); // inline source map
|
||
|
await ('./__source__/__compiled__/external/Example' |> test(%)); // external source map
|
||
|
await ('./__source__/__compiled__/inline/index-map/Example' |> test(%)); // inline index map source map
|
||
|
await ('./__source__/__compiled__/external/index-map/Example' |> test(%)); // external index map source map
|
||
|
await ('./__source__/__compiled__/bundle/index' |> test(%, 'Example')); // bundle source map
|
||
|
await ('./__source__/__compiled__/no-columns/Example' |> test(%)); // simulated Webpack 'cheap-module-source-map'
|
||
|
});
|
||
|
'should work with more complex files and components' |> it(%, async () => {
|
||
|
async function test(path, name = undefined) {
|
||
|
const components = name != null ? (path |> require(%))[name] : path |> require(%);
|
||
|
let hookNames = await (components.List |> getHookNamesForComponent(%));
|
||
|
hookNames |> expectHookNamesToEqual(%, ['newItemText',
|
||
|
// useState
|
||
|
'items',
|
||
|
// useState
|
||
|
'uid',
|
||
|
// useState
|
||
|
'handleClick',
|
||
|
// useCallback
|
||
|
'handleKeyPress',
|
||
|
// useCallback
|
||
|
'handleChange',
|
||
|
// useCallback
|
||
|
'removeItem',
|
||
|
// useCallback
|
||
|
'toggleItem' // useCallback
|
||
|
]);
|
||
|
hookNames = await (components.ListItem |> getHookNamesForComponent(%, {
|
||
|
item: {}
|
||
|
}));
|
||
|
hookNames |> expectHookNamesToEqual(%, ['handleDelete',
|
||
|
// useCallback
|
||
|
'handleToggle' // useCallback
|
||
|
]);
|
||
|
}
|
||
|
await ('./__source__/ToDoList' |> test(%)); // original source (uncompiled)
|
||
|
await ('./__source__/__compiled__/inline/ToDoList' |> test(%)); // inline source map
|
||
|
await ('./__source__/__compiled__/external/ToDoList' |> test(%)); // external source map
|
||
|
await ('./__source__/__compiled__/inline/index-map/ToDoList' |> test(%)); // inline index map source map
|
||
|
await ('./__source__/__compiled__/external/index-map/ToDoList' |> test(%)); // external index map source map
|
||
|
await ('./__source__/__compiled__/bundle' |> test(%, 'ToDoList')); // bundle source map
|
||
|
await ('./__source__/__compiled__/no-columns/ToDoList' |> test(%)); // simulated Webpack 'cheap-module-source-map'
|
||
|
});
|
||
|
'should work for custom hook' |> it(%, async () => {
|
||
|
async function test(path, name = 'Component') {
|
||
|
const Component = (path |> require(%))[name];
|
||
|
const hookNames = await (Component |> getHookNamesForComponent(%));
|
||
|
hookNames |> expectHookNamesToEqual(%, ['count',
|
||
|
// useState()
|
||
|
'isDarkMode',
|
||
|
// useIsDarkMode()
|
||
|
'isDarkMode',
|
||
|
// useIsDarkMode -> useState()
|
||
|
null // useFoo()
|
||
|
]);
|
||
|
}
|
||
|
await ('./__source__/ComponentWithCustomHook' |> test(%)); // original source (uncompiled)
|
||
|
await ('./__source__/__compiled__/inline/ComponentWithCustomHook' |> test(%)); // inline source map
|
||
|
await ('./__source__/__compiled__/external/ComponentWithCustomHook' |> test(%)); // external source map
|
||
|
await ('./__source__/__compiled__/inline/index-map/ComponentWithCustomHook' |> test(%)); // inline index map source map
|
||
|
await ('./__source__/__compiled__/external/index-map/ComponentWithCustomHook' |> test(%)); // external index map source map
|
||
|
await ('./__source__/__compiled__/bundle' |> test(%, 'ComponentWithCustomHook')); // bundle source map
|
||
|
await ('./__source__/__compiled__/no-columns/ComponentWithCustomHook' |> test(%)); // simulated Webpack 'cheap-module-source-map'
|
||
|
});
|
||
|
'should work when code is using hooks indirectly' |> it(%, async () => {
|
||
|
async function test(path, name = 'Component') {
|
||
|
const Component = (path |> require(%))[name];
|
||
|
const hookNames = await (Component |> getHookNamesForComponent(%));
|
||
|
hookNames |> expectHookNamesToEqual(%, ['count',
|
||
|
// useState()
|
||
|
'darkMode',
|
||
|
// useDarkMode()
|
||
|
'isDarkMode' // useState()
|
||
|
]);
|
||
|
}
|
||
|
await ('./__source__/__compiled__/inline/ComponentUsingHooksIndirectly' |> test(%)); // inline source map
|
||
|
await ('./__source__/__compiled__/external/ComponentUsingHooksIndirectly' |> test(%)); // external source map
|
||
|
await ('./__source__/__compiled__/inline/index-map/ComponentUsingHooksIndirectly' |> test(%)); // inline index map source map
|
||
|
await ('./__source__/__compiled__/external/index-map/ComponentUsingHooksIndirectly' |> test(%)); // external index map source map
|
||
|
await ('./__source__/__compiled__/bundle' |> test(%, 'ComponentUsingHooksIndirectly')); // bundle source map
|
||
|
await ('./__source__/__compiled__/no-columns/ComponentUsingHooksIndirectly' |> test(%)); // simulated Webpack 'cheap-module-source-map'
|
||
|
});
|
||
|
'should work when code is using nested hooks' |> it(%, async () => {
|
||
|
async function test(path, name = 'Component') {
|
||
|
const Component = (path |> require(%))[name];
|
||
|
let InnerComponent;
|
||
|
const hookNames = await (Component |> getHookNamesForComponent(%, {
|
||
|
callback: innerComponent => {
|
||
|
InnerComponent = innerComponent;
|
||
|
}
|
||
|
}));
|
||
|
const innerHookNames = await (InnerComponent |> getHookNamesForComponent(%));
|
||
|
hookNames |> expectHookNamesToEqual(%, ['InnerComponent' // useMemo()
|
||
|
]);
|
||
|
innerHookNames |> expectHookNamesToEqual(%, ['state' // useState()
|
||
|
]);
|
||
|
}
|
||
|
await ('./__source__/__compiled__/inline/ComponentWithNestedHooks' |> test(%)); // inline source map
|
||
|
await ('./__source__/__compiled__/external/ComponentWithNestedHooks' |> test(%)); // external source map
|
||
|
await ('./__source__/__compiled__/inline/index-map/ComponentWithNestedHooks' |> test(%)); // inline index map source map
|
||
|
await ('./__source__/__compiled__/external/index-map/ComponentWithNestedHooks' |> test(%)); // external index map source map
|
||
|
await ('./__source__/__compiled__/bundle' |> test(%, 'ComponentWithNestedHooks')); // bundle source map
|
||
|
await ('./__source__/__compiled__/no-columns/ComponentWithNestedHooks' |> test(%)); // simulated Webpack 'cheap-module-source-map'
|
||
|
});
|
||
|
'should work for external hooks' |> it(%, async () => {
|
||
|
async function test(path, name = 'Component') {
|
||
|
const Component = (path |> require(%))[name];
|
||
|
const hookNames = await (Component |> getHookNamesForComponent(%));
|
||
|
hookNames |> expectHookNamesToEqual(%, ['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 ('./__source__/__compiled__/inline/ComponentWithExternalCustomHooks' |> test(%)); // inline source map
|
||
|
await ('./__source__/__compiled__/external/ComponentWithExternalCustomHooks' |> test(%)); // external source map
|
||
|
await ('./__source__/__compiled__/inline/index-map/ComponentWithExternalCustomHooks' |> test(%)); // inline index map source map
|
||
|
await ('./__source__/__compiled__/external/index-map/ComponentWithExternalCustomHooks' |> test(%)); // external index map source map
|
||
|
await ('./__source__/__compiled__/bundle' |> test(%, 'ComponentWithExternalCustomHooks')); // bundle source map
|
||
|
await ('./__source__/__compiled__/no-columns/ComponentWithExternalCustomHooks' |> test(%)); // 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).
|
||
|
'should work when multiple hooks are on a line' |> it(%, async () => {
|
||
|
async function test(path, name = 'Component') {
|
||
|
const Component = (path |> require(%))[name];
|
||
|
const hookNames = await (Component |> getHookNamesForComponent(%));
|
||
|
hookNames |> expectHookNamesToEqual(%, ['a',
|
||
|
// useContext()
|
||
|
'b',
|
||
|
// useContext()
|
||
|
'c',
|
||
|
// useContext()
|
||
|
'd' // useContext()
|
||
|
]);
|
||
|
}
|
||
|
await ('./__source__/__compiled__/inline/ComponentWithMultipleHooksPerLine' |> test(%)); // inline source map
|
||
|
await ('./__source__/__compiled__/external/ComponentWithMultipleHooksPerLine' |> test(%)); // external source map
|
||
|
await ('./__source__/__compiled__/inline/index-map/ComponentWithMultipleHooksPerLine' |> test(%)); // inline index map source map
|
||
|
await ('./__source__/__compiled__/external/index-map/ComponentWithMultipleHooksPerLine' |> test(%)); // external index map source map
|
||
|
await ('./__source__/__compiled__/bundle' |> test(%, 'ComponentWithMultipleHooksPerLine')); // bundle source map
|
||
|
|
||
|
async function noColumnTest(path, name = 'Component') {
|
||
|
const Component = (path |> require(%))[name];
|
||
|
const hookNames = await (Component |> getHookNamesForComponent(%));
|
||
|
hookNames |> expectHookNamesToEqual(%, ['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 ('./__source__/__compiled__/no-columns/ComponentWithMultipleHooksPerLine' |> noColumnTest(%)); // simulated Webpack 'cheap-module-source-map'
|
||
|
});
|
||
|
'should work for inline requires' |> xit(%, async () => {
|
||
|
async function test(path, name = 'Component') {
|
||
|
const Component = (path |> require(%))[name];
|
||
|
const hookNames = await (Component |> getHookNamesForComponent(%));
|
||
|
hookNames |> expectHookNamesToEqual(%, ['count' // useState()
|
||
|
]);
|
||
|
}
|
||
|
await ('./__source__/InlineRequire' |> test(%)); // original source (uncompiled)
|
||
|
await ('./__source__/__compiled__/inline/InlineRequire' |> test(%)); // inline source map
|
||
|
await ('./__source__/__compiled__/external/InlineRequire' |> test(%)); // external source map
|
||
|
await ('./__source__/__compiled__/inline/index-map/InlineRequire' |> test(%)); // inline index map source map
|
||
|
await ('./__source__/__compiled__/external/index-map/InlineRequire' |> test(%)); // external index map source map
|
||
|
await ('./__source__/__compiled__/bundle' |> test(%, 'InlineRequire')); // bundle source map
|
||
|
await ('./__source__/__compiled__/no-columns/InlineRequire' |> test(%)); // simulated Webpack 'cheap-module-source-map'
|
||
|
});
|
||
|
'should support sources that contain the string "sourceMappingURL="' |> it(%, async () => {
|
||
|
async function test(path, name = 'Component') {
|
||
|
const Component = (path |> require(%))[name];
|
||
|
const hookNames = await (Component |> getHookNamesForComponent(%));
|
||
|
hookNames |> expectHookNamesToEqual(%, ['count' // useState()
|
||
|
]);
|
||
|
}
|
||
|
|
||
|
// We expect the inline sourceMappingURL to be invalid in this case; mute the warning.
|
||
|
console.warn = () => {};
|
||
|
await ('./__source__/ContainingStringSourceMappingURL' |> test(%)); // original source (uncompiled)
|
||
|
await ('./__source__/__compiled__/inline/ContainingStringSourceMappingURL' |> test(%)); // inline source map
|
||
|
await ('./__source__/__compiled__/external/ContainingStringSourceMappingURL' |> test(%)); // external source map
|
||
|
await ('./__source__/__compiled__/inline/index-map/ContainingStringSourceMappingURL' |> test(%)); // inline index map source map
|
||
|
await ('./__source__/__compiled__/external/index-map/ContainingStringSourceMappingURL' |> test(%)); // external index map source map
|
||
|
await ('./__source__/__compiled__/bundle' |> test(%, 'ContainingStringSourceMappingURL')); // bundle source map
|
||
|
await ('./__source__/__compiled__/no-columns/ContainingStringSourceMappingURL' |> test(%)); // simulated Webpack 'cheap-module-source-map'
|
||
|
});
|
||
|
});
|
||
|
'extended source maps' |> describe(%, () => {
|
||
|
(() => {
|
||
|
const babelParser = '@babel/parser' |> require(%);
|
||
|
const generateHookMapModule = '../generateHookMap' |> require(%);
|
||
|
babelParser |> jest.spyOn(%, 'parse');
|
||
|
generateHookMapModule |> jest.spyOn(%, 'decodeHookMap');
|
||
|
}) |> beforeEach(%);
|
||
|
'should work for simple components' |> it(%, async () => {
|
||
|
async function test(path, name = 'Component') {
|
||
|
const Component = (path |> require(%))[name];
|
||
|
const hookNames = await (Component |> getHookNamesForComponent(%));
|
||
|
hookNames |> expectHookNamesToEqual(%, ['count' // useState
|
||
|
]);
|
||
|
0 |> (('@babel/parser' |> require(%)).parse |> expect(%)).toHaveBeenCalledTimes(%);
|
||
|
(('../generateHookMap' |> require(%)).decodeHookMap |> expect(%)).toHaveBeenCalled();
|
||
|
}
|
||
|
await ('./__source__/__compiled__/inline/fb-sources-extended/Example' |> test(%)); // x_facebook_sources extended inline source map
|
||
|
await ('./__source__/__compiled__/external/fb-sources-extended/Example' |> test(%)); // x_facebook_sources extended external source map
|
||
|
await ('./__source__/__compiled__/inline/react-sources-extended/Example' |> test(%)); // x_react_sources extended inline source map
|
||
|
await ('./__source__/__compiled__/external/react-sources-extended/Example' |> test(%)); // x_react_sources extended external source map
|
||
|
|
||
|
// Using index map format for source maps
|
||
|
await ('./__source__/__compiled__/inline/fb-sources-extended/index-map/Example' |> test(%)); // x_facebook_sources extended inline index map source map
|
||
|
await ('./__source__/__compiled__/external/fb-sources-extended/index-map/Example' |> test(%)); // x_facebook_sources extended external index map source map
|
||
|
await ('./__source__/__compiled__/inline/react-sources-extended/index-map/Example' |> test(%)); // x_react_sources extended inline index map source map
|
||
|
await ('./__source__/__compiled__/external/react-sources-extended/index-map/Example' |> test(%)); // x_react_sources extended external index map source map
|
||
|
|
||
|
// TODO test no-columns and bundle cases with extended source maps
|
||
|
});
|
||
|
'should work with more complex files and components' |> it(%, async () => {
|
||
|
async function test(path, name = undefined) {
|
||
|
const components = name != null ? (path |> require(%))[name] : path |> require(%);
|
||
|
let hookNames = await (components.List |> getHookNamesForComponent(%));
|
||
|
hookNames |> expectHookNamesToEqual(%, ['newItemText',
|
||
|
// useState
|
||
|
'items',
|
||
|
// useState
|
||
|
'uid',
|
||
|
// useState
|
||
|
'handleClick',
|
||
|
// useCallback
|
||
|
'handleKeyPress',
|
||
|
// useCallback
|
||
|
'handleChange',
|
||
|
// useCallback
|
||
|
'removeItem',
|
||
|
// useCallback
|
||
|
'toggleItem' // useCallback
|
||
|
]);
|
||
|
hookNames = await (components.ListItem |> getHookNamesForComponent(%, {
|
||
|
item: {}
|
||
|
}));
|
||
|
hookNames |> expectHookNamesToEqual(%, ['handleDelete',
|
||
|
// useCallback
|
||
|
'handleToggle' // useCallback
|
||
|
]);
|
||
|
0 |> (('@babel/parser' |> require(%)).parse |> expect(%)).toHaveBeenCalledTimes(%);
|
||
|
(('../generateHookMap' |> require(%)).decodeHookMap |> expect(%)).toHaveBeenCalled();
|
||
|
}
|
||
|
await ('./__source__/__compiled__/inline/fb-sources-extended/ToDoList' |> test(%)); // x_facebook_sources extended inline source map
|
||
|
await ('./__source__/__compiled__/external/fb-sources-extended/ToDoList' |> test(%)); // x_facebook_sources extended external source map
|
||
|
await ('./__source__/__compiled__/inline/react-sources-extended/ToDoList' |> test(%)); // x_react_sources extended inline source map
|
||
|
await ('./__source__/__compiled__/external/react-sources-extended/ToDoList' |> test(%)); // x_react_sources extended external source map
|
||
|
|
||
|
// Using index map format for source maps
|
||
|
await ('./__source__/__compiled__/inline/fb-sources-extended/index-map/ToDoList' |> test(%)); // x_facebook_sources extended inline index map source map
|
||
|
await ('./__source__/__compiled__/external/fb-sources-extended/index-map/ToDoList' |> test(%)); // x_facebook_sources extended external index map source map
|
||
|
await ('./__source__/__compiled__/inline/react-sources-extended/index-map/ToDoList' |> test(%)); // x_react_sources extended inline index map source map
|
||
|
await ('./__source__/__compiled__/external/react-sources-extended/index-map/ToDoList' |> test(%)); // x_react_sources extended external index map source map
|
||
|
|
||
|
// TODO test no-columns and bundle cases with extended source maps
|
||
|
});
|
||
|
'should work for custom hook' |> it(%, async () => {
|
||
|
async function test(path, name = 'Component') {
|
||
|
const Component = (path |> require(%))[name];
|
||
|
const hookNames = await (Component |> getHookNamesForComponent(%));
|
||
|
hookNames |> expectHookNamesToEqual(%, ['count',
|
||
|
// useState()
|
||
|
'isDarkMode',
|
||
|
// useIsDarkMode()
|
||
|
'isDarkMode',
|
||
|
// useIsDarkMode -> useState()
|
||
|
null // isFoo()
|
||
|
]);
|
||
|
0 |> (('@babel/parser' |> require(%)).parse |> expect(%)).toHaveBeenCalledTimes(%);
|
||
|
(('../generateHookMap' |> require(%)).decodeHookMap |> expect(%)).toHaveBeenCalled();
|
||
|
}
|
||
|
await ('./__source__/__compiled__/inline/fb-sources-extended/ComponentWithCustomHook' |> test(%)); // x_facebook_sources extended inline source map
|
||
|
await ('./__source__/__compiled__/external/fb-sources-extended/ComponentWithCustomHook' |> test(%)); // x_facebook_sources extended external source map
|
||
|
await ('./__source__/__compiled__/inline/react-sources-extended/ComponentWithCustomHook' |> test(%)); // x_react_sources extended inline source map
|
||
|
await ('./__source__/__compiled__/external/react-sources-extended/ComponentWithCustomHook' |> test(%)); // x_react_sources extended external source map
|
||
|
|
||
|
// Using index map format for source maps
|
||
|
await ('./__source__/__compiled__/inline/fb-sources-extended/index-map/ComponentWithCustomHook' |> test(%)); // x_facebook_sources extended inline index map source map
|
||
|
await ('./__source__/__compiled__/external/fb-sources-extended/index-map/ComponentWithCustomHook' |> test(%)); // x_facebook_sources extended external index map source map
|
||
|
await ('./__source__/__compiled__/inline/react-sources-extended/index-map/ComponentWithCustomHook' |> test(%)); // x_react_sources extended inline index map source map
|
||
|
await ('./__source__/__compiled__/external/react-sources-extended/index-map/ComponentWithCustomHook' |> test(%)); // x_react_sources extended external index map source map
|
||
|
|
||
|
// TODO test no-columns and bundle cases with extended source maps
|
||
|
});
|
||
|
'should work when code is using hooks indirectly' |> it(%, async () => {
|
||
|
async function test(path, name = 'Component') {
|
||
|
const Component = (path |> require(%))[name];
|
||
|
const hookNames = await (Component |> getHookNamesForComponent(%));
|
||
|
hookNames |> expectHookNamesToEqual(%, ['count',
|
||
|
// useState()
|
||
|
'darkMode',
|
||
|
// useDarkMode()
|
||
|
'isDarkMode' // useState()
|
||
|
]);
|
||
|
0 |> (('@babel/parser' |> require(%)).parse |> expect(%)).toHaveBeenCalledTimes(%);
|
||
|
(('../generateHookMap' |> require(%)).decodeHookMap |> expect(%)).toHaveBeenCalled();
|
||
|
}
|
||
|
await ('./__source__/__compiled__/inline/fb-sources-extended/ComponentUsingHooksIndirectly' |> test(%)); // x_facebook_sources extended inline source map
|
||
|
await ('./__source__/__compiled__/external/fb-sources-extended/ComponentUsingHooksIndirectly' |> test(%)); // x_facebook_sources extended external source map
|
||
|
await ('./__source__/__compiled__/inline/react-sources-extended/ComponentUsingHooksIndirectly' |> test(%)); // x_react_sources extended inline source map
|
||
|
await ('./__source__/__compiled__/external/react-sources-extended/ComponentUsingHooksIndirectly' |> test(%)); // x_react_sources extended external source map
|
||
|
|
||
|
// Using index map format for source maps
|
||
|
await ('./__source__/__compiled__/inline/fb-sources-extended/index-map/ComponentUsingHooksIndirectly' |> test(%)); // x_facebook_sources extended inline index map source map
|
||
|
await ('./__source__/__compiled__/external/fb-sources-extended/index-map/ComponentUsingHooksIndirectly' |> test(%)); // x_facebook_sources extended external index map source map
|
||
|
await ('./__source__/__compiled__/inline/react-sources-extended/index-map/ComponentUsingHooksIndirectly' |> test(%)); // x_react_sources extended inline index map source map
|
||
|
await ('./__source__/__compiled__/external/react-sources-extended/index-map/ComponentUsingHooksIndirectly' |> test(%)); // x_react_sources extended external index map source map
|
||
|
|
||
|
// TODO test no-columns and bundle cases with extended source maps
|
||
|
});
|
||
|
'should work when code is using nested hooks' |> it(%, async () => {
|
||
|
async function test(path, name = 'Component') {
|
||
|
const Component = (path |> require(%))[name];
|
||
|
let InnerComponent;
|
||
|
const hookNames = await (Component |> getHookNamesForComponent(%, {
|
||
|
callback: innerComponent => {
|
||
|
InnerComponent = innerComponent;
|
||
|
}
|
||
|
}));
|
||
|
const innerHookNames = await (InnerComponent |> getHookNamesForComponent(%));
|
||
|
hookNames |> expectHookNamesToEqual(%, ['InnerComponent' // useMemo()
|
||
|
]);
|
||
|
innerHookNames |> expectHookNamesToEqual(%, ['state' // useState()
|
||
|
]);
|
||
|
0 |> (('@babel/parser' |> require(%)).parse |> expect(%)).toHaveBeenCalledTimes(%);
|
||
|
(('../generateHookMap' |> require(%)).decodeHookMap |> expect(%)).toHaveBeenCalled();
|
||
|
}
|
||
|
await ('./__source__/__compiled__/inline/fb-sources-extended/ComponentWithNestedHooks' |> test(%)); // x_facebook_sources extended inline source map
|
||
|
await ('./__source__/__compiled__/external/fb-sources-extended/ComponentWithNestedHooks' |> test(%)); // x_facebook_sources extended external source map
|
||
|
await ('./__source__/__compiled__/inline/react-sources-extended/ComponentWithNestedHooks' |> test(%)); // x_react_sources extended inline source map
|
||
|
await ('./__source__/__compiled__/external/react-sources-extended/ComponentWithNestedHooks' |> test(%)); // x_react_sources extended external source map
|
||
|
|
||
|
// Using index map format for source maps
|
||
|
await ('./__source__/__compiled__/inline/fb-sources-extended/index-map/ComponentWithNestedHooks' |> test(%)); // x_facebook_sources extended inline index map source map
|
||
|
await ('./__source__/__compiled__/external/fb-sources-extended/index-map/ComponentWithNestedHooks' |> test(%)); // x_facebook_sources extended external index map source map
|
||
|
await ('./__source__/__compiled__/inline/react-sources-extended/index-map/ComponentWithNestedHooks' |> test(%)); // x_react_sources extended inline index map source map
|
||
|
await ('./__source__/__compiled__/external/react-sources-extended/index-map/ComponentWithNestedHooks' |> test(%)); // x_react_sources extended external index map source map
|
||
|
|
||
|
// TODO test no-columns and bundle cases with extended source maps
|
||
|
});
|
||
|
'should work for external hooks' |> it(%, async () => {
|
||
|
async function test(path, name = 'Component') {
|
||
|
const Component = (path |> require(%))[name];
|
||
|
const hookNames = await (Component |> getHookNamesForComponent(%));
|
||
|
hookNames |> expectHookNamesToEqual(%, ['theme',
|
||
|
// useTheme()
|
||
|
'theme' // useContext()
|
||
|
]);
|
||
|
0 |> (('@babel/parser' |> require(%)).parse |> expect(%)).toHaveBeenCalledTimes(%);
|
||
|
(('../generateHookMap' |> require(%)).decodeHookMap |> expect(%)).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 ('./__source__/__compiled__/inline/fb-sources-extended/ComponentWithExternalCustomHooks' |> test(%)); // x_facebook_sources extended inline source map
|
||
|
await ('./__source__/__compiled__/external/fb-sources-extended/ComponentWithExternalCustomHooks' |> test(%)); // x_facebook_sources extended external source map
|
||
|
await ('./__source__/__compiled__/inline/react-sources-extended/ComponentWithExternalCustomHooks' |> test(%)); // x_react_sources extended inline source map
|
||
|
await ('./__source__/__compiled__/external/react-sources-extended/ComponentWithExternalCustomHooks' |> test(%)); // x_react_sources extended external source map
|
||
|
|
||
|
// Using index map format for source maps
|
||
|
await ('./__source__/__compiled__/inline/fb-sources-extended/index-map/ComponentWithExternalCustomHooks' |> test(%)); // x_facebook_sources extended inline index map source map
|
||
|
await ('./__source__/__compiled__/external/fb-sources-extended/index-map/ComponentWithExternalCustomHooks' |> test(%)); // x_facebook_sources extended external index map source map
|
||
|
await ('./__source__/__compiled__/inline/react-sources-extended/index-map/ComponentWithExternalCustomHooks' |> test(%)); // x_react_sources extended inline index map source map
|
||
|
await ('./__source__/__compiled__/external/react-sources-extended/index-map/ComponentWithExternalCustomHooks' |> test(%)); // 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).
|
||
|
'should work when multiple hooks are on a line' |> it(%, async () => {
|
||
|
async function test(path, name = 'Component') {
|
||
|
const Component = (path |> require(%))[name];
|
||
|
const hookNames = await (Component |> getHookNamesForComponent(%));
|
||
|
hookNames |> expectHookNamesToEqual(%, ['a',
|
||
|
// useContext()
|
||
|
'b',
|
||
|
// useContext()
|
||
|
'c',
|
||
|
// useContext()
|
||
|
'd' // useContext()
|
||
|
]);
|
||
|
0 |> (('@babel/parser' |> require(%)).parse |> expect(%)).toHaveBeenCalledTimes(%);
|
||
|
(('../generateHookMap' |> require(%)).decodeHookMap |> expect(%)).toHaveBeenCalled();
|
||
|
}
|
||
|
await ('./__source__/__compiled__/inline/fb-sources-extended/ComponentWithMultipleHooksPerLine' |> test(%)); // x_facebook_sources extended inline source map
|
||
|
await ('./__source__/__compiled__/external/fb-sources-extended/ComponentWithMultipleHooksPerLine' |> test(%)); // x_facebook_sources extended external source map
|
||
|
await ('./__source__/__compiled__/inline/react-sources-extended/ComponentWithMultipleHooksPerLine' |> test(%)); // x_react_sources extended inline source map
|
||
|
await ('./__source__/__compiled__/external/react-sources-extended/ComponentWithMultipleHooksPerLine' |> test(%)); // x_react_sources extended external source map
|
||
|
|
||
|
// Using index map format for source maps
|
||
|
await ('./__source__/__compiled__/inline/fb-sources-extended/index-map/ComponentWithMultipleHooksPerLine' |> test(%)); // x_facebook_sources extended inline index map source map
|
||
|
await ('./__source__/__compiled__/external/fb-sources-extended/index-map/ComponentWithMultipleHooksPerLine' |> test(%)); // x_facebook_sources extended external index map source map
|
||
|
await ('./__source__/__compiled__/inline/react-sources-extended/index-map/ComponentWithMultipleHooksPerLine' |> test(%)); // x_react_sources extended inline index map source map
|
||
|
await ('./__source__/__compiled__/external/react-sources-extended/index-map/ComponentWithMultipleHooksPerLine' |> test(%)); // x_react_sources extended external index map source map
|
||
|
|
||
|
// TODO test no-columns and bundle cases with extended source maps
|
||
|
});
|
||
|
'should work for inline requires' |> xit(%, async () => {
|
||
|
async function test(path, name = 'Component') {
|
||
|
const Component = (path |> require(%))[name];
|
||
|
const hookNames = await (Component |> getHookNamesForComponent(%));
|
||
|
hookNames |> expectHookNamesToEqual(%, ['count' // useState()
|
||
|
]);
|
||
|
0 |> (('@babel/parser' |> require(%)).parse |> expect(%)).toHaveBeenCalledTimes(%);
|
||
|
(('../generateHookMap' |> require(%)).decodeHookMap |> expect(%)).toHaveBeenCalled();
|
||
|
}
|
||
|
await ('./__source__/__compiled__/inline/fb-sources-extended/InlineRequire' |> test(%)); // x_facebook_sources extended inline source map
|
||
|
await ('./__source__/__compiled__/external/fb-sources-extended/InlineRequire' |> test(%)); // x_facebook_sources extended external source map
|
||
|
await ('./__source__/__compiled__/inline/react-sources-extended/InlineRequire' |> test(%)); // x_react_sources extended inline source map
|
||
|
await ('./__source__/__compiled__/external/react-sources-extended/InlineRequire' |> test(%)); // x_react_sources extended external source map
|
||
|
|
||
|
// Using index map format for source maps
|
||
|
await ('./__source__/__compiled__/inline/fb-sources-extended/index-map/InlineRequire' |> test(%)); // x_facebook_sources extended inline index map source map
|
||
|
await ('./__source__/__compiled__/external/fb-sources-extended/index-map/InlineRequire' |> test(%)); // x_facebook_sources extended external index map source map
|
||
|
await ('./__source__/__compiled__/inline/react-sources-extended/index-map/InlineRequire' |> test(%)); // x_react_sources extended inline index map source map
|
||
|
await ('./__source__/__compiled__/external/react-sources-extended/index-map/InlineRequire' |> test(%)); // x_react_sources extended external index map source map
|
||
|
|
||
|
// TODO test no-columns and bundle cases with extended source maps
|
||
|
});
|
||
|
'should support sources that contain the string "sourceMappingURL="' |> it(%, async () => {
|
||
|
async function test(path, name = 'Component') {
|
||
|
const Component = (path |> require(%))[name];
|
||
|
const hookNames = await (Component |> getHookNamesForComponent(%));
|
||
|
hookNames |> expectHookNamesToEqual(%, ['count' // useState()
|
||
|
]);
|
||
|
0 |> (('@babel/parser' |> require(%)).parse |> expect(%)).toHaveBeenCalledTimes(%);
|
||
|
(('../generateHookMap' |> require(%)).decodeHookMap |> expect(%)).toHaveBeenCalled();
|
||
|
}
|
||
|
|
||
|
// We expect the inline sourceMappingURL to be invalid in this case; mute the warning.
|
||
|
console.warn = () => {};
|
||
|
await ('./__source__/__compiled__/inline/fb-sources-extended/ContainingStringSourceMappingURL' |> test(%)); // x_facebook_sources extended inline source map
|
||
|
await ('./__source__/__compiled__/external/fb-sources-extended/ContainingStringSourceMappingURL' |> test(%)); // x_facebook_sources extended external source map
|
||
|
await ('./__source__/__compiled__/inline/react-sources-extended/ContainingStringSourceMappingURL' |> test(%)); // x_react_sources extended inline source map
|
||
|
await ('./__source__/__compiled__/external/react-sources-extended/ContainingStringSourceMappingURL' |> test(%)); // x_react_sources extended external source map
|
||
|
|
||
|
// Using index map format for source maps
|
||
|
await ('./__source__/__compiled__/inline/fb-sources-extended/index-map/ContainingStringSourceMappingURL' |> test(%)); // x_facebook_sources extended inline index map source map
|
||
|
await ('./__source__/__compiled__/external/fb-sources-extended/index-map/ContainingStringSourceMappingURL' |> test(%)); // x_facebook_sources extended external index map source map
|
||
|
await ('./__source__/__compiled__/inline/react-sources-extended/index-map/ContainingStringSourceMappingURL' |> test(%)); // x_react_sources extended inline index map source map
|
||
|
await ('./__source__/__compiled__/external/react-sources-extended/index-map/ContainingStringSourceMappingURL' |> test(%)); // x_react_sources extended external index map source map
|
||
|
|
||
|
// TODO test no-columns and bundle cases with extended source maps
|
||
|
});
|
||
|
});
|
||
|
});
|
||
|
'parseHookNames worker' |> describe(%, () => {
|
||
|
let inspectHooks;
|
||
|
let parseHookNames;
|
||
|
let workerizedParseSourceAndMetadataMock;
|
||
|
(() => {
|
||
|
window.Worker = undefined;
|
||
|
workerizedParseSourceAndMetadataMock = jest.fn();
|
||
|
initFetchMock();
|
||
|
'../parseHookNames/parseSourceAndMetadata.worker.js' |> jest.mock(%, () => {
|
||
|
return {
|
||
|
__esModule: true,
|
||
|
default: () => ({
|
||
|
parseSourceAndMetadata: workerizedParseSourceAndMetadataMock
|
||
|
})
|
||
|
};
|
||
|
});
|
||
|
inspectHooks = ('react-debug-tools/src/ReactDebugHooks' |> require(%)).inspectHooks;
|
||
|
parseHookNames = ('../parseHookNames' |> require(%)).parseHookNames;
|
||
|
}) |> beforeEach(%);
|
||
|
async function getHookNamesForComponent(Component, props = {}) {
|
||
|
const hooksTree = inspectHooks(Component, props, undefined);
|
||
|
const hookNames = await (hooksTree |> parseHookNames(%));
|
||
|
return hookNames;
|
||
|
}
|
||
|
'should use worker' |> it(%, async () => {
|
||
|
const Component = ('./__source__/__untransformed__/ComponentWithUseState' |> require(%)).Component;
|
||
|
window.Worker = true;
|
||
|
|
||
|
// Reset module so mocked worker instance can be updated.
|
||
|
jest.resetModules();
|
||
|
parseHookNames = ('../parseHookNames' |> require(%)).parseHookNames;
|
||
|
await (Component |> getHookNamesForComponent(%));
|
||
|
1 |> (workerizedParseSourceAndMetadataMock |> expect(%)).toHaveBeenCalledTimes(%);
|
||
|
});
|
||
|
});
|