feat: introduce TestBed.overrideProvider (#16725)

This allows to overwrite all providers for a token, not matter
where they were defined.

This can be used to test JIT and AOT’ed code in the same way.

Design doc: https://docs.google.com/document/d/1VmTkz0EbEVSWfEEWEvQ5sXyQXSCvtMOw4t7pKU-jOwc/edit?usp=sharing
This commit is contained in:
Tobias Bosch
2017-05-15 13:12:10 -07:00
committed by Jason Aden
parent 1eba623d12
commit 39b92f7e54
20 changed files with 735 additions and 121 deletions

View File

@ -787,15 +787,46 @@ function declareTests({useJit}: {useJit: boolean}) {
expect(child.get(Injector)).toBe(child);
});
it('should allow to inject lazy providers via Injector.get from an eager provider that is declared earlier',
() => {
@NgModule({providers: [{provide: 'a', useFactory: () => 'aValue'}]})
class SomeModule {
public a: string;
constructor(injector: Injector) { this.a = injector.get('a'); }
}
expect(createModule(SomeModule).instance.a).toBe('aValue');
});
describe('injecting lazy providers into an eager provider via Injector.get', () => {
it('should inject providers that were declared before it', () => {
@NgModule({
providers: [
{provide: 'lazy', useFactory: () => 'lazyValue'},
{
provide: 'eager',
useFactory: (i: Injector) => `eagerValue: ${i.get('lazy')}`,
deps: [Injector]
},
]
})
class MyModule {
// NgModule is eager, which makes all of its deps eager
constructor(@Inject('eager') eager: any) {}
}
expect(createModule(MyModule).injector.get('eager')).toBe('eagerValue: lazyValue');
});
it('should inject providers that were declared after it', () => {
@NgModule({
providers: [
{
provide: 'eager',
useFactory: (i: Injector) => `eagerValue: ${i.get('lazy')}`,
deps: [Injector]
},
{provide: 'lazy', useFactory: () => 'lazyValue'},
]
})
class MyModule {
// NgModule is eager, which makes all of its deps eager
constructor(@Inject('eager') eager: any) {}
}
expect(createModule(MyModule).injector.get('eager')).toBe('eagerValue: lazyValue');
});
});
it('should throw when no provider defined', () => {
const injector = createInjector([]);

View File

@ -342,6 +342,53 @@ export function main() {
expect(created).toBe(true);
});
describe('injecting lazy providers into an eager provider via Injector.get', () => {
it('should inject providers that were declared before it', () => {
@Component({
template: '',
providers: [
{provide: 'lazy', useFactory: () => 'lazyValue'},
{
provide: 'eager',
useFactory: (i: Injector) => `eagerValue: ${i.get('lazy')}`,
deps: [Injector]
},
]
})
class MyComp {
// Component is eager, which makes all of its deps eager
constructor(@Inject('eager') eager: any) {}
}
const ctx =
TestBed.configureTestingModule({declarations: [MyComp]}).createComponent(MyComp);
expect(ctx.debugElement.injector.get('eager')).toBe('eagerValue: lazyValue');
});
it('should inject providers that were declared after it', () => {
@Component({
template: '',
providers: [
{
provide: 'eager',
useFactory: (i: Injector) => `eagerValue: ${i.get('lazy')}`,
deps: [Injector]
},
{provide: 'lazy', useFactory: () => 'lazyValue'},
]
})
class MyComp {
// Component is eager, which makes all of its deps eager
constructor(@Inject('eager') eager: any) {}
}
const ctx =
TestBed.configureTestingModule({declarations: [MyComp]}).createComponent(MyComp);
expect(ctx.debugElement.injector.get('eager')).toBe('eagerValue: lazyValue');
});
});
it('should allow injecting lazy providers via Injector.get from an eager provider that is declared earlier',
() => {
@Component({providers: [{provide: 'a', useFactory: () => 'aValue'}], template: ''})

View File

@ -11,7 +11,7 @@ import {ArgumentType, BindingFlags, NodeCheckFn, NodeDef, NodeFlags, RootData, S
import {inject} from '@angular/core/testing';
import {getDOM} from '@angular/platform-browser/src/dom/dom_adapter';
import {createRootView, isBrowser} from './helper';
import {createEmbeddedView, createRootView, isBrowser} from './helper';
export function main() {
describe(`Embedded Views`, () => {
@ -45,8 +45,7 @@ export function main() {
]),
parentContext);
const childView =
Services.createEmbeddedView(parentView, parentView.def.nodes[1], childContext);
const childView = createEmbeddedView(parentView, parentView.def.nodes[1], childContext);
expect(childView.component).toBe(parentContext);
expect(childView.context).toBe(childContext);
});
@ -64,8 +63,8 @@ export function main() {
]));
const viewContainerData = asElementData(parentView, 1);
const childView0 = Services.createEmbeddedView(parentView, parentView.def.nodes[1]);
const childView1 = Services.createEmbeddedView(parentView, parentView.def.nodes[2]);
const childView0 = createEmbeddedView(parentView, parentView.def.nodes[1]);
const childView1 = createEmbeddedView(parentView, parentView.def.nodes[2]);
attachEmbeddedView(parentView, viewContainerData, 0, childView0);
attachEmbeddedView(parentView, viewContainerData, 1, childView1);
@ -95,8 +94,8 @@ export function main() {
]));
const viewContainerData = asElementData(parentView, 1);
const childView0 = Services.createEmbeddedView(parentView, parentView.def.nodes[1]);
const childView1 = Services.createEmbeddedView(parentView, parentView.def.nodes[2]);
const childView0 = createEmbeddedView(parentView, parentView.def.nodes[1]);
const childView1 = createEmbeddedView(parentView, parentView.def.nodes[2]);
attachEmbeddedView(parentView, viewContainerData, 0, childView0);
attachEmbeddedView(parentView, viewContainerData, 1, childView1);
@ -119,7 +118,7 @@ export function main() {
elementDef(NodeFlags.None, null !, null !, 0, 'span', [['name', 'after']])
]));
const childView0 = Services.createEmbeddedView(parentView, parentView.def.nodes[0]);
const childView0 = createEmbeddedView(parentView, parentView.def.nodes[0]);
attachEmbeddedView(parentView, asElementData(parentView, 0), 0, childView0);
const rootNodes = rootRenderNodes(parentView);
@ -146,7 +145,7 @@ export function main() {
update))
]));
const childView0 = Services.createEmbeddedView(parentView, parentView.def.nodes[1]);
const childView0 = createEmbeddedView(parentView, parentView.def.nodes[1]);
attachEmbeddedView(parentView, asElementData(parentView, 1), 0, childView0);
@ -180,7 +179,7 @@ export function main() {
]))
]));
const childView0 = Services.createEmbeddedView(parentView, parentView.def.nodes[1]);
const childView0 = createEmbeddedView(parentView, parentView.def.nodes[1]);
attachEmbeddedView(parentView, asElementData(parentView, 1), 0, childView0);
Services.destroyView(parentView);

View File

@ -7,7 +7,7 @@
*/
import {Injector, NgModuleRef, RootRenderer, Sanitizer} from '@angular/core';
import {ArgumentType, NodeCheckFn, RootData, Services, ViewData, ViewDefinition, initServicesIfNeeded} from '@angular/core/src/view/index';
import {ArgumentType, NodeCheckFn, NodeDef, RootData, Services, ViewData, ViewDefinition, initServicesIfNeeded} from '@angular/core/src/view/index';
import {TestBed} from '@angular/core/testing';
import {getDOM} from '@angular/platform-browser/src/dom/dom_adapter';
@ -37,6 +37,10 @@ export function createRootView(
TestBed.get(NgModuleRef), context);
}
export function createEmbeddedView(parent: ViewData, anchorDef: NodeDef, context?: any): ViewData {
return Services.createEmbeddedView(parent, anchorDef, anchorDef.element !.template !, context);
}
export let removeNodes: Node[];
beforeEach(() => { removeNodes = []; });
afterEach(() => { removeNodes.forEach((node) => getDOM().remove(node)); });

View File

@ -10,7 +10,7 @@ import {Injector, RenderComponentType, RootRenderer, Sanitizer, SecurityContext,
import {DebugContext, NodeDef, NodeFlags, RootData, Services, ViewData, ViewDefinition, ViewDefinitionFactory, ViewFlags, ViewHandleEventFn, ViewUpdateFn, anchorDef, asElementData, asProviderData, asTextData, attachEmbeddedView, detachEmbeddedView, directiveDef, elementDef, ngContentDef, rootRenderNodes, textDef, viewDef} from '@angular/core/src/view/index';
import {getDOM} from '@angular/platform-browser/src/dom/dom_adapter';
import {createRootView, isBrowser} from './helper';
import {createEmbeddedView, createRootView, isBrowser} from './helper';
export function main() {
describe(`View NgContent`, () => {
@ -121,7 +121,7 @@ export function main() {
])));
const componentView = asElementData(view, 0).componentView;
const view0 = Services.createEmbeddedView(componentView, componentView.def.nodes[1]);
const view0 = createEmbeddedView(componentView, componentView.def.nodes[1]);
attachEmbeddedView(view, asElementData(componentView, 1), 0, view0);
expect(getDOM().childNodes(getDOM().firstChild(rootNodes[0])).length).toBe(3);

View File

@ -12,7 +12,7 @@ import {BindingFlags, DebugContext, NodeDef, NodeFlags, QueryBindingType, QueryV
import {inject} from '@angular/core/testing';
import {getDOM} from '@angular/platform-browser/src/dom/dom_adapter';
import {createRootView} from './helper';
import {createEmbeddedView, createRootView} from './helper';
export function main() {
describe(`Query Views`, () => {
@ -155,7 +155,7 @@ export function main() {
...contentQueryProviders(),
]));
const childView = Services.createEmbeddedView(view, view.def.nodes[3]);
const childView = createEmbeddedView(view, view.def.nodes[3]);
attachEmbeddedView(view, asElementData(view, 3), 0, childView);
Services.checkAndUpdateView(view);
@ -183,7 +183,7 @@ export function main() {
anchorDef(NodeFlags.EmbeddedViews, null !, null !, 0),
]));
const childView = Services.createEmbeddedView(view, view.def.nodes[3]);
const childView = createEmbeddedView(view, view.def.nodes[3]);
// attach at a different place than the one where the template was defined
attachEmbeddedView(view, asElementData(view, 7), 0, childView);
@ -214,7 +214,7 @@ export function main() {
const qs: QueryService = asProviderData(view, 1).instance;
expect(qs.a.length).toBe(0);
const childView = Services.createEmbeddedView(view, view.def.nodes[3]);
const childView = createEmbeddedView(view, view.def.nodes[3]);
attachEmbeddedView(view, asElementData(view, 3), 0, childView);
Services.checkAndUpdateView(view);
@ -245,7 +245,7 @@ export function main() {
expect(comp.a.length).toBe(0);
const compView = asElementData(view, 0).componentView;
const childView = Services.createEmbeddedView(compView, compView.def.nodes[1]);
const childView = createEmbeddedView(compView, compView.def.nodes[1]);
attachEmbeddedView(view, asElementData(compView, 1), 0, childView);
Services.checkAndUpdateView(view);