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

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

Issue 256553002: Revert "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 scope2_spec; 1 library scope2_spec;
2 2
3 import '../_specs.dart'; 3 import '../_specs.dart';
4 import 'package:angular/change_detection/change_detection.dart' hide ExceptionHa ndler; 4 import 'package:angular/change_detection/change_detection.dart' hide ExceptionHa ndler;
5 import 'package:angular/change_detection/dirty_checking_change_detector.dart'; 5 import 'package:angular/change_detection/dirty_checking_change_detector.dart';
6 import 'dart:async'; 6 import 'dart:async';
7 import 'dart:math'; 7 import 'dart:math';
8 8
9 void main() { 9 main() => describe('scope', () {
10 describe('scope', () { 10 beforeEach(module((Module module) {
11 beforeEachModule((Module module) { 11 Map context = {};
12 Map context = {}; 12 module.value(GetterCache, new GetterCache({}));
13 module 13 module.type(ChangeDetector, implementedBy: DirtyCheckingChangeDetector);
14 ..type(ChangeDetector, implementedBy: DirtyCheckingChangeDetector) 14 module.value(Object, context);
15 ..value(Object, context) 15 module.value(Map, context);
16 ..value(Map, context) 16 module.type(RootScope);
17 ..type(RootScope) 17 module.type(_MultiplyFilter);
18 ..type(_MultiplyFilter) 18 module.type(_ListHeadFilter);
19 ..type(_ListHeadFilter) 19 module.type(_ListTailFilter);
20 ..type(_ListTailFilter) 20 module.type(_SortFilter);
21 ..type(_SortFilter) 21 }));
22 ..type(_IdentityFilter) 22
23 ..type(_MapKeys) 23 describe('AST Bridge', () {
24 ..type(ScopeStatsEmitter, implementedBy: MockScopeStatsEmitter); 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 }));
25 }); 201 });
26 202
27 describe('AST Bridge', () { 203
28 it('should watch field', (Logger logger, Map context, RootScope rootScope) { 204 describe('parent', () {
29 context['field'] = 'Worked!'; 205 it('should not have parent', inject((RootScope rootScope) {
30 rootScope.watch('field', (value, previous) => logger([value, previous])) ; 206 expect(rootScope.parentScope).toEqual(null);
31 expect(logger).toEqual([]); 207 }));
32 rootScope.digest(); 208
33 expect(logger).toEqual([['Worked!', null]]); 209
34 rootScope.digest(); 210 it('should point to parent', inject((RootScope rootScope) {
35 expect(logger).toEqual([['Worked!', null]]); 211 var child = rootScope.createChild(new PrototypeMap(rootScope.context));
36 }); 212 expect(rootScope.parentScope).toEqual(null);
37 213 expect(child.parentScope).toEqual(rootScope);
38 it('should watch field path', (Logger logger, Map context, RootScope rootS cope) { 214 expect(child.createChild(new PrototypeMap(rootScope.context)).parentScop e).toEqual(child);
39 context['a'] = {'b': 'AB'}; 215 }));
40 rootScope.watch('a.b', (value, previous) => logger(value)); 216 });
41 rootScope.digest(); 217 });
42 expect(logger).toEqual(['AB']); 218
43 context['a']['b'] = '123'; 219
44 rootScope.digest(); 220 describe(r'events', () {
45 expect(logger).toEqual(['AB', '123']); 221
46 context['a'] = {'b': 'XYZ'}; 222 describe('on', () {
47 rootScope.digest(); 223 it('should allow emit/broadcast when no listeners', inject((RootScope scop e) {
48 expect(logger).toEqual(['AB', '123', 'XYZ']); 224 scope.emit('foo');
49 }); 225 scope.broadcast('foo');
50 226 }));
51 it('should watch math operations', (Logger logger, Map context, RootScope rootScope) { 227
52 context['a'] = 1; 228
53 context['b'] = 2; 229 it(r'should add listener for both emit and broadcast events', inject((Root Scope rootScope) {
54 rootScope.watch('a + b + 1', (value, previous) => logger(value)); 230 var log = '',
55 rootScope.digest(); 231 child = rootScope.createChild(new PrototypeMap(rootScope.context));
56 expect(logger).toEqual([4]); 232
57 context['a'] = 3; 233 eventFn(event) {
58 rootScope.digest(); 234 expect(event).not.toEqual(null);
59 expect(logger).toEqual([4, 6]); 235 log += 'X';
60 context['b'] = 5; 236 }
61 rootScope.digest(); 237
62 expect(logger).toEqual([4, 6, 9]); 238 child.on('abc').listen(eventFn);
63 }); 239 expect(log).toEqual('');
64 240
65 241 child.emit('abc');
66 it('should watch literals', (Logger logger, Map context, RootScope rootSco pe) { 242 expect(log).toEqual('X');
67 context['a'] = 1; 243
68 rootScope 244 child.broadcast('abc');
69 ..watch('', (value, previous) => logger(value)) 245 expect(log).toEqual('XX');
70 ..watch('""', (value, previous) => logger(value)) 246 }));
71 ..watch('1', (value, previous) => logger(value)) 247
72 ..watch('"str"', (value, previous) => logger(value)) 248
73 ..watch('[a, 2, 3]', (value, previous) => logger(value)) 249 it(r'should return a function that deregisters the listener', inject((Root Scope rootScope) {
74 ..watch('{a:a, b:2}', (value, previous) => logger(value)) 250 var log = '';
75 ..digest(); 251 var child = rootScope.createChild(new PrototypeMap(rootScope.context));
76 expect(logger).toEqual(['', '', 1, 'str', [1, 2, 3], {'a': 1, 'b': 2}]); 252 var subscription;
77 logger.clear(); 253
78 context['a'] = 3; 254 eventFn(e) {
79 rootScope.digest(); 255 log += 'X';
80 expect(logger).toEqual([[3, 2, 3], {'a': 3, 'b': 2}]); 256 }
81 }); 257
82 258 subscription = child.on('abc').listen(eventFn);
83 it('should watch nulls', (Logger logger, Map context, RootScope rootScope) { 259 expect(log).toEqual('');
84 var r = (value, _) => logger(value); 260 expect(subscription).toBeDefined();
85 rootScope 261
86 ..watch('null < 0',r) 262 child.emit(r'abc');
87 ..watch('null * 3', r) 263 child.broadcast('abc');
88 ..watch('null + 6', r) 264 expect(log).toEqual('XX');
89 ..watch('5 + null', r) 265
90 ..watch('null - 4', r) 266 log = '';
91 ..watch('3 - null', r) 267 expect(subscription.cancel()).toBe(null);
92 ..watch('null + null', r) 268 child.emit(r'abc');
93 ..watch('null - null', r) 269 child.broadcast('abc');
94 ..watch('null == null', r) 270 expect(log).toEqual('');
95 ..watch('null != null', r) 271 }));
96 ..digest(); 272
97 expect(logger).toEqual([null, null, 6, 5, -4, 3, 0, 0, true, false]); 273 it('should not trigger assertions on scope fork', inject((RootScope root) {
98 }); 274 var d1 = root.createChild({});
99 275 var d2 = root.createChild({});
100 it('should invoke closures', (Logger logger, Map context, RootScope rootSc ope) { 276 var d3 = d2.createChild({});
101 context['fn'] = () { 277 expect(root.apply).not.toThrow();
102 logger('fn'); 278 d1.on(ScopeEvent.DESTROY).listen((_) => null);
103 return 1; 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);
104 }; 434 };
105 context['a'] = {'fn': () { 435 }));
106 logger('a.fn'); 436
107 return 2; 437 it(r'should bubble event up to the root scope', inject((RootScope rootScop e) {
108 }}; 438 grandChild.emit(r'myEvent');
109 rootScope.watch('fn()', (value, previous) => logger('=> $value')); 439 expect(log.join('>')).toEqual('2>1>0');
110 rootScope.watch('a.fn()', (value, previous) => logger('-> $value')); 440 }));
111 rootScope.digest(); 441
112 expect(logger).toEqual(['fn', 'a.fn', '=> 1', '-> 2', 442
113 /* second loop*/ 'fn', 'a.fn']); 443 it(r'should dispatch exceptions to the exceptionHandler', () {
114 logger.clear(); 444 module((Module module) {
115 rootScope.digest(); 445 module.type(ExceptionHandler, implementedBy: LoggingExceptionHandler);
116 expect(logger).toEqual(['fn', 'a.fn']);
117 });
118
119 it('should perform conditionals', (Logger logger, Map context, RootScope r ootScope) {
120 context['a'] = 1;
121 context['b'] = 2;
122 context['c'] = 3;
123 rootScope.watch('a?b:c', (value, previous) => logger(value));
124 rootScope.digest();
125 expect(logger).toEqual([2]);
126 logger.clear();
127 context['a'] = 0;
128 rootScope.digest();
129 expect(logger).toEqual([3]);
130 });
131
132
133 xit('should call function', (Logger logger, Map context, RootScope rootSco pe) {
134 context['a'] = () {
135 return () { return 123; };
136 };
137 rootScope.watch('a()()', (value, previous) => logger(value));
138 rootScope.digest();
139 expect(logger).toEqual([123]);
140 logger.clear();
141 rootScope.digest();
142 expect(logger).toEqual([]);
143 });
144
145 it('should access bracket', (Logger logger, Map context, RootScope rootSco pe) {
146 context['a'] = {'b': 123};
147 rootScope.watch('a["b"]', (value, previous) => logger(value));
148 rootScope.digest();
149 expect(logger).toEqual([123]);
150 logger.clear();
151 rootScope.digest();
152 expect(logger).toEqual([]);
153 logger.clear();
154
155 context['a']['b'] = 234;
156 rootScope.digest();
157 expect(logger).toEqual([234]);
158 });
159
160
161 it('should prefix', (Logger logger, Map context, RootScope rootScope) {
162 context['a'] = true;
163 rootScope.watch('!a', (value, previous) => logger(value));
164 rootScope.digest();
165 expect(logger).toEqual([false]);
166 logger.clear();
167 context['a'] = false;
168 rootScope.digest();
169 expect(logger).toEqual([true]);
170 });
171
172 it('should support formatters', (Logger logger, Map context,
173 RootScope rootScope, FormatterMap formatters) {
174 context['a'] = 123;
175 context['b'] = 2;
176 rootScope.watch('a | multiply:b', (value, previous) => logger(value),
177 formatters: formatters);
178 rootScope.digest();
179 expect(logger).toEqual([246]);
180 logger.clear();
181 rootScope.digest();
182 expect(logger).toEqual([]);
183 logger.clear();
184 });
185
186 it('should support arrays in formatters', (Logger logger, Map context,
187 RootScope rootScope, FormatterMap formatters) {
188 context['a'] = [1];
189 rootScope.watch('a | sort | listHead:"A" | listTail:"B"',
190 (value, previous) => logger(value), formatters: formatters);
191 rootScope.digest();
192 expect(logger).toEqual(['sort', 'listHead', 'listTail', ['A', 1, 'B']]);
193 logger.clear();
194
195 rootScope.digest();
196 expect(logger).toEqual([]);
197 logger.clear();
198
199 context['a'].add(2);
200 rootScope.digest();
201 expect(logger).toEqual(['sort', 'listHead', 'listTail', ['A', 1, 2, 'B'] ]);
202 logger.clear();
203
204 // We change the order, but sort should change it to same one and it sho uld not
205 // call subsequent formatters.
206 context['a'] = [2, 1];
207 rootScope.digest();
208 expect(logger).toEqual(['sort']);
209 logger.clear();
210 });
211
212 it('should support maps in formatters', (Logger logger, Map context,
213 RootScope rootScope, FormatterMap formatters) {
214 context['a'] = {'foo': 'bar'};
215 rootScope.watch('a | identity | keys',
216 (value, previous) => logger(value), formatters: formatters);
217 rootScope.digest();
218 expect(logger).toEqual(['identity', 'keys', ['foo']]);
219 logger.clear();
220
221 rootScope.digest();
222 expect(logger).toEqual([]);
223 logger.clear();
224
225 context['a']['bar'] = 'baz';
226 rootScope.digest();
227 expect(logger).toEqual(['identity', 'keys', ['foo', 'bar']]);
228 logger.clear();
229 });
230
231 });
232
233
234 describe('properties', () {
235 describe('root', () {
236 it('should point to itself', (RootScope rootScope) {
237 expect(rootScope.rootScope).toEqual(rootScope);
238 }); 446 });
239 447 inject((ExceptionHandler e) {
240 it('children should point to root', (RootScope rootScope) { 448 LoggingExceptionHandler exceptionHandler = e;
241 var child = rootScope.createChild(new PrototypeMap(rootScope.context)) ; 449 child.on('myEvent').listen((e) { throw 'bubbleException'; });
242 expect(child.rootScope).toEqual(rootScope); 450 grandChild.emit(r'myEvent');
243 expect(child.createChild(new PrototypeMap(rootScope.context)).rootScop e).toEqual(rootScope); 451 expect(log.join('>')).toEqual('2>1>0');
452 expect(exceptionHandler.errors[0].error).toEqual('bubbleException');
244 }); 453 });
245 }); 454 });
246 455
247 456
248 describe('parent', () { 457 it(r'should allow stopping event propagation', inject((RootScope rootScope ) {
249 it('should not have parent', (RootScope rootScope) { 458 child.on('myEvent').listen((event) { event.stopPropagation(); });
250 expect(rootScope.parentScope).toEqual(null); 459 grandChild.emit(r'myEvent');
251 expect(rootScope.id).toEqual(''); 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;
252 }); 470 });
253 471 child.emit('abc', ['arg1', 'arg2']);
254 472 expect(eventName).toEqual('abc');
255 it('should point to parent', (RootScope rootScope) { 473 expect(eventData).toEqual(['arg1', 'arg2']);
256 var child = rootScope.createChild(new PrototypeMap(rootScope.context)) ; 474 }));
257 expect(child.id).toEqual(':0'); 475
258 expect(rootScope.parentScope).toEqual(null); 476
259 expect(child.parentScope).toEqual(rootScope); 477 describe(r'event object', () {
260 expect(child.createChild(new PrototypeMap(rootScope.context)).parentSc ope).toEqual(child); 478 it(r'should have methods/properties', inject((RootScope rootScope) {
261 }); 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 }));
262 }); 501 });
263 }); 502 });
264 503
265 504
266 describe(r'events', () { 505 describe('broadcast', () {
267 506 describe(r'event propagation', () {
268 describe('on', () { 507 var log, child1, child2, child3, grandChild11, grandChild21, grandChild2 2, grandChild23,
269 it('should allow emit/broadcast when no listeners', (RootScope scope) { 508 greatGrandChild211;
270 scope.emit('foo');
271 scope.broadcast('foo');
272 });
273
274
275 it(r'should add listener for both emit and broadcast events', (RootScope rootScope) {
276 var log = '',
277 child = rootScope.createChild(new PrototypeMap(rootScope.context));
278
279 eventFn(event) {
280 expect(event).not.toEqual(null);
281 log += 'X';
282 }
283
284 child.on('abc').listen(eventFn);
285 expect(log).toEqual('');
286
287 child.emit('abc');
288 expect(log).toEqual('X');
289
290 child.broadcast('abc');
291 expect(log).toEqual('XX');
292 });
293
294
295 it(r'should return a function that deregisters the listener', (RootScope rootScope) {
296 var log = '';
297 var child = rootScope.createChild(new PrototypeMap(rootScope.context)) ;
298 var subscription;
299
300 eventFn(e) {
301 log += 'X';
302 }
303
304 subscription = child.on('abc').listen(eventFn);
305 expect(log).toEqual('');
306 expect(subscription).toBeDefined();
307
308 child.emit(r'abc');
309 child.broadcast('abc');
310 expect(log).toEqual('XX');
311
312 log = '';
313 expect(subscription.cancel()).toBe(null);
314 child.emit(r'abc');
315 child.broadcast('abc');
316 expect(log).toEqual('');
317 });
318
319 it('should not trigger assertions on scope fork', (RootScope root) {
320 var d1 = root.createChild({});
321 var d2 = root.createChild({});
322 var d3 = d2.createChild({});
323 expect(root.apply).not.toThrow();
324 d1.on(ScopeEvent.DESTROY).listen((_) => null);
325 expect(root.apply).not.toThrow();
326 d3.on(ScopeEvent.DESTROY).listen((_) => null);
327 expect(root.apply).not.toThrow();
328 d2.on(ScopeEvent.DESTROY).listen((_) => null);
329 expect(root.apply).not.toThrow();
330 });
331
332 it('should not too eagerly create own streams', (RootScope root) {
333 var a = root.createChild({});
334 var a2 = root.createChild({});
335 var b = a.createChild({});
336 var c = b.createChild({});
337 var d = c.createChild({});
338 var e = d.createChild({});
339
340 getStreamState() => [root.hasOwnStreams, a.hasOwnStreams, a2.hasOwnStr eams,
341 b.hasOwnStreams, c.hasOwnStreams, d.hasOwnStreams,
342 e.hasOwnStreams];
343
344 expect(getStreamState()).toEqual([false, false, false, false, false, f alse, false]);
345 expect(root.apply).not.toThrow();
346
347 e.on(ScopeEvent.DESTROY).listen((_) => null);
348 expect(getStreamState()).toEqual([false, false, false, false, false, f alse, true]);
349 expect(root.apply).not.toThrow();
350
351 d.on(ScopeEvent.DESTROY).listen((_) => null);
352 expect(getStreamState()).toEqual([false, false, false, false, false, t rue, true]);
353 expect(root.apply).not.toThrow();
354
355 b.on(ScopeEvent.DESTROY).listen((_) => null);
356 expect(getStreamState()).toEqual([false, false, false, true, false, tr ue, true]);
357 expect(root.apply).not.toThrow();
358
359 c.on(ScopeEvent.DESTROY).listen((_) => null);
360 expect(getStreamState()).toEqual([false, false, false, true, true, tru e, true]);
361 expect(root.apply).not.toThrow();
362
363 a.on(ScopeEvent.DESTROY).listen((_) => null);
364 expect(getStreamState()).toEqual([false, true, false, true, true, true , true]);
365 expect(root.apply).not.toThrow();
366
367 a2.on(ScopeEvent.DESTROY).listen((_) => null);
368 expect(getStreamState()).toEqual([true, true, true, true, true, true, true]);
369 expect(root.apply).not.toThrow();
370 });
371
372
373 it('should not properly merge streams', (RootScope root) {
374 var a = root.createChild({});
375 var a2 = root.createChild({});
376 var b = a.createChild({});
377 var c = b.createChild({});
378 var d = c.createChild({});
379 var e = d.createChild({});
380
381 getStreamState() => [root.hasOwnStreams, a.hasOwnStreams, a2.hasOwnStr eams,
382 b.hasOwnStreams, c.hasOwnStreams, d.hasOwnStreams,
383 e.hasOwnStreams];
384
385 expect(getStreamState()).toEqual([false, false, false, false, false, f alse, false]);
386 expect(root.apply).not.toThrow();
387
388 a2.on(ScopeEvent.DESTROY).listen((_) => null);
389 expect(getStreamState()).toEqual([false, false, true, false, false, fa lse, false]);
390 expect(root.apply).not.toThrow();
391
392 e.on(ScopeEvent.DESTROY).listen((_) => null);
393 expect(getStreamState()).toEqual([true, false, true, false, false, fal se, true]);
394 expect(root.apply).not.toThrow();
395 });
396
397
398 it('should clean up on cancel', (RootScope root) {
399 var child = root.createChild(null);
400 var cl = child.on("E").listen((e) => null);
401 var rl = root.on("E").listen((e) => null);
402 rl.cancel();
403 expect(root.apply).not.toThrow();
404 });
405
406
407 it('should find random bugs', (RootScope root) {
408 List scopes;
409 List listeners;
410 List steps;
411 var random = new Random();
412 for (var i = 0; i < 1000; i++) {
413 if (i % 10 == 0) {
414 scopes = [root.createChild(null)];
415 listeners = [];
416 steps = [];
417 }
418 switch(random.nextInt(4)) {
419 case 0:
420 if (scopes.length > 10) break;
421 var index = random.nextInt(scopes.length);
422 Scope scope = scopes[index];
423 var child = scope.createChild(null);
424 scopes.add(child);
425 steps.add('scopes[$index].createChild(null)');
426 break;
427 case 1:
428 var index = random.nextInt(scopes.length);
429 Scope scope = scopes[index];
430 listeners.add(scope.on('E').listen((e) => null));
431 steps.add('scopes[$index].on("E").listen((e)=>null)');
432 break;
433 case 2:
434 if (scopes.length < 3) break;
435 var index = random.nextInt(scopes.length - 1) + 1;
436 Scope scope = scopes[index];
437 scope.destroy();
438 scopes = scopes.where((Scope s) => s.isAttached).toList();
439 steps.add('scopes[$index].destroy()');
440 break;
441 case 3:
442 if (listeners.length == 0) break;
443 var index = random.nextInt(listeners.length);
444 var l = listeners[index];
445 l.cancel();
446 listeners.remove(l);
447 steps.add('listeners[$index].cancel()');
448 break;
449 }
450 try {
451 root.apply();
452 } catch (e) {
453 expect('').toEqual(steps.join(';\n'));
454 }
455 }
456 });
457 });
458
459
460 describe('emit', () {
461 var log, child, grandChild, greatGrandChild;
462 509
463 logger(event) { 510 logger(event) {
464 log.add(event.currentScope.context['id']); 511 log.add(event.currentScope.context['id']);
465 } 512 }
466 513
467 beforeEachModule(() { 514 beforeEach(inject((RootScope rootScope) {
468 return (RootScope rootScope) { 515 log = [];
469 log = []; 516 child1 = rootScope.createChild({});
470 child = rootScope.createChild({'id': 1}); 517 child2 = rootScope.createChild({});
471 grandChild = child.createChild({'id': 2}); 518 child3 = rootScope.createChild({});
472 greatGrandChild = grandChild.createChild({'id': 3}); 519 grandChild11 = child1.createChild({});
473 520 grandChild21 = child2.createChild({});
474 rootScope.context['id'] = 0; 521 grandChild22 = child2.createChild({});
475 522 grandChild23 = child2.createChild({});
476 rootScope.on('myEvent').listen(logger); 523 greatGrandChild211 = grandChild21.createChild({});
477 child.on('myEvent').listen(logger); 524
478 grandChild.on('myEvent').listen(logger); 525 rootScope.context['id'] = 0;
479 greatGrandChild.on('myEvent').listen(logger); 526 child1.context['id'] = 1;
480 }; 527 child2.context['id'] = 2;
481 }); 528 child3.context['id'] = 3;
482 529 grandChild11.context['id'] = 11;
483 it(r'should bubble event up to the root scope', (RootScope rootScope) { 530 grandChild21.context['id'] = 21;
484 grandChild.emit(r'myEvent'); 531 grandChild22.context['id'] = 22;
485 expect(log.join('>')).toEqual('2>1>0'); 532 grandChild23.context['id'] = 23;
486 }); 533 greatGrandChild211.context['id'] = 211;
487 534
488 535 rootScope.on('myEvent').listen(logger);
489 describe('exceptions', () { 536 child1.on('myEvent').listen(logger);
490 beforeEachModule((Module module) { 537 child2.on('myEvent').listen(logger);
491 module.type(ExceptionHandler, implementedBy: LoggingExceptionHandler ); 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;
492 }); 616 });
493 617 scope.broadcast('fooEvent');
494 618
495 it(r'should dispatch exceptions to the exceptionHandler', (ExceptionHa ndler e) { 619 expect(event.name).toBe('fooEvent');
496 LoggingExceptionHandler exceptionHandler = e; 620 expect(event.targetScope).toBe(scope);
497 child.on('myEvent').listen((e) { throw 'bubbleException'; }); 621 expect(event.currentScope).toBe(child);
498 grandChild.emit(r'myEvent'); 622 }));
499 expect(log.join('>')).toEqual('2>1>0'); 623
500 expect(exceptionHandler.errors[0].error).toEqual('bubbleException'); 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;
501 }); 631 });
502 632 scope.broadcast('fooEvent', ['do', 're', 'me', 'fa']);
503 633
504 it('should throw "model unstable" error when observer is present', (Ro otScope rootScope, VmTurnZone zone, ExceptionHandler e) { 634 expect(args.length).toBe(4);
505 // Generates a different, equal, list on each evaluation. 635 expect(args).toEqual(['do', 're', 'me', 'fa']);
506 rootScope.context['list'] = new UnstableList(); 636 }));
507
508 rootScope.watch('list.list', (n, v) => null, canChangeModel: true);
509 try {
510 zone.run(() => null);
511 } catch(_) {}
512
513 var errors = (e as LoggingExceptionHandler).errors;
514 expect(errors.length).toEqual(1);
515 expect(errors.first.error, startsWith('Model did not stabilize'));
516 });
517 });
518
519 it(r'should allow stopping event propagation', (RootScope rootScope) {
520 child.on('myEvent').listen((event) { event.stopPropagation(); });
521 grandChild.emit(r'myEvent');
522 expect(log.join('>')).toEqual('2>1');
523 });
524
525
526 it(r'should forward method arguments', (RootScope rootScope) {
527 var eventName;
528 var eventData;
529 child.on('abc').listen((event) {
530 eventName = event.name;
531 eventData = event.data;
532 });
533 child.emit('abc', ['arg1', 'arg2']);
534 expect(eventName).toEqual('abc');
535 expect(eventData).toEqual(['arg1', 'arg2']);
536 });
537
538
539 describe(r'event object', () {
540 it(r'should have methods/properties', (RootScope rootScope) {
541 var event;
542 child.on('myEvent').listen((e) {
543 expect(e.targetScope).toBe(grandChild);
544 expect(e.currentScope).toBe(child);
545 expect(e.name).toBe('myEvent');
546 event = e;
547 });
548 grandChild.emit(r'myEvent');
549 expect(event).toBeDefined();
550 });
551
552
553 it(r'should have preventDefault method and defaultPrevented property', (RootScope rootScope) {
554 var event = grandChild.emit(r'myEvent');
555 expect(event.defaultPrevented).toBe(false);
556
557 child.on('myEvent').listen((event) {
558 event.preventDefault();
559 });
560 event = grandChild.emit(r'myEvent');
561 expect(event.defaultPrevented).toBe(true);
562 });
563 });
564 });
565
566
567 describe('broadcast', () {
568 describe(r'event propagation', () {
569 var log, child1, child2, child3, grandChild11, grandChild21, grandChil d22, grandChild23,
570 greatGrandChild211;
571
572 logger(event) {
573 log.add(event.currentScope.context['id']);
574 }
575
576 beforeEach((RootScope rootScope) {
577 log = [];
578 child1 = rootScope.createChild({});
579 child2 = rootScope.createChild({});
580 child3 = rootScope.createChild({});
581 grandChild11 = child1.createChild({});
582 grandChild21 = child2.createChild({});
583 grandChild22 = child2.createChild({});
584 grandChild23 = child2.createChild({});
585 greatGrandChild211 = grandChild21.createChild({});
586
587 rootScope.context['id'] = 0;
588 child1.context['id'] = 1;
589 child2.context['id'] = 2;
590 child3.context['id'] = 3;
591 grandChild11.context['id'] = 11;
592 grandChild21.context['id'] = 21;
593 grandChild22.context['id'] = 22;
594 grandChild23.context['id'] = 23;
595 greatGrandChild211.context['id'] = 211;
596
597 rootScope.on('myEvent').listen(logger);
598 child1.on('myEvent').listen(logger);
599 child2.on('myEvent').listen(logger);
600 child3.on('myEvent').listen(logger);
601 grandChild11.on('myEvent').listen(logger);
602 grandChild21.on('myEvent').listen(logger);
603 grandChild22.on('myEvent').listen(logger);
604 grandChild23.on('myEvent').listen(logger);
605 greatGrandChild211.on('myEvent').listen(logger);
606
607 // R
608 // / | \
609 // 1 2 3
610 // / / | \
611 // 11 21 22 23
612 // |
613 // 211
614 });
615
616
617 it(r'should broadcast an event from the root scope', (RootScope rootSc ope) {
618 rootScope.broadcast('myEvent');
619 expect(log.join('>')).toEqual('0>1>11>2>21>211>22>23>3');
620 });
621
622
623 it(r'should broadcast an event from a child scope', (RootScope rootSco pe) {
624 child2.broadcast('myEvent');
625 expect(log.join('>')).toEqual('2>21>211>22>23');
626 });
627
628
629 it(r'should broadcast an event from a leaf scope with a sibling', (Roo tScope rootScope) {
630 grandChild22.broadcast('myEvent');
631 expect(log.join('>')).toEqual('22');
632 });
633
634
635 it(r'should broadcast an event from a leaf scope without a sibling', ( RootScope rootScope) {
636 grandChild23.broadcast('myEvent');
637 expect(log.join('>')).toEqual('23');
638 });
639
640
641 it(r'should not not fire any listeners for other events', (RootScope r ootScope) {
642 rootScope.broadcast('fooEvent');
643 expect(log.join('>')).toEqual('');
644 });
645
646
647 it(r'should return event object', (RootScope rootScope) {
648 var result = child1.broadcast('some');
649
650 expect(result).toBeDefined();
651 expect(result.name).toBe('some');
652 expect(result.targetScope).toBe(child1);
653 });
654
655
656 it('should skip scopes which dont have given event',
657 inject((RootScope rootScope, Logger log) {
658 var child1 = rootScope.createChild('A');
659 rootScope.createChild('A1');
660 rootScope.createChild('A2');
661 rootScope.createChild('A3');
662 var child2 = rootScope.createChild('B');
663 child2.on('event').listen((e) => log(e.data));
664 rootScope.broadcast('event', 'OK');
665 expect(log).toEqual(['OK']);
666 }));
667 });
668
669
670 describe(r'listener', () {
671 it(r'should receive event object', (RootScope rootScope) {
672 var scope = rootScope,
673 child = scope.createChild({}),
674 event;
675
676 child.on('fooEvent').listen((e) {
677 event = e;
678 });
679 scope.broadcast('fooEvent');
680
681 expect(event.name).toBe('fooEvent');
682 expect(event.targetScope).toBe(scope);
683 expect(event.currentScope).toBe(child);
684 });
685
686 it(r'should support passing messages as varargs', (RootScope rootScope ) {
687 var scope = rootScope,
688 child = scope.createChild({}),
689 args;
690
691 child.on('fooEvent').listen((e) {
692 args = e.data;
693 });
694 scope.broadcast('fooEvent', ['do', 're', 'me', 'fa']);
695
696 expect(args.length).toBe(4);
697 expect(args).toEqual(['do', 're', 'me', 'fa']);
698 });
699
700 it('should allow removing/adding listener during an event', (RootScope rootScope, Logger log) {
701 StreamSubscription subscription;
702 subscription = rootScope.on('foo').listen((_) {
703 subscription.cancel();
704 rootScope.on('foo').listen((_) => log(3));
705 log(2);
706 });
707 expect(() {
708 log(1);
709 rootScope.broadcast('foo');
710 }).not.toThrow();
711 rootScope.broadcast('foo');
712 expect(log).toEqual([1, 2, 3]);
713 });
714 });
715 }); 637 });
716 }); 638 });
717 639 });
718 640
719 describe(r'destroy', () { 641
720 var first = null, middle = null, last = null, log = null; 642 describe(r'destroy', () {
721 643 var first = null, middle = null, last = null, log = null;
722 beforeEach((RootScope rootScope) { 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; };
723 log = ''; 758 log = '';
724 759 rootScope.watch('log()', (v, o) => null);
725 first = rootScope.createChild({"check": (n) { log+= '$n'; return n;}});
726 middle = rootScope.createChild({"check": (n) { log+= '$n'; return n;}});
727 last = rootScope.createChild({"check": (n) { log+= '$n'; return n;}});
728
729 first.watch('check(1)', (v, l) {});
730 middle.watch('check(2)', (v, l) {});
731 last.watch('check(3)', (v, l) {});
732
733 first.on(ScopeEvent.DESTROY).listen((e) { log += 'destroy:first;'; });
734
735 rootScope.digest(); 760 rootScope.digest();
736 log = ''; 761 log = '';
737 }); 762 }));
738 763
739 764
740 it(r'should ignore remove on root', (RootScope rootScope) { 765 it(r'should execute and return value and update', inject(
741 rootScope.destroy(); 766 (RootScope rootScope, ExceptionHandler e) {
742 rootScope.digest(); 767 LoggingExceptionHandler exceptionHandler = e;
743 expect(log).toEqual('123'); 768 rootScope.context['name'] = 'abc';
744 }); 769 expect(rootScope.apply((context) => context['name'])).toEqual('abc');
745 770 expect(log).toEqual('digest;digest;');
746 771 exceptionHandler.assertEmpty();
747 it(r'should remove first', (RootScope rootScope) { 772 }));
748 first.destroy(); 773
749 rootScope.digest(); 774
750 expect(log).toEqual('destroy:first;23'); 775 it(r'should execute and return value and update', inject((RootScope rootSc ope) {
751 }); 776 rootScope.context['name'] = 'abc';
752 777 expect(rootScope.apply('name', {'name': 123})).toEqual(123);
753 778 }));
754 it(r'should remove middle', (RootScope rootScope) { 779
755 middle.destroy(); 780
756 rootScope.digest(); 781 it(r'should catch exception and update', inject((RootScope rootScope, Exce ptionHandler e) {
757 expect(log).toEqual('13'); 782 LoggingExceptionHandler exceptionHandler = e;
758 }); 783 var error = 'MyError';
759 784 rootScope.apply(() { throw error; });
760 785 expect(log).toEqual('digest;digest;');
761 it(r'should remove last', (RootScope rootScope) { 786 expect(exceptionHandler.errors[0].error).toEqual(error);
762 last.destroy(); 787 }));
763 rootScope.digest(); 788 });
764 expect(log).toEqual('12'); 789
765 }); 790 it(r'should proprely reset phase on exception', inject((RootScope rootScope) {
766 791 var error = 'MyError';
767 792 expect(() => rootScope.apply(() { throw error; })).toThrow(error);
768 it(r'should broadcast the destroy event', (RootScope rootScope) { 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;
769 var log = []; 821 var log = [];
770 first.on(ScopeEvent.DESTROY).listen((s) => log.add('first')); 822 var child = rootScope.createChild({});
771 var child = first.createChild({}); 823 rootScope.watch('a', (a, _) => log.add('1'), readOnly: true);
772 child.on(ScopeEvent.DESTROY).listen((s) => log.add('first-child')); 824 rootScope.context['a'] = 0;
773 825 child.apply(() { throw 'MyError'; });
774 first.destroy(); 826 expect(log.join(',')).toEqual('1');
775 expect(log).toEqual(['first', 'first-child']); 827 expect(exceptionHandler.errors[0].error).toEqual('MyError');
776 }); 828 exceptionHandler.errors.removeAt(0);
777 829 exceptionHandler.assertEmpty();
778
779 it('should not call reaction function on destroyed scope', (RootScope root Scope, Logger log) {
780 rootScope.context['name'] = 'misko';
781 var child = rootScope.createChild(rootScope.context);
782 rootScope.watch('name', (v, _) {
783 log('root $v');
784 if (v == 'destroy') {
785 child.destroy();
786 }
787 });
788 rootScope.watch('name', (v, _) => log('root2 $v'));
789 child.watch('name', (v, _) => log('child $v'));
790 rootScope.apply();
791 expect(log).toEqual(['root misko', 'root2 misko', 'child misko']);
792 log.clear();
793
794 rootScope.context['name'] = 'destroy';
795 rootScope.apply();
796 expect(log).toEqual(['root destroy', 'root2 destroy']);
797 });
798
799
800 it('should not call reaction fn when destroyed', (RootScope scope) {
801 var testScope = scope.createChild({});
802 bool called = false;
803 testScope.watch('items', (_, __) {
804 called = true;
805 });
806 testScope.destroy();
807 scope.apply();
808 expect(called).toBeFalsy();
809 }); 830 });
810 }); 831 });
811 832
812 833
813 describe('digest lifecycle', () { 834 describe(r'exceptions', () {
814 it(r'should apply expression with full lifecycle', (RootScope rootScope) { 835 var log;
815 var log = ''; 836 beforeEach(module((Module module) {
816 var child = rootScope.createChild({"parent": rootScope.context}); 837 return module.type(ExceptionHandler, implementedBy: LoggingExceptionHand ler);
817 rootScope.watch('a', (a, _) { log += '1'; }); 838 }));
818 child.apply('parent.a = 0'); 839 beforeEach(inject((RootScope rootScope) {
819 expect(log).toEqual('1'); 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];
820 }); 931 });
821 932 rootScope.digest();
822 describe(r'exceptions', () { 933 log = null;
823 var log; 934
824 beforeEachModule((Module module) { 935 expect(log).toEqual(null);
825 return module.type(ExceptionHandler, implementedBy: LoggingExceptionHa ndler); 936 rootScope.digest();
826 }); 937 expect(log).toEqual(null);
827 938 rootScope.context['name'] = 'misko';
828 beforeEach((RootScope rootScope) { 939 rootScope.digest();
829 rootScope.context['log'] = () { log += 'digest;'; return null; }; 940 expect(log).toEqual(['misko', null]);
830 log = ''; 941 }));
831 rootScope.watch('log()', (v, o) => null); 942
832 rootScope.digest(); 943
833 log = ''; 944 it('should watch/observe on objects other then contex', inject((RootScope ro otScope) {
834 }); 945 var log = '';
835 946 var map = {'a': 'A', 'b': 'B'};
836 it(r'should catch exceptions', (RootScope rootScope, ExceptionHandler e) { 947 rootScope.watch('a', (a, b) => log += a, context: map);
837 LoggingExceptionHandler exceptionHandler = e; 948 rootScope.watch('b', (a, b) => log += a, context: map);
838 var log = []; 949 rootScope.apply();
839 var child = rootScope.createChild({}); 950 expect(log).toEqual('AB');
840 rootScope.watch('a', (a, _) => log.add('1')); 951 }));
841 rootScope.context['a'] = 0; 952
842 child.apply(() { throw 'MyError'; }); 953
843 expect(log.join(',')).toEqual('1'); 954 it(r'should watch and fire on expression change', inject((RootScope rootScop e) {
844 expect(exceptionHandler.errors[0].error).toEqual('MyError'); 955 var log;
845 exceptionHandler.errors.removeAt(0); 956
846 exceptionHandler.assertEmpty(); 957 rootScope.watch('name.first', (a, b) => log = [a, b]);
847 }); 958 rootScope.digest();
848 959 log = null;
849 960
850 it(r'should execute and return value and update', inject( 961 rootScope.context['name'] = {};
851 (RootScope rootScope, ExceptionHandler e) { 962 expect(log).toEqual(null);
852 LoggingExceptionHandler exceptionHandler = e; 963 rootScope.digest();
853 rootScope.context['name'] = 'abc'; 964 expect(log).toEqual(null);
854 expect(rootScope.apply((context) => context['name'])).toEqual('abc '); 965 rootScope.context['name']['first'] = 'misko';
855 expect(log).toEqual('digest;digest;'); 966 rootScope.digest();
856 exceptionHandler.assertEmpty(); 967 expect(log).toEqual(['misko', null]);
857 })); 968 }));
858 969
859 970
860 it(r'should execute and return value and update', (RootScope rootScope) { 971 it(r'should delegate exceptions', () {
861 rootScope.context['name'] = 'abc'; 972 module((Module module) {
862 expect(rootScope.apply('name', {'name': 123})).toEqual(123); 973 module.type(ExceptionHandler, implementedBy: LoggingExceptionHandler);
863 });
864
865
866 it(r'should catch exception and update', (RootScope rootScope, Exception Handler e) {
867 LoggingExceptionHandler exceptionHandler = e;
868 var error = 'MyError';
869 rootScope.apply(() { throw error; });
870 expect(log).toEqual('digest;digest;');
871 expect(exceptionHandler.errors[0].error).toEqual(error);
872 });
873 }); 974 });
874 975 inject((RootScope rootScope, ExceptionHandler e) {
875 it(r'should properly reset phase on exception', (RootScope rootScope) { 976 LoggingExceptionHandler exceptionHandler = e;
876 var error = 'MyError'; 977 rootScope.watch('a', (n, o) {throw 'abc';});
877 expect(() => rootScope.apply(() { throw error; })).toThrow(error); 978 rootScope.context['a'] = 1;
878 expect(() => rootScope.apply(() { throw error; })).toThrow(error); 979 rootScope.digest();
980 expect(exceptionHandler.errors.length).toEqual(1);
981 expect(exceptionHandler.errors[0].error).toEqual('abc');
879 }); 982 });
880 }); 983 });
881 984
882 985
883 describe('flush lifecycle', () { 986 it(r'should fire watches in order of addition', inject((RootScope rootScope) {
884 it(r'should apply expression with full lifecycle', (RootScope rootScope) { 987 // this is not an external guarantee, just our own sanity
885 var log = ''; 988 var log = '';
886 var child = rootScope.createChild({"parent": rootScope.context}); 989 rootScope.watch('a', (a, b) { log += 'a'; });
887 rootScope.watch('a', (a, _) { log += '1'; }, canChangeModel: false); 990 rootScope.watch('b', (a, b) { log += 'b'; });
888 child.apply('parent.a = 0'); 991 rootScope.watch('c', (a, b) { log += 'c'; });
889 expect(log).toEqual('1'); 992 rootScope.context['a'] = rootScope.context['b'] = rootScope.context['c'] = 1;
890 }); 993 rootScope.digest();
891 994 expect(log).toEqual('abc');
892 995 }));
893 it(r'should schedule domWrites and domReads', (RootScope rootScope) { 996
894 var log = ''; 997
895 var child = rootScope.createChild({"parent": rootScope.context}); 998 it(r'should call child watchers in addition order', inject((RootScope rootSc ope) {
896 rootScope.watch('a', (a, _) { log += '1'; }, canChangeModel: false); 999 // this is not an external guarantee, just our own sanity
897 child.apply('parent.a = 0'); 1000 var log = '';
898 expect(log).toEqual('1'); 1001 var childA = rootScope.createChild({});
899 }); 1002 var childB = rootScope.createChild({});
900 1003 var childC = rootScope.createChild({});
901 describe(r'exceptions', () { 1004 childA.watch('a', (a, b) { log += 'a'; });
902 var log; 1005 childB.watch('b', (a, b) { log += 'b'; });
903 beforeEachModule((Module module) { 1006 childC.watch('c', (a, b) { log += 'c'; });
904 return module.type(ExceptionHandler, implementedBy: LoggingExceptionHa ndler); 1007 childA.context['a'] = childB.context['b'] = childC.context['c'] = 1;
905 }); 1008 rootScope.digest();
906 beforeEach((RootScope rootScope) { 1009 expect(log).toEqual('abc');
907 rootScope.context['log'] = () { log += 'digest;'; return null; }; 1010 }));
908 log = ''; 1011
909 rootScope.watch('log()', (v, o) => null, canChangeModel: false); 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
910 rootScope.digest(); 1027 rootScope.digest();
911 log = ''; 1028 expect(log.join('')).toEqual('rabrab');
912 }); 1029 }));
913 1030
914 it(r'should catch exceptions', (RootScope rootScope, ExceptionHandler e) { 1031
915 LoggingExceptionHandler exceptionHandler = e; 1032 it(r'should repeat watch cycle while model changes are identified', inject(( RootScope rootScope) {
916 var log = []; 1033 var log = '';
917 var child = rootScope.createChild({}); 1034 rootScope.watch('c', (v, b) {rootScope.context['d'] = v; log+='c'; });
918 rootScope.watch('a', (a, _) => log.add('1'), canChangeModel: false); 1035 rootScope.watch('b', (v, b) {rootScope.context['c'] = v; log+='b'; });
919 rootScope.context['a'] = 0; 1036 rootScope.watch('a', (v, b) {rootScope.context['b'] = v; log+='a'; });
920 child.apply(() { throw 'MyError'; }); 1037 rootScope.digest();
921 expect(log.join(',')).toEqual('1'); 1038 log = '';
922 expect(exceptionHandler.errors[0].error).toEqual('MyError'); 1039 rootScope.context['a'] = 1;
923 exceptionHandler.errors.removeAt(0); 1040 rootScope.digest();
924 exceptionHandler.assertEmpty(); 1041 expect(rootScope.context['b']).toEqual(1);
925 }); 1042 expect(rootScope.context['c']).toEqual(1);
926 1043 expect(rootScope.context['d']).toEqual(1);
927 it(r'should execute and return value and update', inject( 1044 expect(log).toEqual('abc');
928 (RootScope rootScope, ExceptionHandler e) { 1045 }));
929 LoggingExceptionHandler exceptionHandler = e; 1046
930 rootScope.context['name'] = 'abc'; 1047
931 expect(rootScope.apply((context) => context['name'])).toEqual('abc '); 1048 it(r'should repeat watch cycle from the root element', inject((RootScope roo tScope) {
932 expect(log).toEqual('digest;digest;'); 1049 var log = [];
933 exceptionHandler.assertEmpty(); 1050 rootScope.context['log'] = log;
934 })); 1051 var child = rootScope.createChild({'log':log});
935 1052 rootScope.watch("log.add('a')", (_, __) => null);
936 it(r'should execute and return value and update', (RootScope rootScope) { 1053 child.watch("log.add('b')", (_, __) => null);
937 rootScope.context['name'] = 'abc'; 1054 rootScope.digest();
938 expect(rootScope.apply('name', {'name': 123})).toEqual(123); 1055 expect(log.join('')).toEqual('abab');
939 }); 1056 }));
940 1057
941 it(r'should catch exception and update', (RootScope rootScope, Exception Handler e) { 1058
942 LoggingExceptionHandler exceptionHandler = e; 1059 it(r'should not fire upon watch registration on initial digest', inject((Roo tScope rootScope) {
943 var error = 'MyError'; 1060 var log = '';
944 rootScope.apply(() { throw error; }); 1061 rootScope.context['a'] = 1;
945 expect(log).toEqual('digest;digest;'); 1062 rootScope.watch('a', (a, b) { log += 'a'; });
946 expect(exceptionHandler.errors[0].error).toEqual(error); 1063 rootScope.watch('b', (a, b) { log += 'b'; });
947 }); 1064 rootScope.digest();
948 1065 log = '';
949 it(r'should throw assertion when model changes in flush', (RootScope roo tScope, Logger log) { 1066 rootScope.digest();
950 var retValue = 1; 1067 expect(log).toEqual('');
951 rootScope.context['logger'] = (name) { log(name); return retValue; }; 1068 }));
952 1069
953 rootScope.watch('logger("watch")', (n, v) => null); 1070
954 rootScope.watch('logger("flush")', (n, v) => null, 1071 it(r'should prevent digest recursion', inject((RootScope rootScope) {
955 canChangeModel: false); 1072 var callCount = 0;
956 1073 rootScope.watch('name', (a, b) {
957 // clear watches
958 rootScope.digest();
959 log.clear();
960
961 rootScope.flush();
962 expect(log).toEqual(['flush', /*assertion*/ 'watch', 'flush']);
963
964 retValue = 2;
965 expect(rootScope.flush).
966 toThrow('Observer reaction functions should not change model. \n'
967 'These watch changes were detected: logger("watch"): 2 <= 1\n'
968 'These observe changes were detected: ');
969 });
970 });
971
972 });
973
974
975 describe('ScopeLocals', () {
976 it('should read from locals', (RootScope scope) {
977 scope.context['a'] = 'XXX';
978 scope.context['c'] = 'C';
979 var scopeLocal = new ScopeLocals(scope.context, {'a': 'A', 'b': 'B'});
980 expect(scopeLocal['a']).toEqual('A');
981 expect(scopeLocal['b']).toEqual('B');
982 expect(scopeLocal['c']).toEqual('C');
983 });
984
985 it('should write to Scope', (RootScope scope) {
986 scope.context['a'] = 'XXX';
987 scope.context['c'] = 'C';
988 var scopeLocal = new ScopeLocals(scope.context, {'a': 'A', 'b': 'B'});
989
990 scopeLocal['a'] = 'aW';
991 scopeLocal['b'] = 'bW';
992 scopeLocal['c'] = 'cW';
993
994 expect(scope.context['a']).toEqual('aW');
995 expect(scope.context['b']).toEqual('bW');
996 expect(scope.context['c']).toEqual('cW');
997
998 expect(scopeLocal['a']).toEqual('A');
999 expect(scopeLocal['b']).toEqual('B');
1000 expect(scopeLocal['c']).toEqual('cW');
1001 });
1002 });
1003
1004
1005 describe(r'watch/digest', () {
1006 it(r'should watch and fire on simple property change', (RootScope rootScop e) {
1007 var log;
1008
1009 rootScope.watch('name', (a, b) {
1010 log = [a, b];
1011 });
1012 rootScope.digest();
1013 log = null;
1014
1015 expect(log).toEqual(null);
1016 rootScope.digest();
1017 expect(log).toEqual(null);
1018 rootScope.context['name'] = 'misko';
1019 rootScope.digest();
1020 expect(log).toEqual(['misko', null]);
1021 });
1022
1023
1024 it('should watch/observe on objects other then contex', (RootScope rootSco pe) {
1025 var log = '';
1026 var map = {'a': 'A', 'b': 'B'};
1027 rootScope.watch('a', (a, b) => log += a, context: map);
1028 rootScope.watch('b', (a, b) => log += a, context: map);
1029 rootScope.apply();
1030 expect(log).toEqual('AB');
1031 });
1032
1033
1034 it(r'should watch and fire on expression change', (RootScope rootScope) {
1035 var log;
1036
1037 rootScope.watch('name.first', (a, b) => log = [a, b]);
1038 rootScope.digest();
1039 log = null;
1040
1041 rootScope.context['name'] = {};
1042 expect(log).toEqual(null);
1043 rootScope.digest();
1044 expect(log).toEqual(null);
1045 rootScope.context['name']['first'] = 'misko';
1046 rootScope.digest();
1047 expect(log).toEqual(['misko', null]);
1048 });
1049
1050
1051 describe('exceptions', () {
1052 beforeEachModule((Module module) {
1053 module.type(ExceptionHandler, implementedBy: LoggingExceptionHandler);
1054 });
1055 it(r'should delegate exceptions', (RootScope rootScope, ExceptionHandler e) {
1056 LoggingExceptionHandler exceptionHandler = e;
1057 rootScope.watch('a', (n, o) {throw 'abc';});
1058 rootScope.context['a'] = 1;
1059 rootScope.digest();
1060 expect(exceptionHandler.errors.length).toEqual(1);
1061 expect(exceptionHandler.errors[0].error).toEqual('abc');
1062 });
1063 });
1064
1065
1066
1067 it(r'should fire watches in order of addition', (RootScope rootScope) {
1068 // this is not an external guarantee, just our own sanity
1069 var log = '';
1070 rootScope
1071 ..watch('a', (a, b) { log += 'a'; })
1072 ..watch('b', (a, b) { log += 'b'; })
1073 ..watch('c', (a, b) { log += 'c'; })
1074 ..context['a'] = rootScope.context['b'] = rootScope.context['c'] = 1
1075 ..digest();
1076 expect(log).toEqual('abc');
1077 });
1078
1079
1080 it(r'should call child watchers in addition order', (RootScope rootScope) {
1081 // this is not an external guarantee, just our own sanity
1082 var log = '';
1083 var childA = rootScope.createChild({});
1084 var childB = rootScope.createChild({});
1085 var childC = rootScope.createChild({});
1086 childA.watch('a', (a, b) { log += 'a'; });
1087 childB.watch('b', (a, b) { log += 'b'; });
1088 childC.watch('c', (a, b) { log += 'c'; });
1089 childA.context['a'] = childB.context['b'] = childC.context['c'] = 1;
1090 rootScope.digest();
1091 expect(log).toEqual('abc');
1092 });
1093
1094
1095 it(r'should run digest multiple times', inject(
1096 (RootScope rootScope) {
1097 // tests a traversal edge case which we originally missed
1098 var log = [];
1099 var childA = rootScope.createChild({'log': log});
1100 var childB = rootScope.createChild({'log': log});
1101
1102 rootScope.context['log'] = log;
1103
1104 rootScope.watch("log.add('r')", (_, __) => null);
1105 childA.watch("log.add('a')", (_, __) => null);
1106 childB.watch("log.add('b')", (_, __) => null);
1107
1108 // init
1109 rootScope.digest();
1110 expect(log.join('')).toEqual('rabrab');
1111 }));
1112
1113
1114 it(r'should repeat watch cycle while model changes are identified', (RootS cope rootScope) {
1115 var log = '';
1116 rootScope
1117 ..watch('c', (v, b) {rootScope.context['d'] = v; log+='c'; })
1118 ..watch('b', (v, b) {rootScope.context['c'] = v; log+='b'; })
1119 ..watch('a', (v, b) {rootScope.context['b'] = v; log+='a'; })
1120 ..digest();
1121 log = '';
1122 rootScope.context['a'] = 1;
1123 rootScope.digest();
1124 expect(rootScope.context['b']).toEqual(1);
1125 expect(rootScope.context['c']).toEqual(1);
1126 expect(rootScope.context['d']).toEqual(1);
1127 expect(log).toEqual('abc');
1128 });
1129
1130
1131 it(r'should repeat watch cycle from the root element', (RootScope rootScop e) {
1132 var log = [];
1133 rootScope.context['log'] = log;
1134 var child = rootScope.createChild({'log':log});
1135 rootScope.watch("log.add('a')", (_, __) => null);
1136 child.watch("log.add('b')", (_, __) => null);
1137 rootScope.digest();
1138 expect(log.join('')).toEqual('abab');
1139 });
1140
1141
1142 it(r'should not fire upon watch registration on initial digest', (RootScop e rootScope) {
1143 var log = '';
1144 rootScope.context['a'] = 1;
1145 rootScope.watch('a', (a, b) { log += 'a'; });
1146 rootScope.watch('b', (a, b) { log += 'b'; });
1147 rootScope.digest();
1148 log = '';
1149 rootScope.digest();
1150 expect(log).toEqual('');
1151 });
1152
1153
1154 it(r'should prevent digest recursion', (RootScope rootScope) {
1155 var callCount = 0;
1156 rootScope.watch('name', (a, b) {
1157 expect(() {
1158 rootScope.digest();
1159 }).toThrow(r'digest already in progress');
1160 callCount++;
1161 });
1162 rootScope.context['name'] = 'a';
1163 rootScope.digest();
1164 expect(callCount).toEqual(1);
1165 });
1166
1167
1168 it(r'should return a function that allows listeners to be unregistered', i nject(
1169 (RootScope rootScope) {
1170 var listener = jasmine.createSpy('watch listener');
1171 var watch;
1172
1173 watch = rootScope.watch('foo', listener);
1174 rootScope.digest(); //init
1175 expect(listener).toHaveBeenCalled();
1176 expect(watch).toBeDefined();
1177
1178 listener.reset();
1179 rootScope.context['foo'] = 'bar';
1180 rootScope.digest(); //trigger
1181 expect(listener).toHaveBeenCalledOnce();
1182
1183 listener.reset();
1184 rootScope.context['foo'] = 'baz';
1185 watch.remove();
1186 rootScope.digest(); //trigger
1187 expect(listener).not.toHaveBeenCalled();
1188 }));
1189
1190
1191 it(r'should be possible to remove every watch',
1192 (RootScope rootScope, FormatterMap formatters) {
1193 rootScope.context['foo'] = 'bar';
1194 var watch1 = rootScope.watch('(foo|json)+"bar"', (v, p) => null,
1195 formatters: formatters);
1196 var watch2 = rootScope.watch('(foo|json)+"bar"', (v, p) => null,
1197 formatters: formatters);
1198
1199 expect(() => watch1.remove()).not.toThrow();
1200 expect(() => watch2.remove()).not.toThrow();
1201 });
1202
1203
1204 it(r'should not infinitely digest when current value is NaN', (RootScope r ootScope) {
1205 rootScope.context['nan'] = double.NAN;
1206 rootScope.watch('nan', (_, __) => null);
1207
1208 expect(() { 1074 expect(() {
1209 rootScope.digest(); 1075 rootScope.digest();
1210 }).not.toThrow(); 1076 }).toThrow(r'digest already in progress');
1077 callCount++;
1211 }); 1078 });
1212 1079 rootScope.context['name'] = 'a';
1213 1080 rootScope.digest();
1214 it(r'should prevent infinite digest and should log firing expressions', (R ootScope rootScope) { 1081 expect(callCount).toEqual(1);
1215 rootScope.context['a'] = 0; 1082 }));
1216 rootScope.context['b'] = 0; 1083
1217 rootScope.watch('a', (a, __) => rootScope.context['a'] = a + 1); 1084
1218 rootScope.watch('b', (b, __) => rootScope.context['b'] = b + 1); 1085 it(r'should return a function that allows listeners to be unregistered', inj ect(
1219 1086 (RootScope rootScope) {
1220 expect(() { 1087 var listener = jasmine.createSpy('watch listener');
1221 rootScope.digest(); 1088 var watch;
1222 }).toThrow('Model did not stabilize in 5 digests. ' 1089
1223 'Last 3 iterations:\n' 1090 watch = rootScope.watch('foo', listener);
1224 'a: 2 <= 1, b: 2 <= 1\n' 1091 rootScope.digest(); //init
1225 'a: 3 <= 2, b: 3 <= 2\n' 1092 expect(listener).toHaveBeenCalled();
1226 'a: 4 <= 3, b: 4 <= 3'); 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);
1227 }); 1247 });
1228 1248 rootScope.digest();
1229 1249 expect(rootScope.context['log']).toEqual('.=.');
1230 it(r'should always call the watchr with newVal and oldVal equal on the fir st run', 1250 }));
1231 inject((RootScope rootScope) { 1251
1232 var log = []; 1252 it(r'should run async in the same order as added', inject((RootScope rootSco pe) {
1233 var logger = (newVal, oldVal) { 1253 rootScope.context['log'] = '';
1234 var val = (newVal == oldVal || (newVal != oldVal && oldVal != newVal)) ? newVal : 'xxx'; 1254 rootScope.runAsync(() => rootScope.eval("log = log + 1"));
1235 log.add(val); 1255 rootScope.runAsync(() => rootScope.eval("log = log + 2"));
1236 }; 1256 rootScope.digest();
1237 1257 expect(rootScope.context['log']).toEqual('12');
1238 rootScope 1258 }));
1239 ..context['nanValue'] = double.NAN 1259 });
1240 ..context['nullValue'] = null 1260
1241 ..context['emptyString'] = '' 1261
1242 ..context['falseValue'] = false 1262 describe('domRead/domWrite', () {
1243 ..context['numberValue'] = 23 1263 it(r'should run writes before reads', () {
1244 ..watch('nanValue', logger) 1264 module((Module module) {
1245 ..watch('nullValue', logger)
1246 ..watch('emptyString', logger)
1247 ..watch('falseValue', logger)
1248 ..watch('numberValue', logger)
1249 ..digest();
1250
1251 expect(log.removeAt(0).isNaN).toEqual(true); //jasmine's toBe and toEqua l don't work well with NaNs
1252 expect(log).toEqual([null, '', false, 23]);
1253 log = [];
1254 rootScope.digest();
1255 expect(log).toEqual([]);
1256 }));
1257
1258
1259 it('should properly watch constants', (RootScope rootScope, Logger log) {
1260 rootScope.watch('[1, 2]', (v, o) => log([v, o]));
1261 expect(log).toEqual([]);
1262 rootScope.apply();
1263 expect(log).toEqual([[[1, 2], null]]);
1264 });
1265
1266
1267 it('should properly watch array of fields 1', (RootScope rootScope, Logger log) {
1268 rootScope.context['foo'] = 12;
1269 rootScope.context['bar'] = 34;
1270 rootScope.watch('[foo, bar]', (v, o) => log([v, o]));
1271 expect(log).toEqual([]);
1272 rootScope.apply();
1273 expect(log).toEqual([[[12, 34], null]]);
1274 log.clear();
1275
1276 rootScope.context['foo'] = 56;
1277 rootScope.context['bar'] = 78;
1278 rootScope.apply();
1279 expect(log).toEqual([[[56, 78], [12, 34]]]);
1280 });
1281
1282
1283 it('should properly watch array of fields 2', (RootScope rootScope, Logger log) {
1284 rootScope.context['foo'] = () => 12;
1285 rootScope.watch('foo()', (v, o) => log(v));
1286 expect(log).toEqual([]);
1287 rootScope.apply();
1288 expect(log).toEqual([12]);
1289 });
1290
1291
1292 it('should properly watch array of fields 3', (RootScope rootScope, Logger log) {
1293 rootScope.context['foo'] = 'abc';
1294 rootScope.watch('foo.contains("b")', (v, o) => log([v, o]));
1295 expect(log).toEqual([]);
1296 rootScope.apply();
1297 expect(log).toEqual([[true, null]]);
1298 log.clear();
1299 });
1300
1301
1302 it('should not trigger new watcher in the flush where it was added', (Scop e scope) {
1303 var log = [] ;
1304 scope.context['foo'] = () => 'foo';
1305 scope.context['name'] = 'misko';
1306 scope.context['list'] = [2, 3];
1307 scope.context['map'] = {'bar': 'chocolate'};
1308 scope.watch('1', (value, __) {
1309 expect(value).toEqual(1);
1310 scope.watch('foo()', (value, __) => log.add(value));
1311 scope.watch('name', (value, __) => log.add(value));
1312 scope.watch('(foo() + "-" + name).toUpperCase()', (value, __) => log.a dd(value));
1313 scope.watch('list', (value, __) => log.add(value));
1314 scope.watch('map', (value, __) => log.add(value));
1315 });
1316 scope.apply();
1317 expect(log).toEqual(['foo', 'misko', 'FOO-MISKO', [2, 3], {'bar': 'choco late'}]);
1318 });
1319
1320
1321 it('should allow multiple nested watches', (RootScope scope) {
1322 scope.watch('1', (_, __) {
1323 scope.watch('1', (_, __) {
1324 scope.watch('1', (_, __) {
1325 scope.watch('1', (_, __) {
1326 scope.watch('1', (_, __) {
1327 scope.watch('1', (_, __) {
1328 scope.watch('1', (_, __) {
1329 scope.watch('1', (_, __) {
1330 scope.watch('1', (_, __) {
1331 scope.watch('1', (_, __) {
1332 scope.watch('1', (_, __) {
1333 scope.watch('1', (_, __) {
1334 scope.watch('1', (_, __) {
1335 scope.watch('1', (_, __) {
1336 scope.watch('1', (_, __) {
1337 scope.watch('1', (_, __) {
1338 // make this deeper then ScopeTTL;
1339 });
1340 });
1341 });
1342 });
1343 });
1344 });
1345 });
1346 });
1347 });
1348 });
1349 });
1350 });
1351 });
1352 });
1353 });
1354 });
1355 expect(scope.apply).not.toThrow();
1356 });
1357
1358
1359 it('should properly watch array of fields 4', (RootScope rootScope, Logger log) {
1360 rootScope.watch('[ctrl.foo, ctrl.bar]', (v, o) => log([v, o]));
1361 expect(log).toEqual([]);
1362 rootScope.apply();
1363 expect(log).toEqual([[[null, null], null]]);
1364 log.clear();
1365
1366 rootScope.context['ctrl'] = {'foo': 56, 'bar': 78};
1367 rootScope.apply();
1368 expect(log).toEqual([[[56, 78], [null, null]]]);
1369 });
1370 });
1371
1372
1373 describe('special binding modes', () {
1374 it('should bind one time', (RootScope rootScope, Logger log) {
1375 rootScope.watch('foo', (v, _) => log('foo:$v'));
1376 rootScope.watch(':foo', (v, _) => log(':foo:$v'));
1377 rootScope.watch('::foo', (v, _) => log('::foo:$v'));
1378
1379 rootScope.apply();
1380 expect(log).toEqual(['foo:null']);
1381 log.clear();
1382
1383 rootScope.context['foo'] = true;
1384 rootScope.apply();
1385 expect(log).toEqual(['foo:true', ':foo:true', '::foo:true']);
1386 log.clear();
1387
1388 rootScope.context['foo'] = 123;
1389 rootScope.apply();
1390 expect(log).toEqual(['foo:123', ':foo:123']);
1391 log.clear();
1392
1393 rootScope.context['foo'] = null;
1394 rootScope.apply();
1395 expect(log).toEqual(['foo:null']);
1396 log.clear();
1397 });
1398 });
1399
1400
1401 describe('runAsync', () {
1402 it(r'should run callback before watch', (RootScope rootScope) {
1403 var log = '';
1404 rootScope.runAsync(() { log += 'parent.async;'; });
1405 rootScope.watch('value', (_, __) { log += 'parent.digest;'; });
1406 rootScope.digest();
1407 expect(log).toEqual('parent.async;parent.digest;');
1408 });
1409
1410 it(r'should cause a digest rerun', (RootScope rootScope) {
1411 rootScope.context['log'] = '';
1412 rootScope.context['value'] = 0;
1413 // NOTE(deboer): watch listener string functions not yet supported
1414 //rootScope.watch('value', 'log = log + ".";');
1415 rootScope.watch('value', (_, __) { rootScope.context['log'] += "."; });
1416 rootScope.watch('init', (_, __) {
1417 rootScope.runAsync(() => rootScope.eval('value = 123; log = log + "=" '));
1418 expect(rootScope.context['value']).toEqual(0);
1419 });
1420 rootScope.digest();
1421 expect(rootScope.context['log']).toEqual('.=.');
1422 });
1423
1424 it(r'should run async in the same order as added', (RootScope rootScope) {
1425 rootScope.context['log'] = '';
1426 rootScope.runAsync(() => rootScope.eval("log = log + 1"));
1427 rootScope.runAsync(() => rootScope.eval("log = log + 2"));
1428 rootScope.digest();
1429 expect(rootScope.context['log']).toEqual('12');
1430 });
1431 });
1432
1433
1434 describe('domRead/domWrite', () {
1435 beforeEachModule((Module module) {
1436 module.type(ExceptionHandler, implementedBy: LoggingExceptionHandler); 1265 module.type(ExceptionHandler, implementedBy: LoggingExceptionHandler);
1437 }); 1266 });
1438 1267 inject((RootScope rootScope, Logger logger, ExceptionHandler e) {
1439 it(r'should run writes before reads', (RootScope rootScope, Logger logger, ExceptionHandler e) {
1440 LoggingExceptionHandler exceptionHandler = e as LoggingExceptionHandler; 1268 LoggingExceptionHandler exceptionHandler = e as LoggingExceptionHandler;
1441 rootScope.domWrite(() { 1269 rootScope.domWrite(() {
1442 logger('write1'); 1270 logger('write1');
1443 rootScope.domWrite(() => logger('write2')); 1271 rootScope.domWrite(() => logger('write2'));
1444 throw 'write1'; 1272 throw 'write1';
1445 }); 1273 });
1446 rootScope.domRead(() { 1274 rootScope.domRead(() {
1447 logger('read1'); 1275 logger('read1');
1448 rootScope.domRead(() => logger('read2')); 1276 rootScope.domRead(() => logger('read2'));
1449 rootScope.domWrite(() => logger('write3')); 1277 rootScope.domWrite(() => logger('write3'));
1450 throw 'read1'; 1278 throw 'read1';
1451 }); 1279 });
1452 rootScope.watch('value', (_, __) => logger('observe'), 1280 rootScope.watch('value', (_, __) => logger('observe'), readOnly: true);
1453 canChangeModel: false);
1454 rootScope.flush(); 1281 rootScope.flush();
1455 expect(logger).toEqual(['write1', 'write2', 'observe', 'read1', 'read2', 'write3']); 1282 expect(logger).toEqual(['write1', 'write2', 'observe', 'read1', 'read2', 'write3']);
1456 expect(exceptionHandler.errors.length).toEqual(2); 1283 expect(exceptionHandler.errors.length).toEqual(2);
1457 expect(exceptionHandler.errors[0].error).toEqual('write1'); 1284 expect(exceptionHandler.errors[0].error).toEqual('write1');
1458 expect(exceptionHandler.errors[1].error).toEqual('read1'); 1285 expect(exceptionHandler.errors[1].error).toEqual('read1');
1459 }); 1286 });
1460 }); 1287 });
1288 });
1461 1289
1462 describe('exceptionHander', () { 1290 describe('exceptionHander', () {
1463 beforeEachModule((Module module) { 1291 it('should call ExceptionHandler on zone errors', () {
1292 module((Module module) {
1464 module.type(ExceptionHandler, implementedBy: LoggingExceptionHandler); 1293 module.type(ExceptionHandler, implementedBy: LoggingExceptionHandler);
1465 }); 1294 });
1466 1295 async((inject((RootScope rootScope, NgZone zone, ExceptionHandler e) {
1467 it('should call ExceptionHandler on zone errors',
1468 async((RootScope rootScope, VmTurnZone zone, ExceptionHandler e) {
1469 zone.run(() { 1296 zone.run(() {
1470 scheduleMicrotask(() => throw 'my error'); 1297 scheduleMicrotask(() => throw 'my error');
1471 }); 1298 });
1472 var errors = (e as LoggingExceptionHandler).errors; 1299 var errors = (e as LoggingExceptionHandler).errors;
1473 expect(errors.length).toEqual(1); 1300 expect(errors.length).toEqual(1);
1474 expect(errors.first.error).toEqual('my error'); 1301 expect(errors.first.error).toEqual('my error');
1475 })); 1302 })));
1303 });
1476 1304
1477 it('should call ExceptionHandler on digest errors', 1305 it('should call ExceptionHandler on digest errors', () {
1478 async((RootScope rootScope, VmTurnZone zone, ExceptionHandler e) { 1306 module((Module module) {
1307 module.type(ExceptionHandler, implementedBy: LoggingExceptionHandler);
1308 });
1309 async((inject((RootScope rootScope, NgZone zone, ExceptionHandler e) {
1479 rootScope.context['badOne'] = () => new Map(); 1310 rootScope.context['badOne'] = () => new Map();
1480 rootScope.watch('badOne()', (_, __) => null); 1311 rootScope.watch('badOne()', (_, __) => null);
1481 1312
1482 try { 1313 try {
1483 zone.run(() => null); 1314 zone.run(() => null);
1484 } catch(_) {} 1315 } catch(_) {}
1485 1316
1486 var errors = (e as LoggingExceptionHandler).errors; 1317 var errors = (e as LoggingExceptionHandler).errors;
1487 expect(errors.length).toEqual(1); 1318 expect(errors.length).toEqual(1);
1488 expect(errors.first.error, startsWith('Model did not stabilize')); 1319 expect(errors.first.error, startsWith('Model did not stabilize'));
1489 })); 1320 })));
1490 });
1491
1492 describe('logging', () {
1493 it('should log a message on digest if reporting is enabled', (RootScope ro otScope,
1494 Injector injector) {
1495 ScopeStatsConfig config = injector.get(ScopeStatsConfig);
1496 config.emit = true;
1497 rootScope.digest();
1498 expect((injector.get(ScopeStatsEmitter) as MockScopeStatsEmitter).invoke d)
1499 .toEqual(true);
1500 });
1501
1502 it('should log a message on flush if reporting is enabled', (RootScope roo tScope,
1503 Injector injector) {
1504 ScopeStatsConfig config = injector.get(ScopeStatsConfig);
1505 config.emit = true;
1506 rootScope.flush();
1507 expect((injector.get(ScopeStatsEmitter) as MockScopeStatsEmitter).invoke d)
1508 .toEqual(true);
1509 });
1510
1511 it('should not log a message on digest if reporting is disabled', (RootSco pe rootScope,
1512 Injector injector) {
1513 rootScope.digest();
1514 expect((injector.get(ScopeStatsEmitter) as MockScopeStatsEmitter).invoke d)
1515 .toEqual(false);
1516 });
1517
1518 it('should not log a message on flush if reporting is disabled', (RootScop e rootScope,
1519 Injector injector) {
1520 rootScope.flush();
1521 expect((injector.get(ScopeStatsEmitter) as MockScopeStatsEmitter).invoke d)
1522 .toEqual(false);
1523 });
1524
1525 it('can be turned on at runtime', (RootScope rootScope, Injector injector) {
1526 rootScope.digest();
1527 expect((injector.get(ScopeStatsEmitter) as MockScopeStatsEmitter).invoke d)
1528 .toEqual(false);
1529 ScopeStatsConfig config = injector.get(ScopeStatsConfig);
1530 config.emit = true;
1531 rootScope.digest();
1532 expect((injector.get(ScopeStatsEmitter) as MockScopeStatsEmitter).invoke d)
1533 .toEqual(true);
1534 });
1535 }); 1321 });
1536 }); 1322 });
1537 } 1323 });
1538 1324
1539 @Formatter(name: 'identity') 1325 @NgFilter(name: 'multiply')
1540 class _IdentityFilter {
1541 Logger logger;
1542 _IdentityFilter(this.logger);
1543 call(v) {
1544 logger('identity');
1545 return v;
1546 }
1547 }
1548
1549 @Formatter(name: 'keys')
1550 class _MapKeys {
1551 Logger logger;
1552 _MapKeys(this.logger);
1553 call(Map m) {
1554 logger('keys');
1555 return m.keys;
1556 }
1557 }
1558
1559 @Formatter(name: 'multiply')
1560 class _MultiplyFilter { 1326 class _MultiplyFilter {
1561 call(a, b) => a * b; 1327 call(a, b) => a * b;
1562 } 1328 }
1563 1329
1564 @Formatter(name: 'listHead') 1330 @NgFilter(name: 'listHead')
1565 class _ListHeadFilter { 1331 class _ListHeadFilter {
1566 Logger logger; 1332 Logger logger;
1567 _ListHeadFilter(this.logger); 1333 _ListHeadFilter(Logger this.logger);
1568 call(list, head) { 1334 call(list, head) {
1569 logger('listHead'); 1335 logger('listHead');
1570 return [head]..addAll(list); 1336 return [head]..addAll(list);
1571 } 1337 }
1572 } 1338 }
1573 1339
1574 @Formatter(name: 'listTail') 1340
1341 @NgFilter(name: 'listTail')
1575 class _ListTailFilter { 1342 class _ListTailFilter {
1576 Logger logger; 1343 Logger logger;
1577 _ListTailFilter(this.logger); 1344 _ListTailFilter(Logger this.logger);
1578 call(list, tail) { 1345 call(list, tail) {
1579 logger('listTail'); 1346 logger('listTail');
1580 return new List.from(list)..add(tail); 1347 return new List.from(list)..add(tail);
1581 } 1348 }
1582 } 1349 }
1583 1350
1584 @Formatter(name: 'sort') 1351 @NgFilter(name: 'sort')
1585 class _SortFilter { 1352 class _SortFilter {
1586 Logger logger; 1353 Logger logger;
1587 _SortFilter(this.logger); 1354 _SortFilter(Logger this.logger);
1588 call(list) { 1355 call(list) {
1589 logger('sort'); 1356 logger('sort');
1590 return new List.from(list)..sort(); 1357 return new List.from(list)..sort();
1591 } 1358 }
1592 } 1359 }
1593 1360
1594 @Formatter(name:'newFilter') 1361 @NgFilter(name:'newFilter')
1595 class FilterOne { 1362 class FilterOne {
1596 call(String str) { 1363 call(String str) {
1597 return '$str 1'; 1364 return '$str 1';
1598 } 1365 }
1599 } 1366 }
1600 1367
1601 @Formatter(name:'newFilter') 1368 @NgFilter(name:'newFilter')
1602 class FilterTwo { 1369 class FilterTwo {
1603 call(String str) { 1370 call(String str) {
1604 return '$str 2'; 1371 return '$str 2';
1605 } 1372 }
1606 } 1373 }
1607
1608 class MockScopeStatsEmitter implements ScopeStatsEmitter {
1609 bool invoked = false;
1610
1611 void emitMessage(String message) {}
1612
1613 void emitSummary(List<int> digestTimes, int flushPhaseDuration,
1614 int assertFlushPhaseDuration) {}
1615
1616 void emit(String phaseOrLoopNo, AvgStopwatch fieldStopwatch,
1617 AvgStopwatch evalStopwatch, AvgStopwatch processStopwatch) {
1618 invoked = true;
1619 }
1620 }
1621
1622 class UnstableList {
1623 List get list => new List.generate(3, (i) => i);
1624 }
1625
OLDNEW
« no previous file with comments | « third_party/pkg/angular/test/core/registry_spec.dart ('k') | third_party/pkg/angular/test/core/templateurl_spec.dart » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698