@@ -148,6 +148,7 @@ export default class Runtime {
148148 private _currentlyExecutingModulePath : string ;
149149 private readonly _environment : JestEnvironment ;
150150 private readonly _explicitShouldMock : Map < string , boolean > ;
151+ private readonly _explicitShouldMockModule : Map < string , boolean > ;
151152 private _fakeTimersImplementation :
152153 | LegacyFakeTimers < unknown >
153154 | ModernFakeTimers
@@ -162,6 +163,8 @@ export default class Runtime {
162163 > ;
163164 private _mockRegistry : Map < string , any > ;
164165 private _isolatedMockRegistry : Map < string , any > | null ;
166+ private _moduleMockRegistry : Map < string , VMModule > ;
167+ private readonly _moduleMockFactories : Map < string , ( ) => unknown > ;
165168 private readonly _moduleMocker : ModuleMocker ;
166169 private _isolatedModuleRegistry : ModuleRegistry | null ;
167170 private _moduleRegistry : ModuleRegistry ;
@@ -182,6 +185,7 @@ export default class Runtime {
182185 private readonly _transitiveShouldMock : Map < string , boolean > ;
183186 private _unmockList : RegExp | undefined ;
184187 private readonly _virtualMocks : Map < string , boolean > ;
188+ private readonly _virtualModuleMocks : Map < string , boolean > ;
185189 private _moduleImplementation ?: typeof nativeModule . Module ;
186190 private readonly jestObjectCaches : Map < string , Jest > ;
187191 private jestGlobals ?: JestGlobals ;
@@ -200,11 +204,14 @@ export default class Runtime {
200204 this . _currentlyExecutingModulePath = '' ;
201205 this . _environment = environment ;
202206 this . _explicitShouldMock = new Map ( ) ;
207+ this . _explicitShouldMockModule = new Map ( ) ;
203208 this . _internalModuleRegistry = new Map ( ) ;
204209 this . _isCurrentlyExecutingManualMock = null ;
205210 this . _mainModule = null ;
206211 this . _mockFactories = new Map ( ) ;
207212 this . _mockRegistry = new Map ( ) ;
213+ this . _moduleMockRegistry = new Map ( ) ;
214+ this . _moduleMockFactories = new Map ( ) ;
208215 invariant (
209216 this . _environment . moduleMocker ,
210217 '`moduleMocker` must be set on an environment when created' ,
@@ -221,6 +228,7 @@ export default class Runtime {
221228 this . _sourceMapRegistry = new Map ( ) ;
222229 this . _fileTransforms = new Map ( ) ;
223230 this . _virtualMocks = new Map ( ) ;
231+ this . _virtualModuleMocks = new Map ( ) ;
224232 this . jestObjectCaches = new Map ( ) ;
225233
226234 this . _mockMetaDataCache = new Map ( ) ;
@@ -488,6 +496,16 @@ export default class Runtime {
488496
489497 const [ path , query ] = specifier . split ( '?' ) ;
490498
499+ if (
500+ this . _shouldMock (
501+ referencingIdentifier ,
502+ path ,
503+ this . _explicitShouldMockModule ,
504+ )
505+ ) {
506+ return this . importMock ( referencingIdentifier , path , context ) ;
507+ }
508+
491509 const resolved = this . _resolveModule ( referencingIdentifier , path ) ;
492510
493511 if (
@@ -503,6 +521,8 @@ export default class Runtime {
503521 async unstable_importModule (
504522 from : Config . Path ,
505523 moduleName ?: string ,
524+ // TODO: implement this
525+ _isImportActual = false ,
506526 ) : Promise < void > {
507527 invariant (
508528 runtimeSupportsVmModules ,
@@ -556,11 +576,114 @@ export default class Runtime {
556576 return evaluateSyntheticModule ( module ) ;
557577 }
558578
579+ private async importMock < T = unknown > (
580+ from : Config . Path ,
581+ moduleName : string ,
582+ context : VMContext ,
583+ ) : Promise < T > {
584+ const moduleID = this . _resolver . getModuleID (
585+ this . _virtualModuleMocks ,
586+ from ,
587+ moduleName ,
588+ ) ;
589+
590+ if ( this . _moduleMockRegistry . has ( moduleID ) ) {
591+ return this . _moduleMockRegistry . get ( moduleID ) ;
592+ }
593+
594+ if ( this . _moduleMockFactories . has ( moduleID ) ) {
595+ const invokedFactory : any = await this . _moduleMockFactories . get (
596+ moduleID ,
597+ // has check above makes this ok
598+ ) ! ( ) ;
599+
600+ const module = new SyntheticModule (
601+ Object . keys ( invokedFactory ) ,
602+ function ( ) {
603+ Object . entries ( invokedFactory ) . forEach ( ( [ key , value ] ) => {
604+ // @ts -expect-error: TS doesn't know what `this` is
605+ this . setExport ( key , value ) ;
606+ } ) ;
607+ } ,
608+ // should identifier be `node://${moduleName}`?
609+ { context, identifier : moduleName } ,
610+ ) ;
611+
612+ this . _moduleMockRegistry . set ( moduleID , module ) ;
613+
614+ return evaluateSyntheticModule ( module ) ;
615+ }
616+
617+ const manualMockOrStub = this . _resolver . getMockModule ( from , moduleName ) ;
618+
619+ let modulePath =
620+ this . _resolver . getMockModule ( from , moduleName ) ||
621+ this . _resolveModule ( from , moduleName ) ;
622+
623+ let isManualMock =
624+ manualMockOrStub &&
625+ ! this . _resolver . resolveStubModuleName ( from , moduleName ) ;
626+ if ( ! isManualMock ) {
627+ // If the actual module file has a __mocks__ dir sitting immediately next
628+ // to it, look to see if there is a manual mock for this file.
629+ //
630+ // subDir1/my_module.js
631+ // subDir1/__mocks__/my_module.js
632+ // subDir2/my_module.js
633+ // subDir2/__mocks__/my_module.js
634+ //
635+ // Where some other module does a relative require into each of the
636+ // respective subDir{1,2} directories and expects a manual mock
637+ // corresponding to that particular my_module.js file.
638+
639+ const moduleDir = path . dirname ( modulePath ) ;
640+ const moduleFileName = path . basename ( modulePath ) ;
641+ const potentialManualMock = path . join (
642+ moduleDir ,
643+ '__mocks__' ,
644+ moduleFileName ,
645+ ) ;
646+ if ( fs . existsSync ( potentialManualMock ) ) {
647+ isManualMock = true ;
648+ modulePath = potentialManualMock ;
649+ }
650+ }
651+ if ( isManualMock ) {
652+ const localModule : InitialModule = {
653+ children : [ ] ,
654+ exports : { } ,
655+ filename : modulePath ,
656+ id : modulePath ,
657+ loaded : false ,
658+ path : modulePath ,
659+ } ;
660+
661+ this . _loadModule (
662+ localModule ,
663+ from ,
664+ moduleName ,
665+ modulePath ,
666+ undefined ,
667+ this . _moduleMockRegistry ,
668+ ) ;
669+
670+ this . _moduleMockRegistry . set ( moduleID , localModule . exports ) ;
671+ } else {
672+ // Look for a real module to generate an automock from
673+ this . _moduleMockRegistry . set (
674+ moduleID ,
675+ this . _generateMock ( from , moduleName ) ,
676+ ) ;
677+ }
678+
679+ return this . _moduleMockRegistry . get ( moduleID ) ;
680+ }
681+
559682 requireModule < T = unknown > (
560683 from : Config . Path ,
561684 moduleName ?: string ,
562685 options ?: InternalModuleOptions ,
563- isRequireActual ?: boolean | null ,
686+ isRequireActual = false ,
564687 ) : T {
565688 const moduleID = this . _resolver . getModuleID (
566689 this . _virtualMocks ,
@@ -597,12 +720,10 @@ export default class Runtime {
597720
598721 if ( options ?. isInternalModule ) {
599722 moduleRegistry = this . _internalModuleRegistry ;
723+ } else if ( this . _isolatedModuleRegistry ) {
724+ moduleRegistry = this . _isolatedModuleRegistry ;
600725 } else {
601- if ( this . _isolatedModuleRegistry ) {
602- moduleRegistry = this . _isolatedModuleRegistry ;
603- } else {
604- moduleRegistry = this . _moduleRegistry ;
605- }
726+ moduleRegistry = this . _moduleRegistry ;
606727 }
607728
608729 const module = moduleRegistry . get ( modulePath ) ;
@@ -663,17 +784,12 @@ export default class Runtime {
663784 moduleName ,
664785 ) ;
665786
666- if (
667- this . _isolatedMockRegistry &&
668- this . _isolatedMockRegistry . get ( moduleID )
669- ) {
670- return this . _isolatedMockRegistry . get ( moduleID ) ;
671- } else if ( this . _mockRegistry . get ( moduleID ) ) {
672- return this . _mockRegistry . get ( moduleID ) ;
673- }
674-
675787 const mockRegistry = this . _isolatedMockRegistry || this . _mockRegistry ;
676788
789+ if ( mockRegistry . get ( moduleID ) ) {
790+ return mockRegistry . get ( moduleID ) ;
791+ }
792+
677793 if ( this . _mockFactories . has ( moduleID ) ) {
678794 // has check above makes this ok
679795 const module = this . _mockFactories . get ( moduleID ) ! ( ) ;
@@ -790,7 +906,7 @@ export default class Runtime {
790906 }
791907
792908 try {
793- if ( this . _shouldMock ( from , moduleName ) ) {
909+ if ( this . _shouldMock ( from , moduleName , this . _explicitShouldMock ) ) {
794910 return this . requireMock < T > ( from , moduleName ) ;
795911 } else {
796912 return this . requireModule < T > ( from , moduleName ) ;
@@ -845,6 +961,7 @@ export default class Runtime {
845961 this . _mockRegistry . clear ( ) ;
846962 this . _moduleRegistry . clear ( ) ;
847963 this . _esmoduleRegistry . clear ( ) ;
964+ this . _moduleMockRegistry . clear ( ) ;
848965
849966 if ( this . _environment ) {
850967 if ( this . _environment . global ) {
@@ -933,6 +1050,26 @@ export default class Runtime {
9331050 this . _mockFactories . set ( moduleID , mockFactory ) ;
9341051 }
9351052
1053+ private setModuleMock (
1054+ from : string ,
1055+ moduleName : string ,
1056+ mockFactory : ( ) => Promise < unknown > | unknown ,
1057+ options ?: { virtual ?: boolean } ,
1058+ ) : void {
1059+ if ( options ?. virtual ) {
1060+ const mockPath = this . _resolver . getModulePath ( from , moduleName ) ;
1061+
1062+ this . _virtualModuleMocks . set ( mockPath , true ) ;
1063+ }
1064+ const moduleID = this . _resolver . getModuleID (
1065+ this . _virtualModuleMocks ,
1066+ from ,
1067+ moduleName ,
1068+ ) ;
1069+ this . _explicitShouldMockModule . set ( moduleID , true ) ;
1070+ this . _moduleMockFactories . set ( moduleID , mockFactory ) ;
1071+ }
1072+
9361073 restoreAllMocks ( ) : void {
9371074 this . _moduleMocker . restoreAllMocks ( ) ;
9381075 }
@@ -953,12 +1090,15 @@ export default class Runtime {
9531090 this . _internalModuleRegistry . clear ( ) ;
9541091 this . _mainModule = null ;
9551092 this . _mockFactories . clear ( ) ;
1093+ this . _moduleMockFactories . clear ( ) ;
9561094 this . _mockMetaDataCache . clear ( ) ;
9571095 this . _shouldMockModuleCache . clear ( ) ;
9581096 this . _shouldUnmockTransitiveDependenciesCache . clear ( ) ;
9591097 this . _explicitShouldMock . clear ( ) ;
1098+ this . _explicitShouldMockModule . clear ( ) ;
9601099 this . _transitiveShouldMock . clear ( ) ;
9611100 this . _virtualMocks . clear ( ) ;
1101+ this . _virtualModuleMocks . clear ( ) ;
9621102 this . _cacheFS . clear ( ) ;
9631103 this . _unmockList = undefined ;
9641104
@@ -1350,8 +1490,11 @@ export default class Runtime {
13501490 ) ;
13511491 }
13521492
1353- private _shouldMock ( from : Config . Path , moduleName : string ) : boolean {
1354- const explicitShouldMock = this . _explicitShouldMock ;
1493+ private _shouldMock (
1494+ from : Config . Path ,
1495+ moduleName : string ,
1496+ explicitShouldMock : Map < string , boolean > ,
1497+ ) : boolean {
13551498 const moduleID = this . _resolver . getModuleID (
13561499 this . _virtualMocks ,
13571500 from ,
@@ -1519,6 +1662,24 @@ export default class Runtime {
15191662 this . setMock ( from , moduleName , mockFactory , options ) ;
15201663 return jestObject ;
15211664 } ;
1665+ const mockModule : Jest [ 'mockModule' ] = (
1666+ moduleName ,
1667+ mockFactory ,
1668+ options ,
1669+ ) => {
1670+ if ( mockFactory !== undefined ) {
1671+ this . setModuleMock ( from , moduleName , mockFactory , options ) ;
1672+ return jestObject ;
1673+ }
1674+
1675+ const moduleID = this . _resolver . getModuleID (
1676+ this . _virtualMocks ,
1677+ from ,
1678+ moduleName ,
1679+ ) ;
1680+ this . _explicitShouldMockModule . set ( moduleID , true ) ;
1681+ return jestObject ;
1682+ } ;
15221683 const clearAllMocks = ( ) => {
15231684 this . clearAllMocks ( ) ;
15241685 return jestObject ;
@@ -1617,6 +1778,7 @@ export default class Runtime {
16171778 isMockFunction : this . _moduleMocker . isMockFunction ,
16181779 isolateModules,
16191780 mock,
1781+ mockModule,
16201782 requireActual : this . requireActual . bind ( this , from ) ,
16211783 requireMock : this . requireMock . bind ( this , from ) ,
16221784 resetAllMocks,
0 commit comments