diff --git a/CHANGELOG.md b/CHANGELOG.md index 1be550ab902c..b706156bc4b3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,8 @@ ### Fixes +- `[jest-runtime]` Ensure absolute paths can be resolved within test modules ([11943](https://github.com/facebook/jest/pull/11943)) + ### Chore & Maintenance ### Performance diff --git a/packages/jest-runtime/src/__tests__/runtime_require_resolve.test.ts b/packages/jest-runtime/src/__tests__/runtime_require_resolve.test.ts index 493fc115501e..cc22e0a1eedf 100644 --- a/packages/jest-runtime/src/__tests__/runtime_require_resolve.test.ts +++ b/packages/jest-runtime/src/__tests__/runtime_require_resolve.test.ts @@ -6,6 +6,9 @@ * */ +import os from 'os'; +import path from 'path'; +import {promises as fs} from 'graceful-fs'; import type {Config} from '@jest/types'; import type Runtime from '..'; import {createOutsideJestVmPath} from '../helpers'; @@ -15,6 +18,9 @@ let createRuntime: ( config?: Config.InitialOptions, ) => Promise; +const getTmpDir = async () => + await fs.mkdtemp(path.join(os.tmpdir(), 'jest-resolve-test-')); + describe('Runtime require.resolve', () => { beforeEach(() => { createRuntime = require('createRuntime'); @@ -29,6 +35,47 @@ describe('Runtime require.resolve', () => { expect(resolved).toEqual(require.resolve('./test_root/resolve_self.js')); }); + it('resolves an absolute module path', async () => { + const absoluteFilePath = path.join(await getTmpDir(), 'test.js'); + await fs.writeFile( + absoluteFilePath, + 'module.exports = require.resolve(__filename);', + 'utf-8', + ); + + const runtime = await createRuntime(__filename); + const resolved = runtime.requireModule( + runtime.__mockRootPath, + absoluteFilePath, + ); + + expect(resolved).toEqual(require.resolve(absoluteFilePath)); + }); + + it('required modules can resolve absolute module paths with no paths entries passed', async () => { + const tmpdir = await getTmpDir(); + const entrypoint = path.join(tmpdir, 'test.js'); + const target = path.join(tmpdir, 'target.js'); + + // we want to test the require.resolve implementation within a + // runtime-required module, so we need to create a module that then resolves + // an absolute path, so we need two files: the entrypoint, and an absolute + // target to require. + await fs.writeFile( + entrypoint, + `module.exports = require.resolve(${JSON.stringify( + target, + )}, {paths: []});`, + 'utf-8', + ); + + await fs.writeFile(target, `module.exports = {}`, 'utf-8'); + + const runtime = await createRuntime(__filename); + const resolved = runtime.requireModule(runtime.__mockRootPath, entrypoint); + expect(resolved).toEqual(require.resolve(target, {paths: []})); + }); + it('resolves a module path with moduleNameMapper', async () => { const runtime = await createRuntime(__filename, { moduleNameMapper: { diff --git a/packages/jest-runtime/src/index.ts b/packages/jest-runtime/src/index.ts index 15b9e03a00a6..ffe5bc4a64aa 100644 --- a/packages/jest-runtime/src/index.ts +++ b/packages/jest-runtime/src/index.ts @@ -1242,28 +1242,39 @@ export default class Runtime { ); } - const {paths} = options; - - if (paths) { - for (const p of paths) { - const absolutePath = path.resolve(from, '..', p); - const module = this._resolver.resolveModuleFromDirIfExists( - absolutePath, - moduleName, - // required to also resolve files without leading './' directly in the path - {conditions: this.cjsConditions, paths: [absolutePath]}, - ); - if (module) { - return module; - } + if (path.isAbsolute(moduleName)) { + const module = this._resolver.resolveModuleFromDirIfExists( + moduleName, + moduleName, + {conditions: this.cjsConditions, paths: []}, + ); + if (module) { + return module; } + } else { + const {paths} = options; + if (paths) { + for (const p of paths) { + const absolutePath = path.resolve(from, '..', p); + const module = this._resolver.resolveModuleFromDirIfExists( + absolutePath, + moduleName, + // required to also resolve files without leading './' directly in the path + {conditions: this.cjsConditions, paths: [absolutePath]}, + ); + if (module) { + return module; + } + } - throw new Resolver.ModuleNotFoundError( - `Cannot resolve module '${moduleName}' from paths ['${paths.join( - "', '", - )}'] from ${from}`, - ); + throw new Resolver.ModuleNotFoundError( + `Cannot resolve module '${moduleName}' from paths ['${paths.join( + "', '", + )}'] from ${from}`, + ); + } } + try { return this._resolveModule(from, moduleName, { conditions: this.cjsConditions,