@angular-wave/angular.ts 0.4.4 → 0.4.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (120) hide show
  1. package/dist/angular-ts.esm.js +2 -2
  2. package/dist/angular-ts.umd.js +2 -12
  3. package/index.html +3 -74
  4. package/package.json +1 -1
  5. package/src/angular.spec.js +5 -0
  6. package/src/animations/animate-css.js +13 -5
  7. package/src/animations/animate-queue.js +21 -22
  8. package/src/animations/animate-runner.js +8 -4
  9. package/src/animations/animate.md +1 -1
  10. package/src/animations/animate.spec.js +21 -0
  11. package/src/animations/animation.js +1 -1
  12. package/src/binding.spec.js +1 -0
  13. package/src/core/compile/compile.js +26 -25
  14. package/src/core/compile/compile.spec.js +266 -17
  15. package/src/core/controller/controller.js +0 -2
  16. package/src/core/di/injector.md +1 -1
  17. package/src/core/di/injector.spec.js +2 -0
  18. package/src/core/di/internal-injector.js +1 -2
  19. package/src/core/interpolate/interpolate.js +3 -16
  20. package/src/core/interpolate/interpolate.spec.js +16 -70
  21. package/src/core/interval/interval-factory.js +50 -0
  22. package/src/core/interval/interval.html +18 -0
  23. package/src/core/interval/interval.js +77 -0
  24. package/src/core/interval/interval.md +123 -0
  25. package/src/core/interval/interval.spec.js +280 -0
  26. package/src/core/interval/interval.test.js +1 -1
  27. package/src/core/location/location.js +47 -39
  28. package/src/core/location/location.spec.js +27 -27
  29. package/src/core/on.spec.js +7 -0
  30. package/src/core/parse/interpreter.js +7 -10
  31. package/src/core/parse/parse.js +5 -26
  32. package/src/core/parse/parse.spec.js +91 -95
  33. package/src/core/prop.spec.js +60 -4
  34. package/src/core/q/q.html +18 -0
  35. package/src/core/q/q.js +472 -0
  36. package/src/core/q/q.md +211 -0
  37. package/src/core/q/q.spec.js +2748 -0
  38. package/src/core/q/q.test.js +12 -0
  39. package/src/core/sce/sce.spec.js +8 -0
  40. package/src/core/{model/model.html → scope/scope.html} +1 -1
  41. package/src/core/scope/scope.js +16 -15
  42. package/src/core/scope/scope.spec.js +1959 -24
  43. package/src/core/scope/scope.test.js +12 -0
  44. package/src/core/timeout/timeout.html +18 -0
  45. package/src/core/timeout/timeout.js +109 -0
  46. package/src/core/timeout/timeout.spec.js +354 -0
  47. package/src/core/timeout/timout.test.js +12 -0
  48. package/src/core/url-utils/url-utils.spec.js +1 -1
  49. package/src/directive/aria/aria.js +6 -3
  50. package/src/directive/aria/aria.spec.js +87 -0
  51. package/src/directive/attrs/attrs.spec.js +5 -0
  52. package/src/directive/attrs/boolean.spec.js +15 -0
  53. package/src/directive/attrs/element-style.spec.js +8 -0
  54. package/src/directive/attrs/src.spec.js +7 -0
  55. package/src/directive/bind/bind.spec.js +33 -0
  56. package/src/directive/bind/bing-html.spec.js +3 -0
  57. package/src/directive/class/class.js +3 -3
  58. package/src/directive/class/class.spec.js +75 -9
  59. package/src/directive/controller/controller.spec.js +13 -0
  60. package/src/directive/events/click.spec.js +3 -0
  61. package/src/directive/events/event.spec.js +6 -0
  62. package/src/directive/form/form.js +3 -2
  63. package/src/directive/form/form.spec.js +65 -0
  64. package/src/directive/if/if.spec.js +4 -0
  65. package/src/directive/include/include.spec.js +59 -8
  66. package/src/directive/init/init.js +2 -6
  67. package/src/directive/init/init.spec.js +2 -0
  68. package/src/directive/input/input.spec.js +136 -0
  69. package/src/directive/messages/messages.spec.js +35 -4
  70. package/src/directive/model/model.js +25 -18
  71. package/src/directive/model/model.spec.js +49 -2
  72. package/src/directive/model-options/model-options.spec.js +6 -0
  73. package/src/directive/non-bindable/non-bindable.spec.js +1 -0
  74. package/src/directive/observe/observe.js +1 -0
  75. package/src/directive/observe/observe.spec.js +1 -0
  76. package/src/directive/options/options.spec.js +34 -0
  77. package/src/directive/ref/href.spec.js +15 -0
  78. package/src/directive/repeat/repeat.spec.js +135 -8
  79. package/src/directive/script/script.spec.js +2 -0
  80. package/src/directive/select/select.js +3 -3
  81. package/src/directive/select/select.spec.js +96 -0
  82. package/src/directive/show-hide/show-hide.js +2 -2
  83. package/src/directive/show-hide/show-hide.spec.js +19 -8
  84. package/src/directive/style/style.spec.js +7 -0
  85. package/src/directive/switch/switch.spec.js +5 -5
  86. package/src/directive/validators/validators.spec.js +1 -0
  87. package/src/loader.js +1 -0
  88. package/src/public.js +10 -2
  89. package/src/router/common/coreservices.js +2 -0
  90. package/src/router/directives/state-directives.js +14 -6
  91. package/src/router/directives/state-directives.spec.js +83 -0
  92. package/src/router/directives/view-directive.js +13 -4
  93. package/src/router/directives/view-directive.spec.js +71 -25
  94. package/src/router/hooks/lazy-load.js +2 -2
  95. package/src/router/hooks/views.js +5 -3
  96. package/src/router/resolve/resolvable.js +6 -3
  97. package/src/router/resolve/resolve-context.js +2 -2
  98. package/src/router/state/state-service.js +4 -4
  99. package/src/router/state/state.spec.js +5 -2
  100. package/src/router/state/state.test.js +1 -1
  101. package/src/router/state/views.js +10 -7
  102. package/src/router/template-factory.js +6 -3
  103. package/src/router/template-factory.spec.js +4 -0
  104. package/src/router/transition/transition-hook.js +1 -1
  105. package/src/router/transition/transition.js +1 -1
  106. package/src/router/view-hook.spec.js +2 -2
  107. package/src/router/view-scroll.js +6 -4
  108. package/src/services/browser.js +5 -8
  109. package/src/services/http/http.js +9 -6
  110. package/src/services/http/http.spec.js +31 -30
  111. package/src/services/http/template-request.spec.js +10 -0
  112. package/src/services/http-backend/http-backend.spec.js +3 -3
  113. package/src/services/template-request.js +4 -2
  114. package/src/shared/common.js +2 -1
  115. package/types/core/location/location.d.ts +36 -31
  116. package/types/core/parse/parse.d.ts +0 -26
  117. package/types/core/scope/scope.d.ts +11 -11
  118. package/src/core/model/model.js +0 -944
  119. package/src/core/model/model.spec.js +0 -3012
  120. package/types/core/model/model.d.ts +0 -204
@@ -1,4 +1,4 @@
1
- import { $$asyncQueue, Scope, ScopePhase } from "./scope";
1
+ import { $$asyncQueue, Scope } from "./scope";
2
2
  import { extend, sliceArgs } from "../../shared/utils";
3
3
  import { Angular } from "../../loader";
4
4
  import { createInjector } from "../di/injector";
@@ -9,7 +9,6 @@ describe("Scope", function () {
9
9
  let $browser;
10
10
  let logs;
11
11
  let scope;
12
- let injector;
13
12
 
14
13
  beforeEach(() => {
15
14
  logs = [];
@@ -24,7 +23,7 @@ describe("Scope", function () {
24
23
  };
25
24
  });
26
25
 
27
- injector = createInjector(["myModule"]);
26
+ let injector = createInjector(["myModule"]);
28
27
  $parse = injector.get("$parse");
29
28
  $browser = injector.get("$browser");
30
29
 
@@ -197,6 +196,822 @@ describe("Scope", function () {
197
196
  });
198
197
 
199
198
  describe("$watch/$digest", () => {
199
+ it("calls the listener function of a watch on first $digest", function () {
200
+ var watchFn = function () {
201
+ return "wat";
202
+ };
203
+ var listenerFn = jasmine.createSpy();
204
+ $rootScope.$watch(watchFn, listenerFn);
205
+ $rootScope.$digest();
206
+ expect(listenerFn).toHaveBeenCalled();
207
+ });
208
+
209
+ it("calls the watch function with the scope as the argument", function () {
210
+ var watchFn = jasmine.createSpy();
211
+ var listenerFn = function () {};
212
+ $rootScope.$watch(watchFn, listenerFn);
213
+ $rootScope.$digest();
214
+ expect(watchFn).toHaveBeenCalledWith($rootScope);
215
+ });
216
+
217
+ it("calls the listener function when the watched value changes", function () {
218
+ scope.someValue = "a";
219
+ scope.counter = 0;
220
+ scope.$watch(
221
+ function (scope) {
222
+ return scope.someValue;
223
+ },
224
+ function (newValue, oldValue, scope) {
225
+ scope.counter++;
226
+ },
227
+ );
228
+ expect(scope.counter).toBe(0);
229
+ scope.$digest();
230
+ expect(scope.counter).toBe(1);
231
+ scope.$digest();
232
+ expect(scope.counter).toBe(1);
233
+ scope.someValue = "b";
234
+ expect(scope.counter).toBe(1);
235
+ scope.$digest();
236
+ expect(scope.counter).toBe(2);
237
+ });
238
+
239
+ it("should watch and fire on simple property change", () => {
240
+ const spy = jasmine.createSpy();
241
+ $rootScope.$watch("name", spy);
242
+ $rootScope.$digest();
243
+ spy.calls.reset();
244
+
245
+ expect(spy).not.toHaveBeenCalled();
246
+ $rootScope.$digest();
247
+ expect(spy).not.toHaveBeenCalled();
248
+ $rootScope.name = "misko";
249
+ $rootScope.$digest();
250
+ expect(spy).toHaveBeenCalledWith("misko", undefined, $rootScope);
251
+ });
252
+
253
+ it("should not expose the `inner working of watch", () => {
254
+ function Getter() {
255
+ expect(this).toBeUndefined();
256
+ return "foo";
257
+ }
258
+ function Listener() {
259
+ expect(this).toBeUndefined();
260
+ }
261
+
262
+ $rootScope.$watch(Getter, Listener);
263
+ $rootScope.$digest();
264
+ });
265
+
266
+ it("should watch and fire on expression change", () => {
267
+ const spy = jasmine.createSpy();
268
+ $rootScope.$watch("name.first", spy);
269
+ $rootScope.$digest();
270
+ spy.calls.reset();
271
+
272
+ $rootScope.name = {};
273
+ expect(spy).not.toHaveBeenCalled();
274
+ $rootScope.$digest();
275
+ expect(spy).not.toHaveBeenCalled();
276
+ $rootScope.name.first = "misko";
277
+ $rootScope.$digest();
278
+ expect(spy).toHaveBeenCalled();
279
+ });
280
+
281
+ it("should decrement the watcherCount when destroying a child scope", () => {
282
+ const child1 = $rootScope.$new();
283
+ const child2 = $rootScope.$new();
284
+ const grandChild1 = child1.$new();
285
+ const grandChild2 = child2.$new();
286
+ child1.$watch("a", () => {});
287
+ child2.$watch("a", () => {});
288
+ grandChild1.$watch("a", () => {});
289
+ grandChild2.$watch("a", () => {});
290
+
291
+ expect($rootScope.$$watchersCount).toBe(4);
292
+ expect(child1.$$watchersCount).toBe(2);
293
+ expect(child2.$$watchersCount).toBe(2);
294
+ expect(grandChild1.$$watchersCount).toBe(1);
295
+ expect(grandChild2.$$watchersCount).toBe(1);
296
+
297
+ grandChild2.$destroy();
298
+ expect(child2.$$watchersCount).toBe(1);
299
+ expect($rootScope.$$watchersCount).toBe(3);
300
+ child1.$destroy();
301
+ expect($rootScope.$$watchersCount).toBe(1);
302
+ });
303
+
304
+ it("should decrement the watcherCount when calling the remove function", () => {
305
+ const child1 = $rootScope.$new();
306
+ const child2 = $rootScope.$new();
307
+ const grandChild1 = child1.$new();
308
+ const grandChild2 = child2.$new();
309
+ let remove1;
310
+ let remove2;
311
+
312
+ remove1 = child1.$watch("a", () => {});
313
+ child2.$watch("a", () => {});
314
+ grandChild1.$watch("a", () => {});
315
+ remove2 = grandChild2.$watch("a", () => {});
316
+
317
+ remove2();
318
+ expect(grandChild2.$$watchersCount).toBe(0);
319
+ expect(child2.$$watchersCount).toBe(1);
320
+ expect($rootScope.$$watchersCount).toBe(3);
321
+ remove1();
322
+ expect(grandChild1.$$watchersCount).toBe(1);
323
+ expect(child1.$$watchersCount).toBe(1);
324
+ expect($rootScope.$$watchersCount).toBe(2);
325
+
326
+ // Execute everything a second time to be sure that calling the remove function
327
+ // several times, it only decrements the counter once
328
+ remove2();
329
+ expect(child2.$$watchersCount).toBe(1);
330
+ expect($rootScope.$$watchersCount).toBe(2);
331
+ remove1();
332
+ expect(child1.$$watchersCount).toBe(1);
333
+ expect($rootScope.$$watchersCount).toBe(2);
334
+ });
335
+
336
+ describe("constants cleanup", () => {
337
+ beforeEach(() => (logs = []));
338
+ it("should remove $watch of constant literals after initial digest", () => {
339
+ $rootScope.$watch("[]", () => {});
340
+ $rootScope.$watch("{}", () => {});
341
+ $rootScope.$watch("1", () => {});
342
+ $rootScope.$watch('"foo"', () => {});
343
+ expect($rootScope.$$watchers.length).not.toEqual(0);
344
+ $rootScope.$digest();
345
+
346
+ expect($rootScope.$$watchers.length).toEqual(0);
347
+ });
348
+
349
+ it("should remove $watchCollection of constant literals after initial digest", () => {
350
+ $rootScope.$watchCollection("[]", () => {});
351
+ $rootScope.$watchCollection("{}", () => {});
352
+ $rootScope.$watchCollection("1", () => {});
353
+ $rootScope.$watchCollection('"foo"', () => {});
354
+ expect($rootScope.$$watchers.length).not.toEqual(0);
355
+ $rootScope.$digest();
356
+
357
+ expect($rootScope.$$watchers.length).toEqual(0);
358
+ });
359
+
360
+ it("should remove $watchGroup of constant literals after initial digest", () => {
361
+ $rootScope.$watchGroup(["[]", "{}", "1", '"foo"'], () => {});
362
+ expect($rootScope.$$watchers.length).not.toEqual(0);
363
+ $rootScope.$digest();
364
+
365
+ expect($rootScope.$$watchers.length).toEqual(0);
366
+ });
367
+
368
+ it("should remove $watch of filtered constant literals after initial digest", () => {
369
+ $rootScope.$watch('[1] | filter:"x"', () => {});
370
+ $rootScope.$watch("1 | limitTo:2", () => {});
371
+ expect($rootScope.$$watchers.length).not.toEqual(0);
372
+ $rootScope.$digest();
373
+
374
+ expect($rootScope.$$watchers.length).toEqual(0);
375
+ });
376
+
377
+ it("should remove $watchCollection of filtered constant literals after initial digest", () => {
378
+ $rootScope.$watchCollection('[1] | filter:"x"', () => {});
379
+ expect($rootScope.$$watchers.length).not.toEqual(0);
380
+ $rootScope.$digest();
381
+
382
+ expect($rootScope.$$watchers.length).toEqual(0);
383
+ });
384
+
385
+ it("should remove $watchGroup of filtered constant literals after initial digest", () => {
386
+ $rootScope.$watchGroup(['[1] | filter:"x"', "1 | limitTo:2"], () => {});
387
+ expect($rootScope.$$watchers.length).not.toEqual(0);
388
+ $rootScope.$digest();
389
+
390
+ expect($rootScope.$$watchers.length).toEqual(0);
391
+ });
392
+
393
+ it("should remove $watch of constant expressions after initial digest", () => {
394
+ $rootScope.$watch("1 + 1", () => {});
395
+ $rootScope.$watch('"a" + "b"', () => {});
396
+ $rootScope.$watch('"ab".length', () => {});
397
+ $rootScope.$watch("[].length", () => {});
398
+ $rootScope.$watch("(1 + 1) | limitTo:2", () => {});
399
+ expect($rootScope.$$watchers.length).not.toEqual(0);
400
+ $rootScope.$digest();
401
+
402
+ expect($rootScope.$$watchers.length).toEqual(0);
403
+ });
404
+ });
405
+
406
+ describe("onetime cleanup", () => {
407
+ it("should clean up stable watches on the watch queue", () => {
408
+ $rootScope.$watch("::foo", () => {});
409
+ expect($rootScope.$$watchers.length).toEqual(1);
410
+ $rootScope.$digest();
411
+ expect($rootScope.$$watchers.length).toEqual(1);
412
+
413
+ $rootScope.foo = "foo";
414
+ $rootScope.$digest();
415
+ expect($rootScope.$$watchers.length).toEqual(0);
416
+ });
417
+
418
+ it("should clean up stable watches from $watchCollection", () => {
419
+ $rootScope.$watchCollection("::foo", () => {});
420
+ expect($rootScope.$$watchers.length).toEqual(1);
421
+
422
+ $rootScope.$digest();
423
+ expect($rootScope.$$watchers.length).toEqual(1);
424
+
425
+ $rootScope.foo = [];
426
+ $rootScope.$digest();
427
+ expect($rootScope.$$watchers.length).toEqual(0);
428
+ });
429
+
430
+ it("should clean up stable watches from $watchCollection literals", () => {
431
+ $rootScope.$watchCollection("::[foo, bar]", () => {});
432
+ expect($rootScope.$$watchers.length).toEqual(1);
433
+
434
+ $rootScope.$digest();
435
+ expect($rootScope.$$watchers.length).toEqual(1);
436
+
437
+ $rootScope.foo = 1;
438
+ $rootScope.$digest();
439
+ expect($rootScope.$$watchers.length).toEqual(1);
440
+
441
+ $rootScope.foo = 2;
442
+ $rootScope.$digest();
443
+ expect($rootScope.$$watchers.length).toEqual(1);
444
+
445
+ $rootScope.bar = 3;
446
+ $rootScope.$digest();
447
+ expect($rootScope.$$watchers.length).toEqual(0);
448
+ });
449
+
450
+ it("should clean up stable watches from $watchGroup", () => {
451
+ $rootScope.$watchGroup(["::foo", "::bar"], () => {});
452
+ expect($rootScope.$$watchers.length).toEqual(2);
453
+
454
+ $rootScope.$digest();
455
+ expect($rootScope.$$watchers.length).toEqual(2);
456
+
457
+ $rootScope.foo = "foo";
458
+ $rootScope.$digest();
459
+ expect($rootScope.$$watchers.length).toEqual(1);
460
+
461
+ $rootScope.bar = "bar";
462
+ $rootScope.$digest();
463
+ expect($rootScope.$$watchers.length).toEqual(0);
464
+ });
465
+ });
466
+
467
+ it("should delegate exceptions", () => {
468
+ $rootScope.$watch("a", () => {
469
+ throw new Error("abc");
470
+ });
471
+ $rootScope.a = 1;
472
+ $rootScope.$digest();
473
+ expect(logs[0]).toMatch(/abc/);
474
+ });
475
+
476
+ it("should fire watches in order of addition", () => {
477
+ // this is not an external guarantee, just our own sanity
478
+ logs = "";
479
+ $rootScope.$watch("a", () => {
480
+ logs += "a";
481
+ });
482
+ $rootScope.$watch("b", () => {
483
+ logs += "b";
484
+ });
485
+ // constant expressions have slightly different handling,
486
+ // let's ensure they are kept in the same list as others
487
+ $rootScope.$watch("1", () => {
488
+ logs += "1";
489
+ });
490
+ $rootScope.$watch("c", () => {
491
+ logs += "c";
492
+ });
493
+ $rootScope.$watch("2", () => {
494
+ logs += "2";
495
+ });
496
+ $rootScope.a = $rootScope.b = $rootScope.c = 1;
497
+ $rootScope.$digest();
498
+ expect(logs).toEqual("ab1c2");
499
+ });
500
+
501
+ it("should call child $watchers in addition order", () => {
502
+ // this is not an external guarantee, just our own sanity
503
+ logs = "";
504
+ const childA = $rootScope.$new();
505
+ const childB = $rootScope.$new();
506
+ const childC = $rootScope.$new();
507
+ childA.$watch("a", () => {
508
+ logs += "a";
509
+ });
510
+ childB.$watch("b", () => {
511
+ logs += "b";
512
+ });
513
+ childC.$watch("c", () => {
514
+ logs += "c";
515
+ });
516
+ childA.a = childB.b = childC.c = 1;
517
+ $rootScope.$digest();
518
+ expect(logs).toEqual("abc");
519
+ });
520
+
521
+ it("should allow $digest on a child scope with and without a right sibling", () => {
522
+ // tests a traversal edge case which we originally missed
523
+ logs = "";
524
+ const childA = $rootScope.$new();
525
+ const childB = $rootScope.$new();
526
+
527
+ $rootScope.$watch(() => {
528
+ logs += "r";
529
+ });
530
+ childA.$watch(() => {
531
+ logs += "a";
532
+ });
533
+ childB.$watch(() => {
534
+ logs += "b";
535
+ });
536
+
537
+ // init
538
+ $rootScope.$digest();
539
+ expect(logs).toBe("rabrab");
540
+
541
+ logs = "";
542
+ childA.$digest();
543
+ expect(logs).toBe("a");
544
+
545
+ logs = "";
546
+ childB.$digest();
547
+ expect(logs).toBe("b");
548
+ });
549
+
550
+ it("should repeat watch cycle while model changes are identified", () => {
551
+ logs = "";
552
+ $rootScope.$watch("c", (v) => {
553
+ $rootScope.d = v;
554
+ logs += "c";
555
+ });
556
+ $rootScope.$watch("b", (v) => {
557
+ $rootScope.c = v;
558
+ logs += "b";
559
+ });
560
+ $rootScope.$watch("a", (v) => {
561
+ $rootScope.b = v;
562
+ logs += "a";
563
+ });
564
+ $rootScope.$digest();
565
+ logs = "";
566
+ $rootScope.a = 1;
567
+ $rootScope.$digest();
568
+ expect($rootScope.b).toEqual(1);
569
+ expect($rootScope.c).toEqual(1);
570
+ expect($rootScope.d).toEqual(1);
571
+ expect(logs).toEqual("abc");
572
+ });
573
+
574
+ it("should repeat watch cycle from the root element", () => {
575
+ logs = "";
576
+ const child = $rootScope.$new();
577
+ $rootScope.$watch(() => {
578
+ logs += "a";
579
+ });
580
+ child.$watch(() => {
581
+ logs += "b";
582
+ });
583
+ $rootScope.$digest();
584
+ expect(logs).toEqual("abab");
585
+ });
586
+
587
+ it("should prevent infinite recursion and print watcher expression", () => {
588
+ $rootScope.$watch("a", function () {
589
+ $rootScope.b++;
590
+ });
591
+ $rootScope.$watch("b", function () {
592
+ $rootScope.a++;
593
+ });
594
+ $rootScope.a = $rootScope.b = 0;
595
+ expect(function () {
596
+ $rootScope.$digest();
597
+ }).toThrow();
598
+
599
+ expect($rootScope.$$phase).toBe(0);
600
+ });
601
+
602
+ it("should prevent infinite recursion and print watcher function name or body", () => {
603
+ $rootScope.$watch(
604
+ () => $rootScope.a,
605
+ () => {
606
+ $rootScope.b++;
607
+ },
608
+ );
609
+ $rootScope.$watch(
610
+ () => $rootScope.b,
611
+ () => {
612
+ $rootScope.a++;
613
+ },
614
+ );
615
+ $rootScope.a = $rootScope.b = 0;
616
+
617
+ try {
618
+ $rootScope.$digest();
619
+ throw new Error("Should have thrown exception");
620
+ } catch (e) {
621
+ console.error(e);
622
+ expect(e.message.match(/rootScope.a/g).length).toBeTruthy();
623
+ expect(e.message.match(/rootScope.b/g).length).toBeTruthy();
624
+ }
625
+ });
626
+
627
+ // it("should prevent infinite loop when creating and resolving a promise in a watched expression", () => {
628
+ // module(($rootScopeProvider) => {
629
+ // $rootScopeProvider.digestTtl(10);
630
+ // });
631
+ // () => {
632
+ // const d = $q.defer();
633
+
634
+ // d.resolve("Hello, world.");
635
+ // $rootScope.$watch(
636
+ // () => {
637
+ // const $d2 = $q.defer();
638
+ // $d2.resolve("Goodbye.");
639
+ // $d2.promise.then(() => {});
640
+ // return d.promise;
641
+ // },
642
+ // () => 0,
643
+ // );
644
+
645
+ // expect(() => {
646
+ // $rootScope.$digest();
647
+ // }).toThrow(
648
+ // "$rootScope",
649
+ // "infdig",
650
+ // "10 $digest() iterations reached. Aborting!\n" +
651
+ // "Watchers fired in the last 5 iterations: []",
652
+ // );
653
+
654
+ // expect($rootScope.$$phase).toBeNull();
655
+ // });
656
+ // });
657
+
658
+ it("should not fire upon $watch registration on initial $digest", () => {
659
+ logs = "";
660
+ $rootScope.a = 1;
661
+ $rootScope.$watch("a", () => {
662
+ logs += "a";
663
+ });
664
+ $rootScope.$watch("b", () => {
665
+ logs += "b";
666
+ });
667
+ $rootScope.$digest();
668
+ logs = "";
669
+ $rootScope.$digest();
670
+ expect(logs).toEqual("");
671
+ });
672
+
673
+ it("should watch objects", () => {
674
+ logs = "";
675
+ $rootScope.a = [];
676
+ $rootScope.b = {};
677
+ $rootScope.$watch(
678
+ "a",
679
+ (value) => {
680
+ logs += ".";
681
+ expect(value).toBe($rootScope.a);
682
+ },
683
+ true,
684
+ );
685
+ $rootScope.$watch(
686
+ "b",
687
+ (value) => {
688
+ logs += "!";
689
+ expect(value).toBe($rootScope.b);
690
+ },
691
+ true,
692
+ );
693
+ $rootScope.$digest();
694
+ logs = "";
695
+
696
+ $rootScope.a.push({});
697
+ $rootScope.b.name = "";
698
+
699
+ $rootScope.$digest();
700
+ expect(logs).toEqual(".!");
701
+ });
702
+
703
+ it("should watch functions", () => {
704
+ $rootScope.fn = function () {
705
+ return "a";
706
+ };
707
+ $rootScope.$watch("fn", (fn) => {
708
+ logs.push(fn());
709
+ });
710
+ $rootScope.$digest();
711
+ expect(logs).toEqual(["a"]);
712
+ $rootScope.fn = function () {
713
+ return "b";
714
+ };
715
+ $rootScope.$digest();
716
+ expect(logs).toEqual(["a", "b"]);
717
+ });
718
+
719
+ it("should prevent $digest recursion", () => {
720
+ let callCount = 0;
721
+ $rootScope.$watch("name", () => {
722
+ expect(() => {
723
+ $rootScope.$digest();
724
+ }).toThrowError(/digest already in progress/);
725
+ callCount++;
726
+ });
727
+ $rootScope.name = "a";
728
+ $rootScope.$digest();
729
+ expect(callCount).toEqual(1);
730
+ });
731
+
732
+ it("should allow a watch to be added while in a digest", () => {
733
+ const watch1 = jasmine.createSpy("watch1");
734
+ const watch2 = jasmine.createSpy("watch2");
735
+ $rootScope.$watch("foo", () => {
736
+ $rootScope.$watch("foo", watch1);
737
+ $rootScope.$watch("foo", watch2);
738
+ });
739
+ $rootScope.$apply("foo = true");
740
+ expect(watch1).toHaveBeenCalled();
741
+ expect(watch2).toHaveBeenCalled();
742
+ });
743
+
744
+ it("should not skip watchers when adding new watchers during digest", () => {
745
+ const watchFn1 = function () {
746
+ logs.push(1);
747
+ };
748
+ const watchFn2 = function () {
749
+ logs.push(2);
750
+ };
751
+ const watchFn3 = function () {
752
+ logs.push(3);
753
+ };
754
+ const addWatcherOnce = function (newValue, oldValue) {
755
+ if (newValue === oldValue) {
756
+ $rootScope.$watch(watchFn3);
757
+ }
758
+ };
759
+
760
+ $rootScope.$watch(watchFn1, addWatcherOnce);
761
+ $rootScope.$watch(watchFn2);
762
+
763
+ $rootScope.$digest();
764
+
765
+ expect(logs).toEqual([1, 2, 3, 1, 2, 3]);
766
+ });
767
+
768
+ it("should not run the current watcher twice when removing a watcher during digest", () => {
769
+ let removeWatcher3;
770
+
771
+ const watchFn3 = function () {
772
+ logs.push(3);
773
+ };
774
+ const watchFn2 = function () {
775
+ logs.push(2);
776
+ };
777
+ const watchFn1 = function () {
778
+ logs.push(1);
779
+ };
780
+ const removeWatcherOnce = function (newValue, oldValue) {
781
+ if (newValue === oldValue) {
782
+ removeWatcher3();
783
+ }
784
+ };
785
+
786
+ $rootScope.$watch(watchFn1, removeWatcherOnce);
787
+ $rootScope.$watch(watchFn2);
788
+ removeWatcher3 = $rootScope.$watch(watchFn3);
789
+
790
+ $rootScope.$digest();
791
+
792
+ expect(logs).toEqual([1, 2, 1, 2]);
793
+ });
794
+
795
+ it("should not skip watchers when removing itself during digest", () => {
796
+ let removeWatcher1;
797
+
798
+ const watchFn3 = function () {
799
+ logs.push(3);
800
+ };
801
+ const watchFn2 = function () {
802
+ logs.push(2);
803
+ };
804
+ const watchFn1 = function () {
805
+ logs.push(1);
806
+ };
807
+ const removeItself = function () {
808
+ removeWatcher1();
809
+ };
810
+
811
+ removeWatcher1 = $rootScope.$watch(watchFn1, removeItself);
812
+ $rootScope.$watch(watchFn2);
813
+ $rootScope.$watch(watchFn3);
814
+
815
+ $rootScope.$digest();
816
+
817
+ expect(logs).toEqual([1, 2, 3, 2, 3]);
818
+ });
819
+
820
+ it("should not infinitely digest when current value is NaN", () => {
821
+ $rootScope.$watch(() => NaN);
822
+
823
+ expect(() => {
824
+ $rootScope.$digest();
825
+ }).not.toThrow();
826
+ });
827
+
828
+ it("should always call the watcher with newVal and oldVal equal on the first run", () => {
829
+ function logger(scope, newVal, oldVal) {
830
+ const val =
831
+ newVal === oldVal || (newVal !== oldVal && oldVal !== newVal)
832
+ ? newVal
833
+ : "xxx";
834
+ logs.push(val);
835
+ }
836
+
837
+ $rootScope.$watch(() => NaN, logger);
838
+ $rootScope.$watch(() => undefined, logger);
839
+ $rootScope.$watch(() => "", logger);
840
+ $rootScope.$watch(() => false, logger);
841
+ $rootScope.$watch(() => ({}), logger, true);
842
+ $rootScope.$watch(() => 23, logger);
843
+
844
+ $rootScope.$digest();
845
+ expect(isNaN(logs.shift())).toBe(true); // jasmine's toBe and toEqual don't work well with NaNs
846
+ expect(logs).toEqual([undefined, "", false, {}, 23]);
847
+ logs = [];
848
+ $rootScope.$digest();
849
+ expect(logs).toEqual([]);
850
+ });
851
+
852
+ describe("$watch deregistration", () => {
853
+ beforeEach(() => (logs = []));
854
+ it("should return a function that allows listeners to be deregistered", () => {
855
+ const listener = jasmine.createSpy("watch listener");
856
+ let listenerRemove;
857
+
858
+ listenerRemove = $rootScope.$watch("foo", listener);
859
+ $rootScope.$digest(); // init
860
+ expect(listener).toHaveBeenCalled();
861
+ expect(listenerRemove).toBeDefined();
862
+
863
+ listener.calls.reset();
864
+ $rootScope.foo = "bar";
865
+ $rootScope.$digest(); // trigger
866
+ expect(listener).toHaveBeenCalled();
867
+
868
+ listener.calls.reset();
869
+ $rootScope.foo = "baz";
870
+ listenerRemove();
871
+ $rootScope.$digest(); // trigger
872
+ expect(listener).not.toHaveBeenCalled();
873
+ });
874
+
875
+ it("should allow a watch to be deregistered while in a digest", () => {
876
+ let remove1;
877
+ let remove2;
878
+ $rootScope.$watch("remove", () => {
879
+ remove1();
880
+ remove2();
881
+ });
882
+ remove1 = $rootScope.$watch("thing", () => {});
883
+ remove2 = $rootScope.$watch("thing", () => {});
884
+ expect(() => {
885
+ $rootScope.$apply("remove = true");
886
+ }).not.toThrow();
887
+ });
888
+
889
+ it("should not mess up the digest loop if deregistration happens during digest", () => {
890
+ // we are testing this due to regression #5525 which is related to how the digest loops lastDirtyWatch short-circuiting optimization works
891
+ // scenario: watch1 deregistering watch1
892
+ let scope = $rootScope.$new();
893
+ let deregWatch1 = scope.$watch(
894
+ () => {
895
+ logs.push("watch1");
896
+ return "watch1";
897
+ },
898
+ () => {
899
+ deregWatch1();
900
+ logs.push("watchAction1");
901
+ },
902
+ );
903
+ scope.$watch(
904
+ () => {
905
+ logs.push("watch2");
906
+ return "watch2";
907
+ },
908
+ () => logs.push("watchAction2"),
909
+ );
910
+ scope.$watch(
911
+ () => {
912
+ logs.push("watch3");
913
+ return "watch3";
914
+ },
915
+ () => logs.push("watchAction3"),
916
+ );
917
+
918
+ $rootScope.$digest();
919
+
920
+ expect(logs).toEqual([
921
+ "watch1",
922
+ "watchAction1",
923
+ "watch2",
924
+ "watchAction2",
925
+ "watch3",
926
+ "watchAction3",
927
+ "watch2",
928
+ "watch3",
929
+ ]);
930
+ scope.$destroy();
931
+ logs = [];
932
+
933
+ // scenario: watch1 deregistering watch2
934
+ scope = $rootScope.$new();
935
+ scope.$watch(
936
+ () => {
937
+ logs.push("watch1");
938
+ return "watch1";
939
+ },
940
+ () => {
941
+ deregWatch2();
942
+ logs.push("watchAction1");
943
+ },
944
+ );
945
+ let deregWatch2 = scope.$watch(
946
+ () => {
947
+ logs.push("watch2");
948
+ return "watch2";
949
+ },
950
+ () => logs.push("watchAction2"),
951
+ );
952
+ scope.$watch(
953
+ () => {
954
+ logs.push("watch3");
955
+ return "watch3";
956
+ },
957
+ () => logs.push("watchAction3"),
958
+ );
959
+
960
+ $rootScope.$digest();
961
+
962
+ expect(logs).toEqual([
963
+ "watch1",
964
+ "watchAction1",
965
+ "watch3",
966
+ "watchAction3",
967
+ "watch1",
968
+ "watch3",
969
+ ]);
970
+ scope.$destroy();
971
+ logs = [];
972
+
973
+ // scenario: watch2 deregistering watch1
974
+ scope = $rootScope.$new();
975
+ deregWatch1 = scope.$watch(
976
+ () => {
977
+ logs.push("watch1");
978
+ return "watch1";
979
+ },
980
+ () => logs.push("watchAction1"),
981
+ );
982
+ scope.$watch(
983
+ () => {
984
+ logs.push("watch2");
985
+ return "watch2";
986
+ },
987
+ () => {
988
+ deregWatch1();
989
+ logs.push("watchAction2");
990
+ },
991
+ );
992
+ scope.$watch(
993
+ () => {
994
+ logs.push("watch3");
995
+ return "watch3";
996
+ },
997
+ () => logs.push("watchAction3"),
998
+ );
999
+
1000
+ $rootScope.$digest();
1001
+
1002
+ expect(logs).toEqual([
1003
+ "watch1",
1004
+ "watchAction1",
1005
+ "watch2",
1006
+ "watchAction2",
1007
+ "watch3",
1008
+ "watchAction3",
1009
+ "watch2",
1010
+ "watch3",
1011
+ ]);
1012
+ });
1013
+ });
1014
+
200
1015
  describe("$watchCollection", () => {
201
1016
  describe("constiable", () => {
202
1017
  let deregister;
@@ -214,21 +1029,25 @@ describe("Scope", function () {
214
1029
  });
215
1030
 
216
1031
  it("should not trigger if nothing change", () => {
1032
+ $rootScope.$digest();
217
1033
  expect(logs).toEqual([
218
1034
  { newVal: undefined, oldVal: undefined, identical: true },
219
1035
  ]);
220
1036
  logs = [];
1037
+ $rootScope.$digest();
221
1038
  expect(logs).toEqual([]);
222
1039
  });
223
1040
 
224
1041
  it("should allow deregistration", () => {
225
1042
  $rootScope.obj = [];
1043
+ $rootScope.$digest();
226
1044
  expect(logs.length).toBe(1);
227
1045
  logs = [];
228
1046
 
229
1047
  $rootScope.obj.push("a");
230
1048
  deregister();
231
1049
 
1050
+ $rootScope.$digest();
232
1051
  expect(logs).toEqual([]);
233
1052
  });
234
1053
 
@@ -236,6 +1055,7 @@ describe("Scope", function () {
236
1055
  it("should return oldCollection === newCollection only on the first listener call", () => {
237
1056
  // first time should be identical
238
1057
  $rootScope.obj = ["a", "b"];
1058
+ $rootScope.$digest();
239
1059
  expect(logs).toEqual([
240
1060
  { newVal: ["a", "b"], oldVal: ["a", "b"], identical: true },
241
1061
  ]);
@@ -243,58 +1063,99 @@ describe("Scope", function () {
243
1063
 
244
1064
  // second time should be different
245
1065
  $rootScope.obj[1] = "c";
1066
+ $rootScope.$digest();
246
1067
  expect(logs).toEqual([{ newVal: ["a", "c"], oldVal: ["a", "b"] }]);
247
1068
  });
248
1069
 
1070
+ it("should trigger when property changes into array", () => {
1071
+ $rootScope.obj = "test";
1072
+ $rootScope.$digest();
1073
+ expect(logs).toEqual([
1074
+ { newVal: "test", oldVal: "test", identical: true },
1075
+ ]);
1076
+
1077
+ logs = [];
1078
+ $rootScope.obj = [];
1079
+ $rootScope.$digest();
1080
+ expect(logs).toEqual([{ newVal: [], oldVal: "test" }]);
1081
+
1082
+ logs = [];
1083
+ $rootScope.obj = {};
1084
+ $rootScope.$digest();
1085
+ expect(logs).toEqual([{ newVal: {}, oldVal: [] }]);
1086
+
1087
+ logs = [];
1088
+ $rootScope.obj = [];
1089
+ $rootScope.$digest();
1090
+ expect(logs).toEqual([{ newVal: [], oldVal: {} }]);
1091
+
1092
+ logs = [];
1093
+ $rootScope.obj = undefined;
1094
+ $rootScope.$digest();
1095
+ expect(logs).toEqual([{ newVal: undefined, oldVal: [] }]);
1096
+ });
1097
+
249
1098
  it("should not trigger change when object in collection changes", () => {
250
1099
  $rootScope.obj = [{}];
1100
+ $rootScope.$digest();
251
1101
  expect(logs).toEqual([
252
1102
  { newVal: [{}], oldVal: [{}], identical: true },
253
1103
  ]);
254
1104
 
255
1105
  logs = [];
256
1106
  $rootScope.obj[0].name = "foo";
1107
+ $rootScope.$digest();
257
1108
  expect(logs).toEqual([]);
258
1109
  });
259
1110
 
260
1111
  it("should watch array properties", () => {
261
1112
  $rootScope.obj = [];
1113
+ $rootScope.$digest();
262
1114
  expect(logs).toEqual([{ newVal: [], oldVal: [], identical: true }]);
263
1115
 
264
1116
  logs = [];
265
1117
  $rootScope.obj.push("a");
1118
+ $rootScope.$digest();
266
1119
  expect(logs).toEqual([{ newVal: ["a"], oldVal: [] }]);
267
1120
 
268
1121
  logs = [];
269
1122
  $rootScope.obj[0] = "b";
1123
+ $rootScope.$digest();
270
1124
  expect(logs).toEqual([{ newVal: ["b"], oldVal: ["a"] }]);
271
1125
 
272
1126
  logs = [];
273
1127
  $rootScope.obj.push([]);
274
1128
  $rootScope.obj.push({});
1129
+ $rootScope.$digest();
275
1130
  expect(logs).toEqual([{ newVal: ["b", [], {}], oldVal: ["b"] }]);
276
1131
 
277
1132
  logs = [];
278
1133
  const temp = $rootScope.obj[1];
279
1134
  $rootScope.obj[1] = $rootScope.obj[2];
280
1135
  $rootScope.obj[2] = temp;
1136
+ $rootScope.$digest();
281
1137
  expect(logs).toEqual([
282
1138
  { newVal: ["b", {}, []], oldVal: ["b", [], {}] },
283
1139
  ]);
284
1140
 
285
1141
  logs = [];
286
1142
  $rootScope.obj.shift();
1143
+ $rootScope.$digest();
287
1144
  expect(logs).toEqual([{ newVal: [{}, []], oldVal: ["b", {}, []] }]);
288
1145
  });
289
1146
 
290
1147
  it("should not infinitely digest when current value is NaN", () => {
291
1148
  $rootScope.obj = [NaN];
292
- expect(() => {}).not.toThrow();
1149
+ expect(() => {
1150
+ $rootScope.$digest();
1151
+ }).not.toThrow();
293
1152
  });
294
1153
 
295
1154
  it("should watch array-like objects like arrays", () => {
296
1155
  logs = [];
297
1156
  $rootScope.obj = document.getElementsByTagName("src");
1157
+ $rootScope.$digest();
1158
+
298
1159
  expect(logs.length).toBeTruthy();
299
1160
  });
300
1161
  });
@@ -303,6 +1164,7 @@ describe("Scope", function () {
303
1164
  it("should return oldCollection === newCollection only on the first listener call", () => {
304
1165
  $rootScope.obj = { a: "b" };
305
1166
  // first time should be identical
1167
+ $rootScope.$digest();
306
1168
  expect(logs).toEqual([
307
1169
  { newVal: { a: "b" }, oldVal: { a: "b" }, identical: true },
308
1170
  ]);
@@ -310,45 +1172,54 @@ describe("Scope", function () {
310
1172
 
311
1173
  // second time not identical
312
1174
  $rootScope.obj.a = "c";
1175
+ $rootScope.$digest();
313
1176
  expect(logs).toEqual([{ newVal: { a: "c" }, oldVal: { a: "b" } }]);
314
1177
  });
315
1178
 
316
1179
  it("should trigger when property changes into object", () => {
317
1180
  $rootScope.obj = "test";
1181
+ $rootScope.$digest();
318
1182
  expect(logs).toEqual([
319
1183
  { newVal: "test", oldVal: "test", identical: true },
320
1184
  ]);
321
1185
  logs = [];
322
1186
 
323
1187
  $rootScope.obj = {};
1188
+ $rootScope.$digest();
324
1189
  expect(logs).toEqual([{ newVal: {}, oldVal: "test" }]);
325
1190
  });
326
1191
 
327
1192
  it("should not trigger change when object in collection changes", () => {
328
1193
  $rootScope.obj = { name: {} };
1194
+ $rootScope.$digest();
329
1195
  expect(logs).toEqual([
330
1196
  { newVal: { name: {} }, oldVal: { name: {} }, identical: true },
331
1197
  ]);
332
1198
  logs = [];
333
1199
 
334
1200
  $rootScope.obj.name.bar = "foo";
1201
+ $rootScope.$digest();
335
1202
  expect(logs).toEqual([]);
336
1203
  });
337
1204
 
338
1205
  it("should watch object properties", () => {
339
1206
  $rootScope.obj = {};
1207
+ $rootScope.$digest();
340
1208
  expect(logs).toEqual([{ newVal: {}, oldVal: {}, identical: true }]);
341
1209
  logs = [];
342
1210
  $rootScope.obj.a = "A";
1211
+ $rootScope.$digest();
343
1212
  expect(logs).toEqual([{ newVal: { a: "A" }, oldVal: {} }]);
344
1213
 
345
1214
  logs = [];
346
1215
  $rootScope.obj.a = "B";
1216
+ $rootScope.$digest();
347
1217
  expect(logs).toEqual([{ newVal: { a: "B" }, oldVal: { a: "A" } }]);
348
1218
 
349
1219
  logs = [];
350
1220
  $rootScope.obj.b = [];
351
1221
  $rootScope.obj.c = {};
1222
+ $rootScope.$digest();
352
1223
  expect(logs).toEqual([
353
1224
  { newVal: { a: "B", b: [], c: {} }, oldVal: { a: "B" } },
354
1225
  ]);
@@ -357,6 +1228,7 @@ describe("Scope", function () {
357
1228
  const temp = $rootScope.obj.a;
358
1229
  $rootScope.obj.a = $rootScope.obj.b;
359
1230
  $rootScope.obj.c = temp;
1231
+ $rootScope.$digest();
360
1232
  expect(logs).toEqual([
361
1233
  {
362
1234
  newVal: { a: [], b: [], c: "B" },
@@ -366,6 +1238,7 @@ describe("Scope", function () {
366
1238
 
367
1239
  logs = [];
368
1240
  delete $rootScope.obj.a;
1241
+ $rootScope.$digest();
369
1242
  expect(logs).toEqual([
370
1243
  { newVal: { b: [], c: "B" }, oldVal: { a: [], b: [], c: "B" } },
371
1244
  ]);
@@ -373,18 +1246,22 @@ describe("Scope", function () {
373
1246
 
374
1247
  it("should not infinitely digest when current value is NaN", () => {
375
1248
  $rootScope.obj = { a: NaN };
376
- expect(() => {}).not.toThrow();
1249
+ expect(() => {
1250
+ $rootScope.$digest();
1251
+ }).not.toThrow();
377
1252
  });
378
1253
 
379
1254
  it("should handle objects created using `Object.create(null)`", () => {
380
1255
  $rootScope.obj = Object.create(null);
381
1256
  $rootScope.obj.a = "a";
382
1257
  $rootScope.obj.b = "b";
1258
+ $rootScope.$digest();
383
1259
  expect(logs[0].newVal).toEqual(
384
1260
  extend(Object.create(null), { a: "a", b: "b" }),
385
1261
  );
386
1262
 
387
1263
  delete $rootScope.obj.b;
1264
+ $rootScope.$digest();
388
1265
  expect(logs[0].newVal).toEqual(
389
1266
  extend(Object.create(null), { a: "a" }),
390
1267
  );
@@ -410,6 +1287,7 @@ describe("Scope", function () {
410
1287
  it("should return oldCollection === newCollection only on the first listener call", () => {
411
1288
  // first time should be identical
412
1289
  $rootScope.obj = "a";
1290
+ $rootScope.$digest();
413
1291
  expect(logs).toEqual([
414
1292
  { newVal: ["a"], oldVal: ["a"], identical: true },
415
1293
  ]);
@@ -417,46 +1295,56 @@ describe("Scope", function () {
417
1295
 
418
1296
  // second time should be different
419
1297
  $rootScope.obj = "b";
1298
+ $rootScope.$digest();
420
1299
  expect(logs).toEqual([{ newVal: ["b"], oldVal: ["a"] }]);
421
1300
  });
422
1301
 
423
1302
  it("should trigger when property changes into array", () => {
424
1303
  $rootScope.obj = "test";
1304
+ $rootScope.$digest();
425
1305
  expect(logs).toEqual([
426
1306
  { newVal: ["test"], oldVal: ["test"], identical: true },
427
1307
  ]);
428
1308
 
429
1309
  logs = [];
430
1310
  $rootScope.obj = [];
1311
+ $rootScope.$digest();
431
1312
  expect(logs).toEqual([{ newVal: [[]], oldVal: ["test"] }]);
432
1313
 
433
1314
  logs = [];
434
1315
  $rootScope.obj = {};
1316
+ $rootScope.$digest();
435
1317
  expect(logs).toEqual([{ newVal: [{}], oldVal: [[]] }]);
436
1318
 
437
1319
  logs = [];
438
1320
  $rootScope.obj = [];
1321
+ $rootScope.$digest();
439
1322
  expect(logs).toEqual([{ newVal: [[]], oldVal: [{}] }]);
440
1323
 
441
1324
  logs = [];
442
1325
  $rootScope.obj = undefined;
1326
+ $rootScope.$digest();
443
1327
  expect(logs).toEqual([{ newVal: [undefined], oldVal: [[]] }]);
444
1328
  });
445
1329
 
446
1330
  it("should not trigger change when object in collection changes", () => {
447
1331
  $rootScope.obj = {};
1332
+ $rootScope.$digest();
448
1333
  expect(logs).toEqual([
449
1334
  { newVal: [{}], oldVal: [{}], identical: true },
450
1335
  ]);
451
1336
 
452
1337
  logs = [];
453
1338
  $rootScope.obj.name = "foo";
1339
+ $rootScope.$digest();
454
1340
  expect(logs).toEqual([]);
455
1341
  });
456
1342
 
457
1343
  it("should not infinitely digest when current value is NaN", () => {
458
1344
  $rootScope.obj = NaN;
459
- expect(() => {}).not.toThrow();
1345
+ expect(() => {
1346
+ $rootScope.$digest();
1347
+ }).not.toThrow();
460
1348
  });
461
1349
  });
462
1350
 
@@ -477,6 +1365,7 @@ describe("Scope", function () {
477
1365
  it("should return oldCollection === newCollection only on the first listener call", () => {
478
1366
  $rootScope.obj = "b";
479
1367
  // first time should be identical
1368
+ $rootScope.$digest();
480
1369
  expect(logs).toEqual([
481
1370
  { newVal: { a: "b" }, oldVal: { a: "b" }, identical: true },
482
1371
  ]);
@@ -484,17 +1373,20 @@ describe("Scope", function () {
484
1373
  // second time not identical
485
1374
  logs = [];
486
1375
  $rootScope.obj = "c";
1376
+ $rootScope.$digest();
487
1377
  expect(logs).toEqual([{ newVal: { a: "c" }, oldVal: { a: "b" } }]);
488
1378
  });
489
1379
 
490
1380
  it("should trigger when property changes into object", () => {
491
1381
  $rootScope.obj = "test";
1382
+ $rootScope.$digest();
492
1383
  expect(logs).toEqual([
493
1384
  { newVal: { a: "test" }, oldVal: { a: "test" }, identical: true },
494
1385
  ]);
495
1386
 
496
1387
  logs = [];
497
1388
  $rootScope.obj = {};
1389
+ $rootScope.$digest();
498
1390
  expect(logs).toEqual([
499
1391
  { newVal: { a: {} }, oldVal: { a: "test" } },
500
1392
  ]);
@@ -502,6 +1394,7 @@ describe("Scope", function () {
502
1394
 
503
1395
  it("should not trigger change when object in collection changes", () => {
504
1396
  $rootScope.obj = { name: "foo" };
1397
+ $rootScope.$digest();
505
1398
  expect(logs).toEqual([
506
1399
  {
507
1400
  newVal: { a: { name: "foo" } },
@@ -512,29 +1405,35 @@ describe("Scope", function () {
512
1405
 
513
1406
  logs = [];
514
1407
  $rootScope.obj.name = "bar";
1408
+ $rootScope.$digest();
515
1409
  expect(logs).toEqual([]);
516
1410
  });
517
1411
 
518
1412
  it("should watch object properties", () => {
519
1413
  $rootScope.obj = {};
1414
+ $rootScope.$digest();
520
1415
  expect(logs).toEqual([
521
1416
  { newVal: { a: {} }, oldVal: { a: {} }, identical: true },
522
1417
  ]);
523
1418
 
524
1419
  logs = [];
525
1420
  $rootScope.obj = "A";
1421
+ $rootScope.$digest();
526
1422
  expect(logs).toEqual([{ newVal: { a: "A" }, oldVal: { a: {} } }]);
527
1423
 
528
1424
  logs = [];
529
1425
  $rootScope.obj = "B";
1426
+ $rootScope.$digest();
530
1427
  expect(logs).toEqual([{ newVal: { a: "B" }, oldVal: { a: "A" } }]);
531
1428
 
532
1429
  logs = [];
533
1430
  $rootScope.obj = [];
1431
+ $rootScope.$digest();
534
1432
  expect(logs).toEqual([{ newVal: { a: [] }, oldVal: { a: "B" } }]);
535
1433
 
536
1434
  logs = [];
537
1435
  delete $rootScope.obj;
1436
+ $rootScope.$digest();
538
1437
  expect(logs).toEqual([
539
1438
  { newVal: { a: undefined }, oldVal: { a: [] } },
540
1439
  ]);
@@ -542,7 +1441,9 @@ describe("Scope", function () {
542
1441
 
543
1442
  it("should not infinitely digest when current value is NaN", () => {
544
1443
  $rootScope.obj = NaN;
545
- expect(() => {}).not.toThrow();
1444
+ expect(() => {
1445
+ $rootScope.$digest();
1446
+ }).not.toThrow();
546
1447
  });
547
1448
  });
548
1449
 
@@ -562,6 +1463,7 @@ describe("Scope", function () {
562
1463
 
563
1464
  it('should default to "undefined" key', () => {
564
1465
  $rootScope.obj = "test";
1466
+ $rootScope.$digest();
565
1467
  expect(logs).toEqual([
566
1468
  {
567
1469
  newVal: { undefined: "test" },
@@ -574,18 +1476,21 @@ describe("Scope", function () {
574
1476
  it("should trigger when key changes", () => {
575
1477
  $rootScope.key = "a";
576
1478
  $rootScope.obj = "test";
1479
+ $rootScope.$digest();
577
1480
  expect(logs).toEqual([
578
1481
  { newVal: { a: "test" }, oldVal: { a: "test" }, identical: true },
579
1482
  ]);
580
1483
 
581
1484
  logs = [];
582
1485
  $rootScope.key = "b";
1486
+ $rootScope.$digest();
583
1487
  expect(logs).toEqual([
584
1488
  { newVal: { b: "test" }, oldVal: { a: "test" } },
585
1489
  ]);
586
1490
 
587
1491
  logs = [];
588
1492
  $rootScope.key = true;
1493
+ $rootScope.$digest();
589
1494
  expect(logs).toEqual([
590
1495
  { newVal: { true: "test" }, oldVal: { b: "test" } },
591
1496
  ]);
@@ -594,25 +1499,30 @@ describe("Scope", function () {
594
1499
  it("should not trigger when key changes but stringified key does not", () => {
595
1500
  $rootScope.key = 1;
596
1501
  $rootScope.obj = "test";
1502
+ $rootScope.$digest();
597
1503
  expect(logs).toEqual([
598
1504
  { newVal: { 1: "test" }, oldVal: { 1: "test" }, identical: true },
599
1505
  ]);
600
1506
 
601
1507
  logs = [];
602
1508
  $rootScope.key = "1";
1509
+ $rootScope.$digest();
603
1510
  expect(logs).toEqual([]);
604
1511
 
605
1512
  $rootScope.key = true;
1513
+ $rootScope.$digest();
606
1514
  expect(logs).toEqual([
607
1515
  { newVal: { true: "test" }, oldVal: { 1: "test" } },
608
1516
  ]);
609
1517
 
610
1518
  logs = [];
611
1519
  $rootScope.key = "true";
1520
+ $rootScope.$digest();
612
1521
  expect(logs).toEqual([]);
613
1522
 
614
1523
  logs = [];
615
1524
  $rootScope.key = {};
1525
+ $rootScope.$digest();
616
1526
  expect(logs).toEqual([
617
1527
  {
618
1528
  newVal: { "[object Object]": "test" },
@@ -622,12 +1532,14 @@ describe("Scope", function () {
622
1532
 
623
1533
  logs = [];
624
1534
  $rootScope.key = {};
1535
+ $rootScope.$digest();
625
1536
  expect(logs).toEqual([]);
626
1537
  });
627
1538
 
628
1539
  it("should not trigger change when object in collection changes", () => {
629
1540
  $rootScope.key = "a";
630
1541
  $rootScope.obj = { name: "foo" };
1542
+ $rootScope.$digest();
631
1543
  expect(logs).toEqual([
632
1544
  {
633
1545
  newVal: { a: { name: "foo" } },
@@ -638,13 +1550,16 @@ describe("Scope", function () {
638
1550
  logs = [];
639
1551
 
640
1552
  $rootScope.obj.name = "bar";
1553
+ $rootScope.$digest();
641
1554
  expect(logs).toEqual([]);
642
1555
  });
643
1556
 
644
1557
  it("should not infinitely digest when key value is NaN", () => {
645
1558
  $rootScope.key = NaN;
646
1559
  $rootScope.obj = NaN;
647
- expect(() => {}).not.toThrow();
1560
+ expect(() => {
1561
+ $rootScope.$digest();
1562
+ }).not.toThrow();
648
1563
  });
649
1564
  });
650
1565
  });
@@ -655,6 +1570,7 @@ describe("Scope", function () {
655
1570
  const watchSpy = jasmine.createSpy("watchSpy");
656
1571
  $rootScope.$watch(watchSpy);
657
1572
  $rootScope.$suspend();
1573
+ $rootScope.$digest();
658
1574
  expect(watchSpy).not.toHaveBeenCalled();
659
1575
  });
660
1576
 
@@ -663,6 +1579,7 @@ describe("Scope", function () {
663
1579
  $rootScope.$watch(watchSpy);
664
1580
  $rootScope.$suspend();
665
1581
  $rootScope.$resume();
1582
+ $rootScope.$digest();
666
1583
  expect(watchSpy).toHaveBeenCalled();
667
1584
  });
668
1585
 
@@ -671,6 +1588,7 @@ describe("Scope", function () {
671
1588
  const scope = $rootScope.$new(true);
672
1589
  scope.$watch(watchSpy);
673
1590
  $rootScope.$suspend();
1591
+ $rootScope.$digest();
674
1592
  expect(watchSpy).not.toHaveBeenCalled();
675
1593
  });
676
1594
 
@@ -680,6 +1598,7 @@ describe("Scope", function () {
680
1598
  scope.$watch(watchSpy);
681
1599
  $rootScope.$suspend();
682
1600
  $rootScope.$resume();
1601
+ $rootScope.$digest();
683
1602
  expect(watchSpy).toHaveBeenCalled();
684
1603
  });
685
1604
 
@@ -746,6 +1665,7 @@ describe("Scope", function () {
746
1665
  sibling.$watch(watchSpySibling);
747
1666
 
748
1667
  child.$suspend();
1668
+ $rootScope.$digest();
749
1669
  expect(watchSpyParent).toHaveBeenCalled();
750
1670
  expect(watchSpyChild).not.toHaveBeenCalled();
751
1671
  expect(watchSpySibling).toHaveBeenCalled();
@@ -786,8 +1706,123 @@ describe("Scope", function () {
786
1706
  return scope.w3;
787
1707
  }, log("w3action"));
788
1708
  console.error(logs.length);
1709
+ scope.$digest();
789
1710
  logs = [];
790
1711
  }
1712
+
1713
+ describe("optimizations", () => {
1714
+ beforeEach(() => (logs = []));
1715
+ it("should check watches only once during an empty digest", () => {
1716
+ setupWatches($rootScope, console.log);
1717
+ $rootScope.$digest();
1718
+ expect(logs).toEqual(["w1", "w2", "w3"]);
1719
+ });
1720
+
1721
+ it("should quit digest early after we check the last watch that was previously dirty", () => {
1722
+ setupWatches($rootScope, console.log);
1723
+ $rootScope.w1 = "x";
1724
+ $rootScope.$digest();
1725
+ expect(logs).toEqual(["w1", "w2", "w3", "w1"]);
1726
+ });
1727
+
1728
+ it("should not quit digest early if a new watch was added from an existing watch action", () => {
1729
+ setupWatches($rootScope, console.log);
1730
+ $rootScope.$watch(
1731
+ () => {
1732
+ logs.push("w4");
1733
+ return "w4";
1734
+ },
1735
+ () => {
1736
+ logs.push("w4action");
1737
+ $rootScope.$watch(
1738
+ () => {
1739
+ logs.push("w5");
1740
+ return "w5";
1741
+ },
1742
+ () => logs.push("w5action"),
1743
+ );
1744
+ },
1745
+ );
1746
+ $rootScope.$digest();
1747
+ expect(logs).toEqual([
1748
+ "w1",
1749
+ "w2",
1750
+ "w3",
1751
+ "w4",
1752
+ "w4action",
1753
+ "w5",
1754
+ "w5action",
1755
+ "w1",
1756
+ "w2",
1757
+ "w3",
1758
+ "w4",
1759
+ "w5",
1760
+ ]);
1761
+ });
1762
+
1763
+ it("should not quit digest early if an evalAsync task was scheduled from a watch action", () => {
1764
+ setupWatches($rootScope, console.log);
1765
+ $rootScope.$watch(
1766
+ () => {
1767
+ logs.push("w4");
1768
+ return "w4";
1769
+ },
1770
+ () => {
1771
+ logs.push("w4action");
1772
+ $rootScope.$evalAsync(() => {
1773
+ logs.push("evalAsync");
1774
+ });
1775
+ },
1776
+ );
1777
+ $rootScope.$digest();
1778
+ expect(logs).toEqual([
1779
+ "w1",
1780
+ "w2",
1781
+ "w3",
1782
+ "w4",
1783
+ "w4action",
1784
+ "evalAsync",
1785
+ "w1",
1786
+ "w2",
1787
+ "w3",
1788
+ "w4",
1789
+ ]);
1790
+ });
1791
+
1792
+ it("should quit digest early but not too early when constious watches fire", () => {
1793
+ setupWatches($rootScope, console.log);
1794
+ $rootScope.$watch(
1795
+ () => {
1796
+ logs.push("w4");
1797
+ return $rootScope.w4;
1798
+ },
1799
+ (newVal) => {
1800
+ logs.push("w4action");
1801
+ $rootScope.w2 = newVal;
1802
+ },
1803
+ );
1804
+
1805
+ $rootScope.$digest();
1806
+ logs = [];
1807
+
1808
+ $rootScope.w1 = "x";
1809
+ $rootScope.w4 = "x";
1810
+ $rootScope.$digest();
1811
+ expect(logs).toEqual([
1812
+ "w1",
1813
+ "w2",
1814
+ "w3",
1815
+ "w4",
1816
+ "w4action",
1817
+ "w1",
1818
+ "w2",
1819
+ "w3",
1820
+ "w4",
1821
+ "w1",
1822
+ "w2",
1823
+ ]);
1824
+ });
1825
+ });
791
1826
  });
792
1827
 
793
1828
  describe("$watchGroup", () => {
@@ -848,17 +1883,21 @@ describe("Scope", function () {
848
1883
 
849
1884
  scope.a = "foo";
850
1885
  scope.b = "bar";
1886
+ scope.$digest();
851
1887
  expect(logs[0]).toEqual("foo,bar >>> foo,bar");
852
1888
 
853
1889
  logs = [];
1890
+ scope.$digest();
854
1891
  expect(logs).toEqual([]);
855
1892
 
856
1893
  scope.a = "a";
1894
+ scope.$digest();
857
1895
  expect(logs[0]).toEqual("foo,bar >>> a,bar");
858
1896
 
859
1897
  logs = [];
860
1898
  scope.a = "A";
861
1899
  scope.b = "B";
1900
+ scope.$digest();
862
1901
  expect(logs[0]).toEqual("a,bar >>> A,B");
863
1902
  });
864
1903
 
@@ -870,12 +1909,15 @@ describe("Scope", function () {
870
1909
  });
871
1910
 
872
1911
  scope.a = "foo";
1912
+ scope.$digest();
873
1913
  expect(logs[0]).toEqual("foo >>> foo");
874
1914
 
875
1915
  logs = [];
1916
+ scope.$digest();
876
1917
  expect(logs).toEqual([]);
877
1918
 
878
1919
  scope.a = "bar";
1920
+ scope.$digest();
879
1921
  expect(logs[0]).toEqual("foo >>> bar");
880
1922
  });
881
1923
 
@@ -886,9 +1928,11 @@ describe("Scope", function () {
886
1928
  });
887
1929
 
888
1930
  expect(logs).toEqual([]);
1931
+ scope.$digest();
889
1932
  expect(logs[0]).toEqual(" >>> ");
890
1933
 
891
1934
  logs = [];
1935
+ scope.$digest();
892
1936
  expect(logs).toEqual([]);
893
1937
  });
894
1938
 
@@ -912,6 +1956,7 @@ describe("Scope", function () {
912
1956
  deregisterNone();
913
1957
  scope.a = "xxx";
914
1958
  scope.b = "yyy";
1959
+ scope.$digest();
915
1960
  expect(logs).toEqual([]);
916
1961
  });
917
1962
 
@@ -1093,6 +2138,7 @@ describe("Scope", function () {
1093
2138
  log += "3";
1094
2139
  });
1095
2140
 
2141
+ $rootScope.$digest();
1096
2142
  log = "";
1097
2143
  });
1098
2144
 
@@ -1116,6 +2162,7 @@ describe("Scope", function () {
1116
2162
  const spy = jasmine.createSpy("$watch spy");
1117
2163
  $rootScope.$watch(spy);
1118
2164
  $rootScope.$destroy();
2165
+ $rootScope.$digest();
1119
2166
  expect(spy).not.toHaveBeenCalled();
1120
2167
  });
1121
2168
 
@@ -1127,16 +2174,19 @@ describe("Scope", function () {
1127
2174
 
1128
2175
  it("should remove first", () => {
1129
2176
  first.$destroy();
2177
+ $rootScope.$digest();
1130
2178
  expect(log).toEqual("23");
1131
2179
  });
1132
2180
 
1133
2181
  it("should remove middle", () => {
1134
2182
  middle.$destroy();
2183
+ $rootScope.$digest();
1135
2184
  expect(log).toEqual("13");
1136
2185
  });
1137
2186
 
1138
2187
  it("should remove last", () => {
1139
2188
  last.$destroy();
2189
+ $rootScope.$digest();
1140
2190
  expect(log).toEqual("12");
1141
2191
  });
1142
2192
 
@@ -1150,6 +2200,7 @@ describe("Scope", function () {
1150
2200
  });
1151
2201
 
1152
2202
  it("should $destroy a scope only once and ignore any further destroy calls", () => {
2203
+ $rootScope.$digest();
1153
2204
  expect(log).toBe("123");
1154
2205
 
1155
2206
  first.$destroy();
@@ -1173,6 +2224,27 @@ describe("Scope", function () {
1173
2224
  expect(logs).toEqual(["event"]);
1174
2225
  });
1175
2226
 
2227
+ it("should decrement ancestor $$listenerCount entries", () => {
2228
+ const EVENT = "fooEvent";
2229
+ const spy = jasmine.createSpy("listener");
2230
+ const firstSecond = first.$new();
2231
+
2232
+ firstSecond.$on(EVENT, spy);
2233
+ firstSecond.$on(EVENT, spy);
2234
+ middle.$on(EVENT, spy);
2235
+
2236
+ expect($rootScope.$$listenerCount[EVENT]).toBe(3);
2237
+ expect(first.$$listenerCount[EVENT]).toBe(2);
2238
+
2239
+ firstSecond.$destroy();
2240
+
2241
+ expect($rootScope.$$listenerCount[EVENT]).toBe(1);
2242
+ expect(first.$$listenerCount[EVENT]).toBeUndefined();
2243
+
2244
+ $rootScope.$broadcast(EVENT);
2245
+ expect(spy).toHaveBeenCalledTimes(1);
2246
+ });
2247
+
1176
2248
  it("should do nothing when a child event listener is registered after parent's destruction", () => {
1177
2249
  const parent = $rootScope.$new();
1178
2250
  const child = parent.$new();
@@ -1215,6 +2287,20 @@ describe("Scope", function () {
1215
2287
  expect(called).toBe(false);
1216
2288
  });
1217
2289
 
2290
+ it("should do nothing when $evalAsync()ing after parent's destruction", () => {
2291
+ const parent = $rootScope.$new();
2292
+ const child = parent.$new();
2293
+
2294
+ parent.$destroy();
2295
+
2296
+ let called = false;
2297
+ function applyFunc() {
2298
+ called = true;
2299
+ }
2300
+ child.$evalAsync(applyFunc);
2301
+ expect(called).toBe(false);
2302
+ });
2303
+
1218
2304
  it("should preserve all (own and inherited) model properties on a destroyed scope", () => {
1219
2305
  // This test simulates an async task (xhr response) interacting with the scope after the scope
1220
2306
  // was destroyed. Since we can't abort the request, we should ensure that the task doesn't
@@ -1233,6 +2319,238 @@ describe("Scope", function () {
1233
2319
  });
1234
2320
  });
1235
2321
 
2322
+ describe("$eval", () => {
2323
+ it("should eval an expression", () => {
2324
+ expect($rootScope.$eval("a=1")).toEqual(1);
2325
+ expect($rootScope.a).toEqual(1);
2326
+
2327
+ $rootScope.$eval((self) => {
2328
+ self.b = 2;
2329
+ });
2330
+ expect($rootScope.b).toEqual(2);
2331
+ });
2332
+
2333
+ it("should allow passing locals to the expression", () => {
2334
+ expect($rootScope.$eval("a+1", { a: 2 })).toBe(3);
2335
+
2336
+ $rootScope.$eval(
2337
+ (scope, locals) => {
2338
+ scope.c = locals.b + 4;
2339
+ },
2340
+ { b: 3 },
2341
+ );
2342
+ expect($rootScope.c).toBe(7);
2343
+ });
2344
+ });
2345
+
2346
+ describe("$evalAsync", () => {
2347
+ it("should run callback before $watch", () => {
2348
+ let log = "";
2349
+ const child = $rootScope.$new();
2350
+ $rootScope.$evalAsync((scope) => {
2351
+ log += "parent.async;";
2352
+ });
2353
+ $rootScope.$watch("value", () => {
2354
+ log += "parent.$digest;";
2355
+ });
2356
+ child.$evalAsync((scope) => {
2357
+ log += "child.async;";
2358
+ });
2359
+ child.$watch("value", () => {
2360
+ log += "child.$digest;";
2361
+ });
2362
+ $rootScope.$digest();
2363
+ expect(log).toEqual(
2364
+ "parent.async;child.async;parent.$digest;child.$digest;",
2365
+ );
2366
+ });
2367
+
2368
+ it("should not run another digest for an $$postDigest call", () => {
2369
+ let internalWatchCount = 0;
2370
+ let externalWatchCount = 0;
2371
+
2372
+ $rootScope.internalCount = 0;
2373
+ $rootScope.externalCount = 0;
2374
+
2375
+ $rootScope.$evalAsync((scope) => {
2376
+ $rootScope.internalCount++;
2377
+ });
2378
+
2379
+ $rootScope.$$postDigest((scope) => {
2380
+ $rootScope.externalCount++;
2381
+ });
2382
+
2383
+ $rootScope.$watch("internalCount", (value) => {
2384
+ internalWatchCount = value;
2385
+ });
2386
+ $rootScope.$watch("externalCount", (value) => {
2387
+ externalWatchCount = value;
2388
+ });
2389
+
2390
+ $rootScope.$digest();
2391
+
2392
+ expect(internalWatchCount).toEqual(1);
2393
+ expect(externalWatchCount).toEqual(0);
2394
+ });
2395
+
2396
+ it("should cause a $digest rerun", () => {
2397
+ $rootScope.log = "";
2398
+ $rootScope.value = 0;
2399
+ $rootScope.$watch("value", () => {
2400
+ $rootScope.log += ".";
2401
+ });
2402
+ $rootScope.$watch("init", () => {
2403
+ $rootScope.$evalAsync('value = 123; log = log + "=" ');
2404
+ expect($rootScope.value).toEqual(0);
2405
+ });
2406
+ $rootScope.$digest();
2407
+ expect($rootScope.log).toEqual(".=.");
2408
+ });
2409
+
2410
+ it("should run async in the same order as added", () => {
2411
+ $rootScope.log = "";
2412
+ $rootScope.$evalAsync("log = log + 1");
2413
+ $rootScope.$evalAsync("log = log + 2");
2414
+ $rootScope.$digest();
2415
+ expect($rootScope.log).toBe("12");
2416
+ });
2417
+
2418
+ it("should allow passing locals to the expression", () => {
2419
+ $rootScope.log = "";
2420
+ $rootScope.$evalAsync("log = log + a", { a: 1 });
2421
+ $rootScope.$digest();
2422
+ expect($rootScope.log).toBe("1");
2423
+ });
2424
+
2425
+ it("should run async expressions in their proper context", () => {
2426
+ const child = $rootScope.$new();
2427
+ $rootScope.ctx = "root context";
2428
+ $rootScope.log = "";
2429
+ child.ctx = "child context";
2430
+ child.log = "";
2431
+ child.$evalAsync("log=ctx");
2432
+ $rootScope.$digest();
2433
+ expect($rootScope.log).toBe("");
2434
+ expect(child.log).toBe("child context");
2435
+ });
2436
+
2437
+ it("should operate only with a single queue across all child and isolate scopes", () => {
2438
+ const childScope = $rootScope.$new();
2439
+ const isolateScope = $rootScope.$new(true);
2440
+
2441
+ $rootScope.$evalAsync("rootExpression");
2442
+ childScope.$evalAsync("childExpression");
2443
+ isolateScope.$evalAsync("isolateExpression");
2444
+ expect($$asyncQueue).toEqual([
2445
+ {
2446
+ scope: $rootScope,
2447
+ fn: $parse("rootExpression"),
2448
+ locals: undefined,
2449
+ },
2450
+ {
2451
+ scope: childScope,
2452
+ fn: $parse("childExpression"),
2453
+ locals: undefined,
2454
+ },
2455
+ {
2456
+ scope: isolateScope,
2457
+ fn: $parse("isolateExpression"),
2458
+ locals: undefined,
2459
+ },
2460
+ ]);
2461
+ });
2462
+
2463
+ describe("auto-flushing when queueing outside of an $apply", () => {
2464
+ it("should auto-flush the queue asynchronously and trigger digest", () => {
2465
+ logs = [];
2466
+ $rootScope.$evalAsync(() => {
2467
+ logs.push("eval-ed!");
2468
+ return "eval-ed!";
2469
+ });
2470
+ $rootScope.$watch(() => {
2471
+ logs.push("digesting");
2472
+ return "digesting";
2473
+ });
2474
+ expect(logs).toEqual([]);
2475
+ setTimeout(() => {
2476
+ expect(logs).toEqual(["eval-ed!", "digesting", "digesting"]);
2477
+ });
2478
+ });
2479
+
2480
+ it("should not trigger digest asynchronously if the queue is empty in the next tick", () => {
2481
+ logs = [];
2482
+ $rootScope.$evalAsync(() => {
2483
+ logs.push("eval-ed!");
2484
+ return "eval-ed!";
2485
+ });
2486
+ $rootScope.$watch(() => {
2487
+ logs.push("digesting");
2488
+ return "digesting";
2489
+ });
2490
+ expect(logs).toEqual([]);
2491
+
2492
+ $rootScope.$digest();
2493
+
2494
+ expect(logs).toEqual(["eval-ed!", "digesting", "digesting"]);
2495
+ logs = [];
2496
+
2497
+ setTimeout(() => {
2498
+ expect(logs).toEqual([]);
2499
+ });
2500
+ });
2501
+
2502
+ it("should not schedule more than one auto-flush task", () => {
2503
+ logs = [];
2504
+ $rootScope.$evalAsync(() => {
2505
+ logs.push("eval-ed 1!");
2506
+ return "eval-ed 1!";
2507
+ });
2508
+ $rootScope.$evalAsync(() => {
2509
+ logs.push("eval-ed 2!");
2510
+ return "eval-ed 2!";
2511
+ });
2512
+ expect(logs).toEqual([]);
2513
+ setTimeout(() => {
2514
+ expect(logs).toEqual(["eval-ed 1!", "eval-ed 2!"]);
2515
+ });
2516
+
2517
+ setTimeout(() => {
2518
+ expect(logs).toEqual(["eval-ed 1!", "eval-ed 2!"]);
2519
+ });
2520
+ });
2521
+
2522
+ it("should not have execution affected by an explicit $digest call", () => {
2523
+ const scope1 = $rootScope.$new();
2524
+ const scope2 = $rootScope.$new();
2525
+
2526
+ scope1.$watch("value", (value) => {
2527
+ scope1.result = value;
2528
+ });
2529
+
2530
+ scope1.$evalAsync(() => {
2531
+ scope1.value = "bar";
2532
+ });
2533
+
2534
+ expect(scope1.result).toBe(undefined);
2535
+ scope2.$digest();
2536
+
2537
+ setTimeout(() => expect(scope1.result).toBe("bar"));
2538
+ });
2539
+ });
2540
+
2541
+ it("should not pass anything as `this` to scheduled functions", () => {
2542
+ let this1 = {};
2543
+ const this2 = (function () {
2544
+ return this;
2545
+ })();
2546
+ $rootScope.$evalAsync(function () {
2547
+ this1 = this;
2548
+ });
2549
+ $rootScope.$digest();
2550
+ expect(this1).toEqual(this2);
2551
+ });
2552
+ });
2553
+
1236
2554
  describe("$apply", () => {
1237
2555
  beforeEach(() => (logs = []));
1238
2556
 
@@ -1287,6 +2605,7 @@ describe("Scope", function () {
1287
2605
  $rootScope.$watch(() => {
1288
2606
  log += "$digest;";
1289
2607
  });
2608
+ $rootScope.$digest();
1290
2609
  log = "";
1291
2610
  });
1292
2611
 
@@ -1436,98 +2755,714 @@ describe("Scope", function () {
1436
2755
  const expression = jasmine.createSpy("expr");
1437
2756
 
1438
2757
  $rootScope.$applyAsync(expression);
2758
+ $rootScope.$digest();
1439
2759
  expect(expression).toHaveBeenCalled();
1440
2760
  expect(cancel).toHaveBeenCalled();
1441
2761
  expression.calls.reset();
1442
2762
  cancel.calls.reset();
1443
2763
 
1444
2764
  // assert that another digest won't call the function again
2765
+ $rootScope.$digest();
1445
2766
  expect(expression).not.toHaveBeenCalled();
1446
2767
  expect(cancel).not.toHaveBeenCalled();
1447
2768
  });
1448
2769
  });
1449
2770
 
1450
- describe("$postUpdate", () => {
2771
+ describe("$$postDigest", () => {
1451
2772
  beforeEach(() => (logs = []));
1452
2773
  it("should process callbacks as a queue (FIFO) when the scope is digested", () => {
1453
2774
  let signature = "";
1454
2775
 
1455
- $rootScope.$postUpdate(() => {
2776
+ $rootScope.$$postDigest(() => {
1456
2777
  signature += "A";
1457
- $rootScope.$postUpdate(() => {
2778
+ $rootScope.$$postDigest(() => {
1458
2779
  signature += "D";
1459
2780
  });
1460
2781
  });
1461
2782
 
1462
- $rootScope.$postUpdate(() => {
2783
+ $rootScope.$$postDigest(() => {
1463
2784
  signature += "B";
1464
2785
  });
1465
2786
 
1466
- $rootScope.$postUpdate(() => {
2787
+ $rootScope.$$postDigest(() => {
1467
2788
  signature += "C";
1468
2789
  });
1469
2790
 
1470
2791
  expect(signature).toBe("");
2792
+ $rootScope.$digest();
1471
2793
  expect(signature).toBe("ABCD");
1472
2794
  });
1473
2795
 
1474
- it("should support $apply calls nested in $postUpdate callbacks", () => {
2796
+ it("should support $apply calls nested in $$postDigest callbacks", () => {
1475
2797
  let signature = "";
1476
2798
 
1477
- $rootScope.$postUpdate(() => {
2799
+ $rootScope.$$postDigest(() => {
1478
2800
  signature += "A";
1479
2801
  });
1480
2802
 
1481
- $rootScope.$postUpdate(() => {
2803
+ $rootScope.$$postDigest(() => {
1482
2804
  signature += "B";
1483
2805
  $rootScope.$apply();
1484
2806
  signature += "D";
1485
2807
  });
1486
2808
 
1487
- $rootScope.$postUpdate(() => {
2809
+ $rootScope.$$postDigest(() => {
1488
2810
  signature += "C";
1489
2811
  });
1490
2812
 
1491
2813
  expect(signature).toBe("");
2814
+ $rootScope.$digest();
1492
2815
  expect(signature).toBe("ABCD");
1493
2816
  });
1494
2817
 
1495
- it("should run a $postUpdate call on all child scopes when a parent scope is digested", () => {
2818
+ it("should run a $$postDigest call on all child scopes when a parent scope is digested", () => {
1496
2819
  const parent = $rootScope.$new();
1497
2820
  const child = parent.$new();
1498
2821
  let count = 0;
1499
2822
 
1500
- $rootScope.$postUpdate(() => {
2823
+ $rootScope.$$postDigest(() => {
1501
2824
  count++;
1502
2825
  });
1503
2826
 
1504
- parent.$postUpdate(() => {
2827
+ parent.$$postDigest(() => {
1505
2828
  count++;
1506
2829
  });
1507
2830
 
1508
- child.$postUpdate(() => {
2831
+ child.$$postDigest(() => {
1509
2832
  count++;
1510
2833
  });
1511
2834
 
1512
2835
  expect(count).toBe(0);
2836
+ $rootScope.$digest();
1513
2837
  expect(count).toBe(3);
1514
2838
  });
1515
2839
 
1516
- it("should run a $postUpdate call even if the child scope is isolated", () => {
2840
+ it("should run a $$postDigest call even if the child scope is isolated", () => {
1517
2841
  const parent = $rootScope.$new();
1518
2842
  const child = parent.$new(true);
1519
2843
  let signature = "";
1520
2844
 
1521
- parent.$postUpdate(() => {
2845
+ parent.$$postDigest(() => {
1522
2846
  signature += "A";
1523
2847
  });
1524
2848
 
1525
- child.$postUpdate(() => {
2849
+ child.$$postDigest(() => {
1526
2850
  signature += "B";
1527
2851
  });
1528
2852
 
1529
2853
  expect(signature).toBe("");
2854
+ $rootScope.$digest();
1530
2855
  expect(signature).toBe("AB");
1531
2856
  });
1532
2857
  });
2858
+
2859
+ describe("events", () => {
2860
+ describe("$on", () => {
2861
+ it("should add listener for both $emit and $broadcast events", () => {
2862
+ logs = "";
2863
+ const child = $rootScope.$new();
2864
+
2865
+ function eventFn() {
2866
+ logs += "X";
2867
+ }
2868
+
2869
+ child.$on("abc", eventFn);
2870
+ expect(logs).toEqual("");
2871
+
2872
+ child.$emit("abc");
2873
+ expect(logs).toEqual("X");
2874
+
2875
+ child.$broadcast("abc");
2876
+ expect(logs).toEqual("XX");
2877
+ });
2878
+
2879
+ it("should increment ancestor $$listenerCount entries", () => {
2880
+ const child1 = $rootScope.$new();
2881
+ const child2 = child1.$new();
2882
+ const spy = jasmine.createSpy();
2883
+
2884
+ $rootScope.$on("event1", spy);
2885
+ expect($rootScope.$$listenerCount.event1).toEqual(1);
2886
+
2887
+ child1.$on("event1", spy);
2888
+ expect($rootScope.$$listenerCount.event1).toEqual(2);
2889
+ expect(child1.$$listenerCount.event1).toEqual(1);
2890
+
2891
+ child2.$on("event2", spy);
2892
+ expect($rootScope.$$listenerCount.event1).toEqual(2);
2893
+ expect($rootScope.$$listenerCount.event2).toEqual(1);
2894
+ expect(child1.$$listenerCount.event1).toEqual(1);
2895
+ expect(child1.$$listenerCount.event2).toEqual(1);
2896
+ expect(child2.$$listenerCount.event2).toEqual(1);
2897
+ });
2898
+
2899
+ describe("deregistration", () => {
2900
+ it("should return a function that deregisters the listener", () => {
2901
+ let log = "";
2902
+ const child = $rootScope.$new();
2903
+ let listenerRemove;
2904
+
2905
+ function eventFn() {
2906
+ log += "X";
2907
+ }
2908
+
2909
+ listenerRemove = child.$on("abc", eventFn);
2910
+ expect(log).toEqual("");
2911
+ expect(listenerRemove).toBeDefined();
2912
+
2913
+ child.$emit("abc");
2914
+ child.$broadcast("abc");
2915
+ expect(log).toEqual("XX");
2916
+ expect($rootScope.$$listenerCount.abc).toBe(1);
2917
+
2918
+ log = "";
2919
+ listenerRemove();
2920
+ child.$emit("abc");
2921
+ child.$broadcast("abc");
2922
+ expect(log).toEqual("");
2923
+ expect($rootScope.$$listenerCount.abc).toBeUndefined();
2924
+ });
2925
+
2926
+ // See issue https://github.com/angular/angular.js/issues/16135
2927
+ it("should deallocate the listener array entry", () => {
2928
+ const remove1 = $rootScope.$on("abc", () => {});
2929
+ $rootScope.$on("abc", () => {});
2930
+
2931
+ expect($rootScope.$$listeners.get("abc").length).toBe(2);
2932
+ expect(0 in $rootScope.$$listeners.get("abc")).toBe(true);
2933
+
2934
+ remove1();
2935
+
2936
+ expect($rootScope.$$listeners.get("abc").length).toBe(2);
2937
+ expect(0 in $rootScope.$$listeners.get("abc")).toBe(false);
2938
+ });
2939
+
2940
+ it("should call next listener after removing the current listener via its own handler", () => {
2941
+ const listener1 = jasmine.createSpy("listener1").and.callFake(() => {
2942
+ remove1();
2943
+ });
2944
+ let remove1 = $rootScope.$on("abc", listener1);
2945
+
2946
+ const listener2 = jasmine.createSpy("listener2");
2947
+ const remove2 = $rootScope.$on("abc", listener2);
2948
+
2949
+ const listener3 = jasmine.createSpy("listener3");
2950
+ const remove3 = $rootScope.$on("abc", listener3);
2951
+
2952
+ $rootScope.$broadcast("abc");
2953
+ expect(listener1).toHaveBeenCalled();
2954
+ expect(listener2).toHaveBeenCalled();
2955
+ expect(listener3).toHaveBeenCalled();
2956
+
2957
+ listener1.calls.reset();
2958
+ listener2.calls.reset();
2959
+ listener3.calls.reset();
2960
+
2961
+ $rootScope.$broadcast("abc");
2962
+ expect(listener1).not.toHaveBeenCalled();
2963
+ expect(listener2).toHaveBeenCalled();
2964
+ expect(listener3).toHaveBeenCalled();
2965
+ });
2966
+
2967
+ it("should call all subsequent listeners when a previous listener is removed via a handler", () => {
2968
+ const listener1 = jasmine.createSpy();
2969
+ const remove1 = $rootScope.$on("abc", listener1);
2970
+
2971
+ const listener2 = jasmine.createSpy().and.callFake(remove1);
2972
+ const remove2 = $rootScope.$on("abc", listener2);
2973
+
2974
+ const listener3 = jasmine.createSpy();
2975
+ const remove3 = $rootScope.$on("abc", listener3);
2976
+
2977
+ $rootScope.$broadcast("abc");
2978
+ expect(listener1).toHaveBeenCalled();
2979
+ expect(listener2).toHaveBeenCalled();
2980
+ expect(listener3).toHaveBeenCalled();
2981
+
2982
+ listener1.calls.reset();
2983
+ listener2.calls.reset();
2984
+ listener3.calls.reset();
2985
+
2986
+ $rootScope.$broadcast("abc");
2987
+ expect(listener1).not.toHaveBeenCalled();
2988
+ expect(listener2).toHaveBeenCalled();
2989
+ expect(listener3).toHaveBeenCalled();
2990
+ });
2991
+
2992
+ it("should not call listener when removed by previous", () => {
2993
+ const listener1 = jasmine.createSpy("listener1");
2994
+ const remove1 = $rootScope.$on("abc", listener1);
2995
+
2996
+ const listener2 = jasmine.createSpy("listener2").and.callFake(() => {
2997
+ remove3();
2998
+ });
2999
+ const remove2 = $rootScope.$on("abc", listener2);
3000
+
3001
+ const listener3 = jasmine.createSpy("listener3");
3002
+ let remove3 = $rootScope.$on("abc", listener3);
3003
+
3004
+ const listener4 = jasmine.createSpy("listener4");
3005
+ const remove4 = $rootScope.$on("abc", listener4);
3006
+
3007
+ $rootScope.$broadcast("abc");
3008
+ expect(listener1).toHaveBeenCalled();
3009
+ expect(listener2).toHaveBeenCalled();
3010
+ expect(listener3).not.toHaveBeenCalled();
3011
+ expect(listener4).toHaveBeenCalled();
3012
+
3013
+ listener1.calls.reset();
3014
+ listener2.calls.reset();
3015
+ listener3.calls.reset();
3016
+ listener4.calls.reset();
3017
+
3018
+ $rootScope.$broadcast("abc");
3019
+ expect(listener1).toHaveBeenCalled();
3020
+ expect(listener2).toHaveBeenCalled();
3021
+ expect(listener3).not.toHaveBeenCalled();
3022
+ expect(listener4).toHaveBeenCalled();
3023
+ });
3024
+
3025
+ it("should decrement ancestor $$listenerCount entries", () => {
3026
+ const child1 = $rootScope.$new();
3027
+ const child2 = child1.$new();
3028
+ const spy = jasmine.createSpy();
3029
+
3030
+ $rootScope.$on("event1", spy);
3031
+ expect($rootScope.$$listenerCount.event1).toEqual(1);
3032
+
3033
+ child1.$on("event1", spy);
3034
+ expect($rootScope.$$listenerCount.event1).toEqual(2);
3035
+ expect(child1.$$listenerCount.event1).toEqual(1);
3036
+
3037
+ const deregisterEvent2Listener = child2.$on("event2", spy);
3038
+ expect($rootScope.$$listenerCount.event1).toEqual(2);
3039
+ expect($rootScope.$$listenerCount.event2).toEqual(1);
3040
+ expect(child1.$$listenerCount.event1).toEqual(1);
3041
+ expect(child1.$$listenerCount.event2).toEqual(1);
3042
+ expect(child2.$$listenerCount.event2).toEqual(1);
3043
+
3044
+ deregisterEvent2Listener();
3045
+
3046
+ expect($rootScope.$$listenerCount.event1).toEqual(2);
3047
+ expect(child1.$$listenerCount.event1).toEqual(1);
3048
+ expect(child2.$$listenerCount).toBeTruthy();
3049
+ });
3050
+
3051
+ it("should not decrement $$listenerCount when called second time", () => {
3052
+ const child = $rootScope.$new();
3053
+ const listener1Spy = jasmine.createSpy();
3054
+ const listener2Spy = jasmine.createSpy();
3055
+
3056
+ child.$on("abc", listener1Spy);
3057
+ expect($rootScope.$$listenerCount.abc).toEqual(1);
3058
+ expect(child.$$listenerCount.abc).toEqual(1);
3059
+
3060
+ const deregisterEventListener = child.$on("abc", listener2Spy);
3061
+ expect($rootScope.$$listenerCount.abc).toEqual(2);
3062
+ expect(child.$$listenerCount.abc).toEqual(2);
3063
+
3064
+ deregisterEventListener();
3065
+
3066
+ expect($rootScope.$$listenerCount.abc).toEqual(1);
3067
+ expect(child.$$listenerCount.abc).toEqual(1);
3068
+
3069
+ deregisterEventListener();
3070
+
3071
+ expect($rootScope.$$listenerCount.abc).toEqual(1);
3072
+ expect(child.$$listenerCount.abc).toEqual(1);
3073
+ });
3074
+ });
3075
+ });
3076
+
3077
+ describe("$emit", () => {
3078
+ let log;
3079
+ let child;
3080
+ let grandChild;
3081
+ let greatGrandChild;
3082
+
3083
+ function logger(event) {
3084
+ log += `${event.currentScope.id}>`;
3085
+ }
3086
+
3087
+ beforeEach(() => {
3088
+ log = "";
3089
+ logs = [];
3090
+ child = $rootScope.$new();
3091
+ grandChild = child.$new();
3092
+ greatGrandChild = grandChild.$new();
3093
+
3094
+ $rootScope.id = 0;
3095
+ child.id = 1;
3096
+ grandChild.id = 2;
3097
+ greatGrandChild.id = 3;
3098
+
3099
+ $rootScope.$on("myEvent", logger);
3100
+ child.$on("myEvent", logger);
3101
+ grandChild.$on("myEvent", logger);
3102
+ greatGrandChild.$on("myEvent", logger);
3103
+ });
3104
+
3105
+ it("should bubble event up to the root scope", () => {
3106
+ grandChild.$emit("myEvent");
3107
+ expect(log).toEqual("2>1>0>");
3108
+ });
3109
+
3110
+ it("should allow all events on the same scope to run even if stopPropagation is called", () => {
3111
+ child.$on("myEvent", logger);
3112
+ grandChild.$on("myEvent", (e) => {
3113
+ e.stopPropagation();
3114
+ });
3115
+ grandChild.$on("myEvent", logger);
3116
+ grandChild.$on("myEvent", logger);
3117
+ grandChild.$emit("myEvent");
3118
+ expect(log).toEqual("2>2>2>");
3119
+ });
3120
+
3121
+ it("should dispatch exceptions to the $exceptionHandler", () => {
3122
+ child.$on("myEvent", () => {
3123
+ throw "bubbleException";
3124
+ });
3125
+ grandChild.$emit("myEvent");
3126
+ expect(log).toEqual("2>1>0>");
3127
+ expect(logs).toEqual(["bubbleException"]);
3128
+ });
3129
+
3130
+ it("should allow stopping event propagation", () => {
3131
+ child.$on("myEvent", (event) => {
3132
+ event.stopPropagation();
3133
+ });
3134
+ grandChild.$emit("myEvent");
3135
+ expect(log).toEqual("2>1>");
3136
+ });
3137
+
3138
+ it("should forward method arguments", () => {
3139
+ child.$on("abc", (event, arg1, arg2) => {
3140
+ expect(event.name).toBe("abc");
3141
+ expect(arg1).toBe("arg1");
3142
+ expect(arg2).toBe("arg2");
3143
+ });
3144
+ child.$emit("abc", "arg1", "arg2");
3145
+ });
3146
+
3147
+ it("should allow removing event listener inside a listener on $emit", () => {
3148
+ const spy1 = jasmine.createSpy("1st listener");
3149
+ const spy2 = jasmine.createSpy("2nd listener");
3150
+ const spy3 = jasmine.createSpy("3rd listener");
3151
+
3152
+ const remove1 = child.$on("evt", spy1);
3153
+ const remove2 = child.$on("evt", spy2);
3154
+ const remove3 = child.$on("evt", spy3);
3155
+
3156
+ spy1.and.callFake(remove1);
3157
+
3158
+ expect(child.$$listeners.get("evt").length).toBe(3);
3159
+
3160
+ // should call all listeners and remove 1st
3161
+ child.$emit("evt");
3162
+ expect(spy1).toHaveBeenCalled();
3163
+ expect(spy2).toHaveBeenCalled();
3164
+ expect(spy3).toHaveBeenCalled();
3165
+ expect(child.$$listeners.get("evt").length).toBe(3); // cleanup will happen on next $emit
3166
+
3167
+ spy1.calls.reset();
3168
+ spy2.calls.reset();
3169
+ spy3.calls.reset();
3170
+
3171
+ // should call only 2nd because 1st was already removed and 2nd removes 3rd
3172
+ spy2.and.callFake(remove3);
3173
+ child.$emit("evt");
3174
+ expect(spy1).not.toHaveBeenCalled();
3175
+ expect(spy2).toHaveBeenCalled();
3176
+ expect(spy3).not.toHaveBeenCalled();
3177
+ expect(child.$$listeners.get("evt").length).toBe(1);
3178
+ });
3179
+
3180
+ it("should allow removing event listener inside a listener on $broadcast", () => {
3181
+ const spy1 = jasmine.createSpy("1st listener");
3182
+ const spy2 = jasmine.createSpy("2nd listener");
3183
+ const spy3 = jasmine.createSpy("3rd listener");
3184
+
3185
+ const remove1 = child.$on("evt", spy1);
3186
+ const remove2 = child.$on("evt", spy2);
3187
+ const remove3 = child.$on("evt", spy3);
3188
+
3189
+ spy1.and.callFake(remove1);
3190
+
3191
+ expect(child.$$listeners.get("evt").length).toBe(3);
3192
+
3193
+ // should call all listeners and remove 1st
3194
+ child.$broadcast("evt");
3195
+ expect(spy1).toHaveBeenCalled();
3196
+ expect(spy2).toHaveBeenCalled();
3197
+ expect(spy3).toHaveBeenCalled();
3198
+ expect(child.$$listeners.get("evt").length).toBe(3); // cleanup will happen on next $broadcast
3199
+
3200
+ spy1.calls.reset();
3201
+ spy2.calls.reset();
3202
+ spy3.calls.reset();
3203
+
3204
+ // should call only 2nd because 1st was already removed and 2nd removes 3rd
3205
+ spy2.and.callFake(remove3);
3206
+ child.$broadcast("evt");
3207
+ expect(spy1).not.toHaveBeenCalled();
3208
+ expect(spy2).toHaveBeenCalled();
3209
+ expect(spy3).not.toHaveBeenCalled();
3210
+ expect(child.$$listeners.get("evt").length).toBe(1);
3211
+ });
3212
+
3213
+ describe("event object", () => {
3214
+ it("should have methods/properties", () => {
3215
+ let eventFired = false;
3216
+
3217
+ child.$on("myEvent", (e) => {
3218
+ expect(e.targetScope).toBe(grandChild);
3219
+ expect(e.currentScope).toBe(child);
3220
+ expect(e.name).toBe("myEvent");
3221
+ eventFired = true;
3222
+ });
3223
+ grandChild.$emit("myEvent");
3224
+ expect(eventFired).toBe(true);
3225
+ });
3226
+
3227
+ it("should have its `currentScope` property set to null after emit", () => {
3228
+ let event;
3229
+
3230
+ child.$on("myEvent", (e) => {
3231
+ event = e;
3232
+ });
3233
+ grandChild.$emit("myEvent");
3234
+
3235
+ expect(event.currentScope).toBe(null);
3236
+ expect(event.targetScope).toBe(grandChild);
3237
+ expect(event.name).toBe("myEvent");
3238
+ });
3239
+
3240
+ it("should have preventDefault method and defaultPrevented property", () => {
3241
+ let event = grandChild.$emit("myEvent");
3242
+ expect(event.defaultPrevented).toBe(false);
3243
+
3244
+ child.$on("myEvent", (event) => {
3245
+ event.preventDefault();
3246
+ });
3247
+ event = grandChild.$emit("myEvent");
3248
+ expect(event.defaultPrevented).toBe(true);
3249
+ expect(event.currentScope).toBe(null);
3250
+ });
3251
+ });
3252
+ });
3253
+
3254
+ describe("$broadcast", () => {
3255
+ describe("event propagation", () => {
3256
+ let log;
3257
+ let child1;
3258
+ let child2;
3259
+ let child3;
3260
+ let grandChild11;
3261
+ let grandChild21;
3262
+ let grandChild22;
3263
+ let grandChild23;
3264
+ let greatGrandChild211;
3265
+
3266
+ function logger(event) {
3267
+ log += `${event.currentScope.id}>`;
3268
+ }
3269
+
3270
+ beforeEach(() => {
3271
+ log = "";
3272
+ child1 = $rootScope.$new();
3273
+ child2 = $rootScope.$new();
3274
+ child3 = $rootScope.$new();
3275
+ grandChild11 = child1.$new();
3276
+ grandChild21 = child2.$new();
3277
+ grandChild22 = child2.$new();
3278
+ grandChild23 = child2.$new();
3279
+ greatGrandChild211 = grandChild21.$new();
3280
+
3281
+ $rootScope.id = 0;
3282
+ child1.id = 1;
3283
+ child2.id = 2;
3284
+ child3.id = 3;
3285
+ grandChild11.id = 11;
3286
+ grandChild21.id = 21;
3287
+ grandChild22.id = 22;
3288
+ grandChild23.id = 23;
3289
+ greatGrandChild211.id = 211;
3290
+
3291
+ $rootScope.$on("myEvent", logger);
3292
+ child1.$on("myEvent", logger);
3293
+ child2.$on("myEvent", logger);
3294
+ child3.$on("myEvent", logger);
3295
+ grandChild11.$on("myEvent", logger);
3296
+ grandChild21.$on("myEvent", logger);
3297
+ grandChild22.$on("myEvent", logger);
3298
+ grandChild23.$on("myEvent", logger);
3299
+ greatGrandChild211.$on("myEvent", logger);
3300
+
3301
+ // R
3302
+ // / | \
3303
+ // 1 2 3
3304
+ // / / | \
3305
+ // 11 21 22 23
3306
+ // |
3307
+ // 211
3308
+ });
3309
+
3310
+ it("should broadcast an event from the root scope", () => {
3311
+ $rootScope.$broadcast("myEvent");
3312
+ expect(log).toBe("0>1>11>2>21>211>22>23>3>");
3313
+ });
3314
+
3315
+ it("should broadcast an event from a child scope", () => {
3316
+ child2.$broadcast("myEvent");
3317
+ expect(log).toBe("2>21>211>22>23>");
3318
+ });
3319
+
3320
+ it("should broadcast an event from a leaf scope with a sibling", () => {
3321
+ grandChild22.$broadcast("myEvent");
3322
+ expect(log).toBe("22>");
3323
+ });
3324
+
3325
+ it("should broadcast an event from a leaf scope without a sibling", () => {
3326
+ grandChild23.$broadcast("myEvent");
3327
+ expect(log).toBe("23>");
3328
+ });
3329
+
3330
+ it("should not not fire any listeners for other events", () => {
3331
+ $rootScope.$broadcast("fooEvent");
3332
+ expect(log).toBe("");
3333
+ });
3334
+
3335
+ it("should not descend past scopes with a $$listerCount of 0 or undefined", () => {
3336
+ const EVENT = "fooEvent";
3337
+ const spy = jasmine.createSpy("listener");
3338
+
3339
+ // Precondition: There should be no listeners for fooEvent.
3340
+ expect($rootScope.$$listenerCount[EVENT]).toBeUndefined();
3341
+
3342
+ // Add a spy listener to a child scope.
3343
+ $rootScope.$$childHead.$$listeners[EVENT] = [spy];
3344
+
3345
+ // $rootScope's count for 'fooEvent' is undefined, so spy should not be called.
3346
+ $rootScope.$broadcast(EVENT);
3347
+ expect(spy).not.toHaveBeenCalled();
3348
+ });
3349
+
3350
+ it("should return event object", () => {
3351
+ const result = child1.$broadcast("some");
3352
+
3353
+ expect(result).toBeDefined();
3354
+ expect(result.name).toBe("some");
3355
+ expect(result.targetScope).toBe(child1);
3356
+ });
3357
+ });
3358
+
3359
+ describe("listener", () => {
3360
+ it("should receive event object", () => {
3361
+ const scope = $rootScope;
3362
+ const child = scope.$new();
3363
+ let eventFired = false;
3364
+
3365
+ child.$on("fooEvent", (event) => {
3366
+ eventFired = true;
3367
+ expect(event.name).toBe("fooEvent");
3368
+ expect(event.targetScope).toBe(scope);
3369
+ expect(event.currentScope).toBe(child);
3370
+ });
3371
+ scope.$broadcast("fooEvent");
3372
+
3373
+ expect(eventFired).toBe(true);
3374
+ });
3375
+
3376
+ it("should have the event's `currentScope` property set to null after broadcast", () => {
3377
+ const scope = $rootScope;
3378
+ const child = scope.$new();
3379
+ let event;
3380
+
3381
+ child.$on("fooEvent", (e) => {
3382
+ event = e;
3383
+ });
3384
+ scope.$broadcast("fooEvent");
3385
+
3386
+ expect(event.name).toBe("fooEvent");
3387
+ expect(event.targetScope).toBe(scope);
3388
+ expect(event.currentScope).toBe(null);
3389
+ });
3390
+
3391
+ it("should support passing messages as constargs", () => {
3392
+ const scope = $rootScope;
3393
+ const child = scope.$new();
3394
+ let args;
3395
+
3396
+ child.$on("fooEvent", function () {
3397
+ args = arguments;
3398
+ });
3399
+ scope.$broadcast("fooEvent", "do", "re", "me", "fa");
3400
+
3401
+ expect(args.length).toBe(5);
3402
+ expect(sliceArgs(args, 1)).toEqual(["do", "re", "me", "fa"]);
3403
+ });
3404
+ });
3405
+ });
3406
+
3407
+ it("should allow recursive $emit/$broadcast", () => {
3408
+ let callCount = 0;
3409
+ $rootScope.$on("evt", ($event, arg0) => {
3410
+ callCount++;
3411
+ if (arg0 !== 1234) {
3412
+ $rootScope.$emit("evt", 1234);
3413
+ $rootScope.$broadcast("evt", 1234);
3414
+ }
3415
+ });
3416
+
3417
+ $rootScope.$emit("evt");
3418
+ $rootScope.$broadcast("evt");
3419
+ expect(callCount).toBe(6);
3420
+ });
3421
+
3422
+ it("should allow recursive $emit/$broadcast between parent/child", () => {
3423
+ const child = $rootScope.$new();
3424
+ let calls = "";
3425
+
3426
+ $rootScope.$on("evt", ($event, arg0) => {
3427
+ calls += "r"; // For "root".
3428
+ if (arg0 === "fromChild") {
3429
+ $rootScope.$broadcast("evt", "fromRoot2");
3430
+ }
3431
+ });
3432
+
3433
+ child.$on("evt", ($event, arg0) => {
3434
+ calls += "c"; // For "child".
3435
+ if (arg0 === "fromRoot1") {
3436
+ child.$emit("evt", "fromChild");
3437
+ }
3438
+ });
3439
+
3440
+ $rootScope.$broadcast("evt", "fromRoot1");
3441
+ expect(calls).toBe("rccrrc");
3442
+ });
3443
+ });
3444
+
3445
+ describe("doc examples", () => {
3446
+ it("should properly fire off watch listeners upon scope changes", () => {
3447
+ // <docs tag="docs1">
3448
+ const scope = $rootScope.$new();
3449
+ scope.salutation = "Hello";
3450
+ scope.name = "World";
3451
+
3452
+ expect(scope.greeting).toEqual(undefined);
3453
+
3454
+ scope.$watch("name", () => {
3455
+ scope.greeting = `${scope.salutation} ${scope.name}!`;
3456
+ }); // initialize the watch
3457
+
3458
+ expect(scope.greeting).toEqual(undefined);
3459
+ scope.name = "Misko";
3460
+ // still old value, since watches have not been called yet
3461
+ expect(scope.greeting).toEqual(undefined);
3462
+
3463
+ scope.$digest(); // fire all the watches
3464
+ expect(scope.greeting).toEqual("Hello Misko!");
3465
+ // </docs>
3466
+ });
3467
+ });
1533
3468
  });