Skip to content

Commit

Permalink
fix(ivy): TestBed should not clobber compilation of global-scope modules
Browse files Browse the repository at this point in the history
When an @NgModule decorator executes, the module is added to a queue in
render3/jit/module.ts. Reading an ngComponentDef property causes this queue
to be flushed, ensuring that the component gets the correct module scope
applied.

In before_each.ts, a global beforeEach is added to all Angular tests which
calls TestBed.resetTestingModule() prior to running each test. This in turn
clears the module compilation queue (which is correct behavior, as modules
declared within the test should not leak outside of it via the queue).

So far this is okay. But before the first test runs, the module compilation
queue is full of modules declared in global scope. No definitions have been
read, so no flushes of the queue have been triggered. The global beforeEach
triggers a reset of the queue, aborting all of the in-progress global
compilation, breaking those classes when they're later used in tests.

This commit adds logic to TestBedRender3 to respect the state of the module
queue before the TestBed is first initialized or reset. The queue is flushed
prior to such an operation to ensure global compilation is allowed to finish
properly.

With this fix, a platform-server test now passes (previously the <my-child>
element was not detected as a component, because the encompassing module
never finished compilation.

FW-887 #resolve
  • Loading branch information
alxhub committed Jan 10, 2019
1 parent 2ac973c commit 4c48219
Show file tree
Hide file tree
Showing 3 changed files with 34 additions and 10 deletions.
1 change: 1 addition & 0 deletions packages/core/src/core_render3_private_export.ts
Expand Up @@ -135,6 +135,7 @@ export {
compileNgModuleDefs as ɵcompileNgModuleDefs,
patchComponentDefWithScope as ɵpatchComponentDefWithScope,
resetCompiledComponents as ɵresetCompiledComponents,
flushModuleScopingQueueAsMuchAsPossible as ɵflushModuleScopingQueueAsMuchAsPossible,
transitiveScopesFor as ɵtransitiveScopesFor,
} from './render3/jit/module';
export {
Expand Down
26 changes: 25 additions & 1 deletion packages/core/testing/src/r3_test_bed.ts
Expand Up @@ -6,7 +6,7 @@
* found in the LICENSE file at https://angular.io/license
*/

import {ApplicationInitStatus, Compiler, Component, Directive, Injector, ModuleWithComponentFactories, NgModule, NgModuleFactory, ɵNgModuleFactory as R3NgModuleFactory, NgZone, Pipe, PlatformRef, Provider, SchemaMetadata, Type, resolveForwardRef, ɵInjectableDef as InjectableDef, ɵNG_COMPONENT_DEF as NG_COMPONENT_DEF, ɵNG_DIRECTIVE_DEF as NG_DIRECTIVE_DEF, ɵNG_INJECTOR_DEF as NG_INJECTOR_DEF, ɵNG_MODULE_DEF as NG_MODULE_DEF, ɵNG_PIPE_DEF as NG_PIPE_DEF, ɵNgModuleDef as NgModuleDef, ɵNgModuleTransitiveScopes as NgModuleTransitiveScopes, ɵNgModuleType as NgModuleType, ɵRender3ComponentFactory as ComponentFactory, ɵRender3NgModuleRef as NgModuleRef, ɵcompileComponent as compileComponent, ɵcompileDirective as compileDirective, ɵcompileNgModuleDefs as compileNgModuleDefs, ɵcompilePipe as compilePipe, ɵgetInjectableDef as getInjectableDef, ɵpatchComponentDefWithScope as patchComponentDefWithScope, ɵresetCompiledComponents as resetCompiledComponents, ɵstringify as stringify, ɵtransitiveScopesFor as transitiveScopesFor} from '@angular/core';
import {ApplicationInitStatus, Compiler, Component, Directive, Injector, ModuleWithComponentFactories, NgModule, NgModuleFactory, ɵNgModuleFactory as R3NgModuleFactory, NgZone, Pipe, PlatformRef, Provider, SchemaMetadata, Type, resolveForwardRef, ɵInjectableDef as InjectableDef, ɵNG_COMPONENT_DEF as NG_COMPONENT_DEF, ɵNG_DIRECTIVE_DEF as NG_DIRECTIVE_DEF, ɵNG_INJECTOR_DEF as NG_INJECTOR_DEF, ɵNG_MODULE_DEF as NG_MODULE_DEF, ɵNG_PIPE_DEF as NG_PIPE_DEF, ɵNgModuleDef as NgModuleDef, ɵNgModuleTransitiveScopes as NgModuleTransitiveScopes, ɵNgModuleType as NgModuleType, ɵRender3ComponentFactory as ComponentFactory, ɵRender3NgModuleRef as NgModuleRef, ɵcompileComponent as compileComponent, ɵcompileDirective as compileDirective, ɵcompileNgModuleDefs as compileNgModuleDefs, ɵcompilePipe as compilePipe, ɵflushModuleScopingQueueAsMuchAsPossible as flushModuleScopingQueueAsMuchAsPossible, ɵgetInjectableDef as getInjectableDef, ɵpatchComponentDefWithScope as patchComponentDefWithScope, ɵresetCompiledComponents as resetCompiledComponents, ɵstringify as stringify, ɵtransitiveScopesFor as transitiveScopesFor} from '@angular/core';

import {ComponentFixture} from './component_fixture';
import {MetadataOverride} from './metadata_override';
Expand Down Expand Up @@ -204,6 +204,7 @@ export class TestBedRender3 implements Injector, TestBed {
private _testModuleType: NgModuleType<any> = null !;

private _instantiated: boolean = false;
private _globalCompilationChecked = false;

// Map that keeps initial version of component/directive/pipe defs in case
// we compile a Type again, thus overriding respective static fields. This is
Expand Down Expand Up @@ -244,6 +245,7 @@ export class TestBedRender3 implements Injector, TestBed {
}

resetTestingModule(): void {
this._checkGlobalCompilationFinished();
resetCompiledComponents();
// reset metadata overrides
this._moduleOverrides = [];
Expand Down Expand Up @@ -418,6 +420,7 @@ export class TestBedRender3 implements Injector, TestBed {
// internal methods

private _initIfNeeded(): void {
this._checkGlobalCompilationFinished();
if (this._instantiated) {
return;
}
Expand Down Expand Up @@ -580,6 +583,27 @@ export class TestBedRender3 implements Injector, TestBed {
patchComponentDefWithScope((cmp as any).ngComponentDef, scope);
});
}

/**
* Check whether the module scoping queue should be flushed, and flush it if needed.
*
* When the TestBed is reset, it clears the JIT module compilation queue, cancelling any
* in-progress module compilation. This creates a potential hazard - the very first time the
* TestBed is initialized (or if it's reset without being initialized), there may be pending
* compilations of modules declared in global scope. These compilations should be finished.
*
* To ensure that globally declared modules have their components scoped properly, this function
* is called whenever TestBed is initialized or reset. The _first_ time that this happens, prior
* to any other operations, the scoping queue is flushed.
*/
private _checkGlobalCompilationFinished(): void {
// !this._instantiated should not be necessary, but is left in as an additional guard that
// compilations queued in tests (after instantiation) are never flushed accidentally.
if (!this._globalCompilationChecked && !this._instantiated) {
flushModuleScopingQueueAsMuchAsPossible();
}
this._globalCompilationChecked = true;
}
}

let testBed: TestBedRender3;
Expand Down
17 changes: 8 additions & 9 deletions packages/platform-server/test/integration_spec.ts
Expand Up @@ -617,15 +617,14 @@ class HiddenModule {
});
}));

fixmeIvy('FW-887: JIT: compilation of NgModule')
.it('should handle false values on attributes', async(() => {
renderModule(FalseAttributesModule, {document: doc}).then(output => {
expect(output).toBe(
'<html><head></head><body><app ng-version="0.0.0-PLACEHOLDER">' +
'<my-child ng-reflect-attr="false">Works!</my-child></app></body></html>');
called = true;
});
}));
it('should handle false values on attributes', async(() => {
renderModule(FalseAttributesModule, {document: doc}).then(output => {
expect(output).toBe(
'<html><head></head><body><app ng-version="0.0.0-PLACEHOLDER">' +
'<my-child ng-reflect-attr="false">Works!</my-child></app></body></html>');
called = true;
});
}));

it('should handle element property "name"', async(() => {
renderModule(NameModule, {document: doc}).then(output => {
Expand Down

0 comments on commit 4c48219

Please sign in to comment.