Skip to content

Commit

Permalink
Ensure test modules can require.resolve absolute paths.
Browse files Browse the repository at this point in the history
Before this change, the runtime's `require.resolve` function would always try to resolve paths relative to the passed `paths` option. This prevents requiring paths which are absolute! Normal node `require.resolve` is fine requiring absolute paths, including when passed `{ paths: [] }`, so this updates the jest require algorithm to detect absolute paths and handle them without trying to make the path relative to the passed `paths` option. This is similar to how node's own require.resolve works, see https://github.com/nodejs/node/blob/05002373176e8758c8c604f06659e171de4ca902/lib/internal/modules/cjs/loader.js#L240-L241

Fixes #11927.
  • Loading branch information
airhorns committed Oct 9, 2021
1 parent 1547740 commit 52bd70f
Show file tree
Hide file tree
Showing 3 changed files with 79 additions and 19 deletions.
2 changes: 2 additions & 0 deletions CHANGELOG.md
Expand Up @@ -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
Expand Down
Expand Up @@ -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';
Expand All @@ -15,6 +18,9 @@ let createRuntime: (
config?: Config.InitialOptions,
) => Promise<Runtime & {__mockRootPath: string}>;

const getTmpDir = async () =>
await fs.mkdtemp(path.join(os.tmpdir(), 'jest-resolve-test-'));

describe('Runtime require.resolve', () => {
beforeEach(() => {
createRuntime = require('createRuntime');
Expand All @@ -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: {
Expand Down
49 changes: 30 additions & 19 deletions packages/jest-runtime/src/index.ts
Expand Up @@ -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,
Expand Down

0 comments on commit 52bd70f

Please sign in to comment.