diff --git a/packages/core/test/test_bed_spec.ts b/packages/core/test/test_bed_spec.ts index 02386f5ab1592..e1d78c0cba56b 100644 --- a/packages/core/test/test_bed_spec.ts +++ b/packages/core/test/test_bed_spec.ts @@ -1126,6 +1126,63 @@ describe('TestBed', () => { expect(SomePipe.hasOwnProperty('ɵpipe')).toBeTruthy(); }); + it('should cleanup scopes (configured via `TestBed.configureTestingModule`) between tests', + () => { + @Component({ + selector: 'child', + template: 'Child comp', + }) + class ChildCmp { + } + + @Component({ + selector: 'root', + template: '', + }) + class RootCmp { + } + + // Case #1: `RootCmp` and `ChildCmp` are both included in the `declarations` field of + // the testing module, so `ChildCmp` is in the scope of `RootCmp`. + TestBed.configureTestingModule({ + declarations: [RootCmp, ChildCmp], + }); + + let fixture = TestBed.createComponent(RootCmp); + fixture.detectChanges(); + + let childCmpInstance = fixture.debugElement.query(By.directive(ChildCmp)); + expect(childCmpInstance.componentInstance).toBeAnInstanceOf(ChildCmp); + expect(fixture.nativeElement.textContent).toBe('Child comp'); + + TestBed.resetTestingModule(); + + // Case #2: the `TestBed.configureTestingModule` was not invoked, thus the `ChildCmp` + // should not be available in the `RootCmp` scope and no child content should be + // rendered. + fixture = TestBed.createComponent(RootCmp); + fixture.detectChanges(); + + childCmpInstance = fixture.debugElement.query(By.directive(ChildCmp)); + expect(childCmpInstance).toBeNull(); + expect(fixture.nativeElement.textContent).toBe(''); + + TestBed.resetTestingModule(); + + // Case #3: `ChildCmp` is included in the `declarations` field, but `RootCmp` is not, + // so `ChildCmp` is NOT in the scope of `RootCmp` component. + TestBed.configureTestingModule({ + declarations: [ChildCmp], + }); + + fixture = TestBed.createComponent(RootCmp); + fixture.detectChanges(); + + childCmpInstance = fixture.debugElement.query(By.directive(ChildCmp)); + expect(childCmpInstance).toBeNull(); + expect(fixture.nativeElement.textContent).toBe(''); + }); + it('should clean up overridden providers for modules that are imported more than once', () => { @Injectable() diff --git a/packages/core/testing/src/r3_test_bed_compiler.ts b/packages/core/testing/src/r3_test_bed_compiler.ts index 2fc853801a52d..6c0c1f6feffce 100644 --- a/packages/core/testing/src/r3_test_bed_compiler.ts +++ b/packages/core/testing/src/r3_test_bed_compiler.ts @@ -379,6 +379,11 @@ export class R3TestBedCompiler { const moduleScope = getScopeOfModule(moduleType); this.storeFieldOfDefOnType(componentType, NG_COMP_DEF, 'directiveDefs'); this.storeFieldOfDefOnType(componentType, NG_COMP_DEF, 'pipeDefs'); + // `tView` that is stored on component def contains information about directives and pipes + // that are in the scope of this component. Patching component scope will cause `tView` to be + // changed. Store original `tView` before patching scope, so the `tView` (including scope + // information) is restored back to its previous/original state before running next test. + this.storeFieldOfDefOnType(componentType, NG_COMP_DEF, 'tView'); patchComponentDefWithScope((componentType as any).ɵcmp, moduleScope); });