From f91c087c4605fc1ad283e299dd74e7b6d6d87922 Mon Sep 17 00:00:00 2001 From: Igor Minar Date: Tue, 8 Sep 2015 10:14:57 -0700 Subject: [PATCH] feat(TestComponentBuilder): add #overrideBindings and #overrideViewBindings Closes #4052 --- .../src/mock/directive_resolver_mock.ts | 65 ++++++++++++++ .../src/test_lib/test_component_builder.ts | 60 +++++++++++-- .../angular2/src/test_lib/test_injector.ts | 3 +- .../test_lib/test_component_builder_spec.ts | 86 +++++++++++++++---- 4 files changed, 192 insertions(+), 22 deletions(-) diff --git a/modules/angular2/src/mock/directive_resolver_mock.ts b/modules/angular2/src/mock/directive_resolver_mock.ts index e69de29bb2..f4e35b0174 100644 --- a/modules/angular2/src/mock/directive_resolver_mock.ts +++ b/modules/angular2/src/mock/directive_resolver_mock.ts @@ -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 = new Map(); + private _viewBindingsOverrides: Map = 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); + } +} diff --git a/modules/angular2/src/test_lib/test_component_builder.ts b/modules/angular2/src/test_lib/test_component_builder.ts index dca9aa496e..853e6d9027 100644 --- a/modules/angular2/src/test_lib/test_component_builder.ts +++ b/modules/angular2/src/test_lib/test_component_builder.ts @@ -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; + _bindingsOverrides: Map; _directiveOverrides: Map>; _templateOverrides: Map; + _viewBindingsOverrides: Map; + _viewOverrides: Map; - 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} */ createAsync(rootComponentType: Type): Promise { + 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(`
`); var doc = this._injector.get(DOCUMENT); diff --git a/modules/angular2/src/test_lib/test_injector.ts b/modules/angular2/src/test_lib/test_injector.ts index bc18fa7afb..f92cb5925e 100644 --- a/modules/angular2/src/test_lib/test_injector.ts +++ b/modules/angular2/src/test_lib/test_injector.ts @@ -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, diff --git a/modules/angular2/test/test_lib/test_component_builder_spec.ts b/modules/angular2/test/test_lib/test_component_builder_spec.ts index 911f140be8..6218dfd1ef 100644 --- a/modules/angular2/test/test_lib/test_component_builder_spec.ts +++ b/modules/angular2/test/test_lib/test_component_builder_spec.ts @@ -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: `ChildChild`}) @Injectable() -class ChildChildComp {} +class ChildChildComp { +} @Component({selector: 'child-comp'}) -@View({template: `Original {{childBinding}}()`, - directives: [ChildChildComp]}) +@View({ + template: `Original {{childBinding}}()`, + directives: [ChildChildComp] +}) @Injectable() class ChildWithChildComp { childBinding: string; @@ -62,7 +64,30 @@ class ChildWithChildComp { @Component({selector: 'child-child-comp'}) @View({template: `ChildChild Mock`}) @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(); + }); + })); }); }