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

Side by Side Diff: third_party/pkg/angular/test/change_detection/watch_group_spec.dart

Issue 257423008: Update all Angular libs (run update_all.sh). (Closed) Base URL: https://dart.googlecode.com/svn/branches/bleeding_edge/dart
Patch Set: Created 6 years, 8 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 watch_group_spec; 1 library watch_group_spec;
2 2
3 import '../_specs.dart'; 3 import '../_specs.dart';
4 import 'dart:collection';
4 import 'package:angular/change_detection/watch_group.dart'; 5 import 'package:angular/change_detection/watch_group.dart';
5 import 'package:angular/change_detection/dirty_checking_change_detector.dart'; 6 import 'package:angular/change_detection/dirty_checking_change_detector.dart';
7 import 'package:angular/change_detection/dirty_checking_change_detector_dynamic. dart';
6 import 'dirty_checking_change_detector_spec.dart' hide main; 8 import 'dirty_checking_change_detector_spec.dart' hide main;
7 9 import 'package:angular/core/parser/parser_dynamic.dart' show DynamicClosureMap;
8 main() => describe('WatchGroup', () { 10
9 var context; 11 class TestData {
10 var watchGrp; 12 sub1(a, {b: 0}) => a - b;
11 DirtyCheckingChangeDetector changeDetector; 13 sub2({a: 0, b: 0}) => a - b;
12 Logger logger; 14 }
13 15
14 AST parse(String expression) { 16 void main() {
15 var currentAST = new ContextReferenceAST(); 17 describe('WatchGroup', () {
16 expression.split('.').forEach((name) { 18 var context;
17 currentAST = new FieldReadAST(currentAST, name); 19 var watchGrp;
20 DirtyCheckingChangeDetector changeDetector;
21 Logger logger;
22 Parser parser;
23 ExpressionVisitor visitor;
24
25 beforeEach(inject((Logger _logger, Parser _parser) {
26 context = {};
27 var getterFactory = new DynamicFieldGetterFactory();
28 changeDetector = new DirtyCheckingChangeDetector(getterFactory);
29 watchGrp = new RootWatchGroup(getterFactory, changeDetector, context);
30 visitor = new ExpressionVisitor(new DynamicClosureMap());
31 logger = _logger;
32 parser = _parser;
33 }));
34
35 AST parse(String expression) => visitor.visit(parser(expression));
36
37 eval(String expression, [evalContext]) {
38 AST ast = parse(expression);
39
40 if (evalContext == null) evalContext = context;
41 WatchGroup group = watchGrp.newGroup(evalContext);
42
43 List log = [];
44 Watch watch = group.watch(ast, (v, p) => log.add(v));
45
46 watchGrp.detectChanges();
47 group.remove();
48
49 if (log.isEmpty) {
50 throw new StateError('Expression <$expression> was not evaluated');
51 } else if (log.length > 1) {
52 throw new StateError('Expression <$expression> produced too many values: $log');
53 } else {
54 return log.first;
55 }
56 }
57
58 expectOrder(list) {
59 logger.clear();
60 watchGrp.detectChanges(); // Clear the initial queue
61 logger.clear();
62 watchGrp.detectChanges();
63 expect(logger).toEqual(list);
64 }
65
66 beforeEach(inject((Logger _logger) {
67 context = {};
68 var getterFactory = new DynamicFieldGetterFactory();
69 changeDetector = new DirtyCheckingChangeDetector(getterFactory);
70 watchGrp = new RootWatchGroup(getterFactory, changeDetector, context);
71 logger = _logger;
72 }));
73
74 describe('watch lifecycle', () {
75 it('should prevent reaction fn on removed', () {
76 context['a'] = 'hello';
77 var watch ;
78 watchGrp.watch(parse('a'), (v, p) {
79 logger('removed');
80 watch.remove();
81 });
82 watch = watchGrp.watch(parse('a'), (v, p) => logger(v));
83 watchGrp.detectChanges();
84 expect(logger).toEqual(['removed']);
85 });
18 }); 86 });
19 return currentAST; 87
20 } 88 describe('property chaining', () {
21 89 it('should read property', () {
22 expectOrder(list) { 90 context['a'] = 'hello';
23 logger.clear(); 91
24 watchGrp.detectChanges(); // Clear the initial queue 92 // should fire on initial adding
25 logger.clear(); 93 expect(watchGrp.fieldCost).toEqual(0);
26 watchGrp.detectChanges(); 94 var watch = watchGrp.watch(parse('a'), (v, p) => logger(v));
27 expect(logger).toEqual(list); 95 expect(watch.expression).toEqual('a');
28 } 96 expect(watchGrp.fieldCost).toEqual(1);
29 97 watchGrp.detectChanges();
30 beforeEach(inject((Logger _logger) { 98 expect(logger).toEqual(['hello']);
31 context = {}; 99
32 changeDetector = new DirtyCheckingChangeDetector(new GetterCache({})); 100 // make sore no new changes are logged on extra detectChanges
33 watchGrp = new RootWatchGroup(changeDetector, context); 101 watchGrp.detectChanges();
34 logger = _logger; 102 expect(logger).toEqual(['hello']);
35 })); 103
36 104 // Should detect value change
37 describe('watch lifecycle', () { 105 context['a'] = 'bye';
38 it('should prevent reaction fn on removed', () { 106 watchGrp.detectChanges();
39 context['a'] = 'hello'; 107 expect(logger).toEqual(['hello', 'bye']);
40 var watch ; 108
41 watchGrp.watch(parse('a'), (v, p) { 109 // should cleanup after itself
42 logger('removed');
43 watch.remove(); 110 watch.remove();
44 }); 111 expect(watchGrp.fieldCost).toEqual(0);
45 watch = watchGrp.watch(parse('a'), (v, p) => logger(v)); 112 context['a'] = 'cant see me';
46 watchGrp.detectChanges(); 113 watchGrp.detectChanges();
47 expect(logger).toEqual(['removed']); 114 expect(logger).toEqual(['hello', 'bye']);
115 });
116
117 describe('sequence mutations and ref changes', () {
118 it('should handle a simultaneous map mutation and reference change', () {
119 context['a'] = context['b'] = {1: 10, 2: 20};
120 var watchA = watchGrp.watch(new CollectionAST(parse('a')), (v, p) => l ogger(v));
121 var watchB = watchGrp.watch(new CollectionAST(parse('b')), (v, p) => l ogger(v));
122
123 watchGrp.detectChanges();
124 expect(logger.length).toEqual(2);
125 expect(logger[0], toEqualMapRecord(
126 map: ['1', '2'],
127 previous: ['1', '2']));
128 expect(logger[1], toEqualMapRecord(
129 map: ['1', '2'],
130 previous: ['1', '2']));
131 logger.clear();
132
133 // context['a'] is set to a copy with an addition.
134 context['a'] = new Map.from(context['a'])..[3] = 30;
135 // context['b'] still has the original collection. We'll mutate it.
136 context['b'].remove(1);
137
138 watchGrp.detectChanges();
139 expect(logger.length).toEqual(2);
140 expect(logger[0], toEqualMapRecord(
141 map: ['1', '2', '3[null -> 30]'],
142 previous: ['1', '2'],
143 additions: ['3[null -> 30]']));
144 expect(logger[1], toEqualMapRecord(
145 map: ['2'],
146 previous: ['1[10 -> null]', '2'],
147 removals: ['1[10 -> null]']));
148 logger.clear();
149 });
150
151 it('should handle a simultaneous list mutation and reference change', () {
152 context['a'] = context['b'] = [0, 1];
153 var watchA = watchGrp.watch(new CollectionAST(parse('a')), (v, p) => l ogger(v));
154 var watchB = watchGrp.watch(new CollectionAST(parse('b')), (v, p) => l ogger(v));
155
156 watchGrp.detectChanges();
157 expect(logger.length).toEqual(2);
158 expect(logger[0], toEqualCollectionRecord(
159 collection: ['0', '1'],
160 previous: ['0', '1'],
161 additions: [], moves: [], removals: []));
162 expect(logger[1], toEqualCollectionRecord(
163 collection: ['0', '1'],
164 previous: ['0', '1'],
165 additions: [], moves: [], removals: []));
166 logger.clear();
167
168 // context['a'] is set to a copy with an addition.
169 context['a'] = context['a'].toList()..add(2);
170 // context['b'] still has the original collection. We'll mutate it.
171 context['b'].remove(0);
172
173 watchGrp.detectChanges();
174 expect(logger.length).toEqual(2);
175 expect(logger[0], toEqualCollectionRecord(
176 collection: ['0', '1', '2[null -> 2]'],
177 previous: ['0', '1'],
178 additions: ['2[null -> 2]'],
179 moves: [],
180 removals: []));
181 expect(logger[1], toEqualCollectionRecord(
182 collection: ['1[1 -> 0]'],
183 previous: ['0[0 -> null]', '1[1 -> 0]'],
184 additions: [],
185 moves: ['1[1 -> 0]'],
186 removals: ['0[0 -> null]']));
187 logger.clear();
188 });
189
190 it('should work correctly with UnmodifiableListView', () {
191 context['a'] = new UnmodifiableListView([0, 1]);
192 var watch = watchGrp.watch(new CollectionAST(parse('a')), (v, p) => lo gger(v));
193
194 watchGrp.detectChanges();
195 expect(logger.length).toEqual(1);
196 expect(logger[0], toEqualCollectionRecord(
197 collection: ['0', '1'],
198 previous: ['0', '1']));
199 logger.clear();
200
201 context['a'] = new UnmodifiableListView([1, 0]);
202
203 watchGrp.detectChanges();
204 expect(logger.length).toEqual(1);
205 expect(logger[0], toEqualCollectionRecord(
206 collection: ['1[1 -> 0]', '0[0 -> 1]'],
207 previous: ['0[0 -> 1]', '1[1 -> 0]'],
208 moves: ['1[1 -> 0]', '0[0 -> 1]']));
209 logger.clear();
210 });
211
212 });
213
214 it('should read property chain', () {
215 context['a'] = {'b': 'hello'};
216
217 // should fire on initial adding
218 expect(watchGrp.fieldCost).toEqual(0);
219 expect(changeDetector.count).toEqual(0);
220 var watch = watchGrp.watch(parse('a.b'), (v, p) => logger(v));
221 expect(watch.expression).toEqual('a.b');
222 expect(watchGrp.fieldCost).toEqual(2);
223 expect(changeDetector.count).toEqual(2);
224 watchGrp.detectChanges();
225 expect(logger).toEqual(['hello']);
226
227 // make sore no new changes are logged on extra detectChanges
228 watchGrp.detectChanges();
229 expect(logger).toEqual(['hello']);
230
231 // make sure no changes or logged when intermediary object changes
232 context['a'] = {'b': 'hello'};
233 watchGrp.detectChanges();
234 expect(logger).toEqual(['hello']);
235
236 // Should detect value change
237 context['a'] = {'b': 'hello2'};
238 watchGrp.detectChanges();
239 expect(logger).toEqual(['hello', 'hello2']);
240
241 // Should detect value change
242 context['a']['b'] = 'bye';
243 watchGrp.detectChanges();
244 expect(logger).toEqual(['hello', 'hello2', 'bye']);
245
246 // should cleanup after itself
247 watch.remove();
248 expect(watchGrp.fieldCost).toEqual(0);
249 context['a']['b'] = 'cant see me';
250 watchGrp.detectChanges();
251 expect(logger).toEqual(['hello', 'hello2', 'bye']);
252 });
253
254 it('should reuse handlers', () {
255 var user1 = {'first': 'misko', 'last': 'hevery'};
256 var user2 = {'first': 'misko', 'last': 'Hevery'};
257
258 context['user'] = user1;
259
260 // should fire on initial adding
261 expect(watchGrp.fieldCost).toEqual(0);
262 var watch = watchGrp.watch(parse('user'), (v, p) => logger(v));
263 var watchFirst = watchGrp.watch(parse('user.first'), (v, p) => logger(v) );
264 var watchLast = watchGrp.watch(parse('user.last'), (v, p) => logger(v));
265 expect(watchGrp.fieldCost).toEqual(3);
266
267 watchGrp.detectChanges();
268 expect(logger).toEqual([user1, 'misko', 'hevery']);
269 logger.clear();
270
271 context['user'] = user2;
272 watchGrp.detectChanges();
273 expect(logger).toEqual([user2, 'Hevery']);
274
275
276 watch.remove();
277 expect(watchGrp.fieldCost).toEqual(3);
278
279 watchFirst.remove();
280 expect(watchGrp.fieldCost).toEqual(2);
281
282 watchLast.remove();
283 expect(watchGrp.fieldCost).toEqual(0);
284
285 expect(() => watch.remove()).toThrow('Already deleted!');
286 });
287
288 it('should eval pure FunctionApply', () {
289 context['a'] = {'val': 1};
290
291 FunctionApply fn = new LoggingFunctionApply(logger);
292 var watch = watchGrp.watch(
293 new PureFunctionAST('add', fn, [parse('a.val')]),
294 (v, p) => logger(v)
295 );
296
297 // a; a.val; b; b.val;
298 expect(watchGrp.fieldCost).toEqual(2);
299 // add
300 expect(watchGrp.evalCost).toEqual(1);
301
302 watchGrp.detectChanges();
303 expect(logger).toEqual([[1], null]);
304 logger.clear();
305
306 context['a'] = {'val': 2};
307 watchGrp.detectChanges();
308 expect(logger).toEqual([[2]]);
309 });
310
311
312 it('should eval pure function', () {
313 context['a'] = {'val': 1};
314 context['b'] = {'val': 2};
315
316 var watch = watchGrp.watch(
317 new PureFunctionAST('add',
318 (a, b) { logger('+'); return a+b; },
319 [parse('a.val'), parse('b.val')]
320 ),
321 (v, p) => logger(v)
322 );
323
324 // a; a.val; b; b.val;
325 expect(watchGrp.fieldCost).toEqual(4);
326 // add
327 expect(watchGrp.evalCost).toEqual(1);
328
329 watchGrp.detectChanges();
330 expect(logger).toEqual(['+', 3]);
331
332 // extra checks should not trigger functions
333 watchGrp.detectChanges();
334 watchGrp.detectChanges();
335 expect(logger).toEqual(['+', 3]);
336
337 // multiple arg changes should only trigger function once.
338 context['a']['val'] = 3;
339 context['b']['val'] = 4;
340
341 watchGrp.detectChanges();
342 expect(logger).toEqual(['+', 3, '+', 7]);
343
344 watch.remove();
345 expect(watchGrp.fieldCost).toEqual(0);
346 expect(watchGrp.evalCost).toEqual(0);
347
348 context['a']['val'] = 0;
349 context['b']['val'] = 0;
350
351 watchGrp.detectChanges();
352 expect(logger).toEqual(['+', 3, '+', 7]);
353 });
354
355
356 it('should eval closure', () {
357 context['a'] = {'val': 1};
358 context['b'] = {'val': 2};
359 var innerState = 1;
360
361 var watch = watchGrp.watch(
362 new ClosureAST('sum',
363 (a, b) { logger('+'); return innerState+a+b; },
364 [parse('a.val'), parse('b.val')]
365 ),
366 (v, p) => logger(v)
367 );
368
369 // a; a.val; b; b.val;
370 expect(watchGrp.fieldCost).toEqual(4);
371 // add
372 expect(watchGrp.evalCost).toEqual(1);
373
374 watchGrp.detectChanges();
375 expect(logger).toEqual(['+', 4]);
376
377 // extra checks should trigger closures
378 watchGrp.detectChanges();
379 watchGrp.detectChanges();
380 expect(logger).toEqual(['+', 4, '+', '+']);
381 logger.clear();
382
383 // multiple arg changes should only trigger function once.
384 context['a']['val'] = 3;
385 context['b']['val'] = 4;
386
387 watchGrp.detectChanges();
388 expect(logger).toEqual(['+', 8]);
389 logger.clear();
390
391 // inner state change should only trigger function once.
392 innerState = 2;
393
394 watchGrp.detectChanges();
395 expect(logger).toEqual(['+', 9]);
396 logger.clear();
397
398 watch.remove();
399 expect(watchGrp.fieldCost).toEqual(0);
400 expect(watchGrp.evalCost).toEqual(0);
401
402 context['a']['val'] = 0;
403 context['b']['val'] = 0;
404
405 watchGrp.detectChanges();
406 expect(logger).toEqual([]);
407 });
408
409
410 it('should eval chained pure function', () {
411 context['a'] = {'val': 1};
412 context['b'] = {'val': 2};
413 context['c'] = {'val': 3};
414
415 var a_plus_b = new PureFunctionAST('add1',
416 (a, b) { logger('$a+$b'); return a + b; },
417 [parse('a.val'), parse('b.val')]);
418
419 var a_plus_b_plus_c = new PureFunctionAST('add2',
420 (b, c) { logger('$b+$c'); return b + c; },
421 [a_plus_b, parse('c.val')]);
422
423 var watch = watchGrp.watch(a_plus_b_plus_c, (v, p) => logger(v));
424
425 // a; a.val; b; b.val; c; c.val;
426 expect(watchGrp.fieldCost).toEqual(6);
427 // add
428 expect(watchGrp.evalCost).toEqual(2);
429
430 watchGrp.detectChanges();
431 expect(logger).toEqual(['1+2', '3+3', 6]);
432 logger.clear();
433
434 // extra checks should not trigger functions
435 watchGrp.detectChanges();
436 watchGrp.detectChanges();
437 expect(logger).toEqual([]);
438 logger.clear();
439
440 // multiple arg changes should only trigger function once.
441 context['a']['val'] = 3;
442 context['b']['val'] = 4;
443 context['c']['val'] = 5;
444 watchGrp.detectChanges();
445 expect(logger).toEqual(['3+4', '7+5', 12]);
446 logger.clear();
447
448 context['a']['val'] = 9;
449 watchGrp.detectChanges();
450 expect(logger).toEqual(['9+4', '13+5', 18]);
451 logger.clear();
452
453 context['c']['val'] = 9;
454 watchGrp.detectChanges();
455 expect(logger).toEqual(['13+9', 22]);
456 logger.clear();
457
458
459 watch.remove();
460 expect(watchGrp.fieldCost).toEqual(0);
461 expect(watchGrp.evalCost).toEqual(0);
462
463 context['a']['val'] = 0;
464 context['b']['val'] = 0;
465
466 watchGrp.detectChanges();
467 expect(logger).toEqual([]);
468 });
469
470
471 it('should eval closure', () {
472 var obj;
473 obj = {
474 'methodA': (arg1) {
475 logger('methodA($arg1) => ${obj['valA']}');
476 return obj['valA'];
477 },
478 'valA': 'A'
479 };
480 context['obj'] = obj;
481 context['arg0'] = 1;
482
483 var watch = watchGrp.watch(
484 new MethodAST(parse('obj'), 'methodA', [parse('arg0')]),
485 (v, p) => logger(v)
486 );
487
488 // obj, arg0;
489 expect(watchGrp.fieldCost).toEqual(2);
490 // methodA()
491 expect(watchGrp.evalCost).toEqual(1);
492
493 watchGrp.detectChanges();
494 expect(logger).toEqual(['methodA(1) => A', 'A']);
495 logger.clear();
496
497 watchGrp.detectChanges();
498 watchGrp.detectChanges();
499 expect(logger).toEqual(['methodA(1) => A', 'methodA(1) => A']);
500 logger.clear();
501
502 obj['valA'] = 'B';
503 context['arg0'] = 2;
504
505 watchGrp.detectChanges();
506 expect(logger).toEqual(['methodA(2) => B', 'B']);
507 logger.clear();
508
509 watch.remove();
510 expect(watchGrp.fieldCost).toEqual(0);
511 expect(watchGrp.evalCost).toEqual(0);
512
513 obj['valA'] = 'C';
514 context['arg0'] = 3;
515
516 watchGrp.detectChanges();
517 expect(logger).toEqual([]);
518 });
519
520
521 it('should eval method', () {
522 var obj = new MyClass(logger);
523 obj.valA = 'A';
524 context['obj'] = obj;
525 context['arg0'] = 1;
526
527 var watch = watchGrp.watch(
528 new MethodAST(parse('obj'), 'methodA', [parse('arg0')]),
529 (v, p) => logger(v)
530 );
531
532 // obj, arg0;
533 expect(watchGrp.fieldCost).toEqual(2);
534 // methodA()
535 expect(watchGrp.evalCost).toEqual(1);
536
537 watchGrp.detectChanges();
538 expect(logger).toEqual(['methodA(1) => A', 'A']);
539 logger.clear();
540
541 watchGrp.detectChanges();
542 watchGrp.detectChanges();
543 expect(logger).toEqual(['methodA(1) => A', 'methodA(1) => A']);
544 logger.clear();
545
546 obj.valA = 'B';
547 context['arg0'] = 2;
548
549 watchGrp.detectChanges();
550 expect(logger).toEqual(['methodA(2) => B', 'B']);
551 logger.clear();
552
553 watch.remove();
554 expect(watchGrp.fieldCost).toEqual(0);
555 expect(watchGrp.evalCost).toEqual(0);
556
557 obj.valA = 'C';
558 context['arg0'] = 3;
559
560 watchGrp.detectChanges();
561 expect(logger).toEqual([]);
562 });
563
564 it('should eval method chain', () {
565 var obj1 = new MyClass(logger);
566 var obj2 = new MyClass(logger);
567 obj1.valA = obj2;
568 obj2.valA = 'A';
569 context['obj'] = obj1;
570 context['arg0'] = 0;
571 context['arg1'] = 1;
572
573 // obj.methodA(arg0)
574 var ast = new MethodAST(parse('obj'), 'methodA', [parse('arg0')]);
575 ast = new MethodAST(ast, 'methodA', [parse('arg1')]);
576 var watch = watchGrp.watch(ast, (v, p) => logger(v));
577
578 // obj, arg0, arg1;
579 expect(watchGrp.fieldCost).toEqual(3);
580 // methodA(), methodA()
581 expect(watchGrp.evalCost).toEqual(2);
582
583 watchGrp.detectChanges();
584 expect(logger).toEqual(['methodA(0) => MyClass', 'methodA(1) => A', 'A'] );
585 logger.clear();
586
587 watchGrp.detectChanges();
588 watchGrp.detectChanges();
589 expect(logger).toEqual(['methodA(0) => MyClass', 'methodA(1) => A',
590 'methodA(0) => MyClass', 'methodA(1) => A']);
591 logger.clear();
592
593 obj2.valA = 'B';
594 context['arg0'] = 10;
595 context['arg1'] = 11;
596
597 watchGrp.detectChanges();
598 expect(logger).toEqual(['methodA(10) => MyClass', 'methodA(11) => B', 'B ']);
599 logger.clear();
600
601 watch.remove();
602 expect(watchGrp.fieldCost).toEqual(0);
603 expect(watchGrp.evalCost).toEqual(0);
604
605 obj2.valA = 'C';
606 context['arg0'] = 20;
607 context['arg1'] = 21;
608
609 watchGrp.detectChanges();
610 expect(logger).toEqual([]);
611 });
612
613 it('should not return null when evaling method first time', () {
614 context['text'] ='abc';
615 var ast = new MethodAST(parse('text'), 'toUpperCase', []);
616 var watch = watchGrp.watch(ast, (v, p) => logger(v));
617
618 watchGrp.detectChanges();
619 expect(logger).toEqual(['ABC']);
620 });
621
622 it('should not eval a function if registered during reaction', () {
623 context['text'] ='abc';
624 var ast = new MethodAST(parse('text'), 'toLowerCase', []);
625 var watch = watchGrp.watch(ast, (v, p) {
626 var ast = new MethodAST(parse('text'), 'toUpperCase', []);
627 watchGrp.watch(ast, (v, p) {
628 logger(v);
629 });
630 });
631
632 watchGrp.detectChanges();
633 watchGrp.detectChanges();
634 expect(logger).toEqual(['ABC']);
635 });
636
637
638 it('should eval function eagerly when registered during reaction', () {
639 var fn = (arg) { logger('fn($arg)'); return arg; };
640 context['obj'] = {'fn': fn};
641 context['arg1'] = 'OUT';
642 context['arg2'] = 'IN';
643 var ast = new MethodAST(parse('obj'), 'fn', [parse('arg1')]);
644 var watch = watchGrp.watch(ast, (v, p) {
645 var ast = new MethodAST(parse('obj'), 'fn', [parse('arg2')]);
646 watchGrp.watch(ast, (v, p) {
647 logger('reaction: $v');
648 });
649 });
650
651 expect(logger).toEqual([]);
652 watchGrp.detectChanges();
653 expect(logger).toEqual(['fn(OUT)', 'fn(IN)', 'reaction: IN']);
654 logger.clear();
655 watchGrp.detectChanges();
656 expect(logger).toEqual(['fn(OUT)', 'fn(IN)']);
657 });
658
659
660 it('should read constant', () {
661 // should fire on initial adding
662 expect(watchGrp.fieldCost).toEqual(0);
663 var watch = watchGrp.watch(new ConstantAST(123), (v, p) => logger(v));
664 expect(watch.expression).toEqual('123');
665 expect(watchGrp.fieldCost).toEqual(0);
666 watchGrp.detectChanges();
667 expect(logger).toEqual([123]);
668
669 // make sore no new changes are logged on extra detectChanges
670 watchGrp.detectChanges();
671 expect(logger).toEqual([123]);
672 });
673
674 it('should wrap iterable in ObservableList', () {
675 context['list'] = [];
676 var watch = watchGrp.watch(new CollectionAST(parse('list')), (v, p) => l ogger(v));
677
678 expect(watchGrp.fieldCost).toEqual(1);
679 expect(watchGrp.collectionCost).toEqual(1);
680 expect(watchGrp.evalCost).toEqual(0);
681
682 watchGrp.detectChanges();
683 expect(logger.length).toEqual(1);
684 expect(logger[0], toEqualCollectionRecord(
685 collection: [],
686 additions: [],
687 moves: [],
688 removals: []));
689 logger.clear();
690
691 context['list'] = [1];
692 watchGrp.detectChanges();
693 expect(logger.length).toEqual(1);
694 expect(logger[0], toEqualCollectionRecord(
695 collection: ['1[null -> 0]'],
696 additions: ['1[null -> 0]'],
697 moves: [],
698 removals: []));
699 logger.clear();
700
701 watch.remove();
702 expect(watchGrp.fieldCost).toEqual(0);
703 expect(watchGrp.collectionCost).toEqual(0);
704 expect(watchGrp.evalCost).toEqual(0);
705 });
706
707 it('should watch literal arrays made of expressions', () {
708 context['a'] = 1;
709 var ast = new CollectionAST(
710 new PureFunctionAST('[a]', new ArrayFn(), [parse('a')])
711 );
712 var watch = watchGrp.watch(ast, (v, p) => logger(v));
713 watchGrp.detectChanges();
714 expect(logger[0], toEqualCollectionRecord(
715 collection: ['1[null -> 0]'],
716 additions: ['1[null -> 0]'],
717 moves: [],
718 removals: []));
719 logger.clear();
720
721 context['a'] = 2;
722 watchGrp.detectChanges();
723 expect(logger[0], toEqualCollectionRecord(
724 collection: ['2[null -> 0]'],
725 previous: ['1[0 -> null]'],
726 additions: ['2[null -> 0]'],
727 moves: [],
728 removals: ['1[0 -> null]']));
729 logger.clear();
730 });
731
732 it('should watch pure function whose result goes to pure function', () {
733 context['a'] = 1;
734 var ast = new PureFunctionAST(
735 '-',
736 (v) => -v,
737 [new PureFunctionAST('++', (v) => v + 1, [parse('a')])]
738 );
739 var watch = watchGrp.watch(ast, (v, p) => logger(v));
740
741 expect(watchGrp.detectChanges()).not.toBe(null);
742 expect(logger).toEqual([-2]);
743 logger.clear();
744
745 context['a'] = 2;
746 expect(watchGrp.detectChanges()).not.toBe(null);
747 expect(logger).toEqual([-3]);
748 });
48 }); 749 });
750
751 describe('evaluation', () {
752 it('should support simple literals', () {
753 expect(eval('42')).toBe(42);
754 expect(eval('87')).toBe(87);
755 });
756
757 it('should support context access', () {
758 context['x'] = 42;
759 expect(eval('x')).toBe(42);
760 context['y'] = 87;
761 expect(eval('y')).toBe(87);
762 });
763
764 it('should support custom context', () {
765 expect(eval('x', {'x': 42})).toBe(42);
766 expect(eval('x', {'x': 87})).toBe(87);
767 });
768
769 it('should support named arguments for scope calls', () {
770 var data = new TestData();
771 expect(eval("sub1(1)", data)).toEqual(1);
772 expect(eval("sub1(3, b: 2)", data)).toEqual(1);
773
774 expect(eval("sub2()", data)).toEqual(0);
775 expect(eval("sub2(a: 3)", data)).toEqual(3);
776 expect(eval("sub2(a: 3, b: 2)", data)).toEqual(1);
777 expect(eval("sub2(b: 4)", data)).toEqual(-4);
778 });
779
780 it('should support named arguments for scope calls (map)', () {
781 context["sub1"] = (a, {b: 0}) => a - b;
782 expect(eval("sub1(1)")).toEqual(1);
783 expect(eval("sub1(3, b: 2)")).toEqual(1);
784
785 context["sub2"] = ({a: 0, b: 0}) => a - b;
786 expect(eval("sub2()")).toEqual(0);
787 expect(eval("sub2(a: 3)")).toEqual(3);
788 expect(eval("sub2(a: 3, b: 2)")).toEqual(1);
789 expect(eval("sub2(b: 4)")).toEqual(-4);
790 });
791
792 it('should support named arguments for member calls', () {
793 context['o'] = new TestData();
794 expect(eval("o.sub1(1)")).toEqual(1);
795 expect(eval("o.sub1(3, b: 2)")).toEqual(1);
796
797 expect(eval("o.sub2()")).toEqual(0);
798 expect(eval("o.sub2(a: 3)")).toEqual(3);
799 expect(eval("o.sub2(a: 3, b: 2)")).toEqual(1);
800 expect(eval("o.sub2(b: 4)")).toEqual(-4);
801 });
802
803 it('should support named arguments for member calls (map)', () {
804 context['o'] = {
805 'sub1': (a, {b: 0}) => a - b,
806 'sub2': ({a: 0, b: 0}) => a - b
807 };
808 expect(eval("o.sub1(1)")).toEqual(1);
809 expect(eval("o.sub1(3, b: 2)")).toEqual(1);
810
811 expect(eval("o.sub2()")).toEqual(0);
812 expect(eval("o.sub2(a: 3)")).toEqual(3);
813 expect(eval("o.sub2(a: 3, b: 2)")).toEqual(1);
814 expect(eval("o.sub2(b: 4)")).toEqual(-4);
815 });
816 });
817
818 describe('child group', () {
819 it('should remove all field watches in group and group\'s children', () {
820 watchGrp.watch(parse('a'), (v, p) => logger('0a'));
821 var child1a = watchGrp.newGroup(new PrototypeMap(context));
822 var child1b = watchGrp.newGroup(new PrototypeMap(context));
823 var child2 = child1a.newGroup(new PrototypeMap(context));
824 child1a.watch(parse('a'), (v, p) => logger('1a'));
825 child1b.watch(parse('a'), (v, p) => logger('1b'));
826 watchGrp.watch(parse('a'), (v, p) => logger('0A'));
827 child1a.watch(parse('a'), (v, p) => logger('1A'));
828 child2.watch(parse('a'), (v, p) => logger('2A'));
829
830 // flush initial reaction functions
831 expect(watchGrp.detectChanges()).toEqual(6);
832 // expect(logger).toEqual(['0a', '0A', '1a', '1A', '2A', '1b']);
833 expect(logger).toEqual(['0a', '1a', '1b', '0A', '1A', '2A']); // we go b y registration order
834 expect(watchGrp.fieldCost).toEqual(1);
835 expect(watchGrp.totalFieldCost).toEqual(4);
836 logger.clear();
837
838 context['a'] = 1;
839 expect(watchGrp.detectChanges()).toEqual(6);
840 expect(logger).toEqual(['0a', '0A', '1a', '1A', '2A', '1b']); // we go b y group order
841 logger.clear();
842
843 context['a'] = 2;
844 child1a.remove(); // should also remove child2
845 expect(watchGrp.detectChanges()).toEqual(3);
846 expect(logger).toEqual(['0a', '0A', '1b']);
847 expect(watchGrp.fieldCost).toEqual(1);
848 expect(watchGrp.totalFieldCost).toEqual(2);
849 });
850
851 it('should remove all method watches in group and group\'s children', () {
852 context['my'] = new MyClass(logger);
853 AST countMethod = new MethodAST(parse('my'), 'count', []);
854 watchGrp.watch(countMethod, (v, p) => logger('0a'));
855 expectOrder(['0a']);
856
857 var child1a = watchGrp.newGroup(new PrototypeMap(context));
858 var child1b = watchGrp.newGroup(new PrototypeMap(context));
859 var child2 = child1a.newGroup(new PrototypeMap(context));
860 var child3 = child2.newGroup(new PrototypeMap(context));
861 child1a.watch(countMethod, (v, p) => logger('1a'));
862 expectOrder(['0a', '1a']);
863 child1b.watch(countMethod, (v, p) => logger('1b'));
864 expectOrder(['0a', '1a', '1b']);
865 watchGrp.watch(countMethod, (v, p) => logger('0A'));
866 expectOrder(['0a', '0A', '1a', '1b']);
867 child1a.watch(countMethod, (v, p) => logger('1A'));
868 expectOrder(['0a', '0A', '1a', '1A', '1b']);
869 child2.watch(countMethod, (v, p) => logger('2A'));
870 expectOrder(['0a', '0A', '1a', '1A', '2A', '1b']);
871 child3.watch(countMethod, (v, p) => logger('3'));
872 expectOrder(['0a', '0A', '1a', '1A', '2A', '3', '1b']);
873
874 // flush initial reaction functions
875 expect(watchGrp.detectChanges()).toEqual(7);
876 expectOrder(['0a', '0A', '1a', '1A', '2A', '3', '1b']);
877
878 child1a.remove(); // should also remove child2 and child 3
879 expect(watchGrp.detectChanges()).toEqual(3);
880 expectOrder(['0a', '0A', '1b']);
881 });
882
883 it('should add watches within its own group', () {
884 context['my'] = new MyClass(logger);
885 AST countMethod = new MethodAST(parse('my'), 'count', []);
886 var ra = watchGrp.watch(countMethod, (v, p) => logger('a'));
887 var child = watchGrp.newGroup(new PrototypeMap(context));
888 var cb = child.watch(countMethod, (v, p) => logger('b'));
889
890 expectOrder(['a', 'b']);
891 expectOrder(['a', 'b']);
892
893 ra.remove();
894 expectOrder(['b']);
895
896 cb.remove();
897 expectOrder([]);
898
899 // TODO: add them back in wrong order, assert events in right order
900 cb = child.watch(countMethod, (v, p) => logger('b'));
901 ra = watchGrp.watch(countMethod, (v, p) => logger('a'));;
902 expectOrder(['a', 'b']);
903 });
904
905
906 it('should not call reaction function on removed group', () {
907 var log = [];
908 context['name'] = 'misko';
909 var child = watchGrp.newGroup(context);
910 watchGrp.watch(parse('name'), (v, _) {
911 log.add('root $v');
912 if (v == 'destroy') {
913 child.remove();
914 }
915 });
916 child.watch(parse('name'), (v, _) => log.add('child $v'));
917 watchGrp.detectChanges();
918 expect(log).toEqual(['root misko', 'child misko']);
919 log.clear();
920
921 context['name'] = 'destroy';
922 watchGrp.detectChanges();
923 expect(log).toEqual(['root destroy']);
924 });
925
926
927
928 it('should watch children', () {
929 var childContext = new PrototypeMap(context);
930 context['a'] = 'OK';
931 context['b'] = 'BAD';
932 childContext['b'] = 'OK';
933 watchGrp.watch(parse('a'), (v, p) => logger(v));
934 watchGrp.newGroup(childContext).watch(parse('b'), (v, p) => logger(v));
935
936 watchGrp.detectChanges();
937 expect(logger).toEqual(['OK', 'OK']);
938 logger.clear();
939
940 context['a'] = 'A';
941 childContext['b'] = 'B';
942
943 watchGrp.detectChanges();
944 expect(logger).toEqual(['A', 'B']);
945 logger.clear();
946 });
947 });
948
49 }); 949 });
50 950 }
51 describe('property chaining', () {
52 it('should read property', () {
53 context['a'] = 'hello';
54
55 // should fire on initial adding
56 expect(watchGrp.fieldCost).toEqual(0);
57 var watch = watchGrp.watch(parse('a'), (v, p) => logger(v));
58 expect(watch.expression).toEqual('a');
59 expect(watchGrp.fieldCost).toEqual(1);
60 watchGrp.detectChanges();
61 expect(logger).toEqual(['hello']);
62
63 // make sore no new changes are logged on extra detectChanges
64 watchGrp.detectChanges();
65 expect(logger).toEqual(['hello']);
66
67 // Should detect value change
68 context['a'] = 'bye';
69 watchGrp.detectChanges();
70 expect(logger).toEqual(['hello', 'bye']);
71
72 // should cleanup after itself
73 watch.remove();
74 expect(watchGrp.fieldCost).toEqual(0);
75 context['a'] = 'cant see me';
76 watchGrp.detectChanges();
77 expect(logger).toEqual(['hello', 'bye']);
78 });
79
80 it('should read property chain', () {
81 context['a'] = {'b': 'hello'};
82
83 // should fire on initial adding
84 expect(watchGrp.fieldCost).toEqual(0);
85 expect(changeDetector.count).toEqual(0);
86 var watch = watchGrp.watch(parse('a.b'), (v, p) => logger(v));
87 expect(watch.expression).toEqual('a.b');
88 expect(watchGrp.fieldCost).toEqual(2);
89 expect(changeDetector.count).toEqual(2);
90 watchGrp.detectChanges();
91 expect(logger).toEqual(['hello']);
92
93 // make sore no new changes are logged on extra detectChanges
94 watchGrp.detectChanges();
95 expect(logger).toEqual(['hello']);
96
97 // make sure no changes or logged when intermediary object changes
98 context['a'] = {'b': 'hello'};
99 watchGrp.detectChanges();
100 expect(logger).toEqual(['hello']);
101
102 // Should detect value change
103 context['a'] = {'b': 'hello2'};
104 watchGrp.detectChanges();
105 expect(logger).toEqual(['hello', 'hello2']);
106
107 // Should detect value change
108 context['a']['b'] = 'bye';
109 watchGrp.detectChanges();
110 expect(logger).toEqual(['hello', 'hello2', 'bye']);
111
112 // should cleanup after itself
113 watch.remove();
114 expect(watchGrp.fieldCost).toEqual(0);
115 context['a']['b'] = 'cant see me';
116 watchGrp.detectChanges();
117 expect(logger).toEqual(['hello', 'hello2', 'bye']);
118 });
119
120 it('should reuse handlers', () {
121 var user1 = {'first': 'misko', 'last': 'hevery'};
122 var user2 = {'first': 'misko', 'last': 'Hevery'};
123
124 context['user'] = user1;
125
126 // should fire on initial adding
127 expect(watchGrp.fieldCost).toEqual(0);
128 var watch = watchGrp.watch(parse('user'), (v, p) => logger(v));
129 var watchFirst = watchGrp.watch(parse('user.first'), (v, p) => logger(v));
130 var watchLast = watchGrp.watch(parse('user.last'), (v, p) => logger(v));
131 expect(watchGrp.fieldCost).toEqual(3);
132
133 watchGrp.detectChanges();
134 expect(logger).toEqual([user1, 'misko', 'hevery']);
135 logger.clear();
136
137 context['user'] = user2;
138 watchGrp.detectChanges();
139 expect(logger).toEqual([user2, 'Hevery']);
140
141
142 watch.remove();
143 expect(watchGrp.fieldCost).toEqual(3);
144
145 watchFirst.remove();
146 expect(watchGrp.fieldCost).toEqual(2);
147
148 watchLast.remove();
149 expect(watchGrp.fieldCost).toEqual(0);
150
151 expect(() => watch.remove()).toThrow('Already deleted!');
152 });
153
154 it('should eval pure FunctionApply', () {
155 context['a'] = {'val': 1};
156
157 FunctionApply fn = new LoggingFunctionApply(logger);
158 var watch = watchGrp.watch(
159 new PureFunctionAST('add', fn, [parse('a.val')]),
160 (v, p) => logger(v)
161 );
162
163 // a; a.val; b; b.val;
164 expect(watchGrp.fieldCost).toEqual(2);
165 // add
166 expect(watchGrp.evalCost).toEqual(1);
167
168 watchGrp.detectChanges();
169 expect(logger).toEqual([[1], null]);
170 logger.clear();
171
172 context['a'] = {'val': 2};
173 watchGrp.detectChanges();
174 expect(logger).toEqual([[2]]);
175 });
176
177
178 it('should eval pure function', () {
179 context['a'] = {'val': 1};
180 context['b'] = {'val': 2};
181
182 var watch = watchGrp.watch(
183 new PureFunctionAST('add',
184 (a, b) { logger('+'); return a+b; },
185 [parse('a.val'), parse('b.val')]
186 ),
187 (v, p) => logger(v)
188 );
189
190 // a; a.val; b; b.val;
191 expect(watchGrp.fieldCost).toEqual(4);
192 // add
193 expect(watchGrp.evalCost).toEqual(1);
194
195 watchGrp.detectChanges();
196 expect(logger).toEqual(['+', 3]);
197
198 // extra checks should not trigger functions
199 watchGrp.detectChanges();
200 watchGrp.detectChanges();
201 expect(logger).toEqual(['+', 3]);
202
203 // multiple arg changes should only trigger function once.
204 context['a']['val'] = 3;
205 context['b']['val'] = 4;
206
207 watchGrp.detectChanges();
208 expect(logger).toEqual(['+', 3, '+', 7]);
209
210 watch.remove();
211 expect(watchGrp.fieldCost).toEqual(0);
212 expect(watchGrp.evalCost).toEqual(0);
213
214 context['a']['val'] = 0;
215 context['b']['val'] = 0;
216
217 watchGrp.detectChanges();
218 expect(logger).toEqual(['+', 3, '+', 7]);
219 });
220
221
222 it('should eval chained pure function', () {
223 context['a'] = {'val': 1};
224 context['b'] = {'val': 2};
225 context['c'] = {'val': 3};
226
227 var a_plus_b = new PureFunctionAST('add1',
228 (a, b) { logger('$a+$b'); return a + b; },
229 [parse('a.val'), parse('b.val')]);
230
231 var a_plus_b_plus_c = new PureFunctionAST('add2',
232 (b, c) { logger('$b+$c'); return b + c; },
233 [a_plus_b, parse('c.val')]);
234
235 var watch = watchGrp.watch(a_plus_b_plus_c, (v, p) => logger(v));
236
237 // a; a.val; b; b.val; c; c.val;
238 expect(watchGrp.fieldCost).toEqual(6);
239 // add
240 expect(watchGrp.evalCost).toEqual(2);
241
242 watchGrp.detectChanges();
243 expect(logger).toEqual(['1+2', '3+3', 6]);
244 logger.clear();
245
246 // extra checks should not trigger functions
247 watchGrp.detectChanges();
248 watchGrp.detectChanges();
249 expect(logger).toEqual([]);
250 logger.clear();
251
252 // multiple arg changes should only trigger function once.
253 context['a']['val'] = 3;
254 context['b']['val'] = 4;
255 context['c']['val'] = 5;
256 watchGrp.detectChanges();
257 expect(logger).toEqual(['3+4', '7+5', 12]);
258 logger.clear();
259
260 context['a']['val'] = 9;
261 watchGrp.detectChanges();
262 expect(logger).toEqual(['9+4', '13+5', 18]);
263 logger.clear();
264
265 context['c']['val'] = 9;
266 watchGrp.detectChanges();
267 expect(logger).toEqual(['13+9', 22]);
268 logger.clear();
269
270
271 watch.remove();
272 expect(watchGrp.fieldCost).toEqual(0);
273 expect(watchGrp.evalCost).toEqual(0);
274
275 context['a']['val'] = 0;
276 context['b']['val'] = 0;
277
278 watchGrp.detectChanges();
279 expect(logger).toEqual([]);
280 });
281
282
283 it('should eval closure', () {
284 var obj;
285 obj = {
286 'methodA': (arg1) {
287 logger('methodA($arg1) => ${obj['valA']}');
288 return obj['valA'];
289 },
290 'valA': 'A'
291 };
292 context['obj'] = obj;
293 context['arg0'] = 1;
294
295 var watch = watchGrp.watch(
296 new MethodAST(parse('obj'), 'methodA', [parse('arg0')]),
297 (v, p) => logger(v)
298 );
299
300 // obj, arg0;
301 expect(watchGrp.fieldCost).toEqual(2);
302 // methodA()
303 expect(watchGrp.evalCost).toEqual(1);
304
305 watchGrp.detectChanges();
306 expect(logger).toEqual(['methodA(1) => A', 'A']);
307 logger.clear();
308
309 watchGrp.detectChanges();
310 watchGrp.detectChanges();
311 expect(logger).toEqual(['methodA(1) => A', 'methodA(1) => A']);
312 logger.clear();
313
314 obj['valA'] = 'B';
315 context['arg0'] = 2;
316
317 watchGrp.detectChanges();
318 expect(logger).toEqual(['methodA(2) => B', 'B']);
319 logger.clear();
320
321 watch.remove();
322 expect(watchGrp.fieldCost).toEqual(0);
323 expect(watchGrp.evalCost).toEqual(0);
324
325 obj['valA'] = 'C';
326 context['arg0'] = 3;
327
328 watchGrp.detectChanges();
329 expect(logger).toEqual([]);
330 });
331
332
333 it('should eval method', () {
334 var obj = new MyClass(logger);
335 obj.valA = 'A';
336 context['obj'] = obj;
337 context['arg0'] = 1;
338
339 var watch = watchGrp.watch(
340 new MethodAST(parse('obj'), 'methodA', [parse('arg0')]),
341 (v, p) => logger(v)
342 );
343
344 // obj, arg0;
345 expect(watchGrp.fieldCost).toEqual(2);
346 // methodA()
347 expect(watchGrp.evalCost).toEqual(1);
348
349 watchGrp.detectChanges();
350 expect(logger).toEqual(['methodA(1) => A', 'A']);
351 logger.clear();
352
353 watchGrp.detectChanges();
354 watchGrp.detectChanges();
355 expect(logger).toEqual(['methodA(1) => A', 'methodA(1) => A']);
356 logger.clear();
357
358 obj.valA = 'B';
359 context['arg0'] = 2;
360
361 watchGrp.detectChanges();
362 expect(logger).toEqual(['methodA(2) => B', 'B']);
363 logger.clear();
364
365 watch.remove();
366 expect(watchGrp.fieldCost).toEqual(0);
367 expect(watchGrp.evalCost).toEqual(0);
368
369 obj.valA = 'C';
370 context['arg0'] = 3;
371
372 watchGrp.detectChanges();
373 expect(logger).toEqual([]);
374 });
375
376 it('should eval method chain', () {
377 var obj1 = new MyClass(logger);
378 var obj2 = new MyClass(logger);
379 obj1.valA = obj2;
380 obj2.valA = 'A';
381 context['obj'] = obj1;
382 context['arg0'] = 0;
383 context['arg1'] = 1;
384
385 // obj.methodA(arg0)
386 var ast = new MethodAST(parse('obj'), 'methodA', [parse('arg0')]);
387 ast = new MethodAST(ast, 'methodA', [parse('arg1')]);
388 var watch = watchGrp.watch(ast, (v, p) => logger(v));
389
390 // obj, arg0, arg1;
391 expect(watchGrp.fieldCost).toEqual(3);
392 // methodA(), mothodA()
393 expect(watchGrp.evalCost).toEqual(2);
394
395 watchGrp.detectChanges();
396 expect(logger).toEqual(['methodA(0) => MyClass', 'methodA(1) => A', 'A']);
397 logger.clear();
398
399 watchGrp.detectChanges();
400 watchGrp.detectChanges();
401 expect(logger).toEqual(['methodA(0) => MyClass', 'methodA(1) => A',
402 'methodA(0) => MyClass', 'methodA(1) => A']);
403 logger.clear();
404
405 obj2.valA = 'B';
406 context['arg0'] = 10;
407 context['arg1'] = 11;
408
409 watchGrp.detectChanges();
410 expect(logger).toEqual(['methodA(10) => MyClass', 'methodA(11) => B', 'B'] );
411 logger.clear();
412
413 watch.remove();
414 expect(watchGrp.fieldCost).toEqual(0);
415 expect(watchGrp.evalCost).toEqual(0);
416
417 obj2.valA = 'C';
418 context['arg0'] = 20;
419 context['arg1'] = 21;
420
421 watchGrp.detectChanges();
422 expect(logger).toEqual([]);
423 });
424
425 it('should read connstant', () {
426 // should fire on initial adding
427 expect(watchGrp.fieldCost).toEqual(0);
428 var watch = watchGrp.watch(new ConstantAST(123), (v, p) => logger(v));
429 expect(watch.expression).toEqual('123');
430 expect(watchGrp.fieldCost).toEqual(0);
431 watchGrp.detectChanges();
432 expect(logger).toEqual([123]);
433
434 // make sore no new changes are logged on extra detectChanges
435 watchGrp.detectChanges();
436 expect(logger).toEqual([123]);
437 });
438
439 it('should wrap iterable in ObservableList', () {
440 context['list'] = [];
441 var watch = watchGrp.watch(new CollectionAST(parse('list')), (v, p) => log ger(v));
442
443 expect(watchGrp.fieldCost).toEqual(1);
444 expect(watchGrp.collectionCost).toEqual(1);
445 expect(watchGrp.evalCost).toEqual(0);
446
447 watchGrp.detectChanges();
448 expect(logger.length).toEqual(1);
449 expect(logger[0], toEqualCollectionRecord(
450 collection: [],
451 additions: [],
452 moves: [],
453 removals: []));
454 logger.clear();
455
456 context['list'] = [1];
457 watchGrp.detectChanges();
458 expect(logger.length).toEqual(1);
459 expect(logger[0], toEqualCollectionRecord(
460 collection: ['1[null -> 0]'],
461 additions: ['1[null -> 0]'],
462 moves: [],
463 removals: []));
464 logger.clear();
465
466 watch.remove();
467 expect(watchGrp.fieldCost).toEqual(0);
468 expect(watchGrp.collectionCost).toEqual(0);
469 expect(watchGrp.evalCost).toEqual(0);
470 });
471
472 it('should watch literal arrays made of expressions', () {
473 context['a'] = 1;
474 var ast = new CollectionAST(
475 new PureFunctionAST('[a]', new ArrayFn(), [parse('a')])
476 );
477 var watch = watchGrp.watch(ast, (v, p) => logger(v));
478 watchGrp.detectChanges();
479 expect(logger[0], toEqualCollectionRecord(
480 collection: ['1[null -> 0]'],
481 additions: ['1[null -> 0]'],
482 moves: [],
483 removals: []));
484 logger.clear();
485
486 context['a'] = 2;
487 watchGrp.detectChanges();
488 expect(logger[0], toEqualCollectionRecord(
489 collection: ['2[null -> 0]'],
490 additions: ['2[null -> 0]'],
491 moves: [],
492 removals: ['1[0 -> null]']));
493 logger.clear();
494 });
495
496 it('should watch pure function whose result goes to pure function', () {
497 context['a'] = 1;
498 var ast = new PureFunctionAST(
499 '-',
500 (v) => -v,
501 [new PureFunctionAST('++', (v) => v + 1, [parse('a')])]
502 );
503 var watch = watchGrp.watch(ast, (v, p) => logger(v));
504
505 expect(watchGrp.detectChanges()).not.toBe(null);
506 expect(logger).toEqual([-2]);
507 logger.clear();
508
509 context['a'] = 2;
510 expect(watchGrp.detectChanges()).not.toBe(null);
511 expect(logger).toEqual([-3]);
512 });
513 });
514
515 describe('child group', () {
516 it('should remove all field watches in group and group\'s children', () {
517 watchGrp.watch(parse('a'), (v, p) => logger('0a'));
518 var child1a = watchGrp.newGroup(new PrototypeMap(context));
519 var child1b = watchGrp.newGroup(new PrototypeMap(context));
520 var child2 = child1a.newGroup(new PrototypeMap(context));
521 child1a.watch(parse('a'), (v, p) => logger('1a'));
522 child1b.watch(parse('a'), (v, p) => logger('1b'));
523 watchGrp.watch(parse('a'), (v, p) => logger('0A'));
524 child1a.watch(parse('a'), (v, p) => logger('1A'));
525 child2.watch(parse('a'), (v, p) => logger('2A'));
526
527 // flush initial reaction functions
528 expect(watchGrp.detectChanges()).toEqual(6);
529 // expect(logger).toEqual(['0a', '0A', '1a', '1A', '2A', '1b']);
530 expect(logger).toEqual(['0a', '1a', '1b', '0A', '1A', '2A']); // we go by registration order
531 expect(watchGrp.fieldCost).toEqual(1);
532 expect(watchGrp.totalFieldCost).toEqual(4);
533 logger.clear();
534
535 context['a'] = 1;
536 expect(watchGrp.detectChanges()).toEqual(6);
537 expect(logger).toEqual(['0a', '0A', '1a', '1A', '2A', '1b']); // we go by group order
538 logger.clear();
539
540 context['a'] = 2;
541 child1a.remove(); // should also remove child2
542 expect(watchGrp.detectChanges()).toEqual(3);
543 expect(logger).toEqual(['0a', '0A', '1b']);
544 expect(watchGrp.fieldCost).toEqual(1);
545 expect(watchGrp.totalFieldCost).toEqual(2);
546 });
547
548 it('should remove all method watches in group and group\'s children', () {
549 context['my'] = new MyClass(logger);
550 AST countMethod = new MethodAST(parse('my'), 'count', []);
551 watchGrp.watch(countMethod, (v, p) => logger('0a'));
552 expectOrder(['0a']);
553
554 var child1a = watchGrp.newGroup(new PrototypeMap(context));
555 var child1b = watchGrp.newGroup(new PrototypeMap(context));
556 var child2 = child1a.newGroup(new PrototypeMap(context));
557 child1a.watch(countMethod, (v, p) => logger('1a'));
558 expectOrder(['0a', '1a']);
559 child1b.watch(countMethod, (v, p) => logger('1b'));
560 expectOrder(['0a', '1a', '1b']);
561 watchGrp.watch(countMethod, (v, p) => logger('0A'));
562 expectOrder(['0a', '0A', '1a', '1b']);
563 child1a.watch(countMethod, (v, p) => logger('1A'));
564 expectOrder(['0a', '0A', '1a', '1A', '1b']);
565 child2.watch(countMethod, (v, p) => logger('2A'));
566 expectOrder(['0a', '0A', '1a', '1A', '2A', '1b']);
567
568 // flush initial reaction functions
569 expect(watchGrp.detectChanges()).toEqual(6);
570 expectOrder(['0a', '0A', '1a', '1A', '2A', '1b']);
571
572 child1a.remove(); // should also remove child2
573 expect(watchGrp.detectChanges()).toEqual(3);
574 expectOrder(['0a', '0A', '1b']);
575 });
576
577 it('should add watches within its own group', () {
578 context['my'] = new MyClass(logger);
579 AST countMethod = new MethodAST(parse('my'), 'count', []);
580 var ra = watchGrp.watch(countMethod, (v, p) => logger('a'));
581 var child = watchGrp.newGroup(new PrototypeMap(context));
582 var cb = child.watch(countMethod, (v, p) => logger('b'));
583
584 expectOrder(['a', 'b']);
585 expectOrder(['a', 'b']);
586
587 ra.remove();
588 expectOrder(['b']);
589
590 cb.remove();
591 expectOrder([]);
592
593 // TODO: add them back in wrong order, assert events in right order
594 cb = child.watch(countMethod, (v, p) => logger('b'));
595 ra = watchGrp.watch(countMethod, (v, p) => logger('a'));;
596 expectOrder(['a', 'b']);
597 });
598
599
600 it('should not call reaction function on removed group', () {
601 var log = [];
602 context['name'] = 'misko';
603 var child = watchGrp.newGroup(context);
604 watchGrp.watch(parse('name'), (v, _) {
605 log.add('root $v');
606 if (v == 'destroy') {
607 child.remove();
608 }
609 });
610 child.watch(parse('name'), (v, _) => log.add('child $v'));
611 watchGrp.detectChanges();
612 expect(log).toEqual(['root misko', 'child misko']);
613 log.clear();
614
615 context['name'] = 'destroy';
616 watchGrp.detectChanges();
617 expect(log).toEqual(['root destroy']);
618 });
619
620
621
622 it('should watch children', () {
623 var childContext = new PrototypeMap(context);
624 context['a'] = 'OK';
625 context['b'] = 'BAD';
626 childContext['b'] = 'OK';
627 watchGrp.watch(parse('a'), (v, p) => logger(v));
628 watchGrp.newGroup(childContext).watch(parse('b'), (v, p) => logger(v));
629
630 watchGrp.detectChanges();
631 expect(logger).toEqual(['OK', 'OK']);
632 logger.clear();
633
634 context['a'] = 'A';
635 childContext['b'] = 'B';
636
637 watchGrp.detectChanges();
638 expect(logger).toEqual(['A', 'B']);
639 logger.clear();
640 });
641 });
642
643 });
644 951
645 class MyClass { 952 class MyClass {
646 final Logger logger; 953 final Logger logger;
647 var valA; 954 var valA;
648 int _count = 0; 955 int _count = 0;
649 956
650 MyClass(this.logger); 957 MyClass(this.logger);
651 958
652 methodA(arg1) { 959 methodA(arg1) {
653 logger('methodA($arg1) => $valA'); 960 logger('methodA($arg1) => $valA');
654 return valA; 961 return valA;
655 } 962 }
656 963
657 count() => _count++; 964 count() => _count++;
658 965
659 String toString() => 'MyClass'; 966 String toString() => 'MyClass';
660 } 967 }
661 968
662 class LoggingFunctionApply extends FunctionApply { 969 class LoggingFunctionApply extends FunctionApply {
663 Logger logger; 970 Logger logger;
664 LoggingFunctionApply(this.logger); 971 LoggingFunctionApply(this.logger);
665 apply(List args) => logger(args); 972 apply(List args) => logger(args);
666 } 973 }
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698