feat(ivy): implement a Compiler for use in TestBedRender3 (#28033)

Previously when testing code injected the Compiler, it received the
top-level Compiler implementation defined in linker/compiler.ts
(and governed by the __PRE_R3__ switch). Code running under the
TestBed, however, should always use a TestBed-aware Compiler
implementation.

This commit adds such an implementation to the TestBedRender3,
which passes compiled modules through the _compileNgModule()
function.

With this change, 3 formerly disabled router integration tests
now pass.

FW-855 #resolve

PR Close #28033
This commit is contained in:
Alex Rickabaugh 2019-01-09 16:24:06 -08:00 committed by Andrew Kushnir
parent 94893accdb
commit 51e716b6f2
2 changed files with 122 additions and 49 deletions

View File

@ -6,7 +6,48 @@
* found in the LICENSE file at https://angular.io/license
*/
import {ApplicationInitStatus, Component, Directive, Injector, NgModule, 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';
// The formatter and CI disagree on how this import statement should be formatted. Both try to keep
// it on one line, too, which has gotten very hard to read & manage. So disable the formatter for
// this statement only.
// clang-format off
import {
ApplicationInitStatus,
Compiler,
Component,
Directive,
Injector,
ModuleWithComponentFactories,
NgModule,
NgModuleFactory,
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,
ɵNgModuleFactory as R3NgModuleFactory,
ɵ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';
// clang-format on
import {ComponentFixture} from './component_fixture';
import {MetadataOverride} from './metadata_override';
@ -486,8 +527,12 @@ export class TestBedRender3 implements Injector, TestBed {
}
const ngZone = new NgZone({enableLongStackTrace: true});
const providers =
[{provide: NgZone, useValue: ngZone}, ...this._providers, ...this._providerOverrides];
const providers = [
{provide: NgZone, useValue: ngZone},
{provide: Compiler, useFactory: () => new R3TestCompiler(this)},
...this._providers,
...this._providerOverrides,
];
const declarations = this._declarations;
const imports = [RootScopeModule, this.ngModule, this._imports];
@ -521,7 +566,10 @@ export class TestBedRender3 implements Injector, TestBed {
return Object.keys(overrides).length ? {...meta, ...overrides} : meta;
}
private _compileNgModule(moduleType: NgModuleType): void {
/**
* @internal
*/
_compileNgModule(moduleType: NgModuleType): void {
const ngModule = this._resolvers.module.resolve(moduleType);
if (ngModule === null) {
@ -614,3 +662,31 @@ function flatten<T>(values: any[], mapFn?: (value: T) => any): T[] {
function isNgModule<T>(value: Type<T>): value is Type<T>&{ngModuleDef: NgModuleDef<T>} {
return (value as{ngModuleDef?: NgModuleDef<T>}).ngModuleDef !== undefined;
}
class R3TestCompiler implements Compiler {
constructor(private testBed: TestBedRender3) {}
compileModuleSync<T>(moduleType: Type<T>): NgModuleFactory<T> {
this.testBed._compileNgModule(moduleType as NgModuleType<T>);
return new R3NgModuleFactory(moduleType);
}
compileModuleAsync<T>(moduleType: Type<T>): Promise<NgModuleFactory<T>> {
return Promise.resolve(this.compileModuleSync(moduleType));
}
compileModuleAndAllComponentsSync<T>(moduleType: Type<T>): ModuleWithComponentFactories<T> {
return new ModuleWithComponentFactories(this.compileModuleSync(moduleType), []);
}
compileModuleAndAllComponentsAsync<T>(moduleType: Type<T>):
Promise<ModuleWithComponentFactories<T>> {
return Promise.resolve(this.compileModuleAndAllComponentsSync(moduleType));
}
clearCache(): void {}
clearCacheFor(type: Type<any>): void {}
getModuleId(moduleType: Type<any>): string|undefined { return undefined; }
}

View File

@ -4024,27 +4024,26 @@ describe('Integration', () => {
});
});
fixmeIvy('FW-887: JIT: compilation of NgModule')
.it('should use the injector of the lazily-loaded configuration',
fakeAsync(inject(
[Router, Location, NgModuleFactoryLoader],
(router: Router, location: Location, loader: SpyNgModuleFactoryLoader) => {
loader.stubbedModules = {expected: LoadedModule};
it('should use the injector of the lazily-loaded configuration',
fakeAsync(inject(
[Router, Location, NgModuleFactoryLoader],
(router: Router, location: Location, loader: SpyNgModuleFactoryLoader) => {
loader.stubbedModules = {expected: LoadedModule};
const fixture = createRoot(router, RootCmp);
const fixture = createRoot(router, RootCmp);
router.resetConfig([{
path: 'eager-parent',
component: EagerParentComponent,
children: [{path: 'lazy', loadChildren: 'expected'}]
}]);
router.resetConfig([{
path: 'eager-parent',
component: EagerParentComponent,
children: [{path: 'lazy', loadChildren: 'expected'}]
}]);
router.navigateByUrl('/eager-parent/lazy/lazy-parent/lazy-child');
advance(fixture);
router.navigateByUrl('/eager-parent/lazy/lazy-parent/lazy-child');
advance(fixture);
expect(location.path()).toEqual('/eager-parent/lazy/lazy-parent/lazy-child');
expect(fixture.nativeElement).toHaveText('eager-parent lazy-parent lazy-child');
})));
expect(location.path()).toEqual('/eager-parent/lazy/lazy-parent/lazy-child');
expect(fixture.nativeElement).toHaveText('eager-parent lazy-parent lazy-child');
})));
});
it('works when given a callback',
@ -4367,43 +4366,41 @@ describe('Integration', () => {
class LazyLoadedModule {
}
fixmeIvy('FW-887: JIT: compilation of NgModule')
.it('should not ignore empty path when in legacy mode',
fakeAsync(inject(
[Router, NgModuleFactoryLoader],
(router: Router, loader: SpyNgModuleFactoryLoader) => {
router.relativeLinkResolution = 'legacy';
loader.stubbedModules = {expected: LazyLoadedModule};
it('should not ignore empty path when in legacy mode',
fakeAsync(inject(
[Router, NgModuleFactoryLoader],
(router: Router, loader: SpyNgModuleFactoryLoader) => {
router.relativeLinkResolution = 'legacy';
loader.stubbedModules = {expected: LazyLoadedModule};
const fixture = createRoot(router, RootCmp);
const fixture = createRoot(router, RootCmp);
router.resetConfig([{path: 'lazy', loadChildren: 'expected'}]);
router.resetConfig([{path: 'lazy', loadChildren: 'expected'}]);
router.navigateByUrl('/lazy/foo/bar');
advance(fixture);
router.navigateByUrl('/lazy/foo/bar');
advance(fixture);
const link = fixture.nativeElement.querySelector('a');
expect(link.getAttribute('href')).toEqual('/lazy/foo/bar/simple');
})));
const link = fixture.nativeElement.querySelector('a');
expect(link.getAttribute('href')).toEqual('/lazy/foo/bar/simple');
})));
fixmeIvy('FW-887: JIT: compilation of NgModule')
.it('should ignore empty path when in corrected mode',
fakeAsync(inject(
[Router, NgModuleFactoryLoader],
(router: Router, loader: SpyNgModuleFactoryLoader) => {
router.relativeLinkResolution = 'corrected';
loader.stubbedModules = {expected: LazyLoadedModule};
it('should ignore empty path when in corrected mode',
fakeAsync(inject(
[Router, NgModuleFactoryLoader],
(router: Router, loader: SpyNgModuleFactoryLoader) => {
router.relativeLinkResolution = 'corrected';
loader.stubbedModules = {expected: LazyLoadedModule};
const fixture = createRoot(router, RootCmp);
const fixture = createRoot(router, RootCmp);
router.resetConfig([{path: 'lazy', loadChildren: 'expected'}]);
router.resetConfig([{path: 'lazy', loadChildren: 'expected'}]);
router.navigateByUrl('/lazy/foo/bar');
advance(fixture);
router.navigateByUrl('/lazy/foo/bar');
advance(fixture);
const link = fixture.nativeElement.querySelector('a');
expect(link.getAttribute('href')).toEqual('/lazy/foo/simple');
})));
const link = fixture.nativeElement.querySelector('a');
expect(link.getAttribute('href')).toEqual('/lazy/foo/simple');
})));
});
});