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

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

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