From 2ec6d243612c891d74fb6d3d0f2af453cb4d1106 Mon Sep 17 00:00:00 2001 From: chlorine Date: Fri, 2 Dec 2022 22:15:42 +0800 Subject: [PATCH] fix: add global path in require.resolve.paths (#13633) * fix: add global path in require.resolve.paths * test: update snapshot * test: update snapshot * test: add builtin module test case * refactor: grap global paths only once * test: update cases and snapshot * chore: add path join and update test case * test: update test cases * clarify and add changelog Co-authored-by: Tim Seckinger --- CHANGELOG.md | 2 + .../moduleNameMapper.test.ts.snap | 4 +- .../src/__tests__/resolve.test.ts | 47 +++++++++++++++++++ packages/jest-resolve/src/nodeModulesPaths.ts | 14 ++++++ packages/jest-resolve/src/resolver.ts | 10 +++- .../__tests__/runtime_require_module.test.js | 9 +++- packages/jest-runtime/src/index.ts | 20 ++++++-- 7 files changed, 97 insertions(+), 9 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 413fb973a41b..07f7984aa970 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,8 @@ ### Fixes +- `[jest-resolve]` add global paths to `require.resolve.paths` ([#13633](https://github.com/facebook/jest/pull/13633)) + ### Chore & Maintenance - `[@jest/fake-timers]` Update `@sinonjs/fake-timers` ([#13612](https://github.com/facebook/jest/pull/13612)) diff --git a/e2e/__tests__/__snapshots__/moduleNameMapper.test.ts.snap b/e2e/__tests__/__snapshots__/moduleNameMapper.test.ts.snap index 5e4d71fbf3e9..9a78de751fe5 100644 --- a/e2e/__tests__/__snapshots__/moduleNameMapper.test.ts.snap +++ b/e2e/__tests__/__snapshots__/moduleNameMapper.test.ts.snap @@ -41,7 +41,7 @@ exports[`moduleNameMapper wrong array configuration 1`] = ` 12 | module.exports = () => 'test'; 13 | - at createNoMappedModuleFoundError (../../packages/jest-resolve/build/resolver.js:752:17) + at createNoMappedModuleFoundError (../../packages/jest-resolve/build/resolver.js:758:17) at Object.require (index.js:10:1) at Object.require (__tests__/index.js:10:20)" `; @@ -71,7 +71,7 @@ exports[`moduleNameMapper wrong configuration 1`] = ` 12 | module.exports = () => 'test'; 13 | - at createNoMappedModuleFoundError (../../packages/jest-resolve/build/resolver.js:752:17) + at createNoMappedModuleFoundError (../../packages/jest-resolve/build/resolver.js:758:17) at Object.require (index.js:10:1) at Object.require (__tests__/index.js:10:20)" `; diff --git a/packages/jest-resolve/src/__tests__/resolve.test.ts b/packages/jest-resolve/src/__tests__/resolve.test.ts index 2b85fe4aaa7c..bad6fe4a3cd5 100644 --- a/packages/jest-resolve/src/__tests__/resolve.test.ts +++ b/packages/jest-resolve/src/__tests__/resolve.test.ts @@ -707,3 +707,50 @@ describe('Resolver.getModulePaths() -> nodeModulesPaths()', () => { expect(dirs_actual).toEqual(expect.arrayContaining(dirs_expected)); }); }); + +describe('Resolver.getGlobalPaths()', () => { + const _path = path; + let moduleMap: IModuleMap; + beforeEach(() => { + moduleMap = ModuleMap.create('/'); + }); + + it('return global paths with npm package', () => { + jest.doMock('path', () => _path.posix); + const resolver = new Resolver(moduleMap, {} as ResolverConfig); + const globalPaths = resolver.getGlobalPaths('jest'); + globalPaths.forEach(globalPath => + expect(require.resolve.paths('jest')).toContain(globalPath), + ); + }); + + it('return empty array with builtin module', () => { + jest.doMock('path', () => _path.posix); + const resolver = new Resolver(moduleMap, {} as ResolverConfig); + const globalPaths = resolver.getGlobalPaths('fs'); + expect(globalPaths).toStrictEqual([]); + }); + + it('return global paths with absolute path', () => { + jest.doMock('path', () => _path.posix); + const resolver = new Resolver(moduleMap, {} as ResolverConfig); + const globalPaths = resolver.getGlobalPaths('/'); + globalPaths.forEach(globalPath => + expect(require.resolve.paths('/')).toContain(globalPath), + ); + }); + + it('return empty array with relative path', () => { + jest.doMock('path', () => _path.posix); + const resolver = new Resolver(moduleMap, {} as ResolverConfig); + const globalPaths = resolver.getGlobalPaths('./'); + expect(globalPaths).toStrictEqual([]); + }); + + it('return empty array without module name', () => { + jest.doMock('path', () => _path.posix); + const resolver = new Resolver(moduleMap, {} as ResolverConfig); + const globalPaths = resolver.getGlobalPaths(); + expect(globalPaths).toStrictEqual([]); + }); +}); diff --git a/packages/jest-resolve/src/nodeModulesPaths.ts b/packages/jest-resolve/src/nodeModulesPaths.ts index a2e91914de66..1eb73efa678f 100644 --- a/packages/jest-resolve/src/nodeModulesPaths.ts +++ b/packages/jest-resolve/src/nodeModulesPaths.ts @@ -70,3 +70,17 @@ export default function nodeModulesPaths( return options.paths ? dirs.concat(options.paths) : dirs; } + +function findGlobalPaths(): Array { + const {root} = path.parse(process.cwd()); + const globalPath = path.join(root, 'node_modules'); + const resolvePaths = require.resolve.paths('/'); + + if (resolvePaths) { + // the global paths start one after the root node_modules + const rootIndex = resolvePaths.indexOf(globalPath); + return rootIndex > -1 ? resolvePaths.slice(rootIndex + 1) : []; + } + return []; +} +export const GlobalPaths = findGlobalPaths(); diff --git a/packages/jest-resolve/src/resolver.ts b/packages/jest-resolve/src/resolver.ts index 98dbfc916ad7..9318b3b4059c 100644 --- a/packages/jest-resolve/src/resolver.ts +++ b/packages/jest-resolve/src/resolver.ts @@ -20,7 +20,7 @@ import defaultResolver, { } from './defaultResolver'; import {clearFsCache} from './fileWalkers'; import isBuiltinModule from './isBuiltinModule'; -import nodeModulesPaths from './nodeModulesPaths'; +import nodeModulesPaths, {GlobalPaths} from './nodeModulesPaths'; import shouldLoadAsEsm, {clearCachedLookups} from './shouldLoadAsEsm'; import type {ResolverConfig} from './types'; @@ -526,6 +526,14 @@ export default class Resolver { return paths; } + getGlobalPaths(moduleName?: string): Array { + if (!moduleName || moduleName[0] === '.' || this.isCoreModule(moduleName)) { + return []; + } + + return GlobalPaths; + } + getModuleID( virtualMocks: Map, from: string, diff --git a/packages/jest-runtime/src/__tests__/runtime_require_module.test.js b/packages/jest-runtime/src/__tests__/runtime_require_module.test.js index c2614d7563da..e28b2fcdc5e2 100644 --- a/packages/jest-runtime/src/__tests__/runtime_require_module.test.js +++ b/packages/jest-runtime/src/__tests__/runtime_require_module.test.js @@ -132,8 +132,13 @@ describe('Runtime requireModule', () => { 'RegularModule', ); expect(exports.paths.length).toBeGreaterThan(0); - exports.paths.forEach(path => { - expect(moduleDirectories.some(dir => path.endsWith(dir))).toBe(true); + const root = path.parse(process.cwd()).root; + const globalPath = path.join(root, 'node_modules'); + const rootIndex = exports.paths.findIndex(path => path === globalPath); + exports.paths.forEach((path, index) => { + if (index <= rootIndex) { + expect(moduleDirectories.some(dir => path.endsWith(dir))).toBe(true); + } }); }); diff --git a/packages/jest-runtime/src/index.ts b/packages/jest-runtime/src/index.ts index 5dcf6d7ce6c8..c278d77052c5 100644 --- a/packages/jest-runtime/src/index.ts +++ b/packages/jest-runtime/src/index.ts @@ -1066,7 +1066,13 @@ export default class Runtime { } else { // Only include the fromPath if a moduleName is given. Else treat as root. const fromPath = moduleName ? from : null; - this._execModule(localModule, options, moduleRegistry, fromPath); + this._execModule( + localModule, + options, + moduleRegistry, + fromPath, + moduleName, + ); } localModule.loaded = true; } @@ -1398,6 +1404,7 @@ export default class Runtime { } private _requireResolvePaths(from: string, moduleName?: string) { + const fromDir = path.resolve(from, '..'); if (moduleName == null) { throw new Error( 'The first argument to require.resolve.paths must be a string. Received null or undefined.', @@ -1410,12 +1417,14 @@ export default class Runtime { } if (moduleName[0] === '.') { - return [path.resolve(from, '..')]; + return [fromDir]; } if (this._resolver.isCoreModule(moduleName)) { return null; } - return this._resolver.getModulePaths(path.resolve(from, '..')); + const modulePaths = this._resolver.getModulePaths(fromDir); + const globalPaths = this._resolver.getGlobalPaths(moduleName); + return [...modulePaths, ...globalPaths]; } private _execModule( @@ -1423,6 +1432,7 @@ export default class Runtime { options: InternalModuleOptions | undefined, moduleRegistry: ModuleRegistry, from: string | null, + moduleName?: string, ) { if (this.isTornDown) { this._logFormattedReferenceError( @@ -1454,8 +1464,10 @@ export default class Runtime { return moduleRegistry.get(key) || null; }, }); + const modulePaths = this._resolver.getModulePaths(module.path); + const globalPaths = this._resolver.getGlobalPaths(moduleName); + module.paths = [...modulePaths, ...globalPaths]; - module.paths = this._resolver.getModulePaths(module.path); Object.defineProperty(module, 'require', { value: this._createRequireImplementation(module, options), });