diff --git a/packages/core/src/core_private_export.ts b/packages/core/src/core_private_export.ts index c7413fb0c1..f5086aa77c 100644 --- a/packages/core/src/core_private_export.ts +++ b/packages/core/src/core_private_export.ts @@ -19,7 +19,7 @@ export {APP_ROOT as ɵAPP_ROOT} from './di/scope'; export {ivyEnabled as ɵivyEnabled} from './ivy_switch'; export {ComponentFactory as ɵComponentFactory} from './linker/component_factory'; 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 {GetterFn as ɵGetterFn, MethodFn as ɵMethodFn, SetterFn as ɵSetterFn} from './reflection/types'; export {DirectRenderer as ɵDirectRenderer, RenderDebugInfo as ɵRenderDebugInfo} from './render/api'; diff --git a/packages/core/src/metadata/resource_loading.ts b/packages/core/src/metadata/resource_loading.ts index 1870d997c0..257c937a27 100644 --- a/packages/core/src/metadata/resource_loading.ts +++ b/packages/core/src/metadata/resource_loading.ts @@ -79,7 +79,7 @@ export function resolveComponentResources( }); }); }); - componentResourceResolutionQueue.clear(); + clearResolutionOfComponentResourcesQueue(); return Promise.all(urlFetches).then(() => null); } diff --git a/packages/core/testing/BUILD.bazel b/packages/core/testing/BUILD.bazel index 5dcecb5300..da2e08ffe7 100644 --- a/packages/core/testing/BUILD.bazel +++ b/packages/core/testing/BUILD.bazel @@ -12,6 +12,7 @@ ng_module( module_name = "@angular/core/testing", deps = [ "//packages:types", + "//packages/compiler", "//packages/core", "@ngdeps//@types/jasmine", "@ngdeps//zone.js", diff --git a/packages/core/testing/src/r3_test_bed.ts b/packages/core/testing/src/r3_test_bed.ts index debfc9e285..120f33f5ec 100644 --- a/packages/core/testing/src/r3_test_bed.ts +++ b/packages/core/testing/src/r3_test_bed.ts @@ -34,7 +34,6 @@ import { ɵNG_PIPE_DEF as NG_PIPE_DEF, ɵNgModuleDef as NgModuleDef, ɵNgModuleFactory as R3NgModuleFactory, - ɵNgModuleTransitiveScopes as NgModuleTransitiveScopes, ɵNgModuleType as NgModuleType, ɵRender3ComponentFactory as ComponentFactory, ɵRender3NgModuleRef as NgModuleRef, @@ -46,10 +45,16 @@ import { ɵflushModuleScopingQueueAsMuchAsPossible as flushModuleScopingQueueAsMuchAsPossible, ɵpatchComponentDefWithScope as patchComponentDefWithScope, ɵresetCompiledComponents as resetCompiledComponents, - ɵstringify as stringify, ɵtransitiveScopesFor as transitiveScopesFor, + ɵstringify as stringify, + ɵtransitiveScopesFor as transitiveScopesFor, + CompilerOptions, + StaticProvider, + COMPILER_OPTIONS, } from '@angular/core'; // clang-format on +import {ResourceLoader} from '@angular/compiler'; +import {clearResolutionOfComponentResourcesQueue, resolveComponentResources} from '../../src/metadata/resource_loading'; import {ComponentFixture} from './component_fixture'; import {MetadataOverride} from './metadata_override'; import {ComponentResolver, DirectiveResolver, NgModuleResolver, PipeResolver, Resolver} from './resolvers'; @@ -229,6 +234,7 @@ export class TestBedRender3 implements Injector, TestBed { private _directiveOverrides: [Type, MetadataOverride][] = []; private _pipeOverrides: [Type, MetadataOverride][] = []; private _providerOverrides: Provider[] = []; + private _compilerProviders: StaticProvider[] = []; private _rootProviderOverrides: Provider[] = []; private _providerOverridesByToken: Map = new Map(); private _templateOverrides: Map, string> = new Map(); @@ -236,12 +242,14 @@ export class TestBedRender3 implements Injector, TestBed { // test module configuration private _providers: Provider[] = []; + private _compilerOptions: CompilerOptions[] = []; private _declarations: Array|any[]|any> = []; private _imports: Array|any[]|any> = []; private _schemas: Array = []; private _activeFixtures: ComponentFixture[] = []; + private _compilerInjector: Injector = null !; private _moduleRef: NgModuleRef = null !; private _testModuleType: NgModuleType = null !; @@ -302,12 +310,14 @@ export class TestBedRender3 implements Injector, TestBed { // reset test module config this._providers = []; + this._compilerOptions = []; this._declarations = []; this._imports = []; this._schemas = []; this._moduleRef = null !; this._testModuleType = null !; + this._compilerInjector = null !; this._instantiated = false; this._activeFixtures.forEach((fixture) => { try { @@ -326,6 +336,7 @@ export class TestBedRender3 implements Injector, TestBed { Object.defineProperty(type, value[0], value[1]); }); this._initiaNgDefs.clear(); + clearResolutionOfComponentResourcesQueue(); } configureCompiler(config: {providers?: any[]; useJit?: boolean;}): void { @@ -335,6 +346,7 @@ export class TestBedRender3 implements Injector, TestBed { if (config.providers) { this._providerOverrides.push(...config.providers); + this._compilerProviders.push(...config.providers); } } @@ -355,9 +367,37 @@ export class TestBedRender3 implements Injector, TestBed { } compileComponents(): Promise { - // assume for now that components don't use templateUrl / stylesUrl to unblock further testing - // TODO(pk): plug into the ivy's resource fetching pipeline - return Promise.resolve(); + const resolvers = this._getResolvers(); + const declarations: Type[] = flatten(this._declarations || EMPTY_ARRAY, resolveForwardRef); + + const componentOverrides: [Type, 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, 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 { @@ -549,6 +589,30 @@ export class TestBedRender3 implements Injector, TestBed { 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) { const overrides: {providers?: any[], template?: string} = {}; if (meta.providers && meta.providers.length) { @@ -659,19 +723,6 @@ export function _getTestBedRender3(): 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) { - if (type.hasOwnProperty(OWNER_MODULE)) { - (type as any)[OWNER_MODULE] = undefined; - } -} - function flatten(values: any[], mapFn?: (value: T) => any): T[] { const out: T[] = []; values.forEach(value => { diff --git a/packages/platform-browser/test/browser/bootstrap_spec.ts b/packages/platform-browser/test/browser/bootstrap_spec.ts index 073270b8f5..bc64dd6da8 100644 --- a/packages/platform-browser/test/browser/bootstrap_spec.ts +++ b/packages/platform-browser/test/browser/bootstrap_spec.ts @@ -161,7 +161,9 @@ function bootstrap( afterEach(destroyPlatform); // 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', inject([AsyncTestCompleter], (done: AsyncTestCompleter) => { const logger = new MockConsole(); @@ -190,7 +192,8 @@ function bootstrap( })); // 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', inject([AsyncTestCompleter], (async: AsyncTestCompleter) => { const logger = new MockConsole(); diff --git a/packages/platform-browser/test/testing_public_spec.ts b/packages/platform-browser/test/testing_public_spec.ts index f4c15da29a..bf25a8b037 100644 --- a/packages/platform-browser/test/testing_public_spec.ts +++ b/packages/platform-browser/test/testing_public_spec.ts @@ -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 {TestBed, async, fakeAsync, getTestBed, inject, tick, withModule} from '@angular/core/testing'; 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. @@ -311,12 +311,11 @@ class CompWithUrlTemplate { })); isBrowser && - fixmeIvy('FW-553: TestBed is unaware of async compilation') - .it('should allow to createSync components with templateUrl after explicit async compilation', - () => { - const fixture = TestBed.createComponent(CompWithUrlTemplate); - expect(fixture.nativeElement).toHaveText('from external template'); - }); + it('should allow to createSync components with templateUrl after explicit async compilation', + () => { + const fixture = TestBed.createComponent(CompWithUrlTemplate); + expect(fixture.nativeElement).toHaveText('from external template'); + }); }); describe('overwriting metadata', () => { @@ -792,13 +791,12 @@ class CompWithUrlTemplate { {providers: [{provide: ResourceLoader, useValue: {get: resourceLoaderGet}}]}); }); - fixmeIvy('FW-553: TestBed is unaware of async compilation') - .it('should use set up providers', fakeAsync(() => { - TestBed.compileComponents(); - tick(); - const compFixture = TestBed.createComponent(CompWithUrlTemplate); - expect(compFixture.nativeElement).toHaveText('Hello world!'); - })); + it('should use set up providers', fakeAsync(() => { + TestBed.compileComponents(); + tick(); + const compFixture = TestBed.createComponent(CompWithUrlTemplate); + expect(compFixture.nativeElement).toHaveText('Hello world!'); + })); }); describe('useJit true', () => { @@ -904,22 +902,25 @@ class CompWithUrlTemplate { {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', - () => { - const itPromise = patchJasmineIt(); + it('should report an error for declared components with templateUrl which never call TestBed.compileComponents', + () => { + const itPromise = patchJasmineIt(); - expect( - () => it( - 'should fail', withModule( - {declarations: [CompWithUrlTemplate]}, - () => TestBed.createComponent(CompWithUrlTemplate)))) - .toThrowError( - `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.`); + expect( + () => + it('should fail', withModule( + {declarations: [CompWithUrlTemplate]}, + () => TestBed.createComponent(CompWithUrlTemplate)))) + .toThrowError( + ivyEnabled ? + `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(() => { - const componentFixture = TestBed.createComponent(ParentComp); componentFixture.detectChanges(); expect(componentFixture.nativeElement).toHaveText('Parent(Mock)');