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 {
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: {
@ -185,6 +189,7 @@ export class TestBedRender3 implements Injector, TestBed {
private _providerOverrides: Provider[] = [];
private _rootProviderOverrides: Provider[] = [];
private _providerOverridesByToken: Map<any, Provider[]> = new Map();
private _templateOverrides: Map<Type<any>, string> = new Map();
// test module configuration
private _providers: Provider[] = [];
@ -195,6 +200,7 @@ export class TestBedRender3 implements Injector, TestBed {
private _activeFixtures: ComponentFixture<any>[] = [];
private _moduleRef: NgModuleRef<any> = null !;
private _testModuleType: NgModuleType<any> = null !;
private _instantiated: boolean = false;
@ -241,6 +247,7 @@ export class TestBedRender3 implements Injector, TestBed {
this._providerOverrides = [];
this._rootProviderOverrides = [];
this._providerOverridesByToken.clear();
this._templateOverrides.clear();
// reset test module config
this._providers = [];
@ -248,6 +255,7 @@ export class TestBedRender3 implements Injector, TestBed {
this._imports = [];
this._schemas = [];
this._moduleRef = null !;
this._testModuleType = null !;
this._instantiated = false;
this._activeFixtures.forEach((fixture) => {
@ -402,11 +410,11 @@ export class TestBedRender3 implements Injector, TestBed {
}
const resolvers = this._getResolvers();
const testModuleType = this._createTestModule();
this._compileNgModule(testModuleType, resolvers);
this._testModuleType = this._createTestModule();
this._compileNgModule(this._testModuleType, resolvers);
const parentInjector = this.platform.injector;
this._moduleRef = new NgModuleRef(testModuleType, parentInjector);
this._moduleRef = new NgModuleRef(this._testModuleType, parentInjector);
// ApplicationInitStatus.runInitializers() is marked @internal
// to core. Cast it to any before accessing it.
@ -472,15 +480,20 @@ export class TestBedRender3 implements Injector, TestBed {
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) {
const overrides =
const providerOverrides =
flatten(meta.providers, (provider: any) => this._getProviderOverrides(provider));
if (overrides.length) {
return {...meta, providers: [...meta.providers, ...overrides]};
if (providerOverrides.length) {
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 {
@ -501,7 +514,7 @@ export class TestBedRender3 implements Injector, TestBed {
declarations.forEach(declaration => {
const component = resolvers.component.resolve(declaration);
if (component) {
const metadata = this._getMetaWithOverrides(component);
const metadata = this._getMetaWithOverrides(component, declaration);
compileComponent(declaration, metadata);
compiledComponents.push(declaration);
return;
@ -523,8 +536,15 @@ export class TestBedRender3 implements Injector, TestBed {
// Compile transitive modules, components, directives and pipes
const transitiveScope = this._transitiveScopesFor(moduleType, resolvers);
compiledComponents.forEach(
cmp => patchComponentDefWithScope((cmp as any).ngComponentDef, transitiveScope));
compiledComponents.forEach(cmp => {
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', () => {
fixmeIvy('FW-851: TestBed.overrideTemplateUsingTestingModule is not implemented')
.it('should compile the template in the context of the testing module', () => {
@Component({selector: 'comp', template: 'a'})
class MyComponent {
prop = 'some prop';
}
it('should compile the template in the context of the testing module', () => {
@Component({selector: 'comp', template: 'a'})
class MyComponent {
prop = 'some prop';
}
let testDir: TestDir|undefined;
let testDir: TestDir|undefined;
@Directive({selector: '[test]'})
class TestDir {
constructor() { testDir = this; }
@Directive({selector: '[test]'})
class TestDir {
constructor() { testDir = this; }
// TODO(issue/24571): remove '!'.
@Input('test')
test !: string;
}
// TODO(issue/24571): remove '!'.
@Input('test')
test !: string;
}
TestBed.overrideTemplateUsingTestingModule(
MyComponent, '<div [test]="prop">Hello world!</div>');
TestBed.overrideTemplateUsingTestingModule(
MyComponent, '<div [test]="prop">Hello world!</div>');
const fixture = TestBed.configureTestingModule({declarations: [MyComponent, TestDir]})
.createComponent(MyComponent);
fixture.detectChanges();
expect(fixture.nativeElement).toHaveText('Hello world!');
expect(testDir).toBeAnInstanceOf(TestDir);
expect(testDir !.test).toBe('some prop');
});
const fixture = TestBed.configureTestingModule({declarations: [MyComponent, TestDir]})
.createComponent(MyComponent);
fixture.detectChanges();
expect(fixture.nativeElement).toHaveText('Hello world!');
expect(testDir).toBeAnInstanceOf(TestDir);
expect(testDir !.test).toBe('some prop');
});
fixmeIvy('FW-851: TestBed.overrideTemplateUsingTestingModule is not implemented')
.it('should throw if the TestBed is already created', () => {
@Component({selector: 'comp', template: 'a'})
class MyComponent {
}
it('should throw if the TestBed is already created', () => {
@Component({selector: 'comp', template: 'a'})
class MyComponent {
}
TestBed.get(Injector);
TestBed.get(Injector);
expect(() => TestBed.overrideTemplateUsingTestingModule(MyComponent, 'b'))
.toThrowError(
/Cannot override template when the test module has already been instantiated/);
});
expect(() => TestBed.overrideTemplateUsingTestingModule(MyComponent, 'b'))
.toThrowError(
/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', () => {
@Component({selector: 'comp', template: 'a'})
class MyComponent {
}
it('should reset overrides when the testing module is resetted', () => {
@Component({selector: 'comp', template: 'a'})
class MyComponent {
}
TestBed.overrideTemplateUsingTestingModule(MyComponent, 'b');
TestBed.overrideTemplateUsingTestingModule(MyComponent, 'b');
const fixture = TestBed.resetTestingModule()
.configureTestingModule({declarations: [MyComponent]})
.createComponent(MyComponent);
expect(fixture.nativeElement).toHaveText('a');
});
const fixture = TestBed.resetTestingModule()
.configureTestingModule({declarations: [MyComponent]})
.createComponent(MyComponent);
expect(fixture.nativeElement).toHaveText('a');
});
});
describe('setting up the compiler', () => {