fix(ivy): adding TestBed.overrideProvider support (#27693)
Prior to this change, provider overrides defined via TestBed.overrideProvider were not applied to Components/Directives. Now providers are taken into account while compiling Components/Directives (metadata is updated accordingly before being passed to compilation). PR Close #27693
This commit is contained in:
parent
bba5e2632e
commit
4b67b0af3e
@ -16,6 +16,16 @@ import {ComponentFixtureAutoDetect, ComponentFixtureNoNgZone, TestBedStatic, Tes
|
|||||||
|
|
||||||
let _nextRootElementId = 0;
|
let _nextRootElementId = 0;
|
||||||
|
|
||||||
|
const EMPTY_ARRAY: Type<any>[] = [];
|
||||||
|
|
||||||
|
// Resolvers for Angular decorators
|
||||||
|
type Resolvers = {
|
||||||
|
module: Resolver<NgModule>,
|
||||||
|
component: Resolver<Directive>,
|
||||||
|
directive: Resolver<Component>,
|
||||||
|
pipe: Resolver<Pipe>,
|
||||||
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @description
|
* @description
|
||||||
* Configures and initializes environment for unit testing and provides methods for
|
* Configures and initializes environment for unit testing and provides methods for
|
||||||
@ -174,6 +184,7 @@ export class TestBedRender3 implements Injector, TestBed {
|
|||||||
private _pipeOverrides: [Type<any>, MetadataOverride<Pipe>][] = [];
|
private _pipeOverrides: [Type<any>, MetadataOverride<Pipe>][] = [];
|
||||||
private _providerOverrides: Provider[] = [];
|
private _providerOverrides: Provider[] = [];
|
||||||
private _rootProviderOverrides: Provider[] = [];
|
private _rootProviderOverrides: Provider[] = [];
|
||||||
|
private _providerOverridesByToken: Map<any, Provider[]> = new Map();
|
||||||
|
|
||||||
// test module configuration
|
// test module configuration
|
||||||
private _providers: Provider[] = [];
|
private _providers: Provider[] = [];
|
||||||
@ -229,6 +240,7 @@ export class TestBedRender3 implements Injector, TestBed {
|
|||||||
this._pipeOverrides = [];
|
this._pipeOverrides = [];
|
||||||
this._providerOverrides = [];
|
this._providerOverrides = [];
|
||||||
this._rootProviderOverrides = [];
|
this._rootProviderOverrides = [];
|
||||||
|
this._providerOverridesByToken.clear();
|
||||||
|
|
||||||
// reset test module config
|
// reset test module config
|
||||||
this._providers = [];
|
this._providers = [];
|
||||||
@ -322,17 +334,21 @@ export class TestBedRender3 implements Injector, TestBed {
|
|||||||
*/
|
*/
|
||||||
overrideProvider(token: any, provider: {useFactory?: Function, useValue?: any, deps?: any[]}):
|
overrideProvider(token: any, provider: {useFactory?: Function, useValue?: any, deps?: any[]}):
|
||||||
void {
|
void {
|
||||||
|
const providerDef = provider.useFactory ?
|
||||||
|
{provide: token, useFactory: provider.useFactory, deps: provider.deps || []} :
|
||||||
|
{provide: token, useValue: provider.useValue};
|
||||||
|
|
||||||
let injectableDef: InjectableDef<any>|null;
|
let injectableDef: InjectableDef<any>|null;
|
||||||
const isRoot =
|
const isRoot =
|
||||||
(typeof token !== 'string' && (injectableDef = getInjectableDef(token)) &&
|
(typeof token !== 'string' && (injectableDef = getInjectableDef(token)) &&
|
||||||
injectableDef.providedIn === 'root');
|
injectableDef.providedIn === 'root');
|
||||||
const overrides = isRoot ? this._rootProviderOverrides : this._providerOverrides;
|
const overridesBucket = isRoot ? this._rootProviderOverrides : this._providerOverrides;
|
||||||
|
overridesBucket.push(providerDef);
|
||||||
|
|
||||||
if (provider.useFactory) {
|
// keep all overrides grouped by token as well for fast lookups using token
|
||||||
overrides.push({provide: token, useFactory: provider.useFactory, deps: provider.deps || []});
|
const overridesForToken = this._providerOverridesByToken.get(token) || [];
|
||||||
} else {
|
overridesForToken.push(providerDef);
|
||||||
overrides.push({provide: token, useValue: provider.useValue});
|
this._providerOverridesByToken.set(token, overridesForToken);
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -387,8 +403,7 @@ export class TestBedRender3 implements Injector, TestBed {
|
|||||||
|
|
||||||
const resolvers = this._getResolvers();
|
const resolvers = this._getResolvers();
|
||||||
const testModuleType = this._createTestModule();
|
const testModuleType = this._createTestModule();
|
||||||
|
this._compileNgModule(testModuleType, resolvers);
|
||||||
compileNgModule(testModuleType, resolvers);
|
|
||||||
|
|
||||||
const parentInjector = this.platform.injector;
|
const parentInjector = this.platform.injector;
|
||||||
this._moduleRef = new NgModuleRef(testModuleType, parentInjector);
|
this._moduleRef = new NgModuleRef(testModuleType, parentInjector);
|
||||||
@ -399,6 +414,14 @@ export class TestBedRender3 implements Injector, TestBed {
|
|||||||
this._instantiated = true;
|
this._instantiated = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// get overrides for a specific provider (if any)
|
||||||
|
private _getProviderOverrides(provider: any) {
|
||||||
|
const token = typeof provider === 'object' && provider.hasOwnProperty('provide') ?
|
||||||
|
provider.provide :
|
||||||
|
provider;
|
||||||
|
return this._providerOverridesByToken.get(token) || [];
|
||||||
|
}
|
||||||
|
|
||||||
// creates resolvers taking overrides into account
|
// creates resolvers taking overrides into account
|
||||||
private _getResolvers() {
|
private _getResolvers() {
|
||||||
const module = new NgModuleResolver();
|
const module = new NgModuleResolver();
|
||||||
@ -448,6 +471,150 @@ export class TestBedRender3 implements Injector, TestBed {
|
|||||||
|
|
||||||
return DynamicTestModule as NgModuleType;
|
return DynamicTestModule as NgModuleType;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private _getMetaWithOverrides(meta: Component|Directive|NgModule) {
|
||||||
|
if (meta.providers && meta.providers.length) {
|
||||||
|
const overrides =
|
||||||
|
flatten(meta.providers, (provider: any) => this._getProviderOverrides(provider));
|
||||||
|
if (overrides.length) {
|
||||||
|
return {...meta, providers: [...meta.providers, ...overrides]};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return meta;
|
||||||
|
}
|
||||||
|
|
||||||
|
private _compileNgModule(moduleType: NgModuleType, resolvers: Resolvers): void {
|
||||||
|
const ngModule = resolvers.module.resolve(moduleType);
|
||||||
|
|
||||||
|
if (ngModule === null) {
|
||||||
|
throw new Error(`${stringify(moduleType)} has not @NgModule annotation`);
|
||||||
|
}
|
||||||
|
|
||||||
|
const metadata = this._getMetaWithOverrides(ngModule);
|
||||||
|
compileNgModuleDefs(moduleType, metadata);
|
||||||
|
|
||||||
|
const declarations: Type<any>[] = flatten(ngModule.declarations || EMPTY_ARRAY);
|
||||||
|
const compiledComponents: Type<any>[] = [];
|
||||||
|
|
||||||
|
// Compile the components, directives and pipes declared by this module
|
||||||
|
declarations.forEach(declaration => {
|
||||||
|
const component = resolvers.component.resolve(declaration);
|
||||||
|
if (component) {
|
||||||
|
const metadata = this._getMetaWithOverrides(component);
|
||||||
|
compileComponent(declaration, metadata);
|
||||||
|
compiledComponents.push(declaration);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const directive = resolvers.directive.resolve(declaration);
|
||||||
|
if (directive) {
|
||||||
|
const metadata = this._getMetaWithOverrides(directive);
|
||||||
|
compileDirective(declaration, metadata);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const pipe = resolvers.pipe.resolve(declaration);
|
||||||
|
if (pipe) {
|
||||||
|
compilePipe(declaration, pipe);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Compile transitive modules, components, directives and pipes
|
||||||
|
const transitiveScope = this._transitiveScopesFor(moduleType, resolvers);
|
||||||
|
compiledComponents.forEach(
|
||||||
|
cmp => patchComponentDefWithScope((cmp as any).ngComponentDef, transitiveScope));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Compute the pair of transitive scopes (compilation scope and exported scope) for a given
|
||||||
|
* module.
|
||||||
|
*
|
||||||
|
* This operation is memoized and the result is cached on the module's definition. It can be
|
||||||
|
* called on modules with components that have not fully compiled yet, but the result should not
|
||||||
|
* be used until they have.
|
||||||
|
*/
|
||||||
|
private _transitiveScopesFor<T>(moduleType: Type<T>, resolvers: Resolvers):
|
||||||
|
NgModuleTransitiveScopes {
|
||||||
|
if (!isNgModule(moduleType)) {
|
||||||
|
throw new Error(`${moduleType.name} does not have an ngModuleDef`);
|
||||||
|
}
|
||||||
|
const def = moduleType.ngModuleDef;
|
||||||
|
|
||||||
|
if (def.transitiveCompileScopes !== null) {
|
||||||
|
return def.transitiveCompileScopes;
|
||||||
|
}
|
||||||
|
|
||||||
|
const scopes: NgModuleTransitiveScopes = {
|
||||||
|
compilation: {
|
||||||
|
directives: new Set<any>(),
|
||||||
|
pipes: new Set<any>(),
|
||||||
|
},
|
||||||
|
exported: {
|
||||||
|
directives: new Set<any>(),
|
||||||
|
pipes: new Set<any>(),
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
def.declarations.forEach(declared => {
|
||||||
|
const declaredWithDefs = declared as Type<any>& { ngPipeDef?: any; };
|
||||||
|
|
||||||
|
if (declaredWithDefs.ngPipeDef !== undefined) {
|
||||||
|
scopes.compilation.pipes.add(declared);
|
||||||
|
} else {
|
||||||
|
scopes.compilation.directives.add(declared);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
def.imports.forEach(<I>(imported: NgModuleType) => {
|
||||||
|
const ngModule = resolvers.module.resolve(imported);
|
||||||
|
|
||||||
|
if (ngModule === null) {
|
||||||
|
throw new Error(`Importing ${imported.name} which does not have an @ngModule`);
|
||||||
|
} else {
|
||||||
|
this._compileNgModule(imported, resolvers);
|
||||||
|
}
|
||||||
|
|
||||||
|
// When this module imports another, the imported module's exported directives and pipes are
|
||||||
|
// added to the compilation scope of this module.
|
||||||
|
const importedScope = this._transitiveScopesFor(imported, resolvers);
|
||||||
|
importedScope.exported.directives.forEach(entry => scopes.compilation.directives.add(entry));
|
||||||
|
importedScope.exported.pipes.forEach(entry => scopes.compilation.pipes.add(entry));
|
||||||
|
});
|
||||||
|
|
||||||
|
def.exports.forEach(<E>(exported: Type<E>) => {
|
||||||
|
const exportedTyped = exported as Type<E>& {
|
||||||
|
// Components, Directives, NgModules, and Pipes can all be exported.
|
||||||
|
ngComponentDef?: any;
|
||||||
|
ngDirectiveDef?: any;
|
||||||
|
ngModuleDef?: NgModuleDef<E>;
|
||||||
|
ngPipeDef?: any;
|
||||||
|
};
|
||||||
|
|
||||||
|
// Either the type is a module, a pipe, or a component/directive (which may not have an
|
||||||
|
// ngComponentDef as it might be compiled asynchronously).
|
||||||
|
if (isNgModule(exportedTyped)) {
|
||||||
|
// When this module exports another, the exported module's exported directives and pipes are
|
||||||
|
// added to both the compilation and exported scopes of this module.
|
||||||
|
const exportedScope = this._transitiveScopesFor(exportedTyped, resolvers);
|
||||||
|
exportedScope.exported.directives.forEach(entry => {
|
||||||
|
scopes.compilation.directives.add(entry);
|
||||||
|
scopes.exported.directives.add(entry);
|
||||||
|
});
|
||||||
|
exportedScope.exported.pipes.forEach(entry => {
|
||||||
|
scopes.compilation.pipes.add(entry);
|
||||||
|
scopes.exported.pipes.add(entry);
|
||||||
|
});
|
||||||
|
} else if (exportedTyped.ngPipeDef !== undefined) {
|
||||||
|
scopes.exported.pipes.add(exportedTyped);
|
||||||
|
} else {
|
||||||
|
scopes.exported.directives.add(exportedTyped);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
def.transitiveCompileScopes = scopes;
|
||||||
|
return scopes;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let testBed: TestBedRender3;
|
let testBed: TestBedRender3;
|
||||||
@ -458,9 +625,10 @@ export function _getTestBedRender3(): TestBedRender3 {
|
|||||||
|
|
||||||
const OWNER_MODULE = '__NG_MODULE__';
|
const OWNER_MODULE = '__NG_MODULE__';
|
||||||
/**
|
/**
|
||||||
* This function clears the OWNER_MODULE property from the Types. This is set in r3/jit/modules.ts.
|
* This function clears the OWNER_MODULE property from the Types. This is set in
|
||||||
* It is common for the same Type to be compiled in different tests. If we don't clear this we will
|
* r3/jit/modules.ts. It is common for the same Type to be compiled in different tests. If we don't
|
||||||
* get errors which will complain that the same Component/Directive is in more than one NgModule.
|
* clear this we will get errors which will complain that the same Component/Directive is in more
|
||||||
|
* than one NgModule.
|
||||||
*/
|
*/
|
||||||
function clearNgModules(type: Type<any>) {
|
function clearNgModules(type: Type<any>) {
|
||||||
if (type.hasOwnProperty(OWNER_MODULE)) {
|
if (type.hasOwnProperty(OWNER_MODULE)) {
|
||||||
@ -468,156 +636,13 @@ function clearNgModules(type: Type<any>) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function flatten<T>(values: any[], mapFn?: (value: T) => any): T[] {
|
||||||
// Module compiler
|
|
||||||
|
|
||||||
const EMPTY_ARRAY: Type<any>[] = [];
|
|
||||||
|
|
||||||
// Resolvers for Angular decorators
|
|
||||||
type Resolvers = {
|
|
||||||
module: Resolver<NgModule>,
|
|
||||||
component: Resolver<Directive>,
|
|
||||||
directive: Resolver<Component>,
|
|
||||||
pipe: Resolver<Pipe>,
|
|
||||||
};
|
|
||||||
|
|
||||||
function compileNgModule(moduleType: NgModuleType, resolvers: Resolvers): void {
|
|
||||||
const ngModule = resolvers.module.resolve(moduleType);
|
|
||||||
|
|
||||||
if (ngModule === null) {
|
|
||||||
throw new Error(`${stringify(moduleType)} has not @NgModule annotation`);
|
|
||||||
}
|
|
||||||
|
|
||||||
compileNgModuleDefs(moduleType, ngModule);
|
|
||||||
|
|
||||||
const declarations: Type<any>[] = flatten(ngModule.declarations || EMPTY_ARRAY);
|
|
||||||
|
|
||||||
const compiledComponents: Type<any>[] = [];
|
|
||||||
|
|
||||||
// Compile the components, directives and pipes declared by this module
|
|
||||||
declarations.forEach(declaration => {
|
|
||||||
const component = resolvers.component.resolve(declaration);
|
|
||||||
if (component) {
|
|
||||||
compileComponent(declaration, component);
|
|
||||||
compiledComponents.push(declaration);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const directive = resolvers.directive.resolve(declaration);
|
|
||||||
if (directive) {
|
|
||||||
compileDirective(declaration, directive);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const pipe = resolvers.pipe.resolve(declaration);
|
|
||||||
if (pipe) {
|
|
||||||
compilePipe(declaration, pipe);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
// Compile transitive modules, components, directives and pipes
|
|
||||||
const transitiveScope = transitiveScopesFor(moduleType, resolvers);
|
|
||||||
compiledComponents.forEach(
|
|
||||||
cmp => patchComponentDefWithScope((cmp as any).ngComponentDef, transitiveScope));
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Compute the pair of transitive scopes (compilation scope and exported scope) for a given module.
|
|
||||||
*
|
|
||||||
* This operation is memoized and the result is cached on the module's definition. It can be called
|
|
||||||
* on modules with components that have not fully compiled yet, but the result should not be used
|
|
||||||
* until they have.
|
|
||||||
*/
|
|
||||||
function transitiveScopesFor<T>(
|
|
||||||
moduleType: Type<T>, resolvers: Resolvers): NgModuleTransitiveScopes {
|
|
||||||
if (!isNgModule(moduleType)) {
|
|
||||||
throw new Error(`${moduleType.name} does not have an ngModuleDef`);
|
|
||||||
}
|
|
||||||
const def = moduleType.ngModuleDef;
|
|
||||||
|
|
||||||
if (def.transitiveCompileScopes !== null) {
|
|
||||||
return def.transitiveCompileScopes;
|
|
||||||
}
|
|
||||||
|
|
||||||
const scopes: NgModuleTransitiveScopes = {
|
|
||||||
compilation: {
|
|
||||||
directives: new Set<any>(),
|
|
||||||
pipes: new Set<any>(),
|
|
||||||
},
|
|
||||||
exported: {
|
|
||||||
directives: new Set<any>(),
|
|
||||||
pipes: new Set<any>(),
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
def.declarations.forEach(declared => {
|
|
||||||
const declaredWithDefs = declared as Type<any>& { ngPipeDef?: any; };
|
|
||||||
|
|
||||||
if (declaredWithDefs.ngPipeDef !== undefined) {
|
|
||||||
scopes.compilation.pipes.add(declared);
|
|
||||||
} else {
|
|
||||||
scopes.compilation.directives.add(declared);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
def.imports.forEach(<I>(imported: NgModuleType) => {
|
|
||||||
const ngModule = resolvers.module.resolve(imported);
|
|
||||||
|
|
||||||
if (ngModule === null) {
|
|
||||||
throw new Error(`Importing ${imported.name} which does not have an @ngModule`);
|
|
||||||
} else {
|
|
||||||
compileNgModule(imported, resolvers);
|
|
||||||
}
|
|
||||||
|
|
||||||
// When this module imports another, the imported module's exported directives and pipes are
|
|
||||||
// added to the compilation scope of this module.
|
|
||||||
const importedScope = transitiveScopesFor(imported, resolvers);
|
|
||||||
importedScope.exported.directives.forEach(entry => scopes.compilation.directives.add(entry));
|
|
||||||
importedScope.exported.pipes.forEach(entry => scopes.compilation.pipes.add(entry));
|
|
||||||
});
|
|
||||||
|
|
||||||
def.exports.forEach(<E>(exported: Type<E>) => {
|
|
||||||
const exportedTyped = exported as Type<E>& {
|
|
||||||
// Components, Directives, NgModules, and Pipes can all be exported.
|
|
||||||
ngComponentDef?: any;
|
|
||||||
ngDirectiveDef?: any;
|
|
||||||
ngModuleDef?: NgModuleDef<E>;
|
|
||||||
ngPipeDef?: any;
|
|
||||||
};
|
|
||||||
|
|
||||||
// Either the type is a module, a pipe, or a component/directive (which may not have an
|
|
||||||
// ngComponentDef as it might be compiled asynchronously).
|
|
||||||
if (isNgModule(exportedTyped)) {
|
|
||||||
// When this module exports another, the exported module's exported directives and pipes are
|
|
||||||
// added to both the compilation and exported scopes of this module.
|
|
||||||
const exportedScope = transitiveScopesFor(exportedTyped, resolvers);
|
|
||||||
exportedScope.exported.directives.forEach(entry => {
|
|
||||||
scopes.compilation.directives.add(entry);
|
|
||||||
scopes.exported.directives.add(entry);
|
|
||||||
});
|
|
||||||
exportedScope.exported.pipes.forEach(entry => {
|
|
||||||
scopes.compilation.pipes.add(entry);
|
|
||||||
scopes.exported.pipes.add(entry);
|
|
||||||
});
|
|
||||||
} else if (exportedTyped.ngPipeDef !== undefined) {
|
|
||||||
scopes.exported.pipes.add(exportedTyped);
|
|
||||||
} else {
|
|
||||||
scopes.exported.directives.add(exportedTyped);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
def.transitiveCompileScopes = scopes;
|
|
||||||
return scopes;
|
|
||||||
}
|
|
||||||
|
|
||||||
function flatten<T>(values: any[]): T[] {
|
|
||||||
const out: T[] = [];
|
const out: T[] = [];
|
||||||
values.forEach(value => {
|
values.forEach(value => {
|
||||||
if (Array.isArray(value)) {
|
if (Array.isArray(value)) {
|
||||||
out.push(...flatten<T>(value));
|
out.push(...flatten<T>(value, mapFn));
|
||||||
} else {
|
} else {
|
||||||
out.push(value);
|
out.push(mapFn ? mapFn(value) : value);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
return out;
|
return out;
|
||||||
|
@ -458,7 +458,7 @@ class CompWithUrlTemplate {
|
|||||||
expect(TestBed.get('a')).toBe('mockA: depValue');
|
expect(TestBed.get('a')).toBe('mockA: depValue');
|
||||||
});
|
});
|
||||||
|
|
||||||
fixmeIvy('FW-788: Support metadata override in TestBed (for AOT-compiled components)')
|
fixmeIvy('FW-855: TestBed.get(Compiler) should return TestBed-specific Compiler instance')
|
||||||
.it('should support SkipSelf', () => {
|
.it('should support SkipSelf', () => {
|
||||||
@NgModule({
|
@NgModule({
|
||||||
providers: [
|
providers: [
|
||||||
@ -553,147 +553,137 @@ class CompWithUrlTemplate {
|
|||||||
});
|
});
|
||||||
|
|
||||||
describe('in Components', () => {
|
describe('in Components', () => {
|
||||||
fixmeIvy('FW-788: Support metadata override in TestBed (for AOT-compiled components)')
|
it('should support useValue', () => {
|
||||||
.it('should support useValue', () => {
|
@Component({
|
||||||
@Component({
|
template: '',
|
||||||
template: '',
|
providers: [
|
||||||
providers: [
|
{provide: 'a', useValue: 'aValue'},
|
||||||
{provide: 'a', useValue: 'aValue'},
|
]
|
||||||
]
|
})
|
||||||
})
|
class MComp {
|
||||||
class MComp {
|
}
|
||||||
}
|
|
||||||
|
|
||||||
TestBed.overrideProvider('a', {useValue: 'mockValue'});
|
TestBed.overrideProvider('a', {useValue: 'mockValue'});
|
||||||
const ctx =
|
const ctx =
|
||||||
TestBed.configureTestingModule({declarations: [MComp]}).createComponent(MComp);
|
TestBed.configureTestingModule({declarations: [MComp]}).createComponent(MComp);
|
||||||
|
|
||||||
expect(ctx.debugElement.injector.get('a')).toBe('mockValue');
|
expect(ctx.debugElement.injector.get('a')).toBe('mockValue');
|
||||||
});
|
});
|
||||||
|
|
||||||
fixmeIvy('FW-788: Support metadata override in TestBed (for AOT-compiled components)')
|
it('should support useFactory', () => {
|
||||||
.it('should support useFactory', () => {
|
@Component({
|
||||||
@Component({
|
template: '',
|
||||||
template: '',
|
providers: [
|
||||||
providers: [
|
{provide: 'dep', useValue: 'depValue'},
|
||||||
{provide: 'dep', useValue: 'depValue'},
|
{provide: 'a', useValue: 'aValue'},
|
||||||
{provide: 'a', useValue: 'aValue'},
|
]
|
||||||
]
|
})
|
||||||
})
|
class MyComp {
|
||||||
class MyComp {
|
}
|
||||||
}
|
|
||||||
|
|
||||||
TestBed.overrideProvider(
|
TestBed.overrideProvider(
|
||||||
'a', {useFactory: (dep: any) => `mockA: ${dep}`, deps: ['dep']});
|
'a', {useFactory: (dep: any) => `mockA: ${dep}`, deps: ['dep']});
|
||||||
const ctx = TestBed.configureTestingModule({declarations: [MyComp]})
|
const ctx =
|
||||||
.createComponent(MyComp);
|
TestBed.configureTestingModule({declarations: [MyComp]}).createComponent(MyComp);
|
||||||
|
|
||||||
expect(ctx.debugElement.injector.get('a')).toBe('mockA: depValue');
|
expect(ctx.debugElement.injector.get('a')).toBe('mockA: depValue');
|
||||||
});
|
});
|
||||||
|
|
||||||
fixmeIvy('FW-788: Support metadata override in TestBed (for AOT-compiled components)')
|
it('should support @Optional without matches', () => {
|
||||||
.it('should support @Optional without matches', () => {
|
@Component({
|
||||||
@Component({
|
template: '',
|
||||||
template: '',
|
providers: [
|
||||||
providers: [
|
{provide: 'a', useValue: 'aValue'},
|
||||||
{provide: 'a', useValue: 'aValue'},
|
]
|
||||||
]
|
})
|
||||||
})
|
class MyComp {
|
||||||
class MyComp {
|
}
|
||||||
}
|
|
||||||
|
|
||||||
TestBed.overrideProvider(
|
TestBed.overrideProvider(
|
||||||
'a',
|
'a', {useFactory: (dep: any) => `mockA: ${dep}`, deps: [[new Optional(), 'dep']]});
|
||||||
{useFactory: (dep: any) => `mockA: ${dep}`, deps: [[new Optional(), 'dep']]});
|
const ctx =
|
||||||
const ctx = TestBed.configureTestingModule({declarations: [MyComp]})
|
TestBed.configureTestingModule({declarations: [MyComp]}).createComponent(MyComp);
|
||||||
.createComponent(MyComp);
|
|
||||||
|
|
||||||
expect(ctx.debugElement.injector.get('a')).toBe('mockA: null');
|
expect(ctx.debugElement.injector.get('a')).toBe('mockA: null');
|
||||||
});
|
});
|
||||||
|
|
||||||
fixmeIvy('FW-788: Support metadata override in TestBed (for AOT-compiled components)')
|
it('should support Optional with matches', () => {
|
||||||
.it('should support Optional with matches', () => {
|
@Component({
|
||||||
@Component({
|
template: '',
|
||||||
template: '',
|
providers: [
|
||||||
providers: [
|
{provide: 'dep', useValue: 'depValue'},
|
||||||
{provide: 'dep', useValue: 'depValue'},
|
{provide: 'a', useValue: 'aValue'},
|
||||||
{provide: 'a', useValue: 'aValue'},
|
]
|
||||||
]
|
})
|
||||||
})
|
class MyComp {
|
||||||
class MyComp {
|
}
|
||||||
}
|
|
||||||
|
|
||||||
TestBed.overrideProvider(
|
TestBed.overrideProvider(
|
||||||
'a',
|
'a', {useFactory: (dep: any) => `mockA: ${dep}`, deps: [[new Optional(), 'dep']]});
|
||||||
{useFactory: (dep: any) => `mockA: ${dep}`, deps: [[new Optional(), 'dep']]});
|
const ctx =
|
||||||
const ctx = TestBed.configureTestingModule({declarations: [MyComp]})
|
TestBed.configureTestingModule({declarations: [MyComp]}).createComponent(MyComp);
|
||||||
.createComponent(MyComp);
|
|
||||||
|
|
||||||
expect(ctx.debugElement.injector.get('a')).toBe('mockA: depValue');
|
expect(ctx.debugElement.injector.get('a')).toBe('mockA: depValue');
|
||||||
});
|
});
|
||||||
|
|
||||||
fixmeIvy('FW-788: Support metadata override in TestBed (for AOT-compiled components)')
|
it('should support SkipSelf', () => {
|
||||||
.it('should support SkipSelf', () => {
|
@Directive({
|
||||||
@Directive({
|
selector: '[myDir]',
|
||||||
selector: '[myDir]',
|
providers: [
|
||||||
providers: [
|
{provide: 'a', useValue: 'aValue'},
|
||||||
{provide: 'a', useValue: 'aValue'},
|
{provide: 'dep', useValue: 'depValue'},
|
||||||
{provide: 'dep', useValue: 'depValue'},
|
]
|
||||||
]
|
})
|
||||||
})
|
class MyDir {
|
||||||
class MyDir {
|
}
|
||||||
}
|
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
template: '<div myDir></div>',
|
template: '<div myDir></div>',
|
||||||
providers: [
|
providers: [
|
||||||
{provide: 'dep', useValue: 'parentDepValue'},
|
{provide: 'dep', useValue: 'parentDepValue'},
|
||||||
]
|
]
|
||||||
})
|
})
|
||||||
class MyComp {
|
class MyComp {
|
||||||
}
|
}
|
||||||
|
|
||||||
TestBed.overrideProvider(
|
TestBed.overrideProvider(
|
||||||
'a',
|
'a', {useFactory: (dep: any) => `mockA: ${dep}`, deps: [[new SkipSelf(), 'dep']]});
|
||||||
{useFactory: (dep: any) => `mockA: ${dep}`, deps: [[new SkipSelf(), 'dep']]});
|
const ctx = TestBed.configureTestingModule({declarations: [MyComp, MyDir]})
|
||||||
const ctx = TestBed.configureTestingModule({declarations: [MyComp, MyDir]})
|
.createComponent(MyComp);
|
||||||
.createComponent(MyComp);
|
expect(ctx.debugElement.children[0].injector.get('a')).toBe('mockA: parentDepValue');
|
||||||
expect(ctx.debugElement.children[0].injector.get('a'))
|
});
|
||||||
.toBe('mockA: parentDepValue');
|
|
||||||
});
|
|
||||||
|
|
||||||
fixmeIvy('FW-788: Support metadata override in TestBed (for AOT-compiled components)')
|
it('should support multiple providers in a template', () => {
|
||||||
.it('should support multiple providers in a template', () => {
|
@Directive({
|
||||||
@Directive({
|
selector: '[myDir1]',
|
||||||
selector: '[myDir1]',
|
providers: [
|
||||||
providers: [
|
{provide: 'a', useValue: 'aValue1'},
|
||||||
{provide: 'a', useValue: 'aValue1'},
|
]
|
||||||
]
|
})
|
||||||
})
|
class MyDir1 {
|
||||||
class MyDir1 {
|
}
|
||||||
}
|
|
||||||
|
|
||||||
@Directive({
|
@Directive({
|
||||||
selector: '[myDir2]',
|
selector: '[myDir2]',
|
||||||
providers: [
|
providers: [
|
||||||
{provide: 'a', useValue: 'aValue2'},
|
{provide: 'a', useValue: 'aValue2'},
|
||||||
]
|
]
|
||||||
})
|
})
|
||||||
class MyDir2 {
|
class MyDir2 {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
template: '<div myDir1></div><div myDir2></div>',
|
template: '<div myDir1></div><div myDir2></div>',
|
||||||
})
|
})
|
||||||
class MyComp {
|
class MyComp {
|
||||||
}
|
}
|
||||||
|
|
||||||
TestBed.overrideProvider('a', {useValue: 'mockA'});
|
TestBed.overrideProvider('a', {useValue: 'mockA'});
|
||||||
const ctx = TestBed.configureTestingModule({declarations: [MyComp, MyDir1, MyDir2]})
|
const ctx = TestBed.configureTestingModule({declarations: [MyComp, MyDir1, MyDir2]})
|
||||||
.createComponent(MyComp);
|
.createComponent(MyComp);
|
||||||
expect(ctx.debugElement.children[0].injector.get('a')).toBe('mockA');
|
expect(ctx.debugElement.children[0].injector.get('a')).toBe('mockA');
|
||||||
expect(ctx.debugElement.children[1].injector.get('a')).toBe('mockA');
|
expect(ctx.debugElement.children[1].injector.get('a')).toBe('mockA');
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('injecting eager providers into an eager overwritten provider', () => {
|
describe('injecting eager providers into an eager overwritten provider', () => {
|
||||||
@Component({
|
@Component({
|
||||||
@ -708,25 +698,23 @@ class CompWithUrlTemplate {
|
|||||||
constructor(@Inject('a') a: any, @Inject('b') b: any) {}
|
constructor(@Inject('a') a: any, @Inject('b') b: any) {}
|
||||||
}
|
}
|
||||||
|
|
||||||
fixmeIvy('FW-788: Support metadata override in TestBed (for AOT-compiled components)')
|
it('should inject providers that were declared before it', () => {
|
||||||
.it('should inject providers that were declared before it', () => {
|
TestBed.overrideProvider(
|
||||||
TestBed.overrideProvider(
|
'b', {useFactory: (a: string) => `mockB: ${a}`, deps: ['a']});
|
||||||
'b', {useFactory: (a: string) => `mockB: ${a}`, deps: ['a']});
|
const ctx =
|
||||||
const ctx = TestBed.configureTestingModule({declarations: [MyComp]})
|
TestBed.configureTestingModule({declarations: [MyComp]}).createComponent(MyComp);
|
||||||
.createComponent(MyComp);
|
|
||||||
|
|
||||||
expect(ctx.debugElement.injector.get('b')).toBe('mockB: aValue');
|
expect(ctx.debugElement.injector.get('b')).toBe('mockB: aValue');
|
||||||
});
|
});
|
||||||
|
|
||||||
fixmeIvy('FW-788: Support metadata override in TestBed (for AOT-compiled components)')
|
it('should inject providers that were declared after it', () => {
|
||||||
.it('should inject providers that were declared after it', () => {
|
TestBed.overrideProvider(
|
||||||
TestBed.overrideProvider(
|
'a', {useFactory: (b: string) => `mockA: ${b}`, deps: ['b']});
|
||||||
'a', {useFactory: (b: string) => `mockA: ${b}`, deps: ['b']});
|
const ctx =
|
||||||
const ctx = TestBed.configureTestingModule({declarations: [MyComp]})
|
TestBed.configureTestingModule({declarations: [MyComp]}).createComponent(MyComp);
|
||||||
.createComponent(MyComp);
|
|
||||||
|
|
||||||
expect(ctx.debugElement.injector.get('a')).toBe('mockA: bValue');
|
expect(ctx.debugElement.injector.get('a')).toBe('mockA: bValue');
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -739,7 +727,7 @@ class CompWithUrlTemplate {
|
|||||||
});
|
});
|
||||||
|
|
||||||
describe('overrideTemplateUsingTestingModule', () => {
|
describe('overrideTemplateUsingTestingModule', () => {
|
||||||
fixmeIvy('FW-788: Support metadata override in TestBed (for AOT-compiled components)')
|
fixmeIvy('FW-851: TestBed.overrideTemplateUsingTestingModule is not implemented')
|
||||||
.it('should compile the template in the context of the testing module', () => {
|
.it('should compile the template in the context of the testing module', () => {
|
||||||
@Component({selector: 'comp', template: 'a'})
|
@Component({selector: 'comp', template: 'a'})
|
||||||
class MyComponent {
|
class MyComponent {
|
||||||
@ -768,7 +756,7 @@ class CompWithUrlTemplate {
|
|||||||
expect(testDir !.test).toBe('some prop');
|
expect(testDir !.test).toBe('some prop');
|
||||||
});
|
});
|
||||||
|
|
||||||
fixmeIvy('FW-788: Support metadata override in TestBed (for AOT-compiled components)')
|
fixmeIvy('FW-851: TestBed.overrideTemplateUsingTestingModule is not implemented')
|
||||||
.it('should throw if the TestBed is already created', () => {
|
.it('should throw if the TestBed is already created', () => {
|
||||||
@Component({selector: 'comp', template: 'a'})
|
@Component({selector: 'comp', template: 'a'})
|
||||||
class MyComponent {
|
class MyComponent {
|
||||||
@ -781,7 +769,7 @@ class CompWithUrlTemplate {
|
|||||||
/Cannot override template when the test module has already been instantiated/);
|
/Cannot override template when the test module has already been instantiated/);
|
||||||
});
|
});
|
||||||
|
|
||||||
fixmeIvy('FW-788: Support metadata override in TestBed (for AOT-compiled components)')
|
fixmeIvy('FW-851: TestBed.overrideTemplateUsingTestingModule is not implemented')
|
||||||
.it('should reset overrides when the testing module is resetted', () => {
|
.it('should reset overrides when the testing module is resetted', () => {
|
||||||
@Component({selector: 'comp', template: 'a'})
|
@Component({selector: 'comp', template: 'a'})
|
||||||
class MyComponent {
|
class MyComponent {
|
||||||
|
Loading…
x
Reference in New Issue
Block a user