@@ -57,6 +57,9 @@ export class R3TestBedCompiler {
57
57
private seenComponents = new Set < Type < any > > ( ) ;
58
58
private seenDirectives = new Set < Type < any > > ( ) ;
59
59
60
+ // Keep track of overridden modules, so that we can collect all affected ones in the module tree.
61
+ private overriddenModules = new Set < NgModuleType < any > > ( ) ;
62
+
60
63
// Store resolved styles for Components that have template overrides present and `styleUrls`
61
64
// defined at the same time.
62
65
private existingComponentStyles = new Map < Type < any > , string [ ] > ( ) ;
@@ -88,7 +91,6 @@ export class R3TestBedCompiler {
88
91
89
92
private testModuleType : NgModuleType < any > ;
90
93
private testModuleRef : NgModuleRef < any > | null = null ;
91
- private hasModuleOverrides : boolean = false ;
92
94
93
95
constructor ( private platform : PlatformRef , private additionalModuleTypes : Type < any > | Type < any > [ ] ) {
94
96
class DynamicTestModule { }
@@ -123,7 +125,7 @@ export class R3TestBedCompiler {
123
125
}
124
126
125
127
overrideModule ( ngModule : Type < any > , override : MetadataOverride < NgModule > ) : void {
126
- this . hasModuleOverrides = true ;
128
+ this . overriddenModules . add ( ngModule as NgModuleType < any > ) ;
127
129
128
130
// Compile the module right away.
129
131
this . resolvers . module . addOverride ( ngModule , override ) ;
@@ -348,21 +350,26 @@ export class R3TestBedCompiler {
348
350
}
349
351
350
352
private applyTransitiveScopes ( ) : void {
353
+ if ( this . overriddenModules . size > 0 ) {
354
+ // Module overrides (via `TestBed.overrideModule`) might affect scopes that were previously
355
+ // calculated and stored in `transitiveCompileScopes`. If module overrides are present,
356
+ // collect all affected modules and reset scopes to force their re-calculatation.
357
+ const testingModuleDef = ( this . testModuleType as any ) [ NG_MOD_DEF ] ;
358
+ const affectedModules = this . collectModulesAffectedByOverrides ( testingModuleDef . imports ) ;
359
+ if ( affectedModules . size > 0 ) {
360
+ affectedModules . forEach ( moduleType => {
361
+ this . storeFieldOfDefOnType ( moduleType as any , NG_MOD_DEF , 'transitiveCompileScopes' ) ;
362
+ ( moduleType as any ) [ NG_MOD_DEF ] . transitiveCompileScopes = null ;
363
+ } ) ;
364
+ }
365
+ }
366
+
351
367
const moduleToScope = new Map < Type < any > | TestingModuleOverride , NgModuleTransitiveScopes > ( ) ;
352
368
const getScopeOfModule =
353
369
( moduleType : Type < any > | TestingModuleOverride ) : NgModuleTransitiveScopes => {
354
370
if ( ! moduleToScope . has ( moduleType ) ) {
355
371
const isTestingModule = isTestingModuleOverride ( moduleType ) ;
356
372
const realType = isTestingModule ? this . testModuleType : moduleType as Type < any > ;
357
- // Module overrides (via TestBed.overrideModule) might affect scopes that were
358
- // previously calculated and stored in `transitiveCompileScopes`. If module overrides
359
- // are present, always re-calculate transitive scopes to have the most up-to-date
360
- // information available. The `moduleToScope` map avoids repeated re-calculation of
361
- // scopes for the same module.
362
- if ( ! isTestingModule && this . hasModuleOverrides ) {
363
- this . storeFieldOfDefOnType ( moduleType as any , NG_MOD_DEF , 'transitiveCompileScopes' ) ;
364
- ( moduleType as any ) [ NG_MOD_DEF ] . transitiveCompileScopes = null ;
365
- }
366
373
moduleToScope . set ( moduleType , transitiveScopesFor ( realType ) ) ;
367
374
}
368
375
return moduleToScope . get ( moduleType ) ! ;
@@ -532,6 +539,46 @@ export class R3TestBedCompiler {
532
539
queueTypesFromModulesArrayRecur ( arr ) ;
533
540
}
534
541
542
+ // When module overrides (via `TestBed.overrideModule`) are present, it might affect all modules
543
+ // that import (even transitively) an overridden one. For all affected modules we need to
544
+ // recalculate their scopes for a given test run and restore original scopes at the end. The goal
545
+ // of this function is to collect all affected modules in a set for further processing. Example:
546
+ // if we have the following module hierarchy: A -> B -> C (where `->` means `imports`) and module
547
+ // `C` is overridden, we consider `A` and `B` as affected, since their scopes might become
548
+ // invalidated with the override.
549
+ private collectModulesAffectedByOverrides ( arr : any [ ] ) : Set < NgModuleType < any > > {
550
+ const seenModules = new Set < NgModuleType < any > > ( ) ;
551
+ const affectedModules = new Set < NgModuleType < any > > ( ) ;
552
+ const calcAffectedModulesRecur = ( arr : any [ ] , path : NgModuleType < any > [ ] ) : void => {
553
+ for ( const value of arr ) {
554
+ if ( Array . isArray ( value ) ) {
555
+ // If the value is an array, just flatten it (by invoking this function recursively),
556
+ // keeping "path" the same.
557
+ calcAffectedModulesRecur ( value , path ) ;
558
+ } else if ( hasNgModuleDef ( value ) ) {
559
+ if ( seenModules . has ( value ) ) {
560
+ // If we've seen this module before and it's included into "affected modules" list, mark
561
+ // the whole path that leads to that module as affected, but do not descend into its
562
+ // imports, since we already examined them before.
563
+ if ( affectedModules . has ( value ) ) {
564
+ path . forEach ( item => affectedModules . add ( item ) ) ;
565
+ }
566
+ continue ;
567
+ }
568
+ seenModules . add ( value ) ;
569
+ if ( this . overriddenModules . has ( value ) ) {
570
+ path . forEach ( item => affectedModules . add ( item ) ) ;
571
+ }
572
+ // Examine module imports recursively to look for overridden modules.
573
+ const moduleDef = ( value as any ) [ NG_MOD_DEF ] ;
574
+ calcAffectedModulesRecur ( maybeUnwrapFn ( moduleDef . imports ) , path . concat ( value ) ) ;
575
+ }
576
+ }
577
+ } ;
578
+ calcAffectedModulesRecur ( arr , [ ] ) ;
579
+ return affectedModules ;
580
+ }
581
+
535
582
private maybeStoreNgDef ( prop : string , type : Type < any > ) {
536
583
if ( ! this . initialNgDefs . has ( type ) ) {
537
584
const currentDef = Object . getOwnPropertyDescriptor ( type , prop ) ;
0 commit comments