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