feat(ivy): implement compileComponents
method for TestBedRender3
(#27778)
The implementation of the `compileComponents` method for `TestBedRender3` was missing. We now pass each component through `resolveComponentResources` when `TestBed.compileComponents` is called so that `templateUrl` and `styleUrls` can be resolved asynchronously and used once `TestBed.createComponent` is called. The component's metadata are overriden in `TestBed` instead of mutating the original metadata like this is the case outside of TestBed. The reason for that is that we need to ensure that we didn't mutate anything so that the following tests can run with the same original metadata, otherwise we it could trigger or hide some errors. FW-553 #resolve PR Close #27778
This commit is contained in:
parent
3a31a2795e
commit
29bff0f02e
@ -19,7 +19,7 @@ export {APP_ROOT as ɵAPP_ROOT} from './di/scope';
|
|||||||
export {ivyEnabled as ɵivyEnabled} from './ivy_switch';
|
export {ivyEnabled as ɵivyEnabled} from './ivy_switch';
|
||||||
export {ComponentFactory as ɵComponentFactory} from './linker/component_factory';
|
export {ComponentFactory as ɵComponentFactory} from './linker/component_factory';
|
||||||
export {CodegenComponentFactoryResolver as ɵCodegenComponentFactoryResolver} from './linker/component_factory_resolver';
|
export {CodegenComponentFactoryResolver as ɵCodegenComponentFactoryResolver} from './linker/component_factory_resolver';
|
||||||
export {resolveComponentResources as ɵresolveComponentResources} from './metadata/resource_loading';
|
export {clearResolutionOfComponentResourcesQueue as ɵclearResolutionOfComponentResourcesQueue, resolveComponentResources as ɵresolveComponentResources} from './metadata/resource_loading';
|
||||||
export {ReflectionCapabilities as ɵReflectionCapabilities} from './reflection/reflection_capabilities';
|
export {ReflectionCapabilities as ɵReflectionCapabilities} from './reflection/reflection_capabilities';
|
||||||
export {GetterFn as ɵGetterFn, MethodFn as ɵMethodFn, SetterFn as ɵSetterFn} from './reflection/types';
|
export {GetterFn as ɵGetterFn, MethodFn as ɵMethodFn, SetterFn as ɵSetterFn} from './reflection/types';
|
||||||
export {DirectRenderer as ɵDirectRenderer, RenderDebugInfo as ɵRenderDebugInfo} from './render/api';
|
export {DirectRenderer as ɵDirectRenderer, RenderDebugInfo as ɵRenderDebugInfo} from './render/api';
|
||||||
|
@ -79,7 +79,7 @@ export function resolveComponentResources(
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
componentResourceResolutionQueue.clear();
|
clearResolutionOfComponentResourcesQueue();
|
||||||
return Promise.all(urlFetches).then(() => null);
|
return Promise.all(urlFetches).then(() => null);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -12,6 +12,7 @@ ng_module(
|
|||||||
module_name = "@angular/core/testing",
|
module_name = "@angular/core/testing",
|
||||||
deps = [
|
deps = [
|
||||||
"//packages:types",
|
"//packages:types",
|
||||||
|
"//packages/compiler",
|
||||||
"//packages/core",
|
"//packages/core",
|
||||||
"@ngdeps//@types/jasmine",
|
"@ngdeps//@types/jasmine",
|
||||||
"@ngdeps//zone.js",
|
"@ngdeps//zone.js",
|
||||||
|
@ -34,7 +34,6 @@ import {
|
|||||||
ɵNG_PIPE_DEF as NG_PIPE_DEF,
|
ɵNG_PIPE_DEF as NG_PIPE_DEF,
|
||||||
ɵNgModuleDef as NgModuleDef,
|
ɵNgModuleDef as NgModuleDef,
|
||||||
ɵNgModuleFactory as R3NgModuleFactory,
|
ɵNgModuleFactory as R3NgModuleFactory,
|
||||||
ɵNgModuleTransitiveScopes as NgModuleTransitiveScopes,
|
|
||||||
ɵNgModuleType as NgModuleType,
|
ɵNgModuleType as NgModuleType,
|
||||||
ɵRender3ComponentFactory as ComponentFactory,
|
ɵRender3ComponentFactory as ComponentFactory,
|
||||||
ɵRender3NgModuleRef as NgModuleRef,
|
ɵRender3NgModuleRef as NgModuleRef,
|
||||||
@ -46,10 +45,16 @@ import {
|
|||||||
ɵflushModuleScopingQueueAsMuchAsPossible as flushModuleScopingQueueAsMuchAsPossible,
|
ɵflushModuleScopingQueueAsMuchAsPossible as flushModuleScopingQueueAsMuchAsPossible,
|
||||||
ɵpatchComponentDefWithScope as patchComponentDefWithScope,
|
ɵpatchComponentDefWithScope as patchComponentDefWithScope,
|
||||||
ɵresetCompiledComponents as resetCompiledComponents,
|
ɵresetCompiledComponents as resetCompiledComponents,
|
||||||
ɵstringify as stringify, ɵtransitiveScopesFor as transitiveScopesFor,
|
ɵstringify as stringify,
|
||||||
|
ɵtransitiveScopesFor as transitiveScopesFor,
|
||||||
|
CompilerOptions,
|
||||||
|
StaticProvider,
|
||||||
|
COMPILER_OPTIONS,
|
||||||
} from '@angular/core';
|
} from '@angular/core';
|
||||||
// clang-format on
|
// clang-format on
|
||||||
|
import {ResourceLoader} from '@angular/compiler';
|
||||||
|
|
||||||
|
import {clearResolutionOfComponentResourcesQueue, resolveComponentResources} from '../../src/metadata/resource_loading';
|
||||||
import {ComponentFixture} from './component_fixture';
|
import {ComponentFixture} from './component_fixture';
|
||||||
import {MetadataOverride} from './metadata_override';
|
import {MetadataOverride} from './metadata_override';
|
||||||
import {ComponentResolver, DirectiveResolver, NgModuleResolver, PipeResolver, Resolver} from './resolvers';
|
import {ComponentResolver, DirectiveResolver, NgModuleResolver, PipeResolver, Resolver} from './resolvers';
|
||||||
@ -229,6 +234,7 @@ export class TestBedRender3 implements Injector, TestBed {
|
|||||||
private _directiveOverrides: [Type<any>, MetadataOverride<Directive>][] = [];
|
private _directiveOverrides: [Type<any>, MetadataOverride<Directive>][] = [];
|
||||||
private _pipeOverrides: [Type<any>, MetadataOverride<Pipe>][] = [];
|
private _pipeOverrides: [Type<any>, MetadataOverride<Pipe>][] = [];
|
||||||
private _providerOverrides: Provider[] = [];
|
private _providerOverrides: Provider[] = [];
|
||||||
|
private _compilerProviders: StaticProvider[] = [];
|
||||||
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();
|
private _templateOverrides: Map<Type<any>, string> = new Map();
|
||||||
@ -236,12 +242,14 @@ export class TestBedRender3 implements Injector, TestBed {
|
|||||||
|
|
||||||
// test module configuration
|
// test module configuration
|
||||||
private _providers: Provider[] = [];
|
private _providers: Provider[] = [];
|
||||||
|
private _compilerOptions: CompilerOptions[] = [];
|
||||||
private _declarations: Array<Type<any>|any[]|any> = [];
|
private _declarations: Array<Type<any>|any[]|any> = [];
|
||||||
private _imports: Array<Type<any>|any[]|any> = [];
|
private _imports: Array<Type<any>|any[]|any> = [];
|
||||||
private _schemas: Array<SchemaMetadata|any[]> = [];
|
private _schemas: Array<SchemaMetadata|any[]> = [];
|
||||||
|
|
||||||
private _activeFixtures: ComponentFixture<any>[] = [];
|
private _activeFixtures: ComponentFixture<any>[] = [];
|
||||||
|
|
||||||
|
private _compilerInjector: Injector = null !;
|
||||||
private _moduleRef: NgModuleRef<any> = null !;
|
private _moduleRef: NgModuleRef<any> = null !;
|
||||||
private _testModuleType: NgModuleType<any> = null !;
|
private _testModuleType: NgModuleType<any> = null !;
|
||||||
|
|
||||||
@ -302,12 +310,14 @@ export class TestBedRender3 implements Injector, TestBed {
|
|||||||
|
|
||||||
// reset test module config
|
// reset test module config
|
||||||
this._providers = [];
|
this._providers = [];
|
||||||
|
this._compilerOptions = [];
|
||||||
this._declarations = [];
|
this._declarations = [];
|
||||||
this._imports = [];
|
this._imports = [];
|
||||||
this._schemas = [];
|
this._schemas = [];
|
||||||
this._moduleRef = null !;
|
this._moduleRef = null !;
|
||||||
this._testModuleType = null !;
|
this._testModuleType = null !;
|
||||||
|
|
||||||
|
this._compilerInjector = null !;
|
||||||
this._instantiated = false;
|
this._instantiated = false;
|
||||||
this._activeFixtures.forEach((fixture) => {
|
this._activeFixtures.forEach((fixture) => {
|
||||||
try {
|
try {
|
||||||
@ -326,6 +336,7 @@ export class TestBedRender3 implements Injector, TestBed {
|
|||||||
Object.defineProperty(type, value[0], value[1]);
|
Object.defineProperty(type, value[0], value[1]);
|
||||||
});
|
});
|
||||||
this._initiaNgDefs.clear();
|
this._initiaNgDefs.clear();
|
||||||
|
clearResolutionOfComponentResourcesQueue();
|
||||||
}
|
}
|
||||||
|
|
||||||
configureCompiler(config: {providers?: any[]; useJit?: boolean;}): void {
|
configureCompiler(config: {providers?: any[]; useJit?: boolean;}): void {
|
||||||
@ -335,6 +346,7 @@ export class TestBedRender3 implements Injector, TestBed {
|
|||||||
|
|
||||||
if (config.providers) {
|
if (config.providers) {
|
||||||
this._providerOverrides.push(...config.providers);
|
this._providerOverrides.push(...config.providers);
|
||||||
|
this._compilerProviders.push(...config.providers);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -355,9 +367,37 @@ export class TestBedRender3 implements Injector, TestBed {
|
|||||||
}
|
}
|
||||||
|
|
||||||
compileComponents(): Promise<any> {
|
compileComponents(): Promise<any> {
|
||||||
// assume for now that components don't use templateUrl / stylesUrl to unblock further testing
|
const resolvers = this._getResolvers();
|
||||||
// TODO(pk): plug into the ivy's resource fetching pipeline
|
const declarations: Type<any>[] = flatten(this._declarations || EMPTY_ARRAY, resolveForwardRef);
|
||||||
return Promise.resolve();
|
|
||||||
|
const componentOverrides: [Type<any>, Component][] = [];
|
||||||
|
// Compile the components declared by this module
|
||||||
|
declarations.forEach(declaration => {
|
||||||
|
const component = resolvers.component.resolve(declaration);
|
||||||
|
if (component) {
|
||||||
|
// We make a copy of the metadata to ensure that we don't mutate the original metadata
|
||||||
|
const metadata = {...component};
|
||||||
|
compileComponent(declaration, metadata);
|
||||||
|
componentOverrides.push([declaration, metadata]);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
let resourceLoader: ResourceLoader;
|
||||||
|
|
||||||
|
return resolveComponentResources(url => {
|
||||||
|
if (!resourceLoader) {
|
||||||
|
resourceLoader = this.compilerInjector.get(ResourceLoader);
|
||||||
|
}
|
||||||
|
return Promise.resolve(resourceLoader.get(url));
|
||||||
|
})
|
||||||
|
.then(() => {
|
||||||
|
componentOverrides.forEach((override: [Type<any>, Component]) => {
|
||||||
|
// Once resolved, we override the existing metadata, ensuring that the resolved
|
||||||
|
// resources
|
||||||
|
// are only available until the next TestBed reset (when `resetTestingModule` is called)
|
||||||
|
this.overrideComponent(override[0], {set: override[1]});
|
||||||
|
});
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
get(token: any, notFoundValue: any = Injector.THROW_IF_NOT_FOUND): any {
|
get(token: any, notFoundValue: any = Injector.THROW_IF_NOT_FOUND): any {
|
||||||
@ -549,6 +589,30 @@ export class TestBedRender3 implements Injector, TestBed {
|
|||||||
return DynamicTestModule as NgModuleType;
|
return DynamicTestModule as NgModuleType;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
get compilerInjector(): Injector {
|
||||||
|
if (this._compilerInjector !== undefined) {
|
||||||
|
this._compilerInjector;
|
||||||
|
}
|
||||||
|
|
||||||
|
const providers: StaticProvider[] = [];
|
||||||
|
const compilerOptions = this.platform.injector.get(COMPILER_OPTIONS);
|
||||||
|
compilerOptions.forEach(opts => {
|
||||||
|
if (opts.providers) {
|
||||||
|
providers.push(opts.providers);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
providers.push(...this._compilerProviders);
|
||||||
|
|
||||||
|
// TODO(ocombe): make this work with an Injector directly instead of creating a module for it
|
||||||
|
@NgModule({providers})
|
||||||
|
class CompilerModule {
|
||||||
|
}
|
||||||
|
|
||||||
|
const CompilerModuleFactory = new R3NgModuleFactory(CompilerModule);
|
||||||
|
this._compilerInjector = CompilerModuleFactory.create(this.platform.injector).injector;
|
||||||
|
return this._compilerInjector;
|
||||||
|
}
|
||||||
|
|
||||||
private _getMetaWithOverrides(meta: Component|Directive|NgModule, type?: Type<any>) {
|
private _getMetaWithOverrides(meta: Component|Directive|NgModule, type?: Type<any>) {
|
||||||
const overrides: {providers?: any[], template?: string} = {};
|
const overrides: {providers?: any[], template?: string} = {};
|
||||||
if (meta.providers && meta.providers.length) {
|
if (meta.providers && meta.providers.length) {
|
||||||
@ -659,19 +723,6 @@ export function _getTestBedRender3(): TestBedRender3 {
|
|||||||
return testBed = testBed || new TestBedRender3();
|
return testBed = testBed || new TestBedRender3();
|
||||||
}
|
}
|
||||||
|
|
||||||
const OWNER_MODULE = '__NG_MODULE__';
|
|
||||||
/**
|
|
||||||
* This function clears the OWNER_MODULE property from the Types. This is set in
|
|
||||||
* r3/jit/modules.ts. It is common for the same Type to be compiled in different tests. If we don't
|
|
||||||
* clear this we will get errors which will complain that the same Component/Directive is in more
|
|
||||||
* than one NgModule.
|
|
||||||
*/
|
|
||||||
function clearNgModules(type: Type<any>) {
|
|
||||||
if (type.hasOwnProperty(OWNER_MODULE)) {
|
|
||||||
(type as any)[OWNER_MODULE] = undefined;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function flatten<T>(values: any[], mapFn?: (value: T) => any): T[] {
|
function flatten<T>(values: any[], mapFn?: (value: T) => any): T[] {
|
||||||
const out: T[] = [];
|
const out: T[] = [];
|
||||||
values.forEach(value => {
|
values.forEach(value => {
|
||||||
|
@ -161,7 +161,9 @@ function bootstrap(
|
|||||||
afterEach(destroyPlatform);
|
afterEach(destroyPlatform);
|
||||||
|
|
||||||
// TODO(misko): can't use `fixmeIvy.it` because the `it` is somehow special here.
|
// TODO(misko): can't use `fixmeIvy.it` because the `it` is somehow special here.
|
||||||
fixmeIvy('FW-553: TestBed is unaware of async compilation').isEnabled &&
|
fixmeIvy(
|
||||||
|
'FW-876: Bootstrap factory method should throw if bootstrapped Directive is not a Component')
|
||||||
|
.isEnabled &&
|
||||||
it('should throw if bootstrapped Directive is not a Component',
|
it('should throw if bootstrapped Directive is not a Component',
|
||||||
inject([AsyncTestCompleter], (done: AsyncTestCompleter) => {
|
inject([AsyncTestCompleter], (done: AsyncTestCompleter) => {
|
||||||
const logger = new MockConsole();
|
const logger = new MockConsole();
|
||||||
@ -190,7 +192,8 @@ function bootstrap(
|
|||||||
}));
|
}));
|
||||||
|
|
||||||
// TODO(misko): can't use `fixmeIvy.it` because the `it` is somehow special here.
|
// TODO(misko): can't use `fixmeIvy.it` because the `it` is somehow special here.
|
||||||
fixmeIvy('FW-553: TestBed is unaware of async compilation').isEnabled &&
|
fixmeIvy('FW-875: The source of the error is missing in the `StaticInjectorError` message')
|
||||||
|
.isEnabled &&
|
||||||
it('should throw if no provider',
|
it('should throw if no provider',
|
||||||
inject([AsyncTestCompleter], (async: AsyncTestCompleter) => {
|
inject([AsyncTestCompleter], (async: AsyncTestCompleter) => {
|
||||||
const logger = new MockConsole();
|
const logger = new MockConsole();
|
||||||
|
@ -10,7 +10,7 @@ import {CompilerConfig, ResourceLoader} from '@angular/compiler';
|
|||||||
import {CUSTOM_ELEMENTS_SCHEMA, Compiler, Component, Directive, Inject, Injectable, Injector, Input, NgModule, Optional, Pipe, SkipSelf, ɵstringify as stringify} from '@angular/core';
|
import {CUSTOM_ELEMENTS_SCHEMA, Compiler, Component, Directive, Inject, Injectable, Injector, Input, NgModule, Optional, Pipe, SkipSelf, ɵstringify as stringify} from '@angular/core';
|
||||||
import {TestBed, async, fakeAsync, getTestBed, inject, tick, withModule} from '@angular/core/testing';
|
import {TestBed, async, fakeAsync, getTestBed, inject, tick, withModule} from '@angular/core/testing';
|
||||||
import {expect} from '@angular/platform-browser/testing/src/matchers';
|
import {expect} from '@angular/platform-browser/testing/src/matchers';
|
||||||
import {fixmeIvy, obsoleteInIvy} from '@angular/private/testing';
|
import {fixmeIvy, ivyEnabled, obsoleteInIvy} from '@angular/private/testing';
|
||||||
|
|
||||||
// Services, and components for the tests.
|
// Services, and components for the tests.
|
||||||
|
|
||||||
@ -311,12 +311,11 @@ class CompWithUrlTemplate {
|
|||||||
}));
|
}));
|
||||||
|
|
||||||
isBrowser &&
|
isBrowser &&
|
||||||
fixmeIvy('FW-553: TestBed is unaware of async compilation')
|
it('should allow to createSync components with templateUrl after explicit async compilation',
|
||||||
.it('should allow to createSync components with templateUrl after explicit async compilation',
|
() => {
|
||||||
() => {
|
const fixture = TestBed.createComponent(CompWithUrlTemplate);
|
||||||
const fixture = TestBed.createComponent(CompWithUrlTemplate);
|
expect(fixture.nativeElement).toHaveText('from external template');
|
||||||
expect(fixture.nativeElement).toHaveText('from external template');
|
});
|
||||||
});
|
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('overwriting metadata', () => {
|
describe('overwriting metadata', () => {
|
||||||
@ -792,13 +791,12 @@ class CompWithUrlTemplate {
|
|||||||
{providers: [{provide: ResourceLoader, useValue: {get: resourceLoaderGet}}]});
|
{providers: [{provide: ResourceLoader, useValue: {get: resourceLoaderGet}}]});
|
||||||
});
|
});
|
||||||
|
|
||||||
fixmeIvy('FW-553: TestBed is unaware of async compilation')
|
it('should use set up providers', fakeAsync(() => {
|
||||||
.it('should use set up providers', fakeAsync(() => {
|
TestBed.compileComponents();
|
||||||
TestBed.compileComponents();
|
tick();
|
||||||
tick();
|
const compFixture = TestBed.createComponent(CompWithUrlTemplate);
|
||||||
const compFixture = TestBed.createComponent(CompWithUrlTemplate);
|
expect(compFixture.nativeElement).toHaveText('Hello world!');
|
||||||
expect(compFixture.nativeElement).toHaveText('Hello world!');
|
}));
|
||||||
}));
|
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('useJit true', () => {
|
describe('useJit true', () => {
|
||||||
@ -904,22 +902,25 @@ class CompWithUrlTemplate {
|
|||||||
{providers: [{provide: ResourceLoader, useValue: {get: resourceLoaderGet}}]});
|
{providers: [{provide: ResourceLoader, useValue: {get: resourceLoaderGet}}]});
|
||||||
});
|
});
|
||||||
|
|
||||||
fixmeIvy('FW-553: TestBed is unaware of async compilation')
|
it('should report an error for declared components with templateUrl which never call TestBed.compileComponents',
|
||||||
.it('should report an error for declared components with templateUrl which never call TestBed.compileComponents',
|
() => {
|
||||||
() => {
|
const itPromise = patchJasmineIt();
|
||||||
const itPromise = patchJasmineIt();
|
|
||||||
|
|
||||||
expect(
|
expect(
|
||||||
() => it(
|
() =>
|
||||||
'should fail', withModule(
|
it('should fail', withModule(
|
||||||
{declarations: [CompWithUrlTemplate]},
|
{declarations: [CompWithUrlTemplate]},
|
||||||
() => TestBed.createComponent(CompWithUrlTemplate))))
|
() => TestBed.createComponent(CompWithUrlTemplate))))
|
||||||
.toThrowError(
|
.toThrowError(
|
||||||
`This test module uses the component ${stringify(CompWithUrlTemplate)} which is using a "templateUrl" or "styleUrls", but they were never compiled. ` +
|
ivyEnabled ?
|
||||||
`Please call "TestBed.compileComponents" before your test.`);
|
`Component 'CompWithUrlTemplate' is not resolved:
|
||||||
|
- templateUrl: /base/angular/packages/platform-browser/test/static_assets/test.html
|
||||||
|
Did you run and wait for 'resolveComponentResources()'?` :
|
||||||
|
`This test module uses the component ${stringify(CompWithUrlTemplate)} which is using a "templateUrl" or "styleUrls", but they were never compiled. ` +
|
||||||
|
`Please call "TestBed.compileComponents" before your test.`);
|
||||||
|
|
||||||
restoreJasmineIt();
|
restoreJasmineIt();
|
||||||
});
|
});
|
||||||
|
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -1015,7 +1016,6 @@ class CompWithUrlTemplate {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it('should override component dependencies', async(() => {
|
it('should override component dependencies', async(() => {
|
||||||
|
|
||||||
const componentFixture = TestBed.createComponent(ParentComp);
|
const componentFixture = TestBed.createComponent(ParentComp);
|
||||||
componentFixture.detectChanges();
|
componentFixture.detectChanges();
|
||||||
expect(componentFixture.nativeElement).toHaveText('Parent(Mock)');
|
expect(componentFixture.nativeElement).toHaveText('Parent(Mock)');
|
||||||
|
Loading…
x
Reference in New Issue
Block a user