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;
|
||||
|
||||
const EMPTY_ARRAY: Type<any>[] = [];
|
||||
|
||||
// Resolvers for Angular decorators
|
||||
type Resolvers = {
|
||||
module: Resolver<NgModule>,
|
||||
component: Resolver<Directive>,
|
||||
directive: Resolver<Component>,
|
||||
pipe: Resolver<Pipe>,
|
||||
};
|
||||
|
||||
/**
|
||||
* @description
|
||||
* 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 _providerOverrides: Provider[] = [];
|
||||
private _rootProviderOverrides: Provider[] = [];
|
||||
private _providerOverridesByToken: Map<any, Provider[]> = new Map();
|
||||
|
||||
// test module configuration
|
||||
private _providers: Provider[] = [];
|
||||
@ -229,6 +240,7 @@ export class TestBedRender3 implements Injector, TestBed {
|
||||
this._pipeOverrides = [];
|
||||
this._providerOverrides = [];
|
||||
this._rootProviderOverrides = [];
|
||||
this._providerOverridesByToken.clear();
|
||||
|
||||
// reset test module config
|
||||
this._providers = [];
|
||||
@ -322,17 +334,21 @@ export class TestBedRender3 implements Injector, TestBed {
|
||||
*/
|
||||
overrideProvider(token: any, provider: {useFactory?: Function, useValue?: any, deps?: any[]}):
|
||||
void {
|
||||
const providerDef = provider.useFactory ?
|
||||
{provide: token, useFactory: provider.useFactory, deps: provider.deps || []} :
|
||||
{provide: token, useValue: provider.useValue};
|
||||
|
||||
let injectableDef: InjectableDef<any>|null;
|
||||
const isRoot =
|
||||
(typeof token !== 'string' && (injectableDef = getInjectableDef(token)) &&
|
||||
injectableDef.providedIn === 'root');
|
||||
const overrides = isRoot ? this._rootProviderOverrides : this._providerOverrides;
|
||||
const overridesBucket = isRoot ? this._rootProviderOverrides : this._providerOverrides;
|
||||
overridesBucket.push(providerDef);
|
||||
|
||||
if (provider.useFactory) {
|
||||
overrides.push({provide: token, useFactory: provider.useFactory, deps: provider.deps || []});
|
||||
} else {
|
||||
overrides.push({provide: token, useValue: provider.useValue});
|
||||
}
|
||||
// keep all overrides grouped by token as well for fast lookups using token
|
||||
const overridesForToken = this._providerOverridesByToken.get(token) || [];
|
||||
overridesForToken.push(providerDef);
|
||||
this._providerOverridesByToken.set(token, overridesForToken);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -387,8 +403,7 @@ export class TestBedRender3 implements Injector, TestBed {
|
||||
|
||||
const resolvers = this._getResolvers();
|
||||
const testModuleType = this._createTestModule();
|
||||
|
||||
compileNgModule(testModuleType, resolvers);
|
||||
this._compileNgModule(testModuleType, resolvers);
|
||||
|
||||
const parentInjector = this.platform.injector;
|
||||
this._moduleRef = new NgModuleRef(testModuleType, parentInjector);
|
||||
@ -399,6 +414,14 @@ export class TestBedRender3 implements Injector, TestBed {
|
||||
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
|
||||
private _getResolvers() {
|
||||
const module = new NgModuleResolver();
|
||||
@ -448,6 +471,150 @@ export class TestBedRender3 implements Injector, TestBed {
|
||||
|
||||
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;
|
||||
@ -458,9 +625,10 @@ export function _getTestBedRender3(): TestBedRender3 {
|
||||
|
||||
const OWNER_MODULE = '__NG_MODULE__';
|
||||
/**
|
||||
* This function clears the OWNER_MODULE property from the Types. This is set in r3/jit/modules.ts.
|
||||
* It is common for the same Type to be compiled in different tests. If we don't clear this we will
|
||||
* get errors which will complain that the same Component/Directive is in more than one NgModule.
|
||||
* This function clears the OWNER_MODULE property from the Types. This is set in
|
||||
* r3/jit/modules.ts. It is common for the same Type to be compiled in different tests. If we don't
|
||||
* 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>) {
|
||||
if (type.hasOwnProperty(OWNER_MODULE)) {
|
||||
@ -468,156 +636,13 @@ function clearNgModules(type: Type<any>) {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// 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[] {
|
||||
function flatten<T>(values: any[], mapFn?: (value: T) => any): T[] {
|
||||
const out: T[] = [];
|
||||
values.forEach(value => {
|
||||
if (Array.isArray(value)) {
|
||||
out.push(...flatten<T>(value));
|
||||
out.push(...flatten<T>(value, mapFn));
|
||||
} else {
|
||||
out.push(value);
|
||||
out.push(mapFn ? mapFn(value) : value);
|
||||
}
|
||||
});
|
||||
return out;
|
||||
|
@ -458,7 +458,7 @@ class CompWithUrlTemplate {
|
||||
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', () => {
|
||||
@NgModule({
|
||||
providers: [
|
||||
@ -553,147 +553,137 @@ class CompWithUrlTemplate {
|
||||
});
|
||||
|
||||
describe('in Components', () => {
|
||||
fixmeIvy('FW-788: Support metadata override in TestBed (for AOT-compiled components)')
|
||||
.it('should support useValue', () => {
|
||||
@Component({
|
||||
template: '',
|
||||
providers: [
|
||||
{provide: 'a', useValue: 'aValue'},
|
||||
]
|
||||
})
|
||||
class MComp {
|
||||
}
|
||||
it('should support useValue', () => {
|
||||
@Component({
|
||||
template: '',
|
||||
providers: [
|
||||
{provide: 'a', useValue: 'aValue'},
|
||||
]
|
||||
})
|
||||
class MComp {
|
||||
}
|
||||
|
||||
TestBed.overrideProvider('a', {useValue: 'mockValue'});
|
||||
const ctx =
|
||||
TestBed.configureTestingModule({declarations: [MComp]}).createComponent(MComp);
|
||||
TestBed.overrideProvider('a', {useValue: 'mockValue'});
|
||||
const ctx =
|
||||
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', () => {
|
||||
@Component({
|
||||
template: '',
|
||||
providers: [
|
||||
{provide: 'dep', useValue: 'depValue'},
|
||||
{provide: 'a', useValue: 'aValue'},
|
||||
]
|
||||
})
|
||||
class MyComp {
|
||||
}
|
||||
it('should support useFactory', () => {
|
||||
@Component({
|
||||
template: '',
|
||||
providers: [
|
||||
{provide: 'dep', useValue: 'depValue'},
|
||||
{provide: 'a', useValue: 'aValue'},
|
||||
]
|
||||
})
|
||||
class MyComp {
|
||||
}
|
||||
|
||||
TestBed.overrideProvider(
|
||||
'a', {useFactory: (dep: any) => `mockA: ${dep}`, deps: ['dep']});
|
||||
const ctx = TestBed.configureTestingModule({declarations: [MyComp]})
|
||||
.createComponent(MyComp);
|
||||
TestBed.overrideProvider(
|
||||
'a', {useFactory: (dep: any) => `mockA: ${dep}`, deps: ['dep']});
|
||||
const ctx =
|
||||
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', () => {
|
||||
@Component({
|
||||
template: '',
|
||||
providers: [
|
||||
{provide: 'a', useValue: 'aValue'},
|
||||
]
|
||||
})
|
||||
class MyComp {
|
||||
}
|
||||
it('should support @Optional without matches', () => {
|
||||
@Component({
|
||||
template: '',
|
||||
providers: [
|
||||
{provide: 'a', useValue: 'aValue'},
|
||||
]
|
||||
})
|
||||
class MyComp {
|
||||
}
|
||||
|
||||
TestBed.overrideProvider(
|
||||
'a',
|
||||
{useFactory: (dep: any) => `mockA: ${dep}`, deps: [[new Optional(), 'dep']]});
|
||||
const ctx = TestBed.configureTestingModule({declarations: [MyComp]})
|
||||
.createComponent(MyComp);
|
||||
TestBed.overrideProvider(
|
||||
'a', {useFactory: (dep: any) => `mockA: ${dep}`, deps: [[new Optional(), 'dep']]});
|
||||
const ctx =
|
||||
TestBed.configureTestingModule({declarations: [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', () => {
|
||||
@Component({
|
||||
template: '',
|
||||
providers: [
|
||||
{provide: 'dep', useValue: 'depValue'},
|
||||
{provide: 'a', useValue: 'aValue'},
|
||||
]
|
||||
})
|
||||
class MyComp {
|
||||
}
|
||||
it('should support Optional with matches', () => {
|
||||
@Component({
|
||||
template: '',
|
||||
providers: [
|
||||
{provide: 'dep', useValue: 'depValue'},
|
||||
{provide: 'a', useValue: 'aValue'},
|
||||
]
|
||||
})
|
||||
class MyComp {
|
||||
}
|
||||
|
||||
TestBed.overrideProvider(
|
||||
'a',
|
||||
{useFactory: (dep: any) => `mockA: ${dep}`, deps: [[new Optional(), 'dep']]});
|
||||
const ctx = TestBed.configureTestingModule({declarations: [MyComp]})
|
||||
.createComponent(MyComp);
|
||||
TestBed.overrideProvider(
|
||||
'a', {useFactory: (dep: any) => `mockA: ${dep}`, deps: [[new Optional(), 'dep']]});
|
||||
const ctx =
|
||||
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 SkipSelf', () => {
|
||||
@Directive({
|
||||
selector: '[myDir]',
|
||||
providers: [
|
||||
{provide: 'a', useValue: 'aValue'},
|
||||
{provide: 'dep', useValue: 'depValue'},
|
||||
]
|
||||
})
|
||||
class MyDir {
|
||||
}
|
||||
it('should support SkipSelf', () => {
|
||||
@Directive({
|
||||
selector: '[myDir]',
|
||||
providers: [
|
||||
{provide: 'a', useValue: 'aValue'},
|
||||
{provide: 'dep', useValue: 'depValue'},
|
||||
]
|
||||
})
|
||||
class MyDir {
|
||||
}
|
||||
|
||||
@Component({
|
||||
template: '<div myDir></div>',
|
||||
providers: [
|
||||
{provide: 'dep', useValue: 'parentDepValue'},
|
||||
]
|
||||
})
|
||||
class MyComp {
|
||||
}
|
||||
@Component({
|
||||
template: '<div myDir></div>',
|
||||
providers: [
|
||||
{provide: 'dep', useValue: 'parentDepValue'},
|
||||
]
|
||||
})
|
||||
class MyComp {
|
||||
}
|
||||
|
||||
TestBed.overrideProvider(
|
||||
'a',
|
||||
{useFactory: (dep: any) => `mockA: ${dep}`, deps: [[new SkipSelf(), 'dep']]});
|
||||
const ctx = TestBed.configureTestingModule({declarations: [MyComp, MyDir]})
|
||||
.createComponent(MyComp);
|
||||
expect(ctx.debugElement.children[0].injector.get('a'))
|
||||
.toBe('mockA: parentDepValue');
|
||||
});
|
||||
TestBed.overrideProvider(
|
||||
'a', {useFactory: (dep: any) => `mockA: ${dep}`, deps: [[new SkipSelf(), 'dep']]});
|
||||
const ctx = TestBed.configureTestingModule({declarations: [MyComp, MyDir]})
|
||||
.createComponent(MyComp);
|
||||
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', () => {
|
||||
@Directive({
|
||||
selector: '[myDir1]',
|
||||
providers: [
|
||||
{provide: 'a', useValue: 'aValue1'},
|
||||
]
|
||||
})
|
||||
class MyDir1 {
|
||||
}
|
||||
it('should support multiple providers in a template', () => {
|
||||
@Directive({
|
||||
selector: '[myDir1]',
|
||||
providers: [
|
||||
{provide: 'a', useValue: 'aValue1'},
|
||||
]
|
||||
})
|
||||
class MyDir1 {
|
||||
}
|
||||
|
||||
@Directive({
|
||||
selector: '[myDir2]',
|
||||
providers: [
|
||||
{provide: 'a', useValue: 'aValue2'},
|
||||
]
|
||||
})
|
||||
class MyDir2 {
|
||||
}
|
||||
@Directive({
|
||||
selector: '[myDir2]',
|
||||
providers: [
|
||||
{provide: 'a', useValue: 'aValue2'},
|
||||
]
|
||||
})
|
||||
class MyDir2 {
|
||||
}
|
||||
|
||||
@Component({
|
||||
template: '<div myDir1></div><div myDir2></div>',
|
||||
})
|
||||
class MyComp {
|
||||
}
|
||||
@Component({
|
||||
template: '<div myDir1></div><div myDir2></div>',
|
||||
})
|
||||
class MyComp {
|
||||
}
|
||||
|
||||
TestBed.overrideProvider('a', {useValue: 'mockA'});
|
||||
const ctx = TestBed.configureTestingModule({declarations: [MyComp, MyDir1, MyDir2]})
|
||||
.createComponent(MyComp);
|
||||
expect(ctx.debugElement.children[0].injector.get('a')).toBe('mockA');
|
||||
expect(ctx.debugElement.children[1].injector.get('a')).toBe('mockA');
|
||||
});
|
||||
TestBed.overrideProvider('a', {useValue: 'mockA'});
|
||||
const ctx = TestBed.configureTestingModule({declarations: [MyComp, MyDir1, MyDir2]})
|
||||
.createComponent(MyComp);
|
||||
expect(ctx.debugElement.children[0].injector.get('a')).toBe('mockA');
|
||||
expect(ctx.debugElement.children[1].injector.get('a')).toBe('mockA');
|
||||
});
|
||||
|
||||
describe('injecting eager providers into an eager overwritten provider', () => {
|
||||
@Component({
|
||||
@ -708,25 +698,23 @@ class CompWithUrlTemplate {
|
||||
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', () => {
|
||||
TestBed.overrideProvider(
|
||||
'b', {useFactory: (a: string) => `mockB: ${a}`, deps: ['a']});
|
||||
const ctx = TestBed.configureTestingModule({declarations: [MyComp]})
|
||||
.createComponent(MyComp);
|
||||
it('should inject providers that were declared before it', () => {
|
||||
TestBed.overrideProvider(
|
||||
'b', {useFactory: (a: string) => `mockB: ${a}`, deps: ['a']});
|
||||
const ctx =
|
||||
TestBed.configureTestingModule({declarations: [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', () => {
|
||||
TestBed.overrideProvider(
|
||||
'a', {useFactory: (b: string) => `mockA: ${b}`, deps: ['b']});
|
||||
const ctx = TestBed.configureTestingModule({declarations: [MyComp]})
|
||||
.createComponent(MyComp);
|
||||
it('should inject providers that were declared after it', () => {
|
||||
TestBed.overrideProvider(
|
||||
'a', {useFactory: (b: string) => `mockA: ${b}`, deps: ['b']});
|
||||
const ctx =
|
||||
TestBed.configureTestingModule({declarations: [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', () => {
|
||||
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', () => {
|
||||
@Component({selector: 'comp', template: 'a'})
|
||||
class MyComponent {
|
||||
@ -768,7 +756,7 @@ class CompWithUrlTemplate {
|
||||
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', () => {
|
||||
@Component({selector: 'comp', template: 'a'})
|
||||
class MyComponent {
|
||||
@ -781,7 +769,7 @@ class CompWithUrlTemplate {
|
||||
/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', () => {
|
||||
@Component({selector: 'comp', template: 'a'})
|
||||
class MyComponent {
|
||||
|
Loading…
x
Reference in New Issue
Block a user