From efc76064d91308870bc55ef5da348308234cb7b3 Mon Sep 17 00:00:00 2001 From: Andrew Kushnir Date: Mon, 31 Aug 2020 16:52:40 -0700 Subject: [PATCH] fix(core): reset `tView` between tests in Ivy TestBed (#38659) `tView` that is stored on a component def contains information about directives and pipes that are available in the scope of this component. Patching component scope causes `tView` to be updated. Prior to this commit, the `tView` information was not restored/reset in case component class is not declared in the `declarations` field while calling `TestBed.configureTestingModule`, thus causing `tView` to be reused between tests (thus preserving scopes information between tests). This commit updates TestBed logic to preserve `tView` value before applying scope changes and reset it back to the previous state between tests. Closes #38600. PR Close #38659 --- packages/core/test/test_bed_spec.ts | 57 +++++++++++++++++++ .../core/testing/src/r3_test_bed_compiler.ts | 5 ++ 2 files changed, 62 insertions(+) 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); });