@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,3810 @@
1
+ import { publishExternalAPI } from "../../../src/public";
2
+ import { createInjector } from "../../../src/injector";
3
+ import { jqLite } from "../../../src/jqLite";
4
+ import {
5
+ EMAIL_REGEXP,
6
+ ISO_DATE_REGEXP,
7
+ URL_REGEXP,
8
+ } from "../../../src/directive/input";
9
+ import { forEach } from "../../../src/core/utils";
10
+
11
+ describe("input", () => {
12
+ let $compile;
13
+ let scope;
14
+
15
+ beforeEach(() => {
16
+ publishExternalAPI().decorator("$exceptionHandler", function () {
17
+ return (exception, cause) => {
18
+ throw new Error(exception.message);
19
+ };
20
+ });
21
+ createInjector(["ng"]).invoke((_$compile_, $rootScope) => {
22
+ $compile = _$compile_;
23
+ scope = $rootScope.$new();
24
+ });
25
+ });
26
+
27
+ it("should bind to a model", () => {
28
+ const inputElm = $compile(
29
+ '<input type="text" ng-model="name" name="alias" ng-change="change()" />',
30
+ )(scope);
31
+
32
+ scope.$apply("name = 'misko'");
33
+
34
+ expect(inputElm.val()).toBe("misko");
35
+ });
36
+
37
+ it('should update the model on "blur" event', () => {
38
+ const inputElm = $compile(
39
+ '<input type="text" ng-model="name" name="alias" ng-change="change()" />',
40
+ )(scope);
41
+ inputElm[0].setAttribute("value", "adam");
42
+ inputElm[0].dispatchEvent(new Event("change"));
43
+ expect(scope.name).toEqual("adam");
44
+ });
45
+
46
+ it("should not add the property to the scope if name is unspecified", () => {
47
+ $compile('<input type="text" ng-model="name">')(scope);
48
+
49
+ expect(scope.name).toBeUndefined();
50
+ });
51
+
52
+ it("should not set the `val` property when the value is equal to the current value", () => {
53
+ // This is a workaround for Firefox validation. Look at #12102.
54
+ const input = jqLite('<input type="text" ng-model="foo" required/>');
55
+ let setterCalls = 0;
56
+ scope.foo = "";
57
+ Object.defineProperty(input[0], "value", {
58
+ get() {
59
+ return "";
60
+ },
61
+ set() {
62
+ setterCalls++;
63
+ },
64
+ });
65
+ $compile(input)(scope);
66
+ scope.$digest();
67
+ expect(setterCalls).toBe(0);
68
+ });
69
+
70
+ describe("compositionevents", () => {
71
+ it('should not update the model between "compositionstart" and "compositionend"', () => {
72
+ //$sniffer.android = false;
73
+
74
+ const inputElm = $compile(
75
+ '<input type="text" ng-model="name" name="alias"" />',
76
+ )(scope);
77
+ inputElm[0].setAttribute("value", "a");
78
+ inputElm[0].dispatchEvent(new Event("change"));
79
+ expect(scope.name).toEqual("a");
80
+
81
+ inputElm[0].dispatchEvent(new Event("compositionstart"));
82
+ inputElm[0].setAttribute("value", "adam");
83
+ expect(scope.name).toEqual("a");
84
+ inputElm[0].dispatchEvent(new Event("compositionend"));
85
+ inputElm[0].setAttribute("value", "adam");
86
+ expect(scope.name).toEqual("adam");
87
+ });
88
+ });
89
+
90
+ describe("interpolated names", () => {
91
+ it("should interpolate input names", () => {
92
+ scope.nameID = "47";
93
+ const inputElm = $compile(
94
+ '<form name="form"><input type="text" ng-model="name" name="name{{nameID}}" /></form>',
95
+ )(scope);
96
+ expect(scope.form.name47.$pristine).toBeTruthy();
97
+ inputElm.find("input")[0].setAttribute("value", "caitp");
98
+ inputElm.find("input")[0].dispatchEvent(new Event("change"));
99
+ expect(scope.form.name47.$dirty).toBeTruthy();
100
+ });
101
+
102
+ it("should rename form controls in form when interpolated name changes", () => {
103
+ scope.nameID = "A";
104
+ const inputElm = $compile(
105
+ '<form name="form"><input type="text" ng-model="name" name="name{{nameID}}" /></form>',
106
+ )(scope);
107
+ expect(scope.form.nameA.$name).toBe("nameA");
108
+ const oldModel = scope.form.nameA;
109
+ scope.nameID = "B";
110
+ scope.$digest();
111
+ expect(scope.form.nameA).toBeUndefined();
112
+ expect(scope.form.nameB).toBe(oldModel);
113
+ expect(scope.form.nameB.$name).toBe("nameB");
114
+ });
115
+
116
+ it("should rename form controls in null form when interpolated name changes", () => {
117
+ scope.nameID = "A";
118
+ const inputElm = $compile(
119
+ '<input type="text" ng-model="name" name="name{{nameID}}" />',
120
+ )(scope);
121
+ const model = inputElm.controller("ngModel");
122
+ expect(model.$name).toBe("nameA");
123
+
124
+ scope.nameID = "B";
125
+ scope.$digest();
126
+ expect(model.$name).toBe("nameB");
127
+ });
128
+ });
129
+
130
+ describe('"change" event', () => {
131
+ let assertBrowserSupportsChangeEvent;
132
+
133
+ beforeEach(() => {
134
+ assertBrowserSupportsChangeEvent = function (inputEventSupported) {
135
+ const inputElm = $compile(
136
+ '<input type="text" ng-model="name" name="alias" />',
137
+ )(scope);
138
+
139
+ //inputElm.val("mark");
140
+ inputElm[0].setAttribute("value", "mark");
141
+ inputElm[0].dispatchEvent(new Event("change"));
142
+ expect(scope.name).toEqual("mark");
143
+ };
144
+ });
145
+
146
+ it('should update the model event if the browser does not support the "input" event', () => {
147
+ assertBrowserSupportsChangeEvent(false);
148
+ });
149
+
150
+ it(
151
+ 'should update the model event if the browser supports the "input" ' +
152
+ "event so that form auto complete works",
153
+ () => {
154
+ assertBrowserSupportsChangeEvent(true);
155
+ },
156
+ );
157
+
158
+ describe('"keydown", "paste", "cut" and "drop" events', () => {
159
+ it('should update the model on "paste" event if the input value changes', () => {
160
+ const inputElm = $compile(
161
+ '<input type="text" ng-model="name" name="alias" ng-change="change()" />',
162
+ )(scope);
163
+
164
+ inputElm[0].dispatchEvent(new Event("keydown"));
165
+ expect(inputElm[0].classList.contains("ng-pristine")).toBeTrue();
166
+
167
+ inputElm[0].setAttribute("value", "mark");
168
+ inputElm[0].dispatchEvent(new Event("paste"));
169
+ expect(scope.name).toEqual("mark");
170
+ });
171
+
172
+ it('should update the model on "drop" event if the input value changes', () => {
173
+ const inputElm = $compile(
174
+ '<input type="text" ng-model="name" name="alias" ng-change="change()" />',
175
+ )(scope);
176
+
177
+ inputElm[0].dispatchEvent(new Event("keydown"));
178
+ expect(inputElm[0].classList.contains("ng-pristine")).toBeTrue();
179
+
180
+ inputElm[0].setAttribute("value", "mark");
181
+ inputElm[0].dispatchEvent(new Event("drop"));
182
+ expect(scope.name).toEqual("mark");
183
+ });
184
+
185
+ it('should update the model on "cut" event', () => {
186
+ const inputElm = $compile(
187
+ '<input type="text" ng-model="name" name="alias" ng-change="change()" />',
188
+ )(scope);
189
+
190
+ inputElm[0].setAttribute("value", "john");
191
+ inputElm[0].dispatchEvent(new Event("cut"));
192
+ expect(scope.name).toEqual("john");
193
+ });
194
+
195
+ it("should cancel the delayed dirty if a change occurs", () => {
196
+ const inputElm = $compile('<input type="text" ng-model="name" />')(
197
+ scope,
198
+ );
199
+ const ctrl = inputElm.controller("ngModel");
200
+
201
+ inputElm[0].dispatchEvent(
202
+ new Event("keydown", { target: inputElm[0] }),
203
+ );
204
+ inputElm.val("f");
205
+ inputElm[0].dispatchEvent(new Event("change"));
206
+ expect(inputElm[0].classList.contains("ng-dirty")).toBeTrue();
207
+
208
+ ctrl.$setPristine();
209
+ scope.$apply();
210
+
211
+ expect(inputElm[0].classList.contains("ng-pristine")).toBeTrue();
212
+ });
213
+
214
+ describe("ngTrim", () => {
215
+ it("should update the model and trim the value", () => {
216
+ const inputElm = $compile(
217
+ '<input type="text" ng-model="name" name="alias" ng-change="change()" />',
218
+ )(scope);
219
+
220
+ inputElm[0].setAttribute("value", " a ");
221
+ inputElm[0].dispatchEvent(new Event("change"));
222
+ expect(scope.name).toEqual("a");
223
+ });
224
+
225
+ it("should update the model and not trim the value", () => {
226
+ const inputElm = $compile(
227
+ '<input type="text" ng-model="name" name="alias" ng-trim="false" />',
228
+ )(scope);
229
+
230
+ inputElm[0].setAttribute("value", " a ");
231
+ inputElm[0].dispatchEvent(new Event("change"));
232
+ expect(scope.name).toEqual(" a ");
233
+ });
234
+ });
235
+
236
+ it("should allow complex reference binding", () => {
237
+ const inputElm = $compile(
238
+ '<input type="text" ng-model="obj[\'abc\'].name"/>',
239
+ )(scope);
240
+
241
+ scope.$apply("obj = { abc: { name: 'Misko'} }");
242
+ expect(inputElm.val()).toEqual("Misko");
243
+ });
244
+
245
+ it("should ignore input without ngModel directive", () => {
246
+ const inputElm = $compile(
247
+ '<input type="text" name="whatever" required />',
248
+ )(scope);
249
+
250
+ inputElm[0].setAttribute("value", "");
251
+ inputElm[0].dispatchEvent(new Event("change"));
252
+ expect(inputElm[0].classList.contains("ng-valid")).toBe(false);
253
+ expect(inputElm[0].classList.contains("ng-invalid")).toBe(false);
254
+ expect(inputElm[0].classList.contains("ng-pristine")).toBe(false);
255
+ expect(inputElm[0].classList.contains("ng-dirty")).toBe(false);
256
+ });
257
+
258
+ it("should report error on assignment error", () => {
259
+ expect(() => {
260
+ const inputElm = $compile(
261
+ '<input type="text" ng-model="throw \'\'">',
262
+ )(scope);
263
+ }).toThrowError(/Syntax Error/);
264
+ });
265
+
266
+ it("should render as blank if null", () => {
267
+ const inputElm = $compile('<input type="text" ng-model="age" />')(
268
+ scope,
269
+ );
270
+
271
+ scope.$apply("age = null");
272
+
273
+ expect(scope.age).toBeNull();
274
+ expect(inputElm.val()).toEqual("");
275
+ });
276
+
277
+ it("should render 0 even if it is a number", () => {
278
+ const inputElm = $compile('<input type="text" ng-model="value" />')(
279
+ scope,
280
+ );
281
+ scope.$apply("value = 0");
282
+
283
+ expect(inputElm.val()).toBe("0");
284
+ });
285
+
286
+ it("should render the $viewValue when $modelValue is empty", () => {
287
+ const inputElm = $compile('<input type="text" ng-model="value" />')(
288
+ scope,
289
+ );
290
+
291
+ const ctrl = inputElm.controller("ngModel");
292
+
293
+ ctrl.$modelValue = null;
294
+
295
+ expect(ctrl.$isEmpty(ctrl.$modelValue)).toBe(true);
296
+
297
+ ctrl.$viewValue = "abc";
298
+ ctrl.$render();
299
+
300
+ expect(inputElm.val()).toBe("abc");
301
+ });
302
+ });
303
+
304
+ // INPUT TYPES
305
+ describe("month", () => {
306
+ // IN ANGULAR.JS month types were converted to Date object. This is not standard behavior
307
+ it("should allow a String object in format 'YYYY-MM'", () => {
308
+ const inputElm = $compile('<input type="month" ng-model="january"/>')(
309
+ scope,
310
+ );
311
+
312
+ scope.$apply(() => {
313
+ scope.january = "2013-01";
314
+ });
315
+
316
+ expect(inputElm.val()).toBe("2013-01");
317
+ });
318
+
319
+ it("should throw if the model is a Date object", () => {
320
+ const inputElm = $compile('<input type="month" ng-model="march"/>')(
321
+ scope,
322
+ );
323
+
324
+ expect(() => {
325
+ scope.$apply(() => {
326
+ scope.march = new Date(2013, 2, 1);
327
+ });
328
+ }).toThrowError(/datefmt/);
329
+ });
330
+
331
+ it("should throw if the model is a Invalid string", () => {
332
+ const inputElm = $compile('<input type="month" ng-model="march"/>')(
333
+ scope,
334
+ );
335
+
336
+ expect(() => {
337
+ scope.$apply(() => {
338
+ scope.march = "fail";
339
+ });
340
+ }).toThrowError(/datefmt/);
341
+ });
342
+
343
+ it("should not change the model if the input is an invalid month string", () => {
344
+ const inputElm = $compile('<input type="month" ng-model="value"/>')(
345
+ scope,
346
+ );
347
+
348
+ scope.$apply(() => {
349
+ scope.value = "2013-01";
350
+ });
351
+
352
+ expect(inputElm.val()).toBe("2013-01");
353
+
354
+ inputElm[0].setAttribute("value", "stuff");
355
+ inputElm[0].dispatchEvent(new Event("change"));
356
+ expect(inputElm.val()).toBe("2013-01");
357
+ expect(scope.value).toBe("2013-01");
358
+ });
359
+
360
+ it("should render as blank if null", () => {
361
+ const inputElm = $compile('<input type="month" ng-model="test" />')(
362
+ scope,
363
+ );
364
+
365
+ scope.$apply("test = null");
366
+
367
+ expect(scope.test).toBeNull();
368
+ expect(inputElm.val()).toEqual("");
369
+ });
370
+
371
+ it("should come up blank when no value specified", () => {
372
+ const inputElm = $compile('<input type="month" ng-model="test" />')(
373
+ scope,
374
+ );
375
+
376
+ expect(inputElm.val()).toBe("");
377
+
378
+ scope.$apply("test = null");
379
+
380
+ expect(scope.test).toBeNull();
381
+ expect(inputElm.val()).toBe("");
382
+ });
383
+
384
+ it("should parse empty string to null", () => {
385
+ const inputElm = $compile('<input type="month" ng-model="test" />')(
386
+ scope,
387
+ );
388
+
389
+ inputElm[0].setAttribute("value", "");
390
+ inputElm[0].dispatchEvent(new Event("change"));
391
+ expect(scope.test).toBeNull();
392
+ expect(inputElm[0].classList.contains("ng-valid")).toBeTrue();
393
+ });
394
+
395
+ it("should set scope to a string value", () => {
396
+ const inputElm = $compile('<input type="month" ng-model="value" />')(
397
+ scope,
398
+ );
399
+
400
+ inputElm[0].setAttribute("value", "2013-07");
401
+ inputElm[0].dispatchEvent(new Event("change"));
402
+ expect(scope.value).toBe("2013-07");
403
+ });
404
+
405
+ describe("min", () => {
406
+ let inputElm;
407
+ beforeEach(() => {
408
+ scope.minVal = "2013-01";
409
+ inputElm = $compile(
410
+ '<form name="form"><input type="month" ng-model="value" name="alias" min="{{ minVal }}" /></form>',
411
+ )(scope);
412
+ });
413
+
414
+ it("should invalidate", () => {
415
+ inputElm.find("input")[0].setAttribute("value", "2012-12");
416
+ inputElm.find("input")[0].dispatchEvent(new Event("change"));
417
+ expect(
418
+ inputElm.find("input")[0].classList.contains("ng-invalid"),
419
+ ).toBeTrue();
420
+ expect(scope.value).toBeFalsy();
421
+ expect(scope.form.alias.$error.min).toBeTruthy();
422
+ });
423
+
424
+ it("should validate", () => {
425
+ inputElm.find("input")[0].setAttribute("value", "2013-07");
426
+ inputElm.find("input")[0].dispatchEvent(new Event("change"));
427
+ expect(
428
+ inputElm.find("input")[0].classList.contains("ng-valid"),
429
+ ).toBeTrue();
430
+ expect(scope.value).toBe("2013-07");
431
+ expect(scope.form.alias.$error.min).toBeFalsy();
432
+ });
433
+
434
+ it("should revalidate when the min value changes", () => {
435
+ inputElm.find("input")[0].setAttribute("value", "2013-07");
436
+ expect(
437
+ inputElm.find("input")[0].classList.contains("ng-valid"),
438
+ ).toBeTrue();
439
+ expect(scope.form.alias.$error.min).toBeFalsy();
440
+
441
+ scope.$apply(() => {
442
+ scope.minVal = "2014-01";
443
+ });
444
+
445
+ expect(
446
+ inputElm.find("input")[0].classList.contains("ng-invalid"),
447
+ ).toBeTrue();
448
+ expect(scope.form.alias.$error.min).toBeTruthy();
449
+ });
450
+
451
+ it("should validate if min is empty", () => {
452
+ scope.minVal = undefined;
453
+ scope.value = "2014-01";
454
+ scope.$digest();
455
+
456
+ expect(scope.form.alias.$error.min).toBeFalsy();
457
+ });
458
+ });
459
+
460
+ describe("max", () => {
461
+ let inputElm;
462
+ beforeEach(() => {
463
+ scope.maxVal = "2013-01";
464
+ inputElm = $compile(
465
+ '<form name="form"><input type="month" ng-model="value" name="alias" max="{{ maxVal }}" /></form>',
466
+ )(scope);
467
+ });
468
+
469
+ it("should validate", () => {
470
+ inputElm.find("input")[0].setAttribute("value", "2012-03");
471
+ inputElm.find("input")[0].dispatchEvent(new Event("change"));
472
+ expect(
473
+ inputElm.find("input")[0].classList.contains("ng-valid"),
474
+ ).toBeTrue();
475
+ expect(scope.value).toBe("2012-03");
476
+ expect(scope.form.alias.$error.max).toBeFalsy();
477
+ });
478
+
479
+ it("should invalidate", () => {
480
+ inputElm.find("input")[0].setAttribute("value", "2013-05");
481
+ inputElm.find("input")[0].dispatchEvent(new Event("change"));
482
+ expect(
483
+ inputElm.find("input")[0].classList.contains("ng-invalid"),
484
+ ).toBeTrue();
485
+ expect(scope.value).toBeUndefined();
486
+ expect(scope.form.alias.$error.max).toBeTruthy();
487
+ });
488
+
489
+ it("should revalidate when the max value changes", () => {
490
+ inputElm.find("input")[0].setAttribute("value", "2012-07");
491
+ inputElm.find("input")[0].dispatchEvent(new Event("change"));
492
+ expect(
493
+ inputElm.find("input")[0].classList.contains("ng-valid"),
494
+ ).toBeTrue();
495
+ expect(scope.form.alias.$error.max).toBeFalsy();
496
+
497
+ scope.maxVal = "2012-01";
498
+ scope.$digest();
499
+
500
+ expect(
501
+ inputElm.find("input")[0].classList.contains("ng-invalid"),
502
+ ).toBeTrue();
503
+ expect(scope.form.alias.$error.max).toBeTruthy();
504
+ });
505
+
506
+ it("should validate if max is empty", () => {
507
+ scope.maxVal = undefined;
508
+ scope.value = "2012-03";
509
+ scope.$digest();
510
+
511
+ expect(scope.form.alias.$error.max).toBeFalsy();
512
+ });
513
+ });
514
+ });
515
+
516
+ describe("week", () => {
517
+ it("should throw if model is a Date object", () => {
518
+ const inputElm = $compile('<input type="week" ng-model="secondWeek"/>')(
519
+ scope,
520
+ );
521
+
522
+ expect(() => {
523
+ scope.$apply(() => {
524
+ scope.secondWeek = new Date(2013, 0, 11);
525
+ });
526
+ }).toThrowError(/datefmt/);
527
+ });
528
+
529
+ it("should set the view if the model is a valid String object", () => {
530
+ const inputElm = $compile('<input type="week" ng-model="secondWeek"/>')(
531
+ scope,
532
+ );
533
+
534
+ scope.$apply(() => {
535
+ scope.secondWeek = "2013-W02";
536
+ });
537
+
538
+ expect(inputElm.val()).toBe("2013-W02");
539
+ });
540
+
541
+ it("should set scope to a string value", () => {
542
+ const inputElm = $compile(
543
+ '<input type="week" ng-model="secondWeek" />',
544
+ )(scope);
545
+
546
+ scope.$apply(() => {
547
+ scope.secondWeek = "2013-W02";
548
+ });
549
+
550
+ expect(scope.secondWeek).toBe("2013-W02");
551
+ // input type week in Chrome does not react to changes on the attribute. Value must be set directly
552
+ inputElm[0].value = "2014-W03";
553
+ inputElm[0].dispatchEvent(new Event("change"));
554
+
555
+ expect(scope.secondWeek).toBe("2014-W03");
556
+ });
557
+
558
+ it("should set the model undefined if the input is an invalid week string", () => {
559
+ const inputElm = $compile('<input type="week" ng-model="secondWeek"/>')(
560
+ scope,
561
+ );
562
+
563
+ scope.$apply(() => {
564
+ scope.secondWeek = "2013-W02";
565
+ });
566
+
567
+ expect(inputElm.val()).toBe("2013-W02");
568
+
569
+ // set to text for browsers with datetime-local validation.
570
+ inputElm[0].value = "stuff";
571
+ inputElm[0].dispatchEvent(new Event("change"));
572
+ expect(inputElm.val()).toBe("");
573
+ expect(scope.value).toBeUndefined();
574
+ });
575
+
576
+ it("should render as blank if null", () => {
577
+ const inputElm = $compile('<input type="week" ng-model="test" />')(
578
+ scope,
579
+ );
580
+
581
+ scope.$apply("test = null");
582
+
583
+ expect(scope.test).toBeNull();
584
+ expect(inputElm.val()).toEqual("");
585
+ });
586
+
587
+ it("should come up blank when no value specified", () => {
588
+ const inputElm = $compile('<input type="week" ng-model="test" />')(
589
+ scope,
590
+ );
591
+
592
+ expect(inputElm.val()).toBe("");
593
+
594
+ scope.$apply("test = null");
595
+
596
+ expect(scope.test).toBeNull();
597
+ expect(inputElm.val()).toBe("");
598
+ });
599
+
600
+ it("should parse empty string to null", () => {
601
+ const inputElm = $compile('<input type="week" ng-model="test" />')(
602
+ scope,
603
+ );
604
+
605
+ scope.$apply(() => {
606
+ scope.test = "2013-W02";
607
+ });
608
+
609
+ inputElm[0].value = "";
610
+ inputElm[0].dispatchEvent(new Event("change"));
611
+ expect(scope.test).toBeNull();
612
+ });
613
+
614
+ describe("min", () => {
615
+ let inputElm;
616
+ beforeEach(() => {
617
+ scope.minVal = "2013-W01";
618
+ inputElm = $compile(
619
+ '<form name="form"><input type="week" ng-model="value" name="alias" min="{{ minVal }}" /></from>',
620
+ )(scope);
621
+ });
622
+
623
+ it("should invalidate", () => {
624
+ inputElm.find("input")[0].value = "2012-W12";
625
+ inputElm.find("input")[0].dispatchEvent(new Event("change"));
626
+ expect(scope.value).toBeFalsy();
627
+ expect(scope.form.alias.$error.min).toBeTruthy();
628
+ });
629
+
630
+ it("should validate", () => {
631
+ inputElm.find("input")[0].value = "2013-W03";
632
+ inputElm.find("input")[0].dispatchEvent(new Event("change"));
633
+ expect(
634
+ inputElm.find("input")[0].classList.contains("ng-valid"),
635
+ ).toBeTrue();
636
+ expect(scope.value).toBe("2013-W03");
637
+ expect(scope.form.alias.$error.min).toBeFalsy();
638
+ });
639
+
640
+ it("should revalidate when the min value changes", () => {
641
+ inputElm.find("input")[0].value = "2013-W03";
642
+ inputElm.find("input")[0].dispatchEvent(new Event("change"));
643
+ expect(
644
+ inputElm.find("input")[0].classList.contains("ng-valid"),
645
+ ).toBeTrue();
646
+ expect(scope.form.alias.$error.min).toBeFalsy();
647
+
648
+ scope.minVal = "2014-W01";
649
+ scope.$digest();
650
+
651
+ expect(
652
+ inputElm.find("input")[0].classList.contains("ng-invalid"),
653
+ ).toBeTrue();
654
+ expect(scope.form.alias.$error.min).toBeTruthy();
655
+ });
656
+
657
+ it("should validate if min is empty", () => {
658
+ scope.minVal = undefined;
659
+ scope.value = "2013-W03";
660
+ scope.$digest();
661
+
662
+ expect(scope.form.alias.$error.min).toBeFalsy();
663
+ });
664
+ });
665
+
666
+ describe("max", () => {
667
+ let inputElm;
668
+
669
+ beforeEach(() => {
670
+ scope.maxVal = "2013-W01";
671
+ inputElm = $compile(
672
+ '<form name="form"><input type="week" ng-model="value" name="alias" max="{{ maxVal }}" /></form>',
673
+ )(scope);
674
+ });
675
+
676
+ it("should validate", () => {
677
+ inputElm.find("input")[0].value = "2012-W01";
678
+ inputElm.find("input")[0].dispatchEvent(new Event("change"));
679
+ expect(
680
+ inputElm.find("input")[0].classList.contains("ng-valid"),
681
+ ).toBeTrue();
682
+ expect(scope.value).toBe("2012-W01");
683
+ expect(scope.form.alias.$error.max).toBeFalsy();
684
+ });
685
+
686
+ it("should invalidate", () => {
687
+ inputElm.find("input")[0].value = "2013-W03";
688
+ inputElm.find("input")[0].dispatchEvent(new Event("change"));
689
+ expect(
690
+ inputElm.find("input")[0].classList.contains("ng-invalid"),
691
+ ).toBeTrue();
692
+ expect(scope.value).toBeUndefined();
693
+ expect(scope.form.alias.$error.max).toBeTruthy();
694
+ });
695
+
696
+ it("should revalidate when the max value changes", () => {
697
+ inputElm.find("input")[0].value = "2012-W03";
698
+ inputElm.find("input")[0].dispatchEvent(new Event("change"));
699
+ expect(
700
+ inputElm.find("input")[0].classList.contains("ng-valid"),
701
+ ).toBeTrue();
702
+ expect(scope.form.alias.$error.max).toBeFalsy();
703
+
704
+ scope.maxVal = "2012-W01";
705
+ scope.$digest();
706
+
707
+ expect(
708
+ inputElm.find("input")[0].classList.contains("ng-invalid"),
709
+ ).toBeTrue();
710
+ expect(scope.form.alias.$error.max).toBeTruthy();
711
+ });
712
+
713
+ it("should validate if max is empty", () => {
714
+ scope.maxVal = undefined;
715
+ scope.value = "2012-W01";
716
+ scope.$digest();
717
+
718
+ expect(scope.form.alias.$error.max).toBeFalsy();
719
+ });
720
+ });
721
+ });
722
+
723
+ describe("datetime-local", () => {
724
+ it("should throw if model is a Date object", () => {
725
+ const inputElm = $compile(
726
+ '<input type="datetime-local" ng-model="lunchtime"/>',
727
+ )(scope);
728
+
729
+ expect(() => {
730
+ scope.$apply(() => {
731
+ scope.lunchtime = new Date(2013, 11, 31, 23, 59, 59, 500);
732
+ });
733
+ }).toThrowError(/datefmt/);
734
+ });
735
+
736
+ it("should set the view if the model if a valid String.", () => {
737
+ const inputElm = $compile(
738
+ '<input type="datetime-local" ng-model="halfSecondToNextYear"/>',
739
+ )(scope);
740
+
741
+ scope.$apply(() => {
742
+ scope.halfSecondToNextYear = "2013-12-16T11:30";
743
+ });
744
+
745
+ expect(inputElm.val()).toBe("2013-12-16T11:30");
746
+ });
747
+
748
+ it("should bind to the model if a valid String.", () => {
749
+ const inputElm = $compile(
750
+ '<input type="datetime-local" ng-model="halfSecondToNextYear"/>',
751
+ )(scope);
752
+
753
+ inputElm[0].value = "2013-12-16T11:30";
754
+ inputElm[0].dispatchEvent(new Event("change"));
755
+
756
+ expect(inputElm.val()).toBe("2013-12-16T11:30");
757
+ expect(scope.halfSecondToNextYear).toBe("2013-12-16T11:30");
758
+ });
759
+
760
+ it("should set the model null if the view is invalid", () => {
761
+ const inputElm = $compile(
762
+ '<input type="datetime-local" ng-model="breakMe"/>',
763
+ )(scope);
764
+
765
+ scope.$apply(() => {
766
+ scope.breakMe = "2013-12-16T11:30";
767
+ });
768
+
769
+ expect(inputElm.val()).toBe("2013-12-16T11:30");
770
+
771
+ // set to text for browsers with datetime-local validation.
772
+
773
+ inputElm[0].value = "stuff";
774
+ inputElm[0].dispatchEvent(new Event("change"));
775
+ expect(inputElm.val()).toBe("");
776
+ expect(scope.breakMe).toBeNull();
777
+ });
778
+
779
+ it("should render as blank if null", () => {
780
+ const inputElm = $compile(
781
+ '<input type="datetime-local" ng-model="test" />',
782
+ )(scope);
783
+
784
+ scope.$apply("test = null");
785
+
786
+ expect(scope.test).toBeNull();
787
+ expect(inputElm.val()).toEqual("");
788
+ });
789
+
790
+ it("should come up blank when no value specified", () => {
791
+ const inputElm = $compile(
792
+ '<input type="datetime-local" ng-model="test" />',
793
+ )(scope);
794
+
795
+ expect(inputElm.val()).toBe("");
796
+
797
+ scope.$apply("test = null");
798
+
799
+ expect(scope.test).toBeNull();
800
+ expect(inputElm.val()).toBe("");
801
+ });
802
+
803
+ it("should parse empty string to null", () => {
804
+ const inputElm = $compile(
805
+ '<input type="datetime-local" ng-model="test" />',
806
+ )(scope);
807
+
808
+ scope.$apply(() => {
809
+ scope.test = "2013-12-16T11:30";
810
+ });
811
+
812
+ inputElm[0].value = "";
813
+ inputElm[0].dispatchEvent(new Event("change"));
814
+ expect(scope.test).toBeNull();
815
+ });
816
+
817
+ describe("min", () => {
818
+ let inputElm;
819
+ beforeEach(() => {
820
+ scope.minVal = "2000-01-01T12:30:00";
821
+ let formElm = $compile(
822
+ `<form name="form">
823
+ <input type="datetime-local" ng-model="value" name="alias" min="{{ minVal }}" />
824
+ </form>`,
825
+ )(scope);
826
+ inputElm = formElm.find("input");
827
+ });
828
+
829
+ it("should invalidate", () => {
830
+ inputElm[0].value = "1999-12-31T01:02:00";
831
+ inputElm[0].dispatchEvent(new Event("change"));
832
+ expect(inputElm[0].classList.contains("ng-invalid")).toBeTrue();
833
+ expect(scope.value).toBeFalsy();
834
+ expect(scope.form.alias.$error.min).toBeTruthy();
835
+ });
836
+
837
+ it("should validate", () => {
838
+ inputElm[0].value = "2000-01-01T23:02:00";
839
+ inputElm[0].dispatchEvent(new Event("change"));
840
+ expect(inputElm[0].classList.contains("ng-valid")).toBeTrue();
841
+ expect(scope.value).toBe("2000-01-01T23:02");
842
+ expect(scope.form.alias.$error.min).toBeFalsy();
843
+ });
844
+
845
+ it("should revalidate when the min value changes", () => {
846
+ inputElm[0].value = "2000-02-01T01:02:00";
847
+ inputElm[0].dispatchEvent(new Event("change"));
848
+ expect(inputElm[0].classList.contains("ng-valid")).toBeTrue();
849
+ expect(scope.form.alias.$error.min).toBeFalsy();
850
+
851
+ scope.minVal = "2010-01-01T01:02:00";
852
+ scope.$digest();
853
+
854
+ expect(inputElm[0].classList.contains("ng-invalid")).toBeTrue();
855
+ expect(scope.form.alias.$error.min).toBeTruthy();
856
+ });
857
+
858
+ it("should validate if min is empty", () => {
859
+ scope.minVal = undefined;
860
+ scope.value = "2010-01-01T01:02:00";
861
+ scope.$digest();
862
+
863
+ expect(scope.form.alias.$error.min).toBeFalsy();
864
+ });
865
+ });
866
+
867
+ describe("max", () => {
868
+ let inputElm;
869
+ beforeEach(() => {
870
+ scope.maxVal = "2019-01-01T01:02:00";
871
+ let formElm = $compile(
872
+ '<form name="form"><input type="datetime-local" ng-model="value" name="alias" max="{{ maxVal }}" /></form>',
873
+ )(scope);
874
+ inputElm = formElm.find("input");
875
+ });
876
+
877
+ it("should invalidate", () => {
878
+ inputElm[0].value = "2019-12-31T01:02:00";
879
+ inputElm[0].dispatchEvent(new Event("change"));
880
+ expect(inputElm[0].classList.contains("ng-invalid")).toBeTrue();
881
+ expect(scope.value).toBeFalsy();
882
+ expect(scope.form.alias.$error.max).toBeTruthy();
883
+ });
884
+
885
+ it("should validate", () => {
886
+ inputElm[0].value = "2000-01-01T01:02:00";
887
+ inputElm[0].dispatchEvent(new Event("change"));
888
+ expect(inputElm[0].classList.contains("ng-valid")).toBeTrue();
889
+ expect(scope.value).toBe("2000-01-01T01:02");
890
+ expect(scope.form.alias.$error.max).toBeFalsy();
891
+ });
892
+
893
+ it("should revalidate when the max value changes", () => {
894
+ inputElm[0].value = "2000-02-01T01:02:00";
895
+ inputElm[0].dispatchEvent(new Event("change"));
896
+ expect(inputElm[0].classList.contains("ng-valid")).toBeTrue();
897
+ expect(scope.form.alias.$error.max).toBeFalsy();
898
+
899
+ scope.maxVal = "2000-01-01T01:02:00";
900
+ scope.$digest();
901
+
902
+ expect(inputElm[0].classList.contains("ng-invalid")).toBeTrue();
903
+ expect(scope.form.alias.$error.max).toBeTruthy();
904
+ });
905
+
906
+ it("should validate if max is empty", () => {
907
+ scope.maxVal = undefined;
908
+ scope.value = "2000-01-01T01:02:00";
909
+ scope.$digest();
910
+
911
+ expect(scope.form.alias.$error.max).toBeFalsy();
912
+ });
913
+
914
+ it("should validate when timezone is provided.", () => {
915
+ inputElm = $compile(
916
+ '<input type="datetime-local" ng-model="value" name="alias" ' +
917
+ 'max="{{ maxVal }}" ng-model-options="{timezone: \'UTC\', allowInvalid: true}"/>',
918
+ )(scope);
919
+ scope.maxVal = "2013-01-01T00:00:00";
920
+ scope.value = "2012-01-01T00:00:00";
921
+ scope.$digest();
922
+
923
+ expect(scope.form.alias.$error.max).toBeFalsy();
924
+ expect(scope.form.alias.$valid).toBeTruthy();
925
+
926
+ scope.value = "";
927
+ inputElm[0].value = "2013-01-01T00:00:00";
928
+ expect(inputElm[0].classList.contains("ng-valid")).toBeTrue();
929
+ expect(scope.form.alias.$error.max).toBeFalsy();
930
+ expect(scope.form.alias.$valid).toBeTruthy();
931
+ });
932
+ });
933
+
934
+ it("should validate even if max value changes on-the-fly", () => {
935
+ scope.max = "2013-01-01T01:02:00";
936
+ const inputElm = $compile(
937
+ '<input type="datetime-local" ng-model="value" name="alias" max="{{max}}" />',
938
+ )(scope);
939
+
940
+ inputElm[0].value = "2014-01-01T12:34:00";
941
+ expect(inputElm[0].classList.contains("ng-valid")).toBeTrue();
942
+
943
+ scope.max = "2001-01-01T01:02:00";
944
+ scope.$digest();
945
+
946
+ expect(inputElm[0].classList.contains("ng-valid")).toBeTrue();
947
+
948
+ scope.max = "2024-01-01T01:02:00";
949
+ scope.$digest();
950
+
951
+ expect(inputElm[0].classList.contains("ng-valid")).toBeTrue();
952
+ });
953
+
954
+ it("should validate even if min value changes on-the-fly", () => {
955
+ scope.min = "2013-01-01T01:02:00";
956
+ const inputElm = $compile(
957
+ '<input type="datetime-local" ng-model="value" name="alias" min="{{min}}" />',
958
+ )(scope);
959
+
960
+ inputElm[0].value = "2010-01-01T12:34:00";
961
+ expect(inputElm[0].classList.contains("ng-valid")).toBeTrue();
962
+
963
+ scope.min = "2014-01-01T01:02:00";
964
+ scope.$digest();
965
+
966
+ expect(inputElm[0].classList.contains("ng-valid")).toBeTrue();
967
+
968
+ scope.min = "2009-01-01T01:02:00";
969
+ scope.$digest();
970
+
971
+ expect(inputElm[0].classList.contains("ng-valid")).toBeTrue();
972
+ });
973
+
974
+ it("should validate even if ng-max value changes on-the-fly", () => {
975
+ scope.max = "2013-01-01T01:02:00";
976
+ const inputElm = $compile(
977
+ '<input type="datetime-local" ng-model="value" name="alias" ng-max="max" />',
978
+ )(scope);
979
+
980
+ inputElm[0].value = "2014-01-01T12:34:00";
981
+ expect(inputElm[0].classList.contains("ng-valid")).toBeTrue();
982
+
983
+ scope.max = "2001-01-01T01:02:00";
984
+ scope.$digest();
985
+
986
+ expect(inputElm[0].classList.contains("ng-valid")).toBeTrue();
987
+
988
+ scope.max = "2024-01-01T01:02:00";
989
+ scope.$digest();
990
+
991
+ expect(inputElm[0].classList.contains("ng-valid")).toBeTrue();
992
+ });
993
+
994
+ it("should validate even if ng-min value changes on-the-fly", () => {
995
+ scope.min = "2013-01-01T01:02:00";
996
+ const inputElm = $compile(
997
+ '<input type="datetime-local" ng-model="value" name="alias" ng-min="min" />',
998
+ )(scope);
999
+
1000
+ inputElm[0].value = "2010-01-01T12:34:00";
1001
+ expect(inputElm[0].classList.contains("ng-valid")).toBeTrue();
1002
+
1003
+ scope.min = "2014-01-01T01:02:00";
1004
+ scope.$digest();
1005
+
1006
+ expect(inputElm[0].classList.contains("ng-valid")).toBeTrue();
1007
+
1008
+ scope.min = "2009-01-01T01:02:00";
1009
+ scope.$digest();
1010
+
1011
+ expect(inputElm[0].classList.contains("ng-valid")).toBeTrue();
1012
+ });
1013
+ });
1014
+
1015
+ describe("time", () => {
1016
+ it("should throw if model is a Date object", () => {
1017
+ const inputElm = $compile('<input type="time" ng-model="lunchtime"/>')(
1018
+ scope,
1019
+ );
1020
+ expect(() => {
1021
+ scope.$apply(() => {
1022
+ scope.lunchtime = new Date(1970, 0, 1, 15, 41, 0, 500);
1023
+ });
1024
+ }).toThrowError(/datefmt/);
1025
+ });
1026
+
1027
+ it("should set the view if the model is a valid String object.", () => {
1028
+ const inputElm = $compile(
1029
+ '<input type="time" ng-model="threeFortyOnePm"/>',
1030
+ )(scope);
1031
+
1032
+ scope.$apply(() => {
1033
+ scope.threeFortyOnePm = "15:41:00.500";
1034
+ });
1035
+
1036
+ expect(inputElm.val()).toBe("15:41:00.500");
1037
+ });
1038
+
1039
+ it("should bind to mode if a valid String object.", () => {
1040
+ const inputElm = $compile(
1041
+ '<input type="time" ng-model="threeFortyOnePm"/>',
1042
+ )(scope);
1043
+
1044
+ inputElm[0].value = "15:41:00.500";
1045
+ inputElm[0].dispatchEvent(new Event("change"));
1046
+
1047
+ expect(inputElm.val()).toBe("15:41:00.500");
1048
+ expect(scope.threeFortyOnePm).toBe("15:41:00.500");
1049
+ });
1050
+
1051
+ it("should set the model to null if the view is invalid", () => {
1052
+ const inputElm = $compile('<input type="time" ng-model="breakMe"/>')(
1053
+ scope,
1054
+ );
1055
+
1056
+ scope.$apply(() => {
1057
+ scope.breakMe = "16:25:00.000";
1058
+ });
1059
+
1060
+ expect(inputElm.val()).toBe("16:25:00.000");
1061
+
1062
+ inputElm[0].value = "stuff";
1063
+ inputElm[0].dispatchEvent(new Event("change"));
1064
+ expect(inputElm.val()).toBe("");
1065
+ expect(scope.breakMe).toBeNull();
1066
+ });
1067
+
1068
+ it("should set blank if null", () => {
1069
+ const inputElm = $compile('<input type="time" ng-model="test" />')(
1070
+ scope,
1071
+ );
1072
+
1073
+ scope.$apply("test = null");
1074
+
1075
+ expect(scope.test).toBeNull();
1076
+ expect(inputElm.val()).toEqual("");
1077
+ });
1078
+
1079
+ it("should set blank when no value specified", () => {
1080
+ const inputElm = $compile('<input type="time" ng-model="test" />')(
1081
+ scope,
1082
+ );
1083
+
1084
+ expect(inputElm.val()).toBe("");
1085
+
1086
+ scope.$apply("test = null");
1087
+
1088
+ expect(scope.test).toBeNull();
1089
+ expect(inputElm.val()).toBe("");
1090
+ });
1091
+
1092
+ it("should parse empty string to null", () => {
1093
+ const inputElm = $compile('<input type="time" ng-model="test" />')(
1094
+ scope,
1095
+ );
1096
+
1097
+ scope.$apply(() => {
1098
+ scope.test = "16:25:00";
1099
+ });
1100
+
1101
+ inputElm[0].value = "";
1102
+ inputElm[0].dispatchEvent(new Event("change"));
1103
+ expect(scope.test).toBeNull();
1104
+ });
1105
+
1106
+ it("should allow to specify the milliseconds", () => {
1107
+ const inputElm = $compile('<input type="time" ng-model="value"" />')(
1108
+ scope,
1109
+ );
1110
+
1111
+ inputElm[0].value = "01:02:03.500";
1112
+ inputElm[0].dispatchEvent(new Event("change"));
1113
+ expect(scope.value).toBe("01:02:03.500");
1114
+ });
1115
+
1116
+ it("should allow to specify single digit milliseconds", () => {
1117
+ const inputElm = $compile('<input type="time" ng-model="value"" />')(
1118
+ scope,
1119
+ );
1120
+
1121
+ inputElm[0].value = "01:02:03.4";
1122
+ inputElm[0].dispatchEvent(new Event("change"));
1123
+ expect(scope.value).toBe("01:02:03.4");
1124
+ });
1125
+
1126
+ it("should allow to specify the seconds", () => {
1127
+ const inputElm = $compile('<input type="time" ng-model="value"" />')(
1128
+ scope,
1129
+ );
1130
+
1131
+ inputElm[0].value = "01:02:03";
1132
+ inputElm[0].dispatchEvent(new Event("change"));
1133
+ expect(scope.value).toBe("01:02:03");
1134
+
1135
+ scope.$apply(() => {
1136
+ scope.value = "01:02:03.000";
1137
+ });
1138
+ expect(inputElm.val()).toBe("01:02:03.000");
1139
+ });
1140
+
1141
+ describe("min", () => {
1142
+ let inputElm;
1143
+ beforeEach(() => {
1144
+ scope.minVal = "09:30:00";
1145
+ let formElm = $compile(
1146
+ '<form name="form"><input type="time" ng-model="value" name="alias" min="{{ minVal }}" /></form>',
1147
+ )(scope);
1148
+ inputElm = formElm.find("input");
1149
+ });
1150
+
1151
+ it("should invalidate", () => {
1152
+ inputElm[0].value = "01:02:03";
1153
+ inputElm[0].dispatchEvent(new Event("change"));
1154
+ expect(inputElm[0].classList.contains("ng-invalid")).toBeTrue();
1155
+ expect(scope.value).toBeFalsy();
1156
+ expect(scope.form.alias.$error.min).toBeTruthy();
1157
+ });
1158
+
1159
+ it("should validate", () => {
1160
+ inputElm[0].value = "23:02:00";
1161
+ inputElm[0].dispatchEvent(new Event("change"));
1162
+ expect(inputElm[0].classList.contains("ng-valid")).toBeTrue();
1163
+ expect(scope.value).toBe("23:02:00");
1164
+ expect(scope.form.alias.$error.min).toBeFalsy();
1165
+ });
1166
+
1167
+ it("should revalidate when the min value changes", () => {
1168
+ inputElm[0].value = "23:02:00";
1169
+ inputElm[0].dispatchEvent(new Event("change"));
1170
+ expect(inputElm[0].classList.contains("ng-valid")).toBeTrue();
1171
+ expect(scope.form.alias.$error.min).toBeFalsy();
1172
+
1173
+ scope.minVal = "23:55:00";
1174
+ scope.$digest();
1175
+
1176
+ expect(inputElm[0].classList.contains("ng-invalid")).toBeTrue();
1177
+ expect(scope.form.alias.$error.min).toBeTruthy();
1178
+ });
1179
+
1180
+ it("should validate if min is empty", () => {
1181
+ scope.minVal = undefined;
1182
+ scope.value = "23:55:00";
1183
+ scope.$digest();
1184
+
1185
+ expect(scope.form.alias.$error.min).toBeFalsy();
1186
+ });
1187
+ });
1188
+
1189
+ describe("max", () => {
1190
+ let inputElm;
1191
+ beforeEach(() => {
1192
+ scope.maxVal = "22:30:00";
1193
+ let formElm = $compile(
1194
+ '<form name="form"><input type="time" ng-model="value" name="alias" max="{{ maxVal }}" /></form>',
1195
+ )(scope);
1196
+ inputElm = formElm.find("input");
1197
+ });
1198
+
1199
+ it("should invalidate", () => {
1200
+ inputElm[0].value = "23:00:00";
1201
+ inputElm[0].dispatchEvent(new Event("change"));
1202
+ expect(inputElm[0].classList.contains("ng-invalid")).toBeTrue();
1203
+ expect(scope.value).toBeFalsy();
1204
+ expect(scope.form.alias.$error.max).toBeTruthy();
1205
+ });
1206
+
1207
+ it("should validate", () => {
1208
+ inputElm[0].value = "05:30:00";
1209
+ inputElm[0].dispatchEvent(new Event("change"));
1210
+ expect(inputElm[0].classList.contains("ng-valid")).toBeTrue();
1211
+ expect(scope.value).toBe("05:30:00");
1212
+ expect(scope.form.alias.$error.max).toBeFalsy();
1213
+ });
1214
+
1215
+ it("should validate if max is empty", () => {
1216
+ scope.maxVal = undefined;
1217
+ scope.value = "05:30:00";
1218
+ scope.$digest();
1219
+
1220
+ expect(scope.form.alias.$error.max).toBeFalsy();
1221
+ });
1222
+ });
1223
+
1224
+ it("should validate even if max value changes on-the-fly", () => {
1225
+ scope.max = "04:02:00";
1226
+ const inputElm = $compile(
1227
+ '<input type="time" ng-model="value" name="alias" max="{{max}}" />',
1228
+ )(scope);
1229
+
1230
+ inputElm[0].value = "05:34:00";
1231
+ inputElm[0].dispatchEvent(new Event("change"));
1232
+ expect(inputElm[0].classList.contains("ng-invalid")).toBeTrue();
1233
+
1234
+ scope.max = "06:34:00";
1235
+ scope.$digest();
1236
+
1237
+ expect(inputElm[0].classList.contains("ng-valid")).toBeTrue();
1238
+ });
1239
+
1240
+ it("should validate even if min value changes on-the-fly", () => {
1241
+ scope.min = "08:45:00";
1242
+ const inputElm = $compile(
1243
+ '<input type="time" ng-model="value" name="alias" min="{{min}}" />',
1244
+ )(scope);
1245
+
1246
+ inputElm[0].value = "06:15:00";
1247
+ inputElm[0].dispatchEvent(new Event("change"));
1248
+ expect(inputElm[0].classList.contains("ng-invalid")).toBeTrue();
1249
+
1250
+ scope.min = "05:50:00";
1251
+ scope.$digest();
1252
+
1253
+ expect(inputElm[0].classList.contains("ng-valid")).toBeTrue();
1254
+ });
1255
+
1256
+ it("should validate even if ng-max value changes on-the-fly", () => {
1257
+ scope.max = "04:02:00";
1258
+ const inputElm = $compile(
1259
+ '<input type="time" ng-model="value" name="alias" ng-max="max" />',
1260
+ )(scope);
1261
+
1262
+ inputElm[0].value = "05:34:00";
1263
+ inputElm[0].dispatchEvent(new Event("change"));
1264
+ expect(inputElm[0].classList.contains("ng-invalid")).toBeTrue();
1265
+
1266
+ scope.max = "06:34:00";
1267
+ scope.$digest();
1268
+
1269
+ expect(inputElm[0].classList.contains("ng-valid")).toBeTrue();
1270
+ });
1271
+
1272
+ it("should validate even if ng-min value changes on-the-fly", () => {
1273
+ scope.min = "08:45:00";
1274
+ const inputElm = $compile(
1275
+ '<input type="time" ng-model="value" name="alias" ng-min="min" />',
1276
+ )(scope);
1277
+
1278
+ inputElm[0].value = "06:15:00";
1279
+ inputElm[0].dispatchEvent(new Event("change"));
1280
+ expect(inputElm[0].classList.contains("ng-invalid")).toBeTrue();
1281
+
1282
+ scope.min = "05:50:00";
1283
+ scope.$digest();
1284
+
1285
+ expect(inputElm[0].classList.contains("ng-valid")).toBeTrue();
1286
+ });
1287
+ });
1288
+
1289
+ describe("date", () => {
1290
+ it("should throw if model is a Date object.", () => {
1291
+ const inputElm = $compile('<input type="date" ng-model="birthday"/>')(
1292
+ scope,
1293
+ );
1294
+
1295
+ expect(() => {
1296
+ scope.$apply(() => {
1297
+ scope.birthday = new Date("a");
1298
+ });
1299
+ }).toThrowError(/datefmt/);
1300
+ });
1301
+
1302
+ it("should set the view when the model is an valid String", () => {
1303
+ const inputElm = $compile('<input type="date" ng-model="val"/>')(scope);
1304
+
1305
+ scope.$apply(() => {
1306
+ scope.val = "1977-10-22";
1307
+ });
1308
+
1309
+ expect(inputElm.val()).toBe("1977-10-22");
1310
+ });
1311
+
1312
+ it("should bind to scope when the model is an valid String", () => {
1313
+ const inputElm = $compile('<input type="date" ng-model="val"/>')(scope);
1314
+
1315
+ inputElm[0].value = "1977-10-22";
1316
+ inputElm[0].dispatchEvent(new Event("change"));
1317
+
1318
+ expect(scope.val).toBe("1977-10-22");
1319
+ });
1320
+
1321
+ it("should set the model to null if the view is invalid", () => {
1322
+ const inputElm = $compile('<input type="date" ng-model="arrMatey"/>')(
1323
+ scope,
1324
+ );
1325
+
1326
+ scope.$apply(() => {
1327
+ scope.arrMatey = "2014-09-14";
1328
+ });
1329
+
1330
+ expect(inputElm.val()).toBe("2014-09-14");
1331
+
1332
+ // set to text for browsers with date validation.
1333
+ inputElm[0].value = "1-2-3";
1334
+ inputElm[0].dispatchEvent(new Event("change"));
1335
+ expect(inputElm.val()).toBe("");
1336
+ expect(scope.arrMatey).toBeNull();
1337
+ });
1338
+
1339
+ it("should render as blank if null", () => {
1340
+ const inputElm = $compile('<input type="date" ng-model="test" />')(
1341
+ scope,
1342
+ );
1343
+
1344
+ scope.$apply("test = null");
1345
+
1346
+ expect(scope.test).toBeNull();
1347
+ expect(inputElm.val()).toEqual("");
1348
+ });
1349
+
1350
+ it("should come up blank when no value specified", () => {
1351
+ const inputElm = $compile('<input type="date" ng-model="test" />')(
1352
+ scope,
1353
+ );
1354
+
1355
+ expect(inputElm.val()).toBe("");
1356
+
1357
+ scope.$apply("test = null");
1358
+
1359
+ expect(scope.test).toBeNull();
1360
+ expect(inputElm.val()).toBe("");
1361
+ });
1362
+
1363
+ it("should parse empty string to null", () => {
1364
+ const inputElm = $compile('<input type="date" ng-model="test" />')(
1365
+ scope,
1366
+ );
1367
+
1368
+ scope.$apply(() => {
1369
+ scope.test = "2014-09-14";
1370
+ });
1371
+
1372
+ inputElm[0].value = "";
1373
+ inputElm[0].dispatchEvent(new Event("change"));
1374
+ expect(scope.test).toBeNull();
1375
+ });
1376
+
1377
+ describe("min", () => {
1378
+ it("should invalidate", () => {
1379
+ const formElm = $compile(
1380
+ '<form name="form"><input type="date" ng-model="value" name="alias" min="2000-01-01" /></form>',
1381
+ )(scope);
1382
+ const inputElm = formElm.find("input");
1383
+ inputElm[0].value = "1999-12-31";
1384
+ inputElm[0].dispatchEvent(new Event("change"));
1385
+ expect(inputElm[0].classList.contains("ng-invalid")).toBeTrue();
1386
+ expect(scope.value).toBeFalsy();
1387
+ expect(scope.form.alias.$error.min).toBeTruthy();
1388
+ });
1389
+
1390
+ it("should validate", () => {
1391
+ const formElm = $compile(
1392
+ '<form name="form"><input type="date" ng-model="value" name="alias" min="2000-01-01" /></form>',
1393
+ )(scope);
1394
+ const inputElm = formElm.find("input");
1395
+ inputElm[0].value = "2000-01-01";
1396
+ inputElm[0].dispatchEvent(new Event("change"));
1397
+ expect(inputElm[0].classList.contains("ng-valid")).toBeTrue();
1398
+ expect(scope.value).toBe("2000-01-01");
1399
+ expect(scope.form.alias.$error.min).toBeFalsy();
1400
+ });
1401
+
1402
+ it("should validate if min is empty", () => {
1403
+ const formElm = $compile(
1404
+ '<form name="form"><input name="alias" ng-model="value" type="date" min >',
1405
+ )(scope);
1406
+ const inputElm = formElm.find("input");
1407
+
1408
+ scope.value = "2000-01-01";
1409
+ scope.$digest();
1410
+
1411
+ expect(scope.form.alias.$error.min).toBeFalsy();
1412
+ });
1413
+ });
1414
+
1415
+ describe("max", () => {
1416
+ it("should invalidate", () => {
1417
+ const formElm = $compile(
1418
+ '<form name="form"><input type="date" ng-model="value" name="alias" max="2019-01-01" /></form>',
1419
+ )(scope);
1420
+ const inputElm = formElm.find("input");
1421
+
1422
+ inputElm[0].value = "2019-12-31";
1423
+ inputElm[0].dispatchEvent(new Event("change"));
1424
+ expect(inputElm[0].classList.contains("ng-invalid")).toBeTrue();
1425
+ expect(scope.value).toBeFalsy();
1426
+ expect(scope.form.alias.$error.max).toBeTruthy();
1427
+ });
1428
+
1429
+ it("should validate", () => {
1430
+ const formElm = $compile(
1431
+ '<form name="form"><input type="date" ng-model="value" name="alias" max="2019-01-01" /></form>',
1432
+ )(scope);
1433
+ const inputElm = formElm.find("input");
1434
+
1435
+ inputElm[0].value = "2000-01-01";
1436
+ inputElm[0].dispatchEvent(new Event("change"));
1437
+ expect(inputElm[0].classList.contains("ng-valid")).toBeTrue();
1438
+ expect(scope.value).toBe("2000-01-01");
1439
+ expect(scope.form.alias.$error.max).toBeFalsy();
1440
+ });
1441
+
1442
+ it("should parse ISO-based date strings as a valid max date value", () => {
1443
+ $compile(
1444
+ '<form name="form"><input name="myControl" type="date" max="{{ max }}" ng-model="value"></form>',
1445
+ )(scope);
1446
+
1447
+ scope.value = "2020-01-01";
1448
+ scope.max = new Date(2014, 10, 10, 0, 0, 0).toISOString();
1449
+ scope.$digest();
1450
+
1451
+ expect(scope.form.myControl.$error.max).toBeTruthy();
1452
+ });
1453
+
1454
+ it("should validate if max is empty", () => {
1455
+ $compile(
1456
+ '<form name="form"><input type="date" name="alias" ng-model="value" max /></form>',
1457
+ )(scope);
1458
+
1459
+ scope.value = "2020-01-01";
1460
+ scope.$digest();
1461
+
1462
+ expect(scope.form.alias.$error.max).toBeFalsy();
1463
+ });
1464
+ });
1465
+
1466
+ it("should validate even if max value changes on-the-fly", () => {
1467
+ scope.max = "2013-01-01";
1468
+ const inputElm = $compile(
1469
+ '<input type="date" ng-model="value" name="alias" max="{{max}}" />',
1470
+ )(scope);
1471
+
1472
+ inputElm[0].value = "2014-01-01";
1473
+ inputElm[0].dispatchEvent(new Event("change"));
1474
+ expect(inputElm[0].classList.contains("ng-invalid")).toBeTrue();
1475
+
1476
+ scope.max = "2001-01-01";
1477
+ scope.$digest();
1478
+
1479
+ expect(inputElm[0].classList.contains("ng-invalid")).toBeTrue();
1480
+
1481
+ scope.max = "2021-01-01";
1482
+ scope.$digest();
1483
+
1484
+ expect(inputElm[0].classList.contains("ng-valid")).toBeTrue();
1485
+ });
1486
+
1487
+ it("should validate even if min value changes on-the-fly", () => {
1488
+ scope.min = "2013-01-01";
1489
+ const inputElm = $compile(
1490
+ '<input type="date" ng-model="value" name="alias" min="{{min}}" />',
1491
+ )(scope);
1492
+
1493
+ inputElm[0].value = "2010-01-01";
1494
+ inputElm[0].dispatchEvent(new Event("change"));
1495
+ expect(inputElm[0].classList.contains("ng-invalid")).toBeTrue();
1496
+
1497
+ scope.min = "2014-01-01";
1498
+ scope.$digest();
1499
+
1500
+ expect(inputElm[0].classList.contains("ng-invalid")).toBeTrue();
1501
+
1502
+ scope.min = "2009-01-01";
1503
+ scope.$digest();
1504
+
1505
+ expect(inputElm[0].classList.contains("ng-valid")).toBeTrue();
1506
+ });
1507
+
1508
+ it("should validate even if ng-max value changes on-the-fly", () => {
1509
+ scope.max = "2013-01-01";
1510
+ const inputElm = $compile(
1511
+ '<input type="date" ng-model="value" name="alias" ng-max="max" />',
1512
+ )(scope);
1513
+
1514
+ inputElm[0].value = "2014-01-01";
1515
+ inputElm[0].dispatchEvent(new Event("change"));
1516
+ expect(inputElm[0].classList.contains("ng-invalid")).toBeTrue();
1517
+
1518
+ scope.max = "2001-01-01";
1519
+ scope.$digest();
1520
+
1521
+ expect(inputElm[0].classList.contains("ng-invalid")).toBeTrue();
1522
+
1523
+ scope.max = "2021-01-01";
1524
+ scope.$digest();
1525
+
1526
+ expect(inputElm[0].classList.contains("ng-valid")).toBeTrue();
1527
+ });
1528
+
1529
+ it("should validate even if ng-min value changes on-the-fly", () => {
1530
+ scope.min = "2013-01-01";
1531
+ const inputElm = $compile(
1532
+ '<input type="date" ng-model="value" name="alias" ng-min="min" />',
1533
+ )(scope);
1534
+
1535
+ inputElm[0].value = "2010-01-01";
1536
+ inputElm[0].dispatchEvent(new Event("change"));
1537
+ expect(inputElm[0].classList.contains("ng-invalid")).toBeTrue();
1538
+
1539
+ scope.min = "2014-01-01";
1540
+ scope.$digest();
1541
+
1542
+ expect(inputElm[0].classList.contains("ng-invalid")).toBeTrue();
1543
+
1544
+ scope.min = "2009-01-01";
1545
+ scope.$digest();
1546
+
1547
+ expect(inputElm[0].classList.contains("ng-valid")).toBeTrue();
1548
+ });
1549
+
1550
+ it("should allow Date objects as valid ng-max values", () => {
1551
+ scope.max = new Date(2012, 1, 1, 1, 2, 0);
1552
+ const inputElm = $compile(
1553
+ '<input type="date" ng-model="value" name="alias" ng-max="max" />',
1554
+ )(scope);
1555
+
1556
+ inputElm[0].value = "2014-01-01";
1557
+ inputElm[0].dispatchEvent(new Event("change"));
1558
+ expect(inputElm[0].classList.contains("ng-invalid")).toBeTrue();
1559
+
1560
+ scope.max = new Date(2013, 1, 1, 1, 2, 0);
1561
+ scope.$digest();
1562
+
1563
+ expect(inputElm[0].classList.contains("ng-invalid")).toBeTrue();
1564
+
1565
+ scope.max = new Date(2014, 1, 1, 1, 2, 0);
1566
+ scope.$digest();
1567
+
1568
+ expect(inputElm[0].classList.contains("ng-valid")).toBeTrue();
1569
+ });
1570
+
1571
+ it("should allow Date objects as valid ng-min values", () => {
1572
+ scope.min = new Date(2013, 1, 1, 1, 2, 0);
1573
+ const inputElm = $compile(
1574
+ '<input type="date" ng-model="value" name="alias" ng-min="min" />',
1575
+ )(scope);
1576
+
1577
+ inputElm[0].value = "2010-01-01";
1578
+ inputElm[0].dispatchEvent(new Event("change"));
1579
+ expect(inputElm[0].classList.contains("ng-invalid")).toBeTrue();
1580
+
1581
+ scope.min = new Date(2014, 1, 1, 1, 2, 0);
1582
+ scope.$digest();
1583
+
1584
+ expect(inputElm[0].classList.contains("ng-invalid")).toBeTrue();
1585
+
1586
+ scope.min = new Date(2009, 1, 1, 1, 2, 0);
1587
+ scope.$digest();
1588
+
1589
+ expect(inputElm[0].classList.contains("ng-valid")).toBeTrue();
1590
+ });
1591
+
1592
+ describe("ISO_DATE_REGEXP", () => {
1593
+ [
1594
+ // Validate date
1595
+ ["00:00:00.0000+01:01", false], // date must be specified
1596
+ ["2010.06.15T00:00:00.0000+01:01", false], // date must use dash separator
1597
+ ["x2010-06-15T00:00:00.0000+01:01", false], // invalid leading characters
1598
+
1599
+ // Validate year
1600
+ ["2010-06-15T00:00:00.0000+01:01", true], // year has four or more digits
1601
+ ["20100-06-15T00:00:00.0000+01:01", true], // year has four or more digits
1602
+ ["-06-15T00:00:00.0000+01:01", false], // year has too few digits
1603
+ ["2-06-15T00:00:00.0000+01:01", false], // year has too few digits
1604
+ ["20-06-15T00:00:00.0000+01:01", false], // year has too few digits
1605
+ ["201-06-15T00:00:00.0000+01:01", false], // year has too few digits
1606
+
1607
+ // Validate month
1608
+ ["2010-01-15T00:00:00.0000+01:01", true], // month has two digits
1609
+ ["2010--15T00:00:00.0000+01:01", false], // month has too few digits
1610
+ ["2010-0-15T00:00:00.0000+01:01", false], // month has too few digits
1611
+ ["2010-1-15T00:00:00.0000+01:01", false], // month has too few digits
1612
+ ["2010-111-15T00:00:00.0000+01:01", false], // month has too many digits
1613
+ ["2010-22-15T00:00:00.0000+01:01", false], // month is too large
1614
+
1615
+ // Validate day
1616
+ ["2010-01-01T00:00:00.0000+01:01", true], // day has two digits
1617
+ ["2010-01-T00:00:00.0000+01:01", false], // day has too few digits
1618
+ ["2010-01-1T00:00:00.0000+01:01", false], // day has too few digits
1619
+ ["2010-01-200T00:00:00.0000+01:01", false], // day has too many digits
1620
+ ["2010-01-41T00:00:00.0000+01:01", false], // day is too large
1621
+
1622
+ // Validate time
1623
+ ["2010-01-01", false], // time must be specified
1624
+ ["2010-01-0101:00:00.0000+01:01", false], // missing date time separator
1625
+ ["2010-01-01V01:00:00.0000+01:01", false], // invalid date time separator
1626
+ ["2010-01-01T01-00-00.0000+01:01", false], // time must use colon separator
1627
+
1628
+ // Validate hour
1629
+ ["2010-01-01T01:00:00.0000+01:01", true], // hour has two digits
1630
+ ["2010-01-01T-01:00:00.0000+01:01", false], // hour must be positive
1631
+ ["2010-01-01T:00:00.0000+01:01", false], // hour has too few digits
1632
+ ["2010-01-01T1:00:00.0000+01:01", false], // hour has too few digits
1633
+ ["2010-01-01T220:00:00.0000+01:01", false], // hour has too many digits
1634
+ ["2010-01-01T32:00:00.0000+01:01", false], // hour is too large
1635
+
1636
+ // Validate minutes
1637
+ ["2010-01-01T01:00:00.0000+01:01", true], // minute has two digits
1638
+ ["2010-01-01T01:-00:00.0000+01:01", false], // minute must be positive
1639
+ ["2010-01-01T01::00.0000+01:01", false], // minute has too few digits
1640
+ ["2010-01-01T01:0:00.0000+01:01", false], // minute has too few digits
1641
+ ["2010-01-01T01:100:00.0000+01:01", false], // minute has too many digits
1642
+ ["2010-01-01T01:60:00.0000+01:01", false], // minute is too large
1643
+
1644
+ // Validate seconds
1645
+ ["2010-01-01T01:00:00.0000+01:01", true], // second has two digits
1646
+ ["2010-01-01T01:00:-00.0000+01:01", false], // second must be positive
1647
+ ["2010-01-01T01:00:.0000+01:01", false], // second has too few digits
1648
+ ["2010-01-01T01:00:0.0000+01:01", false], // second has too few digits
1649
+ ["2010-01-01T01:00:100.0000+01:01", false], // second has too many digits
1650
+ ["2010-01-01T01:00:60.0000+01:01", false], // second is too large
1651
+
1652
+ // Validate milliseconds
1653
+ ["2010-01-01T01:00:00+01:01", false], // millisecond must be specified
1654
+ ["2010-01-01T01:00:00.-0000+01:01", false], // millisecond must be positive
1655
+ ["2010-01-01T01:00:00:0000+01:01", false], // millisecond must use period separator
1656
+ ["2010-01-01T01:00:00.+01:01", false], // millisecond has too few digits
1657
+
1658
+ // Validate timezone
1659
+ ["2010-06-15T00:00:00.0000", false], // timezone must be specified
1660
+
1661
+ // Validate timezone offset
1662
+ ["2010-06-15T00:00:00.0000+01:01", true], // timezone offset can be positive hours and minutes
1663
+ ["2010-06-15T00:00:00.0000-01:01", true], // timezone offset can be negative hours and minutes
1664
+ ["2010-06-15T00:00:00.0000~01:01", false], // timezone has postive/negative indicator
1665
+ ["2010-06-15T00:00:00.000001:01", false], // timezone has postive/negative indicator
1666
+ ["2010-06-15T00:00:00.0000+00:01Z", false], // timezone invalid trailing characters
1667
+ ["2010-06-15T00:00:00.0000+00:01 ", false], // timezone invalid trailing characters
1668
+
1669
+ // Validate timezone hour offset
1670
+ ["2010-06-15T00:00:00.0000+:01", false], // timezone hour offset has too few digits
1671
+ ["2010-06-15T00:00:00.0000+0:01", false], // timezone hour offset has too few digits
1672
+ ["2010-06-15T00:00:00.0000+211:01", false], // timezone hour offset too many digits
1673
+ ["2010-06-15T00:00:00.0000+31:01", false], // timezone hour offset value too large
1674
+
1675
+ // Validate timezone minute offset
1676
+ ["2010-06-15T00:00:00.0000+00:-01", false], // timezone minute offset must be positive
1677
+ ["2010-06-15T00:00:00.0000+00.01", false], // timezone minute offset must use colon separator
1678
+ ["2010-06-15T00:00:00.0000+0101", false], // timezone minute offset must use colon separator
1679
+ ["2010-06-15T00:00:00.0000+010", false], // timezone minute offset must use colon separator
1680
+ ["2010-06-15T00:00:00.0000+00", false], // timezone minute offset has too few digits
1681
+ ["2010-06-15T00:00:00.0000+00:", false], // timezone minute offset has too few digits
1682
+ ["2010-06-15T00:00:00.0000+00:0", false], // timezone minute offset has too few digits
1683
+ ["2010-06-15T00:00:00.0000+00:211", false], // timezone minute offset has too many digits
1684
+ ["2010-06-15T00:00:00.0000+01010", false], // timezone minute offset has too many digits
1685
+ ["2010-06-15T00:00:00.0000+00:61", false], // timezone minute offset is too large
1686
+
1687
+ // Validate timezone UTC
1688
+ ["2010-06-15T00:00:00.0000Z", true], // UTC timezone can be indicated with Z
1689
+ ["2010-06-15T00:00:00.0000K", false], // UTC timezone indicator is invalid
1690
+ ["2010-06-15T00:00:00.0000 Z", false], // UTC timezone indicator has extra space
1691
+ ["2010-06-15T00:00:00.0000ZZ", false], // UTC timezone indicator invalid trailing characters
1692
+ ["2010-06-15T00:00:00.0000Z ", false], // UTC timezone indicator invalid trailing characters
1693
+ ].forEach((item) => {
1694
+ it("should validate date: $prop", () => {
1695
+ const date = item[0];
1696
+ const valid = item[1];
1697
+
1698
+ expect(ISO_DATE_REGEXP.test(date)).toBe(valid);
1699
+ });
1700
+ });
1701
+ });
1702
+ });
1703
+
1704
+ describe("number", () => {
1705
+ // Helpers for min / max tests
1706
+ const subtract = function (value) {
1707
+ return value - 5;
1708
+ };
1709
+
1710
+ const add = function (value) {
1711
+ return value + 5;
1712
+ };
1713
+
1714
+ it("should reset the model if view is invalid", () => {
1715
+ const inputElm = $compile('<input type="number" ng-model="age"/>')(
1716
+ scope,
1717
+ );
1718
+
1719
+ scope.$apply("age = 123");
1720
+ expect(inputElm.val()).toBe("123");
1721
+
1722
+ inputElm[0].value = "123X";
1723
+ inputElm[0].dispatchEvent(new Event("change"));
1724
+
1725
+ expect(inputElm.val()).toBe("");
1726
+ expect(scope.age).toBeNull();
1727
+ expect(inputElm[0].classList.contains("ng-valid")).toBeTrue();
1728
+ });
1729
+
1730
+ it("should render as blank if null", () => {
1731
+ const inputElm = $compile('<input type="number" ng-model="age" />')(
1732
+ scope,
1733
+ );
1734
+
1735
+ scope.$apply("age = null");
1736
+
1737
+ expect(scope.age).toBeNull();
1738
+ expect(inputElm.val()).toEqual("");
1739
+ });
1740
+
1741
+ it("should come up blank when no value specified", () => {
1742
+ const inputElm = $compile('<input type="number" ng-model="age" />')(
1743
+ scope,
1744
+ );
1745
+
1746
+ expect(inputElm.val()).toBe("");
1747
+
1748
+ scope.$apply("age = null");
1749
+
1750
+ expect(scope.age).toBeNull();
1751
+ expect(inputElm.val()).toBe("");
1752
+ });
1753
+
1754
+ it("should parse empty string to null", () => {
1755
+ const inputElm = $compile('<input type="number" ng-model="age" />')(
1756
+ scope,
1757
+ );
1758
+
1759
+ scope.$apply("age = 10");
1760
+
1761
+ inputElm[0].value = "";
1762
+ inputElm[0].dispatchEvent(new Event("change"));
1763
+
1764
+ expect(scope.age).toBeNull();
1765
+ expect(inputElm[0].classList.contains("ng-valid")).toBeTrue();
1766
+ });
1767
+
1768
+ it("should only invalidate the model if suffering from bad input when the data is parsed", () => {
1769
+ const inputElm = $compile('<input type="number" ng-model="age" />')(
1770
+ scope,
1771
+ );
1772
+
1773
+ expect(scope.age).toBeUndefined();
1774
+ expect(inputElm[0].classList.contains("ng-valid")).toBeTrue();
1775
+
1776
+ inputElm[0].value = "this-will-fail-because-of-the-badInput-flag";
1777
+ inputElm[0].dispatchEvent(new Event("change"));
1778
+
1779
+ expect(scope.age).toBeNull();
1780
+ expect(inputElm[0].classList.contains("ng-valid")).toBeTrue();
1781
+ });
1782
+
1783
+ it("should validate number if transition from bad input to empty string", () => {
1784
+ const inputElm = $compile('<input type="number" ng-model="age" />')(
1785
+ scope,
1786
+ );
1787
+ inputElm[0].value = "10a";
1788
+ inputElm[0].dispatchEvent(new Event("change"));
1789
+
1790
+ inputElm[0].value = "";
1791
+ inputElm[0].dispatchEvent(new Event("change"));
1792
+ expect(scope.age).toBeNull();
1793
+ expect(inputElm[0].classList.contains("ng-valid")).toBeTrue();
1794
+ });
1795
+
1796
+ it("should validate with undefined viewValue when $validate() called", () => {
1797
+ const inputElm = $compile(
1798
+ '<form name="form"><input type="number" name="alias" ng-model="value" /></form>',
1799
+ )(scope);
1800
+
1801
+ scope.form.alias.$validate();
1802
+
1803
+ expect(inputElm[0].classList.contains("ng-valid")).toBeTrue();
1804
+ expect(scope.form.alias.$error.number).toBeUndefined();
1805
+ });
1806
+
1807
+ it("should throw if the model value is not a number", () => {
1808
+ scope.value = "one";
1809
+ expect(() => {
1810
+ $compile('<input type="number" ng-model="value" />')(scope);
1811
+ scope.$digest();
1812
+ }).toThrowError(/numfmt/);
1813
+ });
1814
+
1815
+ it("should parse exponential notation", () => {
1816
+ const formElm = $compile(
1817
+ '<form name="form"><input type="number" name="alias" ng-model="value" /></form>',
1818
+ )(scope);
1819
+ const inputElm = formElm.find("input");
1820
+
1821
+ // #.###e+##
1822
+ scope.form.alias.$setViewValue("1.23214124123412412e+26");
1823
+ expect(inputElm[0].classList.contains("ng-valid")).toBeTrue();
1824
+ expect(scope.value).toBe(1.23214124123412412e26);
1825
+
1826
+ // #.###e##
1827
+ scope.form.alias.$setViewValue("1.23214124123412412e26");
1828
+ expect(inputElm[0].classList.contains("ng-valid")).toBeTrue();
1829
+ expect(scope.value).toBe(1.23214124123412412e26);
1830
+
1831
+ // #.###e-##
1832
+ scope.form.alias.$setViewValue("1.23214124123412412e-26");
1833
+ expect(inputElm[0].classList.contains("ng-valid")).toBeTrue();
1834
+ expect(scope.value).toBe(1.23214124123412412e-26);
1835
+
1836
+ // ####e+##
1837
+ scope.form.alias.$setViewValue("123214124123412412e+26");
1838
+ expect(inputElm[0].classList.contains("ng-valid")).toBeTrue();
1839
+ expect(scope.value).toBe(123214124123412412e26);
1840
+
1841
+ // ####e##
1842
+ scope.form.alias.$setViewValue("123214124123412412e26");
1843
+ expect(inputElm[0].classList.contains("ng-valid")).toBeTrue();
1844
+ expect(scope.value).toBe(123214124123412412e26);
1845
+
1846
+ // ####e-##
1847
+ scope.form.alias.$setViewValue("123214124123412412e-26");
1848
+ expect(inputElm[0].classList.contains("ng-valid")).toBeTrue();
1849
+ expect(scope.value).toBe(123214124123412412e-26);
1850
+
1851
+ // #.###E+##
1852
+ scope.form.alias.$setViewValue("1.23214124123412412E+26");
1853
+ expect(inputElm[0].classList.contains("ng-valid")).toBeTrue();
1854
+ expect(scope.value).toBe(1.23214124123412412e26);
1855
+
1856
+ // #.###E##
1857
+ scope.form.alias.$setViewValue("1.23214124123412412E26");
1858
+ expect(inputElm[0].classList.contains("ng-valid")).toBeTrue();
1859
+ expect(scope.value).toBe(1.23214124123412412e26);
1860
+
1861
+ // #.###E-##
1862
+ scope.form.alias.$setViewValue("1.23214124123412412E-26");
1863
+ expect(inputElm[0].classList.contains("ng-valid")).toBeTrue();
1864
+ expect(scope.value).toBe(1.23214124123412412e-26);
1865
+
1866
+ // ####E+##
1867
+ scope.form.alias.$setViewValue("123214124123412412E+26");
1868
+ expect(inputElm[0].classList.contains("ng-valid")).toBeTrue();
1869
+ expect(scope.value).toBe(123214124123412412e26);
1870
+
1871
+ // ####E##
1872
+ scope.form.alias.$setViewValue("123214124123412412E26");
1873
+ expect(inputElm[0].classList.contains("ng-valid")).toBeTrue();
1874
+ expect(scope.value).toBe(123214124123412412e26);
1875
+
1876
+ // ####E-##
1877
+ scope.form.alias.$setViewValue("123214124123412412E-26");
1878
+ expect(inputElm[0].classList.contains("ng-valid")).toBeTrue();
1879
+ expect(scope.value).toBe(123214124123412412e-26);
1880
+ });
1881
+
1882
+ it("should bind to scope if input is valid", () => {
1883
+ const inputElm = $compile('<input type="number" ng-model="age"/>')(
1884
+ scope,
1885
+ );
1886
+ const ctrl = inputElm.controller("ngModel");
1887
+
1888
+ let previousParserFail = false;
1889
+ let laterParserFail = false;
1890
+
1891
+ ctrl.$parsers.unshift((value) =>
1892
+ previousParserFail ? undefined : value,
1893
+ );
1894
+
1895
+ ctrl.$parsers.push((value) => (laterParserFail ? undefined : value));
1896
+
1897
+ inputElm[0].value = "123X";
1898
+ inputElm[0].dispatchEvent(new Event("change"));
1899
+ expect(inputElm.val()).toBe("");
1900
+
1901
+ expect(scope.age).toBeNull();
1902
+ expect(inputElm[0].classList.contains("ng-valid")).toBeTrue();
1903
+ expect(ctrl.$error.number).toBeUndefined();
1904
+
1905
+ inputElm[0].value = "123";
1906
+ inputElm[0].dispatchEvent(new Event("change"));
1907
+ expect(inputElm.val()).toBe("123");
1908
+ expect(scope.age).toBe(123);
1909
+ expect(inputElm[0].classList.contains("ng-valid")).toBeTrue();
1910
+ expect(ctrl.$error.number).toBeFalsy();
1911
+ expect(ctrl.$error.parse).toBe(undefined);
1912
+ });
1913
+
1914
+ describe("min", () => {
1915
+ it("should validate", () => {
1916
+ const formElm = $compile(
1917
+ '<form name="form"><input type="number" ng-model="value" name="alias" min="10" /></form>',
1918
+ )(scope);
1919
+ const inputElm = formElm.find("input");
1920
+
1921
+ inputElm[0].value = "1";
1922
+ inputElm[0].dispatchEvent(new Event("change"));
1923
+ expect(inputElm[0].classList.contains("ng-invalid")).toBeTrue();
1924
+ expect(scope.value).toBeFalsy();
1925
+ expect(scope.form.alias.$error.min).toBeTruthy();
1926
+
1927
+ inputElm[0].value = "100";
1928
+ inputElm[0].dispatchEvent(new Event("change"));
1929
+
1930
+ expect(inputElm[0].classList.contains("ng-valid")).toBeTrue();
1931
+ expect(scope.value).toBe(100);
1932
+ expect(scope.form.alias.$error.min).toBeFalsy();
1933
+ });
1934
+
1935
+ it("should validate against the viewValue", () => {
1936
+ const formElm = $compile(
1937
+ '<form name="form"><input type="number" ng-model-options="{allowInvalid: true}" ng-model="value" name="alias" min="10" /></form>',
1938
+ )(scope);
1939
+ const inputElm = formElm.find("input");
1940
+ const ngModelCtrl = inputElm.controller("ngModel");
1941
+ ngModelCtrl.$parsers.push(subtract);
1942
+
1943
+ inputElm[0].value = "10";
1944
+ inputElm[0].dispatchEvent(new Event("change"));
1945
+ expect(inputElm[0].classList.contains("ng-valid")).toBeTrue();
1946
+ expect(scope.value).toBe(5);
1947
+ expect(scope.form.alias.$error.min).toBeFalsy();
1948
+
1949
+ ngModelCtrl.$parsers.pop();
1950
+ ngModelCtrl.$parsers.push(add);
1951
+
1952
+ inputElm[0].value = "5";
1953
+ inputElm[0].dispatchEvent(new Event("change"));
1954
+ expect(inputElm[0].classList.contains("ng-invalid")).toBeTrue();
1955
+ expect(scope.form.alias.$error.min).toBeTruthy();
1956
+ expect(scope.value).toBe(10);
1957
+ });
1958
+
1959
+ it("should validate even if min value changes on-the-fly", () => {
1960
+ scope.min = undefined;
1961
+ const inputElm = $compile(
1962
+ '<input type="number" ng-model="value" name="alias" min="{{min}}" />',
1963
+ )(scope);
1964
+ expect(inputElm[0].classList.contains("ng-valid")).toBeTrue();
1965
+
1966
+ inputElm[0].value = "15";
1967
+ inputElm[0].dispatchEvent(new Event("change"));
1968
+ expect(inputElm[0].classList.contains("ng-valid")).toBeTrue();
1969
+
1970
+ scope.min = 10;
1971
+ scope.$digest();
1972
+ expect(inputElm[0].classList.contains("ng-valid")).toBeTrue();
1973
+
1974
+ scope.min = 20;
1975
+ scope.$digest();
1976
+ expect(inputElm[0].classList.contains("ng-invalid")).toBeTrue();
1977
+
1978
+ scope.min = null;
1979
+ scope.$digest();
1980
+ expect(inputElm[0].classList.contains("ng-valid")).toBeTrue();
1981
+
1982
+ scope.min = "20";
1983
+ scope.$digest();
1984
+ expect(inputElm[0].classList.contains("ng-invalid")).toBeTrue();
1985
+
1986
+ scope.min = "abc";
1987
+ scope.$digest();
1988
+ expect(inputElm[0].classList.contains("ng-valid")).toBeTrue();
1989
+ });
1990
+ });
1991
+
1992
+ describe("ngMin", () => {
1993
+ it("should validate", () => {
1994
+ const formElm = $compile(
1995
+ '<form name="form"><input type="number" ng-model="value" name="alias" ng-min="50" /></form>',
1996
+ )(scope);
1997
+ const inputElm = formElm.find("input");
1998
+
1999
+ inputElm[0].value = "1";
2000
+ inputElm[0].dispatchEvent(new Event("change"));
2001
+ expect(inputElm[0].classList.contains("ng-invalid")).toBeTrue();
2002
+ expect(scope.value).toBeFalsy();
2003
+ expect(scope.form.alias.$error.min).toBeTruthy();
2004
+
2005
+ inputElm[0].value = "100";
2006
+ inputElm[0].dispatchEvent(new Event("change"));
2007
+ expect(inputElm[0].classList.contains("ng-valid")).toBeTrue();
2008
+ expect(scope.value).toBe(100);
2009
+ expect(scope.form.alias.$error.min).toBeFalsy();
2010
+ });
2011
+
2012
+ it("should validate against the viewValue", () => {
2013
+ const formElm = $compile(
2014
+ '<form name="form"><input type="number" ng-model="value" name="alias" ng-min="10" /></form>',
2015
+ )(scope);
2016
+ const inputElm = formElm.find("input");
2017
+ const ngModelCtrl = inputElm.controller("ngModel");
2018
+ ngModelCtrl.$parsers.push(subtract);
2019
+
2020
+ inputElm[0].value = "10";
2021
+ inputElm[0].dispatchEvent(new Event("change"));
2022
+ expect(inputElm[0].classList.contains("ng-valid")).toBeTrue();
2023
+ expect(scope.value).toBe(5);
2024
+ expect(scope.form.alias.$error.min).toBeFalsy();
2025
+
2026
+ ngModelCtrl.$parsers.pop();
2027
+ ngModelCtrl.$parsers.push(add);
2028
+
2029
+ inputElm[0].value = "5";
2030
+ inputElm[0].dispatchEvent(new Event("change"));
2031
+ expect(inputElm[0].classList.contains("ng-invalid")).toBeTrue();
2032
+ expect(scope.form.alias.$error.min).toBeTruthy();
2033
+ expect(scope.value).toBe(undefined);
2034
+ });
2035
+
2036
+ it("should validate even if the ngMin value changes on-the-fly", () => {
2037
+ scope.min = undefined;
2038
+ const inputElm = $compile(
2039
+ '<input type="number" ng-model="value" name="alias" ng-min="min" />',
2040
+ )(scope);
2041
+ expect(inputElm[0].classList.contains("ng-valid")).toBeTrue();
2042
+
2043
+ inputElm[0].value = "15";
2044
+ inputElm[0].dispatchEvent(new Event("change"));
2045
+ expect(inputElm[0].classList.contains("ng-valid")).toBeTrue();
2046
+
2047
+ scope.min = 10;
2048
+ scope.$digest();
2049
+ expect(inputElm[0].classList.contains("ng-valid")).toBeTrue();
2050
+
2051
+ scope.min = 20;
2052
+ scope.$digest();
2053
+ expect(inputElm[0].classList.contains("ng-invalid")).toBeTrue();
2054
+
2055
+ scope.min = null;
2056
+ scope.$digest();
2057
+ expect(inputElm[0].classList.contains("ng-valid")).toBeTrue();
2058
+
2059
+ scope.min = "20";
2060
+ scope.$digest();
2061
+ expect(inputElm[0].classList.contains("ng-invalid")).toBeTrue();
2062
+
2063
+ scope.min = "abc";
2064
+ scope.$digest();
2065
+ expect(inputElm[0].classList.contains("ng-valid")).toBeTrue();
2066
+ });
2067
+ });
2068
+
2069
+ describe("max", () => {
2070
+ it("should validate", () => {
2071
+ const formElm = $compile(
2072
+ '<form name="form"><input type="number" ng-model="value" name="alias" max="10" /></form>',
2073
+ )(scope);
2074
+ const inputElm = formElm.find("input");
2075
+
2076
+ inputElm[0].value = "20";
2077
+ inputElm[0].dispatchEvent(new Event("change"));
2078
+ expect(inputElm[0].classList.contains("ng-invalid")).toBeTrue();
2079
+ expect(scope.value).toBeUndefined();
2080
+ expect(scope.form.alias.$error.max).toBeTruthy();
2081
+
2082
+ inputElm[0].value = "0";
2083
+ inputElm[0].dispatchEvent(new Event("change"));
2084
+ expect(inputElm[0].classList.contains("ng-valid")).toBeTrue();
2085
+ expect(scope.value).toBe(0);
2086
+ expect(scope.form.alias.$error.max).toBeFalsy();
2087
+ });
2088
+
2089
+ it("should validate against the viewValue", () => {
2090
+ const formElm = $compile(
2091
+ '<form name="form"><input type="number"' +
2092
+ 'ng-model-options="{allowInvalid: true}" ng-model="value" name="alias" max="10" /></form>',
2093
+ )(scope);
2094
+ const inputElm = formElm.find("input");
2095
+ const ngModelCtrl = inputElm.controller("ngModel");
2096
+ ngModelCtrl.$parsers.push(add);
2097
+
2098
+ inputElm[0].value = "10";
2099
+ inputElm[0].dispatchEvent(new Event("change"));
2100
+ expect(inputElm[0].classList.contains("ng-valid")).toBeTrue();
2101
+ expect(scope.value).toBe(15);
2102
+ expect(scope.form.alias.$error.max).toBeFalsy();
2103
+
2104
+ ngModelCtrl.$parsers.pop();
2105
+ ngModelCtrl.$parsers.push(subtract);
2106
+
2107
+ inputElm[0].value = "15";
2108
+ inputElm[0].dispatchEvent(new Event("change"));
2109
+ expect(inputElm[0].classList.contains("ng-invalid")).toBeTrue();
2110
+ expect(scope.form.alias.$error.max).toBeTruthy();
2111
+ expect(scope.value).toBe(10);
2112
+ });
2113
+
2114
+ it("should validate even if max value changes on-the-fly", () => {
2115
+ scope.max = undefined;
2116
+ const inputElm = $compile(
2117
+ '<input type="number" ng-model="value" name="alias" max="{{max}}" />',
2118
+ )(scope);
2119
+ expect(inputElm[0].classList.contains("ng-valid")).toBeTrue();
2120
+
2121
+ inputElm[0].value = "5";
2122
+ inputElm[0].dispatchEvent(new Event("change"));
2123
+ expect(inputElm[0].classList.contains("ng-valid")).toBeTrue();
2124
+
2125
+ scope.max = 10;
2126
+ scope.$digest();
2127
+ expect(inputElm[0].classList.contains("ng-valid")).toBeTrue();
2128
+
2129
+ scope.max = 0;
2130
+ scope.$digest();
2131
+ expect(inputElm[0].classList.contains("ng-invalid")).toBeTrue();
2132
+
2133
+ scope.max = null;
2134
+ scope.$digest();
2135
+ expect(inputElm[0].classList.contains("ng-valid")).toBeTrue();
2136
+
2137
+ scope.max = "4";
2138
+ scope.$digest();
2139
+ expect(inputElm[0].classList.contains("ng-invalid")).toBeTrue();
2140
+
2141
+ scope.max = "abc";
2142
+ scope.$digest();
2143
+ expect(inputElm[0].classList.contains("ng-valid")).toBeTrue();
2144
+ });
2145
+ });
2146
+
2147
+ describe("ngMax", () => {
2148
+ it("should validate", () => {
2149
+ const formElm = $compile(
2150
+ '<form name="form"><input type="number" ng-model="value" name="alias" ng-max="5" /></form>',
2151
+ )(scope);
2152
+ const inputElm = formElm.find("input");
2153
+
2154
+ inputElm[0].value = "20";
2155
+ inputElm[0].dispatchEvent(new Event("change"));
2156
+ expect(inputElm[0].classList.contains("ng-invalid")).toBeTrue();
2157
+ expect(scope.value).toBeUndefined();
2158
+ expect(scope.form.alias.$error.max).toBeTruthy();
2159
+
2160
+ inputElm[0].value = "0";
2161
+ inputElm[0].dispatchEvent(new Event("change"));
2162
+ expect(inputElm[0].classList.contains("ng-valid")).toBeTrue();
2163
+ expect(scope.value).toBe(0);
2164
+ expect(scope.form.alias.$error.max).toBeFalsy();
2165
+ });
2166
+
2167
+ it("should validate against the viewValue", () => {
2168
+ const formElm = $compile(
2169
+ '<form name="form"><input type="number"' +
2170
+ 'ng-model-options="{allowInvalid: true}" ng-model="value" name="alias" ng-max="10" /></form>',
2171
+ )(scope);
2172
+ const inputElm = formElm.find("input");
2173
+ const ngModelCtrl = inputElm.controller("ngModel");
2174
+ ngModelCtrl.$parsers.push(add);
2175
+
2176
+ inputElm[0].value = "10";
2177
+ inputElm[0].dispatchEvent(new Event("change"));
2178
+ expect(inputElm[0].classList.contains("ng-valid")).toBeTrue();
2179
+ expect(scope.value).toBe(15);
2180
+ expect(scope.form.alias.$error.max).toBeFalsy();
2181
+
2182
+ ngModelCtrl.$parsers.pop();
2183
+ ngModelCtrl.$parsers.push(subtract);
2184
+
2185
+ inputElm[0].value = "15";
2186
+ inputElm[0].dispatchEvent(new Event("change"));
2187
+ expect(inputElm[0].classList.contains("ng-invalid")).toBeTrue();
2188
+ expect(scope.form.alias.$error.max).toBeTruthy();
2189
+ expect(scope.value).toBe(10);
2190
+ });
2191
+
2192
+ it("should validate even if the ngMax value changes on-the-fly", () => {
2193
+ scope.max = undefined;
2194
+ const inputElm = $compile(
2195
+ '<input type="number" ng-model="value" name="alias" ng-max="max" />',
2196
+ )(scope);
2197
+ expect(inputElm[0].classList.contains("ng-valid")).toBeTrue();
2198
+
2199
+ inputElm[0].value = "5";
2200
+ inputElm[0].dispatchEvent(new Event("change"));
2201
+ expect(inputElm[0].classList.contains("ng-valid")).toBeTrue();
2202
+
2203
+ scope.max = 10;
2204
+ scope.$digest();
2205
+ expect(inputElm[0].classList.contains("ng-valid")).toBeTrue();
2206
+
2207
+ scope.max = 0;
2208
+ scope.$digest();
2209
+ expect(inputElm[0].classList.contains("ng-invalid")).toBeTrue();
2210
+
2211
+ scope.max = null;
2212
+ scope.$digest();
2213
+ expect(inputElm[0].classList.contains("ng-valid")).toBeTrue();
2214
+
2215
+ scope.max = "4";
2216
+ scope.$digest();
2217
+ expect(inputElm[0].classList.contains("ng-invalid")).toBeTrue();
2218
+
2219
+ scope.max = "abc";
2220
+ scope.$digest();
2221
+ expect(inputElm[0].classList.contains("ng-valid")).toBeTrue();
2222
+ });
2223
+ });
2224
+
2225
+ forEach(
2226
+ {
2227
+ step: 'step="{{step}}"',
2228
+ ngStep: 'ng-step="step"',
2229
+ },
2230
+ (attrHtml, attrName) => {
2231
+ describe(attrName, () => {
2232
+ it("should validate", () => {
2233
+ scope.step = 10;
2234
+ scope.value = 20;
2235
+ const formElm = $compile(
2236
+ `<form name="form"><input type="number" ng-model="value" name="alias" ${attrHtml} /></form>`,
2237
+ )(scope);
2238
+ const inputElm = formElm.find("input");
2239
+ scope.$digest();
2240
+ expect(inputElm.val()).toBe("20");
2241
+ expect(inputElm[0].classList.contains("ng-valid")).toBeTrue();
2242
+ expect(scope.value).toBe(20);
2243
+ expect(scope.form.alias.$error.step).toBeFalsy();
2244
+
2245
+ inputElm[0].value = "18";
2246
+ inputElm[0].dispatchEvent(new Event("change"));
2247
+ expect(inputElm[0].classList.contains("ng-invalid")).toBeTrue();
2248
+ expect(inputElm.val()).toBe("18");
2249
+ expect(scope.value).toBeUndefined();
2250
+ expect(scope.form.alias.$error.step).toBeTruthy();
2251
+
2252
+ inputElm[0].value = "10";
2253
+ inputElm[0].dispatchEvent(new Event("change"));
2254
+ expect(inputElm[0].classList.contains("ng-valid")).toBeTrue();
2255
+ expect(inputElm.val()).toBe("10");
2256
+ expect(scope.value).toBe(10);
2257
+ expect(scope.form.alias.$error.step).toBeFalsy();
2258
+
2259
+ scope.$apply("value = 12");
2260
+ expect(inputElm[0].classList.contains("ng-invalid")).toBeTrue();
2261
+ expect(inputElm.val()).toBe("12");
2262
+ expect(scope.value).toBe(12);
2263
+ expect(scope.form.alias.$error.step).toBeTruthy();
2264
+ });
2265
+
2266
+ it("should validate even if the step value changes on-the-fly", () => {
2267
+ scope.step = 10;
2268
+ const formElm = $compile(
2269
+ `<form name="form"><input type="number" ng-model="value" name="alias" ${attrHtml} /></form>`,
2270
+ )(scope);
2271
+ const inputElm = formElm.find("input");
2272
+ inputElm[0].value = "10";
2273
+ inputElm[0].dispatchEvent(new Event("change"));
2274
+ expect(inputElm[0].classList.contains("ng-valid")).toBeTrue();
2275
+ expect(scope.value).toBe(10);
2276
+
2277
+ // Step changes, but value matches
2278
+ scope.$apply("step = 5");
2279
+ expect(inputElm.val()).toBe("10");
2280
+ expect(inputElm[0].classList.contains("ng-valid")).toBeTrue();
2281
+ expect(scope.value).toBe(10);
2282
+ expect(scope.form.alias.$error.step).toBeFalsy();
2283
+
2284
+ // Step changes, value does not match
2285
+ scope.$apply("step = 6");
2286
+ expect(inputElm[0].classList.contains("ng-invalid")).toBeTrue();
2287
+ expect(scope.value).toBeUndefined();
2288
+ expect(inputElm.val()).toBe("10");
2289
+ expect(scope.form.alias.$error.step).toBeTruthy();
2290
+
2291
+ // null = valid
2292
+ scope.$apply("step = null");
2293
+ expect(inputElm[0].classList.contains("ng-valid")).toBeTrue();
2294
+ expect(scope.value).toBe(10);
2295
+ expect(inputElm.val()).toBe("10");
2296
+ expect(scope.form.alias.$error.step).toBeFalsy();
2297
+
2298
+ // Step val as string
2299
+ scope.$apply('step = "7"');
2300
+ expect(inputElm[0].classList.contains("ng-invalid")).toBeTrue();
2301
+ expect(scope.value).toBeUndefined();
2302
+ expect(inputElm.val()).toBe("10");
2303
+ expect(scope.form.alias.$error.step).toBeTruthy();
2304
+
2305
+ // unparsable string is ignored
2306
+ scope.$apply('step = "abc"');
2307
+ expect(inputElm[0].classList.contains("ng-valid")).toBeTrue();
2308
+ expect(scope.value).toBe(10);
2309
+ expect(inputElm.val()).toBe("10");
2310
+ expect(scope.form.alias.$error.step).toBeFalsy();
2311
+ });
2312
+
2313
+ it('should use the correct "step base" when `[min]` is specified', () => {
2314
+ scope.min = 5;
2315
+ scope.step = 10;
2316
+ scope.value = 10;
2317
+ const inputElm = $compile(
2318
+ `<input type="number" ng-model="value" min="{{min}}" ${attrHtml} />`,
2319
+ )(scope);
2320
+ const ngModel = inputElm.controller("ngModel");
2321
+ scope.$digest();
2322
+ expect(inputElm.val()).toBe("10");
2323
+ expect(inputElm[0].classList.contains("ng-invalid")).toBeTrue();
2324
+ expect(ngModel.$error.step).toBe(true);
2325
+ expect(scope.value).toBe(10); // an initially invalid value should not be changed
2326
+
2327
+ inputElm[0].value = "15";
2328
+ inputElm[0].dispatchEvent(new Event("change"));
2329
+ expect(inputElm[0].classList.contains("ng-valid")).toBeTrue();
2330
+ expect(scope.value).toBe(15);
2331
+
2332
+ scope.$apply("step = 3");
2333
+ expect(inputElm.val()).toBe("15");
2334
+ expect(inputElm[0].classList.contains("ng-invalid")).toBeTrue();
2335
+ expect(ngModel.$error.step).toBe(true);
2336
+ expect(scope.value).toBeUndefined();
2337
+
2338
+ inputElm[0].value = "8";
2339
+ inputElm[0].dispatchEvent(new Event("change"));
2340
+ expect(inputElm[0].classList.contains("ng-valid")).toBeTrue();
2341
+ expect(scope.value).toBe(8);
2342
+
2343
+ scope.$apply("min = 10; step = 20");
2344
+ inputElm[0].value = "30";
2345
+ inputElm[0].dispatchEvent(new Event("change"));
2346
+ expect(inputElm.val()).toBe("30");
2347
+ expect(inputElm[0].classList.contains("ng-valid")).toBeTrue();
2348
+ expect(scope.value).toBe(30);
2349
+
2350
+ scope.$apply("min = 5");
2351
+ expect(inputElm.val()).toBe("30");
2352
+ expect(inputElm[0].classList.contains("ng-invalid")).toBeTrue();
2353
+ expect(ngModel.$error.step).toBe(true);
2354
+ expect(scope.value).toBeUndefined();
2355
+
2356
+ scope.$apply("step = 0.00000001");
2357
+ expect(inputElm.val()).toBe("30");
2358
+ expect(inputElm[0].classList.contains("ng-valid")).toBeTrue();
2359
+ expect(scope.value).toBe(30);
2360
+
2361
+ // 0.3 - 0.2 === 0.09999999999999998
2362
+ scope.$apply("min = 0.2; step = (0.3 - 0.2)");
2363
+ inputElm[0].value = "0.3";
2364
+ inputElm[0].dispatchEvent(new Event("change"));
2365
+ expect(inputElm.val()).toBe("0.3");
2366
+ expect(inputElm[0].classList.contains("ng-invalid")).toBeTrue();
2367
+ expect(ngModel.$error.step).toBe(true);
2368
+ expect(scope.value).toBeUndefined();
2369
+ });
2370
+
2371
+ it("should correctly validate even in cases where the JS floating point arithmetic fails", () => {
2372
+ scope.step = 0.1;
2373
+ const inputElm = $compile(
2374
+ `<input type="number" ng-model="value" ${attrHtml} />`,
2375
+ )(scope);
2376
+ const ngModel = inputElm.controller("ngModel");
2377
+
2378
+ expect(inputElm.val()).toBe("");
2379
+ expect(inputElm[0].classList.contains("ng-valid")).toBeTrue();
2380
+ expect(scope.value).toBeUndefined();
2381
+
2382
+ inputElm[0].value = "0.3";
2383
+ inputElm[0].dispatchEvent(new Event("change"));
2384
+ expect(inputElm[0].classList.contains("ng-valid")).toBeTrue();
2385
+ expect(scope.value).toBe(0.3);
2386
+
2387
+ inputElm[0].value = "2.9999999999999996";
2388
+ inputElm[0].dispatchEvent(new Event("change"));
2389
+ expect(inputElm[0].classList.contains("ng-invalid")).toBeTrue();
2390
+ expect(ngModel.$error.step).toBe(true);
2391
+ expect(scope.value).toBeUndefined();
2392
+
2393
+ // 0.5 % 0.1 === 0.09999999999999998
2394
+ inputElm[0].value = "0.5";
2395
+ inputElm[0].dispatchEvent(new Event("change"));
2396
+ expect(inputElm[0].classList.contains("ng-valid")).toBeTrue();
2397
+ expect(scope.value).toBe(0.5);
2398
+
2399
+ // // 3.5 % 0.1 === 0.09999999999999981
2400
+ inputElm[0].value = "3.5";
2401
+ inputElm[0].dispatchEvent(new Event("change"));
2402
+ expect(inputElm[0].classList.contains("ng-valid")).toBeTrue();
2403
+ expect(scope.value).toBe(3.5);
2404
+
2405
+ // 1.16 % 0.01 === 0.009999999999999896
2406
+ // 1.16 * 100 === 115.99999999999999
2407
+ scope.step = 0.01;
2408
+ inputElm[0].value = "1.16";
2409
+ inputElm[0].dispatchEvent(new Event("change"));
2410
+ expect(inputElm[0].classList.contains("ng-valid")).toBeTrue();
2411
+ expect(scope.value).toBe(1.16);
2412
+ });
2413
+ });
2414
+ },
2415
+ );
2416
+
2417
+ describe("required", () => {
2418
+ it("should be valid even if value is 0", () => {
2419
+ const formElm = $compile(
2420
+ '<form name="form"><input type="number" ng-model="value" name="alias" required /></form>',
2421
+ )(scope);
2422
+ const inputElm = formElm.find("input");
2423
+
2424
+ inputElm[0].value = "0";
2425
+ inputElm[0].dispatchEvent(new Event("change"));
2426
+ expect(inputElm[0].classList.contains("ng-valid")).toBeTrue();
2427
+ expect(scope.value).toBe(0);
2428
+ expect(scope.form.alias.$error.required).toBeFalsy();
2429
+ });
2430
+
2431
+ it("should be valid even if value 0 is set from model", () => {
2432
+ const formElm = $compile(
2433
+ '<form name="form"><input type="number" ng-model="value" name="alias" required /></form>',
2434
+ )(scope);
2435
+ const inputElm = formElm.find("input");
2436
+ scope.$apply("value = 0");
2437
+
2438
+ expect(inputElm[0].classList.contains("ng-valid")).toBeTrue();
2439
+ expect(inputElm.val()).toBe("0");
2440
+ expect(scope.form.alias.$error.required).toBeFalsy();
2441
+ });
2442
+
2443
+ it("should register required on non boolean elements", () => {
2444
+ const formElm = $compile(
2445
+ '<form name="form"><div ng-model="value" name="alias" required></form>',
2446
+ )(scope);
2447
+ const inputElm = formElm.find("div");
2448
+
2449
+ scope.$apply("value = ''");
2450
+
2451
+ expect(inputElm[0].classList.contains("ng-invalid")).toBeTrue();
2452
+ expect(scope.form.alias.$error.required).toBeTruthy();
2453
+ });
2454
+
2455
+ it("should not invalidate number if ng-required=false and viewValue has not been committed", () => {
2456
+ const inputElm = $compile(
2457
+ '<input type="number" ng-model="value" name="alias" ng-required="required">',
2458
+ )(scope);
2459
+
2460
+ scope.$apply("required = false");
2461
+
2462
+ expect(inputElm[0].classList.contains("ng-valid")).toBeTrue();
2463
+ });
2464
+ });
2465
+
2466
+ describe("ngRequired", () => {
2467
+ describe("when the ngRequired expression initially evaluates to true", () => {
2468
+ it("should be valid even if value is 0", () => {
2469
+ const formElm = $compile(
2470
+ '<form name="form"><input type="number" ng-model="value" name="numberInput" ng-required="true" /></form>',
2471
+ )(scope);
2472
+ const inputElm = formElm.find("input");
2473
+
2474
+ inputElm[0].value = "0";
2475
+ inputElm[0].dispatchEvent(new Event("change"));
2476
+ expect(inputElm[0].classList.contains("ng-valid")).toBeTrue();
2477
+ expect(scope.value).toBe(0);
2478
+ expect(scope.form.numberInput.$error.required).toBeFalsy();
2479
+ });
2480
+
2481
+ it("should be valid even if value 0 is set from model", () => {
2482
+ const formElm = $compile(
2483
+ '<form name="form"><input type="number" ng-model="value" name="numberInput" ng-required="true" /></form>',
2484
+ )(scope);
2485
+ const inputElm = formElm.find("input");
2486
+
2487
+ scope.$apply("value = 0");
2488
+
2489
+ expect(inputElm[0].classList.contains("ng-valid")).toBeTrue();
2490
+ expect(inputElm.val()).toBe("0");
2491
+ expect(scope.form.numberInput.$error.required).toBeFalsy();
2492
+ });
2493
+
2494
+ it("should register required on non boolean elements", () => {
2495
+ const formElm = $compile(
2496
+ '<form name="form"><div ng-model="value" name="numberInput" ng-required="true"></form>',
2497
+ )(scope);
2498
+ const inputElm = formElm.find("div");
2499
+
2500
+ scope.$apply("value = ''");
2501
+
2502
+ expect(inputElm[0].classList.contains("ng-invalid")).toBeTrue();
2503
+ expect(scope.form.numberInput.$error.required).toBeTruthy();
2504
+ });
2505
+
2506
+ it("should change from invalid to valid when the value is empty and the ngRequired expression changes to false", () => {
2507
+ const formElm = $compile(
2508
+ '<form name="form"><input type="number" ng-model="value" name="numberInput" ng-required="ngRequiredExpr" /></form>',
2509
+ )(scope);
2510
+ const inputElm = formElm.find("input");
2511
+
2512
+ scope.$apply("ngRequiredExpr = true");
2513
+
2514
+ expect(inputElm[0].classList.contains("ng-invalid")).toBeTrue();
2515
+ expect(scope.value).toBeUndefined();
2516
+ expect(scope.form.numberInput.$error.required).toBeTruthy();
2517
+
2518
+ scope.$apply("ngRequiredExpr = false");
2519
+
2520
+ expect(inputElm[0].classList.contains("ng-valid")).toBeTrue();
2521
+ expect(scope.value).toBeUndefined();
2522
+ expect(scope.form.numberInput.$error.required).toBeFalsy();
2523
+ });
2524
+ });
2525
+
2526
+ describe("when the ngRequired expression initially evaluates to false", () => {
2527
+ it("should be valid even if value is empty", () => {
2528
+ const formElm = $compile(
2529
+ '<form name="form"><input type="number" ng-model="value" name="numberInput" ng-required="false" /></form>',
2530
+ )(scope);
2531
+ const inputElm = formElm.find("input");
2532
+
2533
+ expect(inputElm[0].classList.contains("ng-valid")).toBeTrue();
2534
+ expect(scope.value).toBeUndefined();
2535
+ expect(scope.form.numberInput.$error.required).toBeFalsy();
2536
+ expect(scope.form.numberInput.$error.number).toBeFalsy();
2537
+ });
2538
+
2539
+ it("should be valid if value is non-empty", () => {
2540
+ const formElm = $compile(
2541
+ '<form name="form"><input type="number" ng-model="value" name="numberInput" ng-required="false" /></form>',
2542
+ )(scope);
2543
+ const inputElm = formElm.find("input");
2544
+
2545
+ inputElm[0].value = "42";
2546
+ inputElm[0].dispatchEvent(new Event("change"));
2547
+ expect(inputElm[0].classList.contains("ng-valid")).toBeTrue();
2548
+ expect(scope.value).toBe(42);
2549
+ expect(scope.form.numberInput.$error.required).toBeFalsy();
2550
+ });
2551
+
2552
+ it("should not register required on non boolean elements", () => {
2553
+ const formElm = $compile(
2554
+ '<form name="form"><div ng-model="value" name="numberInput" ng-required="false"><form>',
2555
+ )(scope);
2556
+ const inputElm = formElm.find("div");
2557
+
2558
+ scope.$apply("value = ''");
2559
+
2560
+ expect(inputElm[0].classList.contains("ng-valid")).toBeTrue();
2561
+ expect(scope.form.numberInput.$error.required).toBeFalsy();
2562
+ });
2563
+
2564
+ it("should change from valid to invalid when the value is empty and the ngRequired expression changes to true", () => {
2565
+ const formElm = $compile(
2566
+ '<form name="form"><input type="number" ng-model="value" name="numberInput" ng-required="ngRequiredExpr" /><form>',
2567
+ )(scope);
2568
+ const inputElm = formElm.find("input");
2569
+
2570
+ scope.$apply("ngRequiredExpr = false");
2571
+
2572
+ expect(inputElm[0].classList.contains("ng-valid")).toBeTrue();
2573
+ expect(scope.value).toBeUndefined();
2574
+ expect(scope.form.numberInput.$error.required).toBeFalsy();
2575
+
2576
+ scope.$apply("ngRequiredExpr = true");
2577
+
2578
+ expect(inputElm[0].classList.contains("ng-invalid")).toBeTrue();
2579
+ expect(scope.value).toBeUndefined();
2580
+ expect(scope.form.numberInput.$error.required).toBeTruthy();
2581
+ });
2582
+ });
2583
+ });
2584
+
2585
+ describe("minlength", () => {
2586
+ it("should invalidate values that are shorter than the given minlength", () => {
2587
+ const inputElm = $compile(
2588
+ '<input type="number" ng-model="value" ng-minlength="3" />',
2589
+ )(scope);
2590
+
2591
+ inputElm[0].value = "12";
2592
+ inputElm[0].dispatchEvent(new Event("change"));
2593
+ expect(inputElm[0].classList.contains("ng-invalid")).toBeTrue();
2594
+
2595
+ inputElm[0].value = "123";
2596
+ inputElm[0].dispatchEvent(new Event("change"));
2597
+ expect(inputElm[0].classList.contains("ng-valid")).toBeTrue();
2598
+ });
2599
+
2600
+ it("should observe the standard minlength attribute and register it as a validator on the model", () => {
2601
+ const formElm = $compile(
2602
+ '<form name="form"><input type="number" name="input" ng-model="value" minlength="{{ min }}" /></form>',
2603
+ )(scope);
2604
+ const inputElm = formElm.find("input");
2605
+ scope.$apply(() => {
2606
+ scope.min = 10;
2607
+ });
2608
+
2609
+ inputElm[0].value = "12345";
2610
+ inputElm[0].dispatchEvent(new Event("change"));
2611
+ expect(inputElm[0].classList.contains("ng-invalid")).toBeTrue();
2612
+ expect(scope.form.input.$error.minlength).toBe(true);
2613
+
2614
+ scope.$apply(() => {
2615
+ scope.min = 5;
2616
+ });
2617
+
2618
+ expect(inputElm[0].classList.contains("ng-valid")).toBeTrue();
2619
+ expect(scope.form.input.$error.minlength).not.toBe(true);
2620
+ });
2621
+ });
2622
+
2623
+ describe("maxlength", () => {
2624
+ it("should invalidate values that are longer than the given maxlength", () => {
2625
+ const inputElm = $compile(
2626
+ '<input type="number" ng-model="value" ng-maxlength="5" />',
2627
+ )(scope);
2628
+
2629
+ inputElm[0].value = "12345678";
2630
+ inputElm[0].dispatchEvent(new Event("change"));
2631
+ expect(inputElm[0].classList.contains("ng-invalid")).toBeTrue();
2632
+
2633
+ inputElm[0].value = "123";
2634
+ inputElm[0].dispatchEvent(new Event("change"));
2635
+ expect(inputElm[0].classList.contains("ng-valid")).toBeTrue();
2636
+ });
2637
+
2638
+ it("should observe the standard maxlength attribute and register it as a validator on the model", () => {
2639
+ const formElm = $compile(
2640
+ '<form name="form"><input type="number" name="input" ng-model="value" maxlength="{{ max }}" /></form>',
2641
+ )(scope);
2642
+ const inputElm = formElm.find("input");
2643
+ scope.$apply(() => {
2644
+ scope.max = 1;
2645
+ });
2646
+
2647
+ inputElm[0].value = "12345";
2648
+ inputElm[0].dispatchEvent(new Event("change"));
2649
+ expect(inputElm[0].classList.contains("ng-invalid")).toBeTrue();
2650
+ expect(scope.form.input.$error.maxlength).toBe(true);
2651
+
2652
+ scope.$apply(() => {
2653
+ scope.max = 6;
2654
+ });
2655
+
2656
+ expect(inputElm[0].classList.contains("ng-valid")).toBeTrue();
2657
+ expect(scope.form.input.$error.maxlength).not.toBe(true);
2658
+ });
2659
+ });
2660
+ });
2661
+
2662
+ describe("range", () => {
2663
+ const rangeTestEl = jqLite('<input type="range">');
2664
+ const supportsRange = rangeTestEl[0].type === "range";
2665
+
2666
+ it("should render as 50 if null", () => {
2667
+ const inputElm = $compile('<input type="range" ng-model="age" />')(
2668
+ scope,
2669
+ );
2670
+
2671
+ inputElm[0].value = "25";
2672
+ inputElm[0].dispatchEvent(new Event("change"));
2673
+ expect(scope.age).toBe(25);
2674
+
2675
+ scope.$apply("age = null");
2676
+
2677
+ expect(inputElm.val()).toEqual("50");
2678
+ });
2679
+
2680
+ it("should set model to 50 when no value specified and default min/max", () => {
2681
+ const inputElm = $compile('<input type="range" ng-model="age" />')(
2682
+ scope,
2683
+ );
2684
+
2685
+ expect(inputElm.val()).toBe("50");
2686
+
2687
+ scope.$apply("age = null");
2688
+
2689
+ expect(scope.age).toBe(50);
2690
+ });
2691
+
2692
+ it("should parse non-number values to 50 when default min/max", () => {
2693
+ const inputElm = $compile('<input type="range" ng-model="age" />')(
2694
+ scope,
2695
+ );
2696
+
2697
+ scope.$apply("age = 10");
2698
+ expect(inputElm.val()).toBe("10");
2699
+
2700
+ inputElm[0].value = "";
2701
+ inputElm[0].dispatchEvent(new Event("change"));
2702
+ expect(scope.age).toBe(50);
2703
+ expect(inputElm[0].classList.contains("ng-valid")).toBeTrue();
2704
+ });
2705
+
2706
+ it("should parse the input value to a Number", () => {
2707
+ const inputElm = $compile('<input type="range" ng-model="age" />')(
2708
+ scope,
2709
+ );
2710
+
2711
+ inputElm[0].value = "75";
2712
+ inputElm[0].dispatchEvent(new Event("change"));
2713
+ expect(scope.age).toBe(75);
2714
+ });
2715
+
2716
+ it("should only invalidate the model if suffering from bad input when the data is parsed", () => {
2717
+ scope.age = 60;
2718
+
2719
+ const inputElm = $compile('<input type="range" ng-model="age" />')(
2720
+ scope,
2721
+ );
2722
+
2723
+ expect(inputElm[0].classList.contains("ng-valid")).toBeTrue();
2724
+
2725
+ inputElm[0].value = "this-will-fail-because-of-the-badInput-flag";
2726
+ inputElm[0].dispatchEvent(new Event("change"));
2727
+
2728
+ expect(scope.age).toBe(50);
2729
+ expect(inputElm[0].classList.contains("ng-valid")).toBeTrue();
2730
+ });
2731
+
2732
+ it("should throw if the model value is not a number", () => {
2733
+ expect(() => {
2734
+ scope.value = "one";
2735
+ const inputElm = $compile('<input type="range" ng-model="value" />')(
2736
+ scope,
2737
+ );
2738
+ scope.$digest();
2739
+ }).toThrowError(/numfmt/);
2740
+ });
2741
+
2742
+ describe("min", () => {
2743
+ it("should initialize correctly with non-default model and min value", () => {
2744
+ scope.value = -3;
2745
+ scope.min = -5;
2746
+ const formElm = $compile(
2747
+ '<form name="form"><input type="range" ng-model="value" name="alias" min="{{min}}" /></form>',
2748
+ )(scope);
2749
+ const inputElm = formElm.find("input");
2750
+ scope.$digest();
2751
+
2752
+ expect(inputElm[0].classList.contains("ng-valid")).toBeTrue();
2753
+ expect(inputElm.val()).toBe("-3");
2754
+ expect(scope.value).toBe(-3);
2755
+ expect(scope.form.alias.$error.min).toBeFalsy();
2756
+ });
2757
+
2758
+ // Browsers that implement range will never allow you to set the value < min values
2759
+ it("should adjust invalid input values", () => {
2760
+ const formElm = $compile(
2761
+ '<form name="form"><input type="range" ng-model="value" name="alias" min="10" /></form>',
2762
+ )(scope);
2763
+ const inputElm = formElm.find("input");
2764
+
2765
+ inputElm[0].value = "5";
2766
+ inputElm[0].dispatchEvent(new Event("change"));
2767
+ expect(inputElm[0].classList.contains("ng-valid")).toBeTrue();
2768
+ expect(scope.value).toBe(10);
2769
+ expect(scope.form.alias.$error.min).toBeFalsy();
2770
+
2771
+ inputElm[0].value = "100";
2772
+ inputElm[0].dispatchEvent(new Event("change"));
2773
+ expect(inputElm[0].classList.contains("ng-valid")).toBeTrue();
2774
+ expect(scope.value).toBe(100);
2775
+ expect(scope.form.alias.$error.min).toBeFalsy();
2776
+ });
2777
+
2778
+ it("should set the model to the min val if it is less than the min val", () => {
2779
+ scope.value = -10;
2780
+ // Default min is 0
2781
+ const inputElm = $compile(
2782
+ '<input type="range" ng-model="value" name="alias" min="{{min}}" />',
2783
+ )(scope);
2784
+ scope.$digest();
2785
+
2786
+ expect(inputElm[0].classList.contains("ng-valid")).toBeTrue();
2787
+ expect(inputElm.val()).toBe("0");
2788
+ expect(scope.value).toBe(0);
2789
+
2790
+ scope.$apply("value = 5; min = 10");
2791
+
2792
+ expect(inputElm[0].classList.contains("ng-valid")).toBeTrue();
2793
+ expect(inputElm.val()).toBe("10");
2794
+ expect(scope.value).toBe(10);
2795
+ });
2796
+
2797
+ it("should adjust the element and model value when the min value changes on-the-fly", () => {
2798
+ scope.min = 10;
2799
+ const inputElm = $compile(
2800
+ '<input type="range" ng-model="value" name="alias" min="{{min}}" />',
2801
+ )(scope);
2802
+ scope.$digest();
2803
+
2804
+ inputElm[0].value = "15";
2805
+ inputElm[0].dispatchEvent(new Event("change"));
2806
+ expect(inputElm[0].classList.contains("ng-valid")).toBeTrue();
2807
+
2808
+ scope.min = 20;
2809
+ scope.$digest();
2810
+ expect(inputElm[0].classList.contains("ng-valid")).toBeTrue();
2811
+ expect(scope.value).toBe(20);
2812
+ expect(inputElm.val()).toBe("20");
2813
+
2814
+ scope.min = null;
2815
+ scope.$digest();
2816
+ expect(inputElm[0].classList.contains("ng-valid")).toBeTrue();
2817
+ expect(scope.value).toBe(20);
2818
+ expect(inputElm.val()).toBe("20");
2819
+
2820
+ scope.min = "15";
2821
+ scope.$digest();
2822
+ expect(inputElm[0].classList.contains("ng-valid")).toBeTrue();
2823
+ expect(scope.value).toBe(20);
2824
+ expect(inputElm.val()).toBe("20");
2825
+
2826
+ scope.min = "abc";
2827
+ scope.$digest();
2828
+ expect(inputElm[0].classList.contains("ng-valid")).toBeTrue();
2829
+ expect(scope.value).toBe(20);
2830
+ expect(inputElm.val()).toBe("20");
2831
+ });
2832
+ });
2833
+
2834
+ describe("max", () => {
2835
+ // Browsers that implement range will never allow you to set the value > max value
2836
+ it("should initialize correctly with non-default model and max value", () => {
2837
+ scope.value = 130;
2838
+ scope.max = 150;
2839
+ const formElm = $compile(
2840
+ '<form name="form"><input type="range" ng-model="value" name="alias" max="{{max}}" /></form>',
2841
+ )(scope);
2842
+ const inputElm = formElm.find("input");
2843
+ scope.$digest();
2844
+ expect(inputElm[0].classList.contains("ng-valid")).toBeTrue();
2845
+ expect(inputElm.val()).toBe("130");
2846
+ expect(scope.value).toBe(130);
2847
+ expect(scope.form.alias.$error.max).toBeFalsy();
2848
+ });
2849
+
2850
+ it("should validate", () => {
2851
+ const formElm = $compile(
2852
+ '<form name="form"><input type="range" ng-model="value" name="alias" max="10" /></form>',
2853
+ )(scope);
2854
+ const inputElm = formElm.find("input");
2855
+
2856
+ inputElm[0].value = "20";
2857
+ inputElm[0].dispatchEvent(new Event("change"));
2858
+ expect(inputElm[0].classList.contains("ng-valid")).toBeTrue();
2859
+ expect(scope.value).toBe(10);
2860
+ expect(scope.form.alias.$error.max).toBeFalsy();
2861
+
2862
+ inputElm[0].value = "0";
2863
+ inputElm[0].dispatchEvent(new Event("change"));
2864
+ expect(inputElm[0].classList.contains("ng-valid")).toBeTrue();
2865
+ expect(scope.value).toBe(0);
2866
+ expect(scope.form.alias.$error.max).toBeFalsy();
2867
+ });
2868
+
2869
+ it("should set the model to the max val if it is greater than the max val", () => {
2870
+ scope.value = 110;
2871
+ // Default max is 100
2872
+ const inputElm = $compile(
2873
+ '<input type="range" ng-model="value" name="alias" max="{{max}}" />',
2874
+ )(scope);
2875
+ scope.$digest();
2876
+ expect(inputElm[0].classList.contains("ng-valid")).toBeTrue();
2877
+ expect(inputElm.val()).toBe("100");
2878
+ expect(scope.value).toBe(100);
2879
+
2880
+ scope.$apply("value = 90; max = 10");
2881
+
2882
+ expect(inputElm[0].classList.contains("ng-valid")).toBeTrue();
2883
+ expect(inputElm.val()).toBe("10");
2884
+ expect(scope.value).toBe(10);
2885
+ });
2886
+
2887
+ it("should adjust the element and model value if the max value changes on-the-fly", () => {
2888
+ scope.max = 10;
2889
+ const inputElm = $compile(
2890
+ '<input type="range" ng-model="value" name="alias" max="{{max}}" />',
2891
+ )(scope);
2892
+ scope.$digest();
2893
+ inputElm[0].value = "5";
2894
+ inputElm[0].dispatchEvent(new Event("change"));
2895
+ expect(inputElm[0].classList.contains("ng-valid")).toBeTrue();
2896
+
2897
+ scope.max = 0;
2898
+ scope.$digest();
2899
+ expect(inputElm[0].classList.contains("ng-valid")).toBeTrue();
2900
+ expect(scope.value).toBe(0);
2901
+ expect(inputElm.val()).toBe("0");
2902
+
2903
+ scope.max = null;
2904
+ scope.$digest();
2905
+ expect(inputElm[0].classList.contains("ng-valid")).toBeTrue();
2906
+ expect(scope.value).toBe(0);
2907
+ expect(inputElm.val()).toBe("0");
2908
+
2909
+ scope.max = "4";
2910
+ scope.$digest();
2911
+ expect(inputElm[0].classList.contains("ng-valid")).toBeTrue();
2912
+ expect(scope.value).toBe(0);
2913
+ expect(inputElm.val()).toBe("0");
2914
+
2915
+ scope.max = "abc";
2916
+ scope.$digest();
2917
+ expect(inputElm[0].classList.contains("ng-valid")).toBeTrue();
2918
+ expect(scope.value).toBe(0);
2919
+ expect(inputElm.val()).toBe("0");
2920
+ });
2921
+ });
2922
+
2923
+ describe("min and max", () => {
2924
+ it("should set the correct initial value when min and max are specified", () => {
2925
+ scope.max = 80;
2926
+ scope.min = 40;
2927
+ const inputElm = $compile(
2928
+ '<input type="range" ng-model="value" name="alias" max="{{max}}" min="{{min}}" />',
2929
+ )(scope);
2930
+ scope.$digest();
2931
+
2932
+ expect(inputElm.val()).toBe("60");
2933
+ expect(scope.value).toBe(60);
2934
+ });
2935
+
2936
+ it("should set element and model value to min if max is less than min", () => {
2937
+ scope.min = 40;
2938
+ const inputElm = $compile(
2939
+ '<input type="range" ng-model="value" name="alias" max="{{max}}" min="{{min}}" />',
2940
+ )(scope);
2941
+ scope.$digest();
2942
+ expect(inputElm.val()).toBe("70");
2943
+ expect(scope.value).toBe(70);
2944
+
2945
+ scope.max = 20;
2946
+ scope.$digest();
2947
+
2948
+ expect(inputElm.val()).toBe("40");
2949
+ expect(scope.value).toBe(40);
2950
+ });
2951
+ });
2952
+
2953
+ describe("step", () => {
2954
+ // Browsers that implement range will never allow you to set a value that doesn't match the step value
2955
+ // However, currently only Firefox fully implements the spec when setting the value after the step value changes.
2956
+ // Other browsers fail in various edge cases, which is why they are not tested here.
2957
+
2958
+ it("should round the input value to the nearest step on user input", () => {
2959
+ const formElm = $compile(
2960
+ '<form name="form"><input type="range" ng-model="value" name="alias" step="5" /></form>',
2961
+ )(scope);
2962
+ const inputElm = formElm.find("input");
2963
+
2964
+ inputElm[0].value = "5";
2965
+ inputElm[0].dispatchEvent(new Event("change"));
2966
+ expect(inputElm[0].classList.contains("ng-valid")).toBeTrue();
2967
+ expect(scope.value).toBe(5);
2968
+ expect(scope.form.alias.$error.step).toBeFalsy();
2969
+
2970
+ inputElm[0].value = "10";
2971
+ inputElm[0].dispatchEvent(new Event("change"));
2972
+ expect(inputElm[0].classList.contains("ng-valid")).toBeTrue();
2973
+ expect(scope.value).toBe(10);
2974
+ expect(scope.form.alias.$error.step).toBeFalsy();
2975
+
2976
+ inputElm[0].value = "9";
2977
+ inputElm[0].dispatchEvent(new Event("change"));
2978
+ expect(inputElm[0].classList.contains("ng-valid")).toBeTrue();
2979
+ expect(scope.value).toBe(10);
2980
+ expect(scope.form.alias.$error.step).toBeFalsy();
2981
+
2982
+ inputElm[0].value = "7";
2983
+ inputElm[0].dispatchEvent(new Event("change"));
2984
+ expect(inputElm[0].classList.contains("ng-valid")).toBeTrue();
2985
+ expect(scope.value).toBe(5);
2986
+ expect(scope.form.alias.$error.step).toBeFalsy();
2987
+
2988
+ inputElm[0].value = "7.5";
2989
+ inputElm[0].dispatchEvent(new Event("change"));
2990
+ expect(inputElm[0].classList.contains("ng-valid")).toBeTrue();
2991
+ expect(scope.value).toBe(10);
2992
+ expect(scope.form.alias.$error.step).toBeFalsy();
2993
+ });
2994
+
2995
+ it("should round the input value to the nearest step when setting the model", () => {
2996
+ const formElm = $compile(
2997
+ '<form name="form"><input type="range" ng-model="value" name="alias" step="5" /></form>',
2998
+ )(scope);
2999
+ const inputElm = formElm.find("input");
3000
+
3001
+ scope.$apply("value = 10");
3002
+ expect(inputElm.val()).toBe("10");
3003
+ expect(inputElm[0].classList.contains("ng-valid")).toBeTrue();
3004
+ expect(scope.value).toBe(10);
3005
+ expect(scope.form.alias.$error.step).toBeFalsy();
3006
+
3007
+ scope.$apply("value = 5");
3008
+ expect(inputElm.val()).toBe("5");
3009
+ expect(inputElm[0].classList.contains("ng-valid")).toBeTrue();
3010
+ expect(scope.value).toBe(5);
3011
+ expect(scope.form.alias.$error.step).toBeFalsy();
3012
+
3013
+ scope.$apply("value = 7.5");
3014
+ expect(inputElm.val()).toBe("10");
3015
+ expect(inputElm[0].classList.contains("ng-valid")).toBeTrue();
3016
+ expect(scope.value).toBe(10);
3017
+ expect(scope.form.alias.$error.step).toBeFalsy();
3018
+
3019
+ scope.$apply("value = 7");
3020
+ expect(inputElm.val()).toBe("5");
3021
+ expect(inputElm[0].classList.contains("ng-valid")).toBeTrue();
3022
+ expect(scope.value).toBe(5);
3023
+ expect(scope.form.alias.$error.step).toBeFalsy();
3024
+
3025
+ scope.$apply("value = 9");
3026
+ expect(inputElm.val()).toBe("10");
3027
+ expect(inputElm[0].classList.contains("ng-valid")).toBeTrue();
3028
+ expect(scope.value).toBe(10);
3029
+ expect(scope.form.alias.$error.step).toBeFalsy();
3030
+ });
3031
+ });
3032
+ });
3033
+
3034
+ describe("email", () => {
3035
+ it("should validate e-mail", () => {
3036
+ const formElm = $compile(
3037
+ '<form name="form">' +
3038
+ '<input type="email" ng-model="email" name="alias" />' +
3039
+ "</form>",
3040
+ )(scope);
3041
+ const inputElm = formElm.find("input");
3042
+
3043
+ const widget = scope.form.alias;
3044
+ inputElm[0].value = "vojta@google.com";
3045
+ inputElm[0].dispatchEvent(new Event("change"));
3046
+
3047
+ expect(scope.email).toBe("vojta@google.com");
3048
+ expect(inputElm[0].classList.contains("ng-valid")).toBeTrue();
3049
+ expect(widget.$error.email).toBeFalsy();
3050
+
3051
+ inputElm[0].value = "invalid@";
3052
+ inputElm[0].dispatchEvent(new Event("change"));
3053
+ expect(scope.email).toBeUndefined();
3054
+ expect(inputElm[0].classList.contains("ng-invalid")).toBeTrue();
3055
+ expect(widget.$error.email).toBeTruthy();
3056
+ });
3057
+
3058
+ describe("EMAIL_REGEXP", () => {
3059
+ /* global EMAIL_REGEXP: false */
3060
+ it("should validate email", () => {
3061
+ /* basic functionality */
3062
+ expect(EMAIL_REGEXP.test("a@b.com")).toBe(true);
3063
+ expect(EMAIL_REGEXP.test("a@b.museum")).toBe(true);
3064
+ expect(EMAIL_REGEXP.test("a@B.c")).toBe(true);
3065
+ /* domain label separation, hyphen-minus, syntax */
3066
+ expect(EMAIL_REGEXP.test("a@b.c.")).toBe(false);
3067
+ expect(EMAIL_REGEXP.test("a@.b.c")).toBe(false);
3068
+ expect(EMAIL_REGEXP.test("a@-b.c")).toBe(false);
3069
+ expect(EMAIL_REGEXP.test("a@b-.c")).toBe(false);
3070
+ expect(EMAIL_REGEXP.test("a@b-c")).toBe(true);
3071
+ expect(EMAIL_REGEXP.test("a@-")).toBe(false);
3072
+ expect(EMAIL_REGEXP.test("a@.")).toBe(false);
3073
+ expect(EMAIL_REGEXP.test("a@host_name")).toBe(false);
3074
+ /* leading or sole digit */
3075
+ expect(EMAIL_REGEXP.test("a@3b.c")).toBe(true);
3076
+ expect(EMAIL_REGEXP.test("a@3")).toBe(true);
3077
+ /* TLD eMail address */
3078
+ expect(EMAIL_REGEXP.test("a@b")).toBe(true);
3079
+ /* domain valid characters */
3080
+ expect(
3081
+ EMAIL_REGEXP.test(
3082
+ "a@abcdefghijklmnopqrstuvwxyz-ABCDEFGHIJKLMNOPQRSTUVWXYZ.0123456789",
3083
+ ),
3084
+ ).toBe(true);
3085
+ /* domain invalid characters */
3086
+ expect(EMAIL_REGEXP.test("a@")).toBe(false);
3087
+ expect(EMAIL_REGEXP.test("a@ ")).toBe(false);
3088
+ expect(EMAIL_REGEXP.test("a@!")).toBe(false);
3089
+ expect(EMAIL_REGEXP.test('a@"')).toBe(false);
3090
+ expect(EMAIL_REGEXP.test("a@#")).toBe(false);
3091
+ expect(EMAIL_REGEXP.test("a@$")).toBe(false);
3092
+ expect(EMAIL_REGEXP.test("a@%")).toBe(false);
3093
+ expect(EMAIL_REGEXP.test("a@&")).toBe(false);
3094
+ expect(EMAIL_REGEXP.test("a@'")).toBe(false);
3095
+ expect(EMAIL_REGEXP.test("a@(")).toBe(false);
3096
+ expect(EMAIL_REGEXP.test("a@)")).toBe(false);
3097
+ expect(EMAIL_REGEXP.test("a@*")).toBe(false);
3098
+ expect(EMAIL_REGEXP.test("a@+")).toBe(false);
3099
+ expect(EMAIL_REGEXP.test("a@,")).toBe(false);
3100
+ expect(EMAIL_REGEXP.test("a@/")).toBe(false);
3101
+ expect(EMAIL_REGEXP.test("a@:")).toBe(false);
3102
+ expect(EMAIL_REGEXP.test("a@;")).toBe(false);
3103
+ expect(EMAIL_REGEXP.test("a@<")).toBe(false);
3104
+ expect(EMAIL_REGEXP.test("a@=")).toBe(false);
3105
+ expect(EMAIL_REGEXP.test("a@>")).toBe(false);
3106
+ expect(EMAIL_REGEXP.test("a@?")).toBe(false);
3107
+ expect(EMAIL_REGEXP.test("a@@")).toBe(false);
3108
+ expect(EMAIL_REGEXP.test("a@[")).toBe(false);
3109
+ expect(EMAIL_REGEXP.test("a@\\")).toBe(false);
3110
+ expect(EMAIL_REGEXP.test("a@]")).toBe(false);
3111
+ expect(EMAIL_REGEXP.test("a@^")).toBe(false);
3112
+ expect(EMAIL_REGEXP.test("a@_")).toBe(false);
3113
+ expect(EMAIL_REGEXP.test("a@`")).toBe(false);
3114
+ expect(EMAIL_REGEXP.test("a@{")).toBe(false);
3115
+ expect(EMAIL_REGEXP.test("a@|")).toBe(false);
3116
+ expect(EMAIL_REGEXP.test("a@}")).toBe(false);
3117
+ expect(EMAIL_REGEXP.test("a@~")).toBe(false);
3118
+ expect(EMAIL_REGEXP.test("a@İ")).toBe(false);
3119
+ expect(EMAIL_REGEXP.test("a@ı")).toBe(false);
3120
+ /* domain length, label and total */
3121
+ expect(
3122
+ EMAIL_REGEXP.test(
3123
+ "a@xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx",
3124
+ ),
3125
+ ).toBe(true);
3126
+ expect(
3127
+ EMAIL_REGEXP.test(
3128
+ "a@xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx",
3129
+ ),
3130
+ ).toBe(false);
3131
+ /* eslint-disable max-len */
3132
+ expect(
3133
+ EMAIL_REGEXP.test(
3134
+ "a@xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx.xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx.xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx.xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx",
3135
+ ),
3136
+ ).toBe(true);
3137
+ expect(
3138
+ EMAIL_REGEXP.test(
3139
+ "a@xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx.xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx.xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx.xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx.x",
3140
+ ),
3141
+ ).toBe(true);
3142
+ expect(
3143
+ EMAIL_REGEXP.test(
3144
+ "a@xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx.xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx.xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx.xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx.xx",
3145
+ ),
3146
+ ).toBe(false);
3147
+ expect(
3148
+ EMAIL_REGEXP.test(
3149
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa@xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx.xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx.xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx.xx",
3150
+ ),
3151
+ ).toBe(true);
3152
+ expect(
3153
+ EMAIL_REGEXP.test(
3154
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa@xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx.xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx.xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx.xxx",
3155
+ ),
3156
+ ).toBe(false);
3157
+ /* eslint-enable */
3158
+ /* local-part valid characters and dot-atom syntax */
3159
+ expect(EMAIL_REGEXP.test("'@x")).toBe(true);
3160
+ expect(
3161
+ EMAIL_REGEXP.test(
3162
+ "-!#$%&*+/0123456789=?ABCDEFGHIJKLMNOPQRSTUVWXYZ@x",
3163
+ ),
3164
+ ).toBe(true);
3165
+ expect(EMAIL_REGEXP.test("^_`abcdefghijklmnopqrstuvwxyz{|}~@x")).toBe(
3166
+ true,
3167
+ );
3168
+ expect(EMAIL_REGEXP.test(".@x")).toBe(false);
3169
+ expect(EMAIL_REGEXP.test("'.@x")).toBe(false);
3170
+ expect(EMAIL_REGEXP.test(".'@x")).toBe(false);
3171
+ expect(EMAIL_REGEXP.test("'.'@x")).toBe(true);
3172
+ /* local-part invalid characters */
3173
+ expect(EMAIL_REGEXP.test("@x")).toBe(false);
3174
+ expect(EMAIL_REGEXP.test(" @x")).toBe(false);
3175
+ expect(EMAIL_REGEXP.test('"@x')).toBe(false);
3176
+ expect(EMAIL_REGEXP.test("(@x")).toBe(false);
3177
+ expect(EMAIL_REGEXP.test(")@x")).toBe(false);
3178
+ expect(EMAIL_REGEXP.test(",@x")).toBe(false);
3179
+ expect(EMAIL_REGEXP.test(":@x")).toBe(false);
3180
+ expect(EMAIL_REGEXP.test(";@x")).toBe(false);
3181
+ expect(EMAIL_REGEXP.test("<@x")).toBe(false);
3182
+ expect(EMAIL_REGEXP.test(">@x")).toBe(false);
3183
+ expect(EMAIL_REGEXP.test("@@x")).toBe(false);
3184
+ expect(EMAIL_REGEXP.test("[@x")).toBe(false);
3185
+ expect(EMAIL_REGEXP.test("\\@x")).toBe(false);
3186
+ expect(EMAIL_REGEXP.test("]@x")).toBe(false);
3187
+ expect(EMAIL_REGEXP.test("İ@x")).toBe(false);
3188
+ expect(EMAIL_REGEXP.test("ı@x")).toBe(false);
3189
+ /* local-part size limit */
3190
+ expect(
3191
+ EMAIL_REGEXP.test(
3192
+ "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx@x",
3193
+ ),
3194
+ ).toBe(true);
3195
+ expect(
3196
+ EMAIL_REGEXP.test(
3197
+ "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx@x",
3198
+ ),
3199
+ ).toBe(false);
3200
+ /* content (local-part + ‘@’ + domain) is required */
3201
+ expect(EMAIL_REGEXP.test("")).toBe(false);
3202
+ expect(EMAIL_REGEXP.test("a")).toBe(false);
3203
+ expect(EMAIL_REGEXP.test("aa")).toBe(false);
3204
+ });
3205
+ });
3206
+ });
3207
+
3208
+ describe("url", () => {
3209
+ it("should validate url", () => {
3210
+ const formElm = $compile(
3211
+ '<form name="form">' +
3212
+ '<input type="url" ng-model="url" name="alias" />',
3213
+ +"</form>",
3214
+ )(scope);
3215
+ const inputElm = formElm.find("input");
3216
+
3217
+ const widget = scope.form.alias;
3218
+
3219
+ inputElm[0].value = "http://www.something.com";
3220
+ inputElm[0].dispatchEvent(new Event("change"));
3221
+ expect(scope.url).toBe("http://www.something.com");
3222
+ expect(inputElm[0].classList.contains("ng-valid")).toBeTrue();
3223
+ expect(widget.$error.url).toBeFalsy();
3224
+
3225
+ inputElm[0].value = "invalid.com";
3226
+ inputElm[0].dispatchEvent(new Event("change"));
3227
+ expect(scope.url).toBeUndefined();
3228
+ expect(inputElm[0].classList.contains("ng-invalid")).toBeTrue();
3229
+ expect(widget.$error.url).toBeTruthy();
3230
+ });
3231
+
3232
+ describe("URL_REGEXP", () => {
3233
+ // See valid URLs in RFC3987 (http://tools.ietf.org/html/rfc3987)
3234
+ // Note: We are being more lenient, because browsers are too.
3235
+ const urls = [
3236
+ ["scheme://hostname", true],
3237
+ [
3238
+ "scheme://username:password@host.name:7678/pa/t.h?q=u&e=r&y#fragment",
3239
+ true,
3240
+ ],
3241
+
3242
+ // Validating `scheme`
3243
+ ["://example.com", false],
3244
+ ["0scheme://example.com", false],
3245
+ [".scheme://example.com", false],
3246
+ ["+scheme://example.com", false],
3247
+ ["-scheme://example.com", false],
3248
+ ["_scheme://example.com", false],
3249
+ ["scheme0://example.com", true],
3250
+ ["scheme.://example.com", true],
3251
+ ["scheme+://example.com", true],
3252
+ ["scheme-://example.com", true],
3253
+ ["scheme_://example.com", false],
3254
+
3255
+ // Validating `:` and `/` after `scheme`
3256
+ ["scheme//example.com", false],
3257
+ ["scheme:example.com", true],
3258
+ ["scheme:/example.com", true],
3259
+ ["scheme:///example.com", true],
3260
+
3261
+ // Validating `username` and `password`
3262
+ ["scheme://@example.com", true],
3263
+ ["scheme://username@example.com", true],
3264
+ ["scheme://u0s.e+r-n_a~m!e@example.com", true],
3265
+ ["scheme://u#s$e%r^n&a*m;e@example.com", true],
3266
+ ["scheme://:password@example.com", true],
3267
+ ["scheme://username:password@example.com", true],
3268
+ ["scheme://username:pass:word@example.com", true],
3269
+ ["scheme://username:p0a.s+s-w_o~r!d@example.com", true],
3270
+ ["scheme://username:p#a$s%s^w&o*r;d@example.com", true],
3271
+
3272
+ // Validating `hostname`
3273
+ ["scheme:", false], // Chrome, FF: true
3274
+ ["scheme://", false], // Chrome, FF: true
3275
+ ["scheme:// example.com:", false], // Chrome, FF: true
3276
+ ["scheme://example com:", false], // Chrome, FF: true
3277
+ ["scheme://:", false], // Chrome, FF: true
3278
+ ["scheme://?", false], // Chrome, FF: true
3279
+ ["scheme://#", false], // Chrome, FF: true
3280
+ ["scheme://username:password@:", false], // Chrome, FF: true
3281
+ ["scheme://username:password@/", false], // Chrome, FF: true
3282
+ ["scheme://username:password@?", false], // Chrome, FF: true
3283
+ ["scheme://username:password@#", false], // Chrome, FF: true
3284
+ ["scheme://host.name", true],
3285
+ ["scheme://123.456.789.10", true],
3286
+ ["scheme://[1234:0000:0000:5678:9abc:0000:0000:def]", true],
3287
+ ["scheme://[1234:0000:0000:5678:9abc:0000:0000:def]:7678", true],
3288
+ ["scheme://[1234:0:0:5678:9abc:0:0:def]", true],
3289
+ ["scheme://[1234::5678:9abc::def]", true],
3290
+ ["scheme://~`!@$%^&*-_=+|\\;'\",.()[]{}<>", true],
3291
+
3292
+ // Validating `port`
3293
+ ["scheme://example.com/no-port", true],
3294
+ ["scheme://example.com:7678", true],
3295
+ ["scheme://example.com:76T8", false], // Chrome, FF: true
3296
+ ["scheme://example.com:port", false], // Chrome, FF: true
3297
+
3298
+ // Validating `path`
3299
+ ["scheme://example.com/", true],
3300
+ ["scheme://example.com/path", true],
3301
+ ["scheme://example.com/path/~`!@$%^&*-_=+|\\;:'\",./()[]{}<>", true],
3302
+
3303
+ // Validating `query`
3304
+ ["scheme://example.com?query", true],
3305
+ ["scheme://example.com/?query", true],
3306
+ ["scheme://example.com/path?query", true],
3307
+ ["scheme://example.com/path?~`!@$%^&*-_=+|\\;:'\",.?/()[]{}<>", true],
3308
+
3309
+ // Validating `fragment`
3310
+ ["scheme://example.com#fragment", true],
3311
+ ["scheme://example.com/#fragment", true],
3312
+ ["scheme://example.com/path#fragment", true],
3313
+ ["scheme://example.com/path/#fragment", true],
3314
+ ["scheme://example.com/path?query#fragment", true],
3315
+ [
3316
+ "scheme://example.com/path?query#~`!@#$%^&*-_=+|\\;:'\",.?/()[]{}<>",
3317
+ true,
3318
+ ],
3319
+
3320
+ // Validating miscellaneous
3321
+ ["scheme://☺.✪.⌘.➡/䨹", true],
3322
+ ["scheme://مثال.إختبار", true],
3323
+ ["scheme://例子.测试", true],
3324
+ ["scheme://उदाहरण.परीक्षा", true],
3325
+
3326
+ // Legacy tests
3327
+ ["http://server:123/path", true],
3328
+ ["https://server:123/path", true],
3329
+ ["file:///home/user", true],
3330
+ ["mailto:user@example.com?subject=Foo", true],
3331
+ ["r2-d2.c3-p0://localhost/foo", true],
3332
+ ["abc:/foo", true],
3333
+ ["http://example.com/path;path", true],
3334
+ ["http://example.com/[]$'()*,~)", true],
3335
+ ["http:", false], // FF: true
3336
+ ["a@B.c", false],
3337
+ ["a_B.c", false],
3338
+ ["0scheme://example.com", false],
3339
+ ["http://example.com:9999/``", true],
3340
+ ].forEach((item) => {
3341
+ it("should validate url: $prop", () => {
3342
+ const url = item[0];
3343
+ const valid = item[1];
3344
+
3345
+ /* global URL_REGEXP: false */
3346
+ expect(URL_REGEXP.test(url)).toBe(valid);
3347
+ });
3348
+ });
3349
+ });
3350
+ });
3351
+
3352
+ describe("radio", () => {
3353
+ ["click", "change"].forEach((event) => {
3354
+ it("should update the model on $prop event", () => {
3355
+ const inputElm = $compile(
3356
+ '<input type="radio" ng-model="color" value="white" />' +
3357
+ '<input type="radio" ng-model="color" value="red" />' +
3358
+ '<input type="radio" ng-model="color" value="blue" />',
3359
+ )(scope);
3360
+
3361
+ scope.$apply("color = 'white'");
3362
+ expect(inputElm[0].checked).toBe(true);
3363
+ expect(inputElm[1].checked).toBe(false);
3364
+ expect(inputElm[2].checked).toBe(false);
3365
+
3366
+ scope.$apply("color = 'red'");
3367
+ expect(inputElm[0].checked).toBe(false);
3368
+ expect(inputElm[1].checked).toBe(true);
3369
+ expect(inputElm[2].checked).toBe(false);
3370
+
3371
+ if (event === "change") inputElm[2].checked = true;
3372
+ if (event === "click") inputElm[2].click();
3373
+ inputElm[2].dispatchEvent(new Event("change"));
3374
+ expect(scope.color).toBe("blue");
3375
+ });
3376
+ });
3377
+
3378
+ it("should treat the value as a string when evaluating checked-ness", () => {
3379
+ const inputElm = $compile(
3380
+ '<input type="radio" ng-model="model" value="0" />',
3381
+ )(scope);
3382
+
3383
+ scope.$apply("model = '0'");
3384
+ expect(inputElm[0].checked).toBe(true);
3385
+
3386
+ scope.$apply("model = 0");
3387
+ expect(inputElm[0].checked).toBe(false);
3388
+ });
3389
+
3390
+ it("should allow {{expr}} as value", () => {
3391
+ scope.some = 11;
3392
+ const inputElm = $compile(
3393
+ '<input type="radio" ng-model="value" value="{{some}}" />' +
3394
+ '<input type="radio" ng-model="value" value="{{other}}" />',
3395
+ )(scope);
3396
+
3397
+ scope.$apply(() => {
3398
+ scope.value = "blue";
3399
+ scope.some = "blue";
3400
+ scope.other = "red";
3401
+ });
3402
+
3403
+ expect(inputElm[0].checked).toBe(true);
3404
+ expect(inputElm[1].checked).toBe(false);
3405
+
3406
+ inputElm[1].click();
3407
+ inputElm[1].dispatchEvent(new Event("change"));
3408
+ expect(scope.value).toBe("red");
3409
+
3410
+ scope.$apply("other = 'non-red'");
3411
+
3412
+ expect(inputElm[0].checked).toBe(false);
3413
+ expect(inputElm[1].checked).toBe(false);
3414
+ });
3415
+
3416
+ it("should allow the use of ngTrim", () => {
3417
+ scope.some = 11;
3418
+ const inputElm = $compile(
3419
+ '<input type="radio" ng-model="value" value="opt1" />' +
3420
+ '<input type="radio" ng-model="value" value=" opt2 " />' +
3421
+ '<input type="radio" ng-model="value" ng-trim="false" value=" opt3 " />' +
3422
+ '<input type="radio" ng-model="value" ng-trim="false" value="{{some}}" />' +
3423
+ '<input type="radio" ng-model="value" ng-trim="false" value=" {{some}} " />',
3424
+ )(scope);
3425
+
3426
+ scope.$apply(() => {
3427
+ scope.value = "blue";
3428
+ scope.some = "blue";
3429
+ });
3430
+
3431
+ expect(inputElm[0].checked).toBe(false);
3432
+ expect(inputElm[1].checked).toBe(false);
3433
+ expect(inputElm[2].checked).toBe(false);
3434
+ expect(inputElm[3].checked).toBe(true);
3435
+ expect(inputElm[4].checked).toBe(false);
3436
+
3437
+ inputElm[1].click();
3438
+ inputElm[1].dispatchEvent(new Event("change"));
3439
+ expect(scope.value).toBe("opt2");
3440
+ inputElm[2].click();
3441
+ inputElm[2].dispatchEvent(new Event("change"));
3442
+ expect(scope.value).toBe(" opt3 ");
3443
+ inputElm[3].click();
3444
+ inputElm[3].dispatchEvent(new Event("change"));
3445
+ expect(scope.value).toBe("blue");
3446
+ inputElm[4].click();
3447
+ inputElm[4].dispatchEvent(new Event("change"));
3448
+ expect(scope.value).toBe(" blue ");
3449
+
3450
+ scope.$apply("value = ' opt2 '");
3451
+ expect(inputElm[1].checked).toBe(false);
3452
+ scope.$apply("value = 'opt2'");
3453
+ expect(inputElm[1].checked).toBe(true);
3454
+ scope.$apply("value = ' opt3 '");
3455
+ expect(inputElm[2].checked).toBe(true);
3456
+ scope.$apply("value = 'opt3'");
3457
+ expect(inputElm[2].checked).toBe(false);
3458
+
3459
+ scope.$apply("value = 'blue'");
3460
+ expect(inputElm[3].checked).toBe(true);
3461
+ expect(inputElm[4].checked).toBe(false);
3462
+ scope.$apply("value = ' blue '");
3463
+ expect(inputElm[3].checked).toBe(false);
3464
+ expect(inputElm[4].checked).toBe(true);
3465
+ });
3466
+ });
3467
+
3468
+ describe("checkbox", () => {
3469
+ it("should ignore checkbox without ngModel directive", () => {
3470
+ const inputElm = $compile(
3471
+ '<input type="checkbox" name="whatever" required />',
3472
+ )(scope);
3473
+
3474
+ inputElm[0].value = "";
3475
+ inputElm[0].dispatchEvent(new Event("change"));
3476
+ expect(inputElm[0].classList.contains("ng-valid")).toBe(false);
3477
+ expect(inputElm[0].classList.contains("ng-invalid")).toBe(false);
3478
+ expect(inputElm[0].classList.contains("ng-pristine")).toBe(false);
3479
+ expect(inputElm[0].classList.contains("ng-dirty")).toBe(false);
3480
+ });
3481
+
3482
+ ["click", "change"].forEach((event) => {
3483
+ it("should update the model on $prop event", () => {
3484
+ const inputElm = $compile(
3485
+ '<input type="checkbox" ng-model="checkbox" />',
3486
+ )(scope);
3487
+
3488
+ expect(inputElm[0].checked).toBe(false);
3489
+
3490
+ scope.$apply("checkbox = true");
3491
+ expect(inputElm[0].checked).toBe(true);
3492
+
3493
+ scope.$apply("checkbox = false");
3494
+ expect(inputElm[0].checked).toBe(false);
3495
+
3496
+ if (event === "change") inputElm[0].checked = true;
3497
+ if (event === "click") inputElm[0].click();
3498
+ inputElm[0].dispatchEvent(new Event("change"));
3499
+ expect(scope.checkbox).toBe(true);
3500
+ });
3501
+ });
3502
+
3503
+ it("should format booleans", () => {
3504
+ const inputElm = $compile('<input type="checkbox" ng-model="name" />')(
3505
+ scope,
3506
+ );
3507
+
3508
+ scope.$apply("name = false");
3509
+ expect(inputElm[0].checked).toBe(false);
3510
+
3511
+ scope.$apply("name = true");
3512
+ expect(inputElm[0].checked).toBe(true);
3513
+ });
3514
+
3515
+ it('should support type="checkbox" with non-standard capitalization', () => {
3516
+ const inputElm = $compile(
3517
+ '<input type="checkBox" ng-model="checkbox" />',
3518
+ )(scope);
3519
+
3520
+ inputElm[0].click();
3521
+ inputElm[0].dispatchEvent(new Event("change"));
3522
+ expect(scope.checkbox).toBe(true);
3523
+
3524
+ inputElm[0].click();
3525
+ inputElm[0].dispatchEvent(new Event("change"));
3526
+ expect(scope.checkbox).toBe(false);
3527
+ });
3528
+
3529
+ it("should allow custom enumeration", () => {
3530
+ const inputElm = $compile(
3531
+ '<input type="checkbox" ng-model="name" ng-true-value="\'y\'" ' +
3532
+ "ng-false-value=\"'n'\">",
3533
+ )(scope);
3534
+
3535
+ scope.$apply("name = 'y'");
3536
+ expect(inputElm[0].checked).toBe(true);
3537
+
3538
+ scope.$apply("name = 'n'");
3539
+ expect(inputElm[0].checked).toBe(false);
3540
+
3541
+ scope.$apply("name = 'something else'");
3542
+ expect(inputElm[0].checked).toBe(false);
3543
+
3544
+ inputElm[0].click();
3545
+ inputElm[0].dispatchEvent(new Event("change"));
3546
+ expect(scope.name).toEqual("y");
3547
+
3548
+ inputElm[0].click();
3549
+ inputElm[0].dispatchEvent(new Event("change"));
3550
+ expect(scope.name).toEqual("n");
3551
+ });
3552
+
3553
+ it("should throw if ngTrueValue is present and not a constant expression", () => {
3554
+ expect(() => {
3555
+ const inputElm = $compile(
3556
+ '<input type="checkbox" ng-model="value" ng-true-value="yes" />',
3557
+ )(scope);
3558
+ }).toThrowError(/constexpr/);
3559
+ });
3560
+
3561
+ it("should throw if ngFalseValue is present and not a constant expression", () => {
3562
+ expect(() => {
3563
+ const inputElm = $compile(
3564
+ '<input type="checkbox" ng-model="value" ng-false-value="no" />',
3565
+ )(scope);
3566
+ }).toThrowError(/constexpr/);
3567
+ });
3568
+
3569
+ it("should not throw if ngTrueValue or ngFalseValue are not present", () => {
3570
+ expect(() => {
3571
+ const inputElm = $compile(
3572
+ '<input type="checkbox" ng-model="value" />',
3573
+ )(scope);
3574
+ }).not.toThrow();
3575
+ });
3576
+
3577
+ it("should be required if false", () => {
3578
+ const inputElm = $compile(
3579
+ '<input type="checkbox" ng-model="value" required />',
3580
+ )(scope);
3581
+
3582
+ inputElm[0].click();
3583
+ inputElm[0].dispatchEvent(new Event("change"));
3584
+ expect(inputElm[0].checked).toBe(true);
3585
+ expect(inputElm[0].classList.contains("ng-valid")).toBeTrue();
3586
+
3587
+ inputElm[0].click();
3588
+ inputElm[0].dispatchEvent(new Event("change"));
3589
+ expect(inputElm[0].checked).toBe(false);
3590
+ expect(inputElm[0].classList.contains("ng-invalid")).toBeTrue();
3591
+ });
3592
+
3593
+ it('should pass validation for "required" when trueValue is a string', () => {
3594
+ const formElm = $compile(
3595
+ '<form name="form">' +
3596
+ '<input type="checkbox" required name="cb" ng-model="value" ng-true-value="\'yes\'" />' +
3597
+ "</form>",
3598
+ )(scope);
3599
+ scope.$digest();
3600
+
3601
+ const inputElm = formElm.find("input");
3602
+
3603
+ expect(inputElm[0].classList.contains("ng-invalid")).toBeTrue();
3604
+ expect(scope.form.cb.$error.required).toBe(true);
3605
+
3606
+ inputElm[0].click();
3607
+ inputElm[0].dispatchEvent(new Event("change"));
3608
+ expect(inputElm[0].checked).toBe(true);
3609
+ expect(inputElm[0].classList.contains("ng-valid")).toBeTrue();
3610
+ expect(scope.form.cb.$error.required).toBeUndefined();
3611
+ });
3612
+ });
3613
+
3614
+ describe("textarea", () => {
3615
+ it("should process textarea", () => {
3616
+ const inputElm = $compile('<textarea ng-model="name"></textarea>')(
3617
+ scope,
3618
+ );
3619
+
3620
+ scope.$apply("name = 'Adam'");
3621
+ expect(inputElm.val()).toEqual("Adam");
3622
+
3623
+ inputElm[0].value = "Shyam";
3624
+ inputElm[0].dispatchEvent(new Event("change"));
3625
+ expect(scope.name).toEqual("Shyam");
3626
+
3627
+ inputElm[0].value = "Kai";
3628
+ inputElm[0].dispatchEvent(new Event("change"));
3629
+ expect(scope.name).toEqual("Kai");
3630
+ });
3631
+
3632
+ it("should ignore textarea without ngModel directive", () => {
3633
+ const inputElm = $compile(
3634
+ '<textarea name="whatever" required></textarea>',
3635
+ )(scope);
3636
+
3637
+ inputElm[0].value = "";
3638
+ inputElm[0].dispatchEvent(new Event("change"));
3639
+ expect(inputElm[0].classList.contains("ng-valid")).toBe(false);
3640
+ expect(inputElm[0].classList.contains("ng-invalid")).toBe(false);
3641
+ expect(inputElm[0].classList.contains("ng-pristine")).toBe(false);
3642
+ expect(inputElm[0].classList.contains("ng-dirty")).toBe(false);
3643
+ });
3644
+ });
3645
+
3646
+ describe("ngValue", () => {
3647
+ it('should update the dom "value" property and attribute', () => {
3648
+ const inputElm = $compile('<input type="submit" ng-value="value">')(
3649
+ scope,
3650
+ );
3651
+
3652
+ scope.$apply("value = 'something'");
3653
+
3654
+ expect(inputElm[0].value).toBe("something");
3655
+ expect(inputElm[0].getAttribute("value")).toBe("something");
3656
+ });
3657
+
3658
+ it('should clear the "dom" value property and attribute when the value is undefined', () => {
3659
+ const inputElm = $compile('<input type="text" ng-value="value">')(
3660
+ scope,
3661
+ );
3662
+
3663
+ scope.$apply('value = "something"');
3664
+
3665
+ expect(inputElm[0].value).toBe("something");
3666
+ expect(inputElm[0].getAttribute("value")).toBe("something");
3667
+
3668
+ scope.$apply(() => {
3669
+ delete scope.value;
3670
+ });
3671
+
3672
+ expect(inputElm[0].value).toBe("");
3673
+ // Support: IE 9-11, Edge
3674
+ // In IE it is not possible to remove the `value` attribute from an input element.
3675
+ expect(inputElm[0].getAttribute("value")).toBeNull();
3676
+ // } else {
3677
+ // // Support: IE 9-11, Edge
3678
+ // // This will fail if the Edge bug gets fixed
3679
+ // expect(inputElm[0].getAttribute("value")).toBe("something");
3680
+ // }
3681
+ });
3682
+
3683
+ // they(
3684
+ // 'should update the $prop "value" property and attribute after the bound expression changes',
3685
+ // {
3686
+ // input: '<input type="text" ng-value="value">',
3687
+ // textarea: '<textarea ng-value="value"></textarea>',
3688
+ // },
3689
+ // (tmpl) => {
3690
+ // const element = $compile(tmpl)(scope);
3691
+
3692
+ // helper.changeInputValueTo("newValue");
3693
+ // expect(element[0].value).toBe("newValue");
3694
+ // expect(element[0].getAttribute("value")).toBeNull();
3695
+
3696
+ // scope.$apply(() => {
3697
+ // scope.value = "anotherValue";
3698
+ // });
3699
+ // expect(element[0].value).toBe("anotherValue");
3700
+ // expect(element[0].getAttribute("value")).toBe("anotherValue");
3701
+ // },
3702
+ // );
3703
+
3704
+ it("should evaluate and set constant expressions", () => {
3705
+ const inputElm = $compile(
3706
+ '<input type="radio" ng-model="selected" ng-value="true">' +
3707
+ '<input type="radio" ng-model="selected" ng-value="false">' +
3708
+ '<input type="radio" ng-model="selected" ng-value="1">',
3709
+ )(scope);
3710
+
3711
+ inputElm[0].click();
3712
+ inputElm[0].dispatchEvent(new Event("change"));
3713
+ expect(scope.selected).toBe(true);
3714
+
3715
+ inputElm[1].click();
3716
+ inputElm[1].dispatchEvent(new Event("change"));
3717
+ expect(scope.selected).toBe(false);
3718
+
3719
+ inputElm[2].click();
3720
+ inputElm[2].dispatchEvent(new Event("change"));
3721
+ expect(scope.selected).toBe(1);
3722
+ });
3723
+
3724
+ it("should use strict comparison between model and value", () => {
3725
+ scope.selected = false;
3726
+ const inputElm = $compile(
3727
+ '<input type="radio" ng-model="selected" ng-value="false">' +
3728
+ '<input type="radio" ng-model="selected" ng-value="\'\'">' +
3729
+ '<input type="radio" ng-model="selected" ng-value="0">',
3730
+ )(scope);
3731
+ scope.$digest();
3732
+ expect(inputElm[0].checked).toBe(true);
3733
+ expect(inputElm[1].checked).toBe(false);
3734
+ expect(inputElm[2].checked).toBe(false);
3735
+ });
3736
+
3737
+ it("should watch the expression", () => {
3738
+ const inputElm = $compile(
3739
+ '<input type="radio" ng-model="selected" ng-value="value">',
3740
+ )(scope);
3741
+
3742
+ scope.$apply(() => {
3743
+ scope.selected = scope.value = { some: "object" };
3744
+ });
3745
+ expect(inputElm[0].checked).toBe(true);
3746
+
3747
+ scope.$apply(() => {
3748
+ scope.value = { some: "other" };
3749
+ });
3750
+ expect(inputElm[0].checked).toBe(false);
3751
+
3752
+ inputElm[0].click();
3753
+ inputElm[0].dispatchEvent(new Event("change"));
3754
+ expect(scope.selected).toBe(scope.value);
3755
+ });
3756
+
3757
+ it("should work inside ngRepeat", () => {
3758
+ const inputElms = $compile(
3759
+ '<div><input type="radio" ng-repeat="i in items" ng-model="$parent.selected" ng-value="i.id"></div>',
3760
+ )(scope);
3761
+
3762
+ scope.$apply(() => {
3763
+ scope.items = [{ id: 1 }, { id: 2 }];
3764
+ scope.selected = 1;
3765
+ });
3766
+
3767
+ scope.$digest();
3768
+
3769
+ expect(inputElms[0].children[0].checked).toBe(true);
3770
+ expect(inputElms[0].children[1].checked).toBe(false);
3771
+
3772
+ inputElms[0].children[1].click();
3773
+ inputElms[0].children[1].dispatchEvent(new Event("change"));
3774
+
3775
+ expect(scope.selected).toBe(2);
3776
+ });
3777
+ });
3778
+
3779
+ describe("password", () => {
3780
+ // Under no circumstances should input[type=password] trim inputs
3781
+ it("should not trim if ngTrim is unspecified", () => {
3782
+ const inputElm = $compile(
3783
+ '<input type="password" ng-model="password">',
3784
+ )(scope);
3785
+
3786
+ inputElm[0].value = " - - untrimmed - - ";
3787
+ inputElm[0].dispatchEvent(new Event("change"));
3788
+ expect(scope.password.length).toBe(" - - untrimmed - - ".length);
3789
+ });
3790
+
3791
+ it("should not trim if ngTrim !== false", () => {
3792
+ const inputElm = $compile(
3793
+ '<input type="password" ng-model="password" ng-trim="true">',
3794
+ )(scope);
3795
+ inputElm[0].value = " - - untrimmed - - ";
3796
+ inputElm[0].dispatchEvent(new Event("change"));
3797
+ expect(scope.password.length).toBe(" - - untrimmed - - ".length);
3798
+ });
3799
+
3800
+ it("should not trim if ngTrim === false", () => {
3801
+ const inputElm = $compile(
3802
+ '<input type="password" ng-model="password" ng-trim="false">',
3803
+ )(scope);
3804
+ inputElm[0].value = " - - untrimmed - - ";
3805
+ inputElm[0].dispatchEvent(new Event("change"));
3806
+ expect(scope.password.length).toBe(" - - untrimmed - - ".length);
3807
+ });
3808
+ });
3809
+ });
3810
+ });