Skip to content

Commit 4a4c031

Browse files
fix: pass resolve conditions when loading stub module (#15489)
1 parent dc9f98c commit 4a4c031

File tree

11 files changed

+143
-38
lines changed

11 files changed

+143
-38
lines changed

CHANGELOG.md

+1
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@
3737
- `[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))
3838
- `[jest-reporters]` Add support for [DEC mode 2026](https://gist.github.com/christianparpart/d8a62cc1ab659194337d73e399004036) ([#15008](https://github.com/jestjs/jest/pull/15008))
3939
- `[jest-resolver]` Support `file://` URLs as paths ([#15154](https://github.com/jestjs/jest/pull/15154))
40+
- `[jest-resolve,jest-runtime,jest-resolve-dependencies]` Pass the conditions when resolving stub modules ([#15489](https://github.com/jestjs/jest/pull/15489))
4041
- `[jest-runtime]` Exposing new modern timers function `jest.advanceTimersToFrame()` from `@jest/fake-timers` ([#14598](https://github.com/jestjs/jest/pull/14598))
4142
- `[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))
4243
- `[jest-runtime]` Support `import.meta.resolve` ([#14930](https://github.com/jestjs/jest/pull/14930))

e2e/__tests__/__snapshots__/moduleNameMapper.test.ts.snap

+2-2
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,7 @@ exports[`moduleNameMapper wrong array configuration 1`] = `
4141
12 | module.exports = () => 'test';
4242
13 |
4343
44-
at createNoMappedModuleFoundError (../../packages/jest-resolve/build/index.js:1182:17)
44+
at createNoMappedModuleFoundError (../../packages/jest-resolve/build/index.js:1184:17)
4545
at Object.require (index.js:10:1)
4646
at Object.require (__tests__/index.js:10:20)"
4747
`;
@@ -71,7 +71,7 @@ exports[`moduleNameMapper wrong configuration 1`] = `
7171
12 | module.exports = () => 'test';
7272
13 |
7373
74-
at createNoMappedModuleFoundError (../../packages/jest-resolve/build/index.js:1182:17)
74+
at createNoMappedModuleFoundError (../../packages/jest-resolve/build/index.js:1184:17)
7575
at Object.require (index.js:10:1)
7676
at Object.require (__tests__/index.js:10:20)"
7777
`;
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
/**
2+
* Copyright (c) Meta Platforms, Inc. and affiliates.
3+
*
4+
* This source code is licensed under the MIT license found in the
5+
* LICENSE file in the root directory of this source tree.
6+
*
7+
* @jest-environment jest-environment-jsdom
8+
*/
9+
10+
import {fn} from 'fake-dual-dep2';
11+
12+
test('returns correct message', () => {
13+
expect(fn()).toBe('from browser');
14+
});

e2e/resolve-conditions/node_modules/fake-dual-dep2/browser.mjs

+10
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

e2e/resolve-conditions/node_modules/fake-dual-dep2/node.mjs

+10
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

e2e/resolve-conditions/node_modules/fake-dual-dep2/package.json

+10
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

e2e/resolve-conditions/package.json

+3
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,9 @@
99
"testMatch": [
1010
"<rootDir>/**/*.test.*"
1111
],
12+
"moduleNameMapper": {
13+
"^fake-dual-dep2$": "fake-dual-dep2"
14+
},
1215
"transform": {}
1316
}
1417
}

packages/jest-resolve-dependencies/src/index.ts

+8-2
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@ export class DependencyResolver {
3636

3737
resolve(file: string, options?: ResolveModuleConfig): Array<string> {
3838
const dependencies = this._hasteFS.getDependencies(file);
39+
const fallbackOptions: ResolveModuleConfig = {conditions: undefined};
3940
if (!dependencies) {
4041
return [];
4142
}
@@ -51,11 +52,15 @@ export class DependencyResolver {
5152
resolvedDependency = this._resolver.resolveModule(
5253
file,
5354
dependency,
54-
options,
55+
options ?? fallbackOptions,
5556
);
5657
} catch {
5758
try {
58-
resolvedDependency = this._resolver.getMockModule(file, dependency);
59+
resolvedDependency = this._resolver.getMockModule(
60+
file,
61+
dependency,
62+
options ?? fallbackOptions,
63+
);
5964
} catch {
6065
// leave resolvedDependency as undefined if nothing can be found
6166
}
@@ -73,6 +78,7 @@ export class DependencyResolver {
7378
resolvedMockDependency = this._resolver.getMockModule(
7479
resolvedDependency,
7580
path.basename(dependency),
81+
options ?? fallbackOptions,
7682
);
7783
} catch {
7884
// leave resolvedMockDependency as undefined if nothing can be found

packages/jest-resolve/src/__tests__/resolve.test.ts

+7-1
Original file line numberDiff line numberDiff line change
@@ -742,7 +742,9 @@ describe('getMockModuleAsync', () => {
742742
} as ResolverConfig);
743743
const src = require.resolve('../');
744744

745-
await resolver.resolveModuleAsync(src, 'dependentModule');
745+
await resolver.resolveModuleAsync(src, 'dependentModule', {
746+
conditions: ['browser'],
747+
});
746748

747749
expect(mockUserResolverAsync.async).toHaveBeenCalled();
748750
expect(mockUserResolverAsync.async.mock.calls[0][0]).toBe(
@@ -752,6 +754,10 @@ describe('getMockModuleAsync', () => {
752754
'basedir',
753755
path.dirname(src),
754756
);
757+
expect(mockUserResolverAsync.async.mock.calls[0][1]).toHaveProperty(
758+
'conditions',
759+
['browser'],
760+
);
755761
});
756762
});
757763

packages/jest-resolve/src/resolver.ts

+53-23
Original file line numberDiff line numberDiff line change
@@ -341,11 +341,11 @@ export default class Resolver {
341341
resolveModule(
342342
from: string,
343343
moduleName: string,
344-
options?: ResolveModuleConfig,
344+
options: ResolveModuleConfig,
345345
): string {
346346
const dirname = path.dirname(from);
347347
const module =
348-
this.resolveStubModuleName(from, moduleName) ||
348+
this.resolveStubModuleName(from, moduleName, options) ||
349349
this.resolveModuleFromDirIfExists(dirname, moduleName, options);
350350
if (module) return module;
351351

@@ -362,7 +362,7 @@ export default class Resolver {
362362
): Promise<string> {
363363
const dirname = path.dirname(from);
364364
const module =
365-
(await this.resolveStubModuleNameAsync(from, moduleName)) ||
365+
(await this.resolveStubModuleNameAsync(from, moduleName, options)) ||
366366
(await this.resolveModuleFromDirIfExistsAsync(
367367
dirname,
368368
moduleName,
@@ -482,25 +482,37 @@ export default class Resolver {
482482
);
483483
}
484484

485-
getMockModule(from: string, name: string): string | null {
485+
getMockModule(
486+
from: string,
487+
name: string,
488+
options: Pick<ResolveModuleConfig, 'conditions'>,
489+
): string | null {
486490
const mock = this._moduleMap.getMockModule(name);
487491
if (mock) {
488492
return mock;
489493
} else {
490-
const moduleName = this.resolveStubModuleName(from, name);
494+
const moduleName = this.resolveStubModuleName(from, name, options);
491495
if (moduleName) {
492496
return this.getModule(moduleName) || moduleName;
493497
}
494498
}
495499
return null;
496500
}
497501

498-
async getMockModuleAsync(from: string, name: string): Promise<string | null> {
502+
async getMockModuleAsync(
503+
from: string,
504+
name: string,
505+
options: Pick<ResolveModuleConfig, 'conditions'>,
506+
): Promise<string | null> {
499507
const mock = this._moduleMap.getMockModule(name);
500508
if (mock) {
501509
return mock;
502510
} else {
503-
const moduleName = await this.resolveStubModuleNameAsync(from, name);
511+
const moduleName = await this.resolveStubModuleNameAsync(
512+
from,
513+
name,
514+
options,
515+
);
504516
if (moduleName) {
505517
return this.getModule(moduleName) || moduleName;
506518
}
@@ -536,7 +548,7 @@ export default class Resolver {
536548
virtualMocks: Map<string, boolean>,
537549
from: string,
538550
moduleName = '',
539-
options?: ResolveModuleConfig,
551+
options: ResolveModuleConfig,
540552
): string {
541553
const stringifiedOptions = options ? JSON.stringify(options) : '';
542554
const key = from + path.delimiter + moduleName + stringifiedOptions;
@@ -552,7 +564,7 @@ export default class Resolver {
552564
moduleName,
553565
options,
554566
);
555-
const mockPath = this._getMockPath(from, moduleName);
567+
const mockPath = this._getMockPath(from, moduleName, options);
556568

557569
const sep = path.delimiter;
558570
const id =
@@ -570,7 +582,7 @@ export default class Resolver {
570582
virtualMocks: Map<string, boolean>,
571583
from: string,
572584
moduleName = '',
573-
options?: ResolveModuleConfig,
585+
options: ResolveModuleConfig,
574586
): Promise<string> {
575587
const stringifiedOptions = options ? JSON.stringify(options) : '';
576588
const key = from + path.delimiter + moduleName + stringifiedOptions;
@@ -589,7 +601,7 @@ export default class Resolver {
589601
moduleName,
590602
options,
591603
);
592-
const mockPath = await this._getMockPathAsync(from, moduleName);
604+
const mockPath = await this._getMockPathAsync(from, moduleName, options);
593605

594606
const sep = path.delimiter;
595607
const id =
@@ -611,15 +623,15 @@ export default class Resolver {
611623
virtualMocks: Map<string, boolean>,
612624
from: string,
613625
moduleName: string,
614-
options?: ResolveModuleConfig,
626+
options: ResolveModuleConfig,
615627
): string | null {
616628
if (this.isCoreModule(moduleName)) {
617629
return moduleName;
618630
}
619631
if (moduleName.startsWith('data:')) {
620632
return moduleName;
621633
}
622-
return this._isModuleResolved(from, moduleName)
634+
return this._isModuleResolved(from, moduleName, options)
623635
? this.getModule(moduleName)
624636
: this._getVirtualMockPath(virtualMocks, from, moduleName, options);
625637
}
@@ -628,7 +640,7 @@ export default class Resolver {
628640
virtualMocks: Map<string, boolean>,
629641
from: string,
630642
moduleName: string,
631-
options?: ResolveModuleConfig,
643+
options: ResolveModuleConfig,
632644
): Promise<string | null> {
633645
if (this.isCoreModule(moduleName)) {
634646
return moduleName;
@@ -639,32 +651,38 @@ export default class Resolver {
639651
const isModuleResolved = await this._isModuleResolvedAsync(
640652
from,
641653
moduleName,
654+
options,
642655
);
643656
return isModuleResolved
644657
? this.getModule(moduleName)
645658
: this._getVirtualMockPathAsync(virtualMocks, from, moduleName, options);
646659
}
647660

648-
private _getMockPath(from: string, moduleName: string): string | null {
661+
private _getMockPath(
662+
from: string,
663+
moduleName: string,
664+
options: Pick<ResolveModuleConfig, 'conditions'>,
665+
): string | null {
649666
return this.isCoreModule(moduleName)
650667
? null
651-
: this.getMockModule(from, moduleName);
668+
: this.getMockModule(from, moduleName, options);
652669
}
653670

654671
private async _getMockPathAsync(
655672
from: string,
656673
moduleName: string,
674+
options: Pick<ResolveModuleConfig, 'conditions'>,
657675
): Promise<string | null> {
658676
return this.isCoreModule(moduleName)
659677
? null
660-
: this.getMockModuleAsync(from, moduleName);
678+
: this.getMockModuleAsync(from, moduleName, options);
661679
}
662680

663681
private _getVirtualMockPath(
664682
virtualMocks: Map<string, boolean>,
665683
from: string,
666684
moduleName: string,
667-
options?: ResolveModuleConfig,
685+
options: ResolveModuleConfig,
668686
): string {
669687
const virtualMockPath = this.getModulePath(from, moduleName);
670688
return virtualMocks.get(virtualMockPath)
@@ -688,23 +706,33 @@ export default class Resolver {
688706
: from;
689707
}
690708

691-
private _isModuleResolved(from: string, moduleName: string): boolean {
709+
private _isModuleResolved(
710+
from: string,
711+
moduleName: string,
712+
options: Pick<ResolveModuleConfig, 'conditions'>,
713+
): boolean {
692714
return !!(
693-
this.getModule(moduleName) || this.getMockModule(from, moduleName)
715+
this.getModule(moduleName) ||
716+
this.getMockModule(from, moduleName, options)
694717
);
695718
}
696719

697720
private async _isModuleResolvedAsync(
698721
from: string,
699722
moduleName: string,
723+
options: Pick<ResolveModuleConfig, 'conditions'>,
700724
): Promise<boolean> {
701725
return !!(
702726
this.getModule(moduleName) ||
703-
(await this.getMockModuleAsync(from, moduleName))
727+
(await this.getMockModuleAsync(from, moduleName, options))
704728
);
705729
}
706730

707-
resolveStubModuleName(from: string, moduleName: string): string | null {
731+
resolveStubModuleName(
732+
from: string,
733+
moduleName: string,
734+
options: Pick<ResolveModuleConfig, 'conditions'>,
735+
): string | null {
708736
const dirname = path.dirname(from);
709737

710738
const {extensions, moduleDirectory, paths} = this._prepareForResolution(
@@ -727,11 +755,11 @@ export default class Resolver {
727755
let module: string | null = null;
728756
for (const possibleModuleName of possibleModuleNames) {
729757
const updatedName = mapModuleName(possibleModuleName);
730-
731758
module =
732759
this.getModule(updatedName) ||
733760
Resolver.findNodeModule(updatedName, {
734761
basedir: dirname,
762+
conditions: options?.conditions,
735763
extensions,
736764
moduleDirectory,
737765
paths,
@@ -763,6 +791,7 @@ export default class Resolver {
763791
async resolveStubModuleNameAsync(
764792
from: string,
765793
moduleName: string,
794+
options?: Pick<ResolveModuleConfig, 'conditions'>,
766795
): Promise<string | null> {
767796
const dirname = path.dirname(from);
768797

@@ -791,6 +820,7 @@ export default class Resolver {
791820
this.getModule(updatedName) ||
792821
(await Resolver.findNodeModuleAsync(updatedName, {
793822
basedir: dirname,
823+
conditions: options?.conditions,
794824
extensions,
795825
moduleDirectory,
796826
paths,

0 commit comments

Comments
 (0)