refactor(testing): introduce new testing api to support ng modules
BREAKING CHANGE: - deprecations: * `withProviders`, use `TestBed.withModule` instead * `addProviders`, use `TestBed.configureTestingModule` instead * `TestComponentBuilder`, use `TestBed.configureTestModule` / `TestBed.override...` / `TestBed.createComponent` instead. Closes #10354
This commit is contained in:
15
modules/@angular/compiler/core_private_testing.ts
Normal file
15
modules/@angular/compiler/core_private_testing.ts
Normal file
@ -0,0 +1,15 @@
|
||||
/**
|
||||
* @license
|
||||
* Copyright Google Inc. All Rights Reserved.
|
||||
*
|
||||
* Use of this source code is governed by an MIT-style license that can be
|
||||
* found in the LICENSE file at https://angular.io/license
|
||||
*/
|
||||
|
||||
import {__core_private_testing__ as r, __core_private_testing_types__ as t} from '@angular/core/testing';
|
||||
|
||||
export type TestingCompiler = t.TestingCompiler;
|
||||
export var TestingCompiler: typeof t.TestingCompiler = r.TestingCompiler;
|
||||
|
||||
export type TestingCompilerFactory = t.TestingCompilerFactory;
|
||||
export var TestingCompilerFactory: typeof t.TestingCompilerFactory = r.TestingCompilerFactory;
|
@ -6,7 +6,7 @@
|
||||
* found in the LICENSE file at https://angular.io/license
|
||||
*/
|
||||
|
||||
import {Compiler, ComponentFactory, ComponentResolver, ComponentStillLoadingError, Injectable, Injector, NgModule, NgModuleFactory, NgModuleMetadata, OptionalMetadata, Provider, SchemaMetadata, SkipSelfMetadata} from '@angular/core';
|
||||
import {Compiler, ComponentFactory, ComponentResolver, ComponentStillLoadingError, Injectable, Injector, ModuleWithComponentFactories, NgModule, NgModuleFactory, NgModuleMetadata, OptionalMetadata, Provider, SchemaMetadata, SkipSelfMetadata} from '@angular/core';
|
||||
|
||||
import {Console} from '../core_private';
|
||||
import {BaseException} from '../src/facade/exceptions';
|
||||
@ -44,13 +44,13 @@ export class RuntimeCompiler implements Compiler {
|
||||
private _compiledNgModuleCache = new Map<Type, NgModuleFactory<any>>();
|
||||
|
||||
constructor(
|
||||
private __injector: Injector, private _metadataResolver: CompileMetadataResolver,
|
||||
private _injector: Injector, private _metadataResolver: CompileMetadataResolver,
|
||||
private _templateNormalizer: DirectiveNormalizer, private _templateParser: TemplateParser,
|
||||
private _styleCompiler: StyleCompiler, private _viewCompiler: ViewCompiler,
|
||||
private _ngModuleCompiler: NgModuleCompiler, private _compilerConfig: CompilerConfig,
|
||||
private _console: Console) {}
|
||||
|
||||
get _injector(): Injector { return this.__injector; }
|
||||
get injector(): Injector { return this._injector; }
|
||||
|
||||
compileModuleSync<T>(moduleType: ConcreteType<T>): NgModuleFactory<T> {
|
||||
return this._compileModuleAndComponents(moduleType, true).syncResult;
|
||||
@ -60,6 +60,16 @@ export class RuntimeCompiler implements Compiler {
|
||||
return this._compileModuleAndComponents(moduleType, false).asyncResult;
|
||||
}
|
||||
|
||||
compileModuleAndAllComponentsSync<T>(moduleType: ConcreteType<T>):
|
||||
ModuleWithComponentFactories<T> {
|
||||
return this._compileModuleAndAllComponents(moduleType, true).syncResult;
|
||||
}
|
||||
|
||||
compileModuleAndAllComponentsAsync<T>(moduleType: ConcreteType<T>):
|
||||
Promise<ModuleWithComponentFactories<T>> {
|
||||
return this._compileModuleAndAllComponents(moduleType, false).asyncResult;
|
||||
}
|
||||
|
||||
compileComponentAsync<T>(compType: ConcreteType<T>, ngModule: ConcreteType<any> = null):
|
||||
Promise<ComponentFactory<T>> {
|
||||
if (!ngModule) {
|
||||
@ -85,6 +95,34 @@ export class RuntimeCompiler implements Compiler {
|
||||
return new SyncAsyncResult(ngModuleFactory, componentPromise.then(() => ngModuleFactory));
|
||||
}
|
||||
|
||||
private _compileModuleAndAllComponents<T>(moduleType: ConcreteType<T>, isSync: boolean):
|
||||
SyncAsyncResult<ModuleWithComponentFactories<T>> {
|
||||
const componentPromise = this._compileComponents(moduleType, isSync);
|
||||
const ngModuleFactory = this._compileModule(moduleType);
|
||||
const moduleMeta = this._metadataResolver.getNgModuleMetadata(moduleType);
|
||||
const componentFactories: ComponentFactory<any>[] = [];
|
||||
const templates = new Set<CompiledTemplate>();
|
||||
moduleMeta.transitiveModule.modules.forEach((moduleMeta) => {
|
||||
moduleMeta.declaredDirectives.forEach((dirMeta) => {
|
||||
if (dirMeta.isComponent) {
|
||||
const template = this._createCompiledHostTemplate(dirMeta.type.runtime);
|
||||
templates.add(template);
|
||||
componentFactories.push(template.proxyComponentFactory);
|
||||
}
|
||||
});
|
||||
});
|
||||
const syncResult = new ModuleWithComponentFactories(ngModuleFactory, componentFactories);
|
||||
// Note: host components themselves can always be compiled synchronously as they have an
|
||||
// inline template. However, we still need to wait for the components that they
|
||||
// reference to be loaded / compiled.
|
||||
const compile = () => {
|
||||
templates.forEach((template) => { this._compileTemplate(template); });
|
||||
return syncResult;
|
||||
};
|
||||
const asyncResult = isSync ? Promise.resolve(compile()) : componentPromise.then(compile);
|
||||
return new SyncAsyncResult(syncResult, asyncResult);
|
||||
}
|
||||
|
||||
private _compileModule<T>(moduleType: ConcreteType<T>): NgModuleFactory<T> {
|
||||
let ngModuleFactory = this._compiledNgModuleCache.get(moduleType);
|
||||
if (!ngModuleFactory) {
|
||||
@ -381,7 +419,7 @@ class ModuleBoundCompiler implements Compiler, ComponentResolver {
|
||||
private _delegate: RuntimeCompiler, private _ngModule: ConcreteType<any>,
|
||||
private _parentComponentResolver: ComponentResolver, private _console: Console) {}
|
||||
|
||||
get _injector(): Injector { return this._delegate._injector; }
|
||||
get _injector(): Injector { return this._delegate.injector; }
|
||||
|
||||
resolveComponent(component: Type|string): Promise<ComponentFactory<any>> {
|
||||
if (isString(component)) {
|
||||
@ -416,6 +454,15 @@ class ModuleBoundCompiler implements Compiler, ComponentResolver {
|
||||
compileModuleAsync<T>(moduleType: ConcreteType<T>): Promise<NgModuleFactory<T>> {
|
||||
return this._delegate.compileModuleAsync(moduleType);
|
||||
}
|
||||
compileModuleAndAllComponentsSync<T>(moduleType: ConcreteType<T>):
|
||||
ModuleWithComponentFactories<T> {
|
||||
return this._delegate.compileModuleAndAllComponentsSync(moduleType);
|
||||
}
|
||||
|
||||
compileModuleAndAllComponentsAsync<T>(moduleType: ConcreteType<T>):
|
||||
Promise<ModuleWithComponentFactories<T>> {
|
||||
return this._delegate.compileModuleAndAllComponentsAsync(moduleType);
|
||||
}
|
||||
|
||||
/**
|
||||
* Clears all caches
|
||||
|
@ -12,7 +12,7 @@ import {DirectiveNormalizer} from '@angular/compiler/src/directive_normalizer';
|
||||
import {XHR} from '@angular/compiler/src/xhr';
|
||||
import {MockXHR} from '@angular/compiler/testing/xhr_mock';
|
||||
import {ViewEncapsulation} from '@angular/core/src/metadata/view';
|
||||
import {configureCompiler} from '@angular/core/testing';
|
||||
import {TestBed} from '@angular/core/testing';
|
||||
import {AsyncTestCompleter, beforeEach, beforeEachProviders, ddescribe, describe, expect, iit, inject, it, xit} from '@angular/core/testing/testing_internal';
|
||||
|
||||
import {SpyXHR} from './spies';
|
||||
@ -23,7 +23,7 @@ export function main() {
|
||||
var dirType: CompileTypeMetadata;
|
||||
var dirTypeWithHttpUrl: CompileTypeMetadata;
|
||||
|
||||
beforeEach(() => { configureCompiler({providers: TEST_COMPILER_PROVIDERS}); });
|
||||
beforeEach(() => { TestBed.configureCompiler({providers: TEST_COMPILER_PROVIDERS}); });
|
||||
|
||||
beforeEach(() => {
|
||||
dirType = new CompileTypeMetadata({moduleUrl: 'package:some/module/a.js', name: 'SomeComp'});
|
||||
@ -179,7 +179,8 @@ export function main() {
|
||||
|
||||
describe('normalizeExternalStylesheets', () => {
|
||||
|
||||
beforeEach(() => { configureCompiler({providers: [{provide: XHR, useClass: SpyXHR}]}); });
|
||||
beforeEach(
|
||||
() => { TestBed.configureCompiler({providers: [{provide: XHR, useClass: SpyXHR}]}); });
|
||||
|
||||
it('should load an external stylesheet',
|
||||
inject(
|
||||
|
@ -27,7 +27,7 @@ export function main() {
|
||||
expect(ngModule.selector).toEqual('cmp');
|
||||
});
|
||||
|
||||
it('should allow overriding the @NgModule', () => {
|
||||
it('should allow overriding the @Directive', () => {
|
||||
dirResolver.setDirective(
|
||||
SomeComponent, new ComponentMetadata({selector: 'someOtherSelector'}));
|
||||
var metadata = dirResolver.resolve(SomeComponent);
|
||||
|
116
modules/@angular/compiler/test/metadata_overrider_spec.ts
Normal file
116
modules/@angular/compiler/test/metadata_overrider_spec.ts
Normal file
@ -0,0 +1,116 @@
|
||||
/**
|
||||
* @license
|
||||
* Copyright Google Inc. All Rights Reserved.
|
||||
*
|
||||
* Use of this source code is governed by an MIT-style license that can be
|
||||
* found in the LICENSE file at https://angular.io/license
|
||||
*/
|
||||
|
||||
import {expect} from '@angular/platform-browser/testing/matchers';
|
||||
import {MetadataOverrider} from '../testing/metadata_overrider';
|
||||
|
||||
interface SomeMetadataType {
|
||||
plainProp?: string;
|
||||
getterProp?: string;
|
||||
arrayProp?: any[];
|
||||
}
|
||||
|
||||
class SomeMetadata implements SomeMetadataType {
|
||||
plainProp: string;
|
||||
private _getterProp: string;
|
||||
get getterProp(): string { return this._getterProp; }
|
||||
arrayProp: any[];
|
||||
|
||||
constructor(options: SomeMetadataType) {
|
||||
this.plainProp = options.plainProp;
|
||||
this._getterProp = options.getterProp;
|
||||
this.arrayProp = options.arrayProp;
|
||||
}
|
||||
}
|
||||
|
||||
export function main() {
|
||||
describe('metadata overrider', () => {
|
||||
let overrider: MetadataOverrider;
|
||||
|
||||
beforeEach(() => { overrider = new MetadataOverrider(); });
|
||||
|
||||
it('should return a new instance with the same values', () => {
|
||||
const oldInstance = new SomeMetadata({plainProp: 'somePlainProp', getterProp: 'someInput'});
|
||||
const newInstance = overrider.overrideMetadata(SomeMetadata, oldInstance, {});
|
||||
expect(newInstance).not.toBe(oldInstance);
|
||||
expect(newInstance).toBeAnInstanceOf(SomeMetadata);
|
||||
expect(newInstance).toEqual(oldInstance);
|
||||
});
|
||||
|
||||
it('should set individual properties and keep others', () => {
|
||||
const oldInstance =
|
||||
new SomeMetadata({plainProp: 'somePlainProp', getterProp: 'someGetterProp'});
|
||||
const newInstance =
|
||||
overrider.overrideMetadata(SomeMetadata, oldInstance, {set: {plainProp: 'newPlainProp'}});
|
||||
expect(newInstance)
|
||||
.toEqual(new SomeMetadata({plainProp: 'newPlainProp', getterProp: 'someGetterProp'}));
|
||||
});
|
||||
|
||||
describe('add properties', () => {
|
||||
it('should replace non array values', () => {
|
||||
const oldInstance =
|
||||
new SomeMetadata({plainProp: 'somePlainProp', getterProp: 'someGetterProp'});
|
||||
const newInstance = overrider.overrideMetadata(
|
||||
SomeMetadata, oldInstance, {add: {plainProp: 'newPlainProp'}});
|
||||
expect(newInstance)
|
||||
.toEqual(new SomeMetadata({plainProp: 'newPlainProp', getterProp: 'someGetterProp'}));
|
||||
});
|
||||
|
||||
it('should add to array values', () => {
|
||||
const oldInstance = new SomeMetadata({arrayProp: ['a']});
|
||||
const newInstance =
|
||||
overrider.overrideMetadata(SomeMetadata, oldInstance, {add: {arrayProp: ['b']}});
|
||||
expect(newInstance).toEqual(new SomeMetadata({arrayProp: ['a', 'b']}));
|
||||
});
|
||||
});
|
||||
|
||||
describe('remove', () => {
|
||||
it('should set values to undefined if their value matches', () => {
|
||||
const oldInstance =
|
||||
new SomeMetadata({plainProp: 'somePlainProp', getterProp: 'someGetterProp'});
|
||||
const newInstance = overrider.overrideMetadata(
|
||||
SomeMetadata, oldInstance, {remove: {plainProp: 'somePlainProp'}});
|
||||
expect(newInstance)
|
||||
.toEqual(new SomeMetadata({plainProp: undefined, getterProp: 'someGetterProp'}));
|
||||
});
|
||||
|
||||
it('should leave values if their value does not match', () => {
|
||||
const oldInstance =
|
||||
new SomeMetadata({plainProp: 'somePlainProp', getterProp: 'someGetterProp'});
|
||||
const newInstance = overrider.overrideMetadata(
|
||||
SomeMetadata, oldInstance, {remove: {plainProp: 'newPlainProp'}});
|
||||
expect(newInstance)
|
||||
.toEqual(new SomeMetadata({plainProp: 'somePlainProp', getterProp: 'someGetterProp'}));
|
||||
});
|
||||
|
||||
it('should remove a value from an array', () => {
|
||||
const oldInstance =
|
||||
new SomeMetadata({arrayProp: ['a', 'b', 'c'], getterProp: 'someGetterProp'});
|
||||
const newInstance = overrider.overrideMetadata(
|
||||
SomeMetadata, oldInstance, {remove: {arrayProp: ['a', 'c']}});
|
||||
expect(newInstance)
|
||||
.toEqual(new SomeMetadata({arrayProp: ['b'], getterProp: 'someGetterProp'}));
|
||||
});
|
||||
|
||||
it('should support types as values', () => {
|
||||
class Class1 {}
|
||||
class Class2 {}
|
||||
class Class3 {}
|
||||
|
||||
const instance1 = new SomeMetadata({arrayProp: [Class1, Class2, Class3]});
|
||||
const instance2 =
|
||||
overrider.overrideMetadata(SomeMetadata, instance1, {remove: {arrayProp: [Class1]}});
|
||||
expect(instance2).toEqual(new SomeMetadata({arrayProp: [Class2, Class3]}));
|
||||
const instance3 =
|
||||
overrider.overrideMetadata(SomeMetadata, instance2, {remove: {arrayProp: [Class3]}});
|
||||
expect(instance3).toEqual(new SomeMetadata({arrayProp: [Class2]}));
|
||||
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
@ -9,7 +9,7 @@
|
||||
import {CompilerConfig} from '@angular/compiler/src/config';
|
||||
import {AfterContentChecked, AfterContentInit, AfterViewChecked, AfterViewInit, ChangeDetectionStrategy, Component, Directive, DoCheck, Injectable, OnChanges, OnDestroy, OnInit, SimpleChanges, ViewEncapsulation} from '@angular/core';
|
||||
import {LIFECYCLE_HOOKS_VALUES} from '@angular/core/src/metadata/lifecycle_hooks';
|
||||
import {configureCompiler} from '@angular/core/testing';
|
||||
import {TestBed} from '@angular/core/testing';
|
||||
import {afterEach, beforeEach, beforeEachProviders, ddescribe, describe, expect, iit, inject, it, xdescribe, xit} from '@angular/core/testing/testing_internal';
|
||||
|
||||
import {CompileNgModuleMetadata} from '../src/compile_metadata';
|
||||
@ -21,7 +21,7 @@ import {TEST_COMPILER_PROVIDERS} from './test_bindings';
|
||||
|
||||
export function main() {
|
||||
describe('CompileMetadataResolver', () => {
|
||||
beforeEach(() => { configureCompiler({providers: TEST_COMPILER_PROVIDERS}); });
|
||||
beforeEach(() => { TestBed.configureCompiler({providers: TEST_COMPILER_PROVIDERS}); });
|
||||
|
||||
describe('getDirectiveMetadata', () => {
|
||||
it('should read metadata',
|
||||
|
@ -10,7 +10,7 @@ import {beforeEach, ddescribe, xdescribe, describe, iit, inject, beforeEachProvi
|
||||
import {expect} from '@angular/platform-browser/testing/matchers';
|
||||
import {Injectable, Component, Input, ViewMetadata, Compiler, ComponentFactory, Injector, NgModule, NgModuleFactory} from '@angular/core';
|
||||
import {ConcreteType, stringify} from '../src/facade/lang';
|
||||
import {fakeAsync, tick, TestComponentBuilder, ComponentFixture, configureCompiler} from '@angular/core/testing';
|
||||
import {fakeAsync, tick, TestComponentBuilder, ComponentFixture, TestBed} from '@angular/core/testing';
|
||||
import {XHR, DirectiveResolver} from '@angular/compiler';
|
||||
import {MockDirectiveResolver} from '@angular/compiler/testing';
|
||||
|
||||
@ -36,7 +36,8 @@ export function main() {
|
||||
let dirResolver: MockDirectiveResolver;
|
||||
let injector: Injector;
|
||||
|
||||
beforeEach(() => { configureCompiler({providers: [{provide: XHR, useClass: SpyXHR}]}); });
|
||||
beforeEach(
|
||||
() => { TestBed.configureCompiler({providers: [{provide: XHR, useClass: SpyXHR}]}); });
|
||||
|
||||
beforeEach(inject(
|
||||
[Compiler, TestComponentBuilder, XHR, DirectiveResolver, Injector],
|
||||
|
@ -14,7 +14,7 @@ import {TEMPLATE_TRANSFORMS, TemplateParser, splitClasses} from '@angular/compil
|
||||
import {MockSchemaRegistry} from '@angular/compiler/testing';
|
||||
import {SchemaMetadata, SecurityContext} from '@angular/core';
|
||||
import {Console} from '@angular/core/src/console';
|
||||
import {configureCompiler} from '@angular/core/testing';
|
||||
import {TestBed} from '@angular/core/testing';
|
||||
import {afterEach, beforeEach, beforeEachProviders, ddescribe, describe, expect, iit, inject, it, xit} from '@angular/core/testing/testing_internal';
|
||||
|
||||
import {Identifiers, identifierToken} from '../src/identifiers';
|
||||
@ -40,7 +40,7 @@ export function main() {
|
||||
function commonBeforeEach() {
|
||||
beforeEach(() => {
|
||||
console = new ArrayConsole();
|
||||
configureCompiler({providers: [{provide: Console, useValue: console}]});
|
||||
TestBed.configureCompiler({providers: [{provide: Console, useValue: console}]});
|
||||
});
|
||||
beforeEach(inject([TemplateParser], (parser: TemplateParser) => {
|
||||
var component = CompileDirectiveMetadata.create({
|
||||
@ -66,10 +66,10 @@ export function main() {
|
||||
}
|
||||
|
||||
describe('TemplateParser template transform', () => {
|
||||
beforeEach(() => { configureCompiler({providers: TEST_COMPILER_PROVIDERS}); });
|
||||
beforeEach(() => { TestBed.configureCompiler({providers: TEST_COMPILER_PROVIDERS}); });
|
||||
|
||||
beforeEach(() => {
|
||||
configureCompiler({
|
||||
TestBed.configureCompiler({
|
||||
providers:
|
||||
[{provide: TEMPLATE_TRANSFORMS, useValue: new FooAstTransformer(), multi: true}]
|
||||
});
|
||||
@ -84,7 +84,7 @@ export function main() {
|
||||
|
||||
describe('multiple', () => {
|
||||
beforeEach(() => {
|
||||
configureCompiler({
|
||||
TestBed.configureCompiler({
|
||||
providers:
|
||||
[{provide: TEMPLATE_TRANSFORMS, useValue: new BarAstTransformer(), multi: true}]
|
||||
});
|
||||
@ -101,7 +101,7 @@ export function main() {
|
||||
// Semi-integration test to make sure TemplateParser properly sets the security context.
|
||||
// Uses the actual DomElementSchemaRegistry.
|
||||
beforeEach(() => {
|
||||
configureCompiler({
|
||||
TestBed.configureCompiler({
|
||||
providers: [
|
||||
TEST_COMPILER_PROVIDERS,
|
||||
{provide: ElementSchemaRegistry, useClass: DomElementSchemaRegistry}
|
||||
@ -138,7 +138,7 @@ export function main() {
|
||||
|
||||
describe('TemplateParser', () => {
|
||||
beforeEach(() => {
|
||||
configureCompiler({providers: [TEST_COMPILER_PROVIDERS, MOCK_SCHEMA_REGISTRY]});
|
||||
TestBed.configureCompiler({providers: [TEST_COMPILER_PROVIDERS, MOCK_SCHEMA_REGISTRY]});
|
||||
});
|
||||
|
||||
commonBeforeEach();
|
||||
|
@ -327,27 +327,6 @@ export function main() {
|
||||
expect(componentFixture.nativeElement).toHaveText('Mock');
|
||||
}));
|
||||
|
||||
it('should create components synchronously with a custom module',
|
||||
inject([TestComponentBuilder], (tcb: TestComponentBuilder) => {
|
||||
@Pipe({name: 'somePipe'})
|
||||
class SomePipe {
|
||||
transform(value: any) { return `transformed ${value}`; }
|
||||
}
|
||||
|
||||
@NgModule({declarations: [SomePipe]})
|
||||
class SomeModule {
|
||||
}
|
||||
|
||||
@Component({selector: 'comp', template: `{{'hello' | somePipe}}`})
|
||||
class SomeComponent {
|
||||
}
|
||||
|
||||
|
||||
let componentFixture = tcb.createSync(SomeComponent, SomeModule);
|
||||
componentFixture.detectChanges();
|
||||
expect(componentFixture.nativeElement).toHaveText('transformed hello');
|
||||
}));
|
||||
|
||||
describe('ComponentFixture', () => {
|
||||
it('should auto detect changes if autoDetectChanges is called',
|
||||
inject(
|
||||
|
@ -10,12 +10,86 @@ export * from './testing/schema_registry_mock';
|
||||
export * from './testing/test_component_builder';
|
||||
export * from './testing/directive_resolver_mock';
|
||||
export * from './testing/ng_module_resolver_mock';
|
||||
export * from './testing/pipe_resolver_mock';
|
||||
|
||||
import {createPlatformFactory, CompilerOptions, PlatformRef} from '@angular/core';
|
||||
import {platformCoreDynamic, DirectiveResolver, NgModuleResolver} from './index';
|
||||
import {ConcreteType, Type} from './src/facade/lang';
|
||||
import {createPlatformFactory, ModuleWithComponentFactories, Injectable, CompilerOptions, PlatformRef, CompilerFactory, ComponentFactory, NgModuleFactory, Injector, NgModuleMetadata, NgModuleMetadataType, ComponentMetadata, ComponentMetadataType, DirectiveMetadata, DirectiveMetadataType, PipeMetadata, PipeMetadataType} from '@angular/core';
|
||||
import {MetadataOverride} from '@angular/core/testing';
|
||||
import {TestingCompilerFactory, TestingCompiler} from './core_private_testing';
|
||||
import {platformCoreDynamic, RuntimeCompiler, DirectiveResolver, NgModuleResolver, PipeResolver} from './index';
|
||||
import {MockDirectiveResolver} from './testing/directive_resolver_mock';
|
||||
import {MockNgModuleResolver} from './testing/ng_module_resolver_mock';
|
||||
import {MockPipeResolver} from './testing/pipe_resolver_mock';
|
||||
import {MetadataOverrider} from './testing/metadata_overrider';
|
||||
|
||||
@Injectable()
|
||||
export class TestingCompilerFactoryImpl implements TestingCompilerFactory {
|
||||
constructor(private _compilerFactory: CompilerFactory) {}
|
||||
|
||||
createTestingCompiler(options: CompilerOptions[]): TestingCompiler {
|
||||
const compiler = <RuntimeCompiler>this._compilerFactory.createCompiler(options);
|
||||
return new TestingCompilerImpl(
|
||||
compiler, compiler.injector.get(MockDirectiveResolver),
|
||||
compiler.injector.get(MockPipeResolver), compiler.injector.get(MockNgModuleResolver));
|
||||
}
|
||||
}
|
||||
|
||||
export class TestingCompilerImpl implements TestingCompiler {
|
||||
private _overrider = new MetadataOverrider();
|
||||
constructor(
|
||||
private _compiler: RuntimeCompiler, private _directiveResolver: MockDirectiveResolver,
|
||||
private _pipeResolver: MockPipeResolver, private _moduleResolver: MockNgModuleResolver) {}
|
||||
get injector(): Injector { return this._compiler.injector; }
|
||||
compileComponentAsync<T>(component: ConcreteType<T>, ngModule: Type = null):
|
||||
Promise<ComponentFactory<T>> {
|
||||
return this._compiler.compileComponentAsync(component, <any>ngModule);
|
||||
}
|
||||
compileComponentSync<T>(component: ConcreteType<T>, ngModule: Type = null): ComponentFactory<T> {
|
||||
return this._compiler.compileComponentSync(component, <any>ngModule);
|
||||
}
|
||||
compileModuleSync<T>(moduleType: ConcreteType<T>): NgModuleFactory<T> {
|
||||
return this._compiler.compileModuleSync(moduleType);
|
||||
}
|
||||
|
||||
compileModuleAsync<T>(moduleType: ConcreteType<T>): Promise<NgModuleFactory<T>> {
|
||||
return this._compiler.compileModuleAsync(moduleType);
|
||||
}
|
||||
compileModuleAndAllComponentsSync<T>(moduleType: ConcreteType<T>):
|
||||
ModuleWithComponentFactories<T> {
|
||||
return this._compiler.compileModuleAndAllComponentsSync(moduleType);
|
||||
}
|
||||
|
||||
compileModuleAndAllComponentsAsync<T>(moduleType: ConcreteType<T>):
|
||||
Promise<ModuleWithComponentFactories<T>> {
|
||||
return this._compiler.compileModuleAndAllComponentsAsync(moduleType);
|
||||
}
|
||||
|
||||
overrideModule(ngModule: ConcreteType<any>, override: MetadataOverride<NgModuleMetadataType>):
|
||||
void {
|
||||
const oldMetadata = this._moduleResolver.resolve(ngModule, false);
|
||||
this._moduleResolver.setNgModule(
|
||||
ngModule, this._overrider.overrideMetadata(NgModuleMetadata, oldMetadata, override));
|
||||
}
|
||||
overrideDirective(
|
||||
directive: ConcreteType<any>, override: MetadataOverride<DirectiveMetadataType>): void {
|
||||
const oldMetadata = this._directiveResolver.resolve(directive, false);
|
||||
this._directiveResolver.setDirective(
|
||||
directive, this._overrider.overrideMetadata(DirectiveMetadata, oldMetadata, override));
|
||||
}
|
||||
overrideComponent(
|
||||
component: ConcreteType<any>, override: MetadataOverride<ComponentMetadataType>): void {
|
||||
const oldMetadata = this._directiveResolver.resolve(component, false);
|
||||
this._directiveResolver.setDirective(
|
||||
component, this._overrider.overrideMetadata(ComponentMetadata, oldMetadata, override));
|
||||
}
|
||||
overridePipe(pipe: ConcreteType<any>, override: MetadataOverride<PipeMetadataType>): void {
|
||||
const oldMetadata = this._pipeResolver.resolve(pipe, false);
|
||||
this._pipeResolver.setPipe(
|
||||
pipe, this._overrider.overrideMetadata(PipeMetadata, oldMetadata, override));
|
||||
}
|
||||
clearCache(): void { this._compiler.clearCache(); }
|
||||
clearCacheFor(type: Type) { this._compiler.clearCacheFor(type); }
|
||||
}
|
||||
|
||||
/**
|
||||
* Platform for dynamic tests
|
||||
@ -23,13 +97,17 @@ import {MockNgModuleResolver} from './testing/ng_module_resolver_mock';
|
||||
* @experimental
|
||||
*/
|
||||
export const platformCoreDynamicTesting =
|
||||
createPlatformFactory(platformCoreDynamic, 'coreDynamicTesting', [{
|
||||
provide: CompilerOptions,
|
||||
useValue: {
|
||||
providers: [
|
||||
{provide: DirectiveResolver, useClass: MockDirectiveResolver},
|
||||
{provide: NgModuleResolver, useClass: MockNgModuleResolver}
|
||||
]
|
||||
},
|
||||
multi: true
|
||||
}]);
|
||||
createPlatformFactory(platformCoreDynamic, 'coreDynamicTesting', [
|
||||
{
|
||||
provide: CompilerOptions,
|
||||
useValue: {
|
||||
providers: [
|
||||
MockPipeResolver, {provide: PipeResolver, useExisting: MockPipeResolver},
|
||||
MockDirectiveResolver, {provide: DirectiveResolver, useExisting: MockDirectiveResolver},
|
||||
MockNgModuleResolver, {provide: NgModuleResolver, useExisting: MockNgModuleResolver}
|
||||
]
|
||||
},
|
||||
multi: true
|
||||
},
|
||||
{provide: TestingCompilerFactory, useClass: TestingCompilerFactoryImpl}
|
||||
]);
|
||||
|
@ -55,7 +55,8 @@ export class MockDirectiveResolver extends DirectiveResolver {
|
||||
if (metadata instanceof ComponentMetadata) {
|
||||
let viewProviders = metadata.viewProviders;
|
||||
if (isPresent(viewProviderOverrides)) {
|
||||
const originalViewProviders: any[] = isPresent(metadata.viewProviders) ? metadata.viewProviders : [];
|
||||
const originalViewProviders: any[] =
|
||||
isPresent(metadata.viewProviders) ? metadata.viewProviders : [];
|
||||
viewProviders = originalViewProviders.concat(viewProviderOverrides);
|
||||
}
|
||||
|
||||
|
131
modules/@angular/compiler/testing/metadata_overrider.ts
Normal file
131
modules/@angular/compiler/testing/metadata_overrider.ts
Normal file
@ -0,0 +1,131 @@
|
||||
/**
|
||||
* @license
|
||||
* Copyright Google Inc. All Rights Reserved.
|
||||
*
|
||||
* Use of this source code is governed by an MIT-style license that can be
|
||||
* found in the LICENSE file at https://angular.io/license
|
||||
*/
|
||||
|
||||
import {MetadataOverride} from '@angular/core/testing';
|
||||
|
||||
import {BaseException} from '../src/facade/exceptions';
|
||||
import {ConcreteType, stringify} from '../src/facade/lang';
|
||||
|
||||
type StringMap = {
|
||||
[key: string]: any
|
||||
};
|
||||
|
||||
let _nextReferenceId = 0;
|
||||
|
||||
export class MetadataOverrider {
|
||||
private _references = new Map<any, string>();
|
||||
/**
|
||||
* Creates a new instance for the given metadata class
|
||||
* based on an old instance and overrides.
|
||||
*/
|
||||
overrideMetadata<C extends T, T>(
|
||||
metadataClass: {new (options: T): C;}, oldMetadata: C, override: MetadataOverride<T>): C {
|
||||
const props: StringMap = {};
|
||||
if (oldMetadata) {
|
||||
_valueProps(oldMetadata).forEach((prop) => props[prop] = (<any>oldMetadata)[prop]);
|
||||
}
|
||||
|
||||
if (override.set) {
|
||||
if (override.remove || override.add) {
|
||||
throw new BaseException(
|
||||
`Cannot set and add/remove ${stringify(metadataClass)} at the same time!`);
|
||||
}
|
||||
setMetadata(props, override.set);
|
||||
}
|
||||
if (override.remove) {
|
||||
removeMetadata(props, override.remove, this._references);
|
||||
}
|
||||
if (override.add) {
|
||||
addMetadata(props, override.add);
|
||||
}
|
||||
return new metadataClass(<any>props);
|
||||
}
|
||||
}
|
||||
|
||||
function removeMetadata(metadata: StringMap, remove: any, references: Map<any, string>) {
|
||||
const removeObjects = new Set<string>();
|
||||
for (let prop in remove) {
|
||||
const removeValue = remove[prop];
|
||||
if (removeValue instanceof Array) {
|
||||
removeValue.forEach(
|
||||
(value: any) => { removeObjects.add(_propHashKey(prop, value, references)); });
|
||||
} else {
|
||||
removeObjects.add(_propHashKey(prop, removeValue, references));
|
||||
}
|
||||
}
|
||||
|
||||
for (let prop in metadata) {
|
||||
const propValue = metadata[prop];
|
||||
if (propValue instanceof Array) {
|
||||
metadata[prop] = propValue.filter(
|
||||
(value: any) => { return !removeObjects.has(_propHashKey(prop, value, references)); });
|
||||
} else {
|
||||
if (removeObjects.has(_propHashKey(prop, propValue, references))) {
|
||||
metadata[prop] = undefined;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function addMetadata(metadata: StringMap, add: any) {
|
||||
for (let prop in add) {
|
||||
const addValue = add[prop];
|
||||
const propValue = metadata[prop];
|
||||
if (propValue != null && propValue instanceof Array) {
|
||||
metadata[prop] = propValue.concat(addValue);
|
||||
} else {
|
||||
metadata[prop] = addValue;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function setMetadata(metadata: StringMap, set: any) {
|
||||
for (let prop in set) {
|
||||
metadata[prop] = set[prop];
|
||||
}
|
||||
}
|
||||
|
||||
function _propHashKey(propName: any, propValue: any, references: Map<any, string>): string {
|
||||
const replacer = (key: any, value: any) => {
|
||||
if (typeof value === 'function') {
|
||||
value = _serializeReference(value, references);
|
||||
}
|
||||
return value;
|
||||
};
|
||||
|
||||
return `${propName}:${JSON.stringify(propValue, replacer)}`;
|
||||
}
|
||||
|
||||
function _serializeReference(ref: any, references: Map<any, string>): string {
|
||||
let id = references.get(ref);
|
||||
if (!id) {
|
||||
id = `${stringify(ref)}${_nextReferenceId++}`;
|
||||
references.set(ref, id);
|
||||
}
|
||||
return id;
|
||||
}
|
||||
|
||||
|
||||
function _valueProps(obj: any): string[] {
|
||||
const props: string[] = [];
|
||||
// regular public props
|
||||
Object.getOwnPropertyNames(obj).forEach((prop) => {
|
||||
if (!prop.startsWith('_')) {
|
||||
props.push(prop);
|
||||
}
|
||||
});
|
||||
// getters
|
||||
const proto = Object.getPrototypeOf(obj);
|
||||
Object.getOwnPropertyNames(proto).forEach((protoProp) => {
|
||||
var desc = Object.getOwnPropertyDescriptor(proto, protoProp);
|
||||
if (!protoProp.startsWith('_') && desc && 'get' in desc) {
|
||||
props.push(protoProp);
|
||||
}
|
||||
});
|
||||
return props;
|
||||
}
|
@ -19,7 +19,7 @@ export class MockPipeResolver extends PipeResolver {
|
||||
|
||||
private get _compiler(): Compiler { return this._injector.get(Compiler); }
|
||||
|
||||
private _clearCacheFor(component: Type) { this._compiler.clearCacheFor(component); }
|
||||
private _clearCacheFor(pipe: Type) { this._compiler.clearCacheFor(pipe); }
|
||||
|
||||
/**
|
||||
* Overrides the {@link PipeMetadata} for a pipe.
|
||||
|
@ -16,7 +16,10 @@ import {ConcreteType, IS_DART, Type, isPresent} from '../src/facade/lang';
|
||||
|
||||
/**
|
||||
* A TestComponentBuilder that allows overriding based on the compiler.
|
||||
*/
|
||||
*
|
||||
* @deprecated Use `TestBed.configureTestModule` / `TestBed.override...` / `TestBed.createComponent`
|
||||
* instead.
|
||||
*/
|
||||
@Injectable()
|
||||
export class OverridingTestComponentBuilder extends TestComponentBuilder {
|
||||
/** @internal */
|
||||
@ -87,16 +90,14 @@ export class OverridingTestComponentBuilder extends TestComponentBuilder {
|
||||
return clone;
|
||||
}
|
||||
|
||||
createAsync<T>(rootComponentType: ConcreteType<T>, ngModule: ConcreteType<any> = null):
|
||||
Promise<ComponentFixture<T>> {
|
||||
createAsync<T>(rootComponentType: ConcreteType<T>): Promise<ComponentFixture<T>> {
|
||||
this._applyMetadataOverrides();
|
||||
return super.createAsync(rootComponentType, ngModule);
|
||||
return super.createAsync(rootComponentType);
|
||||
}
|
||||
|
||||
createSync<T>(rootComponentType: ConcreteType<T>, ngModule: ConcreteType<any> = null):
|
||||
ComponentFixture<T> {
|
||||
createSync<T>(rootComponentType: ConcreteType<T>): ComponentFixture<T> {
|
||||
this._applyMetadataOverrides();
|
||||
return super.createSync(rootComponentType, ngModule);
|
||||
return super.createSync(rootComponentType);
|
||||
}
|
||||
|
||||
private _applyMetadataOverrides() {
|
||||
|
Reference in New Issue
Block a user