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:
Tobias Bosch
2016-07-28 04:54:49 -07:00
parent acc6c8d0b7
commit d0a95e35af
45 changed files with 1090 additions and 501 deletions

View File

@ -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);
}

View 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;
}

View File

@ -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.

View File

@ -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() {