@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,4655 @@
1
+ import { AST, Lexer } from "../../src/core/parser/parse";
2
+ import {
3
+ forEach,
4
+ isFunction,
5
+ sliceArgs,
6
+ csp,
7
+ valueFn,
8
+ extend,
9
+ identity,
10
+ } from "../../src/core/utils";
11
+ import { publishExternalAPI } from "../../src/public";
12
+ import { createInjector } from "../../src/injector";
13
+
14
+ describe("parser", () => {
15
+ let $rootScope;
16
+ let $parse;
17
+ let scope;
18
+ let logs = [];
19
+
20
+ beforeEach(() => {
21
+ publishExternalAPI().decorator("$exceptionHandler", function () {
22
+ return (exception, cause) => {
23
+ logs.push(exception);
24
+ console.error(exception, cause);
25
+ };
26
+ });
27
+ let injector = createInjector(["ng"]);
28
+ $parse = injector.get("$parse");
29
+ $rootScope = injector.get("$rootScope");
30
+ });
31
+
32
+ describe("lexer", () => {
33
+ let lex;
34
+
35
+ beforeEach(() => {
36
+ lex = function () {
37
+ const lexer = new Lexer({ csp: false });
38
+ return lexer.lex.apply(lexer, arguments);
39
+ };
40
+ });
41
+
42
+ it("should only match number chars with isNumber", () => {
43
+ expect(Lexer.prototype.isNumber("0")).toBe(true);
44
+ expect(Lexer.prototype.isNumber("")).toBeFalsy();
45
+ expect(Lexer.prototype.isNumber(" ")).toBeFalsy();
46
+ expect(Lexer.prototype.isNumber(0)).toBeFalsy();
47
+ expect(Lexer.prototype.isNumber(false)).toBeFalsy();
48
+ expect(Lexer.prototype.isNumber(true)).toBeFalsy();
49
+ expect(Lexer.prototype.isNumber(undefined)).toBeFalsy();
50
+ expect(Lexer.prototype.isNumber(null)).toBeFalsy();
51
+ });
52
+
53
+ it("should tokenize a string", () => {
54
+ const tokens = lex("a.bc[22]+1.3|f:'a\\'c':\"d\\\"e\"");
55
+ let i = 0;
56
+ expect(tokens[i].index).toEqual(0);
57
+ expect(tokens[i].text).toEqual("a");
58
+
59
+ i++;
60
+ expect(tokens[i].index).toEqual(1);
61
+ expect(tokens[i].text).toEqual(".");
62
+
63
+ i++;
64
+ expect(tokens[i].index).toEqual(2);
65
+ expect(tokens[i].text).toEqual("bc");
66
+
67
+ i++;
68
+ expect(tokens[i].index).toEqual(4);
69
+ expect(tokens[i].text).toEqual("[");
70
+
71
+ i++;
72
+ expect(tokens[i].index).toEqual(5);
73
+ expect(tokens[i].text).toEqual("22");
74
+ expect(tokens[i].value).toEqual(22);
75
+ expect(tokens[i].constant).toEqual(true);
76
+
77
+ i++;
78
+ expect(tokens[i].index).toEqual(7);
79
+ expect(tokens[i].text).toEqual("]");
80
+
81
+ i++;
82
+ expect(tokens[i].index).toEqual(8);
83
+ expect(tokens[i].text).toEqual("+");
84
+
85
+ i++;
86
+ expect(tokens[i].index).toEqual(9);
87
+ expect(tokens[i].text).toEqual("1.3");
88
+ expect(tokens[i].value).toEqual(1.3);
89
+ expect(tokens[i].constant).toEqual(true);
90
+
91
+ i++;
92
+ expect(tokens[i].index).toEqual(12);
93
+ expect(tokens[i].text).toEqual("|");
94
+
95
+ i++;
96
+ expect(tokens[i].index).toEqual(13);
97
+ expect(tokens[i].text).toEqual("f");
98
+
99
+ i++;
100
+ expect(tokens[i].index).toEqual(14);
101
+ expect(tokens[i].text).toEqual(":");
102
+
103
+ i++;
104
+ expect(tokens[i].index).toEqual(15);
105
+ expect(tokens[i].value).toEqual("a'c");
106
+
107
+ i++;
108
+ expect(tokens[i].index).toEqual(21);
109
+ expect(tokens[i].text).toEqual(":");
110
+
111
+ i++;
112
+ expect(tokens[i].index).toEqual(22);
113
+ expect(tokens[i].value).toEqual('d"e');
114
+ });
115
+
116
+ it("should tokenize identifiers with spaces around dots the same as without spaces", () => {
117
+ function getText(t) {
118
+ return t.text;
119
+ }
120
+ const spaces = lex("foo. bar . baz").map(getText);
121
+ const noSpaces = lex("foo.bar.baz").map(getText);
122
+
123
+ expect(spaces).toEqual(noSpaces);
124
+ });
125
+
126
+ it("should use callback functions to know when an identifier is valid", () => {
127
+ function getText(t) {
128
+ return t.text;
129
+ }
130
+ const isIdentifierStart = jasmine.createSpy("start");
131
+ const isIdentifierContinue = jasmine.createSpy("continue");
132
+ isIdentifierStart.and.returnValue(true);
133
+ const lex = new Lexer({
134
+ csp: false,
135
+ isIdentifierStart,
136
+ isIdentifierContinue,
137
+ });
138
+
139
+ isIdentifierContinue.and.returnValue(true);
140
+ let tokens = lex.lex("πΣε").map(getText);
141
+ expect(tokens).toEqual(["πΣε"]);
142
+
143
+ isIdentifierContinue.and.returnValue(false);
144
+ tokens = lex.lex("πΣε").map(getText);
145
+ expect(tokens).toEqual(["π", "Σ", "ε"]);
146
+ });
147
+
148
+ it("should send the unicode characters and code points", () => {
149
+ function getText(t) {
150
+ return t.text;
151
+ }
152
+ const isIdentifierStart = jasmine.createSpy("start");
153
+ const isIdentifierContinue = jasmine.createSpy("continue");
154
+ isIdentifierStart.and.returnValue(true);
155
+ isIdentifierContinue.and.returnValue(true);
156
+ const lex = new Lexer({
157
+ csp: false,
158
+ isIdentifierStart,
159
+ isIdentifierContinue,
160
+ });
161
+ const tokens = lex.lex("\uD801\uDC37\uD852\uDF62\uDBFF\uDFFF");
162
+ expect(isIdentifierStart).toHaveBeenCalledTimes(1);
163
+ expect(isIdentifierStart.calls.argsFor(0)).toEqual([
164
+ "\uD801\uDC37",
165
+ 0x10437,
166
+ ]);
167
+ expect(isIdentifierContinue).toHaveBeenCalledTimes(2);
168
+ expect(isIdentifierContinue.calls.argsFor(0)).toEqual([
169
+ "\uD852\uDF62",
170
+ 0x24b62,
171
+ ]);
172
+ expect(isIdentifierContinue.calls.argsFor(1)).toEqual([
173
+ "\uDBFF\uDFFF",
174
+ 0x10ffff,
175
+ ]);
176
+ });
177
+
178
+ it("should tokenize undefined", () => {
179
+ const tokens = lex("undefined");
180
+ const i = 0;
181
+ expect(tokens[i].index).toEqual(0);
182
+ expect(tokens[i].text).toEqual("undefined");
183
+ });
184
+
185
+ it("should tokenize quoted string", () => {
186
+ const str = "['\\'', \"\\\"\"]";
187
+ const tokens = lex(str);
188
+
189
+ expect(tokens[1].index).toEqual(1);
190
+ expect(tokens[1].value).toEqual("'");
191
+
192
+ expect(tokens[3].index).toEqual(7);
193
+ expect(tokens[3].value).toEqual('"');
194
+ });
195
+
196
+ it("should tokenize escaped quoted string", () => {
197
+ const str = '"\\"\\n\\f\\r\\t\\v\\u00A0"';
198
+ const tokens = lex(str);
199
+
200
+ expect(tokens[0].value).toEqual('"\n\f\r\t\v\u00A0');
201
+ });
202
+
203
+ it("should tokenize unicode", () => {
204
+ const tokens = lex('"\\u00A0"');
205
+ expect(tokens.length).toEqual(1);
206
+ expect(tokens[0].value).toEqual("\u00a0");
207
+ });
208
+
209
+ it("should ignore whitespace", () => {
210
+ const tokens = lex("a \t \n \r b");
211
+ expect(tokens[0].text).toEqual("a");
212
+ expect(tokens[1].text).toEqual("b");
213
+ });
214
+
215
+ it("should tokenize relation and equality", () => {
216
+ const tokens = lex("! == != < > <= >= === !==");
217
+ expect(tokens[0].text).toEqual("!");
218
+ expect(tokens[1].text).toEqual("==");
219
+ expect(tokens[2].text).toEqual("!=");
220
+ expect(tokens[3].text).toEqual("<");
221
+ expect(tokens[4].text).toEqual(">");
222
+ expect(tokens[5].text).toEqual("<=");
223
+ expect(tokens[6].text).toEqual(">=");
224
+ expect(tokens[7].text).toEqual("===");
225
+ expect(tokens[8].text).toEqual("!==");
226
+ });
227
+
228
+ it("should tokenize logical and ternary", () => {
229
+ const tokens = lex("&& || ? :");
230
+ expect(tokens[0].text).toEqual("&&");
231
+ expect(tokens[1].text).toEqual("||");
232
+ expect(tokens[2].text).toEqual("?");
233
+ expect(tokens[3].text).toEqual(":");
234
+ });
235
+
236
+ it("should tokenize statements", () => {
237
+ const tokens = lex("a;b;");
238
+ expect(tokens[0].text).toEqual("a");
239
+ expect(tokens[1].text).toEqual(";");
240
+ expect(tokens[2].text).toEqual("b");
241
+ expect(tokens[3].text).toEqual(";");
242
+ });
243
+
244
+ it("should tokenize function invocation", () => {
245
+ const tokens = lex("a()");
246
+ expect(tokens.map((t) => t.text)).toEqual(["a", "(", ")"]);
247
+ });
248
+
249
+ it("should tokenize method invocation", () => {
250
+ const tokens = lex("a.b.c (d) - e.f()");
251
+ expect(tokens.map((t) => t.text)).toEqual([
252
+ "a",
253
+ ".",
254
+ "b",
255
+ ".",
256
+ "c",
257
+ "(",
258
+ "d",
259
+ ")",
260
+ "-",
261
+ "e",
262
+ ".",
263
+ "f",
264
+ "(",
265
+ ")",
266
+ ]);
267
+ });
268
+
269
+ it("should tokenize number", () => {
270
+ const tokens = lex("0.5");
271
+ expect(tokens[0].value).toEqual(0.5);
272
+ });
273
+
274
+ it("should tokenize negative number", () => {
275
+ let value = $rootScope.$eval("-0.5");
276
+ expect(value).toEqual(-0.5);
277
+
278
+ value = $rootScope.$eval("{a:-0.5}");
279
+ expect(value).toEqual({ a: -0.5 });
280
+ });
281
+
282
+ it("should tokenize number with exponent", () => {
283
+ let tokens = lex("0.5E-10");
284
+ expect(tokens[0].value).toEqual(0.5e-10);
285
+ expect($rootScope.$eval("0.5E-10")).toEqual(0.5e-10);
286
+
287
+ tokens = lex("0.5E+10");
288
+ expect(tokens[0].value).toEqual(0.5e10);
289
+ });
290
+
291
+ it("should throws exception for invalid exponent", () => {
292
+ expect(() => {
293
+ lex("0.5E-");
294
+ }).toThrowError(/lexerr/);
295
+
296
+ expect(() => {
297
+ lex("0.5E-A");
298
+ }).toThrowError(/lexerr/);
299
+ });
300
+
301
+ it("should tokenize number starting with a dot", () => {
302
+ const tokens = lex(".5");
303
+ expect(tokens[0].value).toEqual(0.5);
304
+ });
305
+
306
+ it("should throw error on invalid unicode", () => {
307
+ expect(() => {
308
+ lex("'\\u1''bla'");
309
+ }).toThrowError(/lexerr/);
310
+ });
311
+ });
312
+
313
+ describe("ast", () => {
314
+ let createAst;
315
+
316
+ beforeEach(() => {
317
+ /* global AST: false */
318
+ createAst = function () {
319
+ const lexer = new Lexer({ csp: false });
320
+ const ast = new AST(lexer, {
321
+ csp: false,
322
+ literals: {
323
+ true: true,
324
+ false: false,
325
+ undefined: undefined,
326
+ null: null,
327
+ },
328
+ });
329
+ return ast.ast.apply(ast, arguments);
330
+ };
331
+ });
332
+
333
+ it("should handle an empty list of tokens", () => {
334
+ expect(createAst("")).toEqual({ type: "Program", body: [] });
335
+ });
336
+
337
+ it("should understand identifiers", () => {
338
+ expect(createAst("foo")).toEqual({
339
+ type: "Program",
340
+ body: [
341
+ {
342
+ type: "ExpressionStatement",
343
+ expression: { type: "Identifier", name: "foo" },
344
+ },
345
+ ],
346
+ });
347
+ });
348
+
349
+ it("should understand non-computed member expressions", () => {
350
+ expect(createAst("foo.bar")).toEqual({
351
+ type: "Program",
352
+ body: [
353
+ {
354
+ type: "ExpressionStatement",
355
+ expression: {
356
+ type: "MemberExpression",
357
+ object: { type: "Identifier", name: "foo" },
358
+ property: { type: "Identifier", name: "bar" },
359
+ computed: false,
360
+ },
361
+ },
362
+ ],
363
+ });
364
+ });
365
+
366
+ it("should associate non-computed member expressions left-to-right", () => {
367
+ expect(createAst("foo.bar.baz")).toEqual({
368
+ type: "Program",
369
+ body: [
370
+ {
371
+ type: "ExpressionStatement",
372
+ expression: {
373
+ type: "MemberExpression",
374
+ object: {
375
+ type: "MemberExpression",
376
+ object: { type: "Identifier", name: "foo" },
377
+ property: { type: "Identifier", name: "bar" },
378
+ computed: false,
379
+ },
380
+ property: { type: "Identifier", name: "baz" },
381
+ computed: false,
382
+ },
383
+ },
384
+ ],
385
+ });
386
+ });
387
+
388
+ it("should understand computed member expressions", () => {
389
+ expect(createAst("foo[bar]")).toEqual({
390
+ type: "Program",
391
+ body: [
392
+ {
393
+ type: "ExpressionStatement",
394
+ expression: {
395
+ type: "MemberExpression",
396
+ object: { type: "Identifier", name: "foo" },
397
+ property: { type: "Identifier", name: "bar" },
398
+ computed: true,
399
+ },
400
+ },
401
+ ],
402
+ });
403
+ });
404
+
405
+ it("should associate computed member expressions left-to-right", () => {
406
+ expect(createAst("foo[bar][baz]")).toEqual({
407
+ type: "Program",
408
+ body: [
409
+ {
410
+ type: "ExpressionStatement",
411
+ expression: {
412
+ type: "MemberExpression",
413
+ object: {
414
+ type: "MemberExpression",
415
+ object: { type: "Identifier", name: "foo" },
416
+ property: { type: "Identifier", name: "bar" },
417
+ computed: true,
418
+ },
419
+ property: { type: "Identifier", name: "baz" },
420
+ computed: true,
421
+ },
422
+ },
423
+ ],
424
+ });
425
+ });
426
+
427
+ it("should understand call expressions", () => {
428
+ expect(createAst("foo()")).toEqual({
429
+ type: "Program",
430
+ body: [
431
+ {
432
+ type: "ExpressionStatement",
433
+ expression: {
434
+ type: "CallExpression",
435
+ callee: { type: "Identifier", name: "foo" },
436
+ arguments: [],
437
+ },
438
+ },
439
+ ],
440
+ });
441
+ });
442
+
443
+ it("should parse call expression arguments", () => {
444
+ expect(createAst("foo(bar, baz)")).toEqual({
445
+ type: "Program",
446
+ body: [
447
+ {
448
+ type: "ExpressionStatement",
449
+ expression: {
450
+ type: "CallExpression",
451
+ callee: { type: "Identifier", name: "foo" },
452
+ arguments: [
453
+ { type: "Identifier", name: "bar" },
454
+ { type: "Identifier", name: "baz" },
455
+ ],
456
+ },
457
+ },
458
+ ],
459
+ });
460
+ });
461
+
462
+ it("should parse call expression left-to-right", () => {
463
+ expect(createAst("foo(bar, baz)(man, shell)")).toEqual({
464
+ type: "Program",
465
+ body: [
466
+ {
467
+ type: "ExpressionStatement",
468
+ expression: {
469
+ type: "CallExpression",
470
+ callee: {
471
+ type: "CallExpression",
472
+ callee: { type: "Identifier", name: "foo" },
473
+ arguments: [
474
+ { type: "Identifier", name: "bar" },
475
+ { type: "Identifier", name: "baz" },
476
+ ],
477
+ },
478
+ arguments: [
479
+ { type: "Identifier", name: "man" },
480
+ { type: "Identifier", name: "shell" },
481
+ ],
482
+ },
483
+ },
484
+ ],
485
+ });
486
+ });
487
+
488
+ it("should keep the context when having superfluous parenthesis", () => {
489
+ expect(createAst("(foo)(bar, baz)")).toEqual({
490
+ type: "Program",
491
+ body: [
492
+ {
493
+ type: "ExpressionStatement",
494
+ expression: {
495
+ type: "CallExpression",
496
+ callee: { type: "Identifier", name: "foo" },
497
+ arguments: [
498
+ { type: "Identifier", name: "bar" },
499
+ { type: "Identifier", name: "baz" },
500
+ ],
501
+ },
502
+ },
503
+ ],
504
+ });
505
+ });
506
+
507
+ it("should treat member expressions and call expression with the same precedence", () => {
508
+ expect(createAst("foo.bar[baz]()")).toEqual({
509
+ type: "Program",
510
+ body: [
511
+ {
512
+ type: "ExpressionStatement",
513
+ expression: {
514
+ type: "CallExpression",
515
+ callee: {
516
+ type: "MemberExpression",
517
+ object: {
518
+ type: "MemberExpression",
519
+ object: { type: "Identifier", name: "foo" },
520
+ property: { type: "Identifier", name: "bar" },
521
+ computed: false,
522
+ },
523
+ property: { type: "Identifier", name: "baz" },
524
+ computed: true,
525
+ },
526
+ arguments: [],
527
+ },
528
+ },
529
+ ],
530
+ });
531
+ expect(createAst("foo[bar]().baz")).toEqual({
532
+ type: "Program",
533
+ body: [
534
+ {
535
+ type: "ExpressionStatement",
536
+ expression: {
537
+ type: "MemberExpression",
538
+ object: {
539
+ type: "CallExpression",
540
+ callee: {
541
+ type: "MemberExpression",
542
+ object: { type: "Identifier", name: "foo" },
543
+ property: { type: "Identifier", name: "bar" },
544
+ computed: true,
545
+ },
546
+ arguments: [],
547
+ },
548
+ property: { type: "Identifier", name: "baz" },
549
+ computed: false,
550
+ },
551
+ },
552
+ ],
553
+ });
554
+ expect(createAst("foo().bar[baz]")).toEqual({
555
+ type: "Program",
556
+ body: [
557
+ {
558
+ type: "ExpressionStatement",
559
+ expression: {
560
+ type: "MemberExpression",
561
+ object: {
562
+ type: "MemberExpression",
563
+ object: {
564
+ type: "CallExpression",
565
+ callee: { type: "Identifier", name: "foo" },
566
+ arguments: [],
567
+ },
568
+ property: { type: "Identifier", name: "bar" },
569
+ computed: false,
570
+ },
571
+ property: { type: "Identifier", name: "baz" },
572
+ computed: true,
573
+ },
574
+ },
575
+ ],
576
+ });
577
+ });
578
+
579
+ it("should understand literals", () => {
580
+ // In a strict sense, `undefined` is not a literal but an identifier
581
+ forEach(
582
+ {
583
+ 123: 123,
584
+ '"123"': "123",
585
+ true: true,
586
+ false: false,
587
+ null: null,
588
+ undefined: undefined,
589
+ },
590
+ (value, expression) => {
591
+ expect(createAst(expression)).toEqual({
592
+ type: "Program",
593
+ body: [
594
+ {
595
+ type: "ExpressionStatement",
596
+ expression: { type: "Literal", value },
597
+ },
598
+ ],
599
+ });
600
+ },
601
+ );
602
+ });
603
+
604
+ it("should understand the `this` expression", () => {
605
+ expect(createAst("this")).toEqual({
606
+ type: "Program",
607
+ body: [
608
+ {
609
+ type: "ExpressionStatement",
610
+ expression: { type: "ThisExpression" },
611
+ },
612
+ ],
613
+ });
614
+ });
615
+
616
+ it("should understand the `$locals` expression", () => {
617
+ expect(createAst("$locals")).toEqual({
618
+ type: "Program",
619
+ body: [
620
+ {
621
+ type: "ExpressionStatement",
622
+ expression: { type: "LocalsExpression" },
623
+ },
624
+ ],
625
+ });
626
+ });
627
+
628
+ it("should not confuse `this`, `$locals`, `undefined`, `true`, `false`, `null` when used as identifiers", () => {
629
+ forEach(
630
+ ["this", "$locals", "undefined", "true", "false", "null"],
631
+ (identifier) => {
632
+ expect(createAst(`foo.${identifier}`)).toEqual({
633
+ type: "Program",
634
+ body: [
635
+ {
636
+ type: "ExpressionStatement",
637
+ expression: {
638
+ type: "MemberExpression",
639
+ object: { type: "Identifier", name: "foo" },
640
+ property: { type: "Identifier", name: identifier },
641
+ computed: false,
642
+ },
643
+ },
644
+ ],
645
+ });
646
+ },
647
+ );
648
+ });
649
+
650
+ it("should throw when trying to use non-identifiers as identifiers", () => {
651
+ expect(() => {
652
+ createAst("foo.)");
653
+ }).toThrowError(/syntax/);
654
+ });
655
+
656
+ it("should throw when all tokens are not consumed", () => {
657
+ expect(() => {
658
+ createAst("foo bar");
659
+ }).toThrowError(/syntax/);
660
+ });
661
+
662
+ it("should understand the unary operators `-`, `+` and `!`", () => {
663
+ forEach(["-", "+", "!"], (operator) => {
664
+ expect(createAst(`${operator}foo`)).toEqual({
665
+ type: "Program",
666
+ body: [
667
+ {
668
+ type: "ExpressionStatement",
669
+ expression: {
670
+ type: "UnaryExpression",
671
+ operator,
672
+ prefix: true,
673
+ argument: { type: "Identifier", name: "foo" },
674
+ },
675
+ },
676
+ ],
677
+ });
678
+ });
679
+ });
680
+
681
+ it("should handle all unary operators with the same precedence", () => {
682
+ forEach(
683
+ [
684
+ ["+", "-", "!"],
685
+ ["-", "!", "+"],
686
+ ["!", "+", "-"],
687
+ ],
688
+ (operators) => {
689
+ expect(createAst(`${operators.join("")}foo`)).toEqual({
690
+ type: "Program",
691
+ body: [
692
+ {
693
+ type: "ExpressionStatement",
694
+ expression: {
695
+ type: "UnaryExpression",
696
+ operator: operators[0],
697
+ prefix: true,
698
+ argument: {
699
+ type: "UnaryExpression",
700
+ operator: operators[1],
701
+ prefix: true,
702
+ argument: {
703
+ type: "UnaryExpression",
704
+ operator: operators[2],
705
+ prefix: true,
706
+ argument: { type: "Identifier", name: "foo" },
707
+ },
708
+ },
709
+ },
710
+ },
711
+ ],
712
+ });
713
+ },
714
+ );
715
+ });
716
+
717
+ it("should be able to understand binary operators", () => {
718
+ forEach(
719
+ [
720
+ "*",
721
+ "/",
722
+ "%",
723
+ "+",
724
+ "-",
725
+ "<",
726
+ ">",
727
+ "<=",
728
+ ">=",
729
+ "==",
730
+ "!=",
731
+ "===",
732
+ "!==",
733
+ ],
734
+ (operator) => {
735
+ expect(createAst(`foo${operator}bar`)).toEqual({
736
+ type: "Program",
737
+ body: [
738
+ {
739
+ type: "ExpressionStatement",
740
+ expression: {
741
+ type: "BinaryExpression",
742
+ operator,
743
+ left: { type: "Identifier", name: "foo" },
744
+ right: { type: "Identifier", name: "bar" },
745
+ },
746
+ },
747
+ ],
748
+ });
749
+ },
750
+ );
751
+ });
752
+
753
+ it("should associate binary operators with the same precedence left-to-right", () => {
754
+ const operatorsByPrecedence = [
755
+ ["*", "/", "%"],
756
+ ["+", "-"],
757
+ ["<", ">", "<=", ">="],
758
+ ["==", "!=", "===", "!=="],
759
+ ];
760
+ forEach(operatorsByPrecedence, (operators) => {
761
+ forEach(operators, (op1) => {
762
+ forEach(operators, (op2) => {
763
+ expect(createAst(`foo${op1}bar${op2}baz`)).toEqual({
764
+ type: "Program",
765
+ body: [
766
+ {
767
+ type: "ExpressionStatement",
768
+ expression: {
769
+ type: "BinaryExpression",
770
+ operator: op2,
771
+ left: {
772
+ type: "BinaryExpression",
773
+ operator: op1,
774
+ left: { type: "Identifier", name: "foo" },
775
+ right: { type: "Identifier", name: "bar" },
776
+ },
777
+ right: { type: "Identifier", name: "baz" },
778
+ },
779
+ },
780
+ ],
781
+ });
782
+ });
783
+ });
784
+ });
785
+ });
786
+
787
+ it("should give higher precedence to member calls than to unary expressions", () => {
788
+ forEach(["!", "+", "-"], (operator) => {
789
+ expect(createAst(`${operator}foo()`)).toEqual({
790
+ type: "Program",
791
+ body: [
792
+ {
793
+ type: "ExpressionStatement",
794
+ expression: {
795
+ type: "UnaryExpression",
796
+ operator,
797
+ prefix: true,
798
+ argument: {
799
+ type: "CallExpression",
800
+ callee: { type: "Identifier", name: "foo" },
801
+ arguments: [],
802
+ },
803
+ },
804
+ },
805
+ ],
806
+ });
807
+ expect(createAst(`${operator}foo.bar`)).toEqual({
808
+ type: "Program",
809
+ body: [
810
+ {
811
+ type: "ExpressionStatement",
812
+ expression: {
813
+ type: "UnaryExpression",
814
+ operator,
815
+ prefix: true,
816
+ argument: {
817
+ type: "MemberExpression",
818
+ object: { type: "Identifier", name: "foo" },
819
+ property: { type: "Identifier", name: "bar" },
820
+ computed: false,
821
+ },
822
+ },
823
+ },
824
+ ],
825
+ });
826
+ expect(createAst(`${operator}foo[bar]`)).toEqual({
827
+ type: "Program",
828
+ body: [
829
+ {
830
+ type: "ExpressionStatement",
831
+ expression: {
832
+ type: "UnaryExpression",
833
+ operator,
834
+ prefix: true,
835
+ argument: {
836
+ type: "MemberExpression",
837
+ object: { type: "Identifier", name: "foo" },
838
+ property: { type: "Identifier", name: "bar" },
839
+ computed: true,
840
+ },
841
+ },
842
+ },
843
+ ],
844
+ });
845
+ });
846
+ });
847
+
848
+ it("should give higher precedence to unary operators over multiplicative operators", () => {
849
+ forEach(["!", "+", "-"], (op1) => {
850
+ forEach(["*", "/", "%"], (op2) => {
851
+ expect(createAst(`${op1}foo${op2}${op1}bar`)).toEqual({
852
+ type: "Program",
853
+ body: [
854
+ {
855
+ type: "ExpressionStatement",
856
+ expression: {
857
+ type: "BinaryExpression",
858
+ operator: op2,
859
+ left: {
860
+ type: "UnaryExpression",
861
+ operator: op1,
862
+ prefix: true,
863
+ argument: { type: "Identifier", name: "foo" },
864
+ },
865
+ right: {
866
+ type: "UnaryExpression",
867
+ operator: op1,
868
+ prefix: true,
869
+ argument: { type: "Identifier", name: "bar" },
870
+ },
871
+ },
872
+ },
873
+ ],
874
+ });
875
+ });
876
+ });
877
+ });
878
+
879
+ it("should give binary operators their right precedence", () => {
880
+ const operatorsByPrecedence = [
881
+ ["*", "/", "%"],
882
+ ["+", "-"],
883
+ ["<", ">", "<=", ">="],
884
+ ["==", "!=", "===", "!=="],
885
+ ];
886
+ for (let i = 0; i < operatorsByPrecedence.length - 1; ++i) {
887
+ forEach(operatorsByPrecedence[i], (op1) => {
888
+ forEach(operatorsByPrecedence[i + 1], (op2) => {
889
+ expect(createAst(`foo${op1}bar${op2}baz${op1}man`)).toEqual({
890
+ type: "Program",
891
+ body: [
892
+ {
893
+ type: "ExpressionStatement",
894
+ expression: {
895
+ type: "BinaryExpression",
896
+ operator: op2,
897
+ left: {
898
+ type: "BinaryExpression",
899
+ operator: op1,
900
+ left: { type: "Identifier", name: "foo" },
901
+ right: { type: "Identifier", name: "bar" },
902
+ },
903
+ right: {
904
+ type: "BinaryExpression",
905
+ operator: op1,
906
+ left: { type: "Identifier", name: "baz" },
907
+ right: { type: "Identifier", name: "man" },
908
+ },
909
+ },
910
+ },
911
+ ],
912
+ });
913
+ });
914
+ });
915
+ }
916
+ });
917
+
918
+ it("should understand logical operators", () => {
919
+ forEach(["||", "&&"], (operator) => {
920
+ expect(createAst(`foo${operator}bar`)).toEqual({
921
+ type: "Program",
922
+ body: [
923
+ {
924
+ type: "ExpressionStatement",
925
+ expression: {
926
+ type: "LogicalExpression",
927
+ operator,
928
+ left: { type: "Identifier", name: "foo" },
929
+ right: { type: "Identifier", name: "bar" },
930
+ },
931
+ },
932
+ ],
933
+ });
934
+ });
935
+ });
936
+
937
+ it("should associate logical operators left-to-right", () => {
938
+ forEach(["||", "&&"], (op) => {
939
+ expect(createAst(`foo${op}bar${op}baz`)).toEqual({
940
+ type: "Program",
941
+ body: [
942
+ {
943
+ type: "ExpressionStatement",
944
+ expression: {
945
+ type: "LogicalExpression",
946
+ operator: op,
947
+ left: {
948
+ type: "LogicalExpression",
949
+ operator: op,
950
+ left: { type: "Identifier", name: "foo" },
951
+ right: { type: "Identifier", name: "bar" },
952
+ },
953
+ right: { type: "Identifier", name: "baz" },
954
+ },
955
+ },
956
+ ],
957
+ });
958
+ });
959
+ });
960
+
961
+ it("should understand ternary operators", () => {
962
+ expect(createAst("foo?bar:baz")).toEqual({
963
+ type: "Program",
964
+ body: [
965
+ {
966
+ type: "ExpressionStatement",
967
+ expression: {
968
+ type: "ConditionalExpression",
969
+ test: { type: "Identifier", name: "foo" },
970
+ alternate: { type: "Identifier", name: "bar" },
971
+ consequent: { type: "Identifier", name: "baz" },
972
+ },
973
+ },
974
+ ],
975
+ });
976
+ });
977
+
978
+ it("should associate the conditional operator right-to-left", () => {
979
+ expect(createAst("foo0?foo1:foo2?bar0?bar1:bar2:man0?man1:man2")).toEqual(
980
+ {
981
+ type: "Program",
982
+ body: [
983
+ {
984
+ type: "ExpressionStatement",
985
+ expression: {
986
+ type: "ConditionalExpression",
987
+ test: { type: "Identifier", name: "foo0" },
988
+ alternate: { type: "Identifier", name: "foo1" },
989
+ consequent: {
990
+ type: "ConditionalExpression",
991
+ test: { type: "Identifier", name: "foo2" },
992
+ alternate: {
993
+ type: "ConditionalExpression",
994
+ test: { type: "Identifier", name: "bar0" },
995
+ alternate: { type: "Identifier", name: "bar1" },
996
+ consequent: { type: "Identifier", name: "bar2" },
997
+ },
998
+ consequent: {
999
+ type: "ConditionalExpression",
1000
+ test: { type: "Identifier", name: "man0" },
1001
+ alternate: { type: "Identifier", name: "man1" },
1002
+ consequent: { type: "Identifier", name: "man2" },
1003
+ },
1004
+ },
1005
+ },
1006
+ },
1007
+ ],
1008
+ },
1009
+ );
1010
+ });
1011
+
1012
+ it("should understand assignment operator", () => {
1013
+ // Currently, only `=` is supported
1014
+ expect(createAst("foo=bar")).toEqual({
1015
+ type: "Program",
1016
+ body: [
1017
+ {
1018
+ type: "ExpressionStatement",
1019
+ expression: {
1020
+ type: "AssignmentExpression",
1021
+ left: { type: "Identifier", name: "foo" },
1022
+ right: { type: "Identifier", name: "bar" },
1023
+ operator: "=",
1024
+ },
1025
+ },
1026
+ ],
1027
+ });
1028
+ });
1029
+
1030
+ it("should associate assignments right-to-left", () => {
1031
+ // Currently, only `=` is supported
1032
+ expect(createAst("foo=bar=man")).toEqual({
1033
+ type: "Program",
1034
+ body: [
1035
+ {
1036
+ type: "ExpressionStatement",
1037
+ expression: {
1038
+ type: "AssignmentExpression",
1039
+ left: { type: "Identifier", name: "foo" },
1040
+ right: {
1041
+ type: "AssignmentExpression",
1042
+ left: { type: "Identifier", name: "bar" },
1043
+ right: { type: "Identifier", name: "man" },
1044
+ operator: "=",
1045
+ },
1046
+ operator: "=",
1047
+ },
1048
+ },
1049
+ ],
1050
+ });
1051
+ });
1052
+
1053
+ it("should give higher precedence to equality than to the logical `and` operator", () => {
1054
+ forEach(["==", "!=", "===", "!=="], (operator) => {
1055
+ expect(createAst(`foo${operator}bar && man${operator}shell`)).toEqual({
1056
+ type: "Program",
1057
+ body: [
1058
+ {
1059
+ type: "ExpressionStatement",
1060
+ expression: {
1061
+ type: "LogicalExpression",
1062
+ operator: "&&",
1063
+ left: {
1064
+ type: "BinaryExpression",
1065
+ operator,
1066
+ left: { type: "Identifier", name: "foo" },
1067
+ right: { type: "Identifier", name: "bar" },
1068
+ },
1069
+ right: {
1070
+ type: "BinaryExpression",
1071
+ operator,
1072
+ left: { type: "Identifier", name: "man" },
1073
+ right: { type: "Identifier", name: "shell" },
1074
+ },
1075
+ },
1076
+ },
1077
+ ],
1078
+ });
1079
+ });
1080
+ });
1081
+
1082
+ it("should give higher precedence to logical `and` than to logical `or`", () => {
1083
+ expect(createAst("foo&&bar||man&&shell")).toEqual({
1084
+ type: "Program",
1085
+ body: [
1086
+ {
1087
+ type: "ExpressionStatement",
1088
+ expression: {
1089
+ type: "LogicalExpression",
1090
+ operator: "||",
1091
+ left: {
1092
+ type: "LogicalExpression",
1093
+ operator: "&&",
1094
+ left: { type: "Identifier", name: "foo" },
1095
+ right: { type: "Identifier", name: "bar" },
1096
+ },
1097
+ right: {
1098
+ type: "LogicalExpression",
1099
+ operator: "&&",
1100
+ left: { type: "Identifier", name: "man" },
1101
+ right: { type: "Identifier", name: "shell" },
1102
+ },
1103
+ },
1104
+ },
1105
+ ],
1106
+ });
1107
+ });
1108
+
1109
+ it("should give higher precedence to the logical `or` than to the conditional operator", () => {
1110
+ expect(createAst("foo||bar?man:shell")).toEqual({
1111
+ type: "Program",
1112
+ body: [
1113
+ {
1114
+ type: "ExpressionStatement",
1115
+ expression: {
1116
+ type: "ConditionalExpression",
1117
+ test: {
1118
+ type: "LogicalExpression",
1119
+ operator: "||",
1120
+ left: { type: "Identifier", name: "foo" },
1121
+ right: { type: "Identifier", name: "bar" },
1122
+ },
1123
+ alternate: { type: "Identifier", name: "man" },
1124
+ consequent: { type: "Identifier", name: "shell" },
1125
+ },
1126
+ },
1127
+ ],
1128
+ });
1129
+ });
1130
+
1131
+ it("should give higher precedence to the conditional operator than to assignment operators", () => {
1132
+ expect(createAst("foo=bar?man:shell")).toEqual({
1133
+ type: "Program",
1134
+ body: [
1135
+ {
1136
+ type: "ExpressionStatement",
1137
+ expression: {
1138
+ type: "AssignmentExpression",
1139
+ left: { type: "Identifier", name: "foo" },
1140
+ right: {
1141
+ type: "ConditionalExpression",
1142
+ test: { type: "Identifier", name: "bar" },
1143
+ alternate: { type: "Identifier", name: "man" },
1144
+ consequent: { type: "Identifier", name: "shell" },
1145
+ },
1146
+ operator: "=",
1147
+ },
1148
+ },
1149
+ ],
1150
+ });
1151
+ });
1152
+
1153
+ it("should understand array literals", () => {
1154
+ expect(createAst("[]")).toEqual({
1155
+ type: "Program",
1156
+ body: [
1157
+ {
1158
+ type: "ExpressionStatement",
1159
+ expression: {
1160
+ type: "ArrayExpression",
1161
+ elements: [],
1162
+ },
1163
+ },
1164
+ ],
1165
+ });
1166
+ expect(createAst("[foo]")).toEqual({
1167
+ type: "Program",
1168
+ body: [
1169
+ {
1170
+ type: "ExpressionStatement",
1171
+ expression: {
1172
+ type: "ArrayExpression",
1173
+ elements: [{ type: "Identifier", name: "foo" }],
1174
+ },
1175
+ },
1176
+ ],
1177
+ });
1178
+ expect(createAst("[foo,]")).toEqual({
1179
+ type: "Program",
1180
+ body: [
1181
+ {
1182
+ type: "ExpressionStatement",
1183
+ expression: {
1184
+ type: "ArrayExpression",
1185
+ elements: [{ type: "Identifier", name: "foo" }],
1186
+ },
1187
+ },
1188
+ ],
1189
+ });
1190
+ expect(createAst("[foo,bar,man,shell]")).toEqual({
1191
+ type: "Program",
1192
+ body: [
1193
+ {
1194
+ type: "ExpressionStatement",
1195
+ expression: {
1196
+ type: "ArrayExpression",
1197
+ elements: [
1198
+ { type: "Identifier", name: "foo" },
1199
+ { type: "Identifier", name: "bar" },
1200
+ { type: "Identifier", name: "man" },
1201
+ { type: "Identifier", name: "shell" },
1202
+ ],
1203
+ },
1204
+ },
1205
+ ],
1206
+ });
1207
+ expect(createAst("[foo,bar,man,shell,]")).toEqual({
1208
+ type: "Program",
1209
+ body: [
1210
+ {
1211
+ type: "ExpressionStatement",
1212
+ expression: {
1213
+ type: "ArrayExpression",
1214
+ elements: [
1215
+ { type: "Identifier", name: "foo" },
1216
+ { type: "Identifier", name: "bar" },
1217
+ { type: "Identifier", name: "man" },
1218
+ { type: "Identifier", name: "shell" },
1219
+ ],
1220
+ },
1221
+ },
1222
+ ],
1223
+ });
1224
+ });
1225
+
1226
+ it("should understand objects", () => {
1227
+ expect(createAst("{}")).toEqual({
1228
+ type: "Program",
1229
+ body: [
1230
+ {
1231
+ type: "ExpressionStatement",
1232
+ expression: {
1233
+ type: "ObjectExpression",
1234
+ properties: [],
1235
+ },
1236
+ },
1237
+ ],
1238
+ });
1239
+ expect(createAst("{foo: bar}")).toEqual({
1240
+ type: "Program",
1241
+ body: [
1242
+ {
1243
+ type: "ExpressionStatement",
1244
+ expression: {
1245
+ type: "ObjectExpression",
1246
+ properties: [
1247
+ {
1248
+ type: "Property",
1249
+ kind: "init",
1250
+ key: { type: "Identifier", name: "foo" },
1251
+ computed: false,
1252
+ value: { type: "Identifier", name: "bar" },
1253
+ },
1254
+ ],
1255
+ },
1256
+ },
1257
+ ],
1258
+ });
1259
+ expect(createAst("{foo: bar,}")).toEqual({
1260
+ type: "Program",
1261
+ body: [
1262
+ {
1263
+ type: "ExpressionStatement",
1264
+ expression: {
1265
+ type: "ObjectExpression",
1266
+ properties: [
1267
+ {
1268
+ type: "Property",
1269
+ kind: "init",
1270
+ key: { type: "Identifier", name: "foo" },
1271
+ computed: false,
1272
+ value: { type: "Identifier", name: "bar" },
1273
+ },
1274
+ ],
1275
+ },
1276
+ },
1277
+ ],
1278
+ });
1279
+ expect(createAst('{foo: bar, "man": "shell", 42: 23}')).toEqual({
1280
+ type: "Program",
1281
+ body: [
1282
+ {
1283
+ type: "ExpressionStatement",
1284
+ expression: {
1285
+ type: "ObjectExpression",
1286
+ properties: [
1287
+ {
1288
+ type: "Property",
1289
+ kind: "init",
1290
+ key: { type: "Identifier", name: "foo" },
1291
+ computed: false,
1292
+ value: { type: "Identifier", name: "bar" },
1293
+ },
1294
+ {
1295
+ type: "Property",
1296
+ kind: "init",
1297
+ key: { type: "Literal", value: "man" },
1298
+ computed: false,
1299
+ value: { type: "Literal", value: "shell" },
1300
+ },
1301
+ {
1302
+ type: "Property",
1303
+ kind: "init",
1304
+ key: { type: "Literal", value: 42 },
1305
+ computed: false,
1306
+ value: { type: "Literal", value: 23 },
1307
+ },
1308
+ ],
1309
+ },
1310
+ },
1311
+ ],
1312
+ });
1313
+ expect(createAst('{foo: bar, "man": "shell", 42: 23,}')).toEqual({
1314
+ type: "Program",
1315
+ body: [
1316
+ {
1317
+ type: "ExpressionStatement",
1318
+ expression: {
1319
+ type: "ObjectExpression",
1320
+ properties: [
1321
+ {
1322
+ type: "Property",
1323
+ kind: "init",
1324
+ key: { type: "Identifier", name: "foo" },
1325
+ computed: false,
1326
+ value: { type: "Identifier", name: "bar" },
1327
+ },
1328
+ {
1329
+ type: "Property",
1330
+ kind: "init",
1331
+ key: { type: "Literal", value: "man" },
1332
+ computed: false,
1333
+ value: { type: "Literal", value: "shell" },
1334
+ },
1335
+ {
1336
+ type: "Property",
1337
+ kind: "init",
1338
+ key: { type: "Literal", value: 42 },
1339
+ computed: false,
1340
+ value: { type: "Literal", value: 23 },
1341
+ },
1342
+ ],
1343
+ },
1344
+ },
1345
+ ],
1346
+ });
1347
+ });
1348
+
1349
+ it("should understand ES6 object initializer", () => {
1350
+ // Shorthand properties definitions.
1351
+ expect(createAst("{x, y, z}")).toEqual({
1352
+ type: "Program",
1353
+ body: [
1354
+ {
1355
+ type: "ExpressionStatement",
1356
+ expression: {
1357
+ type: "ObjectExpression",
1358
+ properties: [
1359
+ {
1360
+ type: "Property",
1361
+ kind: "init",
1362
+ key: { type: "Identifier", name: "x" },
1363
+ computed: false,
1364
+ value: { type: "Identifier", name: "x" },
1365
+ },
1366
+ {
1367
+ type: "Property",
1368
+ kind: "init",
1369
+ key: { type: "Identifier", name: "y" },
1370
+ computed: false,
1371
+ value: { type: "Identifier", name: "y" },
1372
+ },
1373
+ {
1374
+ type: "Property",
1375
+ kind: "init",
1376
+ key: { type: "Identifier", name: "z" },
1377
+ computed: false,
1378
+ value: { type: "Identifier", name: "z" },
1379
+ },
1380
+ ],
1381
+ },
1382
+ },
1383
+ ],
1384
+ });
1385
+ expect(() => {
1386
+ createAst('{"foo"}');
1387
+ }).toThrow();
1388
+
1389
+ // Computed properties
1390
+ expect(createAst("{[x]: x}")).toEqual({
1391
+ type: "Program",
1392
+ body: [
1393
+ {
1394
+ type: "ExpressionStatement",
1395
+ expression: {
1396
+ type: "ObjectExpression",
1397
+ properties: [
1398
+ {
1399
+ type: "Property",
1400
+ kind: "init",
1401
+ key: { type: "Identifier", name: "x" },
1402
+ computed: true,
1403
+ value: { type: "Identifier", name: "x" },
1404
+ },
1405
+ ],
1406
+ },
1407
+ },
1408
+ ],
1409
+ });
1410
+ expect(createAst("{[x + 1]: x}")).toEqual({
1411
+ type: "Program",
1412
+ body: [
1413
+ {
1414
+ type: "ExpressionStatement",
1415
+ expression: {
1416
+ type: "ObjectExpression",
1417
+ properties: [
1418
+ {
1419
+ type: "Property",
1420
+ kind: "init",
1421
+ key: {
1422
+ type: "BinaryExpression",
1423
+ operator: "+",
1424
+ left: { type: "Identifier", name: "x" },
1425
+ right: { type: "Literal", value: 1 },
1426
+ },
1427
+ computed: true,
1428
+ value: { type: "Identifier", name: "x" },
1429
+ },
1430
+ ],
1431
+ },
1432
+ },
1433
+ ],
1434
+ });
1435
+ });
1436
+
1437
+ it("should understand multiple expressions", () => {
1438
+ expect(createAst("foo = bar; man = shell")).toEqual({
1439
+ type: "Program",
1440
+ body: [
1441
+ {
1442
+ type: "ExpressionStatement",
1443
+ expression: {
1444
+ type: "AssignmentExpression",
1445
+ left: { type: "Identifier", name: "foo" },
1446
+ right: { type: "Identifier", name: "bar" },
1447
+ operator: "=",
1448
+ },
1449
+ },
1450
+ {
1451
+ type: "ExpressionStatement",
1452
+ expression: {
1453
+ type: "AssignmentExpression",
1454
+ left: { type: "Identifier", name: "man" },
1455
+ right: { type: "Identifier", name: "shell" },
1456
+ operator: "=",
1457
+ },
1458
+ },
1459
+ ],
1460
+ });
1461
+ });
1462
+
1463
+ // This is non-standard syntax
1464
+ it("should understand filters", () => {
1465
+ expect(createAst("foo | bar")).toEqual({
1466
+ type: "Program",
1467
+ body: [
1468
+ {
1469
+ type: "ExpressionStatement",
1470
+ expression: {
1471
+ type: "CallExpression",
1472
+ callee: { type: "Identifier", name: "bar" },
1473
+ arguments: [{ type: "Identifier", name: "foo" }],
1474
+ filter: true,
1475
+ },
1476
+ },
1477
+ ],
1478
+ });
1479
+ });
1480
+
1481
+ it("should understand filters with extra parameters", () => {
1482
+ expect(createAst("foo | bar:baz")).toEqual({
1483
+ type: "Program",
1484
+ body: [
1485
+ {
1486
+ type: "ExpressionStatement",
1487
+ expression: {
1488
+ type: "CallExpression",
1489
+ callee: { type: "Identifier", name: "bar" },
1490
+ arguments: [
1491
+ { type: "Identifier", name: "foo" },
1492
+ { type: "Identifier", name: "baz" },
1493
+ ],
1494
+ filter: true,
1495
+ },
1496
+ },
1497
+ ],
1498
+ });
1499
+ });
1500
+
1501
+ it("should associate filters right-to-left", () => {
1502
+ expect(createAst("foo | bar:man | shell")).toEqual({
1503
+ type: "Program",
1504
+ body: [
1505
+ {
1506
+ type: "ExpressionStatement",
1507
+ expression: {
1508
+ type: "CallExpression",
1509
+ callee: { type: "Identifier", name: "shell" },
1510
+ arguments: [
1511
+ {
1512
+ type: "CallExpression",
1513
+ callee: { type: "Identifier", name: "bar" },
1514
+ arguments: [
1515
+ { type: "Identifier", name: "foo" },
1516
+ { type: "Identifier", name: "man" },
1517
+ ],
1518
+ filter: true,
1519
+ },
1520
+ ],
1521
+ filter: true,
1522
+ },
1523
+ },
1524
+ ],
1525
+ });
1526
+ });
1527
+
1528
+ it("should give higher precedence to assignments over filters", () => {
1529
+ expect(createAst("foo=bar | man")).toEqual({
1530
+ type: "Program",
1531
+ body: [
1532
+ {
1533
+ type: "ExpressionStatement",
1534
+ expression: {
1535
+ type: "CallExpression",
1536
+ callee: { type: "Identifier", name: "man" },
1537
+ arguments: [
1538
+ {
1539
+ type: "AssignmentExpression",
1540
+ left: { type: "Identifier", name: "foo" },
1541
+ right: { type: "Identifier", name: "bar" },
1542
+ operator: "=",
1543
+ },
1544
+ ],
1545
+ filter: true,
1546
+ },
1547
+ },
1548
+ ],
1549
+ });
1550
+ });
1551
+
1552
+ it("should accept expression as filters parameters", () => {
1553
+ expect(createAst("foo | bar:baz=man")).toEqual({
1554
+ type: "Program",
1555
+ body: [
1556
+ {
1557
+ type: "ExpressionStatement",
1558
+ expression: {
1559
+ type: "CallExpression",
1560
+ callee: { type: "Identifier", name: "bar" },
1561
+ arguments: [
1562
+ { type: "Identifier", name: "foo" },
1563
+ {
1564
+ type: "AssignmentExpression",
1565
+ left: { type: "Identifier", name: "baz" },
1566
+ right: { type: "Identifier", name: "man" },
1567
+ operator: "=",
1568
+ },
1569
+ ],
1570
+ filter: true,
1571
+ },
1572
+ },
1573
+ ],
1574
+ });
1575
+ });
1576
+
1577
+ it("should accept expression as computer members", () => {
1578
+ expect(createAst("foo[a = 1]")).toEqual({
1579
+ type: "Program",
1580
+ body: [
1581
+ {
1582
+ type: "ExpressionStatement",
1583
+ expression: {
1584
+ type: "MemberExpression",
1585
+ object: { type: "Identifier", name: "foo" },
1586
+ property: {
1587
+ type: "AssignmentExpression",
1588
+ left: { type: "Identifier", name: "a" },
1589
+ right: { type: "Literal", value: 1 },
1590
+ operator: "=",
1591
+ },
1592
+ computed: true,
1593
+ },
1594
+ },
1595
+ ],
1596
+ });
1597
+ });
1598
+
1599
+ it("should accept expression in function arguments", () => {
1600
+ expect(createAst("foo(a = 1)")).toEqual({
1601
+ type: "Program",
1602
+ body: [
1603
+ {
1604
+ type: "ExpressionStatement",
1605
+ expression: {
1606
+ type: "CallExpression",
1607
+ callee: { type: "Identifier", name: "foo" },
1608
+ arguments: [
1609
+ {
1610
+ type: "AssignmentExpression",
1611
+ left: { type: "Identifier", name: "a" },
1612
+ right: { type: "Literal", value: 1 },
1613
+ operator: "=",
1614
+ },
1615
+ ],
1616
+ },
1617
+ },
1618
+ ],
1619
+ });
1620
+ });
1621
+
1622
+ it("should accept expression as part of ternary operators", () => {
1623
+ expect(createAst("foo || bar ? man = 1 : shell = 1")).toEqual({
1624
+ type: "Program",
1625
+ body: [
1626
+ {
1627
+ type: "ExpressionStatement",
1628
+ expression: {
1629
+ type: "ConditionalExpression",
1630
+ test: {
1631
+ type: "LogicalExpression",
1632
+ operator: "||",
1633
+ left: { type: "Identifier", name: "foo" },
1634
+ right: { type: "Identifier", name: "bar" },
1635
+ },
1636
+ alternate: {
1637
+ type: "AssignmentExpression",
1638
+ left: { type: "Identifier", name: "man" },
1639
+ right: { type: "Literal", value: 1 },
1640
+ operator: "=",
1641
+ },
1642
+ consequent: {
1643
+ type: "AssignmentExpression",
1644
+ left: { type: "Identifier", name: "shell" },
1645
+ right: { type: "Literal", value: 1 },
1646
+ operator: "=",
1647
+ },
1648
+ },
1649
+ },
1650
+ ],
1651
+ });
1652
+ });
1653
+
1654
+ it("should accept expression as part of array literals", () => {
1655
+ expect(createAst("[foo = 1]")).toEqual({
1656
+ type: "Program",
1657
+ body: [
1658
+ {
1659
+ type: "ExpressionStatement",
1660
+ expression: {
1661
+ type: "ArrayExpression",
1662
+ elements: [
1663
+ {
1664
+ type: "AssignmentExpression",
1665
+ left: { type: "Identifier", name: "foo" },
1666
+ right: { type: "Literal", value: 1 },
1667
+ operator: "=",
1668
+ },
1669
+ ],
1670
+ },
1671
+ },
1672
+ ],
1673
+ });
1674
+ });
1675
+
1676
+ it("should accept expression as part of object literals", () => {
1677
+ expect(createAst("{foo: bar = 1}")).toEqual({
1678
+ type: "Program",
1679
+ body: [
1680
+ {
1681
+ type: "ExpressionStatement",
1682
+ expression: {
1683
+ type: "ObjectExpression",
1684
+ properties: [
1685
+ {
1686
+ type: "Property",
1687
+ kind: "init",
1688
+ key: { type: "Identifier", name: "foo" },
1689
+ computed: false,
1690
+ value: {
1691
+ type: "AssignmentExpression",
1692
+ left: { type: "Identifier", name: "bar" },
1693
+ right: { type: "Literal", value: 1 },
1694
+ operator: "=",
1695
+ },
1696
+ },
1697
+ ],
1698
+ },
1699
+ },
1700
+ ],
1701
+ });
1702
+ });
1703
+
1704
+ it("should be possible to use parenthesis to indicate precedence", () => {
1705
+ expect(createAst("(foo + bar).man")).toEqual({
1706
+ type: "Program",
1707
+ body: [
1708
+ {
1709
+ type: "ExpressionStatement",
1710
+ expression: {
1711
+ type: "MemberExpression",
1712
+ object: {
1713
+ type: "BinaryExpression",
1714
+ operator: "+",
1715
+ left: { type: "Identifier", name: "foo" },
1716
+ right: { type: "Identifier", name: "bar" },
1717
+ },
1718
+ property: { type: "Identifier", name: "man" },
1719
+ computed: false,
1720
+ },
1721
+ },
1722
+ ],
1723
+ });
1724
+ });
1725
+
1726
+ it("should skip empty expressions", () => {
1727
+ expect(createAst("foo;;;;bar")).toEqual({
1728
+ type: "Program",
1729
+ body: [
1730
+ {
1731
+ type: "ExpressionStatement",
1732
+ expression: { type: "Identifier", name: "foo" },
1733
+ },
1734
+ {
1735
+ type: "ExpressionStatement",
1736
+ expression: { type: "Identifier", name: "bar" },
1737
+ },
1738
+ ],
1739
+ });
1740
+ expect(createAst(";foo")).toEqual({
1741
+ type: "Program",
1742
+ body: [
1743
+ {
1744
+ type: "ExpressionStatement",
1745
+ expression: { type: "Identifier", name: "foo" },
1746
+ },
1747
+ ],
1748
+ });
1749
+ expect(createAst("foo;")).toEqual({
1750
+ type: "Program",
1751
+ body: [
1752
+ {
1753
+ type: "ExpressionStatement",
1754
+ expression: { type: "Identifier", name: "foo" },
1755
+ },
1756
+ ],
1757
+ });
1758
+ expect(createAst(";;;;")).toEqual({ type: "Program", body: [] });
1759
+ expect(createAst("")).toEqual({ type: "Program", body: [] });
1760
+ });
1761
+ });
1762
+
1763
+ let filterProvider;
1764
+
1765
+ forEach([true, false], (cspEnabled) => {
1766
+ beforeEach(() => {
1767
+ createInjector([
1768
+ "ng",
1769
+ function ($filterProvider, $parseProvider) {
1770
+ filterProvider = $filterProvider;
1771
+ $parseProvider.addLiteral("Infinity", Infinity);
1772
+ csp().noUnsafeEval = cspEnabled;
1773
+ },
1774
+ ]).invoke((_$rootScope_) => {
1775
+ $rootScope = _$rootScope_;
1776
+ });
1777
+ });
1778
+
1779
+ it(`should allow extending literals with csp ${cspEnabled}`, () => {
1780
+ expect($rootScope.$eval("Infinity")).toEqual(Infinity);
1781
+ expect($rootScope.$eval("-Infinity")).toEqual(-Infinity);
1782
+ expect(() => {
1783
+ $rootScope.$eval("Infinity = 1");
1784
+ }).toThrow();
1785
+ expect($rootScope.$eval("Infinity")).toEqual(Infinity);
1786
+ });
1787
+ });
1788
+
1789
+ forEach([true, false], (cspEnabled) => {
1790
+ describe(`csp: ${cspEnabled}`, () => {
1791
+ beforeEach(() => {
1792
+ createInjector([
1793
+ "ng",
1794
+ function ($filterProvider) {
1795
+ filterProvider = $filterProvider;
1796
+ csp().noUnsafeEval = cspEnabled;
1797
+ },
1798
+ ]).invoke((_$rootScope_) => {
1799
+ scope = _$rootScope_;
1800
+ });
1801
+ });
1802
+
1803
+ it("should parse expressions", () => {
1804
+ expect(scope.$eval("-1")).toEqual(-1);
1805
+ expect(scope.$eval("1 + 2.5")).toEqual(3.5);
1806
+ expect(scope.$eval("1 + -2.5")).toEqual(-1.5);
1807
+ expect(scope.$eval("1+2*3/4")).toEqual(1 + (2 * 3) / 4);
1808
+ expect(scope.$eval("0--1+1.5")).toEqual(0 - -1 + 1.5);
1809
+ expect(scope.$eval("-0--1++2*-3/-4")).toEqual(-0 - -1 + (+2 * -3) / -4);
1810
+ expect(scope.$eval("1/2*3")).toEqual((1 / 2) * 3);
1811
+ });
1812
+
1813
+ it("should parse unary", () => {
1814
+ expect(scope.$eval("+1")).toEqual(+1);
1815
+ expect(scope.$eval("-1")).toEqual(-1);
1816
+ expect(scope.$eval("+'1'")).toEqual(+"1");
1817
+ expect(scope.$eval("-'1'")).toEqual(-"1");
1818
+ expect(scope.$eval("+undefined")).toEqual(0);
1819
+
1820
+ // Note: don't change toEqual to toBe as toBe collapses 0 & -0.
1821
+ expect(scope.$eval("-undefined")).toEqual(-0);
1822
+ expect(scope.$eval("+null")).toEqual(+null);
1823
+ expect(scope.$eval("-null")).toEqual(-null);
1824
+ expect(scope.$eval("+false")).toEqual(+false);
1825
+ expect(scope.$eval("-false")).toEqual(-false);
1826
+ expect(scope.$eval("+true")).toEqual(+true);
1827
+ expect(scope.$eval("-true")).toEqual(-true);
1828
+ });
1829
+
1830
+ it("should parse comparison", () => {
1831
+ /* eslint-disable eqeqeq, no-self-compare */
1832
+ expect(scope.$eval("false")).toBeFalsy();
1833
+ expect(scope.$eval("!true")).toBeFalsy();
1834
+ expect(scope.$eval("1==1")).toBeTruthy();
1835
+ expect(scope.$eval("1==true")).toBeTruthy();
1836
+ expect(scope.$eval("1!=true")).toBeFalsy();
1837
+ expect(scope.$eval("1===1")).toBeTruthy();
1838
+ expect(scope.$eval("1==='1'")).toBeFalsy();
1839
+ expect(scope.$eval("1===true")).toBeFalsy();
1840
+ expect(scope.$eval("'true'===true")).toBeFalsy();
1841
+ expect(scope.$eval("1!==2")).toBeTruthy();
1842
+ expect(scope.$eval("1!=='1'")).toBeTruthy();
1843
+ expect(scope.$eval("1!=2")).toBeTruthy();
1844
+ expect(scope.$eval("1<2")).toBeTruthy();
1845
+ expect(scope.$eval("1<=1")).toBeTruthy();
1846
+ expect(scope.$eval("1>2")).toEqual(1 > 2);
1847
+ expect(scope.$eval("2>=1")).toEqual(2 >= 1);
1848
+ expect(scope.$eval("true==2<3")).toEqual(2 < 3 == true);
1849
+ expect(scope.$eval("true===2<3")).toEqual(2 < 3 === true);
1850
+
1851
+ expect(scope.$eval("true===3===3")).toEqual((true === 3) === 3);
1852
+ expect(scope.$eval("3===3===true")).toEqual((3 === 3) === true);
1853
+ expect(scope.$eval("3 >= 3 > 2")).toEqual(3 >= 3 > 2);
1854
+ /* eslint-enable */
1855
+ });
1856
+
1857
+ it("should parse logical", () => {
1858
+ expect(scope.$eval("0&&2")).toEqual(0 && 2);
1859
+ expect(scope.$eval("0||2")).toEqual(0 || 2);
1860
+ expect(scope.$eval("0||1&&2")).toEqual(0 || (1 && 2));
1861
+ expect(scope.$eval("true&&a")).toEqual(true && undefined);
1862
+ expect(scope.$eval("true&&a()")).toEqual(true && undefined);
1863
+ expect(scope.$eval("true&&a()()")).toEqual(true && undefined);
1864
+ expect(scope.$eval("true&&a.b")).toEqual(true && undefined);
1865
+ expect(scope.$eval("true&&a.b.c")).toEqual(true && undefined);
1866
+ expect(scope.$eval("false||a")).toEqual(false || undefined);
1867
+ expect(scope.$eval("false||a()")).toEqual(false || undefined);
1868
+ expect(scope.$eval("false||a()()")).toEqual(false || undefined);
1869
+ expect(scope.$eval("false||a.b")).toEqual(false || undefined);
1870
+ expect(scope.$eval("false||a.b.c")).toEqual(false || undefined);
1871
+ });
1872
+
1873
+ it("should parse ternary", () => {
1874
+ const returnTrue = (scope.returnTrue = function () {
1875
+ return true;
1876
+ });
1877
+ const returnFalse = (scope.returnFalse = function () {
1878
+ return false;
1879
+ });
1880
+ const returnString = (scope.returnString = function () {
1881
+ return "asd";
1882
+ });
1883
+ const returnInt = (scope.returnInt = function () {
1884
+ return 123;
1885
+ });
1886
+ const identity = (scope.identity = function (x) {
1887
+ return x;
1888
+ });
1889
+
1890
+ // Simple.
1891
+ expect(scope.$eval("0?0:2")).toEqual(0 ? 0 : 2);
1892
+ expect(scope.$eval("1?0:2")).toEqual(1 ? 0 : 2);
1893
+
1894
+ // Nested on the left.
1895
+ expect(scope.$eval("0?0?0:0:2")).toEqual(0 ? (0 ? 0 : 0) : 2);
1896
+ expect(scope.$eval("1?0?0:0:2")).toEqual(1 ? (0 ? 0 : 0) : 2);
1897
+ expect(scope.$eval("0?1?0:0:2")).toEqual(0 ? (1 ? 0 : 0) : 2);
1898
+ expect(scope.$eval("0?0?1:0:2")).toEqual(0 ? (0 ? 1 : 0) : 2);
1899
+ expect(scope.$eval("0?0?0:2:3")).toEqual(0 ? (0 ? 0 : 2) : 3);
1900
+ expect(scope.$eval("1?1?0:0:2")).toEqual(1 ? (1 ? 0 : 0) : 2);
1901
+ expect(scope.$eval("1?1?1:0:2")).toEqual(1 ? (1 ? 1 : 0) : 2);
1902
+ expect(scope.$eval("1?1?1:2:3")).toEqual(1 ? (1 ? 1 : 2) : 3);
1903
+ expect(scope.$eval("1?1?1:2:3")).toEqual(1 ? (1 ? 1 : 2) : 3);
1904
+
1905
+ // Nested on the right.
1906
+ expect(scope.$eval("0?0:0?0:2")).toEqual(0 ? 0 : 0 ? 0 : 2);
1907
+ expect(scope.$eval("1?0:0?0:2")).toEqual(1 ? 0 : 0 ? 0 : 2);
1908
+ expect(scope.$eval("0?1:0?0:2")).toEqual(0 ? 1 : 0 ? 0 : 2);
1909
+ expect(scope.$eval("0?0:1?0:2")).toEqual(0 ? 0 : 1 ? 0 : 2);
1910
+ expect(scope.$eval("0?0:0?2:3")).toEqual(0 ? 0 : 0 ? 2 : 3);
1911
+ expect(scope.$eval("1?1:0?0:2")).toEqual(1 ? 1 : 0 ? 0 : 2);
1912
+ expect(scope.$eval("1?1:1?0:2")).toEqual(1 ? 1 : 1 ? 0 : 2);
1913
+ expect(scope.$eval("1?1:1?2:3")).toEqual(1 ? 1 : 1 ? 2 : 3);
1914
+ expect(scope.$eval("1?1:1?2:3")).toEqual(1 ? 1 : 1 ? 2 : 3);
1915
+
1916
+ // Precedence with respect to logical operators.
1917
+ expect(scope.$eval("0&&1?0:1")).toEqual(0 && 1 ? 0 : 1);
1918
+ expect(scope.$eval("1||0?0:0")).toEqual(1 || 0 ? 0 : 0);
1919
+
1920
+ expect(scope.$eval("0?0&&1:2")).toEqual(0 ? 0 && 1 : 2);
1921
+ expect(scope.$eval("0?1&&1:2")).toEqual(0 ? 1 && 1 : 2);
1922
+ expect(scope.$eval("0?0||0:1")).toEqual(0 ? 0 || 0 : 1);
1923
+ expect(scope.$eval("0?0||1:2")).toEqual(0 ? 0 || 1 : 2);
1924
+
1925
+ expect(scope.$eval("1?0&&1:2")).toEqual(1 ? 0 && 1 : 2);
1926
+ expect(scope.$eval("1?1&&1:2")).toEqual(1 ? 1 && 1 : 2);
1927
+ expect(scope.$eval("1?0||0:1")).toEqual(1 ? 0 || 0 : 1);
1928
+ expect(scope.$eval("1?0||1:2")).toEqual(1 ? 0 || 1 : 2);
1929
+
1930
+ expect(scope.$eval("0?1:0&&1")).toEqual(0 ? 1 : 0 && 1);
1931
+ expect(scope.$eval("0?2:1&&1")).toEqual(0 ? 2 : 1 && 1);
1932
+ expect(scope.$eval("0?1:0||0")).toEqual(0 ? 1 : 0 || 0);
1933
+ expect(scope.$eval("0?2:0||1")).toEqual(0 ? 2 : 0 || 1);
1934
+
1935
+ expect(scope.$eval("1?1:0&&1")).toEqual(1 ? 1 : 0 && 1);
1936
+ expect(scope.$eval("1?2:1&&1")).toEqual(1 ? 2 : 1 && 1);
1937
+ expect(scope.$eval("1?1:0||0")).toEqual(1 ? 1 : 0 || 0);
1938
+ expect(scope.$eval("1?2:0||1")).toEqual(1 ? 2 : 0 || 1);
1939
+
1940
+ // Function calls.
1941
+ expect(
1942
+ scope.$eval("returnTrue() ? returnString() : returnInt()"),
1943
+ ).toEqual(returnTrue() ? returnString() : returnInt());
1944
+ expect(
1945
+ scope.$eval("returnFalse() ? returnString() : returnInt()"),
1946
+ ).toEqual(returnFalse() ? returnString() : returnInt());
1947
+ expect(
1948
+ scope.$eval("returnTrue() ? returnString() : returnInt()"),
1949
+ ).toEqual(returnTrue() ? returnString() : returnInt());
1950
+ expect(
1951
+ scope.$eval("identity(returnFalse() ? returnString() : returnInt())"),
1952
+ ).toEqual(identity(returnFalse() ? returnString() : returnInt()));
1953
+ });
1954
+
1955
+ it("should parse string", () => {
1956
+ expect(scope.$eval("'a' + 'b c'")).toEqual("ab c");
1957
+ });
1958
+
1959
+ it("should parse filters", () => {
1960
+ filterProvider.register(
1961
+ "substring",
1962
+ valueFn((input, start, end) => input.substring(start, end)),
1963
+ );
1964
+
1965
+ expect(() => {
1966
+ scope.$eval("1|nonexistent");
1967
+ }).toThrowError();
1968
+
1969
+ scope.offset = 3;
1970
+ expect(scope.$eval("'abcd'|substring:1:offset")).toEqual("bc");
1971
+ });
1972
+
1973
+ it("should access scope", () => {
1974
+ scope.a = 123;
1975
+ scope.b = { c: 456 };
1976
+ expect(scope.$eval("a", scope)).toEqual(123);
1977
+ expect(scope.$eval("b.c", scope)).toEqual(456);
1978
+ expect(scope.$eval("x.y.z", scope)).not.toBeDefined();
1979
+ });
1980
+
1981
+ it("should handle white-spaces around dots in paths", () => {
1982
+ scope.a = { b: 4 };
1983
+ expect(scope.$eval("a . b", scope)).toEqual(4);
1984
+ expect(scope.$eval("a. b", scope)).toEqual(4);
1985
+ expect(scope.$eval("a .b", scope)).toEqual(4);
1986
+ expect(scope.$eval("a . \nb", scope)).toEqual(4);
1987
+ });
1988
+
1989
+ it("should handle white-spaces around dots in method invocations", () => {
1990
+ scope.a = {
1991
+ b() {
1992
+ return this.c;
1993
+ },
1994
+ c: 4,
1995
+ };
1996
+ expect(scope.$eval("a . b ()", scope)).toEqual(4);
1997
+ expect(scope.$eval("a. b ()", scope)).toEqual(4);
1998
+ expect(scope.$eval("a .b ()", scope)).toEqual(4);
1999
+ expect(scope.$eval("a \n . \nb \n ()", scope)).toEqual(4);
2000
+ });
2001
+
2002
+ it("should throw syntax error exception for identifiers ending with a dot", () => {
2003
+ scope.a = { b: 4 };
2004
+
2005
+ expect(() => {
2006
+ scope.$eval("a.", scope);
2007
+ }).toThrowError(/ueoe/);
2008
+
2009
+ expect(() => {
2010
+ scope.$eval("a .", scope);
2011
+ }).toThrowError(/ueoe/);
2012
+ });
2013
+
2014
+ it("should resolve deeply nested paths (important for CSP mode)", () => {
2015
+ scope.a = {
2016
+ b: {
2017
+ c: {
2018
+ d: {
2019
+ e: {
2020
+ f: {
2021
+ g: { h: { i: { j: { k: { l: { m: { n: "nooo!" } } } } } } },
2022
+ },
2023
+ },
2024
+ },
2025
+ },
2026
+ },
2027
+ };
2028
+ expect(scope.$eval("a.b.c.d.e.f.g.h.i.j.k.l.m.n", scope)).toBe("nooo!");
2029
+ });
2030
+
2031
+ forEach([2, 3, 4, 5, 6, 7, 8, 9, 10, 20, 42, 99], (pathLength) => {
2032
+ it(`should resolve nested paths of length ${pathLength}`, () => {
2033
+ // Create a nested object {x2: {x3: {x4: ... {x[n]: 42} ... }}}.
2034
+ let obj = 42;
2035
+ const locals = {};
2036
+ for (var i = pathLength; i >= 2; i--) {
2037
+ const newObj = {};
2038
+ newObj[`x${i}`] = obj;
2039
+ obj = newObj;
2040
+ }
2041
+ // Assign to x1 and build path 'x1.x2.x3. ... .x[n]' to access the final value.
2042
+ scope.x1 = obj;
2043
+ let path = "x1";
2044
+ for (i = 2; i <= pathLength; i++) {
2045
+ path += `.x${i}`;
2046
+ }
2047
+ expect(scope.$eval(path)).toBe(42);
2048
+ locals[`x${pathLength}`] = "not 42";
2049
+ expect(scope.$eval(path, locals)).toBe(42);
2050
+ });
2051
+ });
2052
+
2053
+ it("should be forgiving", () => {
2054
+ scope.a = { b: 23 };
2055
+ expect(scope.$eval("b")).toBeUndefined();
2056
+ expect(scope.$eval("a.x")).toBeUndefined();
2057
+ expect(scope.$eval("a.b.c.d")).toBeUndefined();
2058
+ scope.a = undefined;
2059
+ expect(scope.$eval("a - b")).toBe(0);
2060
+ expect(scope.$eval("a + b")).toBeUndefined();
2061
+ scope.a = 0;
2062
+ expect(scope.$eval("a - b")).toBe(0);
2063
+ expect(scope.$eval("a + b")).toBe(0);
2064
+ scope.a = undefined;
2065
+ scope.b = 0;
2066
+ expect(scope.$eval("a - b")).toBe(0);
2067
+ expect(scope.$eval("a + b")).toBe(0);
2068
+ });
2069
+
2070
+ it("should support property names that collide with native object properties", () => {
2071
+ // regression
2072
+ scope.watch = 1;
2073
+ scope.toString = function toString() {
2074
+ return "custom toString";
2075
+ };
2076
+
2077
+ expect(scope.$eval("watch", scope)).toBe(1);
2078
+ expect(scope.$eval("toString()", scope)).toBe("custom toString");
2079
+ });
2080
+
2081
+ it("should not break if hasOwnProperty is referenced in an expression", () => {
2082
+ scope.obj = { value: 1 };
2083
+ // By evaluating an expression that calls hasOwnProperty, the getterFnCache
2084
+ // will store a property called hasOwnProperty. This is effectively:
2085
+ // getterFnCache['hasOwnProperty'] = null
2086
+ scope.$eval('obj.hasOwnProperty("value")');
2087
+ // If we rely on this property then evaluating any expression will fail
2088
+ // because it is not able to find out if obj.value is there in the cache
2089
+ expect(scope.$eval("obj.value")).toBe(1);
2090
+ });
2091
+
2092
+ it('should not break if the expression is "hasOwnProperty"', () => {
2093
+ scope.fooExp = "barVal";
2094
+ // By evaluating hasOwnProperty, the $parse cache will store a getter for
2095
+ // the scope's own hasOwnProperty function, which will mess up future cache look ups.
2096
+ // i.e. cache['hasOwnProperty'] = function(scope) { return scope.hasOwnProperty; }
2097
+ scope.$eval("hasOwnProperty");
2098
+ expect(scope.$eval("fooExp")).toBe("barVal");
2099
+ });
2100
+
2101
+ it("should evaluate grouped expressions", () => {
2102
+ expect(scope.$eval("(1+2)*3")).toEqual((1 + 2) * 3);
2103
+ });
2104
+
2105
+ it("should evaluate assignments", () => {
2106
+ expect(scope.$eval("a=12")).toEqual(12);
2107
+ expect(scope.a).toEqual(12);
2108
+
2109
+ expect(scope.$eval("x.y.z=123;")).toEqual(123);
2110
+ expect(scope.x.y.z).toEqual(123);
2111
+
2112
+ expect(scope.$eval("a=123; b=234")).toEqual(234);
2113
+ expect(scope.a).toEqual(123);
2114
+ expect(scope.b).toEqual(234);
2115
+ });
2116
+
2117
+ it("should throw with invalid left-val in assignments", () => {
2118
+ expect(() => {
2119
+ scope.$eval("1 = 1");
2120
+ }).toThrowError(/lval/);
2121
+ expect(() => {
2122
+ scope.$eval("{} = 1");
2123
+ }).toThrowError(/lval/);
2124
+ expect(() => {
2125
+ scope.$eval("[] = 1");
2126
+ }).toThrowError(/lval/);
2127
+ expect(() => {
2128
+ scope.$eval("true = 1");
2129
+ }).toThrowError(/lval/);
2130
+ expect(() => {
2131
+ scope.$eval("(a=b) = 1");
2132
+ }).toThrowError(/lval/);
2133
+ expect(() => {
2134
+ scope.$eval("(1<2) = 1");
2135
+ }).toThrowError(/lval/);
2136
+ expect(() => {
2137
+ scope.$eval("(1+2) = 1");
2138
+ }).toThrowError(/lval/);
2139
+ expect(() => {
2140
+ scope.$eval("!v = 1");
2141
+ }).toThrowError(/lval/);
2142
+ expect(() => {
2143
+ scope.$eval("this = 1");
2144
+ }).toThrowError(/lval/);
2145
+ expect(() => {
2146
+ scope.$eval("+v = 1");
2147
+ }).toThrowError(/lval/);
2148
+ expect(() => {
2149
+ scope.$eval("(1?v1:v2) = 1");
2150
+ }).toThrowError(/lval/);
2151
+ });
2152
+
2153
+ it("should evaluate assignments in ternary operator", () => {
2154
+ scope.$eval("a = 1 ? 2 : 3");
2155
+ expect(scope.a).toBe(2);
2156
+
2157
+ scope.$eval("0 ? a = 2 : a = 3");
2158
+ expect(scope.a).toBe(3);
2159
+
2160
+ scope.$eval("1 ? a = 2 : a = 3");
2161
+ expect(scope.a).toBe(2);
2162
+ });
2163
+
2164
+ it("should evaluate function call without arguments", () => {
2165
+ scope.const = function (a, b) {
2166
+ return 123;
2167
+ };
2168
+ expect(scope.$eval("const()")).toEqual(123);
2169
+ });
2170
+
2171
+ it("should evaluate function call with arguments", () => {
2172
+ scope.add = function (a, b) {
2173
+ return a + b;
2174
+ };
2175
+ expect(scope.$eval("add(1,2)")).toEqual(3);
2176
+ });
2177
+
2178
+ it("should allow filter chains as arguments", () => {
2179
+ scope.concat = function (a, b) {
2180
+ return a + b;
2181
+ };
2182
+ scope.begin = 1;
2183
+ scope.limit = 2;
2184
+ expect(
2185
+ scope.$eval("concat('abcd'|limitTo:limit:begin,'abcd'|limitTo:2:1)"),
2186
+ ).toEqual("bcbc");
2187
+ });
2188
+
2189
+ it("should evaluate function call from a return value", () => {
2190
+ scope.getter = function () {
2191
+ return function () {
2192
+ return 33;
2193
+ };
2194
+ };
2195
+ expect(scope.$eval("getter()()")).toBe(33);
2196
+ });
2197
+
2198
+ it("should evaluate multiplication and division", () => {
2199
+ scope.taxRate = 8;
2200
+ scope.subTotal = 100;
2201
+ expect(scope.$eval("taxRate / 100 * subTotal")).toEqual(8);
2202
+ expect(scope.$eval("subTotal * taxRate / 100")).toEqual(8);
2203
+ });
2204
+
2205
+ it("should evaluate array", () => {
2206
+ expect(scope.$eval("[]").length).toEqual(0);
2207
+ expect(scope.$eval("[1, 2]").length).toEqual(2);
2208
+ expect(scope.$eval("[1, 2]")[0]).toEqual(1);
2209
+ expect(scope.$eval("[1, 2]")[1]).toEqual(2);
2210
+ expect(scope.$eval("[1, 2,]")[1]).toEqual(2);
2211
+ expect(scope.$eval("[1, 2,]").length).toEqual(2);
2212
+ });
2213
+
2214
+ it("should evaluate array access", () => {
2215
+ expect(scope.$eval("[1][0]")).toEqual(1);
2216
+ expect(scope.$eval("[[1]][0][0]")).toEqual(1);
2217
+ expect(scope.$eval("[].length")).toEqual(0);
2218
+ expect(scope.$eval("[1, 2].length")).toEqual(2);
2219
+ });
2220
+
2221
+ it("should evaluate object", () => {
2222
+ expect(scope.$eval("{}")).toEqual({});
2223
+ expect(scope.$eval("{a:'b'}")).toEqual({ a: "b" });
2224
+ expect(scope.$eval("{'a':'b'}")).toEqual({ a: "b" });
2225
+ expect(scope.$eval("{\"a\":'b'}")).toEqual({ a: "b" });
2226
+ expect(scope.$eval("{a:'b',}")).toEqual({ a: "b" });
2227
+ expect(scope.$eval("{'a':'b',}")).toEqual({ a: "b" });
2228
+ expect(scope.$eval("{\"a\":'b',}")).toEqual({ a: "b" });
2229
+ expect(scope.$eval("{'0':1}")).toEqual({ 0: 1 });
2230
+ expect(scope.$eval("{0:1}")).toEqual({ 0: 1 });
2231
+ expect(scope.$eval("{1:1}")).toEqual({ 1: 1 });
2232
+ expect(scope.$eval("{null:1}")).toEqual({ null: 1 });
2233
+ expect(scope.$eval("{'null':1}")).toEqual({ null: 1 });
2234
+ expect(scope.$eval("{false:1}")).toEqual({ false: 1 });
2235
+ expect(scope.$eval("{'false':1}")).toEqual({ false: 1 });
2236
+ expect(scope.$eval("{'':1,}")).toEqual({ "": 1 });
2237
+
2238
+ // ES6 object initializers.
2239
+ expect(scope.$eval("{x, y}", { x: "foo", y: "bar" })).toEqual({
2240
+ x: "foo",
2241
+ y: "bar",
2242
+ });
2243
+ expect(scope.$eval("{[x]: x}", { x: "foo" })).toEqual({ foo: "foo" });
2244
+ expect(scope.$eval('{[x + "z"]: x}', { x: "foo" })).toEqual({
2245
+ fooz: "foo",
2246
+ });
2247
+ expect(
2248
+ scope.$eval(
2249
+ "{x, 1: x, [x = x + 1]: x, 3: x + 1, [x = x + 2]: x, 5: x + 1}",
2250
+ { x: 1 },
2251
+ ),
2252
+ ).toEqual({ x: 1, 1: 1, 2: 2, 3: 3, 4: 4, 5: 5 });
2253
+ });
2254
+
2255
+ it("should throw syntax error exception for non constant/identifier JSON keys", () => {
2256
+ expect(() => {
2257
+ scope.$eval("{[:0}");
2258
+ }).toThrowError(/syntax/);
2259
+ expect(() => {
2260
+ scope.$eval("{{:0}");
2261
+ }).toThrowError(/syntax/);
2262
+ expect(() => {
2263
+ scope.$eval("{?:0}");
2264
+ }).toThrowError(/syntax/);
2265
+ expect(() => {
2266
+ scope.$eval("{):0}");
2267
+ }).toThrowError(/syntax/);
2268
+ });
2269
+
2270
+ it("should evaluate object access", () => {
2271
+ expect(scope.$eval("{false:'WC', true:'CC'}[false]")).toEqual("WC");
2272
+ });
2273
+
2274
+ it("should evaluate JSON", () => {
2275
+ expect(scope.$eval("[{}]")).toEqual([{}]);
2276
+ expect(scope.$eval("[{a:[]}, {b:1}]")).toEqual([{ a: [] }, { b: 1 }]);
2277
+ });
2278
+
2279
+ it("should evaluate multiple statements", () => {
2280
+ expect(scope.$eval("a=1;b=3;a+b")).toEqual(4);
2281
+ expect(scope.$eval(";;1;;")).toEqual(1);
2282
+ });
2283
+
2284
+ it("should evaluate object methods in correct context (this)", () => {
2285
+ function C() {
2286
+ this.a = 123;
2287
+ }
2288
+ C.prototype.getA = function () {
2289
+ return this.a;
2290
+ };
2291
+
2292
+ scope.obj = new C();
2293
+ expect(scope.$eval("obj.getA()")).toEqual(123);
2294
+ expect(scope.$eval("obj['getA']()")).toEqual(123);
2295
+ });
2296
+
2297
+ it("should evaluate methods in correct context (this) in argument", () => {
2298
+ function C() {
2299
+ this.a = 123;
2300
+ }
2301
+ C.prototype.sum = function (value) {
2302
+ return this.a + value;
2303
+ };
2304
+ C.prototype.getA = function () {
2305
+ return this.a;
2306
+ };
2307
+
2308
+ scope.obj = new C();
2309
+ expect(scope.$eval("obj.sum(obj.getA())")).toEqual(246);
2310
+ expect(scope.$eval("obj['sum'](obj.getA())")).toEqual(246);
2311
+ });
2312
+
2313
+ it("should evaluate objects on scope context", () => {
2314
+ scope.a = "abc";
2315
+ expect(scope.$eval("{a:a}").a).toEqual("abc");
2316
+ });
2317
+
2318
+ it("should evaluate field access on function call result", () => {
2319
+ scope.a = function () {
2320
+ return { name: "misko" };
2321
+ };
2322
+ expect(scope.$eval("a().name")).toEqual("misko");
2323
+ });
2324
+
2325
+ it("should evaluate field access after array access", () => {
2326
+ scope.items = [{}, { name: "misko" }];
2327
+ expect(scope.$eval("items[1].name")).toEqual("misko");
2328
+ });
2329
+
2330
+ it("should evaluate array assignment", () => {
2331
+ scope.items = [];
2332
+
2333
+ expect(scope.$eval('items[1] = "abc"')).toEqual("abc");
2334
+ expect(scope.$eval("items[1]")).toEqual("abc");
2335
+ expect(scope.$eval('books[1] = "moby"')).toEqual("moby");
2336
+ expect(scope.$eval("books[1]")).toEqual("moby");
2337
+ });
2338
+
2339
+ it("should evaluate grouped filters", () => {
2340
+ scope.name = "MISKO";
2341
+ expect(scope.$eval("n = (name|limitTo:2|limitTo:1)")).toEqual("M");
2342
+ expect(scope.$eval("n")).toEqual("M");
2343
+ });
2344
+
2345
+ it("should evaluate remainder", () => {
2346
+ expect(scope.$eval("1%2")).toEqual(1);
2347
+ });
2348
+
2349
+ it("should evaluate sum with undefined", () => {
2350
+ expect(scope.$eval("1+undefined")).toEqual(1);
2351
+ expect(scope.$eval("undefined+1")).toEqual(1);
2352
+ });
2353
+
2354
+ it("should throw exception on non-closed bracket", () => {
2355
+ expect(() => {
2356
+ scope.$eval("[].count(");
2357
+ }).toThrowError(/ueoe/);
2358
+ });
2359
+
2360
+ it("should evaluate double negation", () => {
2361
+ expect(scope.$eval("true")).toBeTruthy();
2362
+ expect(scope.$eval("!true")).toBeFalsy();
2363
+ expect(scope.$eval("!!true")).toBeTruthy();
2364
+ expect(scope.$eval('{true:"a", false:"b"}[!!true]')).toEqual("a");
2365
+ });
2366
+
2367
+ it("should evaluate negation", () => {
2368
+ expect(scope.$eval("!false || true")).toEqual(!false || true);
2369
+ // eslint-disable-next-line eqeqeq
2370
+ expect(scope.$eval("!11 == 10")).toEqual(!11 == 10);
2371
+ expect(scope.$eval("12/6/2")).toEqual(12 / 6 / 2);
2372
+ });
2373
+
2374
+ it("should evaluate exclamation mark", () => {
2375
+ expect(scope.$eval('suffix = "!"')).toEqual("!");
2376
+ });
2377
+
2378
+ it("should evaluate minus", () => {
2379
+ expect(scope.$eval("{a:'-'}")).toEqual({ a: "-" });
2380
+ });
2381
+
2382
+ it("should evaluate undefined", () => {
2383
+ expect(scope.$eval("undefined")).not.toBeDefined();
2384
+ expect(scope.$eval("a=undefined")).not.toBeDefined();
2385
+ expect(scope.a).not.toBeDefined();
2386
+ });
2387
+
2388
+ it("should allow assignment after array dereference", () => {
2389
+ scope.obj = [{}];
2390
+ scope.$eval("obj[0].name=1");
2391
+ expect(scope.obj.name).toBeUndefined();
2392
+ expect(scope.obj[0].name).toEqual(1);
2393
+ });
2394
+
2395
+ it("should short-circuit AND operator", () => {
2396
+ scope.run = function () {
2397
+ throw new Error("IT SHOULD NOT HAVE RUN");
2398
+ };
2399
+ expect(scope.$eval("false && run()")).toBe(false);
2400
+ expect(scope.$eval("false && true && run()")).toBe(false);
2401
+ });
2402
+
2403
+ it("should short-circuit OR operator", () => {
2404
+ scope.run = function () {
2405
+ throw new Error("IT SHOULD NOT HAVE RUN");
2406
+ };
2407
+ expect(scope.$eval("true || run()")).toBe(true);
2408
+ expect(scope.$eval("true || false || run()")).toBe(true);
2409
+ });
2410
+
2411
+ it("should throw TypeError on using a 'broken' object as a key to access a property", () => {
2412
+ scope.object = {};
2413
+ forEach(
2414
+ [
2415
+ { toString: 2 },
2416
+ { toString: null },
2417
+ {
2418
+ toString() {
2419
+ return {};
2420
+ },
2421
+ },
2422
+ ],
2423
+ (brokenObject) => {
2424
+ scope.brokenObject = brokenObject;
2425
+ expect(() => {
2426
+ scope.$eval("object[brokenObject]");
2427
+ }).toThrow();
2428
+ },
2429
+ );
2430
+ });
2431
+
2432
+ it("should support method calls on primitive types", () => {
2433
+ scope.empty = "";
2434
+ scope.zero = 0;
2435
+ scope.bool = false;
2436
+
2437
+ expect(scope.$eval("empty.substr(0)")).toBe("");
2438
+ expect(scope.$eval("zero.toString()")).toBe("0");
2439
+ expect(scope.$eval("bool.toString()")).toBe("false");
2440
+ });
2441
+
2442
+ it("should evaluate expressions with line terminators", () => {
2443
+ scope.a = "a";
2444
+ scope.b = { c: "bc" };
2445
+ expect(
2446
+ scope.$eval('a + \n b.c + \r "\td" + \t \r\n\r "\r\n\n"'),
2447
+ ).toEqual("abc\td\r\n\n");
2448
+ });
2449
+
2450
+ // https://github.com/angular/angular.js/issues/10968
2451
+ it("should evaluate arrays literals initializers left-to-right", () => {
2452
+ const s = {
2453
+ c() {
2454
+ return { b: 1 };
2455
+ },
2456
+ };
2457
+ expect($parse("e=1;[a=c(),d=a.b+1]")(s)).toEqual([{ b: 1 }, 2]);
2458
+ });
2459
+
2460
+ it("should evaluate function arguments left-to-right", () => {
2461
+ const s = {
2462
+ c() {
2463
+ return { b: 1 };
2464
+ },
2465
+ i(x, y) {
2466
+ return [x, y];
2467
+ },
2468
+ };
2469
+ expect($parse("e=1;i(a=c(),d=a.b+1)")(s)).toEqual([{ b: 1 }, 2]);
2470
+ });
2471
+
2472
+ it("should evaluate object properties expressions left-to-right", () => {
2473
+ const s = {
2474
+ c() {
2475
+ return { b: 1 };
2476
+ },
2477
+ };
2478
+ expect($parse("e=1;{x: a=c(), y: d=a.b+1}")(s)).toEqual({
2479
+ x: { b: 1 },
2480
+ y: 2,
2481
+ });
2482
+ });
2483
+
2484
+ it("should call the function from the received instance and not from a new one", () => {
2485
+ let n = 0;
2486
+ scope.fn = function () {
2487
+ const c = n++;
2488
+ return {
2489
+ c,
2490
+ anotherFn() {
2491
+ return this.c === c;
2492
+ },
2493
+ };
2494
+ };
2495
+ expect(scope.$eval("fn().anotherFn()")).toBe(true);
2496
+ });
2497
+
2498
+ it("should call the function once when it is part of the context", () => {
2499
+ let count = 0;
2500
+ scope.fn = function () {
2501
+ count++;
2502
+ return {
2503
+ anotherFn() {
2504
+ return "lucas";
2505
+ },
2506
+ };
2507
+ };
2508
+ expect(scope.$eval("fn().anotherFn()")).toBe("lucas");
2509
+ expect(count).toBe(1);
2510
+ });
2511
+
2512
+ it("should call the function once when it is not part of the context", () => {
2513
+ let count = 0;
2514
+ scope.fn = function () {
2515
+ count++;
2516
+ return function () {
2517
+ return "lucas";
2518
+ };
2519
+ };
2520
+ expect(scope.$eval("fn()()")).toBe("lucas");
2521
+ expect(count).toBe(1);
2522
+ });
2523
+
2524
+ it("should call the function once when it is part of the context on assignments", () => {
2525
+ let count = 0;
2526
+ const element = {};
2527
+ scope.fn = function () {
2528
+ count++;
2529
+ return element;
2530
+ };
2531
+ expect(scope.$eval('fn().name = "lucas"')).toBe("lucas");
2532
+ expect(element.name).toBe("lucas");
2533
+ expect(count).toBe(1);
2534
+ });
2535
+
2536
+ it("should call the function once when it is part of the context on array lookups", () => {
2537
+ let count = 0;
2538
+ const element = [];
2539
+ scope.fn = function () {
2540
+ count++;
2541
+ return element;
2542
+ };
2543
+ expect(scope.$eval('fn()[0] = "lucas"')).toBe("lucas");
2544
+ expect(element[0]).toBe("lucas");
2545
+ expect(count).toBe(1);
2546
+ });
2547
+
2548
+ it("should call the function once when it is part of the context on array lookup function", () => {
2549
+ let count = 0;
2550
+ const element = [
2551
+ {
2552
+ anotherFn() {
2553
+ return "lucas";
2554
+ },
2555
+ },
2556
+ ];
2557
+ scope.fn = function () {
2558
+ count++;
2559
+ return element;
2560
+ };
2561
+ expect(scope.$eval("fn()[0].anotherFn()")).toBe("lucas");
2562
+ expect(count).toBe(1);
2563
+ });
2564
+
2565
+ it("should call the function once when it is part of the context on property lookup function", () => {
2566
+ let count = 0;
2567
+ const element = {
2568
+ name: {
2569
+ anotherFn() {
2570
+ return "lucas";
2571
+ },
2572
+ },
2573
+ };
2574
+ scope.fn = function () {
2575
+ count++;
2576
+ return element;
2577
+ };
2578
+ expect(scope.$eval("fn().name.anotherFn()")).toBe("lucas");
2579
+ expect(count).toBe(1);
2580
+ });
2581
+
2582
+ it("should call the function once when it is part of a sub-expression", () => {
2583
+ let count = 0;
2584
+ scope.element = [{}];
2585
+ scope.fn = function () {
2586
+ count++;
2587
+ return 0;
2588
+ };
2589
+ expect(scope.$eval('element[fn()].name = "lucas"')).toBe("lucas");
2590
+ expect(scope.element[0].name).toBe("lucas");
2591
+ expect(count).toBe(1);
2592
+ });
2593
+ });
2594
+ });
2595
+
2596
+ describe("assignable", () => {
2597
+ beforeEach(() => {
2598
+ createInjector([
2599
+ "ng",
2600
+ function ($filterProvider) {
2601
+ filterProvider = $filterProvider;
2602
+ },
2603
+ ]);
2604
+ });
2605
+
2606
+ it("should expose assignment function", () => {
2607
+ const fn = $parse("a");
2608
+ expect(fn.assign).toBeTruthy();
2609
+ const scope = {};
2610
+ fn.assign(scope, 123);
2611
+ expect(scope).toEqual({ a: 123 });
2612
+ });
2613
+
2614
+ it("should return the assigned value", () => {
2615
+ const fn = $parse("a");
2616
+ const scope = {};
2617
+ expect(fn.assign(scope, 123)).toBe(123);
2618
+ const someObject = {};
2619
+ expect(fn.assign(scope, someObject)).toBe(someObject);
2620
+ });
2621
+
2622
+ it("should expose working assignment function for expressions ending with brackets", () => {
2623
+ const fn = $parse('a.b["c"]');
2624
+ expect(fn.assign).toBeTruthy();
2625
+ const scope = {};
2626
+ fn.assign(scope, 123);
2627
+ expect(scope.a.b.c).toEqual(123);
2628
+ });
2629
+
2630
+ it("should expose working assignment function for expressions with brackets in the middle", () => {
2631
+ const fn = $parse('a["b"].c');
2632
+ expect(fn.assign).toBeTruthy();
2633
+ const scope = {};
2634
+ fn.assign(scope, 123);
2635
+ expect(scope.a.b.c).toEqual(123);
2636
+ });
2637
+
2638
+ it("should create objects when finding a null", () => {
2639
+ const fn = $parse("foo.bar");
2640
+ const scope = { foo: null };
2641
+ fn.assign(scope, 123);
2642
+ expect(scope.foo.bar).toEqual(123);
2643
+ });
2644
+
2645
+ it("should create objects when finding a null", () => {
2646
+ const fn = $parse('foo["bar"]');
2647
+ const scope = { foo: null };
2648
+ fn.assign(scope, 123);
2649
+ expect(scope.foo.bar).toEqual(123);
2650
+ });
2651
+
2652
+ it("should create objects when finding a null", () => {
2653
+ const fn = $parse("foo.bar.baz");
2654
+ const scope = { foo: null };
2655
+ fn.assign(scope, 123);
2656
+ expect(scope.foo.bar.baz).toEqual(123);
2657
+ });
2658
+ });
2659
+
2660
+ describe("one-time binding", () => {
2661
+ beforeEach(() => {
2662
+ createInjector([
2663
+ "ng",
2664
+ function ($filterProvider) {
2665
+ filterProvider = $filterProvider;
2666
+ },
2667
+ ]).invoke((_$rootScope_) => {
2668
+ $rootScope = _$rootScope_;
2669
+ });
2670
+ logs = [];
2671
+ });
2672
+
2673
+ it("should always use the cache", () => {
2674
+ expect($parse("foo")).toBe($parse("foo"));
2675
+ expect($parse("::foo")).toBe($parse("::foo"));
2676
+ });
2677
+
2678
+ it("should not affect calling the parseFn directly", () => {
2679
+ const fn = $parse("::foo");
2680
+ $rootScope.$watch(fn);
2681
+
2682
+ $rootScope.foo = "bar";
2683
+ expect($rootScope.$$watchers.length).toBe(1);
2684
+ expect(fn($rootScope)).toEqual("bar");
2685
+
2686
+ $rootScope.$digest();
2687
+ expect($rootScope.$$watchers.length).toBe(0);
2688
+ expect(fn($rootScope)).toEqual("bar");
2689
+
2690
+ $rootScope.foo = "man";
2691
+ $rootScope.$digest();
2692
+ expect($rootScope.$$watchers.length).toBe(0);
2693
+ expect(fn($rootScope)).toEqual("man");
2694
+
2695
+ $rootScope.foo = "shell";
2696
+ $rootScope.$digest();
2697
+ expect($rootScope.$$watchers.length).toBe(0);
2698
+ expect(fn($rootScope)).toEqual("shell");
2699
+ });
2700
+
2701
+ it("should stay stable once the value defined", () => {
2702
+ const fn = $parse("::foo");
2703
+ $rootScope.$watch(fn, (value, old) => {
2704
+ if (value !== old) logs.push(value);
2705
+ });
2706
+
2707
+ $rootScope.$digest();
2708
+ expect($rootScope.$$watchers.length).toBe(1);
2709
+
2710
+ $rootScope.foo = "bar";
2711
+ $rootScope.$digest();
2712
+ expect($rootScope.$$watchers.length).toBe(0);
2713
+ expect(logs[0]).toEqual("bar");
2714
+
2715
+ $rootScope.foo = "man";
2716
+ $rootScope.$digest();
2717
+ expect($rootScope.$$watchers.length).toBe(0);
2718
+ expect(logs.length).toEqual(1);
2719
+ });
2720
+
2721
+ it("should have a stable value if at the end of a $digest it has a defined value", () => {
2722
+ const fn = $parse("::foo");
2723
+ $rootScope.$watch(fn, (value, old) => {
2724
+ if (value !== old) logs.push(value);
2725
+ });
2726
+ $rootScope.$watch("foo", () => {
2727
+ if ($rootScope.foo === "bar") {
2728
+ $rootScope.foo = undefined;
2729
+ }
2730
+ });
2731
+
2732
+ $rootScope.foo = "bar";
2733
+ $rootScope.$digest();
2734
+ expect($rootScope.$$watchers.length).toBe(2);
2735
+ expect(logs[0]).toBeUndefined();
2736
+
2737
+ $rootScope.foo = "man";
2738
+ $rootScope.$digest();
2739
+ expect($rootScope.$$watchers.length).toBe(1);
2740
+ expect(logs[1]).toEqual("man");
2741
+
2742
+ $rootScope.foo = "shell";
2743
+ $rootScope.$digest();
2744
+ expect($rootScope.$$watchers.length).toBe(1);
2745
+ expect(logs.length).toEqual(2);
2746
+ });
2747
+
2748
+ it("should not throw if the stable value is `null`", () => {
2749
+ const fn = $parse("::foo");
2750
+ $rootScope.$watch(fn);
2751
+ $rootScope.foo = null;
2752
+ $rootScope.$digest();
2753
+ $rootScope.foo = "foo";
2754
+ $rootScope.$digest();
2755
+ expect(fn()).toEqual(undefined);
2756
+ });
2757
+
2758
+ it("should invoke a stateless filter once when the parsed expression has an interceptor", () => {
2759
+ const countFilter = jasmine.createSpy();
2760
+ const interceptor = jasmine.createSpy();
2761
+ countFilter.and.returnValue(1);
2762
+ createInjector([
2763
+ "ng",
2764
+ function ($filterProvider) {
2765
+ $filterProvider.register("count", valueFn(countFilter));
2766
+ },
2767
+ ]).invoke((_$rootScope_, _$parse_) => {
2768
+ scope = _$rootScope_;
2769
+ $parse = _$parse_;
2770
+ });
2771
+
2772
+ scope.foo = function () {
2773
+ return 1;
2774
+ };
2775
+ scope.$watch($parse(":: foo() | count", interceptor));
2776
+ scope.$digest();
2777
+ expect(countFilter.calls.count()).toBe(1);
2778
+ });
2779
+ });
2780
+
2781
+ describe("literal expressions", () => {
2782
+ it("should mark an empty expressions as literal", () => {
2783
+ expect($parse("").literal).toBe(true);
2784
+ expect($parse(" ").literal).toBe(true);
2785
+ expect($parse("::").literal).toBe(true);
2786
+ expect($parse(":: ").literal).toBe(true);
2787
+ });
2788
+
2789
+ [true, false].forEach((isDeep) => {
2790
+ describe(isDeep ? "deepWatch" : "watch", () => {
2791
+ beforeEach(() => {
2792
+ logs = [];
2793
+ });
2794
+
2795
+ it("should only become stable when all the properties of an object have defined values", () => {
2796
+ const fn = $parse("::{foo: foo, bar: bar}");
2797
+ $rootScope.$watch(
2798
+ fn,
2799
+ (value) => {
2800
+ logs.push(value);
2801
+ },
2802
+ isDeep,
2803
+ );
2804
+
2805
+ expect(logs).toEqual([]);
2806
+ expect($rootScope.$$watchers.length).toBe(1);
2807
+
2808
+ $rootScope.$digest();
2809
+ expect($rootScope.$$watchers.length).toBe(1);
2810
+ expect(logs[0]).toEqual({ foo: undefined, bar: undefined });
2811
+
2812
+ $rootScope.foo = "foo";
2813
+ $rootScope.$digest();
2814
+ expect($rootScope.$$watchers.length).toBe(1);
2815
+ expect(logs[0]).toEqual({ foo: undefined, bar: undefined });
2816
+
2817
+ $rootScope.foo = "foobar";
2818
+ $rootScope.bar = "bar";
2819
+ $rootScope.$digest();
2820
+ expect($rootScope.$$watchers.length).toBe(0);
2821
+ expect(logs[2]).toEqual({ foo: "foobar", bar: "bar" });
2822
+
2823
+ $rootScope.foo = "baz";
2824
+ $rootScope.$digest();
2825
+ expect($rootScope.$$watchers.length).toBe(0);
2826
+ expect(logs[3]).toBeUndefined();
2827
+ });
2828
+
2829
+ it("should only become stable when all the elements of an array have defined values", () => {
2830
+ const fn = $parse("::[foo,bar]");
2831
+ $rootScope.$watch(
2832
+ fn,
2833
+ (value) => {
2834
+ logs.push(value);
2835
+ },
2836
+ isDeep,
2837
+ );
2838
+
2839
+ expect(logs.length).toEqual(0);
2840
+ expect($rootScope.$$watchers.length).toBe(1);
2841
+
2842
+ $rootScope.$digest();
2843
+ expect($rootScope.$$watchers.length).toBe(1);
2844
+ expect(logs[0]).toEqual([undefined, undefined]);
2845
+
2846
+ $rootScope.foo = "foo";
2847
+ $rootScope.$digest();
2848
+ expect($rootScope.$$watchers.length).toBe(1);
2849
+ expect(logs[1]).toEqual(["foo", undefined]);
2850
+
2851
+ $rootScope.foo = "foobar";
2852
+ $rootScope.bar = "bar";
2853
+ $rootScope.$digest();
2854
+ expect($rootScope.$$watchers.length).toBe(0);
2855
+ expect(logs[2]).toEqual(["foobar", "bar"]);
2856
+
2857
+ $rootScope.foo = "baz";
2858
+ $rootScope.$digest();
2859
+ expect($rootScope.$$watchers.length).toBe(0);
2860
+ expect(logs[3]).toBeUndefined();
2861
+ });
2862
+
2863
+ it("should only become stable when all the elements of an array have defined values at the end of a $digest", () => {
2864
+ const fn = $parse("::[foo]");
2865
+ $rootScope.$watch(
2866
+ fn,
2867
+ (value) => {
2868
+ logs.push(value);
2869
+ },
2870
+ isDeep,
2871
+ );
2872
+ $rootScope.$watch("foo", () => {
2873
+ if ($rootScope.foo === "bar") {
2874
+ $rootScope.foo = undefined;
2875
+ }
2876
+ });
2877
+
2878
+ $rootScope.foo = "bar";
2879
+ $rootScope.$digest();
2880
+ expect($rootScope.$$watchers.length).toBe(2);
2881
+ expect(logs[0]).toEqual(["bar"]);
2882
+ expect(logs[1]).toEqual([undefined]);
2883
+
2884
+ $rootScope.foo = "baz";
2885
+ $rootScope.$digest();
2886
+ expect($rootScope.$$watchers.length).toBe(1);
2887
+ expect(logs[2]).toEqual(["baz"]);
2888
+
2889
+ $rootScope.bar = "qux";
2890
+ $rootScope.$digest();
2891
+ expect($rootScope.$$watchers.length).toBe(1);
2892
+ expect(logs[3]).toBeUndefined();
2893
+ });
2894
+ });
2895
+ });
2896
+ });
2897
+
2898
+ describe("watched $parse expressions", () => {
2899
+ beforeEach(() => {
2900
+ createInjector(["ng"]).invoke((_$rootScope_) => {
2901
+ scope = _$rootScope_;
2902
+ });
2903
+ });
2904
+
2905
+ it("should respect short-circuiting AND if it could have side effects", () => {
2906
+ let bCalled = 0;
2907
+ scope.b = function () {
2908
+ bCalled++;
2909
+ };
2910
+
2911
+ scope.$watch("a && b()");
2912
+ scope.$digest();
2913
+ scope.$digest();
2914
+ expect(bCalled).toBe(0);
2915
+
2916
+ scope.a = true;
2917
+ scope.$digest();
2918
+ expect(bCalled).toBe(1);
2919
+ scope.$digest();
2920
+ expect(bCalled).toBe(2);
2921
+ });
2922
+
2923
+ it("should respect short-circuiting OR if it could have side effects", () => {
2924
+ let bCalled = false;
2925
+ scope.b = function () {
2926
+ bCalled = true;
2927
+ };
2928
+
2929
+ scope.$watch("a || b()");
2930
+ scope.$digest();
2931
+ expect(bCalled).toBe(true);
2932
+
2933
+ bCalled = false;
2934
+ scope.a = true;
2935
+ scope.$digest();
2936
+ expect(bCalled).toBe(false);
2937
+ });
2938
+
2939
+ it("should respect the branching ternary operator if it could have side effects", () => {
2940
+ let bCalled = false;
2941
+ scope.b = function () {
2942
+ bCalled = true;
2943
+ };
2944
+
2945
+ scope.$watch("a ? b() : 1");
2946
+ scope.$digest();
2947
+ expect(bCalled).toBe(false);
2948
+
2949
+ scope.a = true;
2950
+ scope.$digest();
2951
+ expect(bCalled).toBe(true);
2952
+ });
2953
+ });
2954
+
2955
+ describe("filters", () => {
2956
+ beforeEach(() => {
2957
+ createInjector([
2958
+ "ng",
2959
+ function ($filterProvider) {
2960
+ filterProvider = $filterProvider;
2961
+ },
2962
+ ]).invoke((_$rootScope_, _$parse_) => {
2963
+ scope = _$rootScope_;
2964
+ $parse = _$parse_;
2965
+ });
2966
+ logs = [];
2967
+ });
2968
+
2969
+ it("should not be invoked unless the input/arguments change", () => {
2970
+ let filterCalled = false;
2971
+ filterProvider.register(
2972
+ "foo",
2973
+ valueFn((input) => {
2974
+ filterCalled = true;
2975
+ return input;
2976
+ }),
2977
+ );
2978
+
2979
+ scope.$watch("a | foo:b:1");
2980
+ scope.a = 0;
2981
+ scope.$digest();
2982
+ expect(filterCalled).toBe(true);
2983
+
2984
+ filterCalled = false;
2985
+ scope.$digest();
2986
+ expect(filterCalled).toBe(false);
2987
+
2988
+ scope.a++;
2989
+ scope.$digest();
2990
+ expect(filterCalled).toBe(true);
2991
+ });
2992
+
2993
+ it("should not be invoked unless the input/arguments change within literals", () => {
2994
+ const filterCalls = [];
2995
+ filterProvider.register(
2996
+ "foo",
2997
+ valueFn((input) => {
2998
+ filterCalls.push(input);
2999
+ return input;
3000
+ }),
3001
+ );
3002
+
3003
+ scope.$watch("[(a | foo:b:1), undefined]");
3004
+ scope.a = 0;
3005
+ scope.$digest();
3006
+ expect(filterCalls).toEqual([0]);
3007
+
3008
+ scope.$digest();
3009
+ expect(filterCalls).toEqual([0]);
3010
+
3011
+ scope.a++;
3012
+ scope.$digest();
3013
+ expect(filterCalls).toEqual([0, 1]);
3014
+ });
3015
+
3016
+ it("should not be invoked unless the input/arguments change within literals (one-time)", () => {
3017
+ const filterCalls = [];
3018
+ filterProvider.register(
3019
+ "foo",
3020
+ valueFn((input) => {
3021
+ filterCalls.push(input);
3022
+ return input;
3023
+ }),
3024
+ );
3025
+
3026
+ scope.$watch("::[(a | foo:b:1), undefined]");
3027
+ scope.a = 0;
3028
+ scope.$digest();
3029
+ expect(filterCalls).toEqual([0]);
3030
+
3031
+ scope.$digest();
3032
+ expect(filterCalls).toEqual([0]);
3033
+
3034
+ scope.a++;
3035
+ scope.$digest();
3036
+ expect(filterCalls).toEqual([0, 1]);
3037
+ });
3038
+
3039
+ it("should always be invoked if they are marked as having $stateful", () => {
3040
+ let filterCalled = false;
3041
+ filterProvider.register(
3042
+ "foo",
3043
+ valueFn(
3044
+ extend(
3045
+ (input) => {
3046
+ filterCalled = true;
3047
+ return input;
3048
+ },
3049
+ { $stateful: true },
3050
+ ),
3051
+ ),
3052
+ );
3053
+
3054
+ scope.$watch("a | foo:b:1");
3055
+ scope.a = 0;
3056
+ scope.$digest();
3057
+ expect(filterCalled).toBe(true);
3058
+
3059
+ filterCalled = false;
3060
+ scope.$digest();
3061
+ expect(filterCalled).toBe(true);
3062
+ });
3063
+
3064
+ it("should be treated as constant when input are constant", () => {
3065
+ let filterCalls = 0;
3066
+ filterProvider.register(
3067
+ "foo",
3068
+ valueFn((input) => {
3069
+ filterCalls++;
3070
+ return input;
3071
+ }),
3072
+ );
3073
+
3074
+ const parsed = $parse("{x: 1} | foo:1");
3075
+
3076
+ expect(parsed.constant).toBe(true);
3077
+
3078
+ let watcherCalls = 0;
3079
+ scope.$watch(parsed, (input) => {
3080
+ expect(input).toEqual({ x: 1 });
3081
+ watcherCalls++;
3082
+ });
3083
+
3084
+ scope.$digest();
3085
+ expect(filterCalls).toBe(1);
3086
+ expect(watcherCalls).toBe(1);
3087
+
3088
+ scope.$digest();
3089
+ expect(filterCalls).toBe(1);
3090
+ expect(watcherCalls).toBe(1);
3091
+ });
3092
+
3093
+ it("should ignore changes within nested objects", () => {
3094
+ const watchCalls = [];
3095
+ scope.$watch("[a]", (a) => {
3096
+ watchCalls.push(a[0]);
3097
+ });
3098
+ scope.a = 0;
3099
+ scope.$digest();
3100
+ expect(watchCalls).toEqual([0]);
3101
+
3102
+ scope.$digest();
3103
+ expect(watchCalls).toEqual([0]);
3104
+
3105
+ scope.a++;
3106
+ scope.$digest();
3107
+ expect(watchCalls).toEqual([0, 1]);
3108
+
3109
+ scope.a = {};
3110
+ scope.$digest();
3111
+ expect(watchCalls).toEqual([0, 1, {}]);
3112
+
3113
+ scope.a.foo = 42;
3114
+ scope.$digest();
3115
+ expect(watchCalls).toEqual([0, 1, { foo: 42 }]);
3116
+ });
3117
+
3118
+ it("should ignore changes within nested objects (one-time)", () => {
3119
+ const watchCalls = [];
3120
+ scope.$watch("::[a, undefined]", (a) => {
3121
+ watchCalls.push(a[0]);
3122
+ });
3123
+ scope.a = 0;
3124
+ scope.$digest();
3125
+ expect(watchCalls).toEqual([0]);
3126
+
3127
+ scope.$digest();
3128
+ expect(watchCalls).toEqual([0]);
3129
+
3130
+ scope.a++;
3131
+ scope.$digest();
3132
+ expect(watchCalls).toEqual([0, 1]);
3133
+
3134
+ scope.a = {};
3135
+ scope.$digest();
3136
+ expect(watchCalls).toEqual([0, 1, {}]);
3137
+
3138
+ scope.a.foo = 42;
3139
+ scope.$digest();
3140
+ expect(watchCalls).toEqual([0, 1, { foo: 42 }]);
3141
+ });
3142
+ });
3143
+
3144
+ describe("with non-primitive input", () => {
3145
+ beforeEach(() => {
3146
+ createInjector([
3147
+ "ng",
3148
+ function ($filterProvider) {
3149
+ filterProvider = $filterProvider;
3150
+ },
3151
+ ]).invoke((_$rootScope_, _$parse_) => {
3152
+ scope = _$rootScope_;
3153
+ $parse = _$parse_;
3154
+ });
3155
+ logs = [];
3156
+ });
3157
+
3158
+ describe("that does NOT support valueOf()", () => {
3159
+ it("should always be reevaluated", () => {
3160
+ let filterCalls = 0;
3161
+ filterProvider.register(
3162
+ "foo",
3163
+ valueFn((input) => {
3164
+ filterCalls++;
3165
+ return input;
3166
+ }),
3167
+ );
3168
+
3169
+ const parsed = $parse("obj | foo");
3170
+ const obj = (scope.obj = {});
3171
+
3172
+ let watcherCalls = 0;
3173
+ scope.$watch(parsed, (input) => {
3174
+ expect(input).toBe(obj);
3175
+ watcherCalls++;
3176
+ });
3177
+
3178
+ scope.$digest();
3179
+ expect(filterCalls).toBe(2);
3180
+ expect(watcherCalls).toBe(1);
3181
+
3182
+ scope.$digest();
3183
+ expect(filterCalls).toBe(3);
3184
+ expect(watcherCalls).toBe(1);
3185
+ });
3186
+
3187
+ it("should always be reevaluated in literals", () => {
3188
+ filterProvider.register(
3189
+ "foo",
3190
+ valueFn((input) => input.b > 0),
3191
+ );
3192
+
3193
+ scope.$watch("[(a | foo)]", () => {});
3194
+
3195
+ // Would be great if filter-output was checked for changes and this didn't throw...
3196
+ expect(() => {
3197
+ scope.$apply("a = {b: 1}");
3198
+ }).toThrowError(/infdig/);
3199
+ });
3200
+
3201
+ it("should always be reevaluated when passed literals", () => {
3202
+ scope.$watch("[a] | filter", () => {});
3203
+
3204
+ scope.$apply("a = 1");
3205
+
3206
+ // Would be great if filter-output was checked for changes and this didn't throw...
3207
+ expect(() => {
3208
+ scope.$apply("a = {}");
3209
+ }).toThrowError(/infdig/);
3210
+ });
3211
+ });
3212
+
3213
+ describe("that does support valueOf()", () => {
3214
+ it("should not be reevaluated", () => {
3215
+ let filterCalls = 0;
3216
+ filterProvider.register(
3217
+ "foo",
3218
+ valueFn((input) => {
3219
+ filterCalls++;
3220
+ expect(input instanceof Date).toBe(true);
3221
+ return input;
3222
+ }),
3223
+ );
3224
+
3225
+ const parsed = $parse("date | foo:a");
3226
+ const date = (scope.date = new Date());
3227
+
3228
+ let watcherCalls = 0;
3229
+ scope.$watch(parsed, (input) => {
3230
+ expect(input).toBe(date);
3231
+ watcherCalls++;
3232
+ });
3233
+
3234
+ scope.$digest();
3235
+ expect(filterCalls).toBe(1);
3236
+ expect(watcherCalls).toBe(1);
3237
+
3238
+ scope.$digest();
3239
+ expect(filterCalls).toBe(1);
3240
+ expect(watcherCalls).toBe(1);
3241
+ });
3242
+
3243
+ it("should not be reevaluated in literals", () => {
3244
+ let filterCalls = 0;
3245
+ filterProvider.register(
3246
+ "foo",
3247
+ valueFn((input) => {
3248
+ filterCalls++;
3249
+ return input;
3250
+ }),
3251
+ );
3252
+
3253
+ scope.date = new Date(1234567890123);
3254
+
3255
+ let watcherCalls = 0;
3256
+ scope.$watch("[(date | foo)]", (input) => {
3257
+ watcherCalls++;
3258
+ });
3259
+
3260
+ scope.$digest();
3261
+ expect(filterCalls).toBe(1);
3262
+ expect(watcherCalls).toBe(1);
3263
+
3264
+ scope.$digest();
3265
+ expect(filterCalls).toBe(1);
3266
+ expect(watcherCalls).toBe(1);
3267
+ });
3268
+
3269
+ it("should be reevaluated when valueOf() changes", () => {
3270
+ let filterCalls = 0;
3271
+ filterProvider.register(
3272
+ "foo",
3273
+ valueFn((input) => {
3274
+ filterCalls++;
3275
+ expect(input instanceof Date).toBe(true);
3276
+ return input;
3277
+ }),
3278
+ );
3279
+
3280
+ const parsed = $parse("date | foo:a");
3281
+ const date = (scope.date = new Date());
3282
+
3283
+ let watcherCalls = 0;
3284
+ scope.$watch(parsed, (input) => {
3285
+ expect(input).toBe(date);
3286
+ watcherCalls++;
3287
+ });
3288
+
3289
+ scope.$digest();
3290
+ expect(filterCalls).toBe(1);
3291
+ expect(watcherCalls).toBe(1);
3292
+
3293
+ date.setYear(1901);
3294
+
3295
+ scope.$digest();
3296
+ expect(filterCalls).toBe(2);
3297
+ expect(watcherCalls).toBe(1);
3298
+ });
3299
+
3300
+ it("should be reevaluated in literals when valueOf() changes", () => {
3301
+ let filterCalls = 0;
3302
+ filterProvider.register(
3303
+ "foo",
3304
+ valueFn((input) => {
3305
+ filterCalls++;
3306
+ return input;
3307
+ }),
3308
+ );
3309
+
3310
+ scope.date = new Date(1234567890123);
3311
+
3312
+ let watcherCalls = 0;
3313
+ scope.$watch("[(date | foo)]", (input) => {
3314
+ watcherCalls++;
3315
+ });
3316
+
3317
+ scope.$digest();
3318
+ expect(filterCalls).toBe(1);
3319
+ expect(watcherCalls).toBe(1);
3320
+
3321
+ scope.date.setTime(1234567890);
3322
+
3323
+ scope.$digest();
3324
+ expect(filterCalls).toBe(2);
3325
+ expect(watcherCalls).toBe(2);
3326
+ });
3327
+
3328
+ it("should not be reevaluated when the instance changes but valueOf() does not", () => {
3329
+ let filterCalls = 0;
3330
+ filterProvider.register(
3331
+ "foo",
3332
+ valueFn((input) => {
3333
+ filterCalls++;
3334
+ return input;
3335
+ }),
3336
+ );
3337
+
3338
+ scope.date = new Date(1234567890123);
3339
+
3340
+ let watcherCalls = 0;
3341
+ scope.$watch($parse("[(date | foo)]"), (input) => {
3342
+ watcherCalls++;
3343
+ });
3344
+
3345
+ scope.$digest();
3346
+ expect(watcherCalls).toBe(1);
3347
+ expect(filterCalls).toBe(1);
3348
+
3349
+ scope.date = new Date(1234567890123);
3350
+ scope.$digest();
3351
+ expect(watcherCalls).toBe(1);
3352
+ expect(filterCalls).toBe(1);
3353
+ });
3354
+ });
3355
+
3356
+ it("should not be reevaluated when input is simplified via unary operators", () => {
3357
+ let filterCalls = 0;
3358
+ filterProvider.register(
3359
+ "foo",
3360
+ valueFn((input) => {
3361
+ filterCalls++;
3362
+ return input;
3363
+ }),
3364
+ );
3365
+
3366
+ scope.obj = {};
3367
+
3368
+ let watcherCalls = 0;
3369
+ scope.$watch("!obj | foo:!obj", (input) => {
3370
+ watcherCalls++;
3371
+ });
3372
+
3373
+ scope.$digest();
3374
+ expect(filterCalls).toBe(1);
3375
+ expect(watcherCalls).toBe(1);
3376
+
3377
+ scope.$digest();
3378
+ expect(filterCalls).toBe(1);
3379
+ expect(watcherCalls).toBe(1);
3380
+ });
3381
+
3382
+ it("should not be reevaluated when input is simplified via non-plus/concat binary operators", () => {
3383
+ let filterCalls = 0;
3384
+ filterProvider.register(
3385
+ "foo",
3386
+ valueFn((input) => {
3387
+ filterCalls++;
3388
+ return input;
3389
+ }),
3390
+ );
3391
+
3392
+ scope.obj = {};
3393
+
3394
+ let watcherCalls = 0;
3395
+ scope.$watch("1 - obj | foo:(1 * obj)", (input) => {
3396
+ watcherCalls++;
3397
+ });
3398
+
3399
+ scope.$digest();
3400
+ expect(filterCalls).toBe(1);
3401
+ expect(watcherCalls).toBe(1);
3402
+
3403
+ scope.$digest();
3404
+ expect(filterCalls).toBe(1);
3405
+ expect(watcherCalls).toBe(1);
3406
+ });
3407
+
3408
+ it("should be reevaluated when input is simplified via plus/concat", () => {
3409
+ let filterCalls = 0;
3410
+ filterProvider.register(
3411
+ "foo",
3412
+ valueFn((input) => {
3413
+ filterCalls++;
3414
+ return input;
3415
+ }),
3416
+ );
3417
+
3418
+ scope.obj = {};
3419
+
3420
+ let watcherCalls = 0;
3421
+ scope.$watch("1 + obj | foo", (input) => {
3422
+ watcherCalls++;
3423
+ });
3424
+
3425
+ scope.$digest();
3426
+ expect(filterCalls).toBe(2);
3427
+ expect(watcherCalls).toBe(1);
3428
+
3429
+ scope.$digest();
3430
+ expect(filterCalls).toBe(3);
3431
+ expect(watcherCalls).toBe(1);
3432
+ });
3433
+
3434
+ it("should reevaluate computed member expressions", () => {
3435
+ let toStringCalls = 0;
3436
+
3437
+ scope.obj = {};
3438
+ scope.key = {
3439
+ toString() {
3440
+ toStringCalls++;
3441
+ return "foo";
3442
+ },
3443
+ };
3444
+
3445
+ let watcherCalls = 0;
3446
+ scope.$watch("obj[key]", (input) => {
3447
+ watcherCalls++;
3448
+ });
3449
+
3450
+ scope.$digest();
3451
+ expect(toStringCalls).toBe(2);
3452
+ expect(watcherCalls).toBe(1);
3453
+
3454
+ scope.$digest();
3455
+ expect(toStringCalls).toBe(3);
3456
+ expect(watcherCalls).toBe(1);
3457
+ });
3458
+
3459
+ it("should be reevaluated with input created with null prototype", () => {
3460
+ let filterCalls = 0;
3461
+ filterProvider.register(
3462
+ "foo",
3463
+ valueFn((input) => {
3464
+ filterCalls++;
3465
+ return input;
3466
+ }),
3467
+ );
3468
+
3469
+ const parsed = $parse("obj | foo");
3470
+ const obj = (scope.obj = Object.create(null));
3471
+
3472
+ let watcherCalls = 0;
3473
+ scope.$watch(parsed, (input) => {
3474
+ expect(input).toBe(obj);
3475
+ watcherCalls++;
3476
+ });
3477
+
3478
+ scope.$digest();
3479
+ expect(filterCalls).toBe(2);
3480
+ expect(watcherCalls).toBe(1);
3481
+
3482
+ scope.$digest();
3483
+ expect(filterCalls).toBe(3);
3484
+ expect(watcherCalls).toBe(1);
3485
+ });
3486
+ });
3487
+
3488
+ describe("with primitive input", () => {
3489
+ it("should not be reevaluated when passed literals", () => {
3490
+ let filterCalls = 0;
3491
+ filterProvider.register(
3492
+ "foo",
3493
+ valueFn((input) => {
3494
+ filterCalls++;
3495
+ return input;
3496
+ }),
3497
+ );
3498
+
3499
+ let watcherCalls = 0;
3500
+ scope.$watch("[a] | foo", (input) => {
3501
+ watcherCalls++;
3502
+ });
3503
+
3504
+ scope.$apply("a = 1");
3505
+ expect(filterCalls).toBe(1);
3506
+ expect(watcherCalls).toBe(1);
3507
+
3508
+ scope.$apply("a = 2");
3509
+ expect(filterCalls).toBe(2);
3510
+ expect(watcherCalls).toBe(2);
3511
+ });
3512
+
3513
+ it("should not be reevaluated in literals", () => {
3514
+ let filterCalls = 0;
3515
+ filterProvider.register(
3516
+ "foo",
3517
+ valueFn((input) => {
3518
+ filterCalls++;
3519
+ return input;
3520
+ }),
3521
+ );
3522
+
3523
+ scope.prim = 1234567890123;
3524
+
3525
+ let watcherCalls = 0;
3526
+ scope.$watch("[(prim | foo)]", (input) => {
3527
+ watcherCalls++;
3528
+ });
3529
+
3530
+ scope.$digest();
3531
+ expect(filterCalls).toBe(1);
3532
+ expect(watcherCalls).toBe(1);
3533
+
3534
+ scope.$digest();
3535
+ expect(filterCalls).toBe(1);
3536
+ expect(watcherCalls).toBe(1);
3537
+ });
3538
+ });
3539
+
3540
+ describe("interceptorFns", () => {
3541
+ beforeEach(() => {
3542
+ createInjector([
3543
+ "ng",
3544
+ function ($filterProvider) {
3545
+ filterProvider = $filterProvider;
3546
+ },
3547
+ ]).invoke((_$rootScope_, _$parse_) => {
3548
+ scope = _$rootScope_;
3549
+ $parse = _$parse_;
3550
+ });
3551
+ logs = [];
3552
+ });
3553
+
3554
+ it("should only be passed the intercepted value", () => {
3555
+ let args;
3556
+ function interceptor(v) {
3557
+ args = sliceArgs(arguments);
3558
+ return v;
3559
+ }
3560
+
3561
+ scope.$watch($parse("a", interceptor));
3562
+
3563
+ scope.a = 1;
3564
+ scope.$digest();
3565
+ expect(args).toEqual([1]);
3566
+ });
3567
+
3568
+ it("should only be passed the intercepted value when wrapping one-time", () => {
3569
+ let args;
3570
+ function interceptor(v) {
3571
+ args = sliceArgs(arguments);
3572
+ return v;
3573
+ }
3574
+
3575
+ scope.$watch($parse("::a", interceptor));
3576
+
3577
+ scope.a = 1;
3578
+ scope.$digest();
3579
+ expect(args).toEqual([1]);
3580
+ });
3581
+
3582
+ it("should only be passed the intercepted value when double-intercepted", () => {
3583
+ let args1;
3584
+ function int1(v) {
3585
+ args1 = sliceArgs(arguments);
3586
+ return v + 2;
3587
+ }
3588
+ let args2;
3589
+ function int2(v) {
3590
+ args2 = sliceArgs(arguments);
3591
+ return v + 4;
3592
+ }
3593
+
3594
+ scope.$watch($parse($parse("a", int1), int2));
3595
+
3596
+ scope.a = 1;
3597
+ scope.$digest();
3598
+ expect(args1).toEqual([1]);
3599
+ expect(args2).toEqual([3]);
3600
+ });
3601
+
3602
+ it("should support locals", () => {
3603
+ let args;
3604
+ function interceptor(v) {
3605
+ args = sliceArgs(arguments);
3606
+ return v + 4;
3607
+ }
3608
+
3609
+ const exp = $parse("a + b", interceptor);
3610
+ scope.a = 1;
3611
+
3612
+ expect(exp(scope, { b: 2 })).toBe(7);
3613
+ expect(args).toEqual([3]);
3614
+ });
3615
+
3616
+ it("should support locals when double-intercepted", () => {
3617
+ let args1;
3618
+ function int1(v) {
3619
+ args1 = sliceArgs(arguments);
3620
+ return v + 4;
3621
+ }
3622
+ let args2;
3623
+ function int2(v) {
3624
+ args2 = sliceArgs(arguments);
3625
+ return v + 8;
3626
+ }
3627
+
3628
+ const exp = $parse($parse("a + b", int1), int2);
3629
+
3630
+ scope.a = 1;
3631
+ expect(exp(scope, { b: 2 })).toBe(15);
3632
+ expect(args1).toEqual([3]);
3633
+ expect(args2).toEqual([7]);
3634
+ });
3635
+
3636
+ it("should always be invoked if they are flagged as having $stateful", () => {
3637
+ let called = false;
3638
+ function interceptor() {
3639
+ called = true;
3640
+ }
3641
+ interceptor.$stateful = true;
3642
+
3643
+ scope.$watch($parse("a", interceptor));
3644
+ scope.a = 0;
3645
+ scope.$digest();
3646
+ expect(called).toBe(true);
3647
+
3648
+ called = false;
3649
+ scope.$digest();
3650
+ expect(called).toBe(true);
3651
+
3652
+ scope.a++;
3653
+ called = false;
3654
+ scope.$digest();
3655
+ expect(called).toBe(true);
3656
+ });
3657
+
3658
+ it("should always be invoked if flagged as $stateful when wrapping one-time", () => {
3659
+ let interceptorCalls = 0;
3660
+ function interceptor() {
3661
+ interceptorCalls++;
3662
+ return 123;
3663
+ }
3664
+ interceptor.$stateful = true;
3665
+
3666
+ scope.$watch($parse("::a", interceptor));
3667
+
3668
+ interceptorCalls = 0;
3669
+ scope.$digest();
3670
+ expect(interceptorCalls).not.toBe(0);
3671
+
3672
+ interceptorCalls = 0;
3673
+ scope.$digest();
3674
+ expect(interceptorCalls).not.toBe(0);
3675
+ });
3676
+
3677
+ it("should always be invoked if flagged as $stateful when wrapping one-time with inputs", () => {
3678
+ filterProvider.register("identity", valueFn(identity));
3679
+
3680
+ let interceptorCalls = 0;
3681
+ function interceptor() {
3682
+ interceptorCalls++;
3683
+ return 123;
3684
+ }
3685
+ interceptor.$stateful = true;
3686
+
3687
+ scope.$watch($parse("::a | identity", interceptor));
3688
+
3689
+ interceptorCalls = 0;
3690
+ scope.$digest();
3691
+ expect(interceptorCalls).not.toBe(0);
3692
+
3693
+ interceptorCalls = 0;
3694
+ scope.$digest();
3695
+ expect(interceptorCalls).not.toBe(0);
3696
+ });
3697
+
3698
+ it("should always be invoked if flagged as $stateful when wrapping one-time literal", () => {
3699
+ let interceptorCalls = 0;
3700
+ function interceptor() {
3701
+ interceptorCalls++;
3702
+ return 123;
3703
+ }
3704
+ interceptor.$stateful = true;
3705
+
3706
+ scope.$watch($parse("::[a]", interceptor));
3707
+
3708
+ interceptorCalls = 0;
3709
+ scope.$digest();
3710
+ expect(interceptorCalls).not.toBe(0);
3711
+
3712
+ interceptorCalls = 0;
3713
+ scope.$digest();
3714
+ expect(interceptorCalls).not.toBe(0);
3715
+ });
3716
+
3717
+ it("should not be invoked unless the input changes", () => {
3718
+ let called = false;
3719
+ function interceptor(v) {
3720
+ called = true;
3721
+ return v;
3722
+ }
3723
+ scope.$watch($parse("a", interceptor));
3724
+ scope.$watch($parse("a + b", interceptor));
3725
+ scope.a = scope.b = 0;
3726
+ scope.$digest();
3727
+ expect(called).toBe(true);
3728
+
3729
+ called = false;
3730
+ scope.$digest();
3731
+ expect(called).toBe(false);
3732
+
3733
+ scope.a++;
3734
+ scope.$digest();
3735
+ expect(called).toBe(true);
3736
+ });
3737
+
3738
+ it("should always be invoked if inputs are non-primitive", () => {
3739
+ let called = false;
3740
+ function interceptor(v) {
3741
+ called = true;
3742
+ return v.sub;
3743
+ }
3744
+
3745
+ scope.$watch($parse("[o]", interceptor));
3746
+ scope.o = { sub: 1 };
3747
+
3748
+ called = false;
3749
+ scope.$digest();
3750
+ expect(called).toBe(true);
3751
+
3752
+ called = false;
3753
+ scope.$digest();
3754
+ expect(called).toBe(true);
3755
+ });
3756
+
3757
+ it("should not be invoked unless the input.valueOf() changes even if the instance changes", () => {
3758
+ let called = false;
3759
+ function interceptor(v) {
3760
+ called = true;
3761
+ return v;
3762
+ }
3763
+ scope.$watch($parse("a", interceptor));
3764
+ scope.a = new Date();
3765
+ scope.$digest();
3766
+ expect(called).toBe(true);
3767
+
3768
+ called = false;
3769
+ scope.a = new Date(scope.a.valueOf());
3770
+ scope.$digest();
3771
+ expect(called).toBe(false);
3772
+ });
3773
+
3774
+ it("should be invoked if input.valueOf() changes even if the instance does not", () => {
3775
+ let called = false;
3776
+ function interceptor(v) {
3777
+ called = true;
3778
+ return v;
3779
+ }
3780
+ scope.$watch($parse("a", interceptor));
3781
+ scope.a = new Date();
3782
+ scope.$digest();
3783
+ expect(called).toBe(true);
3784
+
3785
+ called = false;
3786
+ scope.a.setTime(scope.a.getTime() + 1);
3787
+ scope.$digest();
3788
+ expect(called).toBe(true);
3789
+ });
3790
+
3791
+ it("should be invoked when the expression is `undefined`", () => {
3792
+ let called = false;
3793
+ function interceptor(v) {
3794
+ called = true;
3795
+ return v;
3796
+ }
3797
+ scope.$watch($parse(undefined, interceptor));
3798
+ scope.$digest();
3799
+ expect(called).toBe(true);
3800
+ });
3801
+
3802
+ it("should not affect when a one-time binding becomes stable", () => {
3803
+ scope.$watch($parse("::x"));
3804
+ scope.$watch($parse("::x", identity));
3805
+ scope.$watch($parse("::x", () => 1)); // interceptor that returns non-undefined
3806
+
3807
+ scope.$digest();
3808
+ expect(scope.$$watchersCount).toBe(3);
3809
+
3810
+ scope.x = 1;
3811
+ scope.$digest();
3812
+ expect(scope.$$watchersCount).toBe(0);
3813
+ });
3814
+
3815
+ it("should not affect when a one-time literal binding becomes stable", () => {
3816
+ scope.$watch($parse("::[x]"));
3817
+ scope.$watch($parse("::[x]", identity));
3818
+ scope.$watch($parse("::[x]", () => 1)); // interceptor that returns non-literal
3819
+
3820
+ scope.$digest();
3821
+ expect(scope.$$watchersCount).toBe(3);
3822
+
3823
+ scope.x = 1;
3824
+ scope.$digest();
3825
+ expect(scope.$$watchersCount).toBe(0);
3826
+ });
3827
+
3828
+ it("should watch the intercepted value of one-time bindings", () => {
3829
+ scope.$watch(
3830
+ $parse("::{x:x, y:y}", (lit) => lit.x),
3831
+ (val) => logs.push(val),
3832
+ );
3833
+
3834
+ scope.$apply();
3835
+ expect(logs[0]).toBeUndefined();
3836
+
3837
+ scope.$apply("x = 1");
3838
+ expect(logs[1]).toEqual(1);
3839
+
3840
+ scope.$apply("x = 2; y=1");
3841
+ expect(logs[2]).toEqual(2);
3842
+
3843
+ scope.$apply("x = 1; y=2");
3844
+ expect(logs[3]).toBeUndefined();
3845
+ });
3846
+
3847
+ it("should watch the intercepted value of one-time bindings in nested interceptors", () => {
3848
+ scope.$watch(
3849
+ $parse(
3850
+ $parse("::{x:x, y:y}", (lit) => lit.x),
3851
+ identity,
3852
+ ),
3853
+ (val) => logs.push(val),
3854
+ );
3855
+
3856
+ scope.$apply();
3857
+ expect(logs[0]).toBeUndefined();
3858
+
3859
+ scope.$apply("x = 1");
3860
+ expect(logs[1]).toEqual(1);
3861
+
3862
+ scope.$apply("x = 2; y=1");
3863
+ expect(logs[2]).toEqual(2);
3864
+ expect(logs[3]).toBeUndefined();
3865
+ });
3866
+
3867
+ it("should nest interceptors around eachother, not around the intercepted", () => {
3868
+ function origin() {
3869
+ return 0;
3870
+ }
3871
+
3872
+ let fn = origin;
3873
+ function addOne(n) {
3874
+ return n + 1;
3875
+ }
3876
+
3877
+ fn = $parse(fn, addOne);
3878
+ expect(fn.$$intercepted).toBe(origin);
3879
+ expect(fn()).toBe(1);
3880
+
3881
+ fn = $parse(fn, addOne);
3882
+ expect(fn.$$intercepted).toBe(origin);
3883
+ expect(fn()).toBe(2);
3884
+
3885
+ fn = $parse(fn, addOne);
3886
+ expect(fn.$$intercepted).toBe(origin);
3887
+ expect(fn()).toBe(3);
3888
+ });
3889
+
3890
+ it("should not propogate $$watchDelegate to the interceptor wrapped expression", () => {
3891
+ function getter(s) {
3892
+ return s.x;
3893
+ }
3894
+ getter.$$watchDelegate = getter;
3895
+
3896
+ function doubler(v) {
3897
+ return 2 * v;
3898
+ }
3899
+
3900
+ let lastValue;
3901
+ function watcher(val) {
3902
+ lastValue = val;
3903
+ }
3904
+ scope.$watch($parse(getter, doubler), watcher);
3905
+
3906
+ scope.$apply("x = 1");
3907
+ expect(lastValue).toBe(2 * 1);
3908
+
3909
+ scope.$apply("x = 123");
3910
+ expect(lastValue).toBe(2 * 123);
3911
+ });
3912
+ });
3913
+
3914
+ describe("literals", () => {
3915
+ beforeEach(() => {
3916
+ createInjector([
3917
+ "ng",
3918
+ function ($filterProvider) {
3919
+ filterProvider = $filterProvider;
3920
+ },
3921
+ ]).invoke((_$rootScope_, _$parse_) => {
3922
+ scope = _$rootScope_;
3923
+ $parse = _$parse_;
3924
+ });
3925
+ logs = [];
3926
+ });
3927
+
3928
+ it("should support watching", () => {
3929
+ let lastVal = NaN;
3930
+ let callCount = 0;
3931
+ const listener = function (val) {
3932
+ callCount++;
3933
+ lastVal = val;
3934
+ };
3935
+
3936
+ scope.$watch("{val: val}", listener);
3937
+
3938
+ scope.$apply("val = 1");
3939
+ expect(callCount).toBe(1);
3940
+ expect(lastVal).toEqual({ val: 1 });
3941
+
3942
+ scope.$apply("val = []");
3943
+ expect(callCount).toBe(2);
3944
+ expect(lastVal).toEqual({ val: [] });
3945
+
3946
+ scope.$apply("val = []");
3947
+ expect(callCount).toBe(3);
3948
+ expect(lastVal).toEqual({ val: [] });
3949
+
3950
+ scope.$apply("val = {}");
3951
+ expect(callCount).toBe(4);
3952
+ expect(lastVal).toEqual({ val: {} });
3953
+ });
3954
+
3955
+ it("should only watch the direct inputs", () => {
3956
+ let lastVal = NaN;
3957
+ let callCount = 0;
3958
+ const listener = function (val) {
3959
+ callCount++;
3960
+ lastVal = val;
3961
+ };
3962
+
3963
+ scope.$watch("{val: val}", listener);
3964
+
3965
+ scope.$apply("val = 1");
3966
+ expect(callCount).toBe(1);
3967
+ expect(lastVal).toEqual({ val: 1 });
3968
+
3969
+ scope.$apply("val = [2]");
3970
+ expect(callCount).toBe(2);
3971
+ expect(lastVal).toEqual({ val: [2] });
3972
+
3973
+ scope.$apply("val.push(3)");
3974
+ expect(callCount).toBe(2);
3975
+
3976
+ scope.$apply("val.length = 0");
3977
+ expect(callCount).toBe(2);
3978
+ });
3979
+
3980
+ it("should only watch the direct inputs when nested", () => {
3981
+ let lastVal = NaN;
3982
+ let callCount = 0;
3983
+ const listener = function (val) {
3984
+ callCount++;
3985
+ lastVal = val;
3986
+ };
3987
+
3988
+ scope.$watch("[{val: [val]}]", listener);
3989
+
3990
+ scope.$apply("val = 1");
3991
+ expect(callCount).toBe(1);
3992
+ expect(lastVal).toEqual([{ val: [1] }]);
3993
+
3994
+ scope.$apply("val = [2]");
3995
+ expect(callCount).toBe(2);
3996
+ expect(lastVal).toEqual([{ val: [[2]] }]);
3997
+
3998
+ scope.$apply("val.push(3)");
3999
+ expect(callCount).toBe(2);
4000
+
4001
+ scope.$apply("val.length = 0");
4002
+ expect(callCount).toBe(2);
4003
+ });
4004
+ });
4005
+
4006
+ describe("with non-primative input", () => {
4007
+ beforeEach(() => {
4008
+ createInjector([
4009
+ "ng",
4010
+ function ($filterProvider) {
4011
+ filterProvider = $filterProvider;
4012
+ },
4013
+ ]).invoke((_$rootScope_, _$parse_) => {
4014
+ scope = _$rootScope_;
4015
+ $parse = _$parse_;
4016
+ });
4017
+ logs = [];
4018
+ });
4019
+
4020
+ describe("that does NOT support valueOf()", () => {
4021
+ it("should not be reevaluated", () => {
4022
+ const obj = (scope.obj = {});
4023
+
4024
+ const parsed = $parse("[obj]");
4025
+ let watcherCalls = 0;
4026
+ scope.$watch(parsed, (input) => {
4027
+ expect(input[0]).toBe(obj);
4028
+ watcherCalls++;
4029
+ });
4030
+
4031
+ scope.$digest();
4032
+ expect(watcherCalls).toBe(1);
4033
+
4034
+ scope.$digest();
4035
+ expect(watcherCalls).toBe(1);
4036
+ });
4037
+ });
4038
+
4039
+ describe("that does support valueOf()", () => {
4040
+ it("should not be reevaluated", () => {
4041
+ const date = (scope.date = new Date());
4042
+
4043
+ const parsed = $parse("[date]");
4044
+ let watcherCalls = 0;
4045
+ scope.$watch(parsed, (input) => {
4046
+ expect(input[0]).toBe(date);
4047
+ watcherCalls++;
4048
+ });
4049
+
4050
+ scope.$digest();
4051
+ expect(watcherCalls).toBe(1);
4052
+
4053
+ scope.$digest();
4054
+ expect(watcherCalls).toBe(1);
4055
+ });
4056
+
4057
+ it("should be reevaluated even when valueOf() changes", () => {
4058
+ const date = (scope.date = new Date());
4059
+
4060
+ const parsed = $parse("[date]");
4061
+ let watcherCalls = 0;
4062
+ scope.$watch(parsed, (input) => {
4063
+ expect(input[0]).toBe(date);
4064
+ watcherCalls++;
4065
+ });
4066
+
4067
+ scope.$digest();
4068
+ expect(watcherCalls).toBe(1);
4069
+
4070
+ date.setYear(1901);
4071
+
4072
+ scope.$digest();
4073
+ expect(watcherCalls).toBe(2);
4074
+ });
4075
+
4076
+ it("should not be reevaluated when the instance changes but valueOf() does not", () => {
4077
+ scope.date = new Date(1234567890123);
4078
+
4079
+ const parsed = $parse("[date]");
4080
+ let watcherCalls = 0;
4081
+ scope.$watch(parsed, (input) => {
4082
+ watcherCalls++;
4083
+ });
4084
+
4085
+ scope.$digest();
4086
+ expect(watcherCalls).toBe(1);
4087
+
4088
+ scope.date = new Date(1234567890123);
4089
+ scope.$digest();
4090
+ expect(watcherCalls).toBe(1);
4091
+ });
4092
+
4093
+ it("should be reevaluated when the instance does not change but valueOf() does", () => {
4094
+ scope.date = new Date(1234567890123);
4095
+
4096
+ const parsed = $parse("[date]");
4097
+ let watcherCalls = 0;
4098
+ scope.$watch(parsed, (input) => {
4099
+ watcherCalls++;
4100
+ });
4101
+
4102
+ scope.$digest();
4103
+ expect(watcherCalls).toBe(1);
4104
+
4105
+ scope.date.setTime(scope.date.getTime() + 1);
4106
+ scope.$digest();
4107
+ expect(watcherCalls).toBe(2);
4108
+ });
4109
+ });
4110
+
4111
+ it("should continue with the evaluation of the expression without invoking computed parts", () => {
4112
+ let value = "foo";
4113
+ const spy = jasmine.createSpy();
4114
+
4115
+ spy.and.callFake(() => value);
4116
+ scope.foo = spy;
4117
+ scope.$watch("foo()");
4118
+ scope.$digest();
4119
+ expect(spy).toHaveBeenCalledTimes(2);
4120
+ scope.$digest();
4121
+ expect(spy).toHaveBeenCalledTimes(3);
4122
+ value = "bar";
4123
+ scope.$digest();
4124
+ expect(spy).toHaveBeenCalledTimes(5);
4125
+ });
4126
+
4127
+ it("should invoke all statements in multi-statement expressions", () => {
4128
+ let lastVal = NaN;
4129
+ const listener = function (val) {
4130
+ lastVal = val;
4131
+ };
4132
+
4133
+ scope.setBarToOne = false;
4134
+ scope.bar = 0;
4135
+ scope.two = 2;
4136
+ scope.foo = function () {
4137
+ if (scope.setBarToOne) scope.bar = 1;
4138
+ };
4139
+ scope.$watch("foo(); bar + two", listener);
4140
+
4141
+ scope.$digest();
4142
+ expect(lastVal).toBe(2);
4143
+
4144
+ scope.bar = 2;
4145
+ scope.$digest();
4146
+ expect(lastVal).toBe(4);
4147
+
4148
+ scope.setBarToOne = true;
4149
+ scope.$digest();
4150
+ expect(lastVal).toBe(3);
4151
+ });
4152
+
4153
+ it("should watch the left side of assignments", () => {
4154
+ let lastVal = NaN;
4155
+ const listener = function (val) {
4156
+ lastVal = val;
4157
+ };
4158
+
4159
+ const objA = {};
4160
+ const objB = {};
4161
+
4162
+ scope.$watch("curObj.value = input", () => {});
4163
+
4164
+ scope.curObj = objA;
4165
+ scope.input = 1;
4166
+ scope.$digest();
4167
+ expect(objA.value).toBe(scope.input);
4168
+
4169
+ scope.curObj = objB;
4170
+ scope.$digest();
4171
+ expect(objB.value).toBe(scope.input);
4172
+
4173
+ scope.input = 2;
4174
+ scope.$digest();
4175
+ expect(objB.value).toBe(scope.input);
4176
+ });
4177
+
4178
+ it("should watch ES6 object computed property changes", () => {
4179
+ let count = 0;
4180
+ let lastValue;
4181
+
4182
+ scope.$watch("{[a]: true}", (val) => {
4183
+ count++;
4184
+ lastValue = val;
4185
+ });
4186
+
4187
+ scope.$digest();
4188
+ expect(count).toBe(1);
4189
+ expect(lastValue).toEqual({ undefined: true });
4190
+
4191
+ scope.$digest();
4192
+ expect(count).toBe(1);
4193
+ expect(lastValue).toEqual({ undefined: true });
4194
+
4195
+ scope.a = true;
4196
+ scope.$digest();
4197
+ expect(count).toBe(2);
4198
+ expect(lastValue).toEqual({ true: true });
4199
+
4200
+ scope.a = "abc";
4201
+ scope.$digest();
4202
+ expect(count).toBe(3);
4203
+ expect(lastValue).toEqual({ abc: true });
4204
+
4205
+ scope.a = undefined;
4206
+ scope.$digest();
4207
+ expect(count).toBe(4);
4208
+ expect(lastValue).toEqual({ undefined: true });
4209
+ });
4210
+
4211
+ it("should not shallow-watch ES6 object computed properties in case of stateful toString", () => {
4212
+ let count = 0;
4213
+ let lastValue;
4214
+
4215
+ scope.$watch("{[a]: true}", (val) => {
4216
+ count++;
4217
+ lastValue = val;
4218
+ });
4219
+
4220
+ scope.a = {
4221
+ toString() {
4222
+ return this.b;
4223
+ },
4224
+ };
4225
+ scope.a.b = 1;
4226
+
4227
+ // TODO: would be great if it didn't throw!
4228
+ expect(() => {
4229
+ scope.$apply();
4230
+ }).toThrowError(/infdig/);
4231
+ expect(lastValue).toEqual({ 1: true });
4232
+
4233
+ expect(() => {
4234
+ scope.$apply("a.b = 2");
4235
+ }).toThrowError(/infdig/);
4236
+ expect(lastValue).toEqual({ 2: true });
4237
+ });
4238
+
4239
+ describe("locals", () => {
4240
+ it("should expose local variables", () => {
4241
+ expect($parse("a")({ a: 0 }, { a: 1 })).toEqual(1);
4242
+ expect(
4243
+ $parse("add(a,b)")(
4244
+ {
4245
+ b: 1,
4246
+ add(a, b) {
4247
+ return a + b;
4248
+ },
4249
+ },
4250
+ { a: 2 },
4251
+ ),
4252
+ ).toEqual(3);
4253
+ });
4254
+
4255
+ it("should expose traverse locals", () => {
4256
+ expect($parse("a.b")({ a: { b: 0 } }, { a: { b: 1 } })).toEqual(1);
4257
+ expect($parse("a.b")({ a: null }, { a: { b: 1 } })).toEqual(1);
4258
+ expect($parse("a.b")({ a: { b: 0 } }, { a: null })).toEqual(undefined);
4259
+ expect($parse("a.b.c")({ a: null }, { a: { b: { c: 1 } } })).toEqual(1);
4260
+ });
4261
+
4262
+ it("should not use locals to resolve object properties", () => {
4263
+ expect($parse("a[0].b")({ a: [{ b: "scope" }] }, { b: "locals" })).toBe(
4264
+ "scope",
4265
+ );
4266
+ expect(
4267
+ $parse('a[0]["b"]')({ a: [{ b: "scope" }] }, { b: "locals" }),
4268
+ ).toBe("scope");
4269
+ expect(
4270
+ $parse("a[0][0].b")({ a: [[{ b: "scope" }]] }, { b: "locals" }),
4271
+ ).toBe("scope");
4272
+ expect(
4273
+ $parse("a[0].b.c")(
4274
+ { a: [{ b: { c: "scope" } }] },
4275
+ { b: { c: "locals" } },
4276
+ ),
4277
+ ).toBe("scope");
4278
+ });
4279
+
4280
+ it("should assign directly to locals when the local property exists", () => {
4281
+ const s = {};
4282
+ const l = {};
4283
+
4284
+ $parse("a = 1")(s, l);
4285
+ expect(s.a).toBe(1);
4286
+ expect(l.a).toBeUndefined();
4287
+
4288
+ l.a = 2;
4289
+ $parse("a = 0")(s, l);
4290
+ expect(s.a).toBe(1);
4291
+ expect(l.a).toBe(0);
4292
+
4293
+ $parse("toString = 1")(s, l);
4294
+ expect(isFunction(s.toString)).toBe(true);
4295
+ expect(l.toString).toBe(1);
4296
+ });
4297
+
4298
+ it("should overwrite undefined / null scope properties when assigning", () => {
4299
+ let scope;
4300
+
4301
+ scope = {};
4302
+ $parse("a.b = 1")(scope);
4303
+ $parse('c["d"] = 2')(scope);
4304
+ expect(scope).toEqual({ a: { b: 1 }, c: { d: 2 } });
4305
+
4306
+ scope = { a: {} };
4307
+ $parse("a.b.c = 1")(scope);
4308
+ $parse('a.c["d"] = 2')(scope);
4309
+ expect(scope).toEqual({ a: { b: { c: 1 }, c: { d: 2 } } });
4310
+
4311
+ scope = { a: undefined, c: undefined };
4312
+ $parse("a.b = 1")(scope);
4313
+ $parse('c["d"] = 2')(scope);
4314
+ expect(scope).toEqual({ a: { b: 1 }, c: { d: 2 } });
4315
+
4316
+ scope = { a: { b: undefined, c: undefined } };
4317
+ $parse("a.b.c = 1")(scope);
4318
+ $parse('a.c["d"] = 2')(scope);
4319
+ expect(scope).toEqual({ a: { b: { c: 1 }, c: { d: 2 } } });
4320
+
4321
+ scope = { a: null, c: null };
4322
+ $parse("a.b = 1")(scope);
4323
+ $parse('c["d"] = 2')(scope);
4324
+ expect(scope).toEqual({ a: { b: 1 }, c: { d: 2 } });
4325
+
4326
+ scope = { a: { b: null, c: null } };
4327
+ $parse("a.b.c = 1")(scope);
4328
+ $parse('a.c["d"] = 2')(scope);
4329
+ expect(scope).toEqual({ a: { b: { c: 1 }, c: { d: 2 } } });
4330
+ });
4331
+
4332
+ [0, false, "", NaN].forEach((falsyValue) => {
4333
+ "should not overwrite $prop scope properties when assigning",
4334
+ () => {
4335
+ let scope;
4336
+
4337
+ scope = { a: falsyValue, c: falsyValue };
4338
+ tryParseAndIgnoreException("a.b = 1");
4339
+ tryParseAndIgnoreException('c["d"] = 2');
4340
+ expect(scope).toEqual({ a: falsyValue, c: falsyValue });
4341
+
4342
+ scope = { a: { b: falsyValue, c: falsyValue } };
4343
+ tryParseAndIgnoreException("a.b.c = 1");
4344
+ tryParseAndIgnoreException('a.c["d"] = 2');
4345
+ expect(scope).toEqual({ a: { b: falsyValue, c: falsyValue } });
4346
+
4347
+ // Helpers
4348
+ //
4349
+ // Normally assigning property on a primitive should throw exception in strict mode
4350
+ // and silently fail in non-strict mode, IE seems to always have the non-strict-mode behavior,
4351
+ // so if we try to use 'expect(() => {$parse('a.b=1')({a:false});).toThrow()' for testing
4352
+ // the test will fail in case of IE because it will not throw exception, and if we just use
4353
+ // '$parse('a.b=1')({a:false})' the test will fail because it will throw exception in case of Chrome
4354
+ // so we use tryParseAndIgnoreException helper to catch the exception silently for all cases.
4355
+ //
4356
+ function tryParseAndIgnoreException(expression) {
4357
+ try {
4358
+ $parse(expression)(scope);
4359
+ } catch (error) {
4360
+ /* ignore exception */
4361
+ }
4362
+ }
4363
+ };
4364
+ });
4365
+ });
4366
+
4367
+ describe("literal", () => {
4368
+ it("should mark scalar value expressions as literal", () => {
4369
+ expect($parse("0").literal).toBe(true);
4370
+ expect($parse('"hello"').literal).toBe(true);
4371
+ expect($parse("true").literal).toBe(true);
4372
+ expect($parse("false").literal).toBe(true);
4373
+ expect($parse("null").literal).toBe(true);
4374
+ expect($parse("undefined").literal).toBe(true);
4375
+ });
4376
+
4377
+ it("should mark array expressions as literal", () => {
4378
+ expect($parse("[]").literal).toBe(true);
4379
+ expect($parse("[1, 2, 3]").literal).toBe(true);
4380
+ expect($parse("[1, identifier]").literal).toBe(true);
4381
+ });
4382
+
4383
+ it("should mark object expressions as literal", () => {
4384
+ expect($parse("{}").literal).toBe(true);
4385
+ expect($parse("{x: 1}").literal).toBe(true);
4386
+ expect($parse("{foo: bar}").literal).toBe(true);
4387
+ });
4388
+
4389
+ it("should not mark function calls or operator expressions as literal", () => {
4390
+ expect($parse("1 + 1").literal).toBe(false);
4391
+ expect($parse("call()").literal).toBe(false);
4392
+ expect($parse("[].length").literal).toBe(false);
4393
+ });
4394
+ });
4395
+
4396
+ describe("constant", () => {
4397
+ it("should mark an empty expressions as constant", () => {
4398
+ expect($parse("").constant).toBe(true);
4399
+ expect($parse(" ").constant).toBe(true);
4400
+ expect($parse("::").constant).toBe(true);
4401
+ expect($parse(":: ").constant).toBe(true);
4402
+ });
4403
+
4404
+ it("should mark scalar value expressions as constant", () => {
4405
+ expect($parse("12.3").constant).toBe(true);
4406
+ expect($parse('"string"').constant).toBe(true);
4407
+ expect($parse("true").constant).toBe(true);
4408
+ expect($parse("false").constant).toBe(true);
4409
+ expect($parse("null").constant).toBe(true);
4410
+ expect($parse("undefined").constant).toBe(true);
4411
+ });
4412
+
4413
+ it("should mark arrays as constant if they only contain constant elements", () => {
4414
+ expect($parse("[]").constant).toBe(true);
4415
+ expect($parse("[1, 2, 3]").constant).toBe(true);
4416
+ expect($parse('["string", null]').constant).toBe(true);
4417
+ expect($parse("[[]]").constant).toBe(true);
4418
+ expect($parse("[1, [2, 3], {4: 5}]").constant).toBe(true);
4419
+ });
4420
+
4421
+ it("should not mark arrays as constant if they contain any non-constant elements", () => {
4422
+ expect($parse("[foo]").constant).toBe(false);
4423
+ expect($parse("[x + 1]").constant).toBe(false);
4424
+ expect($parse("[bar[0]]").constant).toBe(false);
4425
+ });
4426
+
4427
+ it("should mark complex expressions involving constant values as constant", () => {
4428
+ expect($parse("!true").constant).toBe(true);
4429
+ expect($parse("-42").constant).toBe(true);
4430
+ expect($parse("1 - 1").constant).toBe(true);
4431
+ expect($parse('"foo" + "bar"').constant).toBe(true);
4432
+ expect($parse("5 != null").constant).toBe(true);
4433
+ expect($parse("{standard: 4/3, wide: 16/9}").constant).toBe(true);
4434
+ expect($parse("{[standard]: 4/3, wide: 16/9}").constant).toBe(false);
4435
+ expect($parse('{["key"]: 1}').constant).toBe(true);
4436
+ expect($parse("[0].length").constant).toBe(true);
4437
+ expect($parse("[0][0]").constant).toBe(true);
4438
+ expect($parse("{x: 1}.x").constant).toBe(true);
4439
+ expect($parse('{x: 1}["x"]').constant).toBe(true);
4440
+ });
4441
+
4442
+ it("should not mark any expression involving variables or function calls as constant", () => {
4443
+ expect($parse("true.toString()").constant).toBe(false);
4444
+ expect($parse("foo(1, 2, 3)").constant).toBe(false);
4445
+ expect($parse('"name" + id').constant).toBe(false);
4446
+ });
4447
+ });
4448
+
4449
+ describe("null/undefined in expressions", () => {
4450
+ // simpleGetterFn1
4451
+ it("should return null for `a` where `a` is null", () => {
4452
+ $rootScope.a = null;
4453
+ expect($rootScope.$eval("a")).toBe(null);
4454
+ });
4455
+
4456
+ it("should return undefined for `a` where `a` is undefined", () => {
4457
+ expect($rootScope.$eval("a")).toBeUndefined();
4458
+ });
4459
+
4460
+ // simpleGetterFn2
4461
+ it("should return undefined for properties of `null` constant", () => {
4462
+ expect($rootScope.$eval("null.a")).toBeUndefined();
4463
+ });
4464
+
4465
+ it("should return undefined for properties of `null` values", () => {
4466
+ $rootScope.a = null;
4467
+ expect($rootScope.$eval("a.b")).toBeUndefined();
4468
+ });
4469
+
4470
+ it("should return null for `a.b` where `b` is null", () => {
4471
+ $rootScope.a = { b: null };
4472
+ expect($rootScope.$eval("a.b")).toBe(null);
4473
+ });
4474
+
4475
+ // cspSafeGetter && pathKeys.length < 6 || pathKeys.length > 2
4476
+ it("should return null for `a.b.c.d.e` where `e` is null", () => {
4477
+ $rootScope.a = { b: { c: { d: { e: null } } } };
4478
+ expect($rootScope.$eval("a.b.c.d.e")).toBe(null);
4479
+ });
4480
+
4481
+ it("should return undefined for `a.b.c.d.e` where `d` is null", () => {
4482
+ $rootScope.a = { b: { c: { d: null } } };
4483
+ expect($rootScope.$eval("a.b.c.d.e")).toBeUndefined();
4484
+ });
4485
+
4486
+ // cspSafeGetter || pathKeys.length > 6
4487
+ it("should return null for `a.b.c.d.e.f.g` where `g` is null", () => {
4488
+ $rootScope.a = { b: { c: { d: { e: { f: { g: null } } } } } };
4489
+ expect($rootScope.$eval("a.b.c.d.e.f.g")).toBe(null);
4490
+ });
4491
+
4492
+ it("should return undefined for `a.b.c.d.e.f.g` where `f` is null", () => {
4493
+ $rootScope.a = { b: { c: { d: { e: { f: null } } } } };
4494
+ expect($rootScope.$eval("a.b.c.d.e.f.g")).toBeUndefined();
4495
+ });
4496
+
4497
+ it("should return undefined if the return value of a function invocation is undefined", () => {
4498
+ $rootScope.fn = function () {};
4499
+ expect($rootScope.$eval("fn()")).toBeUndefined();
4500
+ });
4501
+
4502
+ it("should ignore undefined values when doing addition/concatenation", () => {
4503
+ $rootScope.fn = function () {};
4504
+ expect($rootScope.$eval('foo + "bar" + fn()')).toBe("bar");
4505
+ });
4506
+
4507
+ it("should treat properties named null/undefined as normal properties", () => {
4508
+ expect(
4509
+ $rootScope.$eval("a.null.undefined.b", {
4510
+ a: { null: { undefined: { b: 1 } } },
4511
+ }),
4512
+ ).toBe(1);
4513
+ });
4514
+
4515
+ it("should not allow overriding null/undefined keywords", () => {
4516
+ expect($rootScope.$eval("null.a", { null: { a: 42 } })).toBeUndefined();
4517
+ });
4518
+
4519
+ it("should allow accessing null/undefined properties on `this`", () => {
4520
+ $rootScope.null = { a: 42 };
4521
+ expect($rootScope.$eval("this.null.a")).toBe(42);
4522
+ });
4523
+
4524
+ it("should allow accessing $locals", () => {
4525
+ $rootScope.foo = "foo";
4526
+ $rootScope.bar = "bar";
4527
+ $rootScope.$locals = "foo";
4528
+ const locals = { foo: 42 };
4529
+ expect($rootScope.$eval("$locals")).toBeUndefined();
4530
+ expect($rootScope.$eval("$locals.foo")).toBeUndefined();
4531
+ expect($rootScope.$eval("this.$locals")).toBe("foo");
4532
+ expect(() => {
4533
+ $rootScope.$eval("$locals = {}");
4534
+ }).toThrow();
4535
+ expect(() => {
4536
+ $rootScope.$eval("$locals.bar = 23");
4537
+ }).toThrow();
4538
+ expect($rootScope.$eval("$locals", locals)).toBe(locals);
4539
+ expect($rootScope.$eval("$locals.foo", locals)).toBe(42);
4540
+ expect($rootScope.$eval("this.$locals", locals)).toBe("foo");
4541
+ expect(() => {
4542
+ $rootScope.$eval("$locals = {}", locals);
4543
+ }).toThrow();
4544
+ expect($rootScope.$eval("$locals.bar = 23", locals)).toEqual(23);
4545
+ expect(locals.bar).toBe(23);
4546
+ });
4547
+ });
4548
+
4549
+ [true, false].forEach((cspEnabled) => {
4550
+ describe(`custom identifiers (csp: ${cspEnabled})`, () => {
4551
+ const isIdentifierStartRe = /[#a-z]/;
4552
+ const isIdentifierContinueRe = /[-a-z]/;
4553
+ let isIdentifierStartFn;
4554
+ let isIdentifierContinueFn;
4555
+ let scope;
4556
+
4557
+ beforeEach(() => {
4558
+ createInjector([
4559
+ "ng",
4560
+ function ($parseProvider) {
4561
+ isIdentifierStartFn = jasmine
4562
+ .createSpy("isIdentifierStart")
4563
+ .and.callFake((ch, cp) => isIdentifierStartRe.test(ch));
4564
+ isIdentifierContinueFn = jasmine
4565
+ .createSpy("isIdentifierContinue")
4566
+ .and.callFake((ch, cp) => isIdentifierContinueRe.test(ch));
4567
+
4568
+ $parseProvider.setIdentifierFns(
4569
+ isIdentifierStartFn,
4570
+ isIdentifierContinueFn,
4571
+ );
4572
+ csp().noUnsafeEval = cspEnabled;
4573
+ },
4574
+ ]).invoke((_$rootScope_) => {
4575
+ scope = _$rootScope_;
4576
+ });
4577
+ });
4578
+
4579
+ it("should allow specifying a custom `isIdentifierStart/Continue` functions", () => {
4580
+ scope.x = {};
4581
+
4582
+ scope["#foo"] = "foo";
4583
+ scope.x["#foo"] = "foo";
4584
+ expect(scope.$eval("#foo")).toBe("foo");
4585
+ expect(scope.$eval("x.#foo")).toBe("foo");
4586
+
4587
+ scope["bar--"] = 42;
4588
+ scope.x["bar--"] = 42;
4589
+ expect(scope.$eval("bar--")).toBe(42);
4590
+ expect(scope.$eval("x.bar--")).toBe(42);
4591
+ expect(scope["bar--"]).toBe(42);
4592
+ expect(scope.x["bar--"]).toBe(42);
4593
+
4594
+ scope["#-"] = "baz";
4595
+ scope.x["#-"] = "baz";
4596
+ expect(scope.$eval("#-")).toBe("baz");
4597
+ expect(scope.$eval("x.#-")).toBe("baz");
4598
+
4599
+ expect(() => {
4600
+ scope.$eval("##");
4601
+ }).toThrow();
4602
+ expect(() => {
4603
+ scope.$eval("x.##");
4604
+ }).toThrow();
4605
+
4606
+ expect(() => {
4607
+ scope.$eval("--");
4608
+ }).toThrow();
4609
+ expect(() => {
4610
+ scope.$eval("x.--");
4611
+ }).toThrow();
4612
+ });
4613
+
4614
+ it("should pass the character and codepoint to the custom functions", () => {
4615
+ scope.$eval("#-");
4616
+ expect(isIdentifierStartFn).toHaveBeenCalledOnceWith(
4617
+ "#",
4618
+ "#".charCodeAt(0),
4619
+ );
4620
+ expect(isIdentifierContinueFn).toHaveBeenCalledOnceWith(
4621
+ "-",
4622
+ "-".charCodeAt(0),
4623
+ );
4624
+
4625
+ isIdentifierStartFn.calls.reset();
4626
+ isIdentifierContinueFn.calls.reset();
4627
+
4628
+ scope.$eval("#.foo.#-.bar-");
4629
+ expect(isIdentifierStartFn).toHaveBeenCalledTimes(7);
4630
+ expect(isIdentifierStartFn.calls.allArgs()).toEqual([
4631
+ ["#", "#".charCodeAt(0)],
4632
+ [".", ".".charCodeAt(0)],
4633
+ ["f", "f".charCodeAt(0)],
4634
+ [".", ".".charCodeAt(0)],
4635
+ ["#", "#".charCodeAt(0)],
4636
+ [".", ".".charCodeAt(0)],
4637
+ ["b", "b".charCodeAt(0)],
4638
+ ]);
4639
+ expect(isIdentifierContinueFn).toHaveBeenCalledTimes(9);
4640
+ expect(isIdentifierContinueFn.calls.allArgs()).toEqual([
4641
+ [".", ".".charCodeAt(0)],
4642
+ ["o", "o".charCodeAt(0)],
4643
+ ["o", "o".charCodeAt(0)],
4644
+ [".", ".".charCodeAt(0)],
4645
+ ["-", "-".charCodeAt(0)],
4646
+ [".", ".".charCodeAt(0)],
4647
+ ["a", "a".charCodeAt(0)],
4648
+ ["r", "r".charCodeAt(0)],
4649
+ ["-", "-".charCodeAt(0)],
4650
+ ]);
4651
+ });
4652
+ });
4653
+ });
4654
+ });
4655
+ });