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