diff --git a/packages/core/src/metadata/resource_loading.ts b/packages/core/src/metadata/resource_loading.ts index 257c937a27..26691ce10a 100644 --- a/packages/core/src/metadata/resource_loading.ts +++ b/packages/core/src/metadata/resource_loading.ts @@ -91,8 +91,8 @@ export function maybeQueueResolutionOfComponentResources(metadata: Component) { } } -export function componentNeedsResolution(component: Component) { - return component.templateUrl || component.styleUrls && component.styleUrls.length; +export function componentNeedsResolution(component: Component): boolean { + return !!(component.templateUrl || component.styleUrls && component.styleUrls.length); } export function clearResolutionOfComponentResourcesQueue() { componentResourceResolutionQueue.clear(); @@ -100,4 +100,4 @@ export function clearResolutionOfComponentResourcesQueue() { function unwrapResponse(response: string | {text(): Promise}): string|Promise { return typeof response == 'string' ? response : response.text(); -} \ No newline at end of file +} diff --git a/packages/core/test/test_bed_spec.ts b/packages/core/test/test_bed_spec.ts index c76adf23e8..d1a0d2f3ae 100644 --- a/packages/core/test/test_bed_spec.ts +++ b/packages/core/test/test_bed_spec.ts @@ -79,6 +79,10 @@ export class ComponentWithPropBindings { export class SimpleApp { } +@Component({selector: 'inline-template', template: '

Hello

'}) +export class ComponentWithInlineTemplate { +} + @NgModule({ declarations: [ HelloWorld, SimpleCmp, WithRefsCmp, InheritedCmp, SimpleApp, ComponentWithPropBindings, @@ -232,6 +236,26 @@ describe('TestBed', () => { expect(simpleApp.nativeElement).toHaveText('simple - inherited'); }); + it('should resolve components without async resources synchronously', (done) => { + TestBed + .configureTestingModule({ + declarations: [ComponentWithInlineTemplate], + }) + .compileComponents() + .then(done) + .catch(error => { + // This should not throw any errors. If an error is thrown, the test will fail. + // Specifically use `catch` here to mark the test as done and *then* throw the error + // so that the test isn't treated as a timeout. + done(); + throw error; + }); + + // Intentionally call `createComponent` before `compileComponents` is resolved. We want this to + // work for components that don't have any async resources (templateUrl, styleUrls). + TestBed.createComponent(ComponentWithInlineTemplate); + }); + onlyInIvy('patched ng defs should be removed after resetting TestingModule') .it('make sure we restore ng defs to their initial states', () => { @Pipe({name: 'somePipe', pure: true}) diff --git a/packages/core/testing/src/r3_test_bed.ts b/packages/core/testing/src/r3_test_bed.ts index 5eec6ba78e..442e7b8ff1 100644 --- a/packages/core/testing/src/r3_test_bed.ts +++ b/packages/core/testing/src/r3_test_bed.ts @@ -54,7 +54,7 @@ import { // clang-format on import {ResourceLoader} from '@angular/compiler'; -import {clearResolutionOfComponentResourcesQueue, resolveComponentResources} from '../../src/metadata/resource_loading'; +import {clearResolutionOfComponentResourcesQueue, componentNeedsResolution, resolveComponentResources} from '../../src/metadata/resource_loading'; import {ComponentFixture} from './component_fixture'; import {MetadataOverride} from './metadata_override'; import {ComponentResolver, DirectiveResolver, NgModuleResolver, PipeResolver, Resolver} from './resolvers'; @@ -374,6 +374,8 @@ export class TestBedRender3 implements Injector, TestBed { const declarations: Type[] = flatten(this._declarations || EMPTY_ARRAY, resolveForwardRef); const componentOverrides: [Type, Component][] = []; + let hasAsyncResources = false; + // Compile the components declared by this module declarations.forEach(declaration => { const component = resolvers.component.resolve(declaration); @@ -382,25 +384,34 @@ export class TestBedRender3 implements Injector, TestBed { const metadata = {...component}; compileComponent(declaration, metadata); componentOverrides.push([declaration, metadata]); + hasAsyncResources = hasAsyncResources || componentNeedsResolution(component); } }); - let resourceLoader: ResourceLoader; + const overrideComponents = () => { + componentOverrides.forEach((override: [Type, Component]) => { + // 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]}); + }); + }; - 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]}); - }); - }); + // If the component has no async resources (templateUrl, styleUrls), we can finish + // synchronously. This is important so that users who mistakenly treat `compileComponents` + // as synchronous don't encounter an error, as ViewEngine was tolerant of this. + if (!hasAsyncResources) { + overrideComponents(); + return Promise.resolve(); + } else { + let resourceLoader: ResourceLoader; + return resolveComponentResources(url => { + if (!resourceLoader) { + resourceLoader = this.compilerInjector.get(ResourceLoader); + } + return Promise.resolve(resourceLoader.get(url)); + }) + .then(overrideComponents); + } } get(token: any, notFoundValue: any = Injector.THROW_IF_NOT_FOUND): any {