Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(446)

Side by Side Diff: third_party/pkg/angular/test/core/scope_spec.dart

Issue 180843004: Revert revision 33053 (Closed) Base URL: http://dart.googlecode.com/svn/branches/bleeding_edge/dart/
Patch Set: Created 6 years, 10 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch | Annotate | Revision Log
OLDNEW
1 library scope2_spec; 1 library scope_spec;
2 2
3 import '../_specs.dart'; 3 import '../_specs.dart';
4 import 'package:angular/change_detection/change_detection.dart' hide ExceptionHa ndler; 4 import 'dart:convert' show JSON;
5 import 'package:angular/change_detection/dirty_checking_change_detector.dart'; 5
6 import 'dart:async'; 6
7 import 'dart:math'; 7 main() {
8 8 describe(r'Scope', () {
9 main() => describe('scope', () { 9 NgZone zone;
10 beforeEach(module((Module module) { 10
11 Map context = {}; 11 noop() {}
12 module.value(GetterCache, new GetterCache({})); 12
13 module.type(ChangeDetector, implementedBy: DirtyCheckingChangeDetector); 13 beforeEach(module(() {
14 module.value(Object, context); 14 return (NgZone _zone) {
15 module.value(Map, context); 15 zone = _zone;
16 module.type(RootScope); 16 zone.onError = (e, s, l) => null;
17 module.type(_MultiplyFilter); 17 };
18 module.type(_ListHeadFilter);
19 module.type(_ListTailFilter);
20 module.type(_SortFilter);
21 }));
22
23 describe('AST Bridge', () {
24 it('should watch field', inject((Logger logger, Map context, RootScope rootS cope) {
25 context['field'] = 'Worked!';
26 rootScope.watch('field', (value, previous) => logger([value, previous]));
27 expect(logger).toEqual([]);
28 rootScope.digest();
29 expect(logger).toEqual([['Worked!', null]]);
30 rootScope.digest();
31 expect(logger).toEqual([['Worked!', null]]);
32 })); 18 }));
33 19
34 it('should watch field path', inject((Logger logger, Map context, RootScope rootScope) { 20 describe(r'$root', () {
35 context['a'] = {'b': 'AB'}; 21 it(r'should point to itself', inject((Scope $rootScope) {
36 rootScope.watch('a.b', (value, previous) => logger(value)); 22 expect($rootScope.$root).toEqual($rootScope);
37 rootScope.digest(); 23 expect($rootScope.$root).toEqual($rootScope);
38 expect(logger).toEqual(['AB']); 24 expect($rootScope.$root).toBeTruthy();
39 context['a']['b'] = '123'; 25 }));
40 rootScope.digest(); 26
41 expect(logger).toEqual(['AB', '123']); 27
42 context['a'] = {'b': 'XYZ'}; 28 it(r'should not have $root on children, but should inherit', inject((Scope $rootScope) {
43 rootScope.digest(); 29 var child = $rootScope.$new();
44 expect(logger).toEqual(['AB', '123', 'XYZ']); 30 expect(child.$root).toEqual($rootScope);
45 })); 31 expect(child._$root).toBeFalsy();
46 32 }));
47 it('should watch math operations', inject((Logger logger, Map context, RootS cope rootScope) { 33
48 context['a'] = 1; 34 });
49 context['b'] = 2; 35
50 rootScope.watch('a + b + 1', (value, previous) => logger(value)); 36
51 rootScope.digest(); 37 describe(r'$parent', () {
52 expect(logger).toEqual([4]); 38 it(r'should point to itself in root', inject((Scope $rootScope) {
53 context['a'] = 3; 39 expect($rootScope.$root).toEqual($rootScope);
54 rootScope.digest(); 40 }));
55 expect(logger).toEqual([4, 6]); 41
56 context['b'] = 5; 42
57 rootScope.digest(); 43 it(r'should point to parent', inject((Scope $rootScope) {
58 expect(logger).toEqual([4, 6, 9]); 44 var child = $rootScope.$new();
59 })); 45 expect($rootScope.$parent).toEqual(null);
60 46 expect(child.$parent).toEqual($rootScope);
61 47 expect(child.$new().$parent).toEqual(child);
62 it('should watch literals', inject((Logger logger, Map context, RootScope ro otScope) { 48 }));
63 context['a'] = 1; 49 });
64 rootScope.watch('1', (value, previous) => logger(value)); 50
65 rootScope.watch('"str"', (value, previous) => logger(value)); 51
66 rootScope.watch('[a, 2, 3]', (value, previous) => logger(value)); 52 describe(r'$id', () {
67 rootScope.watch('{a:a, b:2}', (value, previous) => logger(value)); 53 it(r'should have a unique id', inject((Scope $rootScope) {
68 rootScope.digest(); 54 expect($rootScope.$id != $rootScope.$new().$id).toBe(true);
69 expect(logger).toEqual([1, 'str', [1, 2, 3], {'a': 1, 'b': 2}]); 55 }));
70 logger.clear(); 56 });
71 context['a'] = 3; 57
72 rootScope.digest(); 58
73 expect(logger).toEqual([[3, 2, 3], {'a': 3, 'b': 2}]); 59 describe(r'this', () {
74 })); 60 it('should have a \'this\'', inject((Scope $rootScope) {
75 61 expect($rootScope['this']).toEqual($rootScope);
76 it('should invoke closures', inject((Logger logger, Map context, RootScope r ootScope) { 62 }));
77 context['fn'] = () { 63 });
78 logger('fn'); 64
79 return 1; 65
80 }; 66 describe(r'$new()', () {
81 context['a'] = {'fn': () { 67 it(r'should create a child scope', inject((Scope $rootScope) {
82 logger('a.fn'); 68 var child = $rootScope.$new();
83 return 2; 69 $rootScope.a = 123;
84 }}; 70 expect(child.a).toEqual(123);
85 rootScope.watch('fn()', (value, previous) => logger('=> $value')); 71 }));
86 rootScope.watch('a.fn()', (value, previous) => logger('-> $value')); 72
87 rootScope.digest(); 73 it(r'should create a non prototypically inherited child scope', inject((Sc ope $rootScope) {
88 expect(logger).toEqual(['fn', 'a.fn', '=> 1', '-> 2', 74 var child = $rootScope.$new(isolate: true);
89 /* second loop*/ 'fn', 'a.fn']); 75 $rootScope.a = 123;
90 logger.clear(); 76 expect(child.a).toEqual(null);
91 rootScope.digest(); 77 expect(child.$parent).toEqual($rootScope);
92 expect(logger).toEqual(['fn', 'a.fn']); 78 expect(child.$root).toBe($rootScope);
93 })); 79 }));
94 80 });
95 it('should perform conditionals', inject((Logger logger, Map context, RootSc ope rootScope) { 81
96 context['a'] = 1; 82
97 context['b'] = 2; 83 describe(r'auto digest', () {
98 context['c'] = 3; 84 it(r'should auto digest at the end of the turn', inject((Scope $rootScope) {
99 rootScope.watch('a?b:c', (value, previous) => logger(value)); 85 var digestedValue = 0;
100 rootScope.digest(); 86 $rootScope.a = 1;
101 expect(logger).toEqual([2]); 87 $rootScope.$watch('a', (newValue, oldValue, _this) {
102 logger.clear(); 88 digestedValue = newValue;
103 context['a'] = 0; 89 });
104 rootScope.digest(); 90 expect(digestedValue).toEqual(0);
105 expect(logger).toEqual([3]); 91 zone.run(noop);
106 })); 92 expect(digestedValue).toEqual(1);
107 93 }));
108 94
109 xit('should call function', inject((Logger logger, Map context, RootScope ro otScope) { 95 it(r'should skip auto digest if requested', inject((Scope $rootScope) {
110 context['a'] = () { 96 var digestedValue = 0;
111 return () { return 123; }; 97 $rootScope.a = 1;
112 }; 98 $rootScope.$watch('a', (newValue, oldValue, _this) {
113 rootScope.watch('a()()', (value, previous) => logger(value)); 99 digestedValue = newValue;
114 rootScope.digest(); 100 });
115 expect(logger).toEqual([123]); 101 expect(digestedValue).toEqual(0);
116 logger.clear(); 102 zone.run(() {
117 rootScope.digest(); 103 $rootScope.$skipAutoDigest();
118 expect(logger).toEqual([]); 104 });
119 })); 105 expect(digestedValue).toEqual(0);
120 106 zone.run(noop);
121 it('should access bracket', inject((Logger logger, Map context, RootScope ro otScope) { 107 expect(digestedValue).toEqual(1);
122 context['a'] = {'b': 123}; 108 }));
123 rootScope.watch('a["b"]', (value, previous) => logger(value)); 109
124 rootScope.digest(); 110 it(r'should skip auto digest if requested on any scope', inject((Scope $ro otScope) {
125 expect(logger).toEqual([123]); 111 var scope = $rootScope.$new();
126 logger.clear(); 112 var digestedValue = 0;
127 rootScope.digest(); 113 scope.a = 1;
128 expect(logger).toEqual([]); 114 scope.$watch('a', (newValue, oldValue, _this) {
129 })); 115 digestedValue = newValue;
130 116 });
131 117 expect(digestedValue).toEqual(0);
132 it('should prefix', inject((Logger logger, Map context, RootScope rootScope) { 118 zone.run(() {
133 context['a'] = true; 119 scope.$skipAutoDigest();
134 rootScope.watch('!a', (value, previous) => logger(value)); 120 });
135 rootScope.digest(); 121 expect(digestedValue).toEqual(0);
136 expect(logger).toEqual([false]); 122 zone.run(noop);
137 logger.clear(); 123 expect(digestedValue).toEqual(1);
138 context['a'] = false; 124 }));
139 rootScope.digest(); 125
140 expect(logger).toEqual([true]); 126 it(r'should throw exception if asked to skip auto digest outside of a turn ',
141 })); 127 inject((Scope $rootScope) {
142 128 var digestedValue = 0;
143 it('should support filters', inject((Logger logger, Map context, 129 $rootScope.a = 1;
144 RootScope rootScope, AstParser parser, 130 $rootScope.$watch('a', (newValue, oldValue, _this) {
145 FilterMap filters) { 131 digestedValue = newValue;
146 context['a'] = 123; 132 });
147 context['b'] = 2; 133 expect(digestedValue).toEqual(0);
148 rootScope.watch( 134 expect($rootScope.$skipAutoDigest).toThrow();
149 parser('a | multiply:b', filters: filters), 135 }));
150 (value, previous) => logger(value)); 136 });
151 rootScope.digest(); 137
152 expect(logger).toEqual([246]); 138
153 logger.clear(); 139 describe(r'$watch/$digest', () {
154 rootScope.digest(); 140 it(r'should watch and fire on simple property change', inject((Scope $root Scope) {
155 expect(logger).toEqual([]); 141 var log;
156 logger.clear(); 142
157 })); 143 $rootScope.$watch('name', (a, b, c) {
158 144 log = [a, b, c];
159 it('should support arrays in filters', inject((Logger logger, Map context, 145 });
160 RootScope rootScope, 146 $rootScope.$digest();
161 AstParser parser, 147 log = null;
162 FilterMap filters) { 148
163 context['a'] = [1]; 149 expect(log).toEqual(null);
164 rootScope.watch( 150 $rootScope.$digest();
165 parser('a | sort | listHead:"A" | listTail:"B"', filters: filters), 151 expect(log).toEqual(null);
166 (value, previous) => logger(value)); 152 $rootScope.name = 'misko';
167 rootScope.digest(); 153 $rootScope.$digest();
168 expect(logger).toEqual(['sort', 'listHead', 'listTail', ['A', 1, 'B']]); 154 expect(log).toEqual(['misko', null, $rootScope]);
169 logger.clear(); 155 }));
170 156
171 rootScope.digest(); 157
172 expect(logger).toEqual([]); 158 it(r'should watch and fire on expression change', inject((Scope $rootScope ) {
173 logger.clear(); 159 var log;
174 160
175 context['a'].add(2); 161 $rootScope.$watch('name.first', (a, b, c) {
176 rootScope.digest(); 162 log = [a, b, c];
177 expect(logger).toEqual(['sort', 'listHead', 'listTail', ['A', 1, 2, 'B']]) ; 163 });
178 logger.clear(); 164 $rootScope.$digest();
179 165 log = null;
180 // We change the order, but sort should change it to same one and it shoul d not 166
181 // call subsequent filters. 167 $rootScope.name = {};
182 context['a'] = [2, 1]; 168 expect(log).toEqual(null);
183 rootScope.digest(); 169 $rootScope.$digest();
184 expect(logger).toEqual(['sort']); 170 expect(log).toEqual(null);
185 logger.clear(); 171 $rootScope.name['first'] = 'misko';
186 })); 172 $rootScope.$digest();
187 }); 173 expect(log).toEqual(['misko', null, $rootScope]);
188 174 }));
189 175
190 describe('properties', () { 176
191 describe('root', () { 177 it(r'should delegate exceptions', () {
192 it('should point to itself', inject((RootScope rootScope) {
193 expect(rootScope.rootScope).toEqual(rootScope);
194 }));
195
196 it('children should point to root', inject((RootScope rootScope) {
197 var child = rootScope.createChild(new PrototypeMap(rootScope.context));
198 expect(child.rootScope).toEqual(rootScope);
199 expect(child.createChild(new PrototypeMap(rootScope.context)).rootScope) .toEqual(rootScope);
200 }));
201 });
202
203
204 describe('parent', () {
205 it('should not have parent', inject((RootScope rootScope) {
206 expect(rootScope.parentScope).toEqual(null);
207 }));
208
209
210 it('should point to parent', inject((RootScope rootScope) {
211 var child = rootScope.createChild(new PrototypeMap(rootScope.context));
212 expect(rootScope.parentScope).toEqual(null);
213 expect(child.parentScope).toEqual(rootScope);
214 expect(child.createChild(new PrototypeMap(rootScope.context)).parentScop e).toEqual(child);
215 }));
216 });
217 });
218
219
220 describe(r'events', () {
221
222 describe('on', () {
223 it('should allow emit/broadcast when no listeners', inject((RootScope scop e) {
224 scope.emit('foo');
225 scope.broadcast('foo');
226 }));
227
228
229 it(r'should add listener for both emit and broadcast events', inject((Root Scope rootScope) {
230 var log = '',
231 child = rootScope.createChild(new PrototypeMap(rootScope.context));
232
233 eventFn(event) {
234 expect(event).not.toEqual(null);
235 log += 'X';
236 }
237
238 child.on('abc').listen(eventFn);
239 expect(log).toEqual('');
240
241 child.emit('abc');
242 expect(log).toEqual('X');
243
244 child.broadcast('abc');
245 expect(log).toEqual('XX');
246 }));
247
248
249 it(r'should return a function that deregisters the listener', inject((Root Scope rootScope) {
250 var log = '';
251 var child = rootScope.createChild(new PrototypeMap(rootScope.context));
252 var subscription;
253
254 eventFn(e) {
255 log += 'X';
256 }
257
258 subscription = child.on('abc').listen(eventFn);
259 expect(log).toEqual('');
260 expect(subscription).toBeDefined();
261
262 child.emit(r'abc');
263 child.broadcast('abc');
264 expect(log).toEqual('XX');
265
266 log = '';
267 expect(subscription.cancel()).toBe(null);
268 child.emit(r'abc');
269 child.broadcast('abc');
270 expect(log).toEqual('');
271 }));
272
273 it('should not trigger assertions on scope fork', inject((RootScope root) {
274 var d1 = root.createChild({});
275 var d2 = root.createChild({});
276 var d3 = d2.createChild({});
277 expect(root.apply).not.toThrow();
278 d1.on(ScopeEvent.DESTROY).listen((_) => null);
279 expect(root.apply).not.toThrow();
280 d3.on(ScopeEvent.DESTROY).listen((_) => null);
281 expect(root.apply).not.toThrow();
282 d2.on(ScopeEvent.DESTROY).listen((_) => null);
283 expect(root.apply).not.toThrow();
284 }));
285
286 it('should not too eagerly create own streams', inject((RootScope root) {
287 var a = root.createChild({});
288 var a2 = root.createChild({});
289 var b = a.createChild({});
290 var c = b.createChild({});
291 var d = c.createChild({});
292 var e = d.createChild({});
293
294 getStreamState() => [root.hasOwnStreams, a.hasOwnStreams, a2.hasOwnStrea ms,
295 b.hasOwnStreams, c.hasOwnStreams, d.hasOwnStreams,
296 e.hasOwnStreams];
297
298 expect(getStreamState()).toEqual([false, false, false, false, false, fal se, false]);
299 expect(root.apply).not.toThrow();
300
301 e.on(ScopeEvent.DESTROY).listen((_) => null);
302 expect(getStreamState()).toEqual([false, false, false, false, false, fal se, true]);
303 expect(root.apply).not.toThrow();
304
305 d.on(ScopeEvent.DESTROY).listen((_) => null);
306 expect(getStreamState()).toEqual([false, false, false, false, false, tru e, true]);
307 expect(root.apply).not.toThrow();
308
309 b.on(ScopeEvent.DESTROY).listen((_) => null);
310 expect(getStreamState()).toEqual([false, false, false, true, false, true , true]);
311 expect(root.apply).not.toThrow();
312
313 c.on(ScopeEvent.DESTROY).listen((_) => null);
314 expect(getStreamState()).toEqual([false, false, false, true, true, true, true]);
315 expect(root.apply).not.toThrow();
316
317 a.on(ScopeEvent.DESTROY).listen((_) => null);
318 expect(getStreamState()).toEqual([false, true, false, true, true, true, true]);
319 expect(root.apply).not.toThrow();
320
321 a2.on(ScopeEvent.DESTROY).listen((_) => null);
322 expect(getStreamState()).toEqual([true, true, true, true, true, true, tr ue]);
323 expect(root.apply).not.toThrow();
324 }));
325
326
327 it('should not properly merge streams', inject((RootScope root) {
328 var a = root.createChild({});
329 var a2 = root.createChild({});
330 var b = a.createChild({});
331 var c = b.createChild({});
332 var d = c.createChild({});
333 var e = d.createChild({});
334
335 getStreamState() => [root.hasOwnStreams, a.hasOwnStreams, a2.hasOwnStrea ms,
336 b.hasOwnStreams, c.hasOwnStreams, d.hasOwnStreams,
337 e.hasOwnStreams];
338
339 expect(getStreamState()).toEqual([false, false, false, false, false, fal se, false]);
340 expect(root.apply).not.toThrow();
341
342 a2.on(ScopeEvent.DESTROY).listen((_) => null);
343 expect(getStreamState()).toEqual([false, false, true, false, false, fals e, false]);
344 expect(root.apply).not.toThrow();
345
346 e.on(ScopeEvent.DESTROY).listen((_) => null);
347 expect(getStreamState()).toEqual([true, false, true, false, false, false , true]);
348 expect(root.apply).not.toThrow();
349 }));
350
351
352 it('should clean up on cancel', inject((RootScope root) {
353 var child = root.createChild(null);
354 var cl = child.on("E").listen((e) => null);
355 var rl = root.on("E").listen((e) => null);
356 rl.cancel();
357 expect(root.apply).not.toThrow();
358 }));
359
360
361 it('should find random bugs', inject((RootScope root) {
362 List scopes;
363 List listeners;
364 List steps;
365 var random = new Random();
366 for (var i = 0; i < 1000; i++) {
367 if (i % 10 == 0) {
368 scopes = [root.createChild(null)];
369 listeners = [];
370 steps = [];
371 }
372 switch(random.nextInt(4)) {
373 case 0:
374 if (scopes.length > 10) break;
375 var index = random.nextInt(scopes.length);
376 Scope scope = scopes[index];
377 var child = scope.createChild(null);
378 scopes.add(child);
379 steps.add('scopes[$index].createChild(null)');
380 break;
381 case 1:
382 var index = random.nextInt(scopes.length);
383 Scope scope = scopes[index];
384 listeners.add(scope.on('E').listen((e) => null));
385 steps.add('scopes[$index].on("E").listen((e)=>null)');
386 break;
387 case 2:
388 if (scopes.length < 3) break;
389 var index = random.nextInt(scopes.length - 1) + 1;
390 Scope scope = scopes[index];
391 scope.destroy();
392 scopes = scopes.where((Scope s) => s.isAttached).toList();
393 steps.add('scopes[$index].destroy()');
394 break;
395 case 3:
396 if (listeners.length == 0) break;
397 var index = random.nextInt(listeners.length);
398 var l = listeners[index];
399 l.cancel();
400 listeners.remove(l);
401 steps.add('listeners[$index].cancel()');
402 break;
403 }
404 try {
405 root.apply();
406 } catch (e) {
407 expect('').toEqual(steps.join(';\n'));
408 }
409 }
410 }));
411 });
412
413
414 describe('emit', () {
415 var log, child, grandChild, greatGrandChild;
416
417 logger(event) {
418 log.add(event.currentScope.context['id']);
419 }
420
421 beforeEach(module(() {
422 return (RootScope rootScope) {
423 log = [];
424 child = rootScope.createChild({'id': 1});
425 grandChild = child.createChild({'id': 2});
426 greatGrandChild = grandChild.createChild({'id': 3});
427
428 rootScope.context['id'] = 0;
429
430 rootScope.on('myEvent').listen(logger);
431 child.on('myEvent').listen(logger);
432 grandChild.on('myEvent').listen(logger);
433 greatGrandChild.on('myEvent').listen(logger);
434 };
435 }));
436
437 it(r'should bubble event up to the root scope', inject((RootScope rootScop e) {
438 grandChild.emit(r'myEvent');
439 expect(log.join('>')).toEqual('2>1>0');
440 }));
441
442
443 it(r'should dispatch exceptions to the exceptionHandler', () {
444 module((Module module) { 178 module((Module module) {
445 module.type(ExceptionHandler, implementedBy: LoggingExceptionHandler); 179 module.type(ExceptionHandler, implementedBy: LoggingExceptionHandler);
446 }); 180 });
447 inject((ExceptionHandler e) { 181 inject((Scope $rootScope, ExceptionHandler e) {
182 LoggingExceptionHandler $exceptionHandler = e;
183 $rootScope.$watch('a', () {throw 'abc';});
184 $rootScope.a = 1;
185 $rootScope.$digest();
186 expect($exceptionHandler.errors.length).toEqual(1);
187 expect($exceptionHandler.errors[0].error).toEqual('abc');
188 });
189 });
190
191
192 it(r'should fire watches in order of addition', inject((Scope $rootScope) {
193 // this is not an external guarantee, just our own sanity
194 var log = '';
195 $rootScope.$watch('a', (a, b, c) { log += 'a'; });
196 $rootScope.$watch('b', (a, b, c) { log += 'b'; });
197 $rootScope.$watch('c', (a, b, c) { log += 'c'; });
198 $rootScope.a = $rootScope.b = $rootScope.c = 1;
199 $rootScope.$digest();
200 expect(log).toEqual('abc');
201 }));
202
203
204 it(r'should call child $watchers in addition order', inject((Scope $rootSc ope) {
205 // this is not an external guarantee, just our own sanity
206 var log = '';
207 var childA = $rootScope.$new();
208 var childB = $rootScope.$new();
209 var childC = $rootScope.$new();
210 childA.$watch('a', (a, b, c) { log += 'a'; });
211 childB.$watch('b', (a, b, c) { log += 'b'; });
212 childC.$watch('c', (a, b, c) { log += 'c'; });
213 childA.a = childB.b = childC.c = 1;
214 $rootScope.$digest();
215 expect(log).toEqual('abc');
216 }));
217
218
219 it(r'should allow $digest on a child scope with and without a right siblin g', inject(
220 (Scope $rootScope) {
221 // tests a traversal edge case which we originally missed
222 var log = [],
223 childA = $rootScope.$new(),
224 childB = $rootScope.$new();
225
226 $rootScope.$watch((a) { log.add('r'); });
227 childA.$watch((a) { log.add('a'); });
228 childB.$watch((a) { log.add('b'); });
229
230 // init
231 $rootScope.$digest();
232 expect(log.join('')).toEqual('rabra');
233
234 log.removeWhere((e) => true);
235 childA.$digest();
236 expect(log.join('')).toEqual('a');
237
238 log.removeWhere((e) => true);
239 childB.$digest();
240 expect(log.join('')).toEqual('b');
241 }));
242
243
244 it(r'should repeat watch cycle while model changes are identified', inject ((Scope $rootScope) {
245 var log = '';
246 $rootScope.$watch('c', (v, b, c) {$rootScope.d = v; log+='c'; });
247 $rootScope.$watch('b', (v, b, c) {$rootScope.c = v; log+='b'; });
248 $rootScope.$watch('a', (v, b, c) {$rootScope.b = v; log+='a'; });
249 $rootScope.$digest();
250 log = '';
251 $rootScope.a = 1;
252 $rootScope.$digest();
253 expect($rootScope.b).toEqual(1);
254 expect($rootScope.c).toEqual(1);
255 expect($rootScope.d).toEqual(1);
256 expect(log).toEqual('abc');
257 }));
258
259
260 it(r'should repeat watch cycle from the root element', inject((Scope $root Scope) {
261 var log = '';
262 var child = $rootScope.$new();
263 $rootScope.$watch((a) { log += 'a'; });
264 child.$watch((a) { log += 'b'; });
265 $rootScope.$digest();
266 expect(log).toEqual('aba');
267 }));
268
269
270 it(r'should not fire upon $watch registration on initial $digest', inject( (Scope $rootScope) {
271 var log = '';
272 $rootScope.a = 1;
273 $rootScope.$watch('a', (a, b, c) { log += 'a'; });
274 $rootScope.$watch('b', (a, b, c) { log += 'b'; });
275 $rootScope.$digest();
276 log = '';
277 $rootScope.$digest();
278 expect(log).toEqual('');
279 }));
280
281
282 it(r'should watch functions', () {
283 module((Module module) {
284 module.type(ExceptionHandler, implementedBy: LoggingExceptionHandler);
285 });
286 inject((Scope $rootScope, ExceptionHandler e) {
448 LoggingExceptionHandler exceptionHandler = e; 287 LoggingExceptionHandler exceptionHandler = e;
449 child.on('myEvent').listen((e) { throw 'bubbleException'; }); 288 $rootScope.fn = () {return 'a';};
450 grandChild.emit(r'myEvent'); 289 $rootScope.$watch('fn', (fn, a, b) {
290 exceptionHandler.errors.add(fn());
291 });
292 $rootScope.$digest();
293 expect(exceptionHandler.errors).toEqual(['a']);
294 $rootScope.fn = () {return 'b';};
295 $rootScope.$digest();
296 expect(exceptionHandler.errors).toEqual(['a', 'b']);
297 });
298 });
299
300
301 it(r'should prevent $digest recursion', inject((Scope $rootScope) {
302 var callCount = 0;
303 $rootScope.$watch('name', (a, b, c) {
304 expect(() {
305 $rootScope.$digest();
306 }).toThrow(r'$digest already in progress');
307 callCount++;
308 });
309 $rootScope.name = 'a';
310 $rootScope.$digest();
311 expect(callCount).toEqual(1);
312 }));
313
314
315 it(r'should return a function that allows listeners to be unregistered', i nject(
316 (Scope $rootScope) {
317 var listener = jasmine.createSpy('watch listener'),
318 listenerRemove;
319
320 listenerRemove = $rootScope.$watch('foo', listener);
321 $rootScope.$digest(); //init
322 expect(listener).toHaveBeenCalled();
323 expect(listenerRemove).toBeDefined();
324
325 listener.reset();
326 $rootScope.foo = 'bar';
327 $rootScope.$digest(); //triger
328 expect(listener).toHaveBeenCalledOnce();
329
330 listener.reset();
331 $rootScope.foo = 'baz';
332 listenerRemove();
333 $rootScope.$digest(); //trigger
334 expect(listener).not.toHaveBeenCalled();
335 }));
336
337
338 it(r'should not infinitely digest when current value is NaN', inject((Scop e $rootScope) {
339 $rootScope.$watch((a) { return double.NAN;});
340
341 expect(() {
342 $rootScope.$digest();
343 }).not.toThrow();
344 }));
345
346
347 it(r'should prevent infinite digest and should log firing expressions', in ject((Scope $rootScope) {
348 $rootScope['a'] = 0;
349 $rootScope['b'] = 0;
350 $rootScope.$watch('a = a + 1');
351 $rootScope.$watch('b = b + 1');
352
353 expect(() {
354 $rootScope.$digest();
355 }).toThrow('Watchers fired in the last 3 iterations: ['
356 '["a = a + 1","b = b + 1"],'
357 '["a = a + 1","b = b + 1"],'
358 '["a = a + 1","b = b + 1"]'
359 ']');
360 }));
361
362
363 it(r'should always call the watchr with newVal and oldVal equal on the fir st run',
364 inject((Scope $rootScope) {
365 var log = [];
366 var logger = (scope, newVal, oldVal) {
367 var val = (newVal == oldVal || (newVal != oldVal && oldVal != newVal)) ? newVal : 'xxx';
368 log.add(val);
369 };
370
371 $rootScope.$watch((s) { return double.NAN;}, logger);
372 $rootScope.$watch((s) { return null;}, logger);
373 $rootScope.$watch((s) { return '';}, logger);
374 $rootScope.$watch((s) { return false;}, logger);
375 $rootScope.$watch((s) { return 23;}, logger);
376
377 $rootScope.$digest();
378 expect(log.removeAt(0).isNaN).toEqual(true); //jasmine's toBe and toEqua l don't work well with NaNs
379 expect(log).toEqual([null, '', false, 23]);
380 log = [];
381 $rootScope.$digest();
382 expect(log).toEqual([]);
383 }));
384
385 describe('lazy digest', () {
386 var rootScope, lazyScope, eagerScope;
387
388 beforeEach(inject((Scope root) {
389 rootScope = root;
390 lazyScope = root.$new(lazy: true);
391 eagerScope = root.$new();
392 }));
393
394 it('should digest initially', () {
395 var log = '';
396 lazyScope.$watch(() {log += 'lazy;';});
397 eagerScope.$watch(() {log += 'eager;';});
398
399 rootScope.$digest();
400 expect(log).toEqual('lazy;eager;');
401
402 rootScope.$digest();
403 expect(log).toEqual('lazy;eager;eager;');
404
405 lazyScope.$dirty();
406 rootScope.$digest();
407 expect(log).toEqual('lazy;eager;eager;lazy;eager;');
408 });
409 });
410
411 describe('disabled digest', () {
412 var rootScope, childScope;
413
414 beforeEach(inject((Scope root) {
415 rootScope = root;
416 childScope = root.$new();
417 }));
418
419 it('should disable digest', () {
420 var log = '';
421 childScope.$watch(() {log += 'digest;';});
422
423 rootScope.$digest();
424 expect(log).toEqual('digest;');
425
426 childScope.$disabled = true;
427 expect(childScope.$disabled).toEqual(true);
428 rootScope.$digest();
429 expect(log).toEqual('digest;');
430
431 childScope.$disabled = false;
432 expect(childScope.$disabled).toEqual(false);
433 rootScope.$digest();
434 expect(log).toEqual('digest;digest;');
435 });
436 });
437 });
438
439
440 describe(r'$watchSet', () {
441 var scope;
442 beforeEach(inject((Scope s) => scope = s));
443
444 it('should skip empty sets', () {
445 expect(scope.$watchSet([], null)()).toBe(null);
446 });
447
448 it('should treat set of 1 as direct watch', () {
449 var lastValues = ['foo'];
450 var log = '';
451 var clean = scope.$watchSet(['a'], (values, oldValues, s) {
452 log += values.join(',') + ';';
453 expect(s).toBe(scope);
454 expect(oldValues).toEqual(lastValues);
455 lastValues = new List.from(values);
456 });
457
458 scope.a = 'foo';
459 scope.$digest();
460 expect(log).toEqual('foo;');
461
462 scope.$digest();
463 expect(log).toEqual('foo;');
464
465 scope.a = 'bar';
466 scope.$digest();
467 expect(log).toEqual('foo;bar;');
468
469 clean();
470 scope.a = 'xxx';
471 scope.$digest();
472 expect(log).toEqual('foo;bar;');
473 });
474
475 it('should detect a change to any one in a set', () {
476 var lastValues = ['foo', 'bar'];
477 var log = '';
478 var clean = scope.$watchSet(['a', 'b'], (values, oldValues, s) {
479 log += values.join(',') + ';';
480 expect(oldValues).toEqual(lastValues);
481 lastValues = new List.from(values);
482 });
483
484 scope.a = 'foo';
485 scope.b = 'bar';
486 scope.$digest();
487 expect(log).toEqual('foo,bar;');
488
489 scope.$digest();
490 expect(log).toEqual('foo,bar;');
491
492 scope.a = 'a';
493 scope.$digest();
494 expect(log).toEqual('foo,bar;a,bar;');
495
496 scope.a = 'A';
497 scope.b = 'B';
498 scope.$digest();
499 expect(log).toEqual('foo,bar;a,bar;A,B;');
500
501 clean();
502 scope.a = 'xxx';
503 scope.$digest();
504 expect(log).toEqual('foo,bar;a,bar;A,B;');
505 });
506 });
507
508
509 describe(r'$destroy', () {
510 var first = null, middle = null, last = null, log = null;
511
512 beforeEach(inject((Scope $rootScope) {
513 log = '';
514
515 first = $rootScope.$new();
516 middle = $rootScope.$new();
517 last = $rootScope.$new();
518
519 first.$watch((s) { log += '1';});
520 middle.$watch((s) { log += '2';});
521 last.$watch((s) { log += '3';});
522
523 $rootScope.$digest();
524 log = '';
525 }));
526
527
528 it(r'should ignore remove on root', inject((Scope $rootScope) {
529 $rootScope.$destroy();
530 $rootScope.$digest();
531 expect(log).toEqual('123');
532 }));
533
534
535 it(r'should remove first', inject((Scope $rootScope) {
536 first.$destroy();
537 $rootScope.$digest();
538 expect(log).toEqual('23');
539 }));
540
541
542 it(r'should remove middle', inject((Scope $rootScope) {
543 middle.$destroy();
544 $rootScope.$digest();
545 expect(log).toEqual('13');
546 }));
547
548
549 it(r'should remove last', inject((Scope $rootScope) {
550 last.$destroy();
551 $rootScope.$digest();
552 expect(log).toEqual('12');
553 }));
554
555
556 it(r'should broadcast the $destroy event', inject((Scope $rootScope) {
557 var log = [];
558 first.$on(r'$destroy', (s) => log.add('first'));
559 first.$new().$on(r'$destroy', (s) => log.add('first-child'));
560
561 first.$destroy();
562 expect(log).toEqual(['first', 'first-child']);
563 }));
564 });
565
566
567 describe(r'$eval', () {
568 it(r'should eval an expression', inject((Scope $rootScope) {
569 expect($rootScope.$eval('a=1')).toEqual(1);
570 expect($rootScope.a).toEqual(1);
571
572 $rootScope.$eval((self, locals) {self.b=2;});
573 expect($rootScope.b).toEqual(2);
574 }));
575
576
577 it(r'should allow passing locals to the expression', inject((Scope $rootSc ope) {
578 expect($rootScope.$eval('a+1', {"a": 2})).toBe(3);
579
580 $rootScope.$eval((scope) {
581 scope['c'] = scope['b'] + 4;
582 }, {"b": 3});
583 expect($rootScope.c).toBe(7);
584 }));
585 });
586
587
588 describe(r'$evalAsync', () {
589
590 it(r'should run callback before $watch', inject((Scope $rootScope) {
591 var log = '';
592 var child = $rootScope.$new();
593 $rootScope.$evalAsync((scope, _) { log += 'parent.async;'; });
594 $rootScope.$watch('value', (_, _0, _1) { log += 'parent.\$digest;'; });
595 child.$evalAsync((scope, _) { log += 'child.async;'; });
596 child.$watch('value', (_, _0, _1) { log += 'child.\$digest;'; });
597 $rootScope.$digest();
598 expect(log).toEqual('parent.async;child.async;parent.\$digest;child.\$di gest;');
599 }));
600
601 it(r'should cause a $digest rerun', inject((Scope $rootScope) {
602 $rootScope.log = '';
603 $rootScope.value = 0;
604 // NOTE(deboer): watch listener string functions not yet supported
605 //$rootScope.$watch('value', 'log = log + ".";');
606 $rootScope.$watch('value', (__, _, scope) { scope.log = scope.log + "."; });
607 $rootScope.$watch('init', (_, __, _0) {
608 $rootScope.$evalAsync('value = 123; log = log + "=" ');
609 expect($rootScope.value).toEqual(0);
610 });
611 $rootScope.$digest();
612 expect($rootScope.log).toEqual('.=.');
613 }));
614
615 it(r'should run async in the same order as added', inject((Scope $rootScop e) {
616 $rootScope.log = '';
617 $rootScope.$evalAsync("log = log + 1");
618 $rootScope.$evalAsync("log = log + 2");
619 $rootScope.$digest();
620 expect($rootScope.log).toEqual('12');
621 }));
622
623 it(r'should allow running after digest', inject((Scope $rootScope) {
624 $rootScope.log = '';
625 $rootScope.$evalAsync(() => $rootScope.log += 'eval;', outsideDigest: tr ue);
626 $rootScope.$watch(() { $rootScope.log += 'digest;'; });
627 $rootScope.$digest();
628 expect($rootScope.log).toEqual('digest;eval;');
629 }));
630
631 it(r'should allow running after digest in issolate scope', inject((Scope $ rootScope) {
632 var isolateScope = $rootScope.$new(isolate: true);
633 isolateScope.log = '';
634 isolateScope.$evalAsync(() => isolateScope.log += 'eval;', outsideDigest : true);
635 isolateScope.$watch(() { isolateScope.log += 'digest;'; });
636 isolateScope.$digest();
637 expect(isolateScope.log).toEqual('digest;eval;');
638 }));
639
640 });
641
642
643 describe(r'$apply', () {
644 it(r'should apply expression with full lifecycle', inject((Scope $rootScop e) {
645 var log = '';
646 var child = $rootScope.$new();
647 $rootScope.$watch('a', (a, _, __) { log += '1'; });
648 child.$apply(r'$parent.a=0');
649 expect(log).toEqual('1');
650 }));
651
652
653 it(r'should catch exceptions', () {
654 module((Module module) => module.type(ExceptionHandler, implementedBy: L oggingExceptionHandler));
655 inject((Scope $rootScope, ExceptionHandler e) {
656 LoggingExceptionHandler $exceptionHandler = e;
657 var log = [];
658 var child = $rootScope.$new();
659 $rootScope.$watch('a', (a, _, __) => log.add('1'));
660 $rootScope.a = 0;
661 child.$apply((_, __) { throw 'MyError'; });
662 expect(log.join(',')).toEqual('1');
663 expect($exceptionHandler.errors[0].error).toEqual('MyError');
664 $exceptionHandler.errors.removeAt(0);
665 $exceptionHandler.assertEmpty();
666 });
667 });
668
669
670 describe(r'exceptions', () {
671 var log;
672 beforeEach(module((Module module) {
673 return module.type(ExceptionHandler, implementedBy: LoggingExceptionHa ndler);
674 }));
675 beforeEach(inject((Scope $rootScope) {
676 log = '';
677 $rootScope.$watch(() { log += '\$digest;'; });
678 $rootScope.$digest();
679 log = '';
680 }));
681
682
683 it(r'should execute and return value and update', inject(
684 (Scope $rootScope, ExceptionHandler e) {
685 LoggingExceptionHandler $exceptionHandler = e;
686 $rootScope.name = 'abc';
687 expect($rootScope.$apply((scope) => scope.name)).toEqual('abc');
688 expect(log).toEqual(r'$digest;');
689 $exceptionHandler.assertEmpty();
690 }));
691
692
693 it(r'should catch exception and update', inject((Scope $rootScope, Excep tionHandler e) {
694 LoggingExceptionHandler $exceptionHandler = e;
695 var error = 'MyError';
696 $rootScope.$apply(() { throw error; });
697 expect(log).toEqual(r'$digest;');
698 expect($exceptionHandler.errors[0].error).toEqual(error);
699 }));
700 });
701
702 it(r'should proprely reset phase on exception', inject((Scope $rootScope) {
703 var error = 'MyError';
704 expect(() =>$rootScope.$apply(() { throw error; })).toThrow(error);
705 expect(() =>$rootScope.$apply(() { throw error; })).toThrow(error);
706 }));
707 });
708
709
710 describe(r'events', () {
711
712 describe(r'$on', () {
713
714 it(r'should add listener for both $emit and $broadcast events', inject(( Scope $rootScope) {
715 var log = '',
716 child = $rootScope.$new();
717
718 eventFn() {
719 log += 'X';
720 }
721
722 child.$on('abc', eventFn);
723 expect(log).toEqual('');
724
725 child.$emit(r'abc');
726 expect(log).toEqual('X');
727
728 child.$broadcast('abc');
729 expect(log).toEqual('XX');
730 }));
731
732
733 it(r'should return a function that deregisters the listener', inject((Sc ope $rootScope) {
734 var log = '',
735 child = $rootScope.$new(),
736 listenerRemove;
737
738 eventFn() {
739 log += 'X';
740 }
741
742 listenerRemove = child.$on('abc', eventFn);
743 expect(log).toEqual('');
744 expect(listenerRemove).toBeDefined();
745
746 child.$emit(r'abc');
747 child.$broadcast('abc');
748 expect(log).toEqual('XX');
749
750 log = '';
751 listenerRemove();
752 child.$emit(r'abc');
753 child.$broadcast('abc');
754 expect(log).toEqual('');
755 }));
756 });
757
758
759 describe(r'$emit', () {
760 var log, child, grandChild, greatGrandChild;
761
762 logger(event) {
763 log.add(event.currentScope.id);
764 }
765
766 beforeEach(module((Module module) {
767 return module.type(ExceptionHandler, implementedBy: LoggingExceptionHa ndler);
768 }));
769 beforeEach(inject((Scope $rootScope) {
770 log = [];
771 child = $rootScope.$new();
772 grandChild = child.$new();
773 greatGrandChild = grandChild.$new();
774
775 $rootScope.id = 0;
776 child.id = 1;
777 grandChild.id = 2;
778 greatGrandChild.id = 3;
779
780 $rootScope.$on('myEvent', logger);
781 child.$on('myEvent', logger);
782 grandChild.$on('myEvent', logger);
783 greatGrandChild.$on('myEvent', logger);
784 }));
785
786 it(r'should bubble event up to the root scope', () {
787 grandChild.$emit(r'myEvent');
451 expect(log.join('>')).toEqual('2>1>0'); 788 expect(log.join('>')).toEqual('2>1>0');
452 expect(exceptionHandler.errors[0].error).toEqual('bubbleException'); 789 });
453 }); 790
454 }); 791
455 792 it(r'should dispatch exceptions to the $exceptionHandler',
456 793 inject((ExceptionHandler e) {
457 it(r'should allow stopping event propagation', inject((RootScope rootScope ) { 794 LoggingExceptionHandler $exceptionHandler = e;
458 child.on('myEvent').listen((event) { event.stopPropagation(); }); 795 child.$on('myEvent', () { throw 'bubbleException'; });
459 grandChild.emit(r'myEvent'); 796 grandChild.$emit(r'myEvent');
460 expect(log.join('>')).toEqual('2>1'); 797 expect(log.join('>')).toEqual('2>1>0');
461 })); 798 expect($exceptionHandler.errors[0].error).toEqual('bubbleException');
462 799 }));
463 800
464 it(r'should forward method arguments', inject((RootScope rootScope) { 801
465 var eventName; 802 it(r'should allow stopping event propagation', () {
466 var eventData; 803 child.$on('myEvent', (event) { event.stopPropagation(); });
467 child.on('abc').listen((event) { 804 grandChild.$emit(r'myEvent');
468 eventName = event.name; 805 expect(log.join('>')).toEqual('2>1');
469 eventData = event.data; 806 });
470 }); 807
471 child.emit('abc', ['arg1', 'arg2']); 808
472 expect(eventName).toEqual('abc'); 809 it(r'should forward method arguments', () {
473 expect(eventData).toEqual(['arg1', 'arg2']); 810 child.$on('abc', (event, arg1, arg2) {
474 })); 811 expect(event.name).toBe('abc');
475 812 expect(arg1).toBe('arg1');
476 813 expect(arg2).toBe('arg2');
477 describe(r'event object', () {
478 it(r'should have methods/properties', inject((RootScope rootScope) {
479 var event;
480 child.on('myEvent').listen((e) {
481 expect(e.targetScope).toBe(grandChild);
482 expect(e.currentScope).toBe(child);
483 expect(e.name).toBe('myEvent');
484 event = e;
485 }); 814 });
486 grandChild.emit(r'myEvent'); 815 child.$emit(r'abc', ['arg1', 'arg2']);
487 expect(event).toBeDefined(); 816 });
488 })); 817
489 818
490 819 describe(r'event object', () {
491 it(r'should have preventDefault method and defaultPrevented property', i nject((RootScope rootScope) { 820 it(r'should have methods/properties', () {
492 var event = grandChild.emit(r'myEvent'); 821 var event;
493 expect(event.defaultPrevented).toBe(false); 822 child.$on('myEvent', (e) {
494 823 expect(e.targetScope).toBe(grandChild);
495 child.on('myEvent').listen((event) { 824 expect(e.currentScope).toBe(child);
496 event.preventDefault(); 825 expect(e.name).toBe('myEvent');
826 event = e;
827 });
828 grandChild.$emit(r'myEvent');
829 expect(event).toBeDefined();
497 }); 830 });
498 event = grandChild.emit(r'myEvent'); 831
499 expect(event.defaultPrevented).toBe(true); 832
500 })); 833 it(r'should have preventDefault method and defaultPrevented property', () {
501 }); 834 var event = grandChild.$emit(r'myEvent');
502 }); 835 expect(event.defaultPrevented).toBe(false);
503 836
504 837 child.$on('myEvent', (event) {
505 describe('broadcast', () { 838 event.preventDefault();
506 describe(r'event propagation', () { 839 });
507 var log, child1, child2, child3, grandChild11, grandChild21, grandChild2 2, grandChild23, 840 event = grandChild.$emit(r'myEvent');
508 greatGrandChild211; 841 expect(event.defaultPrevented).toBe(true);
509 842 });
510 logger(event) { 843 });
511 log.add(event.currentScope.context['id']); 844 });
512 } 845
513 846
514 beforeEach(inject((RootScope rootScope) { 847 describe(r'$broadcast', () {
848 describe(r'event propagation', () {
849 var log, child1, child2, child3, grandChild11, grandChild21, grandChil d22, grandChild23,
850 greatGrandChild211;
851
852 logger(event) {
853 log.add(event.currentScope.id);
854 }
855
856 beforeEach(inject((Scope $rootScope) {
857 log = [];
858 child1 = $rootScope.$new();
859 child2 = $rootScope.$new();
860 child3 = $rootScope.$new();
861 grandChild11 = child1.$new();
862 grandChild21 = child2.$new();
863 grandChild22 = child2.$new();
864 grandChild23 = child2.$new();
865 greatGrandChild211 = grandChild21.$new();
866
867 $rootScope.id = 0;
868 child1.id = 1;
869 child2.id = 2;
870 child3.id = 3;
871 grandChild11.id = 11;
872 grandChild21.id = 21;
873 grandChild22.id = 22;
874 grandChild23.id = 23;
875 greatGrandChild211.id = 211;
876
877 $rootScope.$on('myEvent', logger);
878 child1.$on('myEvent', logger);
879 child2.$on('myEvent', logger);
880 child3.$on('myEvent', logger);
881 grandChild11.$on('myEvent', logger);
882 grandChild21.$on('myEvent', logger);
883 grandChild22.$on('myEvent', logger);
884 grandChild23.$on('myEvent', logger);
885 greatGrandChild211.$on('myEvent', logger);
886
887 // R
888 // / | \
889 // 1 2 3
890 // / / | \
891 // 11 21 22 23
892 // |
893 // 211
894 }));
895
896
897 it(r'should broadcast an event from the root scope', inject((Scope $ro otScope) {
898 $rootScope.$broadcast('myEvent');
899 expect(log.join('>')).toEqual('0>1>11>2>21>211>22>23>3');
900 }));
901
902
903 it(r'should broadcast an event from a child scope', () {
904 child2.$broadcast('myEvent');
905 expect(log.join('>')).toEqual('2>21>211>22>23');
906 });
907
908
909 it(r'should broadcast an event from a leaf scope with a sibling', () {
910 grandChild22.$broadcast('myEvent');
911 expect(log.join('>')).toEqual('22');
912 });
913
914
915 it(r'should broadcast an event from a leaf scope without a sibling', ( ) {
916 grandChild23.$broadcast('myEvent');
917 expect(log.join('>')).toEqual('23');
918 });
919
920
921 it(r'should not not fire any listeners for other events', inject((Scop e $rootScope) {
922 $rootScope.$broadcast('fooEvent');
923 expect(log.join('>')).toEqual('');
924 }));
925
926
927 it(r'should return event object', () {
928 var result = child1.$broadcast('some');
929
930 expect(result).toBeDefined();
931 expect(result.name).toBe('some');
932 expect(result.targetScope).toBe(child1);
933 });
934 });
935
936
937 describe(r'listener', () {
938 it(r'should receive event object', inject((Scope $rootScope) {
939 var scope = $rootScope,
940 child = scope.$new(),
941 event;
942
943 child.$on('fooEvent', (e) {
944 event = e;
945 });
946 scope.$broadcast('fooEvent');
947
948 expect(event.name).toBe('fooEvent');
949 expect(event.targetScope).toBe(scope);
950 expect(event.currentScope).toBe(child);
951 }));
952
953
954 it(r'should support passing messages as varargs', inject((Scope $rootS cope) {
955 var scope = $rootScope,
956 child = scope.$new(),
957 args;
958
959 child.$on('fooEvent', (a, b, c, d, e) {
960 args = [a, b, c, d, e];
961 });
962 scope.$broadcast('fooEvent', ['do', 're', 'me', 'fa']);
963
964 expect(args.length).toBe(5);
965 expect(args.sublist(1)).toEqual(['do', 're', 'me', 'fa']);
966 }));
967 });
968 });
969 });
970
971
972 describe('\$watchCollection', () {
973 var log, $rootScope, deregister;
974
975 beforeEach(inject((Scope _$rootScope_) {
976 log = [];
977 $rootScope = _$rootScope_;
978 deregister = $rootScope.$watchCollection('obj', (obj) {
979 log.add(JSON.encode(obj));
980 });
981 }));
982
983
984 it('should not trigger if nothing change', inject((Scope $rootScope) {
985 $rootScope.$digest();
986 expect(log).toEqual(['null']);
987
988 $rootScope.$digest();
989 expect(log).toEqual(['null']);
990 }));
991
992
993 it('should allow deregistration', inject((Scope $rootScope) {
994 $rootScope.obj = [];
995 $rootScope.$digest();
996
997 expect(log).toEqual(['[]']);
998
999 $rootScope.obj.add('a');
1000 deregister();
1001
1002 $rootScope.$digest();
1003 expect(log).toEqual(['[]']);
1004 }));
1005
1006
1007 describe('array', () {
1008 it('should trigger when property changes into array', () {
1009 $rootScope.obj = 'test';
1010 $rootScope.$digest();
1011 expect(log).toEqual(['"test"']);
1012
1013 $rootScope.obj = [];
1014 $rootScope.$digest();
1015 expect(log).toEqual(['"test"', '[]']);
1016 });
1017
1018
1019 it('should not trigger change when object in collection changes', () {
1020 $rootScope.obj = [{}];
1021 $rootScope.$digest();
1022 expect(log).toEqual(['[{}]']);
1023
1024 $rootScope.obj[0]['name'] = 'foo';
1025 $rootScope.$digest();
1026 expect(log).toEqual(['[{}]']);
1027 });
1028
1029
1030 it('should watch array properties', () {
1031 $rootScope.obj = [];
1032 $rootScope.$digest();
1033 expect(log).toEqual(['[]']);
1034
1035 $rootScope.obj.add('a');
1036 $rootScope.$digest();
1037 expect(log).toEqual(['[]', '["a"]']);
1038
1039 $rootScope.obj[0] = 'b';
1040 $rootScope.$digest();
1041 expect(log).toEqual(['[]', '["a"]', '["b"]']);
1042
1043 $rootScope.obj.add([]);
1044 $rootScope.obj.add({});
515 log = []; 1045 log = [];
516 child1 = rootScope.createChild({}); 1046 $rootScope.$digest();
517 child2 = rootScope.createChild({}); 1047 expect(log).toEqual(['["b",[],{}]']);
518 child3 = rootScope.createChild({}); 1048
519 grandChild11 = child1.createChild({}); 1049 var temp = $rootScope.obj[1];
520 grandChild21 = child2.createChild({}); 1050 $rootScope.obj[1] = $rootScope.obj[2];
521 grandChild22 = child2.createChild({}); 1051 $rootScope.obj[2] = temp;
522 grandChild23 = child2.createChild({}); 1052 $rootScope.$digest();
523 greatGrandChild211 = grandChild21.createChild({}); 1053 expect(log).toEqual([ '["b",[],{}]', '["b",{},[]]' ]);
524 1054
525 rootScope.context['id'] = 0; 1055 $rootScope.obj.removeAt(0);
526 child1.context['id'] = 1; 1056 log = [];
527 child2.context['id'] = 2; 1057 $rootScope.$digest();
528 child3.context['id'] = 3; 1058 expect(log).toEqual([ '[{},[]]' ]);
529 grandChild11.context['id'] = 11; 1059 });
530 grandChild21.context['id'] = 21; 1060 });
531 grandChild22.context['id'] = 22; 1061
532 grandChild23.context['id'] = 23; 1062
533 greatGrandChild211.context['id'] = 211; 1063 it('should watch iterable properties', () {
534 1064 $rootScope.obj = _toJsonableIterable([]);
535 rootScope.on('myEvent').listen(logger); 1065 $rootScope.$digest();
536 child1.on('myEvent').listen(logger); 1066 expect(log).toEqual(['[]']);
537 child2.on('myEvent').listen(logger); 1067
538 child3.on('myEvent').listen(logger); 1068 $rootScope.obj = _toJsonableIterable(['a']);
539 grandChild11.on('myEvent').listen(logger); 1069 $rootScope.$digest();
540 grandChild21.on('myEvent').listen(logger); 1070 expect(log).toEqual(['[]', '["a"]']);
541 grandChild22.on('myEvent').listen(logger); 1071
542 grandChild23.on('myEvent').listen(logger); 1072 $rootScope.obj = _toJsonableIterable(['b']);
543 greatGrandChild211.on('myEvent').listen(logger); 1073 $rootScope.$digest();
544 1074 expect(log).toEqual(['[]', '["a"]', '["b"]']);
545 // R 1075
546 // / | \ 1076 $rootScope.obj = _toJsonableIterable(['b', [], {}]);
547 // 1 2 3 1077 log = [];
548 // / / | \ 1078 $rootScope.$digest();
549 // 11 21 22 23 1079 expect(log).toEqual(['["b",[],{}]']);
550 // | 1080 });
551 // 211 1081
552 })); 1082
553 1083 describe('objects', () {
554 1084 it('should trigger when property changes into object', () {
555 it(r'should broadcast an event from the root scope', inject((RootScope r ootScope) { 1085 $rootScope.obj = 'test';
556 rootScope.broadcast('myEvent'); 1086 $rootScope.$digest();
557 expect(log.join('>')).toEqual('0>1>11>2>21>211>22>23>3'); 1087 expect(log).toEqual(['"test"']);
558 })); 1088
559 1089 $rootScope.obj = {};
560 1090 $rootScope.$digest();
561 it(r'should broadcast an event from a child scope', inject((RootScope ro otScope) { 1091 expect(log).toEqual(['"test"', '{}']);
562 child2.broadcast('myEvent'); 1092 });
563 expect(log.join('>')).toEqual('2>21>211>22>23'); 1093
564 })); 1094
565 1095 it('should not trigger change when object in collection changes', () {
566 1096 $rootScope.obj = {'name': {}};
567 it(r'should broadcast an event from a leaf scope with a sibling', inject ((RootScope rootScope) { 1097 $rootScope.$digest();
568 grandChild22.broadcast('myEvent'); 1098 expect(log).toEqual(['{"name":{}}']);
569 expect(log.join('>')).toEqual('22'); 1099
570 })); 1100 $rootScope.obj['name']['bar'] = 'foo';
571 1101 $rootScope.$digest();
572 1102 expect(log).toEqual(['{"name":{}}']);
573 it(r'should broadcast an event from a leaf scope without a sibling', inj ect((RootScope rootScope) { 1103 });
574 grandChild23.broadcast('myEvent'); 1104
575 expect(log.join('>')).toEqual('23'); 1105
576 })); 1106 it('should watch object properties', () {
577 1107 $rootScope.obj = {};
578 1108 $rootScope.$digest();
579 it(r'should not not fire any listeners for other events', inject((RootSc ope rootScope) { 1109 expect(log).toEqual(['{}']);
580 rootScope.broadcast('fooEvent'); 1110
581 expect(log.join('>')).toEqual(''); 1111 $rootScope.obj['a']= 'A';
582 })); 1112 $rootScope.$digest();
583 1113 expect(log).toEqual(['{}', '{"a":"A"}']);
584 1114
585 it(r'should return event object', inject((RootScope rootScope) { 1115 $rootScope.obj['a'] = 'B';
586 var result = child1.broadcast('some'); 1116 $rootScope.$digest();
587 1117 expect(log).toEqual(['{}', '{"a":"A"}', '{"a":"B"}']);
588 expect(result).toBeDefined(); 1118
589 expect(result.name).toBe('some'); 1119 $rootScope.obj['b'] = [];
590 expect(result.targetScope).toBe(child1); 1120 $rootScope.obj['c'] = {};
591 })); 1121 log = [];
592 1122 $rootScope.$digest();
593 1123 expect(log).toEqual(['{"a":"B","b":[],"c":{}}']);
594 it('should skip scopes which dont have given event', 1124
595 inject((RootScope rootScope, Logger log) { 1125 var temp = $rootScope.obj['a'];
596 var child1 = rootScope.createChild('A'); 1126 $rootScope.obj['a'] = $rootScope.obj['b'];
597 rootScope.createChild('A1'); 1127 $rootScope.obj['c'] = temp;
598 rootScope.createChild('A2'); 1128 $rootScope.$digest();
599 rootScope.createChild('A3'); 1129 expect(log).toEqual([ '{"a":"B","b":[],"c":{}}', '{"a":[],"b":[],"c":" B"}' ]);
600 var child2 = rootScope.createChild('B'); 1130
601 child2.on('event').listen((e) => log(e.data)); 1131 $rootScope.obj.remove('a');
602 rootScope.broadcast('event', 'OK'); 1132 log = [];
603 expect(log).toEqual(['OK']); 1133 $rootScope.$digest();
604 })); 1134 expect(log).toEqual([ '{"b":[],"c":"B"}' ]);
605 }); 1135 });
606 1136 });
607 1137 });
608 describe(r'listener', () { 1138
609 it(r'should receive event object', inject((RootScope rootScope) { 1139
610 var scope = rootScope, 1140 describe('perf', () {
611 child = scope.createChild({}), 1141 describe('counters', () {
612 event; 1142
613 1143 it('should expose scope count', inject((Profiler perf, Scope scope) {
614 child.on('fooEvent').listen((e) { 1144 scope.$digest();
615 event = e; 1145 expect(perf.counters['ng.scopes']).toEqual(1);
616 }); 1146
617 scope.broadcast('fooEvent'); 1147 scope.$new();
618 1148 scope.$new();
619 expect(event.name).toBe('fooEvent'); 1149 var lastChild = scope.$new();
620 expect(event.targetScope).toBe(scope); 1150 scope.$digest();
621 expect(event.currentScope).toBe(child); 1151 expect(perf.counters['ng.scopes']).toEqual(4);
622 })); 1152
623 1153 // Create a child scope and make sure it's counted as well.
624 it(r'should support passing messages as varargs', inject((RootScope root Scope) { 1154 lastChild.$new();
625 var scope = rootScope, 1155 scope.$digest();
626 child = scope.createChild({}), 1156 expect(perf.counters['ng.scopes']).toEqual(5);
627 args; 1157 }));
628 1158
629 child.on('fooEvent').listen((e) { 1159
630 args = e.data; 1160 it('should update scope count when scope destroyed',
631 }); 1161 inject((Profiler perf, Scope scope) {
632 scope.broadcast('fooEvent', ['do', 're', 'me', 'fa']); 1162
633 1163 var child = scope.$new();
634 expect(args.length).toBe(4); 1164 scope.$digest();
635 expect(args).toEqual(['do', 're', 'me', 'fa']); 1165 expect(perf.counters['ng.scopes']).toEqual(2);
636 })); 1166
1167 child.$destroy();
1168 scope.$digest();
1169 expect(perf.counters['ng.scopes']).toEqual(1);
1170 }));
1171
1172
1173 it('should expose watcher count', inject((Profiler perf, Scope scope) {
1174 scope.$digest();
1175 expect(perf.counters['ng.scope.watchers']).toEqual(0);
1176
1177 scope.$watch(() => 0, (_) {});
1178 scope.$watch(() => 0, (_) {});
1179 scope.$watch(() => 0, (_) {});
1180 scope.$digest();
1181 expect(perf.counters['ng.scope.watchers']).toEqual(3);
1182
1183 // Create a child scope and make sure it's counted as well.
1184 scope.$new().$watch(() => 0, (_) {});
1185 scope.$digest();
1186 expect(perf.counters['ng.scope.watchers']).toEqual(4);
1187 }));
1188
1189
1190 it('should update watcher count when watcher removed',
1191 inject((Profiler perf, Scope scope) {
1192
1193 var unwatch = scope.$new().$watch(() => 0, (_) {});
1194 scope.$digest();
1195 expect(perf.counters['ng.scope.watchers']).toEqual(1);
1196
1197 unwatch();
1198 scope.$digest();
1199 expect(perf.counters['ng.scope.watchers']).toEqual(0);
1200 }));
1201 });
1202 });
1203
1204
1205 describe('optimizations', () {
1206 var scope;
1207 var log;
1208 beforeEach(inject((Scope _scope, Logger _log) {
1209 scope = _scope;
1210 log = _log;
1211 scope['a'] = 1;
1212 scope['b'] = 2;
1213 scope['c'] = 3;
1214 scope.$watch(() {log('a'); return scope['a'];}, (value) => log('fire:a') );
1215 scope.$watch(() {log('b'); return scope['b'];}, (value) => log('fire:b') );
1216 scope.$watch(() {log('c'); return scope['c'];}, (value) {log('fire:c'); scope['b']++; });
1217 scope.$digest();
1218 log.clear();
1219 }));
1220
1221 it('should loop once on no dirty', () {
1222 scope.$digest();
1223 expect(log.result()).toEqual('a; b; c');
1224 });
1225
1226 it('should exit early on second loop', () {
1227 scope['b']++;
1228 scope.$digest();
1229 expect(log.result()).toEqual('a; b; fire:b; c; a');
1230 });
1231
1232 it('should continue checking if second loop dirty', () {
1233 scope['c']++;
1234 scope.$digest();
1235 expect(log.result()).toEqual('a; b; c; fire:c; a; b; fire:b; c; a');
1236 });
1237 });
1238
1239 describe('ScopeLocals', () {
1240 var scope;
1241
1242 beforeEach(inject((Scope _scope) => scope = _scope));
1243
1244 it('should read from locals', () {
1245 scope['a'] = 'XXX';
1246 scope['c'] = 'C';
1247 var scopeLocal = new ScopeLocals(scope, {'a': 'A', 'b': 'B'});
1248 expect(scopeLocal['a']).toEqual('A');
1249 expect(scopeLocal['b']).toEqual('B');
1250 expect(scopeLocal['c']).toEqual('C');
1251 });
1252
1253 it('should write to Scope', () {
1254 scope['a'] = 'XXX';
1255 scope['c'] = 'C';
1256 var scopeLocal = new ScopeLocals(scope, {'a': 'A', 'b': 'B'});
1257
1258 scopeLocal['a'] = 'aW';
1259 scopeLocal['b'] = 'bW';
1260 scopeLocal['c'] = 'cW';
1261
1262 expect(scope['a']).toEqual('aW');
1263 expect(scope['b']).toEqual('bW');
1264 expect(scope['c']).toEqual('cW');
1265
1266 expect(scopeLocal['a']).toEqual('A');
1267 expect(scopeLocal['b']).toEqual('B');
1268 expect(scopeLocal['c']).toEqual('cW');
637 }); 1269 });
638 }); 1270 });
639 }); 1271 });
640
641
642 describe(r'destroy', () {
643 var first = null, middle = null, last = null, log = null;
644
645 beforeEach(inject((RootScope rootScope) {
646 log = '';
647
648 first = rootScope.createChild({"check": (n) { log+= '$n'; return n;}});
649 middle = rootScope.createChild({"check": (n) { log+= '$n'; return n;}});
650 last = rootScope.createChild({"check": (n) { log+= '$n'; return n;}});
651
652 first.watch('check(1)', (v, l) {});
653 middle.watch('check(2)', (v, l) {});
654 last.watch('check(3)', (v, l) {});
655
656 first.on(ScopeEvent.DESTROY).listen((e) { log += 'destroy:first;'; });
657
658 rootScope.digest();
659 log = '';
660 }));
661
662
663 it(r'should ignore remove on root', inject((RootScope rootScope) {
664 rootScope.destroy();
665 rootScope.digest();
666 expect(log).toEqual('123');
667 }));
668
669
670 it(r'should remove first', inject((RootScope rootScope) {
671 first.destroy();
672 rootScope.digest();
673 expect(log).toEqual('destroy:first;23');
674 }));
675
676
677 it(r'should remove middle', inject((RootScope rootScope) {
678 middle.destroy();
679 rootScope.digest();
680 expect(log).toEqual('13');
681 }));
682
683
684 it(r'should remove last', inject((RootScope rootScope) {
685 last.destroy();
686 rootScope.digest();
687 expect(log).toEqual('12');
688 }));
689
690
691 it(r'should broadcast the destroy event', inject((RootScope rootScope) {
692 var log = [];
693 first.on(ScopeEvent.DESTROY).listen((s) => log.add('first'));
694 var child = first.createChild({});
695 child.on(ScopeEvent.DESTROY).listen((s) => log.add('first-child'));
696
697 first.destroy();
698 expect(log).toEqual(['first', 'first-child']);
699 }));
700
701
702 it('should not call reaction function on destroyed scope', inject((RootScope rootScope, Logger log) {
703 rootScope.context['name'] = 'misko';
704 var child = rootScope.createChild(rootScope.context);
705 rootScope.watch('name', (v, _) {
706 log('root $v');
707 if (v == 'destroy') {
708 child.destroy();
709 }
710 });
711 rootScope.watch('name', (v, _) => log('root2 $v'));
712 child.watch('name', (v, _) => log('child $v'));
713 rootScope.apply();
714 expect(log).toEqual(['root misko', 'root2 misko', 'child misko']);
715 log.clear();
716
717 rootScope.context['name'] = 'destroy';
718 rootScope.apply();
719 expect(log).toEqual(['root destroy', 'root2 destroy']);
720 }));
721 });
722
723
724 describe('digest lifecycle', () {
725 it(r'should apply expression with full lifecycle', inject((RootScope rootSco pe) {
726 var log = '';
727 var child = rootScope.createChild({"parent": rootScope.context});
728 rootScope.watch('a', (a, _) { log += '1'; });
729 child.apply('parent.a = 0');
730 expect(log).toEqual('1');
731 }));
732
733
734 it(r'should catch exceptions', () {
735 module((Module module) => module.type(ExceptionHandler, implementedBy: Log gingExceptionHandler));
736 inject((RootScope rootScope, ExceptionHandler e) {
737 LoggingExceptionHandler exceptionHandler = e;
738 var log = [];
739 var child = rootScope.createChild({});
740 rootScope.watch('a', (a, _) => log.add('1'));
741 rootScope.context['a'] = 0;
742 child.apply(() { throw 'MyError'; });
743 expect(log.join(',')).toEqual('1');
744 expect(exceptionHandler.errors[0].error).toEqual('MyError');
745 exceptionHandler.errors.removeAt(0);
746 exceptionHandler.assertEmpty();
747 });
748 });
749
750
751 describe(r'exceptions', () {
752 var log;
753 beforeEach(module((Module module) {
754 return module.type(ExceptionHandler, implementedBy: LoggingExceptionHand ler);
755 }));
756 beforeEach(inject((RootScope rootScope) {
757 rootScope.context['log'] = () { log += 'digest;'; return null; };
758 log = '';
759 rootScope.watch('log()', (v, o) => null);
760 rootScope.digest();
761 log = '';
762 }));
763
764
765 it(r'should execute and return value and update', inject(
766 (RootScope rootScope, ExceptionHandler e) {
767 LoggingExceptionHandler exceptionHandler = e;
768 rootScope.context['name'] = 'abc';
769 expect(rootScope.apply((context) => context['name'])).toEqual('abc');
770 expect(log).toEqual('digest;digest;');
771 exceptionHandler.assertEmpty();
772 }));
773
774
775 it(r'should execute and return value and update', inject((RootScope rootSc ope) {
776 rootScope.context['name'] = 'abc';
777 expect(rootScope.apply('name', {'name': 123})).toEqual(123);
778 }));
779
780
781 it(r'should catch exception and update', inject((RootScope rootScope, Exce ptionHandler e) {
782 LoggingExceptionHandler exceptionHandler = e;
783 var error = 'MyError';
784 rootScope.apply(() { throw error; });
785 expect(log).toEqual('digest;digest;');
786 expect(exceptionHandler.errors[0].error).toEqual(error);
787 }));
788 });
789
790 it(r'should proprely reset phase on exception', inject((RootScope rootScope) {
791 var error = 'MyError';
792 expect(() => rootScope.apply(() { throw error; })).toThrow(error);
793 expect(() => rootScope.apply(() { throw error; })).toThrow(error);
794 }));
795 });
796
797
798 describe('flush lifecycle', () {
799 it(r'should apply expression with full lifecycle', inject((RootScope rootSco pe) {
800 var log = '';
801 var child = rootScope.createChild({"parent": rootScope.context});
802 rootScope.watch('a', (a, _) { log += '1'; }, readOnly: true);
803 child.apply('parent.a = 0');
804 expect(log).toEqual('1');
805 }));
806
807
808 it(r'should schedule domWrites and domReads', inject((RootScope rootScope) {
809 var log = '';
810 var child = rootScope.createChild({"parent": rootScope.context});
811 rootScope.watch('a', (a, _) { log += '1'; }, readOnly: true);
812 child.apply('parent.a = 0');
813 expect(log).toEqual('1');
814 }));
815
816
817 it(r'should catch exceptions', () {
818 module((Module module) => module.type(ExceptionHandler, implementedBy: Log gingExceptionHandler));
819 inject((RootScope rootScope, ExceptionHandler e) {
820 LoggingExceptionHandler exceptionHandler = e;
821 var log = [];
822 var child = rootScope.createChild({});
823 rootScope.watch('a', (a, _) => log.add('1'), readOnly: true);
824 rootScope.context['a'] = 0;
825 child.apply(() { throw 'MyError'; });
826 expect(log.join(',')).toEqual('1');
827 expect(exceptionHandler.errors[0].error).toEqual('MyError');
828 exceptionHandler.errors.removeAt(0);
829 exceptionHandler.assertEmpty();
830 });
831 });
832
833
834 describe(r'exceptions', () {
835 var log;
836 beforeEach(module((Module module) {
837 return module.type(ExceptionHandler, implementedBy: LoggingExceptionHand ler);
838 }));
839 beforeEach(inject((RootScope rootScope) {
840 rootScope.context['log'] = () { log += 'digest;'; return null; };
841 log = '';
842 rootScope.watch('log()', (v, o) => null, readOnly: true);
843 rootScope.digest();
844 log = '';
845 }));
846
847
848 it(r'should execute and return value and update', inject(
849 (RootScope rootScope, ExceptionHandler e) {
850 LoggingExceptionHandler exceptionHandler = e;
851 rootScope.context['name'] = 'abc';
852 expect(rootScope.apply((context) => context['name'])).toEqual('abc');
853 expect(log).toEqual('digest;digest;');
854 exceptionHandler.assertEmpty();
855 }));
856
857 it(r'should execute and return value and update', inject((RootScope rootSc ope) {
858 rootScope.context['name'] = 'abc';
859 expect(rootScope.apply('name', {'name': 123})).toEqual(123);
860 }));
861
862 it(r'should catch exception and update', inject((RootScope rootScope, Exce ptionHandler e) {
863 LoggingExceptionHandler exceptionHandler = e;
864 var error = 'MyError';
865 rootScope.apply(() { throw error; });
866 expect(log).toEqual('digest;digest;');
867 expect(exceptionHandler.errors[0].error).toEqual(error);
868 }));
869
870 it(r'should throw assertion when model changes in flush', inject((RootScop e rootScope, Logger log) {
871 var retValue = 1;
872 rootScope.context['logger'] = (name) { log(name); return retValue; };
873
874 rootScope.watch('logger("watch")', (n, v) => null);
875 rootScope.watch('logger("flush")', (n, v) => null, readOnly: true);
876
877 // clear watches
878 rootScope.digest();
879 log.clear();
880
881 rootScope.flush();
882 expect(log).toEqual(['flush', /*assertion*/ 'watch', 'flush']);
883
884 retValue = 2;
885 expect(rootScope.flush).
886 toThrow('Observer reaction functions should not change model. \n'
887 'These watch changes were detected: logger("watch"): 2 <= 1\n'
888 'These observe changes were detected: ');
889 }));
890 });
891
892 });
893
894
895 describe('ScopeLocals', () {
896 it('should read from locals', inject((RootScope scope) {
897 scope.context['a'] = 'XXX';
898 scope.context['c'] = 'C';
899 var scopeLocal = new ScopeLocals(scope.context, {'a': 'A', 'b': 'B'});
900 expect(scopeLocal['a']).toEqual('A');
901 expect(scopeLocal['b']).toEqual('B');
902 expect(scopeLocal['c']).toEqual('C');
903 }));
904
905 it('should write to Scope', inject((RootScope scope) {
906 scope.context['a'] = 'XXX';
907 scope.context['c'] = 'C';
908 var scopeLocal = new ScopeLocals(scope.context, {'a': 'A', 'b': 'B'});
909
910 scopeLocal['a'] = 'aW';
911 scopeLocal['b'] = 'bW';
912 scopeLocal['c'] = 'cW';
913
914 expect(scope.context['a']).toEqual('aW');
915 expect(scope.context['b']).toEqual('bW');
916 expect(scope.context['c']).toEqual('cW');
917
918 expect(scopeLocal['a']).toEqual('A');
919 expect(scopeLocal['b']).toEqual('B');
920 expect(scopeLocal['c']).toEqual('cW');
921 }));
922 });
923
924
925 describe(r'watch/digest', () {
926 it(r'should watch and fire on simple property change', inject((RootScope roo tScope) {
927 var log;
928
929 rootScope.watch('name', (a, b) {
930 log = [a, b];
931 });
932 rootScope.digest();
933 log = null;
934
935 expect(log).toEqual(null);
936 rootScope.digest();
937 expect(log).toEqual(null);
938 rootScope.context['name'] = 'misko';
939 rootScope.digest();
940 expect(log).toEqual(['misko', null]);
941 }));
942
943
944 it('should watch/observe on objects other then contex', inject((RootScope ro otScope) {
945 var log = '';
946 var map = {'a': 'A', 'b': 'B'};
947 rootScope.watch('a', (a, b) => log += a, context: map);
948 rootScope.watch('b', (a, b) => log += a, context: map);
949 rootScope.apply();
950 expect(log).toEqual('AB');
951 }));
952
953
954 it(r'should watch and fire on expression change', inject((RootScope rootScop e) {
955 var log;
956
957 rootScope.watch('name.first', (a, b) => log = [a, b]);
958 rootScope.digest();
959 log = null;
960
961 rootScope.context['name'] = {};
962 expect(log).toEqual(null);
963 rootScope.digest();
964 expect(log).toEqual(null);
965 rootScope.context['name']['first'] = 'misko';
966 rootScope.digest();
967 expect(log).toEqual(['misko', null]);
968 }));
969
970
971 it(r'should delegate exceptions', () {
972 module((Module module) {
973 module.type(ExceptionHandler, implementedBy: LoggingExceptionHandler);
974 });
975 inject((RootScope rootScope, ExceptionHandler e) {
976 LoggingExceptionHandler exceptionHandler = e;
977 rootScope.watch('a', (n, o) {throw 'abc';});
978 rootScope.context['a'] = 1;
979 rootScope.digest();
980 expect(exceptionHandler.errors.length).toEqual(1);
981 expect(exceptionHandler.errors[0].error).toEqual('abc');
982 });
983 });
984
985
986 it(r'should fire watches in order of addition', inject((RootScope rootScope) {
987 // this is not an external guarantee, just our own sanity
988 var log = '';
989 rootScope.watch('a', (a, b) { log += 'a'; });
990 rootScope.watch('b', (a, b) { log += 'b'; });
991 rootScope.watch('c', (a, b) { log += 'c'; });
992 rootScope.context['a'] = rootScope.context['b'] = rootScope.context['c'] = 1;
993 rootScope.digest();
994 expect(log).toEqual('abc');
995 }));
996
997
998 it(r'should call child watchers in addition order', inject((RootScope rootSc ope) {
999 // this is not an external guarantee, just our own sanity
1000 var log = '';
1001 var childA = rootScope.createChild({});
1002 var childB = rootScope.createChild({});
1003 var childC = rootScope.createChild({});
1004 childA.watch('a', (a, b) { log += 'a'; });
1005 childB.watch('b', (a, b) { log += 'b'; });
1006 childC.watch('c', (a, b) { log += 'c'; });
1007 childA.context['a'] = childB.context['b'] = childC.context['c'] = 1;
1008 rootScope.digest();
1009 expect(log).toEqual('abc');
1010 }));
1011
1012
1013 it(r'should run digest multiple times', inject(
1014 (RootScope rootScope) {
1015 // tests a traversal edge case which we originally missed
1016 var log = [];
1017 var childA = rootScope.createChild({'log': log});
1018 var childB = rootScope.createChild({'log': log});
1019
1020 rootScope.context['log'] = log;
1021
1022 rootScope.watch("log.add('r')", (_, __) => null);
1023 childA.watch("log.add('a')", (_, __) => null);
1024 childB.watch("log.add('b')", (_, __) => null);
1025
1026 // init
1027 rootScope.digest();
1028 expect(log.join('')).toEqual('rabrab');
1029 }));
1030
1031
1032 it(r'should repeat watch cycle while model changes are identified', inject(( RootScope rootScope) {
1033 var log = '';
1034 rootScope.watch('c', (v, b) {rootScope.context['d'] = v; log+='c'; });
1035 rootScope.watch('b', (v, b) {rootScope.context['c'] = v; log+='b'; });
1036 rootScope.watch('a', (v, b) {rootScope.context['b'] = v; log+='a'; });
1037 rootScope.digest();
1038 log = '';
1039 rootScope.context['a'] = 1;
1040 rootScope.digest();
1041 expect(rootScope.context['b']).toEqual(1);
1042 expect(rootScope.context['c']).toEqual(1);
1043 expect(rootScope.context['d']).toEqual(1);
1044 expect(log).toEqual('abc');
1045 }));
1046
1047
1048 it(r'should repeat watch cycle from the root element', inject((RootScope roo tScope) {
1049 var log = [];
1050 rootScope.context['log'] = log;
1051 var child = rootScope.createChild({'log':log});
1052 rootScope.watch("log.add('a')", (_, __) => null);
1053 child.watch("log.add('b')", (_, __) => null);
1054 rootScope.digest();
1055 expect(log.join('')).toEqual('abab');
1056 }));
1057
1058
1059 it(r'should not fire upon watch registration on initial digest', inject((Roo tScope rootScope) {
1060 var log = '';
1061 rootScope.context['a'] = 1;
1062 rootScope.watch('a', (a, b) { log += 'a'; });
1063 rootScope.watch('b', (a, b) { log += 'b'; });
1064 rootScope.digest();
1065 log = '';
1066 rootScope.digest();
1067 expect(log).toEqual('');
1068 }));
1069
1070
1071 it(r'should prevent digest recursion', inject((RootScope rootScope) {
1072 var callCount = 0;
1073 rootScope.watch('name', (a, b) {
1074 expect(() {
1075 rootScope.digest();
1076 }).toThrow(r'digest already in progress');
1077 callCount++;
1078 });
1079 rootScope.context['name'] = 'a';
1080 rootScope.digest();
1081 expect(callCount).toEqual(1);
1082 }));
1083
1084
1085 it(r'should return a function that allows listeners to be unregistered', inj ect(
1086 (RootScope rootScope) {
1087 var listener = jasmine.createSpy('watch listener');
1088 var watch;
1089
1090 watch = rootScope.watch('foo', listener);
1091 rootScope.digest(); //init
1092 expect(listener).toHaveBeenCalled();
1093 expect(watch).toBeDefined();
1094
1095 listener.reset();
1096 rootScope.context['foo'] = 'bar';
1097 rootScope.digest(); //triger
1098 expect(listener).toHaveBeenCalledOnce();
1099
1100 listener.reset();
1101 rootScope.context['foo'] = 'baz';
1102 watch.remove();
1103 rootScope.digest(); //trigger
1104 expect(listener).not.toHaveBeenCalled();
1105 }));
1106
1107
1108 it(r'should not infinitely digest when current value is NaN', inject((RootSc ope rootScope) {
1109 rootScope.context['nan'] = double.NAN;
1110 rootScope.watch('nan', (_, __) => null);
1111
1112 expect(() {
1113 rootScope.digest();
1114 }).not.toThrow();
1115 }));
1116
1117
1118 it(r'should prevent infinite digest and should log firing expressions', inje ct((RootScope rootScope) {
1119 rootScope.context['a'] = 0;
1120 rootScope.context['b'] = 0;
1121 rootScope.watch('a', (a, __) => rootScope.context['a'] = a + 1);
1122 rootScope.watch('b', (b, __) => rootScope.context['b'] = b + 1);
1123
1124 expect(() {
1125 rootScope.digest();
1126 }).toThrow('Model did not stabilize in 5 digests. '
1127 'Last 3 iterations:\n'
1128 'a: 2 <= 1, b: 2 <= 1\n'
1129 'a: 3 <= 2, b: 3 <= 2\n'
1130 'a: 4 <= 3, b: 4 <= 3');
1131 }));
1132
1133
1134 it(r'should always call the watchr with newVal and oldVal equal on the first run',
1135 inject((RootScope rootScope) {
1136 var log = [];
1137 var logger = (newVal, oldVal) {
1138 var val = (newVal == oldVal || (newVal != oldVal && oldVal != newVal)) ? newVal : 'xxx';
1139 log.add(val);
1140 };
1141
1142 rootScope.context['nanValue'] = double.NAN;
1143 rootScope.context['nullValue'] = null;
1144 rootScope.context['emptyString'] = '';
1145 rootScope.context['falseValue'] = false;
1146 rootScope.context['numberValue'] = 23;
1147
1148 rootScope.watch('nanValue', logger);
1149 rootScope.watch('nullValue', logger);
1150 rootScope.watch('emptyString', logger);
1151 rootScope.watch('falseValue', logger);
1152 rootScope.watch('numberValue', logger);
1153
1154 rootScope.digest();
1155 expect(log.removeAt(0).isNaN).toEqual(true); //jasmine's toBe and toEqual don't work well with NaNs
1156 expect(log).toEqual([null, '', false, 23]);
1157 log = [];
1158 rootScope.digest();
1159 expect(log).toEqual([]);
1160 }));
1161
1162
1163 it('should properly watch canstants', inject((RootScope rootScope, Logger lo g) {
1164 rootScope.watch('[1, 2]', (v, o) => log([v, o]));
1165 expect(log).toEqual([]);
1166 rootScope.apply();
1167 expect(log).toEqual([[[1, 2], null]]);
1168 }));
1169
1170
1171 it('should properly watch array of fields', inject((RootScope rootScope, Log ger log) {
1172 rootScope.context['foo'] = 12;
1173 rootScope.context['bar'] = 34;
1174 rootScope.watch('[foo, bar]', (v, o) => log([v, o]));
1175 expect(log).toEqual([]);
1176 rootScope.apply();
1177 expect(log).toEqual([[[12, 34], null]]);
1178 log.clear();
1179
1180 rootScope.context['foo'] = 56;
1181 rootScope.context['bar'] = 78;
1182 rootScope.apply();
1183 expect(log).toEqual([[[56, 78], [12, 34]]]);
1184 }));
1185
1186
1187 it('should properly watch array of fields2', inject((RootScope rootScope, Lo gger log) {
1188 rootScope.watch('[ctrl.foo, ctrl.bar]', (v, o) => log([v, o]));
1189 expect(log).toEqual([]);
1190 rootScope.apply();
1191 expect(log).toEqual([[[null, null], null]]);
1192 log.clear();
1193
1194 rootScope.context['ctrl'] = {'foo': 56, 'bar': 78};
1195 rootScope.apply();
1196 expect(log).toEqual([[[56, 78], [null, null]]]);
1197 }));
1198 });
1199
1200
1201 describe('special binding modes', () {
1202 it('should bind one time', inject((RootScope rootScope, Logger log) {
1203 rootScope.watch('foo', (v, _) => log('foo:$v'));
1204 rootScope.watch(':foo', (v, _) => log(':foo:$v'));
1205 rootScope.watch('::foo', (v, _) => log('::foo:$v'));
1206
1207 rootScope.apply();
1208 expect(log).toEqual(['foo:null']);
1209 log.clear();
1210
1211 rootScope.context['foo'] = true;
1212 rootScope.apply();
1213 expect(log).toEqual(['foo:true', ':foo:true', '::foo:true']);
1214 log.clear();
1215
1216 rootScope.context['foo'] = 123;
1217 rootScope.apply();
1218 expect(log).toEqual(['foo:123', ':foo:123']);
1219 log.clear();
1220
1221 rootScope.context['foo'] = null;
1222 rootScope.apply();
1223 expect(log).toEqual(['foo:null']);
1224 log.clear();
1225 }));
1226 });
1227
1228
1229 describe('runAsync', () {
1230 it(r'should run callback before watch', inject((RootScope rootScope) {
1231 var log = '';
1232 rootScope.runAsync(() { log += 'parent.async;'; });
1233 rootScope.watch('value', (_, __) { log += 'parent.digest;'; });
1234 rootScope.digest();
1235 expect(log).toEqual('parent.async;parent.digest;');
1236 }));
1237
1238 it(r'should cause a digest rerun', inject((RootScope rootScope) {
1239 rootScope.context['log'] = '';
1240 rootScope.context['value'] = 0;
1241 // NOTE(deboer): watch listener string functions not yet supported
1242 //rootScope.watch('value', 'log = log + ".";');
1243 rootScope.watch('value', (_, __) { rootScope.context['log'] += "."; });
1244 rootScope.watch('init', (_, __) {
1245 rootScope.runAsync(() => rootScope.eval('value = 123; log = log + "=" ') );
1246 expect(rootScope.context['value']).toEqual(0);
1247 });
1248 rootScope.digest();
1249 expect(rootScope.context['log']).toEqual('.=.');
1250 }));
1251
1252 it(r'should run async in the same order as added', inject((RootScope rootSco pe) {
1253 rootScope.context['log'] = '';
1254 rootScope.runAsync(() => rootScope.eval("log = log + 1"));
1255 rootScope.runAsync(() => rootScope.eval("log = log + 2"));
1256 rootScope.digest();
1257 expect(rootScope.context['log']).toEqual('12');
1258 }));
1259 });
1260
1261
1262 describe('domRead/domWrite', () {
1263 it(r'should run writes before reads', () {
1264 module((Module module) {
1265 module.type(ExceptionHandler, implementedBy: LoggingExceptionHandler);
1266 });
1267 inject((RootScope rootScope, Logger logger, ExceptionHandler e) {
1268 LoggingExceptionHandler exceptionHandler = e as LoggingExceptionHandler;
1269 rootScope.domWrite(() {
1270 logger('write1');
1271 rootScope.domWrite(() => logger('write2'));
1272 throw 'write1';
1273 });
1274 rootScope.domRead(() {
1275 logger('read1');
1276 rootScope.domRead(() => logger('read2'));
1277 rootScope.domWrite(() => logger('write3'));
1278 throw 'read1';
1279 });
1280 rootScope.watch('value', (_, __) => logger('observe'), readOnly: true);
1281 rootScope.flush();
1282 expect(logger).toEqual(['write1', 'write2', 'observe', 'read1', 'read2', 'write3']);
1283 expect(exceptionHandler.errors.length).toEqual(2);
1284 expect(exceptionHandler.errors[0].error).toEqual('write1');
1285 expect(exceptionHandler.errors[1].error).toEqual('read1');
1286 });
1287 });
1288 });
1289
1290 describe('exceptionHander', () {
1291 it('should call ExceptionHandler on zone errors', () {
1292 module((Module module) {
1293 module.type(ExceptionHandler, implementedBy: LoggingExceptionHandler);
1294 });
1295 async((inject((RootScope rootScope, NgZone zone, ExceptionHandler e) {
1296 zone.run(() {
1297 scheduleMicrotask(() => throw 'my error');
1298 });
1299 var errors = (e as LoggingExceptionHandler).errors;
1300 expect(errors.length).toEqual(1);
1301 expect(errors.first.error).toEqual('my error');
1302 })));
1303 });
1304
1305 it('should call ExceptionHandler on digest errors', () {
1306 module((Module module) {
1307 module.type(ExceptionHandler, implementedBy: LoggingExceptionHandler);
1308 });
1309 async((inject((RootScope rootScope, NgZone zone, ExceptionHandler e) {
1310 rootScope.context['badOne'] = () => new Map();
1311 rootScope.watch('badOne()', (_, __) => null);
1312
1313 try {
1314 zone.run(() => null);
1315 } catch(_) {}
1316
1317 var errors = (e as LoggingExceptionHandler).errors;
1318 expect(errors.length).toEqual(1);
1319 expect(errors.first.error, startsWith('Model did not stabilize'));
1320 })));
1321 });
1322 });
1323 });
1324
1325 @NgFilter(name: 'multiply')
1326 class _MultiplyFilter {
1327 call(a, b) => a * b;
1328 } 1272 }
1329 1273
1330 @NgFilter(name: 'listHead') 1274 _toJsonableIterable(Iterable source) => new _JsonableIterableWrapper(source);
1331 class _ListHeadFilter { 1275
1332 Logger logger; 1276 class _JsonableIterableWrapper<T> implements Iterable<T> {
1333 _ListHeadFilter(Logger this.logger); 1277 final Iterable<T> source;
1334 call(list, head) { 1278
1335 logger('listHead'); 1279 _JsonableIterableWrapper(this.source);
1336 return [head]..addAll(list); 1280
1337 } 1281 bool any(bool test(T element)) => source.any(test);
1282
1283 bool contains(Object element) => source.contains(element);
1284
1285 T elementAt(int index) => source.elementAt(index);
1286
1287 bool every(bool test(T element)) => source.every(test);
1288
1289 Iterable expand(Iterable f(T element)) => source.expand(f);
1290
1291 T get first => source.first;
1292
1293 T firstWhere(bool test(T element), {T orElse()}) =>
1294 source.firstWhere(test, orElse: orElse);
1295
1296 fold(initialValue, combine(previousValue, T element)) =>
1297 source.fold(initialValue, combine);
1298
1299 void forEach(void f(T element)) => source.forEach(f);
1300
1301 bool get isEmpty => source.isEmpty;
1302
1303 bool get isNotEmpty => source.isNotEmpty;
1304
1305 Iterator<T> get iterator => source.iterator;
1306
1307 String join([String separator = ""]) => source.join(separator);
1308
1309 T get last => source.last;
1310
1311 T lastWhere(bool test(T element), {T orElse()}) =>
1312 source.lastWhere(test, orElse: orElse);
1313
1314 int get length => source.length;
1315
1316 Iterable map(f(T element)) => source.map(f);
1317
1318 T reduce(T combine(T value, T element)) => source.reduce(combine);
1319
1320 T get single => source.single;
1321
1322 T singleWhere(bool test(T element)) => source.singleWhere(test);
1323
1324 Iterable<T> skip(int n) => source.skip(n);
1325
1326 Iterable<T> skipWhile(bool test(T value)) => source.skipWhile(test);
1327
1328 Iterable<T> take(int n) => source.take(n);
1329
1330 Iterable<T> takeWhile(bool test(T value)) => source.takeWhile(test);
1331
1332 List<T> toList({bool growable: true}) => source.toList(growable: growable);
1333
1334 Set<T> toSet() => source.toSet();
1335
1336 Iterable<T> where(bool test(T element)) => source.where(test);
1337
1338 toJson() => source.toList();
1338 } 1339 }
1339
1340
1341 @NgFilter(name: 'listTail')
1342 class _ListTailFilter {
1343 Logger logger;
1344 _ListTailFilter(Logger this.logger);
1345 call(list, tail) {
1346 logger('listTail');
1347 return new List.from(list)..add(tail);
1348 }
1349 }
1350
1351 @NgFilter(name: 'sort')
1352 class _SortFilter {
1353 Logger logger;
1354 _SortFilter(Logger this.logger);
1355 call(list) {
1356 logger('sort');
1357 return new List.from(list)..sort();
1358 }
1359 }
1360
1361 @NgFilter(name:'newFilter')
1362 class FilterOne {
1363 call(String str) {
1364 return '$str 1';
1365 }
1366 }
1367
1368 @NgFilter(name:'newFilter')
1369 class FilterTwo {
1370 call(String str) {
1371 return '$str 2';
1372 }
1373 }
OLDNEW
« no previous file with comments | « third_party/pkg/angular/test/core/registry_spec.dart ('k') | third_party/pkg/angular/test/core/templateurl_spec.dart » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698