OLD | NEW |
| (Empty) |
1 library scope2_spec; | |
2 | |
3 import '../_specs.dart'; | |
4 import 'package:angular/change_detection/change_detection.dart' hide ExceptionHa
ndler; | |
5 import 'package:angular/change_detection/dirty_checking_change_detector.dart'; | |
6 import 'dart:async'; | |
7 import 'dart:math'; | |
8 | |
9 main() => describe('scope', () { | |
10 beforeEach(module((Module module) { | |
11 Map context = {}; | |
12 module.value(GetterCache, new GetterCache({})); | |
13 module.type(ChangeDetector, implementedBy: DirtyCheckingChangeDetector); | |
14 module.value(Object, context); | |
15 module.value(Map, context); | |
16 module.type(RootScope); | |
17 module.type(_MultiplyFilter); | |
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 })); | |
33 | |
34 it('should watch field path', inject((Logger logger, Map context, RootScope
rootScope) { | |
35 context['a'] = {'b': 'AB'}; | |
36 rootScope.watch('a.b', (value, previous) => logger(value)); | |
37 rootScope.digest(); | |
38 expect(logger).toEqual(['AB']); | |
39 context['a']['b'] = '123'; | |
40 rootScope.digest(); | |
41 expect(logger).toEqual(['AB', '123']); | |
42 context['a'] = {'b': 'XYZ'}; | |
43 rootScope.digest(); | |
44 expect(logger).toEqual(['AB', '123', 'XYZ']); | |
45 })); | |
46 | |
47 it('should watch math operations', inject((Logger logger, Map context, RootS
cope rootScope) { | |
48 context['a'] = 1; | |
49 context['b'] = 2; | |
50 rootScope.watch('a + b + 1', (value, previous) => logger(value)); | |
51 rootScope.digest(); | |
52 expect(logger).toEqual([4]); | |
53 context['a'] = 3; | |
54 rootScope.digest(); | |
55 expect(logger).toEqual([4, 6]); | |
56 context['b'] = 5; | |
57 rootScope.digest(); | |
58 expect(logger).toEqual([4, 6, 9]); | |
59 })); | |
60 | |
61 | |
62 it('should watch literals', inject((Logger logger, Map context, RootScope ro
otScope) { | |
63 context['a'] = 1; | |
64 rootScope.watch('1', (value, previous) => logger(value)); | |
65 rootScope.watch('"str"', (value, previous) => logger(value)); | |
66 rootScope.watch('[a, 2, 3]', (value, previous) => logger(value)); | |
67 rootScope.watch('{a:a, b:2}', (value, previous) => logger(value)); | |
68 rootScope.digest(); | |
69 expect(logger).toEqual([1, 'str', [1, 2, 3], {'a': 1, 'b': 2}]); | |
70 logger.clear(); | |
71 context['a'] = 3; | |
72 rootScope.digest(); | |
73 expect(logger).toEqual([[3, 2, 3], {'a': 3, 'b': 2}]); | |
74 })); | |
75 | |
76 it('should invoke closures', inject((Logger logger, Map context, RootScope r
ootScope) { | |
77 context['fn'] = () { | |
78 logger('fn'); | |
79 return 1; | |
80 }; | |
81 context['a'] = {'fn': () { | |
82 logger('a.fn'); | |
83 return 2; | |
84 }}; | |
85 rootScope.watch('fn()', (value, previous) => logger('=> $value')); | |
86 rootScope.watch('a.fn()', (value, previous) => logger('-> $value')); | |
87 rootScope.digest(); | |
88 expect(logger).toEqual(['fn', 'a.fn', '=> 1', '-> 2', | |
89 /* second loop*/ 'fn', 'a.fn']); | |
90 logger.clear(); | |
91 rootScope.digest(); | |
92 expect(logger).toEqual(['fn', 'a.fn']); | |
93 })); | |
94 | |
95 it('should perform conditionals', inject((Logger logger, Map context, RootSc
ope rootScope) { | |
96 context['a'] = 1; | |
97 context['b'] = 2; | |
98 context['c'] = 3; | |
99 rootScope.watch('a?b:c', (value, previous) => logger(value)); | |
100 rootScope.digest(); | |
101 expect(logger).toEqual([2]); | |
102 logger.clear(); | |
103 context['a'] = 0; | |
104 rootScope.digest(); | |
105 expect(logger).toEqual([3]); | |
106 })); | |
107 | |
108 | |
109 xit('should call function', inject((Logger logger, Map context, RootScope ro
otScope) { | |
110 context['a'] = () { | |
111 return () { return 123; }; | |
112 }; | |
113 rootScope.watch('a()()', (value, previous) => logger(value)); | |
114 rootScope.digest(); | |
115 expect(logger).toEqual([123]); | |
116 logger.clear(); | |
117 rootScope.digest(); | |
118 expect(logger).toEqual([]); | |
119 })); | |
120 | |
121 it('should access bracket', inject((Logger logger, Map context, RootScope ro
otScope) { | |
122 context['a'] = {'b': 123}; | |
123 rootScope.watch('a["b"]', (value, previous) => logger(value)); | |
124 rootScope.digest(); | |
125 expect(logger).toEqual([123]); | |
126 logger.clear(); | |
127 rootScope.digest(); | |
128 expect(logger).toEqual([]); | |
129 })); | |
130 | |
131 | |
132 it('should prefix', inject((Logger logger, Map context, RootScope rootScope)
{ | |
133 context['a'] = true; | |
134 rootScope.watch('!a', (value, previous) => logger(value)); | |
135 rootScope.digest(); | |
136 expect(logger).toEqual([false]); | |
137 logger.clear(); | |
138 context['a'] = false; | |
139 rootScope.digest(); | |
140 expect(logger).toEqual([true]); | |
141 })); | |
142 | |
143 it('should support filters', inject((Logger logger, Map context, | |
144 RootScope rootScope, AstParser parser, | |
145 FilterMap filters) { | |
146 context['a'] = 123; | |
147 context['b'] = 2; | |
148 rootScope.watch( | |
149 parser('a | multiply:b', filters: filters), | |
150 (value, previous) => logger(value)); | |
151 rootScope.digest(); | |
152 expect(logger).toEqual([246]); | |
153 logger.clear(); | |
154 rootScope.digest(); | |
155 expect(logger).toEqual([]); | |
156 logger.clear(); | |
157 })); | |
158 | |
159 it('should support arrays in filters', inject((Logger logger, Map context, | |
160 RootScope rootScope, | |
161 AstParser parser, | |
162 FilterMap filters) { | |
163 context['a'] = [1]; | |
164 rootScope.watch( | |
165 parser('a | sort | listHead:"A" | listTail:"B"', filters: filters), | |
166 (value, previous) => logger(value)); | |
167 rootScope.digest(); | |
168 expect(logger).toEqual(['sort', 'listHead', 'listTail', ['A', 1, 'B']]); | |
169 logger.clear(); | |
170 | |
171 rootScope.digest(); | |
172 expect(logger).toEqual([]); | |
173 logger.clear(); | |
174 | |
175 context['a'].add(2); | |
176 rootScope.digest(); | |
177 expect(logger).toEqual(['sort', 'listHead', 'listTail', ['A', 1, 2, 'B']])
; | |
178 logger.clear(); | |
179 | |
180 // We change the order, but sort should change it to same one and it shoul
d not | |
181 // call subsequent filters. | |
182 context['a'] = [2, 1]; | |
183 rootScope.digest(); | |
184 expect(logger).toEqual(['sort']); | |
185 logger.clear(); | |
186 })); | |
187 }); | |
188 | |
189 | |
190 describe('properties', () { | |
191 describe('root', () { | |
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) { | |
445 module.type(ExceptionHandler, implementedBy: LoggingExceptionHandler); | |
446 }); | |
447 inject((ExceptionHandler e) { | |
448 LoggingExceptionHandler exceptionHandler = e; | |
449 child.on('myEvent').listen((e) { throw 'bubbleException'; }); | |
450 grandChild.emit(r'myEvent'); | |
451 expect(log.join('>')).toEqual('2>1>0'); | |
452 expect(exceptionHandler.errors[0].error).toEqual('bubbleException'); | |
453 }); | |
454 }); | |
455 | |
456 | |
457 it(r'should allow stopping event propagation', inject((RootScope rootScope
) { | |
458 child.on('myEvent').listen((event) { event.stopPropagation(); }); | |
459 grandChild.emit(r'myEvent'); | |
460 expect(log.join('>')).toEqual('2>1'); | |
461 })); | |
462 | |
463 | |
464 it(r'should forward method arguments', inject((RootScope rootScope) { | |
465 var eventName; | |
466 var eventData; | |
467 child.on('abc').listen((event) { | |
468 eventName = event.name; | |
469 eventData = event.data; | |
470 }); | |
471 child.emit('abc', ['arg1', 'arg2']); | |
472 expect(eventName).toEqual('abc'); | |
473 expect(eventData).toEqual(['arg1', 'arg2']); | |
474 })); | |
475 | |
476 | |
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 }); | |
486 grandChild.emit(r'myEvent'); | |
487 expect(event).toBeDefined(); | |
488 })); | |
489 | |
490 | |
491 it(r'should have preventDefault method and defaultPrevented property', i
nject((RootScope rootScope) { | |
492 var event = grandChild.emit(r'myEvent'); | |
493 expect(event.defaultPrevented).toBe(false); | |
494 | |
495 child.on('myEvent').listen((event) { | |
496 event.preventDefault(); | |
497 }); | |
498 event = grandChild.emit(r'myEvent'); | |
499 expect(event.defaultPrevented).toBe(true); | |
500 })); | |
501 }); | |
502 }); | |
503 | |
504 | |
505 describe('broadcast', () { | |
506 describe(r'event propagation', () { | |
507 var log, child1, child2, child3, grandChild11, grandChild21, grandChild2
2, grandChild23, | |
508 greatGrandChild211; | |
509 | |
510 logger(event) { | |
511 log.add(event.currentScope.context['id']); | |
512 } | |
513 | |
514 beforeEach(inject((RootScope rootScope) { | |
515 log = []; | |
516 child1 = rootScope.createChild({}); | |
517 child2 = rootScope.createChild({}); | |
518 child3 = rootScope.createChild({}); | |
519 grandChild11 = child1.createChild({}); | |
520 grandChild21 = child2.createChild({}); | |
521 grandChild22 = child2.createChild({}); | |
522 grandChild23 = child2.createChild({}); | |
523 greatGrandChild211 = grandChild21.createChild({}); | |
524 | |
525 rootScope.context['id'] = 0; | |
526 child1.context['id'] = 1; | |
527 child2.context['id'] = 2; | |
528 child3.context['id'] = 3; | |
529 grandChild11.context['id'] = 11; | |
530 grandChild21.context['id'] = 21; | |
531 grandChild22.context['id'] = 22; | |
532 grandChild23.context['id'] = 23; | |
533 greatGrandChild211.context['id'] = 211; | |
534 | |
535 rootScope.on('myEvent').listen(logger); | |
536 child1.on('myEvent').listen(logger); | |
537 child2.on('myEvent').listen(logger); | |
538 child3.on('myEvent').listen(logger); | |
539 grandChild11.on('myEvent').listen(logger); | |
540 grandChild21.on('myEvent').listen(logger); | |
541 grandChild22.on('myEvent').listen(logger); | |
542 grandChild23.on('myEvent').listen(logger); | |
543 greatGrandChild211.on('myEvent').listen(logger); | |
544 | |
545 // R | |
546 // / | \ | |
547 // 1 2 3 | |
548 // / / | \ | |
549 // 11 21 22 23 | |
550 // | | |
551 // 211 | |
552 })); | |
553 | |
554 | |
555 it(r'should broadcast an event from the root scope', inject((RootScope r
ootScope) { | |
556 rootScope.broadcast('myEvent'); | |
557 expect(log.join('>')).toEqual('0>1>11>2>21>211>22>23>3'); | |
558 })); | |
559 | |
560 | |
561 it(r'should broadcast an event from a child scope', inject((RootScope ro
otScope) { | |
562 child2.broadcast('myEvent'); | |
563 expect(log.join('>')).toEqual('2>21>211>22>23'); | |
564 })); | |
565 | |
566 | |
567 it(r'should broadcast an event from a leaf scope with a sibling', inject
((RootScope rootScope) { | |
568 grandChild22.broadcast('myEvent'); | |
569 expect(log.join('>')).toEqual('22'); | |
570 })); | |
571 | |
572 | |
573 it(r'should broadcast an event from a leaf scope without a sibling', inj
ect((RootScope rootScope) { | |
574 grandChild23.broadcast('myEvent'); | |
575 expect(log.join('>')).toEqual('23'); | |
576 })); | |
577 | |
578 | |
579 it(r'should not not fire any listeners for other events', inject((RootSc
ope rootScope) { | |
580 rootScope.broadcast('fooEvent'); | |
581 expect(log.join('>')).toEqual(''); | |
582 })); | |
583 | |
584 | |
585 it(r'should return event object', inject((RootScope rootScope) { | |
586 var result = child1.broadcast('some'); | |
587 | |
588 expect(result).toBeDefined(); | |
589 expect(result.name).toBe('some'); | |
590 expect(result.targetScope).toBe(child1); | |
591 })); | |
592 | |
593 | |
594 it('should skip scopes which dont have given event', | |
595 inject((RootScope rootScope, Logger log) { | |
596 var child1 = rootScope.createChild('A'); | |
597 rootScope.createChild('A1'); | |
598 rootScope.createChild('A2'); | |
599 rootScope.createChild('A3'); | |
600 var child2 = rootScope.createChild('B'); | |
601 child2.on('event').listen((e) => log(e.data)); | |
602 rootScope.broadcast('event', 'OK'); | |
603 expect(log).toEqual(['OK']); | |
604 })); | |
605 }); | |
606 | |
607 | |
608 describe(r'listener', () { | |
609 it(r'should receive event object', inject((RootScope rootScope) { | |
610 var scope = rootScope, | |
611 child = scope.createChild({}), | |
612 event; | |
613 | |
614 child.on('fooEvent').listen((e) { | |
615 event = e; | |
616 }); | |
617 scope.broadcast('fooEvent'); | |
618 | |
619 expect(event.name).toBe('fooEvent'); | |
620 expect(event.targetScope).toBe(scope); | |
621 expect(event.currentScope).toBe(child); | |
622 })); | |
623 | |
624 it(r'should support passing messages as varargs', inject((RootScope root
Scope) { | |
625 var scope = rootScope, | |
626 child = scope.createChild({}), | |
627 args; | |
628 | |
629 child.on('fooEvent').listen((e) { | |
630 args = e.data; | |
631 }); | |
632 scope.broadcast('fooEvent', ['do', 're', 'me', 'fa']); | |
633 | |
634 expect(args.length).toBe(4); | |
635 expect(args).toEqual(['do', 're', 'me', 'fa']); | |
636 })); | |
637 }); | |
638 }); | |
639 }); | |
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 } | |
1329 | |
1330 @NgFilter(name: 'listHead') | |
1331 class _ListHeadFilter { | |
1332 Logger logger; | |
1333 _ListHeadFilter(Logger this.logger); | |
1334 call(list, head) { | |
1335 logger('listHead'); | |
1336 return [head]..addAll(list); | |
1337 } | |
1338 } | |
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 } | |
OLD | NEW |