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