Skip to content

Commit 4943c0f

Browse files
committed
fix(view): fixed hydrator to pass the right element index when attaching an event listener
1 parent 00e2d70 commit 4943c0f

File tree

7 files changed

+120
-36
lines changed

7 files changed

+120
-36
lines changed

modules/angular2/src/core/compiler/element_injector.js

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import {isPresent, isBlank, Type, int, BaseException} from 'angular2/src/facade/lang';
2+
import {EventEmitter, ObservableWrapper} from 'angular2/src/facade/async';
23
import {Math} from 'angular2/src/facade/math';
34
import {List, ListWrapper, MapWrapper} from 'angular2/src/facade/collection';
45
import {Injector, Key, Dependency, bind, Binding, ResolvedBinding, NoBindingError,
@@ -318,6 +319,12 @@ class EventEmitterAccessor {
318319
this.eventName = eventName;
319320
this.getter = getter;
320321
}
322+
323+
subscribe(view:viewModule.AppView, boundElementIndex:number, directive:Object) {
324+
var eventEmitter = this.getter(directive);
325+
return ObservableWrapper.subscribe(eventEmitter,
326+
eventObj => view.triggerEventHandlers(this.eventName, eventObj, boundElementIndex));
327+
}
321328
}
322329

323330
/**

modules/angular2/src/core/compiler/view_hydrator.js

Lines changed: 3 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@ import {Injectable, Inject, OpaqueToken, Injector} from 'angular2/di';
22
import {ListWrapper, MapWrapper, Map, StringMapWrapper, List} from 'angular2/src/facade/collection';
33
import * as eli from './element_injector';
44
import {isPresent, isBlank, BaseException} from 'angular2/src/facade/lang';
5-
import {ObservableWrapper} from 'angular2/src/facade/async';
65
import * as vcModule from './view_container';
76
import * as viewModule from './view';
87
import {BindingPropagationConfig, Locals} from 'angular2/change_detection';
@@ -160,7 +159,7 @@ export class AppViewHydrator {
160159
var elementInjector = view.elementInjectors[i];
161160
if (isPresent(elementInjector)) {
162161
elementInjector.instantiateDirectives(appInjector, hostElementInjector, shadowDomAppInjector, view.preBuiltObjects[i]);
163-
this._setUpEventEmitters(view, elementInjector);
162+
this._setUpEventEmitters(view, elementInjector, i);
164163

165164
// The exporting of $implicit is a special case. Since multiple elements will all export
166165
// the different values as $implicit, directly assign $implicit bindings to the variable
@@ -190,25 +189,19 @@ export class AppViewHydrator {
190189
return renderComponentIndex;
191190
}
192191

193-
_setUpEventEmitters(view:viewModule.AppView, elementInjector:eli.ElementInjector) {
192+
_setUpEventEmitters(view:viewModule.AppView, elementInjector:eli.ElementInjector, boundElementIndex:number) {
194193
var emitters = elementInjector.getEventEmitterAccessors();
195194
for(var directiveIndex = 0; directiveIndex < emitters.length; ++directiveIndex) {
196195
var directiveEmitters = emitters[directiveIndex];
197196
var directive = elementInjector.getDirectiveAtIndex(directiveIndex);
198197

199198
for (var eventIndex = 0; eventIndex < directiveEmitters.length; ++eventIndex) {
200199
var eventEmitterAccessor = directiveEmitters[eventIndex];
201-
this._setUpSubscription(view, directive, directiveIndex, eventEmitterAccessor);
200+
eventEmitterAccessor.subscribe(view, boundElementIndex, directive);
202201
}
203202
}
204203
}
205204

206-
_setUpSubscription(view:viewModule.AppView, directive:Object, directiveIndex:number, eventEmitterAccessor) {
207-
var eventEmitter = eventEmitterAccessor.getter(directive);
208-
ObservableWrapper.subscribe(eventEmitter,
209-
eventObj => view.triggerEventHandlers(eventEmitterAccessor.eventName, eventObj, directiveIndex));
210-
}
211-
212205
/**
213206
* This should only be called by View or ViewContainer.
214207
*/

modules/angular2/src/facade/collection.dart

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -77,7 +77,9 @@ class StringMapWrapper {
7777
}
7878
static Map merge(Map a, Map b) {
7979
var m = new Map.from(a);
80-
b.forEach((k, v) => m[k] = v);
80+
if (b != null) {
81+
b.forEach((k, v) => m[k] = v);
82+
}
8183
return m;
8284
}
8385
static bool isEmpty(Map m) => m.isEmpty;

modules/angular2/src/test_lib/test_lib.dart

Lines changed: 32 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
library test_lib.test_lib;
22

33
import 'package:guinness/guinness.dart' as gns;
4-
export 'package:guinness/guinness.dart' hide Expect, expect, NotExpect, beforeEach, it, iit, xit, SpyObject;
4+
export 'package:guinness/guinness.dart' hide Expect, expect, NotExpect, beforeEach, it, iit, xit;
55
import 'package:unittest/unittest.dart' hide expect;
66

77
import 'dart:async';
@@ -13,6 +13,7 @@ import 'package:angular2/src/reflection/reflection_capabilities.dart';
1313

1414
import 'package:angular2/src/di/binding.dart' show bind;
1515
import 'package:angular2/src/di/injector.dart' show Injector;
16+
import 'package:angular2/src/facade/collection.dart' show StringMapWrapper;
1617

1718
import './test_injector.dart';
1819
export './test_injector.dart' show inject;
@@ -149,13 +150,40 @@ xit(name, fn) {
149150
_it(gns.xit, name, fn);
150151
}
151152

153+
class SpyFunction extends gns.SpyFunction {
154+
SpyFunction(name): super(name);
155+
156+
// TODO: vsavkin move to guinness
157+
andReturn(value) {
158+
return andCallFake(([a0, a1, a2, a3, a4, a5]) => value);
159+
}
160+
}
161+
152162
class SpyObject extends gns.SpyObject {
153-
// Need to take an optional type as this is required by
154-
// the JS SpyObject.
155-
SpyObject([type = null]) {
163+
final Map<String, SpyFunction> _spyFuncs = {};
164+
165+
SpyObject([arg]){}
166+
167+
SpyFunction spy(String funcName) =>
168+
_spyFuncs.putIfAbsent(funcName, () => new SpyFunction(funcName));
169+
170+
static stub([object = null, config = null, overrides = null]) {
171+
if (object is! SpyObject) {
172+
overrides = config;
173+
config = object;
174+
object = new SpyObject();
175+
}
176+
177+
var m = StringMapWrapper.merge(config, overrides);
178+
StringMapWrapper.forEach(m, (value, key){
179+
object.spy(key).andReturn(value);
180+
});
181+
return object;
156182
}
157183
}
158184

185+
186+
159187
String elementText(n) {
160188
hasNodes(n) {
161189
var children = DOM.childNodes(n);

modules/angular2/src/test_lib/test_lib.es6

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import {DOM} from 'angular2/src/dom/dom_adapter';
2+
import {StringMapWrapper} from 'angular2/src/facade/collection';
23

34
import {bind} from 'angular2/di';
45

@@ -289,13 +290,28 @@ export class SpyObject {
289290
return this[name];
290291
}
291292

293+
static stub(object = null, config = null, overrides = null) {
294+
if (!(object instanceof SpyObject)) {
295+
overrides = config;
296+
config = object;
297+
object = new SpyObject();
298+
}
299+
300+
var m = StringMapWrapper.merge(config, overrides);
301+
StringMapWrapper.forEach(m, (value, key) => {
302+
object.spy(key).andReturn(value);
303+
});
304+
return object;
305+
}
306+
292307
rttsAssert(value) {
293308
return true;
294309
}
295310

296311
_createGuinnessCompatibleSpy(){
297312
var newSpy = jasmine.createSpy();
298313
newSpy.andCallFake = newSpy.and.callFake;
314+
newSpy.andReturn = newSpy.and.returnValue;
299315
// return null by default to satisfy our rtts asserts
300316
newSpy.and.returnValue(null);
301317
return newSpy;

modules/angular2/test/core/compiler/view_hydrator_spec.js

Lines changed: 52 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -14,8 +14,8 @@ import {
1414
xit,
1515
SpyObject, proxy
1616
} from 'angular2/test_lib';
17-
import {IMPLEMENTS, isBlank} from 'angular2/src/facade/lang';
18-
import {MapWrapper, ListWrapper} from 'angular2/src/facade/collection';
17+
import {IMPLEMENTS, isBlank, isPresent} from 'angular2/src/facade/lang';
18+
import {MapWrapper, ListWrapper, StringMapWrapper} from 'angular2/src/facade/collection';
1919

2020
import {AppProtoView, AppView} from 'angular2/src/core/compiler/view';
2121
import {Renderer, ViewRef} from 'angular2/src/render/api';
@@ -43,12 +43,13 @@ export function main() {
4343
return DirectiveBinding.createFromType(meta.type, meta.annotation);
4444
}
4545

46-
function createElementInjector() {
47-
var res = new SpyElementInjector();
48-
res.spy('isExportingComponent').andCallFake( () => false );
49-
res.spy('isExportingElement').andCallFake( () => false );
50-
res.spy('getEventEmitterAccessors').andCallFake( () => [] );
51-
return res;
46+
function createElementInjector(overrides) {
47+
return SpyObject.stub(new SpyElementInjector(), {
48+
'isExportingComponent' : false,
49+
'isExportingElement' : false,
50+
'getEventEmitterAccessors' : [],
51+
'getComponent' : null
52+
}, overrides);
5253
}
5354

5455
function createEmptyElBinder() {
@@ -86,13 +87,18 @@ export function main() {
8687
return view;
8788
}
8889

89-
function createHostView(pv, shadowView, componentInstance) {
90+
function createHostView(pv, shadowView, componentInstance, elementInjectors = null) {
9091
var view = new AppView(renderer, null, null, pv, MapWrapper.create());
9192
var changeDetector = new SpyChangeDetector();
92-
var eij = createElementInjector();
93-
eij.spy('getComponent').andCallFake( () => componentInstance );
94-
view.init(changeDetector, [eij], [eij],
95-
[null], [shadowView]);
93+
94+
var eis;
95+
if (isPresent(elementInjectors)) {
96+
eis = elementInjectors;
97+
} else {
98+
eis = [createElementInjector({'getComponent': componentInstance})];
99+
}
100+
101+
view.init(changeDetector, eis, eis, ListWrapper.createFixedSize(eis.length), [shadowView]);
96102
return view;
97103
}
98104

@@ -128,9 +134,7 @@ export function main() {
128134
var pv = createHostProtoView(null);
129135
var shadowView = createEmptyView();
130136
var view = createHostView(pv, null, null);
131-
renderer.spy('createDynamicComponentView').andCallFake( (a,b,c) => {
132-
return [new ViewRef(), new ViewRef()];
133-
});
137+
renderer.spy('createDynamicComponentView').andReturn([new ViewRef(), new ViewRef()]);
134138
hydrator.hydrateDynamicComponentView(view, 0, shadowView, createDirectiveBinding(SomeComponent), null);
135139
expect(
136140
() => hydrator.hydrateDynamicComponentView(view, 0, shadowView, null, null)
@@ -143,7 +147,7 @@ export function main() {
143147

144148
it('should hydrate existing child components', () => {
145149
var hostPv = createHostProtoView(createProtoView());
146-
var componentInstance = {};
150+
var componentInstance = new Object();
147151
var shadowView = createEmptyView();
148152
var hostView = createHostView(hostPv, shadowView, componentInstance);
149153
renderer.spy('createInPlaceHostView').andCallFake( (a,b,c) => {
@@ -155,20 +159,47 @@ export function main() {
155159
expect(shadowView.hydrated()).toBe(true);
156160
});
157161

162+
it("should set up event listeners", () => {
163+
var dir = new Object();
164+
165+
var hostPv = createProtoView([
166+
createComponentElBinder(createDirectiveBinding(SomeComponent)),
167+
createEmptyElBinder()
168+
]);
169+
170+
var spyEventAccessor1 = SpyObject.stub({"subscribe" : null});
171+
var ei1 = createElementInjector({
172+
'getEventEmitterAccessors': [[spyEventAccessor1]],
173+
'getDirectiveAtIndex': dir
174+
});
175+
176+
var spyEventAccessor2 = SpyObject.stub({"subscribe" : null});
177+
var ei2 = createElementInjector({
178+
'getEventEmitterAccessors': [[spyEventAccessor2]],
179+
'getDirectiveAtIndex': dir
180+
});
181+
182+
var shadowView = createEmptyView();
183+
var hostView = createHostView(hostPv, shadowView, null, [ei1, ei2]);
184+
renderer.spy('createInPlaceHostView').andReturn([new ViewRef(), new ViewRef()]);
185+
186+
hydrate(hostView);
187+
188+
expect(spyEventAccessor1.spy('subscribe')).toHaveBeenCalledWith(hostView, 0, dir);
189+
expect(spyEventAccessor2.spy('subscribe')).toHaveBeenCalledWith(hostView, 1, dir);
190+
});
158191
});
159192

160193
describe('dehydrate... shared functionality', () => {
161194
var hostView;
162195
var shadowView;
163196

164197
function createAndHydrate(nestedProtoView) {
165-
var componentInstance = {};
198+
var componentInstance = new Object();
166199
shadowView = createEmptyView();
167200
var hostPv = createHostProtoView(nestedProtoView);
168201
hostView = createHostView(hostPv, shadowView, componentInstance);
169-
renderer.spy('createInPlaceHostView').andCallFake( (a,b,c) => {
170-
return [new ViewRef(), new ViewRef()];
171-
});
202+
renderer.spy('createInPlaceHostView').andReturn([new ViewRef(), new ViewRef()]);
172203

173204
hydrate(hostView);
174205
}

modules/angular2/test/test_lib/test_lib_spec.js

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -80,6 +80,13 @@ export function main() {
8080
expect(spyObj.spy("someFunc")).toHaveBeenCalledWith(1,2);
8181
});
8282

83+
it("should support stubs", () => {
84+
var s = SpyObject.stub({"a":1}, {"b":2});
85+
86+
expect(s.a()).toEqual(1);
87+
expect(s.b()).toEqual(2);
88+
});
89+
8390
it('should create spys for all methods', () => {
8491
expect(() => spyObj.someFunc()).not.toThrow();
8592
});

0 commit comments

Comments
 (0)