mirror of https://github.com/facebook/jest.git
fix: pass resolve conditions when loading stub module (#15489)
This commit is contained in:
parent
dc9f98cae4
commit
4a4c031c4d
|
@ -37,6 +37,7 @@
|
|||
- `[jest-mock]` Add support for the Explicit Resource Management proposal to use the `using` keyword with `jest.spyOn(object, methodName)` ([#14895](https://github.com/jestjs/jest/pull/14895))
|
||||
- `[jest-reporters]` Add support for [DEC mode 2026](https://gist.github.com/christianparpart/d8a62cc1ab659194337d73e399004036) ([#15008](https://github.com/jestjs/jest/pull/15008))
|
||||
- `[jest-resolver]` Support `file://` URLs as paths ([#15154](https://github.com/jestjs/jest/pull/15154))
|
||||
- `[jest-resolve,jest-runtime,jest-resolve-dependencies]` Pass the conditions when resolving stub modules ([#15489](https://github.com/jestjs/jest/pull/15489))
|
||||
- `[jest-runtime]` Exposing new modern timers function `jest.advanceTimersToFrame()` from `@jest/fake-timers` ([#14598](https://github.com/jestjs/jest/pull/14598))
|
||||
- `[jest-runtime]` Support `import.meta.filename` and `import.meta.dirname` (available from [Node 20.11](https://nodejs.org/en/blog/release/v20.11.0)) ([#14854](https://github.com/jestjs/jest/pull/14854))
|
||||
- `[jest-runtime]` Support `import.meta.resolve` ([#14930](https://github.com/jestjs/jest/pull/14930))
|
||||
|
|
|
@ -41,7 +41,7 @@ exports[`moduleNameMapper wrong array configuration 1`] = `
|
|||
12 | module.exports = () => 'test';
|
||||
13 |
|
||||
|
||||
at createNoMappedModuleFoundError (../../packages/jest-resolve/build/index.js:1182:17)
|
||||
at createNoMappedModuleFoundError (../../packages/jest-resolve/build/index.js:1184: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/index.js:1182:17)
|
||||
at createNoMappedModuleFoundError (../../packages/jest-resolve/build/index.js:1184:17)
|
||||
at Object.require (index.js:10:1)
|
||||
at Object.require (__tests__/index.js:10:20)"
|
||||
`;
|
||||
|
|
|
@ -0,0 +1,14 @@
|
|||
/**
|
||||
* 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.
|
||||
*
|
||||
* @jest-environment jest-environment-jsdom
|
||||
*/
|
||||
|
||||
import {fn} from 'fake-dual-dep2';
|
||||
|
||||
test('returns correct message', () => {
|
||||
expect(fn()).toBe('from browser');
|
||||
});
|
|
@ -0,0 +1,10 @@
|
|||
/**
|
||||
* 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.
|
||||
*/
|
||||
|
||||
export function fn () {
|
||||
return 'from browser';
|
||||
}
|
|
@ -0,0 +1,10 @@
|
|||
/**
|
||||
* 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.
|
||||
*/
|
||||
|
||||
export function fn () {
|
||||
return 'from node';
|
||||
}
|
|
@ -0,0 +1,10 @@
|
|||
{
|
||||
"name": "fake-dual-dep2",
|
||||
"version": "1.0.0",
|
||||
"exports": {
|
||||
".": {
|
||||
"node": "./node.mjs",
|
||||
"browser": "./browser.mjs"
|
||||
}
|
||||
}
|
||||
}
|
|
@ -9,6 +9,9 @@
|
|||
"testMatch": [
|
||||
"<rootDir>/**/*.test.*"
|
||||
],
|
||||
"moduleNameMapper": {
|
||||
"^fake-dual-dep2$": "fake-dual-dep2"
|
||||
},
|
||||
"transform": {}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -36,6 +36,7 @@ export class DependencyResolver {
|
|||
|
||||
resolve(file: string, options?: ResolveModuleConfig): Array<string> {
|
||||
const dependencies = this._hasteFS.getDependencies(file);
|
||||
const fallbackOptions: ResolveModuleConfig = {conditions: undefined};
|
||||
if (!dependencies) {
|
||||
return [];
|
||||
}
|
||||
|
@ -51,11 +52,15 @@ export class DependencyResolver {
|
|||
resolvedDependency = this._resolver.resolveModule(
|
||||
file,
|
||||
dependency,
|
||||
options,
|
||||
options ?? fallbackOptions,
|
||||
);
|
||||
} catch {
|
||||
try {
|
||||
resolvedDependency = this._resolver.getMockModule(file, dependency);
|
||||
resolvedDependency = this._resolver.getMockModule(
|
||||
file,
|
||||
dependency,
|
||||
options ?? fallbackOptions,
|
||||
);
|
||||
} catch {
|
||||
// leave resolvedDependency as undefined if nothing can be found
|
||||
}
|
||||
|
@ -73,6 +78,7 @@ export class DependencyResolver {
|
|||
resolvedMockDependency = this._resolver.getMockModule(
|
||||
resolvedDependency,
|
||||
path.basename(dependency),
|
||||
options ?? fallbackOptions,
|
||||
);
|
||||
} catch {
|
||||
// leave resolvedMockDependency as undefined if nothing can be found
|
||||
|
|
|
@ -742,7 +742,9 @@ describe('getMockModuleAsync', () => {
|
|||
} as ResolverConfig);
|
||||
const src = require.resolve('../');
|
||||
|
||||
await resolver.resolveModuleAsync(src, 'dependentModule');
|
||||
await resolver.resolveModuleAsync(src, 'dependentModule', {
|
||||
conditions: ['browser'],
|
||||
});
|
||||
|
||||
expect(mockUserResolverAsync.async).toHaveBeenCalled();
|
||||
expect(mockUserResolverAsync.async.mock.calls[0][0]).toBe(
|
||||
|
@ -752,6 +754,10 @@ describe('getMockModuleAsync', () => {
|
|||
'basedir',
|
||||
path.dirname(src),
|
||||
);
|
||||
expect(mockUserResolverAsync.async.mock.calls[0][1]).toHaveProperty(
|
||||
'conditions',
|
||||
['browser'],
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
|
|
|
@ -341,11 +341,11 @@ export default class Resolver {
|
|||
resolveModule(
|
||||
from: string,
|
||||
moduleName: string,
|
||||
options?: ResolveModuleConfig,
|
||||
options: ResolveModuleConfig,
|
||||
): string {
|
||||
const dirname = path.dirname(from);
|
||||
const module =
|
||||
this.resolveStubModuleName(from, moduleName) ||
|
||||
this.resolveStubModuleName(from, moduleName, options) ||
|
||||
this.resolveModuleFromDirIfExists(dirname, moduleName, options);
|
||||
if (module) return module;
|
||||
|
||||
|
@ -362,7 +362,7 @@ export default class Resolver {
|
|||
): Promise<string> {
|
||||
const dirname = path.dirname(from);
|
||||
const module =
|
||||
(await this.resolveStubModuleNameAsync(from, moduleName)) ||
|
||||
(await this.resolveStubModuleNameAsync(from, moduleName, options)) ||
|
||||
(await this.resolveModuleFromDirIfExistsAsync(
|
||||
dirname,
|
||||
moduleName,
|
||||
|
@ -482,12 +482,16 @@ export default class Resolver {
|
|||
);
|
||||
}
|
||||
|
||||
getMockModule(from: string, name: string): string | null {
|
||||
getMockModule(
|
||||
from: string,
|
||||
name: string,
|
||||
options: Pick<ResolveModuleConfig, 'conditions'>,
|
||||
): string | null {
|
||||
const mock = this._moduleMap.getMockModule(name);
|
||||
if (mock) {
|
||||
return mock;
|
||||
} else {
|
||||
const moduleName = this.resolveStubModuleName(from, name);
|
||||
const moduleName = this.resolveStubModuleName(from, name, options);
|
||||
if (moduleName) {
|
||||
return this.getModule(moduleName) || moduleName;
|
||||
}
|
||||
|
@ -495,12 +499,20 @@ export default class Resolver {
|
|||
return null;
|
||||
}
|
||||
|
||||
async getMockModuleAsync(from: string, name: string): Promise<string | null> {
|
||||
async getMockModuleAsync(
|
||||
from: string,
|
||||
name: string,
|
||||
options: Pick<ResolveModuleConfig, 'conditions'>,
|
||||
): Promise<string | null> {
|
||||
const mock = this._moduleMap.getMockModule(name);
|
||||
if (mock) {
|
||||
return mock;
|
||||
} else {
|
||||
const moduleName = await this.resolveStubModuleNameAsync(from, name);
|
||||
const moduleName = await this.resolveStubModuleNameAsync(
|
||||
from,
|
||||
name,
|
||||
options,
|
||||
);
|
||||
if (moduleName) {
|
||||
return this.getModule(moduleName) || moduleName;
|
||||
}
|
||||
|
@ -536,7 +548,7 @@ export default class Resolver {
|
|||
virtualMocks: Map<string, boolean>,
|
||||
from: string,
|
||||
moduleName = '',
|
||||
options?: ResolveModuleConfig,
|
||||
options: ResolveModuleConfig,
|
||||
): string {
|
||||
const stringifiedOptions = options ? JSON.stringify(options) : '';
|
||||
const key = from + path.delimiter + moduleName + stringifiedOptions;
|
||||
|
@ -552,7 +564,7 @@ export default class Resolver {
|
|||
moduleName,
|
||||
options,
|
||||
);
|
||||
const mockPath = this._getMockPath(from, moduleName);
|
||||
const mockPath = this._getMockPath(from, moduleName, options);
|
||||
|
||||
const sep = path.delimiter;
|
||||
const id =
|
||||
|
@ -570,7 +582,7 @@ export default class Resolver {
|
|||
virtualMocks: Map<string, boolean>,
|
||||
from: string,
|
||||
moduleName = '',
|
||||
options?: ResolveModuleConfig,
|
||||
options: ResolveModuleConfig,
|
||||
): Promise<string> {
|
||||
const stringifiedOptions = options ? JSON.stringify(options) : '';
|
||||
const key = from + path.delimiter + moduleName + stringifiedOptions;
|
||||
|
@ -589,7 +601,7 @@ export default class Resolver {
|
|||
moduleName,
|
||||
options,
|
||||
);
|
||||
const mockPath = await this._getMockPathAsync(from, moduleName);
|
||||
const mockPath = await this._getMockPathAsync(from, moduleName, options);
|
||||
|
||||
const sep = path.delimiter;
|
||||
const id =
|
||||
|
@ -611,7 +623,7 @@ export default class Resolver {
|
|||
virtualMocks: Map<string, boolean>,
|
||||
from: string,
|
||||
moduleName: string,
|
||||
options?: ResolveModuleConfig,
|
||||
options: ResolveModuleConfig,
|
||||
): string | null {
|
||||
if (this.isCoreModule(moduleName)) {
|
||||
return moduleName;
|
||||
|
@ -619,7 +631,7 @@ export default class Resolver {
|
|||
if (moduleName.startsWith('data:')) {
|
||||
return moduleName;
|
||||
}
|
||||
return this._isModuleResolved(from, moduleName)
|
||||
return this._isModuleResolved(from, moduleName, options)
|
||||
? this.getModule(moduleName)
|
||||
: this._getVirtualMockPath(virtualMocks, from, moduleName, options);
|
||||
}
|
||||
|
@ -628,7 +640,7 @@ export default class Resolver {
|
|||
virtualMocks: Map<string, boolean>,
|
||||
from: string,
|
||||
moduleName: string,
|
||||
options?: ResolveModuleConfig,
|
||||
options: ResolveModuleConfig,
|
||||
): Promise<string | null> {
|
||||
if (this.isCoreModule(moduleName)) {
|
||||
return moduleName;
|
||||
|
@ -639,32 +651,38 @@ export default class Resolver {
|
|||
const isModuleResolved = await this._isModuleResolvedAsync(
|
||||
from,
|
||||
moduleName,
|
||||
options,
|
||||
);
|
||||
return isModuleResolved
|
||||
? this.getModule(moduleName)
|
||||
: this._getVirtualMockPathAsync(virtualMocks, from, moduleName, options);
|
||||
}
|
||||
|
||||
private _getMockPath(from: string, moduleName: string): string | null {
|
||||
private _getMockPath(
|
||||
from: string,
|
||||
moduleName: string,
|
||||
options: Pick<ResolveModuleConfig, 'conditions'>,
|
||||
): string | null {
|
||||
return this.isCoreModule(moduleName)
|
||||
? null
|
||||
: this.getMockModule(from, moduleName);
|
||||
: this.getMockModule(from, moduleName, options);
|
||||
}
|
||||
|
||||
private async _getMockPathAsync(
|
||||
from: string,
|
||||
moduleName: string,
|
||||
options: Pick<ResolveModuleConfig, 'conditions'>,
|
||||
): Promise<string | null> {
|
||||
return this.isCoreModule(moduleName)
|
||||
? null
|
||||
: this.getMockModuleAsync(from, moduleName);
|
||||
: this.getMockModuleAsync(from, moduleName, options);
|
||||
}
|
||||
|
||||
private _getVirtualMockPath(
|
||||
virtualMocks: Map<string, boolean>,
|
||||
from: string,
|
||||
moduleName: string,
|
||||
options?: ResolveModuleConfig,
|
||||
options: ResolveModuleConfig,
|
||||
): string {
|
||||
const virtualMockPath = this.getModulePath(from, moduleName);
|
||||
return virtualMocks.get(virtualMockPath)
|
||||
|
@ -688,23 +706,33 @@ export default class Resolver {
|
|||
: from;
|
||||
}
|
||||
|
||||
private _isModuleResolved(from: string, moduleName: string): boolean {
|
||||
private _isModuleResolved(
|
||||
from: string,
|
||||
moduleName: string,
|
||||
options: Pick<ResolveModuleConfig, 'conditions'>,
|
||||
): boolean {
|
||||
return !!(
|
||||
this.getModule(moduleName) || this.getMockModule(from, moduleName)
|
||||
this.getModule(moduleName) ||
|
||||
this.getMockModule(from, moduleName, options)
|
||||
);
|
||||
}
|
||||
|
||||
private async _isModuleResolvedAsync(
|
||||
from: string,
|
||||
moduleName: string,
|
||||
options: Pick<ResolveModuleConfig, 'conditions'>,
|
||||
): Promise<boolean> {
|
||||
return !!(
|
||||
this.getModule(moduleName) ||
|
||||
(await this.getMockModuleAsync(from, moduleName))
|
||||
(await this.getMockModuleAsync(from, moduleName, options))
|
||||
);
|
||||
}
|
||||
|
||||
resolveStubModuleName(from: string, moduleName: string): string | null {
|
||||
resolveStubModuleName(
|
||||
from: string,
|
||||
moduleName: string,
|
||||
options: Pick<ResolveModuleConfig, 'conditions'>,
|
||||
): string | null {
|
||||
const dirname = path.dirname(from);
|
||||
|
||||
const {extensions, moduleDirectory, paths} = this._prepareForResolution(
|
||||
|
@ -727,11 +755,11 @@ export default class Resolver {
|
|||
let module: string | null = null;
|
||||
for (const possibleModuleName of possibleModuleNames) {
|
||||
const updatedName = mapModuleName(possibleModuleName);
|
||||
|
||||
module =
|
||||
this.getModule(updatedName) ||
|
||||
Resolver.findNodeModule(updatedName, {
|
||||
basedir: dirname,
|
||||
conditions: options?.conditions,
|
||||
extensions,
|
||||
moduleDirectory,
|
||||
paths,
|
||||
|
@ -763,6 +791,7 @@ export default class Resolver {
|
|||
async resolveStubModuleNameAsync(
|
||||
from: string,
|
||||
moduleName: string,
|
||||
options?: Pick<ResolveModuleConfig, 'conditions'>,
|
||||
): Promise<string | null> {
|
||||
const dirname = path.dirname(from);
|
||||
|
||||
|
@ -791,6 +820,7 @@ export default class Resolver {
|
|||
this.getModule(updatedName) ||
|
||||
(await Resolver.findNodeModuleAsync(updatedName, {
|
||||
basedir: dirname,
|
||||
conditions: options?.conditions,
|
||||
extensions,
|
||||
moduleDirectory,
|
||||
paths,
|
||||
|
|
|
@ -925,11 +925,12 @@ export default class Runtime {
|
|||
isRequireActual = false,
|
||||
): T {
|
||||
const isInternal = options?.isInternalModule ?? false;
|
||||
const resolveModuleOptions = {conditions: this.cjsConditions};
|
||||
const moduleID = this._resolver.getModuleID(
|
||||
this._virtualMocks,
|
||||
from,
|
||||
moduleName,
|
||||
{conditions: this.cjsConditions},
|
||||
resolveModuleOptions,
|
||||
);
|
||||
let modulePath: string | undefined;
|
||||
|
||||
|
@ -937,7 +938,8 @@ export default class Runtime {
|
|||
// to be more explicit.
|
||||
const moduleResource = moduleName && this._resolver.getModule(moduleName);
|
||||
const manualMock =
|
||||
moduleName && this._resolver.getMockModule(from, moduleName);
|
||||
moduleName &&
|
||||
this._resolver.getMockModule(from, moduleName, resolveModuleOptions);
|
||||
if (
|
||||
!options?.isInternalModule &&
|
||||
!isRequireActual &&
|
||||
|
@ -1043,11 +1045,12 @@ export default class Runtime {
|
|||
}
|
||||
|
||||
requireMock<T = unknown>(from: string, moduleName: string): T {
|
||||
const options = {conditions: this.cjsConditions};
|
||||
const moduleID = this._resolver.getModuleID(
|
||||
this._virtualMocks,
|
||||
from,
|
||||
moduleName,
|
||||
{conditions: this.cjsConditions},
|
||||
options,
|
||||
);
|
||||
|
||||
if (this._isolatedMockRegistry?.has(moduleID)) {
|
||||
|
@ -1065,15 +1068,19 @@ export default class Runtime {
|
|||
return module as T;
|
||||
}
|
||||
|
||||
const manualMockOrStub = this._resolver.getMockModule(from, moduleName);
|
||||
const manualMockOrStub = this._resolver.getMockModule(
|
||||
from,
|
||||
moduleName,
|
||||
options,
|
||||
);
|
||||
|
||||
let modulePath =
|
||||
this._resolver.getMockModule(from, moduleName) ||
|
||||
this._resolver.getMockModule(from, moduleName, options) ||
|
||||
this._resolveCjsModule(from, moduleName);
|
||||
|
||||
let isManualMock =
|
||||
manualMockOrStub &&
|
||||
!this._resolver.resolveStubModuleName(from, moduleName);
|
||||
!this._resolver.resolveStubModuleName(from, moduleName, options);
|
||||
if (!isManualMock) {
|
||||
// If the actual module file has a __mocks__ dir sitting immediately next
|
||||
// to it, look to see if there is a manual mock for this file.
|
||||
|
@ -1507,7 +1514,9 @@ export default class Runtime {
|
|||
try {
|
||||
return this._resolveCjsModule(from, moduleName);
|
||||
} catch (error) {
|
||||
const module = this._resolver.getMockModule(from, moduleName);
|
||||
const module = this._resolver.getMockModule(from, moduleName, {
|
||||
conditions: this.cjsConditions,
|
||||
});
|
||||
|
||||
if (module) {
|
||||
return module;
|
||||
|
@ -1899,8 +1908,9 @@ export default class Runtime {
|
|||
|
||||
private _generateMock<T>(from: string, moduleName: string) {
|
||||
const modulePath =
|
||||
this._resolver.resolveStubModuleName(from, moduleName) ||
|
||||
this._resolveCjsModule(from, moduleName);
|
||||
this._resolver.resolveStubModuleName(from, moduleName, {
|
||||
conditions: this.cjsConditions,
|
||||
}) || this._resolveCjsModule(from, moduleName);
|
||||
if (!this._mockMetaDataCache.has(modulePath)) {
|
||||
// This allows us to handle circular dependencies while generating an
|
||||
// automock
|
||||
|
@ -1982,7 +1992,11 @@ export default class Runtime {
|
|||
try {
|
||||
modulePath = this._resolveCjsModule(from, moduleName);
|
||||
} catch (error) {
|
||||
const manualMock = this._resolver.getMockModule(from, moduleName);
|
||||
const manualMock = this._resolver.getMockModule(
|
||||
from,
|
||||
moduleName,
|
||||
options,
|
||||
);
|
||||
if (manualMock) {
|
||||
this._shouldMockModuleCache.set(moduleID, true);
|
||||
return true;
|
||||
|
@ -2056,6 +2070,7 @@ export default class Runtime {
|
|||
const manualMock = await this._resolver.getMockModuleAsync(
|
||||
from,
|
||||
moduleName,
|
||||
options,
|
||||
);
|
||||
if (manualMock) {
|
||||
this._shouldMockModuleCache.set(moduleID, true);
|
||||
|
|
Loading…
Reference in New Issue