
Prior to this change, Ivy version of TestBed was not designed to support the logic to avoid recompilations - most of the Components/Directives/Pipes were recompiled for each test, even if there were no overrides defined for a given Type. Additional checks to avoid recompilation were introduced in one of the previous commits (0244a2433e2d7009d98bc589899edcd5b1fa003f), but there were still some corner cases that required attention. In order to support the necessary logic better, Ivy TestBed was rewritten/refactored. Main results of this rewrite are: * no recompilation for Components/Directives/Pipes without overrides * the logic to restore state between tests (isolate tests) was improved * transitive scopes calculation no longer performs recompilation (it works with compiled defs) As a result of these changes we see reduction in memory consumption (3.5-4x improvement) and pefromance increase (4-4.5x improvement). PR Close #29483
101 lines
3.3 KiB
TypeScript
101 lines
3.3 KiB
TypeScript
/**
|
|
* @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 {Component, Directive, NgModule, Pipe, Type, ɵReflectionCapabilities as ReflectionCapabilities} from '@angular/core';
|
|
|
|
import {MetadataOverride} from './metadata_override';
|
|
import {MetadataOverrider} from './metadata_overrider';
|
|
|
|
const reflection = new ReflectionCapabilities();
|
|
|
|
/**
|
|
* Base interface to resolve `@Component`, `@Directive`, `@Pipe` and `@NgModule`.
|
|
*/
|
|
export interface Resolver<T> {
|
|
addOverride(type: Type<any>, override: MetadataOverride<T>): void;
|
|
setOverrides(overrides: Array<[Type<any>, MetadataOverride<T>]>): void;
|
|
resolve(type: Type<any>): T|null;
|
|
}
|
|
|
|
/**
|
|
* Allows to override ivy metadata for tests (via the `TestBed`).
|
|
*/
|
|
abstract class OverrideResolver<T> implements Resolver<T> {
|
|
private overrides = new Map<Type<any>, MetadataOverride<T>[]>();
|
|
private resolved = new Map<Type<any>, T|null>();
|
|
|
|
abstract get type(): any;
|
|
|
|
addOverride(type: Type<any>, override: MetadataOverride<T>) {
|
|
const overrides = this.overrides.get(type) || [];
|
|
overrides.push(override);
|
|
this.overrides.set(type, overrides);
|
|
this.resolved.delete(type);
|
|
}
|
|
|
|
setOverrides(overrides: Array<[Type<any>, MetadataOverride<T>]>) {
|
|
this.overrides.clear();
|
|
overrides.forEach(([type, override]) => { this.addOverride(type, override); });
|
|
}
|
|
|
|
getAnnotation(type: Type<any>): T|null {
|
|
const annotations = reflection.annotations(type);
|
|
// Try to find the nearest known Type annotation and make sure that this annotation is an
|
|
// instance of the type we are looking for, so we can use it for resolution. Note: there might
|
|
// be multiple known annotations found due to the fact that Components can extend Directives (so
|
|
// both Directive and Component annotations would be present), so we always check if the known
|
|
// annotation has the right type.
|
|
for (let i = annotations.length - 1; i >= 0; i--) {
|
|
const annotation = annotations[i];
|
|
const isKnownType = annotation instanceof Directive || annotation instanceof Component ||
|
|
annotation instanceof Pipe || annotation instanceof NgModule;
|
|
if (isKnownType) {
|
|
return annotation instanceof this.type ? annotation : null;
|
|
}
|
|
}
|
|
return null;
|
|
}
|
|
|
|
resolve(type: Type<any>): T|null {
|
|
let resolved = this.resolved.get(type) || null;
|
|
|
|
if (!resolved) {
|
|
resolved = this.getAnnotation(type);
|
|
if (resolved) {
|
|
const overrides = this.overrides.get(type);
|
|
if (overrides) {
|
|
const overrider = new MetadataOverrider();
|
|
overrides.forEach(override => {
|
|
resolved = overrider.overrideMetadata(this.type, resolved !, override);
|
|
});
|
|
}
|
|
}
|
|
this.resolved.set(type, resolved);
|
|
}
|
|
|
|
return resolved;
|
|
}
|
|
}
|
|
|
|
|
|
export class DirectiveResolver extends OverrideResolver<Directive> {
|
|
get type() { return Directive; }
|
|
}
|
|
|
|
export class ComponentResolver extends OverrideResolver<Component> {
|
|
get type() { return Component; }
|
|
}
|
|
|
|
export class PipeResolver extends OverrideResolver<Pipe> {
|
|
get type() { return Pipe; }
|
|
}
|
|
|
|
export class NgModuleResolver extends OverrideResolver<NgModule> {
|
|
get type() { return NgModule; }
|
|
}
|