feat(TestComponentBuilder): add #overrideBindings and #overrideViewBindings

Closes #4052
This commit is contained in:
Igor Minar 2015-09-08 10:14:57 -07:00
parent 39a6f85e95
commit f91c087c46
4 changed files with 192 additions and 22 deletions

View File

@ -0,0 +1,65 @@
import {Map, MapWrapper, ListWrapper} from 'angular2/src/core/facade/collection';
import {
Type,
isPresent,
BaseException,
stringify,
isBlank,
print
} from 'angular2/src/core/facade/lang';
import {DirectiveMetadata, ComponentMetadata} from '../core/metadata';
import {DirectiveResolver} from 'angular2/src/core/compiler/directive_resolver';
export class MockDirectiveResolver extends DirectiveResolver {
private _bindingsOverrides: Map<Type, any[]> = new Map();
private _viewBindingsOverrides: Map<Type, any[]> = new Map();
resolve(type: Type): DirectiveMetadata {
var dm = super.resolve(type);
var bindingsOverride = this._bindingsOverrides.get(type);
var viewBindingsOverride = this._viewBindingsOverrides.get(type);
var bindings = dm.bindings;
if (isPresent(bindingsOverride)) {
bindings = dm.bindings.concat(bindingsOverride);
}
if (dm instanceof ComponentMetadata) {
var viewBindings = dm.viewBindings;
if (isPresent(viewBindingsOverride)) {
viewBindings = dm.viewBindings.concat(viewBindingsOverride);
}
return new ComponentMetadata({
selector: dm.selector,
properties: dm.properties,
events: dm.events,
host: dm.host,
bindings: bindings,
exportAs: dm.exportAs,
compileChildren: dm.compileChildren,
changeDetection: dm.changeDetection,
viewBindings: viewBindings
});
}
return new DirectiveMetadata({
selector: dm.selector,
properties: dm.properties,
events: dm.events,
host: dm.host,
bindings: bindings,
exportAs: dm.exportAs,
compileChildren: dm.compileChildren
});
}
setBindingsOverride(type: Type, bindings: any[]): void {
this._bindingsOverrides.set(type, bindings);
}
setViewBindingsOverride(type: Type, viewBindings: any[]): void {
this._viewBindingsOverrides.set(type, viewBindings);
}
}

View File

@ -6,6 +6,7 @@ import {ListWrapper, MapWrapper} from 'angular2/src/core/facade/collection';
import {ViewMetadata} from '../core/metadata';
import {DirectiveResolver} from 'angular2/src/core/compiler/directive_resolver';
import {ViewResolver} from 'angular2/src/core/compiler/view_resolver';
import {AppView} from 'angular2/src/core/compiler/view';
import {internalView, ViewRef} from 'angular2/src/core/compiler/view_ref';
@ -47,16 +48,19 @@ var _nextRootElementId = 0;
*/
@Injectable()
export class TestComponentBuilder {
_injector: Injector;
_viewOverrides: Map<Type, ViewMetadata>;
_bindingsOverrides: Map<Type, any[]>;
_directiveOverrides: Map<Type, Map<Type, Type>>;
_templateOverrides: Map<Type, string>;
_viewBindingsOverrides: Map<Type, any[]>;
_viewOverrides: Map<Type, ViewMetadata>;
constructor(injector: Injector) {
this._injector = injector;
this._viewOverrides = new Map();
constructor(private _injector: Injector) {
this._bindingsOverrides = new Map();
this._directiveOverrides = new Map();
this._templateOverrides = new Map();
this._viewBindingsOverrides = new Map();
this._viewOverrides = new Map();
}
_clone(): TestComponentBuilder {
@ -116,12 +120,53 @@ export class TestComponentBuilder {
return clone;
}
/**
* Overrides one or more injectables configured via `bindings` metadata property of a directive or
* component.
* Very useful when certain bindings need to be mocked out.
*
* The bindings specified via this method are appended to the existing `bindings` causing the
* duplicated bindings to
* be overridden.
*
* @param {Type} component
* @param {any[]} bindings
*
* @return {TestComponentBuilder}
*/
overrideBindings(type: Type, bindings: any[]): TestComponentBuilder {
var clone = this._clone();
clone._bindingsOverrides.set(type, bindings);
return clone;
}
/**
* Overrides one or more injectables configured via `bindings` metadata property of a directive or
* component.
* Very useful when certain bindings need to be mocked out.
*
* The bindings specified via this method are appended to the existing `bindings` causing the
* duplicated bindings to
* be overridden.
*
* @param {Type} component
* @param {any[]} bindings
*
* @return {TestComponentBuilder}
*/
overrideViewBindings(type: Type, bindings: any[]): TestComponentBuilder {
var clone = this._clone();
clone._viewBindingsOverrides.set(type, bindings);
return clone;
}
/**
* Builds and returns a RootTestComponent.
*
* @return {Promise<RootTestComponent>}
*/
createAsync(rootComponentType: Type): Promise<RootTestComponent> {
var mockDirectiveResolver = this._injector.get(DirectiveResolver);
var mockViewResolver = this._injector.get(ViewResolver);
MapWrapper.forEach(this._viewOverrides,
(view, type) => { mockViewResolver.setView(type, view); });
@ -133,6 +178,11 @@ export class TestComponentBuilder {
});
});
this._bindingsOverrides.forEach((bindings, type) =>
mockDirectiveResolver.setBindingsOverride(type, bindings));
this._viewBindingsOverrides.forEach(
(bindings, type) => mockDirectiveResolver.setViewBindingsOverride(type, bindings));
var rootElId = `root${_nextRootElementId++}`;
var rootEl = el(`<div id="${rootElId}"></div>`);
var doc = this._injector.get(DOCUMENT);

View File

@ -36,6 +36,7 @@ import {
EVENT_MANAGER_PLUGINS
} from 'angular2/src/core/render/dom/events/event_manager';
import {MockDirectiveResolver} from 'angular2/src/mock/directive_resolver_mock';
import {MockViewResolver} from 'angular2/src/mock/view_resolver_mock';
import {MockXHR} from 'angular2/src/core/render/xhr_mock';
import {MockLocationStrategy} from 'angular2/src/mock/mock_location_strategy';
@ -125,6 +126,7 @@ function _getAppBindings() {
bind(APP_VIEW_POOL_CAPACITY).toValue(500),
Compiler,
CompilerCache,
bind(DirectiveResolver).toClass(MockDirectiveResolver),
bind(ViewResolver).toClass(MockViewResolver),
DEFAULT_PIPES,
bind(IterableDiffers).toValue(defaultIterableDiffers),
@ -133,7 +135,6 @@ function _getAppBindings() {
Log,
ViewLoader,
DynamicComponentLoader,
DirectiveResolver,
PipeResolver,
Parser,
Lexer,

View File

@ -14,8 +14,7 @@ import {
TestComponentBuilder
} from 'angular2/test_lib';
import {Injectable, NgIf} from 'angular2/core';
import {Injectable, NgIf, bind} from 'angular2/core';
import {Directive, Component, View, ViewMetadata} from 'angular2/src/core/metadata';
@Component({selector: 'child-comp'})
@ -48,11 +47,14 @@ class MyIfComp {
@Component({selector: 'child-child-comp'})
@View({template: `<span>ChildChild</span>`})
@Injectable()
class ChildChildComp {}
class ChildChildComp {
}
@Component({selector: 'child-comp'})
@View({template: `<span>Original {{childBinding}}(<child-child-comp></child-child-comp>)</span>`,
directives: [ChildChildComp]})
@View({
template: `<span>Original {{childBinding}}(<child-child-comp></child-child-comp>)</span>`,
directives: [ChildChildComp]
})
@Injectable()
class ChildWithChildComp {
childBinding: string;
@ -62,7 +64,30 @@ class ChildWithChildComp {
@Component({selector: 'child-child-comp'})
@View({template: `<span>ChildChild Mock</span>`})
@Injectable()
class MockChildChildComp {}
class MockChildChildComp {
}
class FancyService {
value: string = 'real value';
}
class MockFancyService extends FancyService {
value: string = 'mocked out value';
}
@Component({selector: 'my-service-comp', bindings: [FancyService]})
@View({template: `injected value: {{fancyService.value}}`})
class TestBindingsComp {
constructor(private fancyService: FancyService) {}
}
@Component({selector: 'my-service-comp', viewBindings: [FancyService]})
@View({template: `injected value: {{fancyService.value}}`})
class TestViewBindingsComp {
constructor(private fancyService: FancyService) {}
}
export function main() {
@ -135,17 +160,46 @@ export function main() {
it("should override child component's dependencies",
inject([TestComponentBuilder, AsyncTestCompleter], (tcb, async) => {
inject([TestComponentBuilder, AsyncTestCompleter], (tcb, async) => {
tcb.overrideDirective(ParentComp, ChildComp, ChildWithChildComp)
.overrideDirective(ChildWithChildComp, ChildChildComp, MockChildChildComp)
.createAsync(ParentComp)
.then((rootTestComponent) => {
rootTestComponent.detectChanges();
expect(rootTestComponent.nativeElement).toHaveText('Parent(Original Child(ChildChild Mock))');
tcb.overrideDirective(ParentComp, ChildComp, ChildWithChildComp)
.overrideDirective(ChildWithChildComp, ChildChildComp, MockChildChildComp)
.createAsync(ParentComp)
.then((rootTestComponent) => {
rootTestComponent.detectChanges();
expect(rootTestComponent.nativeElement)
.toHaveText('Parent(Original Child(ChildChild Mock))');
async.done();
});
}));
async.done();
});
}));
it('should override a binding',
inject([TestComponentBuilder, AsyncTestCompleter], (tcb, async) => {
tcb.overrideBindings(TestBindingsComp, [bind(FancyService).toClass(MockFancyService)])
.createAsync(TestBindingsComp)
.then((rootTestComponent) => {
rootTestComponent.detectChanges();
expect(rootTestComponent.nativeElement)
.toHaveText('injected value: mocked out value');
async.done();
});
}));
it('should override a viewBinding',
inject([TestComponentBuilder, AsyncTestCompleter], (tcb, async) => {
tcb.overrideViewBindings(TestViewBindingsComp,
[bind(FancyService).toClass(MockFancyService)])
.createAsync(TestViewBindingsComp)
.then((rootTestComponent) => {
rootTestComponent.detectChanges();
expect(rootTestComponent.nativeElement)
.toHaveText('injected value: mocked out value');
async.done();
});
}));
});
}