@angular-wave/angular.ts 0.0.1

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 (231) hide show
  1. package/.eslintignore +1 -0
  2. package/.eslintrc.cjs +29 -0
  3. package/.github/workflows/playwright.yml +27 -0
  4. package/CHANGELOG.md +17974 -0
  5. package/CODE_OF_CONDUCT.md +3 -0
  6. package/CONTRIBUTING.md +246 -0
  7. package/DEVELOPERS.md +488 -0
  8. package/LICENSE +22 -0
  9. package/Makefile +31 -0
  10. package/README.md +115 -0
  11. package/RELEASE.md +98 -0
  12. package/SECURITY.md +16 -0
  13. package/TRIAGING.md +135 -0
  14. package/css/angular.css +22 -0
  15. package/dist/angular-ts.cjs.js +36843 -0
  16. package/dist/angular-ts.esm.js +36841 -0
  17. package/dist/angular-ts.umd.js +36848 -0
  18. package/dist/build/angular-animate.js +4272 -0
  19. package/dist/build/angular-aria.js +426 -0
  20. package/dist/build/angular-message-format.js +1072 -0
  21. package/dist/build/angular-messages.js +829 -0
  22. package/dist/build/angular-mocks.js +3757 -0
  23. package/dist/build/angular-parse-ext.js +1275 -0
  24. package/dist/build/angular-resource.js +911 -0
  25. package/dist/build/angular-route.js +1266 -0
  26. package/dist/build/angular-sanitize.js +891 -0
  27. package/dist/build/angular-touch.js +368 -0
  28. package/dist/build/angular.js +36600 -0
  29. package/e2e/unit.spec.ts +15 -0
  30. package/images/android-chrome-192x192.png +0 -0
  31. package/images/android-chrome-512x512.png +0 -0
  32. package/images/apple-touch-icon.png +0 -0
  33. package/images/favicon-16x16.png +0 -0
  34. package/images/favicon-32x32.png +0 -0
  35. package/images/favicon.ico +0 -0
  36. package/images/site.webmanifest +1 -0
  37. package/index.html +104 -0
  38. package/package.json +47 -0
  39. package/playwright.config.ts +78 -0
  40. package/public/circle.html +1 -0
  41. package/public/my_child_directive.html +1 -0
  42. package/public/my_directive.html +1 -0
  43. package/public/my_other_directive.html +1 -0
  44. package/public/test.html +1 -0
  45. package/rollup.config.js +31 -0
  46. package/src/animations/animateCache.js +55 -0
  47. package/src/animations/animateChildrenDirective.js +105 -0
  48. package/src/animations/animateCss.js +1139 -0
  49. package/src/animations/animateCssDriver.js +291 -0
  50. package/src/animations/animateJs.js +367 -0
  51. package/src/animations/animateJsDriver.js +67 -0
  52. package/src/animations/animateQueue.js +851 -0
  53. package/src/animations/animation.js +506 -0
  54. package/src/animations/module.js +779 -0
  55. package/src/animations/ngAnimateSwap.js +119 -0
  56. package/src/animations/rafScheduler.js +50 -0
  57. package/src/animations/shared.js +378 -0
  58. package/src/constants.js +20 -0
  59. package/src/core/animate.js +845 -0
  60. package/src/core/animateCss.js +73 -0
  61. package/src/core/animateRunner.js +195 -0
  62. package/src/core/attributes.js +199 -0
  63. package/src/core/cache.js +45 -0
  64. package/src/core/compile.js +4727 -0
  65. package/src/core/controller.js +225 -0
  66. package/src/core/exceptionHandler.js +63 -0
  67. package/src/core/filter.js +146 -0
  68. package/src/core/interpolate.js +442 -0
  69. package/src/core/interval.js +188 -0
  70. package/src/core/intervalFactory.js +57 -0
  71. package/src/core/location.js +1086 -0
  72. package/src/core/parser/parse.js +2562 -0
  73. package/src/core/parser/parse.md +13 -0
  74. package/src/core/q.js +746 -0
  75. package/src/core/rootScope.js +1596 -0
  76. package/src/core/sanitizeUri.js +85 -0
  77. package/src/core/sce.js +1161 -0
  78. package/src/core/taskTrackerFactory.js +125 -0
  79. package/src/core/timeout.js +121 -0
  80. package/src/core/urlUtils.js +187 -0
  81. package/src/core/utils.js +1349 -0
  82. package/src/directive/a.js +37 -0
  83. package/src/directive/attrs.js +283 -0
  84. package/src/directive/bind.js +51 -0
  85. package/src/directive/bind.md +142 -0
  86. package/src/directive/change.js +12 -0
  87. package/src/directive/change.md +25 -0
  88. package/src/directive/cloak.js +12 -0
  89. package/src/directive/cloak.md +24 -0
  90. package/src/directive/events.js +75 -0
  91. package/src/directive/events.md +166 -0
  92. package/src/directive/form.js +725 -0
  93. package/src/directive/init.js +15 -0
  94. package/src/directive/init.md +41 -0
  95. package/src/directive/input.js +1783 -0
  96. package/src/directive/list.js +46 -0
  97. package/src/directive/list.md +22 -0
  98. package/src/directive/ngClass.js +249 -0
  99. package/src/directive/ngController.js +64 -0
  100. package/src/directive/ngCsp.js +82 -0
  101. package/src/directive/ngIf.js +134 -0
  102. package/src/directive/ngInclude.js +217 -0
  103. package/src/directive/ngModel.js +1356 -0
  104. package/src/directive/ngModelOptions.js +509 -0
  105. package/src/directive/ngOptions.js +670 -0
  106. package/src/directive/ngRef.js +90 -0
  107. package/src/directive/ngRepeat.js +650 -0
  108. package/src/directive/ngShowHide.js +255 -0
  109. package/src/directive/ngSwitch.js +178 -0
  110. package/src/directive/ngTransclude.js +98 -0
  111. package/src/directive/non-bindable.js +11 -0
  112. package/src/directive/non-bindable.md +17 -0
  113. package/src/directive/script.js +30 -0
  114. package/src/directive/select.js +624 -0
  115. package/src/directive/style.js +25 -0
  116. package/src/directive/style.md +23 -0
  117. package/src/directive/validators.js +329 -0
  118. package/src/exts/aria.js +544 -0
  119. package/src/exts/messages.js +852 -0
  120. package/src/filters/filter.js +207 -0
  121. package/src/filters/filter.md +69 -0
  122. package/src/filters/filters.js +239 -0
  123. package/src/filters/json.md +16 -0
  124. package/src/filters/limit-to.js +43 -0
  125. package/src/filters/limit-to.md +19 -0
  126. package/src/filters/order-by.js +183 -0
  127. package/src/filters/order-by.md +83 -0
  128. package/src/index.js +13 -0
  129. package/src/injector.js +1034 -0
  130. package/src/jqLite.js +1117 -0
  131. package/src/loader.js +1320 -0
  132. package/src/public.js +215 -0
  133. package/src/routeToRegExp.js +41 -0
  134. package/src/services/anchorScroll.js +135 -0
  135. package/src/services/browser.js +321 -0
  136. package/src/services/cacheFactory.js +398 -0
  137. package/src/services/cookieReader.js +72 -0
  138. package/src/services/document.js +64 -0
  139. package/src/services/http.js +1537 -0
  140. package/src/services/httpBackend.js +206 -0
  141. package/src/services/log.js +160 -0
  142. package/src/services/templateRequest.js +139 -0
  143. package/test/angular.spec.js +2153 -0
  144. package/test/aria/aria.spec.js +1245 -0
  145. package/test/binding.spec.js +504 -0
  146. package/test/build-test.html +14 -0
  147. package/test/injector.spec.js +2327 -0
  148. package/test/jasmine/jasmine-5.1.2/boot0.js +65 -0
  149. package/test/jasmine/jasmine-5.1.2/boot1.js +133 -0
  150. package/test/jasmine/jasmine-5.1.2/jasmine-html.js +963 -0
  151. package/test/jasmine/jasmine-5.1.2/jasmine.css +320 -0
  152. package/test/jasmine/jasmine-5.1.2/jasmine.js +10824 -0
  153. package/test/jasmine/jasmine-5.1.2/jasmine_favicon.png +0 -0
  154. package/test/jasmine/jasmine-browser.json +17 -0
  155. package/test/jasmine/jasmine.json +9 -0
  156. package/test/jqlite.spec.js +2133 -0
  157. package/test/loader.spec.js +219 -0
  158. package/test/messages/messages.spec.js +1146 -0
  159. package/test/min-err.spec.js +174 -0
  160. package/test/mock-test.html +13 -0
  161. package/test/module-test.html +15 -0
  162. package/test/ng/anomate.spec.js +606 -0
  163. package/test/ng/cache-factor.spec.js +334 -0
  164. package/test/ng/compile.spec.js +17956 -0
  165. package/test/ng/controller-provider.spec.js +227 -0
  166. package/test/ng/cookie-reader.spec.js +98 -0
  167. package/test/ng/directive/a.spec.js +192 -0
  168. package/test/ng/directive/bind.spec.js +334 -0
  169. package/test/ng/directive/boolean.spec.js +136 -0
  170. package/test/ng/directive/change.spec.js +71 -0
  171. package/test/ng/directive/class.spec.js +858 -0
  172. package/test/ng/directive/click.spec.js +38 -0
  173. package/test/ng/directive/cloak.spec.js +44 -0
  174. package/test/ng/directive/constoller.spec.js +194 -0
  175. package/test/ng/directive/element-style.spec.js +92 -0
  176. package/test/ng/directive/event.spec.js +282 -0
  177. package/test/ng/directive/form.spec.js +1518 -0
  178. package/test/ng/directive/href.spec.js +143 -0
  179. package/test/ng/directive/if.spec.js +402 -0
  180. package/test/ng/directive/include.spec.js +828 -0
  181. package/test/ng/directive/init.spec.js +68 -0
  182. package/test/ng/directive/input.spec.js +3810 -0
  183. package/test/ng/directive/list.spec.js +170 -0
  184. package/test/ng/directive/model-options.spec.js +1008 -0
  185. package/test/ng/directive/model.spec.js +1905 -0
  186. package/test/ng/directive/non-bindable.spec.js +55 -0
  187. package/test/ng/directive/options.spec.js +3583 -0
  188. package/test/ng/directive/ref.spec.js +575 -0
  189. package/test/ng/directive/repeat.spec.js +1675 -0
  190. package/test/ng/directive/script.spec.js +52 -0
  191. package/test/ng/directive/scrset.spec.js +67 -0
  192. package/test/ng/directive/select.spec.js +2541 -0
  193. package/test/ng/directive/show-hide.spec.js +253 -0
  194. package/test/ng/directive/src.spec.js +157 -0
  195. package/test/ng/directive/style.spec.js +178 -0
  196. package/test/ng/directive/switch.spec.js +647 -0
  197. package/test/ng/directive/validators.spec.js +717 -0
  198. package/test/ng/document.spec.js +52 -0
  199. package/test/ng/filter/filter.spec.js +714 -0
  200. package/test/ng/filter/filters.spec.js +35 -0
  201. package/test/ng/filter/limit-to.spec.js +251 -0
  202. package/test/ng/filter/order-by.spec.js +891 -0
  203. package/test/ng/filter.spec.js +149 -0
  204. package/test/ng/http-backend.spec.js +398 -0
  205. package/test/ng/http.spec.js +4071 -0
  206. package/test/ng/interpolate.spec.js +642 -0
  207. package/test/ng/interval.spec.js +343 -0
  208. package/test/ng/location.spec.js +3488 -0
  209. package/test/ng/on.spec.js +229 -0
  210. package/test/ng/parse.spec.js +4655 -0
  211. package/test/ng/prop.spec.js +805 -0
  212. package/test/ng/q.spec.js +2904 -0
  213. package/test/ng/root-element.spec.js +16 -0
  214. package/test/ng/sanitize-uri.spec.js +249 -0
  215. package/test/ng/sce.spec.js +660 -0
  216. package/test/ng/scope.spec.js +3442 -0
  217. package/test/ng/template-request.spec.js +236 -0
  218. package/test/ng/timeout.spec.js +351 -0
  219. package/test/ng/url-utils.spec.js +156 -0
  220. package/test/ng/utils.spec.js +144 -0
  221. package/test/original-test.html +21 -0
  222. package/test/public.spec.js +34 -0
  223. package/test/sanitize/bing-html.spec.js +36 -0
  224. package/test/server/express.js +158 -0
  225. package/test/test-utils.js +11 -0
  226. package/tsconfig.json +17 -0
  227. package/types/angular.d.ts +138 -0
  228. package/types/global.d.ts +9 -0
  229. package/types/index.d.ts +2357 -0
  230. package/types/jqlite.d.ts +558 -0
  231. package/vite.config.js +14 -0
@@ -0,0 +1,3442 @@
1
+ import { getQueues } from "../../src/core/rootScope";
2
+ import { extend, sliceArgs } from "../../src/core/utils";
3
+ import { publishExternalAPI } from "../../src/public";
4
+ import { createInjector } from "../../src/injector";
5
+
6
+ describe("Scope", function () {
7
+ let $rootScope;
8
+ let $parse;
9
+ let $browser;
10
+ let logs;
11
+
12
+ beforeEach(() => {
13
+ logs = [];
14
+ delete window.angular;
15
+ publishExternalAPI().decorator("$exceptionHandler", function () {
16
+ return (exception, cause) => {
17
+ logs.push(exception);
18
+ console.error(exception, cause);
19
+ };
20
+ });
21
+
22
+ let injector = createInjector(["ng"]);
23
+ $parse = injector.get("$parse");
24
+ $browser = injector.get("$browser");
25
+
26
+ $rootScope = injector.get("$rootScope");
27
+ });
28
+
29
+ describe("inheritance", () => {
30
+ it("can be constructed and used as an object", () => {
31
+ const scope = $rootScope.$new();
32
+ scope.aProperty = 1;
33
+
34
+ expect(scope.aProperty).toBe(1);
35
+ });
36
+
37
+ it("inherits the parents properties", () => {
38
+ $rootScope.aValue = [1, 2, 3];
39
+
40
+ const child = $rootScope.$new();
41
+
42
+ expect(child.aValue).toEqual([1, 2, 3]);
43
+ });
44
+
45
+ it("does not cause a parent to inherit its properties", () => {
46
+ const child = $rootScope.$new();
47
+ child.aValue = [1, 2, 3];
48
+
49
+ expect(parent.aValue).toBeUndefined();
50
+ });
51
+
52
+ it("inherits the parents properties whenever they are defined", () => {
53
+ const child = $rootScope.$new();
54
+
55
+ $rootScope.aValue = [1, 2, 3];
56
+
57
+ expect(child.aValue).toEqual([1, 2, 3]);
58
+ });
59
+
60
+ it("can be nested at any depth", () => {
61
+ const a = $rootScope;
62
+ const aa = a.$new();
63
+ const aaa = aa.$new();
64
+ const aab = aa.$new();
65
+ const ab = a.$new();
66
+ const abb = ab.$new();
67
+
68
+ a.value = 1;
69
+
70
+ expect(aa.value).toBe(1);
71
+ expect(aaa.value).toBe(1);
72
+ expect(aab.value).toBe(1);
73
+ expect(ab.value).toBe(1);
74
+ expect(abb.value).toBe(1);
75
+
76
+ ab.anotherValue = 2;
77
+
78
+ expect(abb.anotherValue).toBe(2);
79
+ expect(aa.anotherValue).toBeUndefined();
80
+ expect(aaa.anotherValue).toBeUndefined();
81
+ });
82
+
83
+ it("can manipulate a parent scopes property", () => {
84
+ const child = $rootScope.$new();
85
+
86
+ $rootScope.aValue = [1, 2, 3];
87
+ child.aValue.push(4);
88
+
89
+ expect(child.aValue).toEqual([1, 2, 3, 4]);
90
+ expect($rootScope.aValue).toEqual([1, 2, 3, 4]);
91
+ });
92
+ });
93
+
94
+ describe("$id", () => {
95
+ it("should have a unique id", () => {
96
+ expect($rootScope.$id < $rootScope.$new().$id).toBeTruthy();
97
+ });
98
+ });
99
+
100
+ describe("$new()", () => {
101
+ it("should create a child scope", () => {
102
+ const child = $rootScope.$new();
103
+ $rootScope.a = 123;
104
+ expect(child.a).toEqual(123);
105
+ });
106
+
107
+ it("should create a non prototypically inherited child scope", () => {
108
+ const child = $rootScope.$new(true);
109
+ $rootScope.a = 123;
110
+ expect(child.a).toBeUndefined();
111
+ expect(child.$parent).toEqual($rootScope);
112
+ expect(child.$new).toBe($rootScope.$new);
113
+ expect(child.$root).toBe($rootScope);
114
+ });
115
+
116
+ it("should attach the child scope to a specified parent", () => {
117
+ const isolated = $rootScope.$new(true);
118
+ const trans = $rootScope.$new(false, isolated);
119
+ $rootScope.a = 123;
120
+ expect(isolated.a).toBeUndefined();
121
+ expect(trans.a).toEqual(123);
122
+ expect(trans.$parent).toBe(isolated);
123
+ });
124
+ });
125
+
126
+ describe("$root", () => {
127
+ it("should point to itself", () => {
128
+ expect($rootScope.$root).toEqual($rootScope);
129
+ expect($rootScope.hasOwnProperty("$root")).toBeTruthy();
130
+ });
131
+
132
+ it("should expose the constructor", () => {
133
+ expect(Object.getPrototypeOf($rootScope)).toBe(
134
+ $rootScope.constructor.prototype,
135
+ );
136
+ });
137
+
138
+ it("should not have $root on children, but should inherit", () => {
139
+ const child = $rootScope.$new();
140
+ expect(child.$root).toEqual($rootScope);
141
+ expect(child.hasOwnProperty("$root")).toBeFalsy();
142
+ });
143
+ });
144
+
145
+ describe("$parent", () => {
146
+ it("should point to parent", () => {
147
+ const child = $rootScope.$new();
148
+ expect($rootScope.$parent).toEqual(null);
149
+ expect(child.$parent).toEqual($rootScope);
150
+ expect(child.$new().$parent).toEqual(child);
151
+ });
152
+ });
153
+
154
+ describe("this", () => {
155
+ it("should evaluate 'this' to be the scope", () => {
156
+ const child = $rootScope.$new();
157
+ expect($rootScope.$eval("this")).toEqual($rootScope);
158
+ expect(child.$eval("this")).toEqual(child);
159
+ });
160
+
161
+ it("'this' should not be recursive", () => {
162
+ expect($rootScope.$eval("this.this")).toBeUndefined();
163
+ expect($rootScope.$eval("$parent.this")).toBeUndefined();
164
+ });
165
+
166
+ it("should not be able to overwrite the 'this' keyword", () => {
167
+ $rootScope.this = 123;
168
+ expect($rootScope.$eval("this")).toEqual($rootScope);
169
+ });
170
+
171
+ it("should be able to access a constiable named 'this'", () => {
172
+ $rootScope.this = 42;
173
+ expect($rootScope.$eval("this['this']")).toBe(42);
174
+ });
175
+ });
176
+
177
+ describe("$watch/$digest", () => {
178
+ it("should watch and fire on simple property change", () => {
179
+ const spy = jasmine.createSpy();
180
+ $rootScope.$watch("name", spy);
181
+ $rootScope.$digest();
182
+ spy.calls.reset();
183
+
184
+ expect(spy).not.toHaveBeenCalled();
185
+ $rootScope.$digest();
186
+ expect(spy).not.toHaveBeenCalled();
187
+ $rootScope.name = "misko";
188
+ $rootScope.$digest();
189
+ expect(spy).toHaveBeenCalledWith("misko", undefined, $rootScope);
190
+ });
191
+
192
+ it("should not expose the `inner working of watch", () => {
193
+ function Getter() {
194
+ expect(this).toBeUndefined();
195
+ return "foo";
196
+ }
197
+ function Listener() {
198
+ expect(this).toBeUndefined();
199
+ }
200
+
201
+ $rootScope.$watch(Getter, Listener);
202
+ $rootScope.$digest();
203
+ });
204
+
205
+ it("should watch and fire on expression change", () => {
206
+ const spy = jasmine.createSpy();
207
+ $rootScope.$watch("name.first", spy);
208
+ $rootScope.$digest();
209
+ spy.calls.reset();
210
+
211
+ $rootScope.name = {};
212
+ expect(spy).not.toHaveBeenCalled();
213
+ $rootScope.$digest();
214
+ expect(spy).not.toHaveBeenCalled();
215
+ $rootScope.name.first = "misko";
216
+ $rootScope.$digest();
217
+ expect(spy).toHaveBeenCalled();
218
+ });
219
+
220
+ it("should decrement the watcherCount when destroying a child scope", () => {
221
+ const child1 = $rootScope.$new();
222
+ const child2 = $rootScope.$new();
223
+ const grandChild1 = child1.$new();
224
+ const grandChild2 = child2.$new();
225
+ child1.$watch("a", () => {});
226
+ child2.$watch("a", () => {});
227
+ grandChild1.$watch("a", () => {});
228
+ grandChild2.$watch("a", () => {});
229
+
230
+ expect($rootScope.$$watchersCount).toBe(4);
231
+ expect(child1.$$watchersCount).toBe(2);
232
+ expect(child2.$$watchersCount).toBe(2);
233
+ expect(grandChild1.$$watchersCount).toBe(1);
234
+ expect(grandChild2.$$watchersCount).toBe(1);
235
+
236
+ grandChild2.$destroy();
237
+ expect(child2.$$watchersCount).toBe(1);
238
+ expect($rootScope.$$watchersCount).toBe(3);
239
+ child1.$destroy();
240
+ expect($rootScope.$$watchersCount).toBe(1);
241
+ });
242
+
243
+ it("should decrement the watcherCount when calling the remove function", () => {
244
+ const child1 = $rootScope.$new();
245
+ const child2 = $rootScope.$new();
246
+ const grandChild1 = child1.$new();
247
+ const grandChild2 = child2.$new();
248
+ let remove1;
249
+ let remove2;
250
+
251
+ remove1 = child1.$watch("a", () => {});
252
+ child2.$watch("a", () => {});
253
+ grandChild1.$watch("a", () => {});
254
+ remove2 = grandChild2.$watch("a", () => {});
255
+
256
+ remove2();
257
+ expect(grandChild2.$$watchersCount).toBe(0);
258
+ expect(child2.$$watchersCount).toBe(1);
259
+ expect($rootScope.$$watchersCount).toBe(3);
260
+ remove1();
261
+ expect(grandChild1.$$watchersCount).toBe(1);
262
+ expect(child1.$$watchersCount).toBe(1);
263
+ expect($rootScope.$$watchersCount).toBe(2);
264
+
265
+ // Execute everything a second time to be sure that calling the remove function
266
+ // several times, it only decrements the counter once
267
+ remove2();
268
+ expect(child2.$$watchersCount).toBe(1);
269
+ expect($rootScope.$$watchersCount).toBe(2);
270
+ remove1();
271
+ expect(child1.$$watchersCount).toBe(1);
272
+ expect($rootScope.$$watchersCount).toBe(2);
273
+ });
274
+
275
+ describe("constants cleanup", () => {
276
+ beforeEach(() => (logs = []));
277
+ it("should remove $watch of constant literals after initial digest", () => {
278
+ $rootScope.$watch("[]", () => {});
279
+ $rootScope.$watch("{}", () => {});
280
+ $rootScope.$watch("1", () => {});
281
+ $rootScope.$watch('"foo"', () => {});
282
+ expect($rootScope.$$watchers.length).not.toEqual(0);
283
+ $rootScope.$digest();
284
+
285
+ expect($rootScope.$$watchers.length).toEqual(0);
286
+ });
287
+
288
+ it("should remove $watchCollection of constant literals after initial digest", () => {
289
+ $rootScope.$watchCollection("[]", () => {});
290
+ $rootScope.$watchCollection("{}", () => {});
291
+ $rootScope.$watchCollection("1", () => {});
292
+ $rootScope.$watchCollection('"foo"', () => {});
293
+ expect($rootScope.$$watchers.length).not.toEqual(0);
294
+ $rootScope.$digest();
295
+
296
+ expect($rootScope.$$watchers.length).toEqual(0);
297
+ });
298
+
299
+ it("should remove $watchGroup of constant literals after initial digest", () => {
300
+ $rootScope.$watchGroup(["[]", "{}", "1", '"foo"'], () => {});
301
+ expect($rootScope.$$watchers.length).not.toEqual(0);
302
+ $rootScope.$digest();
303
+
304
+ expect($rootScope.$$watchers.length).toEqual(0);
305
+ });
306
+
307
+ it("should remove $watch of filtered constant literals after initial digest", () => {
308
+ $rootScope.$watch('[1] | filter:"x"', () => {});
309
+ $rootScope.$watch("1 | limitTo:2", () => {});
310
+ expect($rootScope.$$watchers.length).not.toEqual(0);
311
+ $rootScope.$digest();
312
+
313
+ expect($rootScope.$$watchers.length).toEqual(0);
314
+ });
315
+
316
+ it("should remove $watchCollection of filtered constant literals after initial digest", () => {
317
+ $rootScope.$watchCollection('[1] | filter:"x"', () => {});
318
+ expect($rootScope.$$watchers.length).not.toEqual(0);
319
+ $rootScope.$digest();
320
+
321
+ expect($rootScope.$$watchers.length).toEqual(0);
322
+ });
323
+
324
+ it("should remove $watchGroup of filtered constant literals after initial digest", () => {
325
+ $rootScope.$watchGroup(['[1] | filter:"x"', "1 | limitTo:2"], () => {});
326
+ expect($rootScope.$$watchers.length).not.toEqual(0);
327
+ $rootScope.$digest();
328
+
329
+ expect($rootScope.$$watchers.length).toEqual(0);
330
+ });
331
+
332
+ it("should remove $watch of constant expressions after initial digest", () => {
333
+ $rootScope.$watch("1 + 1", () => {});
334
+ $rootScope.$watch('"a" + "b"', () => {});
335
+ $rootScope.$watch('"ab".length', () => {});
336
+ $rootScope.$watch("[].length", () => {});
337
+ $rootScope.$watch("(1 + 1) | limitTo:2", () => {});
338
+ expect($rootScope.$$watchers.length).not.toEqual(0);
339
+ $rootScope.$digest();
340
+
341
+ expect($rootScope.$$watchers.length).toEqual(0);
342
+ });
343
+ });
344
+
345
+ describe("onetime cleanup", () => {
346
+ it("should clean up stable watches on the watch queue", () => {
347
+ $rootScope.$watch("::foo", () => {});
348
+ expect($rootScope.$$watchers.length).toEqual(1);
349
+ $rootScope.$digest();
350
+ expect($rootScope.$$watchers.length).toEqual(1);
351
+
352
+ $rootScope.foo = "foo";
353
+ $rootScope.$digest();
354
+ expect($rootScope.$$watchers.length).toEqual(0);
355
+ });
356
+
357
+ it("should clean up stable watches from $watchCollection", () => {
358
+ $rootScope.$watchCollection("::foo", () => {});
359
+ expect($rootScope.$$watchers.length).toEqual(1);
360
+
361
+ $rootScope.$digest();
362
+ expect($rootScope.$$watchers.length).toEqual(1);
363
+
364
+ $rootScope.foo = [];
365
+ $rootScope.$digest();
366
+ expect($rootScope.$$watchers.length).toEqual(0);
367
+ });
368
+
369
+ it("should clean up stable watches from $watchCollection literals", () => {
370
+ $rootScope.$watchCollection("::[foo, bar]", () => {});
371
+ expect($rootScope.$$watchers.length).toEqual(1);
372
+
373
+ $rootScope.$digest();
374
+ expect($rootScope.$$watchers.length).toEqual(1);
375
+
376
+ $rootScope.foo = 1;
377
+ $rootScope.$digest();
378
+ expect($rootScope.$$watchers.length).toEqual(1);
379
+
380
+ $rootScope.foo = 2;
381
+ $rootScope.$digest();
382
+ expect($rootScope.$$watchers.length).toEqual(1);
383
+
384
+ $rootScope.bar = 3;
385
+ $rootScope.$digest();
386
+ expect($rootScope.$$watchers.length).toEqual(0);
387
+ });
388
+
389
+ it("should clean up stable watches from $watchGroup", () => {
390
+ $rootScope.$watchGroup(["::foo", "::bar"], () => {});
391
+ expect($rootScope.$$watchers.length).toEqual(2);
392
+
393
+ $rootScope.$digest();
394
+ expect($rootScope.$$watchers.length).toEqual(2);
395
+
396
+ $rootScope.foo = "foo";
397
+ $rootScope.$digest();
398
+ expect($rootScope.$$watchers.length).toEqual(1);
399
+
400
+ $rootScope.bar = "bar";
401
+ $rootScope.$digest();
402
+ expect($rootScope.$$watchers.length).toEqual(0);
403
+ });
404
+ });
405
+
406
+ it("should delegate exceptions", () => {
407
+ $rootScope.$watch("a", () => {
408
+ throw new Error("abc");
409
+ });
410
+ $rootScope.a = 1;
411
+ $rootScope.$digest();
412
+ expect(logs[0]).toMatch(/abc/);
413
+ });
414
+
415
+ it("should fire watches in order of addition", () => {
416
+ // this is not an external guarantee, just our own sanity
417
+ logs = "";
418
+ $rootScope.$watch("a", () => {
419
+ logs += "a";
420
+ });
421
+ $rootScope.$watch("b", () => {
422
+ logs += "b";
423
+ });
424
+ // constant expressions have slightly different handling,
425
+ // let's ensure they are kept in the same list as others
426
+ $rootScope.$watch("1", () => {
427
+ logs += "1";
428
+ });
429
+ $rootScope.$watch("c", () => {
430
+ logs += "c";
431
+ });
432
+ $rootScope.$watch("2", () => {
433
+ logs += "2";
434
+ });
435
+ $rootScope.a = $rootScope.b = $rootScope.c = 1;
436
+ $rootScope.$digest();
437
+ expect(logs).toEqual("ab1c2");
438
+ });
439
+
440
+ it("should call child $watchers in addition order", () => {
441
+ // this is not an external guarantee, just our own sanity
442
+ logs = "";
443
+ const childA = $rootScope.$new();
444
+ const childB = $rootScope.$new();
445
+ const childC = $rootScope.$new();
446
+ childA.$watch("a", () => {
447
+ logs += "a";
448
+ });
449
+ childB.$watch("b", () => {
450
+ logs += "b";
451
+ });
452
+ childC.$watch("c", () => {
453
+ logs += "c";
454
+ });
455
+ childA.a = childB.b = childC.c = 1;
456
+ $rootScope.$digest();
457
+ expect(logs).toEqual("abc");
458
+ });
459
+
460
+ it("should allow $digest on a child scope with and without a right sibling", () => {
461
+ // tests a traversal edge case which we originally missed
462
+ logs = "";
463
+ const childA = $rootScope.$new();
464
+ const childB = $rootScope.$new();
465
+
466
+ $rootScope.$watch(() => {
467
+ logs += "r";
468
+ });
469
+ childA.$watch(() => {
470
+ logs += "a";
471
+ });
472
+ childB.$watch(() => {
473
+ logs += "b";
474
+ });
475
+
476
+ // init
477
+ $rootScope.$digest();
478
+ expect(logs).toBe("rabrab");
479
+
480
+ logs = "";
481
+ childA.$digest();
482
+ expect(logs).toBe("a");
483
+
484
+ logs = "";
485
+ childB.$digest();
486
+ expect(logs).toBe("b");
487
+ });
488
+
489
+ it("should repeat watch cycle while model changes are identified", () => {
490
+ logs = "";
491
+ $rootScope.$watch("c", (v) => {
492
+ $rootScope.d = v;
493
+ logs += "c";
494
+ });
495
+ $rootScope.$watch("b", (v) => {
496
+ $rootScope.c = v;
497
+ logs += "b";
498
+ });
499
+ $rootScope.$watch("a", (v) => {
500
+ $rootScope.b = v;
501
+ logs += "a";
502
+ });
503
+ $rootScope.$digest();
504
+ logs = "";
505
+ $rootScope.a = 1;
506
+ $rootScope.$digest();
507
+ expect($rootScope.b).toEqual(1);
508
+ expect($rootScope.c).toEqual(1);
509
+ expect($rootScope.d).toEqual(1);
510
+ expect(logs).toEqual("abc");
511
+ });
512
+
513
+ it("should repeat watch cycle from the root element", () => {
514
+ logs = "";
515
+ const child = $rootScope.$new();
516
+ $rootScope.$watch(() => {
517
+ logs += "a";
518
+ });
519
+ child.$watch(() => {
520
+ logs += "b";
521
+ });
522
+ $rootScope.$digest();
523
+ expect(logs).toEqual("abab");
524
+ });
525
+
526
+ it("should prevent infinite recursion and print watcher expression", () => {
527
+ $rootScope.$watch("a", function () {
528
+ $rootScope.b++;
529
+ });
530
+ $rootScope.$watch("b", function () {
531
+ $rootScope.a++;
532
+ });
533
+ $rootScope.a = $rootScope.b = 0;
534
+ expect(function () {
535
+ $rootScope.$digest();
536
+ }).toThrow();
537
+
538
+ expect($rootScope.$$phase).toBeNull();
539
+ });
540
+
541
+ it("should prevent infinite recursion and print watcher function name or body", () => {
542
+ $rootScope.$watch(
543
+ () => $rootScope.a,
544
+ () => {
545
+ $rootScope.b++;
546
+ },
547
+ );
548
+ $rootScope.$watch(
549
+ () => $rootScope.b,
550
+ () => {
551
+ $rootScope.a++;
552
+ },
553
+ );
554
+ $rootScope.a = $rootScope.b = 0;
555
+
556
+ try {
557
+ $rootScope.$digest();
558
+ throw new Error("Should have thrown exception");
559
+ } catch (e) {
560
+ console.error(e);
561
+ expect(e.message.match(/rootScope.a/g).length).toBeTruthy();
562
+ expect(e.message.match(/rootScope.b/g).length).toBeTruthy();
563
+ }
564
+ });
565
+
566
+ // it("should prevent infinite loop when creating and resolving a promise in a watched expression", () => {
567
+ // module(($rootScopeProvider) => {
568
+ // $rootScopeProvider.digestTtl(10);
569
+ // });
570
+ // () => {
571
+ // const d = $q.defer();
572
+
573
+ // d.resolve("Hello, world.");
574
+ // $rootScope.$watch(
575
+ // () => {
576
+ // const $d2 = $q.defer();
577
+ // $d2.resolve("Goodbye.");
578
+ // $d2.promise.then(() => {});
579
+ // return d.promise;
580
+ // },
581
+ // () => 0,
582
+ // );
583
+
584
+ // expect(() => {
585
+ // $rootScope.$digest();
586
+ // }).toThrow(
587
+ // "$rootScope",
588
+ // "infdig",
589
+ // "10 $digest() iterations reached. Aborting!\n" +
590
+ // "Watchers fired in the last 5 iterations: []",
591
+ // );
592
+
593
+ // expect($rootScope.$$phase).toBeNull();
594
+ // });
595
+ // });
596
+
597
+ it("should not fire upon $watch registration on initial $digest", () => {
598
+ logs = "";
599
+ $rootScope.a = 1;
600
+ $rootScope.$watch("a", () => {
601
+ logs += "a";
602
+ });
603
+ $rootScope.$watch("b", () => {
604
+ logs += "b";
605
+ });
606
+ $rootScope.$digest();
607
+ logs = "";
608
+ $rootScope.$digest();
609
+ expect(logs).toEqual("");
610
+ });
611
+
612
+ it("should watch objects", () => {
613
+ logs = "";
614
+ $rootScope.a = [];
615
+ $rootScope.b = {};
616
+ $rootScope.$watch(
617
+ "a",
618
+ (value) => {
619
+ logs += ".";
620
+ expect(value).toBe($rootScope.a);
621
+ },
622
+ true,
623
+ );
624
+ $rootScope.$watch(
625
+ "b",
626
+ (value) => {
627
+ logs += "!";
628
+ expect(value).toBe($rootScope.b);
629
+ },
630
+ true,
631
+ );
632
+ $rootScope.$digest();
633
+ logs = "";
634
+
635
+ $rootScope.a.push({});
636
+ $rootScope.b.name = "";
637
+
638
+ $rootScope.$digest();
639
+ expect(logs).toEqual(".!");
640
+ });
641
+
642
+ it("should watch functions", () => {
643
+ $rootScope.fn = function () {
644
+ return "a";
645
+ };
646
+ $rootScope.$watch("fn", (fn) => {
647
+ logs.push(fn());
648
+ });
649
+ $rootScope.$digest();
650
+ expect(logs).toEqual(["a"]);
651
+ $rootScope.fn = function () {
652
+ return "b";
653
+ };
654
+ $rootScope.$digest();
655
+ expect(logs).toEqual(["a", "b"]);
656
+ });
657
+
658
+ it("should prevent $digest recursion", () => {
659
+ let callCount = 0;
660
+ $rootScope.$watch("name", () => {
661
+ expect(() => {
662
+ $rootScope.$digest();
663
+ }).toThrowError(/digest already in progress/);
664
+ callCount++;
665
+ });
666
+ $rootScope.name = "a";
667
+ $rootScope.$digest();
668
+ expect(callCount).toEqual(1);
669
+ });
670
+
671
+ it("should allow a watch to be added while in a digest", () => {
672
+ const watch1 = jasmine.createSpy("watch1");
673
+ const watch2 = jasmine.createSpy("watch2");
674
+ $rootScope.$watch("foo", () => {
675
+ $rootScope.$watch("foo", watch1);
676
+ $rootScope.$watch("foo", watch2);
677
+ });
678
+ $rootScope.$apply("foo = true");
679
+ expect(watch1).toHaveBeenCalled();
680
+ expect(watch2).toHaveBeenCalled();
681
+ });
682
+
683
+ it("should not skip watchers when adding new watchers during digest", () => {
684
+ const watchFn1 = function () {
685
+ logs.push(1);
686
+ };
687
+ const watchFn2 = function () {
688
+ logs.push(2);
689
+ };
690
+ const watchFn3 = function () {
691
+ logs.push(3);
692
+ };
693
+ const addWatcherOnce = function (newValue, oldValue) {
694
+ if (newValue === oldValue) {
695
+ $rootScope.$watch(watchFn3);
696
+ }
697
+ };
698
+
699
+ $rootScope.$watch(watchFn1, addWatcherOnce);
700
+ $rootScope.$watch(watchFn2);
701
+
702
+ $rootScope.$digest();
703
+
704
+ expect(logs).toEqual([1, 2, 3, 1, 2, 3]);
705
+ });
706
+
707
+ it("should not run the current watcher twice when removing a watcher during digest", () => {
708
+ let removeWatcher3;
709
+
710
+ const watchFn3 = function () {
711
+ logs.push(3);
712
+ };
713
+ const watchFn2 = function () {
714
+ logs.push(2);
715
+ };
716
+ const watchFn1 = function () {
717
+ logs.push(1);
718
+ };
719
+ const removeWatcherOnce = function (newValue, oldValue) {
720
+ if (newValue === oldValue) {
721
+ removeWatcher3();
722
+ }
723
+ };
724
+
725
+ $rootScope.$watch(watchFn1, removeWatcherOnce);
726
+ $rootScope.$watch(watchFn2);
727
+ removeWatcher3 = $rootScope.$watch(watchFn3);
728
+
729
+ $rootScope.$digest();
730
+
731
+ expect(logs).toEqual([1, 2, 1, 2]);
732
+ });
733
+
734
+ it("should not skip watchers when removing itself during digest", () => {
735
+ let removeWatcher1;
736
+
737
+ const watchFn3 = function () {
738
+ logs.push(3);
739
+ };
740
+ const watchFn2 = function () {
741
+ logs.push(2);
742
+ };
743
+ const watchFn1 = function () {
744
+ logs.push(1);
745
+ };
746
+ const removeItself = function () {
747
+ removeWatcher1();
748
+ };
749
+
750
+ removeWatcher1 = $rootScope.$watch(watchFn1, removeItself);
751
+ $rootScope.$watch(watchFn2);
752
+ $rootScope.$watch(watchFn3);
753
+
754
+ $rootScope.$digest();
755
+
756
+ expect(logs).toEqual([1, 2, 3, 2, 3]);
757
+ });
758
+
759
+ it("should not infinitely digest when current value is NaN", () => {
760
+ $rootScope.$watch(() => NaN);
761
+
762
+ expect(() => {
763
+ $rootScope.$digest();
764
+ }).not.toThrow();
765
+ });
766
+
767
+ it("should always call the watcher with newVal and oldVal equal on the first run", () => {
768
+ function logger(scope, newVal, oldVal) {
769
+ const val =
770
+ newVal === oldVal || (newVal !== oldVal && oldVal !== newVal)
771
+ ? newVal
772
+ : "xxx";
773
+ logs.push(val);
774
+ }
775
+
776
+ $rootScope.$watch(() => NaN, logger);
777
+ $rootScope.$watch(() => undefined, logger);
778
+ $rootScope.$watch(() => "", logger);
779
+ $rootScope.$watch(() => false, logger);
780
+ $rootScope.$watch(() => ({}), logger, true);
781
+ $rootScope.$watch(() => 23, logger);
782
+
783
+ $rootScope.$digest();
784
+ expect(isNaN(logs.shift())).toBe(true); // jasmine's toBe and toEqual don't work well with NaNs
785
+ expect(logs).toEqual([undefined, "", false, {}, 23]);
786
+ logs = [];
787
+ $rootScope.$digest();
788
+ expect(logs).toEqual([]);
789
+ });
790
+
791
+ describe("$watch deregistration", () => {
792
+ beforeEach(() => (logs = []));
793
+ it("should return a function that allows listeners to be deregistered", () => {
794
+ const listener = jasmine.createSpy("watch listener");
795
+ let listenerRemove;
796
+
797
+ listenerRemove = $rootScope.$watch("foo", listener);
798
+ $rootScope.$digest(); // init
799
+ expect(listener).toHaveBeenCalled();
800
+ expect(listenerRemove).toBeDefined();
801
+
802
+ listener.calls.reset();
803
+ $rootScope.foo = "bar";
804
+ $rootScope.$digest(); // trigger
805
+ expect(listener).toHaveBeenCalled();
806
+
807
+ listener.calls.reset();
808
+ $rootScope.foo = "baz";
809
+ listenerRemove();
810
+ $rootScope.$digest(); // trigger
811
+ expect(listener).not.toHaveBeenCalled();
812
+ });
813
+
814
+ it("should allow a watch to be deregistered while in a digest", () => {
815
+ let remove1;
816
+ let remove2;
817
+ $rootScope.$watch("remove", () => {
818
+ remove1();
819
+ remove2();
820
+ });
821
+ remove1 = $rootScope.$watch("thing", () => {});
822
+ remove2 = $rootScope.$watch("thing", () => {});
823
+ expect(() => {
824
+ $rootScope.$apply("remove = true");
825
+ }).not.toThrow();
826
+ });
827
+
828
+ it("should not mess up the digest loop if deregistration happens during digest", () => {
829
+ // we are testing this due to regression #5525 which is related to how the digest loops lastDirtyWatch short-circuiting optimization works
830
+ // scenario: watch1 deregistering watch1
831
+ let scope = $rootScope.$new();
832
+ let deregWatch1 = scope.$watch(
833
+ () => {
834
+ logs.push("watch1");
835
+ return "watch1";
836
+ },
837
+ () => {
838
+ deregWatch1();
839
+ logs.push("watchAction1");
840
+ },
841
+ );
842
+ scope.$watch(
843
+ () => {
844
+ logs.push("watch2");
845
+ return "watch2";
846
+ },
847
+ () => logs.push("watchAction2"),
848
+ );
849
+ scope.$watch(
850
+ () => {
851
+ logs.push("watch3");
852
+ return "watch3";
853
+ },
854
+ () => logs.push("watchAction3"),
855
+ );
856
+
857
+ $rootScope.$digest();
858
+
859
+ expect(logs).toEqual([
860
+ "watch1",
861
+ "watchAction1",
862
+ "watch2",
863
+ "watchAction2",
864
+ "watch3",
865
+ "watchAction3",
866
+ "watch2",
867
+ "watch3",
868
+ ]);
869
+ scope.$destroy();
870
+ logs = [];
871
+
872
+ // scenario: watch1 deregistering watch2
873
+ scope = $rootScope.$new();
874
+ scope.$watch(
875
+ () => {
876
+ logs.push("watch1");
877
+ return "watch1";
878
+ },
879
+ () => {
880
+ deregWatch2();
881
+ logs.push("watchAction1");
882
+ },
883
+ );
884
+ let deregWatch2 = scope.$watch(
885
+ () => {
886
+ logs.push("watch2");
887
+ return "watch2";
888
+ },
889
+ () => logs.push("watchAction2"),
890
+ );
891
+ scope.$watch(
892
+ () => {
893
+ logs.push("watch3");
894
+ return "watch3";
895
+ },
896
+ () => logs.push("watchAction3"),
897
+ );
898
+
899
+ $rootScope.$digest();
900
+
901
+ expect(logs).toEqual([
902
+ "watch1",
903
+ "watchAction1",
904
+ "watch3",
905
+ "watchAction3",
906
+ "watch1",
907
+ "watch3",
908
+ ]);
909
+ scope.$destroy();
910
+ logs = [];
911
+
912
+ // scenario: watch2 deregistering watch1
913
+ scope = $rootScope.$new();
914
+ deregWatch1 = scope.$watch(
915
+ () => {
916
+ logs.push("watch1");
917
+ return "watch1";
918
+ },
919
+ () => logs.push("watchAction1"),
920
+ );
921
+ scope.$watch(
922
+ () => {
923
+ logs.push("watch2");
924
+ return "watch2";
925
+ },
926
+ () => {
927
+ deregWatch1();
928
+ logs.push("watchAction2");
929
+ },
930
+ );
931
+ scope.$watch(
932
+ () => {
933
+ logs.push("watch3");
934
+ return "watch3";
935
+ },
936
+ () => logs.push("watchAction3"),
937
+ );
938
+
939
+ $rootScope.$digest();
940
+
941
+ expect(logs).toEqual([
942
+ "watch1",
943
+ "watchAction1",
944
+ "watch2",
945
+ "watchAction2",
946
+ "watch3",
947
+ "watchAction3",
948
+ "watch2",
949
+ "watch3",
950
+ ]);
951
+ });
952
+ });
953
+
954
+ describe("$watchCollection", () => {
955
+ describe("constiable", () => {
956
+ let deregister;
957
+ beforeEach(() => {
958
+ logs = [];
959
+ deregister = $rootScope.$watchCollection("obj", (newVal, oldVal) => {
960
+ const msg = { newVal, oldVal };
961
+
962
+ if (newVal === oldVal) {
963
+ msg.identical = true;
964
+ }
965
+
966
+ logs.push(msg);
967
+ });
968
+ });
969
+
970
+ it("should not trigger if nothing change", () => {
971
+ $rootScope.$digest();
972
+ expect(logs).toEqual([
973
+ { newVal: undefined, oldVal: undefined, identical: true },
974
+ ]);
975
+ logs = [];
976
+ $rootScope.$digest();
977
+ expect(logs).toEqual([]);
978
+ });
979
+
980
+ it("should allow deregistration", () => {
981
+ $rootScope.obj = [];
982
+ $rootScope.$digest();
983
+ expect(logs.length).toBe(1);
984
+ logs = [];
985
+
986
+ $rootScope.obj.push("a");
987
+ deregister();
988
+
989
+ $rootScope.$digest();
990
+ expect(logs).toEqual([]);
991
+ });
992
+
993
+ describe("array", () => {
994
+ it("should return oldCollection === newCollection only on the first listener call", () => {
995
+ // first time should be identical
996
+ $rootScope.obj = ["a", "b"];
997
+ $rootScope.$digest();
998
+ expect(logs).toEqual([
999
+ { newVal: ["a", "b"], oldVal: ["a", "b"], identical: true },
1000
+ ]);
1001
+ logs = [];
1002
+
1003
+ // second time should be different
1004
+ $rootScope.obj[1] = "c";
1005
+ $rootScope.$digest();
1006
+ expect(logs).toEqual([{ newVal: ["a", "c"], oldVal: ["a", "b"] }]);
1007
+ });
1008
+
1009
+ it("should trigger when property changes into array", () => {
1010
+ $rootScope.obj = "test";
1011
+ $rootScope.$digest();
1012
+ expect(logs).toEqual([
1013
+ { newVal: "test", oldVal: "test", identical: true },
1014
+ ]);
1015
+
1016
+ logs = [];
1017
+ $rootScope.obj = [];
1018
+ $rootScope.$digest();
1019
+ expect(logs).toEqual([{ newVal: [], oldVal: "test" }]);
1020
+
1021
+ logs = [];
1022
+ $rootScope.obj = {};
1023
+ $rootScope.$digest();
1024
+ expect(logs).toEqual([{ newVal: {}, oldVal: [] }]);
1025
+
1026
+ logs = [];
1027
+ $rootScope.obj = [];
1028
+ $rootScope.$digest();
1029
+ expect(logs).toEqual([{ newVal: [], oldVal: {} }]);
1030
+
1031
+ logs = [];
1032
+ $rootScope.obj = undefined;
1033
+ $rootScope.$digest();
1034
+ expect(logs).toEqual([{ newVal: undefined, oldVal: [] }]);
1035
+ });
1036
+
1037
+ it("should not trigger change when object in collection changes", () => {
1038
+ $rootScope.obj = [{}];
1039
+ $rootScope.$digest();
1040
+ expect(logs).toEqual([
1041
+ { newVal: [{}], oldVal: [{}], identical: true },
1042
+ ]);
1043
+
1044
+ logs = [];
1045
+ $rootScope.obj[0].name = "foo";
1046
+ $rootScope.$digest();
1047
+ expect(logs).toEqual([]);
1048
+ });
1049
+
1050
+ it("should watch array properties", () => {
1051
+ $rootScope.obj = [];
1052
+ $rootScope.$digest();
1053
+ expect(logs).toEqual([{ newVal: [], oldVal: [], identical: true }]);
1054
+
1055
+ logs = [];
1056
+ $rootScope.obj.push("a");
1057
+ $rootScope.$digest();
1058
+ expect(logs).toEqual([{ newVal: ["a"], oldVal: [] }]);
1059
+
1060
+ logs = [];
1061
+ $rootScope.obj[0] = "b";
1062
+ $rootScope.$digest();
1063
+ expect(logs).toEqual([{ newVal: ["b"], oldVal: ["a"] }]);
1064
+
1065
+ logs = [];
1066
+ $rootScope.obj.push([]);
1067
+ $rootScope.obj.push({});
1068
+ $rootScope.$digest();
1069
+ expect(logs).toEqual([{ newVal: ["b", [], {}], oldVal: ["b"] }]);
1070
+
1071
+ logs = [];
1072
+ const temp = $rootScope.obj[1];
1073
+ $rootScope.obj[1] = $rootScope.obj[2];
1074
+ $rootScope.obj[2] = temp;
1075
+ $rootScope.$digest();
1076
+ expect(logs).toEqual([
1077
+ { newVal: ["b", {}, []], oldVal: ["b", [], {}] },
1078
+ ]);
1079
+
1080
+ logs = [];
1081
+ $rootScope.obj.shift();
1082
+ $rootScope.$digest();
1083
+ expect(logs).toEqual([{ newVal: [{}, []], oldVal: ["b", {}, []] }]);
1084
+ });
1085
+
1086
+ it("should not infinitely digest when current value is NaN", () => {
1087
+ $rootScope.obj = [NaN];
1088
+ expect(() => {
1089
+ $rootScope.$digest();
1090
+ }).not.toThrow();
1091
+ });
1092
+
1093
+ it("should watch array-like objects like arrays", () => {
1094
+ logs = [];
1095
+ $rootScope.obj = window.document.getElementsByTagName("src");
1096
+ $rootScope.$digest();
1097
+
1098
+ expect(logs.length).toBeTruthy();
1099
+ });
1100
+ });
1101
+
1102
+ describe("object", () => {
1103
+ it("should return oldCollection === newCollection only on the first listener call", () => {
1104
+ $rootScope.obj = { a: "b" };
1105
+ // first time should be identical
1106
+ $rootScope.$digest();
1107
+ expect(logs).toEqual([
1108
+ { newVal: { a: "b" }, oldVal: { a: "b" }, identical: true },
1109
+ ]);
1110
+ logs = [];
1111
+
1112
+ // second time not identical
1113
+ $rootScope.obj.a = "c";
1114
+ $rootScope.$digest();
1115
+ expect(logs).toEqual([{ newVal: { a: "c" }, oldVal: { a: "b" } }]);
1116
+ });
1117
+
1118
+ it("should trigger when property changes into object", () => {
1119
+ $rootScope.obj = "test";
1120
+ $rootScope.$digest();
1121
+ expect(logs).toEqual([
1122
+ { newVal: "test", oldVal: "test", identical: true },
1123
+ ]);
1124
+ logs = [];
1125
+
1126
+ $rootScope.obj = {};
1127
+ $rootScope.$digest();
1128
+ expect(logs).toEqual([{ newVal: {}, oldVal: "test" }]);
1129
+ });
1130
+
1131
+ it("should not trigger change when object in collection changes", () => {
1132
+ $rootScope.obj = { name: {} };
1133
+ $rootScope.$digest();
1134
+ expect(logs).toEqual([
1135
+ { newVal: { name: {} }, oldVal: { name: {} }, identical: true },
1136
+ ]);
1137
+ logs = [];
1138
+
1139
+ $rootScope.obj.name.bar = "foo";
1140
+ $rootScope.$digest();
1141
+ expect(logs).toEqual([]);
1142
+ });
1143
+
1144
+ it("should watch object properties", () => {
1145
+ $rootScope.obj = {};
1146
+ $rootScope.$digest();
1147
+ expect(logs).toEqual([{ newVal: {}, oldVal: {}, identical: true }]);
1148
+ logs = [];
1149
+ $rootScope.obj.a = "A";
1150
+ $rootScope.$digest();
1151
+ expect(logs).toEqual([{ newVal: { a: "A" }, oldVal: {} }]);
1152
+
1153
+ logs = [];
1154
+ $rootScope.obj.a = "B";
1155
+ $rootScope.$digest();
1156
+ expect(logs).toEqual([{ newVal: { a: "B" }, oldVal: { a: "A" } }]);
1157
+
1158
+ logs = [];
1159
+ $rootScope.obj.b = [];
1160
+ $rootScope.obj.c = {};
1161
+ $rootScope.$digest();
1162
+ expect(logs).toEqual([
1163
+ { newVal: { a: "B", b: [], c: {} }, oldVal: { a: "B" } },
1164
+ ]);
1165
+
1166
+ logs = [];
1167
+ const temp = $rootScope.obj.a;
1168
+ $rootScope.obj.a = $rootScope.obj.b;
1169
+ $rootScope.obj.c = temp;
1170
+ $rootScope.$digest();
1171
+ expect(logs).toEqual([
1172
+ {
1173
+ newVal: { a: [], b: [], c: "B" },
1174
+ oldVal: { a: "B", b: [], c: {} },
1175
+ },
1176
+ ]);
1177
+
1178
+ logs = [];
1179
+ delete $rootScope.obj.a;
1180
+ $rootScope.$digest();
1181
+ expect(logs).toEqual([
1182
+ { newVal: { b: [], c: "B" }, oldVal: { a: [], b: [], c: "B" } },
1183
+ ]);
1184
+ });
1185
+
1186
+ it("should not infinitely digest when current value is NaN", () => {
1187
+ $rootScope.obj = { a: NaN };
1188
+ expect(() => {
1189
+ $rootScope.$digest();
1190
+ }).not.toThrow();
1191
+ });
1192
+
1193
+ it("should handle objects created using `Object.create(null)`", () => {
1194
+ $rootScope.obj = Object.create(null);
1195
+ $rootScope.obj.a = "a";
1196
+ $rootScope.obj.b = "b";
1197
+ $rootScope.$digest();
1198
+ expect(logs[0].newVal).toEqual(
1199
+ extend(Object.create(null), { a: "a", b: "b" }),
1200
+ );
1201
+
1202
+ delete $rootScope.obj.b;
1203
+ $rootScope.$digest();
1204
+ expect(logs[0].newVal).toEqual(
1205
+ extend(Object.create(null), { a: "a" }),
1206
+ );
1207
+ });
1208
+ });
1209
+ });
1210
+
1211
+ describe("literal", () => {
1212
+ describe("array", () => {
1213
+ beforeEach(() => {
1214
+ logs = [];
1215
+ $rootScope.$watchCollection("[obj]", (newVal, oldVal) => {
1216
+ const msg = { newVal, oldVal };
1217
+
1218
+ if (newVal === oldVal) {
1219
+ msg.identical = true;
1220
+ }
1221
+
1222
+ logs.push(msg);
1223
+ });
1224
+ });
1225
+
1226
+ it("should return oldCollection === newCollection only on the first listener call", () => {
1227
+ // first time should be identical
1228
+ $rootScope.obj = "a";
1229
+ $rootScope.$digest();
1230
+ expect(logs).toEqual([
1231
+ { newVal: ["a"], oldVal: ["a"], identical: true },
1232
+ ]);
1233
+ logs = [];
1234
+
1235
+ // second time should be different
1236
+ $rootScope.obj = "b";
1237
+ $rootScope.$digest();
1238
+ expect(logs).toEqual([{ newVal: ["b"], oldVal: ["a"] }]);
1239
+ });
1240
+
1241
+ it("should trigger when property changes into array", () => {
1242
+ $rootScope.obj = "test";
1243
+ $rootScope.$digest();
1244
+ expect(logs).toEqual([
1245
+ { newVal: ["test"], oldVal: ["test"], identical: true },
1246
+ ]);
1247
+
1248
+ logs = [];
1249
+ $rootScope.obj = [];
1250
+ $rootScope.$digest();
1251
+ expect(logs).toEqual([{ newVal: [[]], oldVal: ["test"] }]);
1252
+
1253
+ logs = [];
1254
+ $rootScope.obj = {};
1255
+ $rootScope.$digest();
1256
+ expect(logs).toEqual([{ newVal: [{}], oldVal: [[]] }]);
1257
+
1258
+ logs = [];
1259
+ $rootScope.obj = [];
1260
+ $rootScope.$digest();
1261
+ expect(logs).toEqual([{ newVal: [[]], oldVal: [{}] }]);
1262
+
1263
+ logs = [];
1264
+ $rootScope.obj = undefined;
1265
+ $rootScope.$digest();
1266
+ expect(logs).toEqual([{ newVal: [undefined], oldVal: [[]] }]);
1267
+ });
1268
+
1269
+ it("should not trigger change when object in collection changes", () => {
1270
+ $rootScope.obj = {};
1271
+ $rootScope.$digest();
1272
+ expect(logs).toEqual([
1273
+ { newVal: [{}], oldVal: [{}], identical: true },
1274
+ ]);
1275
+
1276
+ logs = [];
1277
+ $rootScope.obj.name = "foo";
1278
+ $rootScope.$digest();
1279
+ expect(logs).toEqual([]);
1280
+ });
1281
+
1282
+ it("should not infinitely digest when current value is NaN", () => {
1283
+ $rootScope.obj = NaN;
1284
+ expect(() => {
1285
+ $rootScope.$digest();
1286
+ }).not.toThrow();
1287
+ });
1288
+ });
1289
+
1290
+ describe("object", () => {
1291
+ beforeEach(() => {
1292
+ logs = [];
1293
+ $rootScope.$watchCollection("{a: obj}", (newVal, oldVal) => {
1294
+ const msg = { newVal, oldVal };
1295
+
1296
+ if (newVal === oldVal) {
1297
+ msg.identical = true;
1298
+ }
1299
+
1300
+ logs.push(msg);
1301
+ });
1302
+ });
1303
+
1304
+ it("should return oldCollection === newCollection only on the first listener call", () => {
1305
+ $rootScope.obj = "b";
1306
+ // first time should be identical
1307
+ $rootScope.$digest();
1308
+ expect(logs).toEqual([
1309
+ { newVal: { a: "b" }, oldVal: { a: "b" }, identical: true },
1310
+ ]);
1311
+
1312
+ // second time not identical
1313
+ logs = [];
1314
+ $rootScope.obj = "c";
1315
+ $rootScope.$digest();
1316
+ expect(logs).toEqual([{ newVal: { a: "c" }, oldVal: { a: "b" } }]);
1317
+ });
1318
+
1319
+ it("should trigger when property changes into object", () => {
1320
+ $rootScope.obj = "test";
1321
+ $rootScope.$digest();
1322
+ expect(logs).toEqual([
1323
+ { newVal: { a: "test" }, oldVal: { a: "test" }, identical: true },
1324
+ ]);
1325
+
1326
+ logs = [];
1327
+ $rootScope.obj = {};
1328
+ $rootScope.$digest();
1329
+ expect(logs).toEqual([
1330
+ { newVal: { a: {} }, oldVal: { a: "test" } },
1331
+ ]);
1332
+ });
1333
+
1334
+ it("should not trigger change when object in collection changes", () => {
1335
+ $rootScope.obj = { name: "foo" };
1336
+ $rootScope.$digest();
1337
+ expect(logs).toEqual([
1338
+ {
1339
+ newVal: { a: { name: "foo" } },
1340
+ oldVal: { a: { name: "foo" } },
1341
+ identical: true,
1342
+ },
1343
+ ]);
1344
+
1345
+ logs = [];
1346
+ $rootScope.obj.name = "bar";
1347
+ $rootScope.$digest();
1348
+ expect(logs).toEqual([]);
1349
+ });
1350
+
1351
+ it("should watch object properties", () => {
1352
+ $rootScope.obj = {};
1353
+ $rootScope.$digest();
1354
+ expect(logs).toEqual([
1355
+ { newVal: { a: {} }, oldVal: { a: {} }, identical: true },
1356
+ ]);
1357
+
1358
+ logs = [];
1359
+ $rootScope.obj = "A";
1360
+ $rootScope.$digest();
1361
+ expect(logs).toEqual([{ newVal: { a: "A" }, oldVal: { a: {} } }]);
1362
+
1363
+ logs = [];
1364
+ $rootScope.obj = "B";
1365
+ $rootScope.$digest();
1366
+ expect(logs).toEqual([{ newVal: { a: "B" }, oldVal: { a: "A" } }]);
1367
+
1368
+ logs = [];
1369
+ $rootScope.obj = [];
1370
+ $rootScope.$digest();
1371
+ expect(logs).toEqual([{ newVal: { a: [] }, oldVal: { a: "B" } }]);
1372
+
1373
+ logs = [];
1374
+ delete $rootScope.obj;
1375
+ $rootScope.$digest();
1376
+ expect(logs).toEqual([
1377
+ { newVal: { a: undefined }, oldVal: { a: [] } },
1378
+ ]);
1379
+ });
1380
+
1381
+ it("should not infinitely digest when current value is NaN", () => {
1382
+ $rootScope.obj = NaN;
1383
+ expect(() => {
1384
+ $rootScope.$digest();
1385
+ }).not.toThrow();
1386
+ });
1387
+ });
1388
+
1389
+ describe("object computed property", () => {
1390
+ beforeEach(() => {
1391
+ logs = [];
1392
+ $rootScope.$watchCollection("{[key]: obj}", (newVal, oldVal) => {
1393
+ const msg = { newVal, oldVal };
1394
+
1395
+ if (newVal === oldVal) {
1396
+ msg.identical = true;
1397
+ }
1398
+
1399
+ logs.push(msg);
1400
+ });
1401
+ });
1402
+
1403
+ it('should default to "undefined" key', () => {
1404
+ $rootScope.obj = "test";
1405
+ $rootScope.$digest();
1406
+ expect(logs).toEqual([
1407
+ {
1408
+ newVal: { undefined: "test" },
1409
+ oldVal: { undefined: "test" },
1410
+ identical: true,
1411
+ },
1412
+ ]);
1413
+ });
1414
+
1415
+ it("should trigger when key changes", () => {
1416
+ $rootScope.key = "a";
1417
+ $rootScope.obj = "test";
1418
+ $rootScope.$digest();
1419
+ expect(logs).toEqual([
1420
+ { newVal: { a: "test" }, oldVal: { a: "test" }, identical: true },
1421
+ ]);
1422
+
1423
+ logs = [];
1424
+ $rootScope.key = "b";
1425
+ $rootScope.$digest();
1426
+ expect(logs).toEqual([
1427
+ { newVal: { b: "test" }, oldVal: { a: "test" } },
1428
+ ]);
1429
+
1430
+ logs = [];
1431
+ $rootScope.key = true;
1432
+ $rootScope.$digest();
1433
+ expect(logs).toEqual([
1434
+ { newVal: { true: "test" }, oldVal: { b: "test" } },
1435
+ ]);
1436
+ });
1437
+
1438
+ it("should not trigger when key changes but stringified key does not", () => {
1439
+ $rootScope.key = 1;
1440
+ $rootScope.obj = "test";
1441
+ $rootScope.$digest();
1442
+ expect(logs).toEqual([
1443
+ { newVal: { 1: "test" }, oldVal: { 1: "test" }, identical: true },
1444
+ ]);
1445
+
1446
+ logs = [];
1447
+ $rootScope.key = "1";
1448
+ $rootScope.$digest();
1449
+ expect(logs).toEqual([]);
1450
+
1451
+ $rootScope.key = true;
1452
+ $rootScope.$digest();
1453
+ expect(logs).toEqual([
1454
+ { newVal: { true: "test" }, oldVal: { 1: "test" } },
1455
+ ]);
1456
+
1457
+ logs = [];
1458
+ $rootScope.key = "true";
1459
+ $rootScope.$digest();
1460
+ expect(logs).toEqual([]);
1461
+
1462
+ logs = [];
1463
+ $rootScope.key = {};
1464
+ $rootScope.$digest();
1465
+ expect(logs).toEqual([
1466
+ {
1467
+ newVal: { "[object Object]": "test" },
1468
+ oldVal: { true: "test" },
1469
+ },
1470
+ ]);
1471
+
1472
+ logs = [];
1473
+ $rootScope.key = {};
1474
+ $rootScope.$digest();
1475
+ expect(logs).toEqual([]);
1476
+ });
1477
+
1478
+ it("should not trigger change when object in collection changes", () => {
1479
+ $rootScope.key = "a";
1480
+ $rootScope.obj = { name: "foo" };
1481
+ $rootScope.$digest();
1482
+ expect(logs).toEqual([
1483
+ {
1484
+ newVal: { a: { name: "foo" } },
1485
+ oldVal: { a: { name: "foo" } },
1486
+ identical: true,
1487
+ },
1488
+ ]);
1489
+ logs = [];
1490
+
1491
+ $rootScope.obj.name = "bar";
1492
+ $rootScope.$digest();
1493
+ expect(logs).toEqual([]);
1494
+ });
1495
+
1496
+ it("should not infinitely digest when key value is NaN", () => {
1497
+ $rootScope.key = NaN;
1498
+ $rootScope.obj = NaN;
1499
+ expect(() => {
1500
+ $rootScope.$digest();
1501
+ }).not.toThrow();
1502
+ });
1503
+ });
1504
+ });
1505
+ });
1506
+
1507
+ describe("$suspend/$resume/$isSuspended", () => {
1508
+ it("should suspend watchers on scope", () => {
1509
+ const watchSpy = jasmine.createSpy("watchSpy");
1510
+ $rootScope.$watch(watchSpy);
1511
+ $rootScope.$suspend();
1512
+ $rootScope.$digest();
1513
+ expect(watchSpy).not.toHaveBeenCalled();
1514
+ });
1515
+
1516
+ it("should resume watchers on scope", () => {
1517
+ const watchSpy = jasmine.createSpy("watchSpy");
1518
+ $rootScope.$watch(watchSpy);
1519
+ $rootScope.$suspend();
1520
+ $rootScope.$resume();
1521
+ $rootScope.$digest();
1522
+ expect(watchSpy).toHaveBeenCalled();
1523
+ });
1524
+
1525
+ it("should suspend watchers on child scope", () => {
1526
+ const watchSpy = jasmine.createSpy("watchSpy");
1527
+ const scope = $rootScope.$new(true);
1528
+ scope.$watch(watchSpy);
1529
+ $rootScope.$suspend();
1530
+ $rootScope.$digest();
1531
+ expect(watchSpy).not.toHaveBeenCalled();
1532
+ });
1533
+
1534
+ it("should resume watchers on child scope", () => {
1535
+ const watchSpy = jasmine.createSpy("watchSpy");
1536
+ const scope = $rootScope.$new(true);
1537
+ scope.$watch(watchSpy);
1538
+ $rootScope.$suspend();
1539
+ $rootScope.$resume();
1540
+ $rootScope.$digest();
1541
+ expect(watchSpy).toHaveBeenCalled();
1542
+ });
1543
+
1544
+ it("should resume digesting immediately if `$resume` is called from an ancestor scope watch handler", () => {
1545
+ const watchSpy = jasmine.createSpy("watchSpy");
1546
+ const scope = $rootScope.$new();
1547
+
1548
+ // Setup a handler that will toggle the scope suspension
1549
+ $rootScope.$watch("a", (a) => {
1550
+ if (a) scope.$resume();
1551
+ else scope.$suspend();
1552
+ });
1553
+
1554
+ // Spy on the scope watches being called
1555
+ scope.$watch(watchSpy);
1556
+
1557
+ // Trigger a digest that should suspend the scope from within the watch handler
1558
+ $rootScope.$apply("a = false");
1559
+ // The scope is suspended before it gets to do a digest
1560
+ expect(watchSpy).not.toHaveBeenCalled();
1561
+
1562
+ // Trigger a digest that should resume the scope from within the watch handler
1563
+ $rootScope.$apply("a = true");
1564
+ // The watch handler that resumes the scope is in the parent, so the resumed scope will digest immediately
1565
+ expect(watchSpy).toHaveBeenCalled();
1566
+ });
1567
+
1568
+ it("should resume digesting immediately if `$resume` is called from a non-ancestor scope watch handler", () => {
1569
+ const watchSpy = jasmine.createSpy("watchSpy");
1570
+ const scope = $rootScope.$new();
1571
+ const sibling = $rootScope.$new();
1572
+
1573
+ // Setup a handler that will toggle the scope suspension
1574
+ sibling.$watch("a", (a) => {
1575
+ if (a) scope.$resume();
1576
+ else scope.$suspend();
1577
+ });
1578
+
1579
+ // Spy on the scope watches being called
1580
+ scope.$watch(watchSpy);
1581
+
1582
+ // Trigger a digest that should suspend the scope from within the watch handler
1583
+ $rootScope.$apply("a = false");
1584
+ // The scope is suspended by the sibling handler after the scope has already digested
1585
+ expect(watchSpy).toHaveBeenCalled();
1586
+ watchSpy.calls.reset();
1587
+
1588
+ // Trigger a digest that should resume the scope from within the watch handler
1589
+ $rootScope.$apply("a = true");
1590
+ // The watch handler that resumes the scope marks the digest as dirty, so it will run an extra digest
1591
+ expect(watchSpy).toHaveBeenCalled();
1592
+ });
1593
+
1594
+ it("should not suspend watchers on parent or sibling scopes", () => {
1595
+ const watchSpyParent = jasmine.createSpy("watchSpyParent");
1596
+ const watchSpyChild = jasmine.createSpy("watchSpyChild");
1597
+ const watchSpySibling = jasmine.createSpy("watchSpySibling");
1598
+
1599
+ const parent = $rootScope.$new();
1600
+ parent.$watch(watchSpyParent);
1601
+ const child = parent.$new();
1602
+ child.$watch(watchSpyChild);
1603
+ const sibling = parent.$new();
1604
+ sibling.$watch(watchSpySibling);
1605
+
1606
+ child.$suspend();
1607
+ $rootScope.$digest();
1608
+ expect(watchSpyParent).toHaveBeenCalled();
1609
+ expect(watchSpyChild).not.toHaveBeenCalled();
1610
+ expect(watchSpySibling).toHaveBeenCalled();
1611
+ });
1612
+
1613
+ it("should return true from `$isSuspended()` when a scope is suspended", () => {
1614
+ $rootScope.$suspend();
1615
+ expect($rootScope.$isSuspended()).toBe(true);
1616
+ $rootScope.$resume();
1617
+ expect($rootScope.$isSuspended()).toBe(false);
1618
+ });
1619
+
1620
+ it("should return false from `$isSuspended()` for a non-suspended scope that has a suspended ancestor", () => {
1621
+ const childScope = $rootScope.$new();
1622
+ $rootScope.$suspend();
1623
+ expect(childScope.$isSuspended()).toBe(false);
1624
+ childScope.$suspend();
1625
+ expect(childScope.$isSuspended()).toBe(true);
1626
+ childScope.$resume();
1627
+ expect(childScope.$isSuspended()).toBe(false);
1628
+ $rootScope.$resume();
1629
+ expect(childScope.$isSuspended()).toBe(false);
1630
+ });
1631
+ });
1632
+
1633
+ logs = [];
1634
+ function setupWatches(scope, log) {
1635
+ scope.$watch(() => {
1636
+ logs.push("w1");
1637
+ return scope.w1;
1638
+ }, log("w1action"));
1639
+ scope.$watch(() => {
1640
+ logs.push("w2");
1641
+ return scope.w2;
1642
+ }, log("w2action"));
1643
+ scope.$watch(() => {
1644
+ logs.push("w3");
1645
+ return scope.w3;
1646
+ }, log("w3action"));
1647
+ console.error(logs.length);
1648
+ scope.$digest();
1649
+ logs = [];
1650
+ }
1651
+
1652
+ describe("optimizations", () => {
1653
+ beforeEach(() => (logs = []));
1654
+ it("should check watches only once during an empty digest", () => {
1655
+ setupWatches($rootScope, console.log);
1656
+ $rootScope.$digest();
1657
+ expect(logs).toEqual(["w1", "w2", "w3"]);
1658
+ });
1659
+
1660
+ it("should quit digest early after we check the last watch that was previously dirty", () => {
1661
+ setupWatches($rootScope, console.log);
1662
+ $rootScope.w1 = "x";
1663
+ $rootScope.$digest();
1664
+ expect(logs).toEqual(["w1", "w2", "w3", "w1"]);
1665
+ });
1666
+
1667
+ it("should not quit digest early if a new watch was added from an existing watch action", () => {
1668
+ setupWatches($rootScope, console.log);
1669
+ $rootScope.$watch(
1670
+ () => {
1671
+ logs.push("w4");
1672
+ return "w4";
1673
+ },
1674
+ () => {
1675
+ logs.push("w4action");
1676
+ $rootScope.$watch(
1677
+ () => {
1678
+ logs.push("w5");
1679
+ return "w5";
1680
+ },
1681
+ () => logs.push("w5action"),
1682
+ );
1683
+ },
1684
+ );
1685
+ $rootScope.$digest();
1686
+ expect(logs).toEqual([
1687
+ "w1",
1688
+ "w2",
1689
+ "w3",
1690
+ "w4",
1691
+ "w4action",
1692
+ "w5",
1693
+ "w5action",
1694
+ "w1",
1695
+ "w2",
1696
+ "w3",
1697
+ "w4",
1698
+ "w5",
1699
+ ]);
1700
+ });
1701
+
1702
+ it("should not quit digest early if an evalAsync task was scheduled from a watch action", () => {
1703
+ setupWatches($rootScope, console.log);
1704
+ $rootScope.$watch(
1705
+ () => {
1706
+ logs.push("w4");
1707
+ return "w4";
1708
+ },
1709
+ () => {
1710
+ logs.push("w4action");
1711
+ $rootScope.$evalAsync(() => {
1712
+ logs.push("evalAsync");
1713
+ });
1714
+ },
1715
+ );
1716
+ $rootScope.$digest();
1717
+ expect(logs).toEqual([
1718
+ "w1",
1719
+ "w2",
1720
+ "w3",
1721
+ "w4",
1722
+ "w4action",
1723
+ "evalAsync",
1724
+ "w1",
1725
+ "w2",
1726
+ "w3",
1727
+ "w4",
1728
+ ]);
1729
+ });
1730
+
1731
+ it("should quit digest early but not too early when constious watches fire", () => {
1732
+ setupWatches($rootScope, console.log);
1733
+ $rootScope.$watch(
1734
+ () => {
1735
+ logs.push("w4");
1736
+ return $rootScope.w4;
1737
+ },
1738
+ (newVal) => {
1739
+ logs.push("w4action");
1740
+ $rootScope.w2 = newVal;
1741
+ },
1742
+ );
1743
+
1744
+ $rootScope.$digest();
1745
+ logs = [];
1746
+
1747
+ $rootScope.w1 = "x";
1748
+ $rootScope.w4 = "x";
1749
+ $rootScope.$digest();
1750
+ expect(logs).toEqual([
1751
+ "w1",
1752
+ "w2",
1753
+ "w3",
1754
+ "w4",
1755
+ "w4action",
1756
+ "w1",
1757
+ "w2",
1758
+ "w3",
1759
+ "w4",
1760
+ "w1",
1761
+ "w2",
1762
+ ]);
1763
+ });
1764
+ });
1765
+ });
1766
+
1767
+ describe("$watchGroup", () => {
1768
+ let scope;
1769
+
1770
+ beforeEach(() => {
1771
+ scope = $rootScope.$new();
1772
+ });
1773
+
1774
+ it("should pass same group instance on first call (no expressions)", () => {
1775
+ let newValues;
1776
+ let oldValues;
1777
+ scope.$watchGroup([], (n, o) => {
1778
+ newValues = n;
1779
+ oldValues = o;
1780
+ });
1781
+
1782
+ scope.$apply();
1783
+ expect(newValues).toBe(oldValues);
1784
+ });
1785
+
1786
+ it("should pass same group instance on first call (single expression)", () => {
1787
+ let newValues;
1788
+ let oldValues;
1789
+ scope.$watchGroup(["a"], (n, o) => {
1790
+ newValues = n;
1791
+ oldValues = o;
1792
+ });
1793
+
1794
+ scope.$apply();
1795
+ expect(newValues).toBe(oldValues);
1796
+
1797
+ scope.$apply("a = 1");
1798
+ expect(newValues).not.toBe(oldValues);
1799
+ });
1800
+
1801
+ it("should pass same group instance on first call (multiple expressions)", () => {
1802
+ let newValues;
1803
+ let oldValues;
1804
+ scope.$watchGroup(["a", "b"], (n, o) => {
1805
+ newValues = n;
1806
+ oldValues = o;
1807
+ });
1808
+
1809
+ scope.$apply();
1810
+ expect(newValues).toBe(oldValues);
1811
+
1812
+ scope.$apply("a = 1");
1813
+ expect(newValues).not.toBe(oldValues);
1814
+ });
1815
+
1816
+ it("should detect a change to any one expression in the group", () => {
1817
+ logs = [];
1818
+ scope.$watchGroup(["a", "b"], (values, oldValues, s) => {
1819
+ expect(s).toBe(scope);
1820
+ logs.push(`${oldValues} >>> ${values}`);
1821
+ });
1822
+
1823
+ scope.a = "foo";
1824
+ scope.b = "bar";
1825
+ scope.$digest();
1826
+ expect(logs[0]).toEqual("foo,bar >>> foo,bar");
1827
+
1828
+ logs = [];
1829
+ scope.$digest();
1830
+ expect(logs).toEqual([]);
1831
+
1832
+ scope.a = "a";
1833
+ scope.$digest();
1834
+ expect(logs[0]).toEqual("foo,bar >>> a,bar");
1835
+
1836
+ logs = [];
1837
+ scope.a = "A";
1838
+ scope.b = "B";
1839
+ scope.$digest();
1840
+ expect(logs[0]).toEqual("a,bar >>> A,B");
1841
+ });
1842
+
1843
+ it("should work for a group with just a single expression", () => {
1844
+ logs = [];
1845
+ scope.$watchGroup(["a"], (values, oldValues, s) => {
1846
+ expect(s).toBe(scope);
1847
+ logs.push(`${oldValues} >>> ${values}`);
1848
+ });
1849
+
1850
+ scope.a = "foo";
1851
+ scope.$digest();
1852
+ expect(logs[0]).toEqual("foo >>> foo");
1853
+
1854
+ logs = [];
1855
+ scope.$digest();
1856
+ expect(logs).toEqual([]);
1857
+
1858
+ scope.a = "bar";
1859
+ scope.$digest();
1860
+ expect(logs[0]).toEqual("foo >>> bar");
1861
+ });
1862
+
1863
+ it("should call the listener once when the array of watchExpressions is empty", () => {
1864
+ logs = [];
1865
+ scope.$watchGroup([], (values, oldValues) => {
1866
+ logs.push(`${oldValues} >>> ${values}`);
1867
+ });
1868
+
1869
+ expect(logs).toEqual([]);
1870
+ scope.$digest();
1871
+ expect(logs[0]).toEqual(" >>> ");
1872
+
1873
+ logs = [];
1874
+ scope.$digest();
1875
+ expect(logs).toEqual([]);
1876
+ });
1877
+
1878
+ it("should not call watch action fn when watchGroup was deregistered", () => {
1879
+ logs = [];
1880
+ const deregisterMany = scope.$watchGroup(
1881
+ ["a", "b"],
1882
+ (values, oldValues) => {
1883
+ logs.push(`${oldValues} >>> ${values}`);
1884
+ },
1885
+ );
1886
+ const deregisterOne = scope.$watchGroup(["a"], (values, oldValues) => {
1887
+ logs.push(`${oldValues} >>> ${values}`);
1888
+ });
1889
+ const deregisterNone = scope.$watchGroup([], (values, oldValues) => {
1890
+ logs.push(`${oldValues} >>> ${values}`);
1891
+ });
1892
+
1893
+ deregisterMany();
1894
+ deregisterOne();
1895
+ deregisterNone();
1896
+ scope.a = "xxx";
1897
+ scope.b = "yyy";
1898
+ scope.$digest();
1899
+ expect(logs).toEqual([]);
1900
+ });
1901
+
1902
+ it("should have each individual old value equal to new values of previous watcher invocation", () => {
1903
+ let newValues;
1904
+ let oldValues;
1905
+ scope.$watchGroup(["a", "b"], (n, o) => {
1906
+ newValues = n.slice();
1907
+ oldValues = o.slice();
1908
+ });
1909
+
1910
+ scope.$apply(); // skip the initial invocation
1911
+
1912
+ scope.$apply("a = 1");
1913
+ expect(newValues).toEqual([1, undefined]);
1914
+ expect(oldValues).toEqual([undefined, undefined]);
1915
+
1916
+ scope.$apply("a = 2");
1917
+ expect(newValues).toEqual([2, undefined]);
1918
+ expect(oldValues).toEqual([1, undefined]);
1919
+
1920
+ scope.$apply("b = 3");
1921
+ expect(newValues).toEqual([2, 3]);
1922
+ expect(oldValues).toEqual([2, undefined]);
1923
+
1924
+ scope.$apply("a = b = 4");
1925
+ expect(newValues).toEqual([4, 4]);
1926
+ expect(oldValues).toEqual([2, 3]);
1927
+
1928
+ scope.$apply("a = 5");
1929
+ expect(newValues).toEqual([5, 4]);
1930
+ expect(oldValues).toEqual([4, 4]);
1931
+
1932
+ scope.$apply("b = 6");
1933
+ expect(newValues).toEqual([5, 6]);
1934
+ expect(oldValues).toEqual([5, 4]);
1935
+ });
1936
+
1937
+ it("should have each individual old value equal to new values of previous watcher invocation, with modifications from other watchers", () => {
1938
+ scope.$watch("a", () => {
1939
+ scope.b++;
1940
+ });
1941
+ scope.$watch("b", () => {
1942
+ scope.c++;
1943
+ });
1944
+
1945
+ let newValues;
1946
+ let oldValues;
1947
+ scope.$watchGroup(["a", "b", "c"], (n, o) => {
1948
+ newValues = n.slice();
1949
+ oldValues = o.slice();
1950
+ });
1951
+
1952
+ scope.$apply(); // skip the initial invocation
1953
+
1954
+ scope.$apply("a = b = c = 1");
1955
+ expect(newValues).toEqual([1, 2, 2]);
1956
+ expect(oldValues).toEqual([undefined, NaN, NaN]);
1957
+
1958
+ scope.$apply("a = 3");
1959
+ expect(newValues).toEqual([3, 3, 3]);
1960
+ expect(oldValues).toEqual([1, 2, 2]);
1961
+
1962
+ scope.$apply("b = 5");
1963
+ expect(newValues).toEqual([3, 5, 4]);
1964
+ expect(oldValues).toEqual([3, 3, 3]);
1965
+
1966
+ scope.$apply("c = 7");
1967
+ expect(newValues).toEqual([3, 5, 7]);
1968
+ expect(oldValues).toEqual([3, 5, 4]);
1969
+ });
1970
+
1971
+ it("should remove all watchers once one-time/constant bindings are stable", () => {
1972
+ // empty
1973
+ scope.$watchGroup([], () => {});
1974
+ // single one-time
1975
+ scope.$watchGroup(["::a"], () => {});
1976
+ // multi one-time
1977
+ scope.$watchGroup(["::a", "::b"], () => {});
1978
+ // single constant
1979
+ scope.$watchGroup(["1"], () => {});
1980
+ // multi constant
1981
+ scope.$watchGroup(["1", "2"], () => {});
1982
+ // multi one-time/constant
1983
+ scope.$watchGroup(["::a", "1"], () => {});
1984
+
1985
+ expect(scope.$$watchersCount).not.toBe(0);
1986
+ scope.$apply("a = b = 1");
1987
+ expect(scope.$$watchersCount).toBe(0);
1988
+ });
1989
+
1990
+ it("should maintain correct new/old values with one time bindings", () => {
1991
+ let newValues;
1992
+ let oldValues;
1993
+ scope.$watchGroup(["a", "::b", "b", "4"], (n, o) => {
1994
+ newValues = n.slice();
1995
+ oldValues = o.slice();
1996
+ });
1997
+
1998
+ scope.$apply();
1999
+ expect(newValues).toEqual(oldValues);
2000
+ expect(oldValues).toEqual([undefined, undefined, undefined, 4]);
2001
+
2002
+ scope.$apply("a = 1");
2003
+ expect(newValues).toEqual([1, undefined, undefined, 4]);
2004
+ expect(oldValues).toEqual([undefined, undefined, undefined, 4]);
2005
+
2006
+ scope.$apply("b = 2");
2007
+ expect(newValues).toEqual([1, 2, 2, 4]);
2008
+ expect(oldValues).toEqual([1, undefined, undefined, 4]);
2009
+
2010
+ scope.$apply("b = 3");
2011
+ expect(newValues).toEqual([1, 2, 3, 4]);
2012
+ expect(oldValues).toEqual([1, 2, 2, 4]);
2013
+
2014
+ scope.$apply("b = 4");
2015
+ expect(newValues).toEqual([1, 2, 4, 4]);
2016
+ expect(oldValues).toEqual([1, 2, 3, 4]);
2017
+ });
2018
+ });
2019
+
2020
+ describe("$watchGroup with logging $exceptionHandler", () => {
2021
+ it("should maintain correct new/old values even when listener throws", () => {
2022
+ let newValues;
2023
+ let oldValues;
2024
+ $rootScope.$watchGroup(["a", "::b", "b", "4"], (n, o) => {
2025
+ newValues = n.slice();
2026
+ oldValues = o.slice();
2027
+ throw "test";
2028
+ });
2029
+
2030
+ $rootScope.$apply();
2031
+ expect(newValues).toEqual(oldValues);
2032
+ expect(oldValues).toEqual([undefined, undefined, undefined, 4]);
2033
+ expect(logs.length).toBe(1);
2034
+
2035
+ $rootScope.$apply("a = 1");
2036
+ expect(newValues).toEqual([1, undefined, undefined, 4]);
2037
+ expect(oldValues).toEqual([undefined, undefined, undefined, 4]);
2038
+ expect(logs.length).toBe(2);
2039
+
2040
+ $rootScope.$apply("b = 2");
2041
+ expect(newValues).toEqual([1, 2, 2, 4]);
2042
+ expect(oldValues).toEqual([1, undefined, undefined, 4]);
2043
+ expect(logs.length).toBe(3);
2044
+
2045
+ $rootScope.$apply("b = 3");
2046
+ expect(newValues).toEqual([1, 2, 3, 4]);
2047
+ expect(oldValues).toEqual([1, 2, 2, 4]);
2048
+ expect(logs.length).toBe(4);
2049
+
2050
+ $rootScope.$apply("b = 4");
2051
+ expect(newValues).toEqual([1, 2, 4, 4]);
2052
+ expect(oldValues).toEqual([1, 2, 3, 4]);
2053
+ expect(logs.length).toBe(5);
2054
+ });
2055
+ });
2056
+
2057
+ describe("$destroy", () => {
2058
+ let first = null;
2059
+ let middle = null;
2060
+ let last = null;
2061
+ let log = null;
2062
+
2063
+ beforeEach(() => {
2064
+ log = "";
2065
+
2066
+ first = $rootScope.$new();
2067
+ middle = $rootScope.$new();
2068
+ last = $rootScope.$new();
2069
+
2070
+ first.$watch(() => {
2071
+ log += "1";
2072
+ });
2073
+ middle.$watch(() => {
2074
+ log += "2";
2075
+ });
2076
+ last.$watch(() => {
2077
+ log += "3";
2078
+ });
2079
+
2080
+ $rootScope.$digest();
2081
+ log = "";
2082
+ });
2083
+
2084
+ it("should broadcast $destroy on rootScope", () => {
2085
+ const spy = jasmine.createSpy("$destroy handler");
2086
+ $rootScope.$on("$destroy", spy);
2087
+ $rootScope.$destroy();
2088
+ expect(spy).toHaveBeenCalled();
2089
+ expect($rootScope.$$destroyed).toBe(true);
2090
+ });
2091
+
2092
+ it("should remove all listeners after $destroy of rootScope", () => {
2093
+ const spy = jasmine.createSpy("$destroy handler");
2094
+ $rootScope.$on("dummy", spy);
2095
+ $rootScope.$destroy();
2096
+ $rootScope.$broadcast("dummy");
2097
+ expect(spy).not.toHaveBeenCalled();
2098
+ });
2099
+
2100
+ it("should remove all watchers after $destroy of rootScope", () => {
2101
+ const spy = jasmine.createSpy("$watch spy");
2102
+ $rootScope.$watch(spy);
2103
+ $rootScope.$destroy();
2104
+ $rootScope.$digest();
2105
+ expect(spy).not.toHaveBeenCalled();
2106
+ });
2107
+
2108
+ it("should call $browser.$$applicationDestroyed when destroying rootScope", () => {
2109
+ spyOn($browser, "$$applicationDestroyed");
2110
+ $rootScope.$destroy();
2111
+ expect($browser.$$applicationDestroyed).toHaveBeenCalled();
2112
+ });
2113
+
2114
+ it("should remove first", () => {
2115
+ first.$destroy();
2116
+ $rootScope.$digest();
2117
+ expect(log).toEqual("23");
2118
+ });
2119
+
2120
+ it("should remove middle", () => {
2121
+ middle.$destroy();
2122
+ $rootScope.$digest();
2123
+ expect(log).toEqual("13");
2124
+ });
2125
+
2126
+ it("should remove last", () => {
2127
+ last.$destroy();
2128
+ $rootScope.$digest();
2129
+ expect(log).toEqual("12");
2130
+ });
2131
+
2132
+ it("should broadcast the $destroy event", () => {
2133
+ logs = [];
2134
+ first.$on("$destroy", () => logs.push("first"));
2135
+ first.$new().$on("$destroy", () => logs.push("first-child"));
2136
+
2137
+ first.$destroy();
2138
+ expect(logs).toEqual(["first", "first-child"]);
2139
+ });
2140
+
2141
+ it("should $destroy a scope only once and ignore any further destroy calls", () => {
2142
+ $rootScope.$digest();
2143
+ expect(log).toBe("123");
2144
+
2145
+ first.$destroy();
2146
+
2147
+ // once a scope is destroyed apply should not do anything any more
2148
+ first.$apply();
2149
+ expect(log).toBe("123");
2150
+
2151
+ first.$destroy();
2152
+ first.$destroy();
2153
+ first.$apply();
2154
+ expect(log).toBe("123");
2155
+ });
2156
+
2157
+ it("should broadcast the $destroy only once", () => {
2158
+ logs = [];
2159
+ const isolateScope = first.$new(true);
2160
+ isolateScope.$on("$destroy", () => logs.push("event"));
2161
+ first.$destroy();
2162
+ isolateScope.$destroy();
2163
+ expect(logs).toEqual(["event"]);
2164
+ });
2165
+
2166
+ it("should decrement ancestor $$listenerCount entries", () => {
2167
+ const EVENT = "fooEvent";
2168
+ const spy = jasmine.createSpy("listener");
2169
+ const firstSecond = first.$new();
2170
+
2171
+ firstSecond.$on(EVENT, spy);
2172
+ firstSecond.$on(EVENT, spy);
2173
+ middle.$on(EVENT, spy);
2174
+
2175
+ expect($rootScope.$$listenerCount[EVENT]).toBe(3);
2176
+ expect(first.$$listenerCount[EVENT]).toBe(2);
2177
+
2178
+ firstSecond.$destroy();
2179
+
2180
+ expect($rootScope.$$listenerCount[EVENT]).toBe(1);
2181
+ expect(first.$$listenerCount[EVENT]).toBeUndefined();
2182
+
2183
+ $rootScope.$broadcast(EVENT);
2184
+ expect(spy).toHaveBeenCalledTimes(1);
2185
+ });
2186
+
2187
+ it("should do nothing when a child event listener is registered after parent's destruction", () => {
2188
+ const parent = $rootScope.$new();
2189
+ const child = parent.$new();
2190
+ parent.$destroy();
2191
+ let called = false;
2192
+ child.$on("someEvent", () => {
2193
+ called = true;
2194
+ });
2195
+
2196
+ // Trigger the event
2197
+ child.$broadcast("someEvent");
2198
+
2199
+ // Check if the listener was called
2200
+ expect(called).toBe(false);
2201
+ });
2202
+
2203
+ it("should do nothing when a child watch is registered after parent's destruction", () => {
2204
+ const parent = $rootScope.$new();
2205
+ const child = parent.$new();
2206
+ parent.$destroy();
2207
+ let called = false;
2208
+ child.$watch("someEvent", () => {
2209
+ called = true;
2210
+ });
2211
+ expect(called).toBe(false);
2212
+ });
2213
+
2214
+ it("should do nothing when $apply()ing after parent's destruction", () => {
2215
+ const parent = $rootScope.$new();
2216
+ const child = parent.$new();
2217
+
2218
+ parent.$destroy();
2219
+
2220
+ let called = false;
2221
+ function applyFunc() {
2222
+ called = true;
2223
+ }
2224
+ child.$apply(applyFunc);
2225
+
2226
+ expect(called).toBe(false);
2227
+ });
2228
+
2229
+ it("should do nothing when $evalAsync()ing after parent's destruction", () => {
2230
+ const parent = $rootScope.$new();
2231
+ const child = parent.$new();
2232
+
2233
+ parent.$destroy();
2234
+
2235
+ let called = false;
2236
+ function applyFunc() {
2237
+ called = true;
2238
+ }
2239
+ child.$evalAsync(applyFunc);
2240
+ expect(called).toBe(false);
2241
+ });
2242
+
2243
+ it("should preserve all (own and inherited) model properties on a destroyed scope", () => {
2244
+ // This test simulates an async task (xhr response) interacting with the scope after the scope
2245
+ // was destroyed. Since we can't abort the request, we should ensure that the task doesn't
2246
+ // throw NPEs because the scope was cleaned up during destruction.
2247
+
2248
+ const parent = $rootScope.$new();
2249
+ const child = parent.$new();
2250
+
2251
+ parent.parentModel = "parent";
2252
+ child.childModel = "child";
2253
+
2254
+ child.$destroy();
2255
+
2256
+ expect(child.parentModel).toBe("parent");
2257
+ expect(child.childModel).toBe("child");
2258
+ });
2259
+ });
2260
+
2261
+ describe("$eval", () => {
2262
+ it("should eval an expression", () => {
2263
+ expect($rootScope.$eval("a=1")).toEqual(1);
2264
+ expect($rootScope.a).toEqual(1);
2265
+
2266
+ $rootScope.$eval((self) => {
2267
+ self.b = 2;
2268
+ });
2269
+ expect($rootScope.b).toEqual(2);
2270
+ });
2271
+
2272
+ it("should allow passing locals to the expression", () => {
2273
+ expect($rootScope.$eval("a+1", { a: 2 })).toBe(3);
2274
+
2275
+ $rootScope.$eval(
2276
+ (scope, locals) => {
2277
+ scope.c = locals.b + 4;
2278
+ },
2279
+ { b: 3 },
2280
+ );
2281
+ expect($rootScope.c).toBe(7);
2282
+ });
2283
+ });
2284
+
2285
+ describe("$evalAsync", () => {
2286
+ it("should run callback before $watch", () => {
2287
+ let log = "";
2288
+ const child = $rootScope.$new();
2289
+ $rootScope.$evalAsync((scope) => {
2290
+ log += "parent.async;";
2291
+ });
2292
+ $rootScope.$watch("value", () => {
2293
+ log += "parent.$digest;";
2294
+ });
2295
+ child.$evalAsync((scope) => {
2296
+ log += "child.async;";
2297
+ });
2298
+ child.$watch("value", () => {
2299
+ log += "child.$digest;";
2300
+ });
2301
+ $rootScope.$digest();
2302
+ expect(log).toEqual(
2303
+ "parent.async;child.async;parent.$digest;child.$digest;",
2304
+ );
2305
+ });
2306
+
2307
+ it("should not run another digest for an $$postDigest call", () => {
2308
+ let internalWatchCount = 0;
2309
+ let externalWatchCount = 0;
2310
+
2311
+ $rootScope.internalCount = 0;
2312
+ $rootScope.externalCount = 0;
2313
+
2314
+ $rootScope.$evalAsync((scope) => {
2315
+ $rootScope.internalCount++;
2316
+ });
2317
+
2318
+ $rootScope.$$postDigest((scope) => {
2319
+ $rootScope.externalCount++;
2320
+ });
2321
+
2322
+ $rootScope.$watch("internalCount", (value) => {
2323
+ internalWatchCount = value;
2324
+ });
2325
+ $rootScope.$watch("externalCount", (value) => {
2326
+ externalWatchCount = value;
2327
+ });
2328
+
2329
+ $rootScope.$digest();
2330
+
2331
+ expect(internalWatchCount).toEqual(1);
2332
+ expect(externalWatchCount).toEqual(0);
2333
+ });
2334
+
2335
+ it("should cause a $digest rerun", () => {
2336
+ $rootScope.log = "";
2337
+ $rootScope.value = 0;
2338
+ $rootScope.$watch("value", () => {
2339
+ $rootScope.log += ".";
2340
+ });
2341
+ $rootScope.$watch("init", () => {
2342
+ $rootScope.$evalAsync('value = 123; log = log + "=" ');
2343
+ expect($rootScope.value).toEqual(0);
2344
+ });
2345
+ $rootScope.$digest();
2346
+ expect($rootScope.log).toEqual(".=.");
2347
+ });
2348
+
2349
+ it("should run async in the same order as added", () => {
2350
+ $rootScope.log = "";
2351
+ $rootScope.$evalAsync("log = log + 1");
2352
+ $rootScope.$evalAsync("log = log + 2");
2353
+ $rootScope.$digest();
2354
+ expect($rootScope.log).toBe("12");
2355
+ });
2356
+
2357
+ it("should allow passing locals to the expression", () => {
2358
+ $rootScope.log = "";
2359
+ $rootScope.$evalAsync("log = log + a", { a: 1 });
2360
+ $rootScope.$digest();
2361
+ expect($rootScope.log).toBe("1");
2362
+ });
2363
+
2364
+ it("should run async expressions in their proper context", () => {
2365
+ const child = $rootScope.$new();
2366
+ $rootScope.ctx = "root context";
2367
+ $rootScope.log = "";
2368
+ child.ctx = "child context";
2369
+ child.log = "";
2370
+ child.$evalAsync("log=ctx");
2371
+ $rootScope.$digest();
2372
+ expect($rootScope.log).toBe("");
2373
+ expect(child.log).toBe("child context");
2374
+ });
2375
+
2376
+ it("should operate only with a single queue across all child and isolate scopes", () => {
2377
+ const childScope = $rootScope.$new();
2378
+ const isolateScope = $rootScope.$new(true);
2379
+
2380
+ $rootScope.$evalAsync("rootExpression");
2381
+ childScope.$evalAsync("childExpression");
2382
+ isolateScope.$evalAsync("isolateExpression");
2383
+ expect(getQueues().asyncQueue).toEqual([
2384
+ {
2385
+ scope: $rootScope,
2386
+ fn: $parse("rootExpression"),
2387
+ locals: undefined,
2388
+ },
2389
+ {
2390
+ scope: childScope,
2391
+ fn: $parse("childExpression"),
2392
+ locals: undefined,
2393
+ },
2394
+ {
2395
+ scope: isolateScope,
2396
+ fn: $parse("isolateExpression"),
2397
+ locals: undefined,
2398
+ },
2399
+ ]);
2400
+ });
2401
+
2402
+ describe("auto-flushing when queueing outside of an $apply", () => {
2403
+ it("should auto-flush the queue asynchronously and trigger digest", () => {
2404
+ logs = [];
2405
+ $rootScope.$evalAsync(() => {
2406
+ logs.push("eval-ed!");
2407
+ return "eval-ed!";
2408
+ });
2409
+ $rootScope.$watch(() => {
2410
+ logs.push("digesting");
2411
+ return "digesting";
2412
+ });
2413
+ expect(logs).toEqual([]);
2414
+ setTimeout(() => {
2415
+ expect(logs).toEqual(["eval-ed!", "digesting", "digesting"]);
2416
+ });
2417
+ });
2418
+
2419
+ it("should not trigger digest asynchronously if the queue is empty in the next tick", () => {
2420
+ logs = [];
2421
+ $rootScope.$evalAsync(() => {
2422
+ logs.push("eval-ed!");
2423
+ return "eval-ed!";
2424
+ });
2425
+ $rootScope.$watch(() => {
2426
+ logs.push("digesting");
2427
+ return "digesting";
2428
+ });
2429
+ expect(logs).toEqual([]);
2430
+
2431
+ $rootScope.$digest();
2432
+
2433
+ expect(logs).toEqual(["eval-ed!", "digesting", "digesting"]);
2434
+ logs = [];
2435
+
2436
+ setTimeout(() => {
2437
+ expect(logs).toEqual([]);
2438
+ });
2439
+ });
2440
+
2441
+ it("should not schedule more than one auto-flush task", () => {
2442
+ logs = [];
2443
+ $rootScope.$evalAsync(() => {
2444
+ logs.push("eval-ed 1!");
2445
+ return "eval-ed 1!";
2446
+ });
2447
+ $rootScope.$evalAsync(() => {
2448
+ logs.push("eval-ed 2!");
2449
+ return "eval-ed 2!";
2450
+ });
2451
+ expect(logs).toEqual([]);
2452
+ setTimeout(() => {
2453
+ expect(logs).toEqual(["eval-ed 1!", "eval-ed 2!"]);
2454
+ });
2455
+
2456
+ setTimeout(() => {
2457
+ expect(logs).toEqual(["eval-ed 1!", "eval-ed 2!"]);
2458
+ });
2459
+ });
2460
+
2461
+ it("should not have execution affected by an explicit $digest call", () => {
2462
+ const scope1 = $rootScope.$new();
2463
+ const scope2 = $rootScope.$new();
2464
+
2465
+ scope1.$watch("value", (value) => {
2466
+ scope1.result = value;
2467
+ });
2468
+
2469
+ scope1.$evalAsync(() => {
2470
+ scope1.value = "bar";
2471
+ });
2472
+
2473
+ expect(scope1.result).toBe(undefined);
2474
+ scope2.$digest();
2475
+
2476
+ setTimeout(() => expect(scope1.result).toBe("bar"));
2477
+ });
2478
+ });
2479
+
2480
+ it("should not pass anything as `this` to scheduled functions", () => {
2481
+ let this1 = {};
2482
+ const this2 = (function () {
2483
+ return this;
2484
+ })();
2485
+ $rootScope.$evalAsync(function () {
2486
+ this1 = this;
2487
+ });
2488
+ $rootScope.$digest();
2489
+ expect(this1).toEqual(this2);
2490
+ });
2491
+ });
2492
+
2493
+ describe("$apply", () => {
2494
+ beforeEach(() => (logs = []));
2495
+
2496
+ it("should apply expression with full lifecycle", () => {
2497
+ let log = "";
2498
+ const child = $rootScope.$new();
2499
+ $rootScope.$watch("a", (a) => {
2500
+ log += "1";
2501
+ });
2502
+ child.$apply("$parent.a=0");
2503
+ expect(log).toEqual("1");
2504
+ });
2505
+
2506
+ it("should catch exceptions", () => {
2507
+ let log = "";
2508
+ const child = $rootScope.$new();
2509
+ $rootScope.$watch("a", (a) => {
2510
+ log += "1";
2511
+ });
2512
+ $rootScope.a = 0;
2513
+ child.$apply(() => {
2514
+ throw new Error("MyError");
2515
+ });
2516
+ expect(log).toEqual("1");
2517
+ expect(logs[0].message).toEqual("MyError");
2518
+ });
2519
+
2520
+ it("should log exceptions from $digest", () => {
2521
+ $rootScope.$watch("a", () => {
2522
+ $rootScope.b++;
2523
+ });
2524
+ $rootScope.$watch("b", () => {
2525
+ $rootScope.a++;
2526
+ });
2527
+ $rootScope.a = $rootScope.b = 0;
2528
+
2529
+ expect(() => {
2530
+ $rootScope.$apply();
2531
+ }).toThrow();
2532
+
2533
+ expect(logs[0]).toBeDefined();
2534
+
2535
+ expect($rootScope.$$phase).toBeNull();
2536
+ });
2537
+
2538
+ describe("exceptions", () => {
2539
+ let log;
2540
+
2541
+ beforeEach(() => {
2542
+ logs = [];
2543
+ log = "";
2544
+ $rootScope.$watch(() => {
2545
+ log += "$digest;";
2546
+ });
2547
+ $rootScope.$digest();
2548
+ log = "";
2549
+ });
2550
+
2551
+ it("should execute and return value and update", () => {
2552
+ $rootScope.name = "abc";
2553
+ expect($rootScope.$apply((scope) => scope.name)).toEqual("abc");
2554
+ expect(log).toEqual("$digest;");
2555
+ expect(logs).toEqual([]);
2556
+ });
2557
+
2558
+ it("should catch exception and update", () => {
2559
+ const error = new Error("MyError");
2560
+ $rootScope.$apply(() => {
2561
+ throw error;
2562
+ });
2563
+ expect(log).toEqual("$digest;");
2564
+ expect(logs).toEqual([error]);
2565
+ });
2566
+ });
2567
+
2568
+ describe("recursive $apply protection", () => {
2569
+ beforeEach(() => (logs = []));
2570
+
2571
+ it("should throw an exception if $apply is called while an $apply is in progress", () => {
2572
+ $rootScope.$apply(() => {
2573
+ $rootScope.$apply();
2574
+ });
2575
+ expect(logs[0].message.match(/progress/g).length).toBeTruthy();
2576
+ });
2577
+
2578
+ it("should not clear the state when calling $apply during an $apply", () => {
2579
+ $rootScope.$apply(() => {
2580
+ $rootScope.$apply();
2581
+ expect(logs[0].message.match(/progress/g).length).toBeTruthy();
2582
+ logs = [];
2583
+ $rootScope.$apply();
2584
+ expect(logs[0].message.match(/progress/g).length).toBeTruthy();
2585
+ });
2586
+ logs = [];
2587
+ $rootScope.$apply();
2588
+ expect(logs).toEqual([]);
2589
+ });
2590
+
2591
+ it("should throw an exception if $apply is called while flushing evalAsync queue", () => {
2592
+ $rootScope.$apply(() => {
2593
+ $rootScope.$evalAsync(() => {
2594
+ $rootScope.$apply();
2595
+ });
2596
+ });
2597
+ expect(logs[0].message.match(/progress/g).length).toBeTruthy();
2598
+ });
2599
+
2600
+ it("should throw an exception if $apply is called while a watch is being initialized", () => {
2601
+ const childScope1 = $rootScope.$new();
2602
+ childScope1.$watch("x", () => {
2603
+ childScope1.$apply();
2604
+ });
2605
+ childScope1.$apply();
2606
+ expect(logs[0].message.match(/progress/g).length).toBeTruthy();
2607
+ });
2608
+
2609
+ it("should thrown an exception if $apply in called from a watch fn (after init)", () => {
2610
+ const childScope2 = $rootScope.$new();
2611
+ childScope2.$apply(() => {
2612
+ childScope2.$watch("x", (newVal, oldVal) => {
2613
+ if (newVal !== oldVal) {
2614
+ childScope2.$apply();
2615
+ }
2616
+ });
2617
+ });
2618
+ childScope2.$apply(() => {
2619
+ childScope2.x = "something";
2620
+ });
2621
+
2622
+ expect(logs[0].message.match(/progress/g).length).toBeTruthy();
2623
+ });
2624
+ });
2625
+ });
2626
+
2627
+ describe("$applyAsync", () => {
2628
+ beforeEach(() => (logs = []));
2629
+ it("should evaluate in the context of specific $scope", () => {
2630
+ const scope = $rootScope.$new();
2631
+ let id = scope.$applyAsync('x = "CODE ORANGE"');
2632
+
2633
+ $browser.defer.cancel(id);
2634
+ setTimeout(() => {
2635
+ expect(scope.x).toBe("CODE ORANGE");
2636
+ expect($rootScope.x).toBeUndefined();
2637
+ });
2638
+
2639
+ expect(scope.x).toBeUndefined();
2640
+ });
2641
+
2642
+ it("should evaluate queued expressions in order", () => {
2643
+ $rootScope.x = [];
2644
+ let id1 = $rootScope.$applyAsync('x.push("expr1")');
2645
+ let id2 = $rootScope.$applyAsync('x.push("expr2")');
2646
+
2647
+ $browser.defer.cancel(id1);
2648
+ $browser.defer.cancel(id2);
2649
+ setTimeout(() => {
2650
+ expect($rootScope.x).toEqual(["expr1", "expr2"]);
2651
+ });
2652
+ expect($rootScope.x).toEqual([]);
2653
+ });
2654
+
2655
+ it("should evaluate subsequently queued items in same turn", () => {
2656
+ $rootScope.x = [];
2657
+ let id = $rootScope.$applyAsync(() => {
2658
+ $rootScope.x.push("expr1");
2659
+ $rootScope.$applyAsync('x.push("expr2")');
2660
+ expect($browser.deferredFns.length).toBe(0);
2661
+ });
2662
+
2663
+ $browser.defer.cancel(id);
2664
+ setTimeout(() => {
2665
+ expect($rootScope.x).toEqual(["expr1", "expr2"]);
2666
+ });
2667
+ expect($rootScope.x).toEqual([]);
2668
+ });
2669
+
2670
+ it("should pass thrown exceptions to $exceptionHandler", () => {
2671
+ let id = $rootScope.$applyAsync(() => {
2672
+ throw "OOPS";
2673
+ });
2674
+
2675
+ $browser.defer.cancel(id);
2676
+ expect(logs).toEqual([]);
2677
+ setTimeout(() => expect(logs[0]).toEqual("OOPS"));
2678
+ });
2679
+
2680
+ it("should evaluate subsequent expressions after an exception is thrown", () => {
2681
+ let id = $rootScope.$applyAsync(() => {
2682
+ throw "OOPS";
2683
+ });
2684
+ let id2 = $rootScope.$applyAsync('x = "All good!"');
2685
+
2686
+ $browser.defer.cancel(id);
2687
+ $browser.defer.cancel(id2);
2688
+ setTimeout(() => expect($rootScope.x).toBe("All good!"));
2689
+ expect($rootScope.x).toBeUndefined();
2690
+ });
2691
+
2692
+ it("should be cancelled if a $rootScope digest occurs before the next tick", () => {
2693
+ const cancel = spyOn($browser.defer, "cancel").and.callThrough();
2694
+ const expression = jasmine.createSpy("expr");
2695
+
2696
+ $rootScope.$applyAsync(expression);
2697
+ $rootScope.$digest();
2698
+ expect(expression).toHaveBeenCalled();
2699
+ expect(cancel).toHaveBeenCalled();
2700
+ expression.calls.reset();
2701
+ cancel.calls.reset();
2702
+
2703
+ // assert that another digest won't call the function again
2704
+ $rootScope.$digest();
2705
+ expect(expression).not.toHaveBeenCalled();
2706
+ expect(cancel).not.toHaveBeenCalled();
2707
+ });
2708
+ });
2709
+
2710
+ describe("$$postDigest", () => {
2711
+ beforeEach(() => (logs = []));
2712
+ it("should process callbacks as a queue (FIFO) when the scope is digested", () => {
2713
+ let signature = "";
2714
+
2715
+ $rootScope.$$postDigest(() => {
2716
+ signature += "A";
2717
+ $rootScope.$$postDigest(() => {
2718
+ signature += "D";
2719
+ });
2720
+ });
2721
+
2722
+ $rootScope.$$postDigest(() => {
2723
+ signature += "B";
2724
+ });
2725
+
2726
+ $rootScope.$$postDigest(() => {
2727
+ signature += "C";
2728
+ });
2729
+
2730
+ expect(signature).toBe("");
2731
+ $rootScope.$digest();
2732
+ expect(signature).toBe("ABCD");
2733
+ });
2734
+
2735
+ it("should support $apply calls nested in $$postDigest callbacks", () => {
2736
+ let signature = "";
2737
+
2738
+ $rootScope.$$postDigest(() => {
2739
+ signature += "A";
2740
+ });
2741
+
2742
+ $rootScope.$$postDigest(() => {
2743
+ signature += "B";
2744
+ $rootScope.$apply();
2745
+ signature += "D";
2746
+ });
2747
+
2748
+ $rootScope.$$postDigest(() => {
2749
+ signature += "C";
2750
+ });
2751
+
2752
+ expect(signature).toBe("");
2753
+ $rootScope.$digest();
2754
+ expect(signature).toBe("ABCD");
2755
+ });
2756
+
2757
+ it("should run a $$postDigest call on all child scopes when a parent scope is digested", () => {
2758
+ const parent = $rootScope.$new();
2759
+ const child = parent.$new();
2760
+ let count = 0;
2761
+
2762
+ $rootScope.$$postDigest(() => {
2763
+ count++;
2764
+ });
2765
+
2766
+ parent.$$postDigest(() => {
2767
+ count++;
2768
+ });
2769
+
2770
+ child.$$postDigest(() => {
2771
+ count++;
2772
+ });
2773
+
2774
+ expect(count).toBe(0);
2775
+ $rootScope.$digest();
2776
+ expect(count).toBe(3);
2777
+ });
2778
+
2779
+ it("should run a $$postDigest call even if the child scope is isolated", () => {
2780
+ const parent = $rootScope.$new();
2781
+ const child = parent.$new(true);
2782
+ let signature = "";
2783
+
2784
+ parent.$$postDigest(() => {
2785
+ signature += "A";
2786
+ });
2787
+
2788
+ child.$$postDigest(() => {
2789
+ signature += "B";
2790
+ });
2791
+
2792
+ expect(signature).toBe("");
2793
+ $rootScope.$digest();
2794
+ expect(signature).toBe("AB");
2795
+ });
2796
+ });
2797
+
2798
+ describe("events", () => {
2799
+ describe("$on", () => {
2800
+ it("should add listener for both $emit and $broadcast events", () => {
2801
+ logs = "";
2802
+ const child = $rootScope.$new();
2803
+
2804
+ function eventFn() {
2805
+ logs += "X";
2806
+ }
2807
+
2808
+ child.$on("abc", eventFn);
2809
+ expect(logs).toEqual("");
2810
+
2811
+ child.$emit("abc");
2812
+ expect(logs).toEqual("X");
2813
+
2814
+ child.$broadcast("abc");
2815
+ expect(logs).toEqual("XX");
2816
+ });
2817
+
2818
+ it("should increment ancestor $$listenerCount entries", () => {
2819
+ const child1 = $rootScope.$new();
2820
+ const child2 = child1.$new();
2821
+ const spy = jasmine.createSpy();
2822
+
2823
+ $rootScope.$on("event1", spy);
2824
+ expect($rootScope.$$listenerCount.event1).toEqual(1);
2825
+
2826
+ child1.$on("event1", spy);
2827
+ expect($rootScope.$$listenerCount.event1).toEqual(2);
2828
+ expect(child1.$$listenerCount.event1).toEqual(1);
2829
+
2830
+ child2.$on("event2", spy);
2831
+ expect($rootScope.$$listenerCount.event1).toEqual(2);
2832
+ expect($rootScope.$$listenerCount.event2).toEqual(1);
2833
+ expect(child1.$$listenerCount.event1).toEqual(1);
2834
+ expect(child1.$$listenerCount.event2).toEqual(1);
2835
+ expect(child2.$$listenerCount.event2).toEqual(1);
2836
+ });
2837
+
2838
+ describe("deregistration", () => {
2839
+ it("should return a function that deregisters the listener", () => {
2840
+ let log = "";
2841
+ const child = $rootScope.$new();
2842
+ let listenerRemove;
2843
+
2844
+ function eventFn() {
2845
+ log += "X";
2846
+ }
2847
+
2848
+ listenerRemove = child.$on("abc", eventFn);
2849
+ expect(log).toEqual("");
2850
+ expect(listenerRemove).toBeDefined();
2851
+
2852
+ child.$emit("abc");
2853
+ child.$broadcast("abc");
2854
+ expect(log).toEqual("XX");
2855
+ expect($rootScope.$$listenerCount.abc).toBe(1);
2856
+
2857
+ log = "";
2858
+ listenerRemove();
2859
+ child.$emit("abc");
2860
+ child.$broadcast("abc");
2861
+ expect(log).toEqual("");
2862
+ expect($rootScope.$$listenerCount.abc).toBeUndefined();
2863
+ });
2864
+
2865
+ // See issue https://github.com/angular/angular.js/issues/16135
2866
+ it("should deallocate the listener array entry", () => {
2867
+ const remove1 = $rootScope.$on("abc", () => {});
2868
+ $rootScope.$on("abc", () => {});
2869
+
2870
+ expect($rootScope.$$listeners.abc.length).toBe(2);
2871
+ expect(0 in $rootScope.$$listeners.abc).toBe(true);
2872
+
2873
+ remove1();
2874
+
2875
+ expect($rootScope.$$listeners.abc.length).toBe(2);
2876
+ expect(0 in $rootScope.$$listeners.abc).toBe(false);
2877
+ });
2878
+
2879
+ it("should call next listener after removing the current listener via its own handler", () => {
2880
+ const listener1 = jasmine.createSpy("listener1").and.callFake(() => {
2881
+ remove1();
2882
+ });
2883
+ let remove1 = $rootScope.$on("abc", listener1);
2884
+
2885
+ const listener2 = jasmine.createSpy("listener2");
2886
+ const remove2 = $rootScope.$on("abc", listener2);
2887
+
2888
+ const listener3 = jasmine.createSpy("listener3");
2889
+ const remove3 = $rootScope.$on("abc", listener3);
2890
+
2891
+ $rootScope.$broadcast("abc");
2892
+ expect(listener1).toHaveBeenCalled();
2893
+ expect(listener2).toHaveBeenCalled();
2894
+ expect(listener3).toHaveBeenCalled();
2895
+
2896
+ listener1.calls.reset();
2897
+ listener2.calls.reset();
2898
+ listener3.calls.reset();
2899
+
2900
+ $rootScope.$broadcast("abc");
2901
+ expect(listener1).not.toHaveBeenCalled();
2902
+ expect(listener2).toHaveBeenCalled();
2903
+ expect(listener3).toHaveBeenCalled();
2904
+ });
2905
+
2906
+ it("should call all subsequent listeners when a previous listener is removed via a handler", () => {
2907
+ const listener1 = jasmine.createSpy();
2908
+ const remove1 = $rootScope.$on("abc", listener1);
2909
+
2910
+ const listener2 = jasmine.createSpy().and.callFake(remove1);
2911
+ const remove2 = $rootScope.$on("abc", listener2);
2912
+
2913
+ const listener3 = jasmine.createSpy();
2914
+ const remove3 = $rootScope.$on("abc", listener3);
2915
+
2916
+ $rootScope.$broadcast("abc");
2917
+ expect(listener1).toHaveBeenCalled();
2918
+ expect(listener2).toHaveBeenCalled();
2919
+ expect(listener3).toHaveBeenCalled();
2920
+
2921
+ listener1.calls.reset();
2922
+ listener2.calls.reset();
2923
+ listener3.calls.reset();
2924
+
2925
+ $rootScope.$broadcast("abc");
2926
+ expect(listener1).not.toHaveBeenCalled();
2927
+ expect(listener2).toHaveBeenCalled();
2928
+ expect(listener3).toHaveBeenCalled();
2929
+ });
2930
+
2931
+ it("should not call listener when removed by previous", () => {
2932
+ const listener1 = jasmine.createSpy("listener1");
2933
+ const remove1 = $rootScope.$on("abc", listener1);
2934
+
2935
+ const listener2 = jasmine.createSpy("listener2").and.callFake(() => {
2936
+ remove3();
2937
+ });
2938
+ const remove2 = $rootScope.$on("abc", listener2);
2939
+
2940
+ const listener3 = jasmine.createSpy("listener3");
2941
+ let remove3 = $rootScope.$on("abc", listener3);
2942
+
2943
+ const listener4 = jasmine.createSpy("listener4");
2944
+ const remove4 = $rootScope.$on("abc", listener4);
2945
+
2946
+ $rootScope.$broadcast("abc");
2947
+ expect(listener1).toHaveBeenCalled();
2948
+ expect(listener2).toHaveBeenCalled();
2949
+ expect(listener3).not.toHaveBeenCalled();
2950
+ expect(listener4).toHaveBeenCalled();
2951
+
2952
+ listener1.calls.reset();
2953
+ listener2.calls.reset();
2954
+ listener3.calls.reset();
2955
+ listener4.calls.reset();
2956
+
2957
+ $rootScope.$broadcast("abc");
2958
+ expect(listener1).toHaveBeenCalled();
2959
+ expect(listener2).toHaveBeenCalled();
2960
+ expect(listener3).not.toHaveBeenCalled();
2961
+ expect(listener4).toHaveBeenCalled();
2962
+ });
2963
+
2964
+ it("should decrement ancestor $$listenerCount entries", () => {
2965
+ const child1 = $rootScope.$new();
2966
+ const child2 = child1.$new();
2967
+ const spy = jasmine.createSpy();
2968
+
2969
+ $rootScope.$on("event1", spy);
2970
+ expect($rootScope.$$listenerCount.event1).toEqual(1);
2971
+
2972
+ child1.$on("event1", spy);
2973
+ expect($rootScope.$$listenerCount.event1).toEqual(2);
2974
+ expect(child1.$$listenerCount.event1).toEqual(1);
2975
+
2976
+ const deregisterEvent2Listener = child2.$on("event2", spy);
2977
+ expect($rootScope.$$listenerCount.event1).toEqual(2);
2978
+ expect($rootScope.$$listenerCount.event2).toEqual(1);
2979
+ expect(child1.$$listenerCount.event1).toEqual(1);
2980
+ expect(child1.$$listenerCount.event2).toEqual(1);
2981
+ expect(child2.$$listenerCount.event2).toEqual(1);
2982
+
2983
+ deregisterEvent2Listener();
2984
+
2985
+ expect($rootScope.$$listenerCount.event1).toEqual(2);
2986
+ expect(child1.$$listenerCount.event1).toEqual(1);
2987
+ expect(child2.$$listenerCount).toBeTruthy();
2988
+ });
2989
+
2990
+ it("should not decrement $$listenerCount when called second time", () => {
2991
+ const child = $rootScope.$new();
2992
+ const listener1Spy = jasmine.createSpy();
2993
+ const listener2Spy = jasmine.createSpy();
2994
+
2995
+ child.$on("abc", listener1Spy);
2996
+ expect($rootScope.$$listenerCount.abc).toEqual(1);
2997
+ expect(child.$$listenerCount.abc).toEqual(1);
2998
+
2999
+ const deregisterEventListener = child.$on("abc", listener2Spy);
3000
+ expect($rootScope.$$listenerCount.abc).toEqual(2);
3001
+ expect(child.$$listenerCount.abc).toEqual(2);
3002
+
3003
+ deregisterEventListener();
3004
+
3005
+ expect($rootScope.$$listenerCount.abc).toEqual(1);
3006
+ expect(child.$$listenerCount.abc).toEqual(1);
3007
+
3008
+ deregisterEventListener();
3009
+
3010
+ expect($rootScope.$$listenerCount.abc).toEqual(1);
3011
+ expect(child.$$listenerCount.abc).toEqual(1);
3012
+ });
3013
+ });
3014
+ });
3015
+
3016
+ describe("$emit", () => {
3017
+ let log;
3018
+ let child;
3019
+ let grandChild;
3020
+ let greatGrandChild;
3021
+
3022
+ function logger(event) {
3023
+ log += `${event.currentScope.id}>`;
3024
+ }
3025
+
3026
+ beforeEach(() => {
3027
+ log = "";
3028
+ logs = [];
3029
+ child = $rootScope.$new();
3030
+ grandChild = child.$new();
3031
+ greatGrandChild = grandChild.$new();
3032
+
3033
+ $rootScope.id = 0;
3034
+ child.id = 1;
3035
+ grandChild.id = 2;
3036
+ greatGrandChild.id = 3;
3037
+
3038
+ $rootScope.$on("myEvent", logger);
3039
+ child.$on("myEvent", logger);
3040
+ grandChild.$on("myEvent", logger);
3041
+ greatGrandChild.$on("myEvent", logger);
3042
+ });
3043
+
3044
+ it("should bubble event up to the root scope", () => {
3045
+ grandChild.$emit("myEvent");
3046
+ expect(log).toEqual("2>1>0>");
3047
+ });
3048
+
3049
+ it("should allow all events on the same scope to run even if stopPropagation is called", () => {
3050
+ child.$on("myEvent", logger);
3051
+ grandChild.$on("myEvent", (e) => {
3052
+ e.stopPropagation();
3053
+ });
3054
+ grandChild.$on("myEvent", logger);
3055
+ grandChild.$on("myEvent", logger);
3056
+ grandChild.$emit("myEvent");
3057
+ expect(log).toEqual("2>2>2>");
3058
+ });
3059
+
3060
+ it("should dispatch exceptions to the $exceptionHandler", () => {
3061
+ child.$on("myEvent", () => {
3062
+ throw "bubbleException";
3063
+ });
3064
+ grandChild.$emit("myEvent");
3065
+ expect(log).toEqual("2>1>0>");
3066
+ expect(logs).toEqual(["bubbleException"]);
3067
+ });
3068
+
3069
+ it("should allow stopping event propagation", () => {
3070
+ child.$on("myEvent", (event) => {
3071
+ event.stopPropagation();
3072
+ });
3073
+ grandChild.$emit("myEvent");
3074
+ expect(log).toEqual("2>1>");
3075
+ });
3076
+
3077
+ it("should forward method arguments", () => {
3078
+ child.$on("abc", (event, arg1, arg2) => {
3079
+ expect(event.name).toBe("abc");
3080
+ expect(arg1).toBe("arg1");
3081
+ expect(arg2).toBe("arg2");
3082
+ });
3083
+ child.$emit("abc", "arg1", "arg2");
3084
+ });
3085
+
3086
+ it("should allow removing event listener inside a listener on $emit", () => {
3087
+ const spy1 = jasmine.createSpy("1st listener");
3088
+ const spy2 = jasmine.createSpy("2nd listener");
3089
+ const spy3 = jasmine.createSpy("3rd listener");
3090
+
3091
+ const remove1 = child.$on("evt", spy1);
3092
+ const remove2 = child.$on("evt", spy2);
3093
+ const remove3 = child.$on("evt", spy3);
3094
+
3095
+ spy1.and.callFake(remove1);
3096
+
3097
+ expect(child.$$listeners.evt.length).toBe(3);
3098
+
3099
+ // should call all listeners and remove 1st
3100
+ child.$emit("evt");
3101
+ expect(spy1).toHaveBeenCalled();
3102
+ expect(spy2).toHaveBeenCalled();
3103
+ expect(spy3).toHaveBeenCalled();
3104
+ expect(child.$$listeners.evt.length).toBe(3); // cleanup will happen on next $emit
3105
+
3106
+ spy1.calls.reset();
3107
+ spy2.calls.reset();
3108
+ spy3.calls.reset();
3109
+
3110
+ // should call only 2nd because 1st was already removed and 2nd removes 3rd
3111
+ spy2.and.callFake(remove3);
3112
+ child.$emit("evt");
3113
+ expect(spy1).not.toHaveBeenCalled();
3114
+ expect(spy2).toHaveBeenCalled();
3115
+ expect(spy3).not.toHaveBeenCalled();
3116
+ expect(child.$$listeners.evt.length).toBe(1);
3117
+ });
3118
+
3119
+ it("should allow removing event listener inside a listener on $broadcast", () => {
3120
+ const spy1 = jasmine.createSpy("1st listener");
3121
+ const spy2 = jasmine.createSpy("2nd listener");
3122
+ const spy3 = jasmine.createSpy("3rd listener");
3123
+
3124
+ const remove1 = child.$on("evt", spy1);
3125
+ const remove2 = child.$on("evt", spy2);
3126
+ const remove3 = child.$on("evt", spy3);
3127
+
3128
+ spy1.and.callFake(remove1);
3129
+
3130
+ expect(child.$$listeners.evt.length).toBe(3);
3131
+
3132
+ // should call all listeners and remove 1st
3133
+ child.$broadcast("evt");
3134
+ expect(spy1).toHaveBeenCalled();
3135
+ expect(spy2).toHaveBeenCalled();
3136
+ expect(spy3).toHaveBeenCalled();
3137
+ expect(child.$$listeners.evt.length).toBe(3); // cleanup will happen on next $broadcast
3138
+
3139
+ spy1.calls.reset();
3140
+ spy2.calls.reset();
3141
+ spy3.calls.reset();
3142
+
3143
+ // should call only 2nd because 1st was already removed and 2nd removes 3rd
3144
+ spy2.and.callFake(remove3);
3145
+ child.$broadcast("evt");
3146
+ expect(spy1).not.toHaveBeenCalled();
3147
+ expect(spy2).toHaveBeenCalled();
3148
+ expect(spy3).not.toHaveBeenCalled();
3149
+ expect(child.$$listeners.evt.length).toBe(1);
3150
+ });
3151
+
3152
+ describe("event object", () => {
3153
+ it("should have methods/properties", () => {
3154
+ let eventFired = false;
3155
+
3156
+ child.$on("myEvent", (e) => {
3157
+ expect(e.targetScope).toBe(grandChild);
3158
+ expect(e.currentScope).toBe(child);
3159
+ expect(e.name).toBe("myEvent");
3160
+ eventFired = true;
3161
+ });
3162
+ grandChild.$emit("myEvent");
3163
+ expect(eventFired).toBe(true);
3164
+ });
3165
+
3166
+ it("should have its `currentScope` property set to null after emit", () => {
3167
+ let event;
3168
+
3169
+ child.$on("myEvent", (e) => {
3170
+ event = e;
3171
+ });
3172
+ grandChild.$emit("myEvent");
3173
+
3174
+ expect(event.currentScope).toBe(null);
3175
+ expect(event.targetScope).toBe(grandChild);
3176
+ expect(event.name).toBe("myEvent");
3177
+ });
3178
+
3179
+ it("should have preventDefault method and defaultPrevented property", () => {
3180
+ let event = grandChild.$emit("myEvent");
3181
+ expect(event.defaultPrevented).toBe(false);
3182
+
3183
+ child.$on("myEvent", (event) => {
3184
+ event.preventDefault();
3185
+ });
3186
+ event = grandChild.$emit("myEvent");
3187
+ expect(event.defaultPrevented).toBe(true);
3188
+ expect(event.currentScope).toBe(null);
3189
+ });
3190
+ });
3191
+ });
3192
+
3193
+ describe("$broadcast", () => {
3194
+ describe("event propagation", () => {
3195
+ let log;
3196
+ let child1;
3197
+ let child2;
3198
+ let child3;
3199
+ let grandChild11;
3200
+ let grandChild21;
3201
+ let grandChild22;
3202
+ let grandChild23;
3203
+ let greatGrandChild211;
3204
+
3205
+ function logger(event) {
3206
+ log += `${event.currentScope.id}>`;
3207
+ }
3208
+
3209
+ beforeEach(() => {
3210
+ log = "";
3211
+ child1 = $rootScope.$new();
3212
+ child2 = $rootScope.$new();
3213
+ child3 = $rootScope.$new();
3214
+ grandChild11 = child1.$new();
3215
+ grandChild21 = child2.$new();
3216
+ grandChild22 = child2.$new();
3217
+ grandChild23 = child2.$new();
3218
+ greatGrandChild211 = grandChild21.$new();
3219
+
3220
+ $rootScope.id = 0;
3221
+ child1.id = 1;
3222
+ child2.id = 2;
3223
+ child3.id = 3;
3224
+ grandChild11.id = 11;
3225
+ grandChild21.id = 21;
3226
+ grandChild22.id = 22;
3227
+ grandChild23.id = 23;
3228
+ greatGrandChild211.id = 211;
3229
+
3230
+ $rootScope.$on("myEvent", logger);
3231
+ child1.$on("myEvent", logger);
3232
+ child2.$on("myEvent", logger);
3233
+ child3.$on("myEvent", logger);
3234
+ grandChild11.$on("myEvent", logger);
3235
+ grandChild21.$on("myEvent", logger);
3236
+ grandChild22.$on("myEvent", logger);
3237
+ grandChild23.$on("myEvent", logger);
3238
+ greatGrandChild211.$on("myEvent", logger);
3239
+
3240
+ // R
3241
+ // / | \
3242
+ // 1 2 3
3243
+ // / / | \
3244
+ // 11 21 22 23
3245
+ // |
3246
+ // 211
3247
+ });
3248
+
3249
+ it("should broadcast an event from the root scope", () => {
3250
+ $rootScope.$broadcast("myEvent");
3251
+ expect(log).toBe("0>1>11>2>21>211>22>23>3>");
3252
+ });
3253
+
3254
+ it("should broadcast an event from a child scope", () => {
3255
+ child2.$broadcast("myEvent");
3256
+ expect(log).toBe("2>21>211>22>23>");
3257
+ });
3258
+
3259
+ it("should broadcast an event from a leaf scope with a sibling", () => {
3260
+ grandChild22.$broadcast("myEvent");
3261
+ expect(log).toBe("22>");
3262
+ });
3263
+
3264
+ it("should broadcast an event from a leaf scope without a sibling", () => {
3265
+ grandChild23.$broadcast("myEvent");
3266
+ expect(log).toBe("23>");
3267
+ });
3268
+
3269
+ it("should not not fire any listeners for other events", () => {
3270
+ $rootScope.$broadcast("fooEvent");
3271
+ expect(log).toBe("");
3272
+ });
3273
+
3274
+ it("should not descend past scopes with a $$listerCount of 0 or undefined", () => {
3275
+ const EVENT = "fooEvent";
3276
+ const spy = jasmine.createSpy("listener");
3277
+
3278
+ // Precondition: There should be no listeners for fooEvent.
3279
+ expect($rootScope.$$listenerCount[EVENT]).toBeUndefined();
3280
+
3281
+ // Add a spy listener to a child scope.
3282
+ $rootScope.$$childHead.$$listeners[EVENT] = [spy];
3283
+
3284
+ // $rootScope's count for 'fooEvent' is undefined, so spy should not be called.
3285
+ $rootScope.$broadcast(EVENT);
3286
+ expect(spy).not.toHaveBeenCalled();
3287
+ });
3288
+
3289
+ it("should return event object", () => {
3290
+ const result = child1.$broadcast("some");
3291
+
3292
+ expect(result).toBeDefined();
3293
+ expect(result.name).toBe("some");
3294
+ expect(result.targetScope).toBe(child1);
3295
+ });
3296
+ });
3297
+
3298
+ describe("listener", () => {
3299
+ it("should receive event object", () => {
3300
+ const scope = $rootScope;
3301
+ const child = scope.$new();
3302
+ let eventFired = false;
3303
+
3304
+ child.$on("fooEvent", (event) => {
3305
+ eventFired = true;
3306
+ expect(event.name).toBe("fooEvent");
3307
+ expect(event.targetScope).toBe(scope);
3308
+ expect(event.currentScope).toBe(child);
3309
+ });
3310
+ scope.$broadcast("fooEvent");
3311
+
3312
+ expect(eventFired).toBe(true);
3313
+ });
3314
+
3315
+ it("should have the event's `currentScope` property set to null after broadcast", () => {
3316
+ const scope = $rootScope;
3317
+ const child = scope.$new();
3318
+ let event;
3319
+
3320
+ child.$on("fooEvent", (e) => {
3321
+ event = e;
3322
+ });
3323
+ scope.$broadcast("fooEvent");
3324
+
3325
+ expect(event.name).toBe("fooEvent");
3326
+ expect(event.targetScope).toBe(scope);
3327
+ expect(event.currentScope).toBe(null);
3328
+ });
3329
+
3330
+ it("should support passing messages as constargs", () => {
3331
+ const scope = $rootScope;
3332
+ const child = scope.$new();
3333
+ let args;
3334
+
3335
+ child.$on("fooEvent", function () {
3336
+ args = arguments;
3337
+ });
3338
+ scope.$broadcast("fooEvent", "do", "re", "me", "fa");
3339
+
3340
+ expect(args.length).toBe(5);
3341
+ expect(sliceArgs(args, 1)).toEqual(["do", "re", "me", "fa"]);
3342
+ });
3343
+ });
3344
+ });
3345
+
3346
+ it("should allow recursive $emit/$broadcast", () => {
3347
+ let callCount = 0;
3348
+ $rootScope.$on("evt", ($event, arg0) => {
3349
+ callCount++;
3350
+ if (arg0 !== 1234) {
3351
+ $rootScope.$emit("evt", 1234);
3352
+ $rootScope.$broadcast("evt", 1234);
3353
+ }
3354
+ });
3355
+
3356
+ $rootScope.$emit("evt");
3357
+ $rootScope.$broadcast("evt");
3358
+ expect(callCount).toBe(6);
3359
+ });
3360
+
3361
+ it("should allow recursive $emit/$broadcast between parent/child", () => {
3362
+ const child = $rootScope.$new();
3363
+ let calls = "";
3364
+
3365
+ $rootScope.$on("evt", ($event, arg0) => {
3366
+ calls += "r"; // For "root".
3367
+ if (arg0 === "fromChild") {
3368
+ $rootScope.$broadcast("evt", "fromRoot2");
3369
+ }
3370
+ });
3371
+
3372
+ child.$on("evt", ($event, arg0) => {
3373
+ calls += "c"; // For "child".
3374
+ if (arg0 === "fromRoot1") {
3375
+ child.$emit("evt", "fromChild");
3376
+ }
3377
+ });
3378
+
3379
+ $rootScope.$broadcast("evt", "fromRoot1");
3380
+ expect(calls).toBe("rccrrc");
3381
+ });
3382
+ });
3383
+
3384
+ describe("doc examples", () => {
3385
+ it("should properly fire off watch listeners upon scope changes", () => {
3386
+ // <docs tag="docs1">
3387
+ const scope = $rootScope.$new();
3388
+ scope.salutation = "Hello";
3389
+ scope.name = "World";
3390
+
3391
+ expect(scope.greeting).toEqual(undefined);
3392
+
3393
+ scope.$watch("name", () => {
3394
+ scope.greeting = `${scope.salutation} ${scope.name}!`;
3395
+ }); // initialize the watch
3396
+
3397
+ expect(scope.greeting).toEqual(undefined);
3398
+ scope.name = "Misko";
3399
+ // still old value, since watches have not been called yet
3400
+ expect(scope.greeting).toEqual(undefined);
3401
+
3402
+ scope.$digest(); // fire all the watches
3403
+ expect(scope.greeting).toEqual("Hello Misko!");
3404
+ // </docs>
3405
+ });
3406
+ });
3407
+
3408
+ describe("TTL configurability", () => {
3409
+ it("allows configuring a shorter TTL", () => {
3410
+ const injector = createInjector([
3411
+ "ng",
3412
+ function ($rootScopeProvider) {
3413
+ $rootScopeProvider.digestTtl(2);
3414
+ },
3415
+ ]);
3416
+ const scope = injector.get("$rootScope");
3417
+ scope.counterA = 0;
3418
+ scope.counterB = 0;
3419
+ scope.$watch(
3420
+ (scope) => {
3421
+ return scope.counterA;
3422
+ },
3423
+ (newValue, oldValue, scope) => {
3424
+ if (scope.counterB < 10) {
3425
+ scope.counterB++;
3426
+ }
3427
+ },
3428
+ );
3429
+ scope.$watch(
3430
+ (scope) => {
3431
+ return scope.counterB;
3432
+ },
3433
+ (newValue, oldValue, scope) => {
3434
+ scope.counterA++;
3435
+ },
3436
+ );
3437
+ expect(() => {
3438
+ scope.$digest();
3439
+ }).toThrow();
3440
+ });
3441
+ });
3442
+ });