Skip to content

Commit

Permalink
fix(core): reset tView between tests in Ivy TestBed (#38659)
Browse files Browse the repository at this point in the history
`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
  • Loading branch information
AndrewKushnir authored and atscott committed Sep 3, 2020
1 parent dbab744 commit efc7606
Show file tree
Hide file tree
Showing 2 changed files with 62 additions and 0 deletions.
57 changes: 57 additions & 0 deletions packages/core/test/test_bed_spec.ts
Expand Up @@ -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: '<child></child>',
})
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()
Expand Down
5 changes: 5 additions & 0 deletions packages/core/testing/src/r3_test_bed_compiler.ts
Expand Up @@ -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);
});

Expand Down

0 comments on commit efc7606

Please sign in to comment.