fix(ivy): implement 'TestBed.overrideTemplateUsingTestingModule' function (#27717)

Adding 'TestBed.overrideTemplateUsingTestingModule' function that overrides Component template and uses TestingModule as a scope.

PR Close #27717
This commit is contained in:
Andrew Kushnir 2018-12-17 14:47:51 -08:00 committed by Kara Erickson
parent 176b3f12f4
commit 38b4d15227
2 changed files with 73 additions and 56 deletions

View File

@ -124,7 +124,11 @@ export class TestBedRender3 implements Injector, TestBed {
} }
overrideTemplateUsingTestingModule(component: Type<any>, template: string): void { overrideTemplateUsingTestingModule(component: Type<any>, template: string): void {
throw new Error('Render3TestBed.overrideTemplateUsingTestingModule is not implemented yet'); if (this._instantiated) {
throw new Error(
'Cannot override template when the test module has already been instantiated');
}
this._templateOverrides.set(component, template);
} }
static overrideProvider(token: any, provider: { static overrideProvider(token: any, provider: {
@ -185,6 +189,7 @@ export class TestBedRender3 implements Injector, TestBed {
private _providerOverrides: Provider[] = []; private _providerOverrides: Provider[] = [];
private _rootProviderOverrides: Provider[] = []; private _rootProviderOverrides: Provider[] = [];
private _providerOverridesByToken: Map<any, Provider[]> = new Map(); private _providerOverridesByToken: Map<any, Provider[]> = new Map();
private _templateOverrides: Map<Type<any>, string> = new Map();
// test module configuration // test module configuration
private _providers: Provider[] = []; private _providers: Provider[] = [];
@ -195,6 +200,7 @@ export class TestBedRender3 implements Injector, TestBed {
private _activeFixtures: ComponentFixture<any>[] = []; private _activeFixtures: ComponentFixture<any>[] = [];
private _moduleRef: NgModuleRef<any> = null !; private _moduleRef: NgModuleRef<any> = null !;
private _testModuleType: NgModuleType<any> = null !;
private _instantiated: boolean = false; private _instantiated: boolean = false;
@ -241,6 +247,7 @@ export class TestBedRender3 implements Injector, TestBed {
this._providerOverrides = []; this._providerOverrides = [];
this._rootProviderOverrides = []; this._rootProviderOverrides = [];
this._providerOverridesByToken.clear(); this._providerOverridesByToken.clear();
this._templateOverrides.clear();
// reset test module config // reset test module config
this._providers = []; this._providers = [];
@ -248,6 +255,7 @@ export class TestBedRender3 implements Injector, TestBed {
this._imports = []; this._imports = [];
this._schemas = []; this._schemas = [];
this._moduleRef = null !; this._moduleRef = null !;
this._testModuleType = null !;
this._instantiated = false; this._instantiated = false;
this._activeFixtures.forEach((fixture) => { this._activeFixtures.forEach((fixture) => {
@ -402,11 +410,11 @@ export class TestBedRender3 implements Injector, TestBed {
} }
const resolvers = this._getResolvers(); const resolvers = this._getResolvers();
const testModuleType = this._createTestModule(); this._testModuleType = this._createTestModule();
this._compileNgModule(testModuleType, resolvers); this._compileNgModule(this._testModuleType, resolvers);
const parentInjector = this.platform.injector; const parentInjector = this.platform.injector;
this._moduleRef = new NgModuleRef(testModuleType, parentInjector); this._moduleRef = new NgModuleRef(this._testModuleType, parentInjector);
// ApplicationInitStatus.runInitializers() is marked @internal // ApplicationInitStatus.runInitializers() is marked @internal
// to core. Cast it to any before accessing it. // to core. Cast it to any before accessing it.
@ -472,15 +480,20 @@ export class TestBedRender3 implements Injector, TestBed {
return DynamicTestModule as NgModuleType; return DynamicTestModule as NgModuleType;
} }
private _getMetaWithOverrides(meta: Component|Directive|NgModule) { private _getMetaWithOverrides(meta: Component|Directive|NgModule, type?: Type<any>) {
const overrides: {providers?: any[], template?: string} = {};
if (meta.providers && meta.providers.length) { if (meta.providers && meta.providers.length) {
const overrides = const providerOverrides =
flatten(meta.providers, (provider: any) => this._getProviderOverrides(provider)); flatten(meta.providers, (provider: any) => this._getProviderOverrides(provider));
if (overrides.length) { if (providerOverrides.length) {
return {...meta, providers: [...meta.providers, ...overrides]}; overrides.providers = [...meta.providers, ...providerOverrides];
} }
} }
return meta; const hasTemplateOverride = !!type && this._templateOverrides.has(type);
if (hasTemplateOverride) {
overrides.template = this._templateOverrides.get(type !);
}
return Object.keys(overrides).length ? {...meta, ...overrides} : meta;
} }
private _compileNgModule(moduleType: NgModuleType, resolvers: Resolvers): void { private _compileNgModule(moduleType: NgModuleType, resolvers: Resolvers): void {
@ -501,7 +514,7 @@ export class TestBedRender3 implements Injector, TestBed {
declarations.forEach(declaration => { declarations.forEach(declaration => {
const component = resolvers.component.resolve(declaration); const component = resolvers.component.resolve(declaration);
if (component) { if (component) {
const metadata = this._getMetaWithOverrides(component); const metadata = this._getMetaWithOverrides(component, declaration);
compileComponent(declaration, metadata); compileComponent(declaration, metadata);
compiledComponents.push(declaration); compiledComponents.push(declaration);
return; return;
@ -523,8 +536,15 @@ export class TestBedRender3 implements Injector, TestBed {
// Compile transitive modules, components, directives and pipes // Compile transitive modules, components, directives and pipes
const transitiveScope = this._transitiveScopesFor(moduleType, resolvers); const transitiveScope = this._transitiveScopesFor(moduleType, resolvers);
compiledComponents.forEach( compiledComponents.forEach(cmp => {
cmp => patchComponentDefWithScope((cmp as any).ngComponentDef, transitiveScope)); const scope = this._templateOverrides.has(cmp) ?
// if we have template override via `TestBed.overrideTemplateUsingTestingModule` -
// define Component scope as TestingModule scope, instead of the scope of NgModule
// where this Component was declared
this._transitiveScopesFor(this._testModuleType, resolvers) :
transitiveScope;
patchComponentDefWithScope((cmp as any).ngComponentDef, scope);
});
} }
/** /**

View File

@ -727,61 +727,58 @@ class CompWithUrlTemplate {
}); });
describe('overrideTemplateUsingTestingModule', () => { describe('overrideTemplateUsingTestingModule', () => {
fixmeIvy('FW-851: TestBed.overrideTemplateUsingTestingModule is not implemented') it('should compile the template in the context of the testing module', () => {
.it('should compile the template in the context of the testing module', () => { @Component({selector: 'comp', template: 'a'})
@Component({selector: 'comp', template: 'a'}) class MyComponent {
class MyComponent { prop = 'some prop';
prop = 'some prop'; }
}
let testDir: TestDir|undefined; let testDir: TestDir|undefined;
@Directive({selector: '[test]'}) @Directive({selector: '[test]'})
class TestDir { class TestDir {
constructor() { testDir = this; } constructor() { testDir = this; }
// TODO(issue/24571): remove '!'. // TODO(issue/24571): remove '!'.
@Input('test') @Input('test')
test !: string; test !: string;
} }
TestBed.overrideTemplateUsingTestingModule( TestBed.overrideTemplateUsingTestingModule(
MyComponent, '<div [test]="prop">Hello world!</div>'); MyComponent, '<div [test]="prop">Hello world!</div>');
const fixture = TestBed.configureTestingModule({declarations: [MyComponent, TestDir]}) const fixture = TestBed.configureTestingModule({declarations: [MyComponent, TestDir]})
.createComponent(MyComponent); .createComponent(MyComponent);
fixture.detectChanges(); fixture.detectChanges();
expect(fixture.nativeElement).toHaveText('Hello world!'); expect(fixture.nativeElement).toHaveText('Hello world!');
expect(testDir).toBeAnInstanceOf(TestDir); expect(testDir).toBeAnInstanceOf(TestDir);
expect(testDir !.test).toBe('some prop'); expect(testDir !.test).toBe('some prop');
}); });
fixmeIvy('FW-851: TestBed.overrideTemplateUsingTestingModule is not implemented') it('should throw if the TestBed is already created', () => {
.it('should throw if the TestBed is already created', () => { @Component({selector: 'comp', template: 'a'})
@Component({selector: 'comp', template: 'a'}) class MyComponent {
class MyComponent { }
}
TestBed.get(Injector); TestBed.get(Injector);
expect(() => TestBed.overrideTemplateUsingTestingModule(MyComponent, 'b')) expect(() => TestBed.overrideTemplateUsingTestingModule(MyComponent, 'b'))
.toThrowError( .toThrowError(
/Cannot override template when the test module has already been instantiated/); /Cannot override template when the test module has already been instantiated/);
}); });
fixmeIvy('FW-851: TestBed.overrideTemplateUsingTestingModule is not implemented') it('should reset overrides when the testing module is resetted', () => {
.it('should reset overrides when the testing module is resetted', () => { @Component({selector: 'comp', template: 'a'})
@Component({selector: 'comp', template: 'a'}) class MyComponent {
class MyComponent { }
}
TestBed.overrideTemplateUsingTestingModule(MyComponent, 'b'); TestBed.overrideTemplateUsingTestingModule(MyComponent, 'b');
const fixture = TestBed.resetTestingModule() const fixture = TestBed.resetTestingModule()
.configureTestingModule({declarations: [MyComponent]}) .configureTestingModule({declarations: [MyComponent]})
.createComponent(MyComponent); .createComponent(MyComponent);
expect(fixture.nativeElement).toHaveText('a'); expect(fixture.nativeElement).toHaveText('a');
}); });
}); });
describe('setting up the compiler', () => { describe('setting up the compiler', () => {