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