@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.
- package/.eslintignore +1 -0
- package/.eslintrc.cjs +29 -0
- package/.github/workflows/playwright.yml +27 -0
- package/CHANGELOG.md +17974 -0
- package/CODE_OF_CONDUCT.md +3 -0
- package/CONTRIBUTING.md +246 -0
- package/DEVELOPERS.md +488 -0
- package/LICENSE +22 -0
- package/Makefile +31 -0
- package/README.md +115 -0
- package/RELEASE.md +98 -0
- package/SECURITY.md +16 -0
- package/TRIAGING.md +135 -0
- package/css/angular.css +22 -0
- package/dist/angular-ts.cjs.js +36843 -0
- package/dist/angular-ts.esm.js +36841 -0
- package/dist/angular-ts.umd.js +36848 -0
- package/dist/build/angular-animate.js +4272 -0
- package/dist/build/angular-aria.js +426 -0
- package/dist/build/angular-message-format.js +1072 -0
- package/dist/build/angular-messages.js +829 -0
- package/dist/build/angular-mocks.js +3757 -0
- package/dist/build/angular-parse-ext.js +1275 -0
- package/dist/build/angular-resource.js +911 -0
- package/dist/build/angular-route.js +1266 -0
- package/dist/build/angular-sanitize.js +891 -0
- package/dist/build/angular-touch.js +368 -0
- package/dist/build/angular.js +36600 -0
- package/e2e/unit.spec.ts +15 -0
- package/images/android-chrome-192x192.png +0 -0
- package/images/android-chrome-512x512.png +0 -0
- package/images/apple-touch-icon.png +0 -0
- package/images/favicon-16x16.png +0 -0
- package/images/favicon-32x32.png +0 -0
- package/images/favicon.ico +0 -0
- package/images/site.webmanifest +1 -0
- package/index.html +104 -0
- package/package.json +47 -0
- package/playwright.config.ts +78 -0
- package/public/circle.html +1 -0
- package/public/my_child_directive.html +1 -0
- package/public/my_directive.html +1 -0
- package/public/my_other_directive.html +1 -0
- package/public/test.html +1 -0
- package/rollup.config.js +31 -0
- package/src/animations/animateCache.js +55 -0
- package/src/animations/animateChildrenDirective.js +105 -0
- package/src/animations/animateCss.js +1139 -0
- package/src/animations/animateCssDriver.js +291 -0
- package/src/animations/animateJs.js +367 -0
- package/src/animations/animateJsDriver.js +67 -0
- package/src/animations/animateQueue.js +851 -0
- package/src/animations/animation.js +506 -0
- package/src/animations/module.js +779 -0
- package/src/animations/ngAnimateSwap.js +119 -0
- package/src/animations/rafScheduler.js +50 -0
- package/src/animations/shared.js +378 -0
- package/src/constants.js +20 -0
- package/src/core/animate.js +845 -0
- package/src/core/animateCss.js +73 -0
- package/src/core/animateRunner.js +195 -0
- package/src/core/attributes.js +199 -0
- package/src/core/cache.js +45 -0
- package/src/core/compile.js +4727 -0
- package/src/core/controller.js +225 -0
- package/src/core/exceptionHandler.js +63 -0
- package/src/core/filter.js +146 -0
- package/src/core/interpolate.js +442 -0
- package/src/core/interval.js +188 -0
- package/src/core/intervalFactory.js +57 -0
- package/src/core/location.js +1086 -0
- package/src/core/parser/parse.js +2562 -0
- package/src/core/parser/parse.md +13 -0
- package/src/core/q.js +746 -0
- package/src/core/rootScope.js +1596 -0
- package/src/core/sanitizeUri.js +85 -0
- package/src/core/sce.js +1161 -0
- package/src/core/taskTrackerFactory.js +125 -0
- package/src/core/timeout.js +121 -0
- package/src/core/urlUtils.js +187 -0
- package/src/core/utils.js +1349 -0
- package/src/directive/a.js +37 -0
- package/src/directive/attrs.js +283 -0
- package/src/directive/bind.js +51 -0
- package/src/directive/bind.md +142 -0
- package/src/directive/change.js +12 -0
- package/src/directive/change.md +25 -0
- package/src/directive/cloak.js +12 -0
- package/src/directive/cloak.md +24 -0
- package/src/directive/events.js +75 -0
- package/src/directive/events.md +166 -0
- package/src/directive/form.js +725 -0
- package/src/directive/init.js +15 -0
- package/src/directive/init.md +41 -0
- package/src/directive/input.js +1783 -0
- package/src/directive/list.js +46 -0
- package/src/directive/list.md +22 -0
- package/src/directive/ngClass.js +249 -0
- package/src/directive/ngController.js +64 -0
- package/src/directive/ngCsp.js +82 -0
- package/src/directive/ngIf.js +134 -0
- package/src/directive/ngInclude.js +217 -0
- package/src/directive/ngModel.js +1356 -0
- package/src/directive/ngModelOptions.js +509 -0
- package/src/directive/ngOptions.js +670 -0
- package/src/directive/ngRef.js +90 -0
- package/src/directive/ngRepeat.js +650 -0
- package/src/directive/ngShowHide.js +255 -0
- package/src/directive/ngSwitch.js +178 -0
- package/src/directive/ngTransclude.js +98 -0
- package/src/directive/non-bindable.js +11 -0
- package/src/directive/non-bindable.md +17 -0
- package/src/directive/script.js +30 -0
- package/src/directive/select.js +624 -0
- package/src/directive/style.js +25 -0
- package/src/directive/style.md +23 -0
- package/src/directive/validators.js +329 -0
- package/src/exts/aria.js +544 -0
- package/src/exts/messages.js +852 -0
- package/src/filters/filter.js +207 -0
- package/src/filters/filter.md +69 -0
- package/src/filters/filters.js +239 -0
- package/src/filters/json.md +16 -0
- package/src/filters/limit-to.js +43 -0
- package/src/filters/limit-to.md +19 -0
- package/src/filters/order-by.js +183 -0
- package/src/filters/order-by.md +83 -0
- package/src/index.js +13 -0
- package/src/injector.js +1034 -0
- package/src/jqLite.js +1117 -0
- package/src/loader.js +1320 -0
- package/src/public.js +215 -0
- package/src/routeToRegExp.js +41 -0
- package/src/services/anchorScroll.js +135 -0
- package/src/services/browser.js +321 -0
- package/src/services/cacheFactory.js +398 -0
- package/src/services/cookieReader.js +72 -0
- package/src/services/document.js +64 -0
- package/src/services/http.js +1537 -0
- package/src/services/httpBackend.js +206 -0
- package/src/services/log.js +160 -0
- package/src/services/templateRequest.js +139 -0
- package/test/angular.spec.js +2153 -0
- package/test/aria/aria.spec.js +1245 -0
- package/test/binding.spec.js +504 -0
- package/test/build-test.html +14 -0
- package/test/injector.spec.js +2327 -0
- package/test/jasmine/jasmine-5.1.2/boot0.js +65 -0
- package/test/jasmine/jasmine-5.1.2/boot1.js +133 -0
- package/test/jasmine/jasmine-5.1.2/jasmine-html.js +963 -0
- package/test/jasmine/jasmine-5.1.2/jasmine.css +320 -0
- package/test/jasmine/jasmine-5.1.2/jasmine.js +10824 -0
- package/test/jasmine/jasmine-5.1.2/jasmine_favicon.png +0 -0
- package/test/jasmine/jasmine-browser.json +17 -0
- package/test/jasmine/jasmine.json +9 -0
- package/test/jqlite.spec.js +2133 -0
- package/test/loader.spec.js +219 -0
- package/test/messages/messages.spec.js +1146 -0
- package/test/min-err.spec.js +174 -0
- package/test/mock-test.html +13 -0
- package/test/module-test.html +15 -0
- package/test/ng/anomate.spec.js +606 -0
- package/test/ng/cache-factor.spec.js +334 -0
- package/test/ng/compile.spec.js +17956 -0
- package/test/ng/controller-provider.spec.js +227 -0
- package/test/ng/cookie-reader.spec.js +98 -0
- package/test/ng/directive/a.spec.js +192 -0
- package/test/ng/directive/bind.spec.js +334 -0
- package/test/ng/directive/boolean.spec.js +136 -0
- package/test/ng/directive/change.spec.js +71 -0
- package/test/ng/directive/class.spec.js +858 -0
- package/test/ng/directive/click.spec.js +38 -0
- package/test/ng/directive/cloak.spec.js +44 -0
- package/test/ng/directive/constoller.spec.js +194 -0
- package/test/ng/directive/element-style.spec.js +92 -0
- package/test/ng/directive/event.spec.js +282 -0
- package/test/ng/directive/form.spec.js +1518 -0
- package/test/ng/directive/href.spec.js +143 -0
- package/test/ng/directive/if.spec.js +402 -0
- package/test/ng/directive/include.spec.js +828 -0
- package/test/ng/directive/init.spec.js +68 -0
- package/test/ng/directive/input.spec.js +3810 -0
- package/test/ng/directive/list.spec.js +170 -0
- package/test/ng/directive/model-options.spec.js +1008 -0
- package/test/ng/directive/model.spec.js +1905 -0
- package/test/ng/directive/non-bindable.spec.js +55 -0
- package/test/ng/directive/options.spec.js +3583 -0
- package/test/ng/directive/ref.spec.js +575 -0
- package/test/ng/directive/repeat.spec.js +1675 -0
- package/test/ng/directive/script.spec.js +52 -0
- package/test/ng/directive/scrset.spec.js +67 -0
- package/test/ng/directive/select.spec.js +2541 -0
- package/test/ng/directive/show-hide.spec.js +253 -0
- package/test/ng/directive/src.spec.js +157 -0
- package/test/ng/directive/style.spec.js +178 -0
- package/test/ng/directive/switch.spec.js +647 -0
- package/test/ng/directive/validators.spec.js +717 -0
- package/test/ng/document.spec.js +52 -0
- package/test/ng/filter/filter.spec.js +714 -0
- package/test/ng/filter/filters.spec.js +35 -0
- package/test/ng/filter/limit-to.spec.js +251 -0
- package/test/ng/filter/order-by.spec.js +891 -0
- package/test/ng/filter.spec.js +149 -0
- package/test/ng/http-backend.spec.js +398 -0
- package/test/ng/http.spec.js +4071 -0
- package/test/ng/interpolate.spec.js +642 -0
- package/test/ng/interval.spec.js +343 -0
- package/test/ng/location.spec.js +3488 -0
- package/test/ng/on.spec.js +229 -0
- package/test/ng/parse.spec.js +4655 -0
- package/test/ng/prop.spec.js +805 -0
- package/test/ng/q.spec.js +2904 -0
- package/test/ng/root-element.spec.js +16 -0
- package/test/ng/sanitize-uri.spec.js +249 -0
- package/test/ng/sce.spec.js +660 -0
- package/test/ng/scope.spec.js +3442 -0
- package/test/ng/template-request.spec.js +236 -0
- package/test/ng/timeout.spec.js +351 -0
- package/test/ng/url-utils.spec.js +156 -0
- package/test/ng/utils.spec.js +144 -0
- package/test/original-test.html +21 -0
- package/test/public.spec.js +34 -0
- package/test/sanitize/bing-html.spec.js +36 -0
- package/test/server/express.js +158 -0
- package/test/test-utils.js +11 -0
- package/tsconfig.json +17 -0
- package/types/angular.d.ts +138 -0
- package/types/global.d.ts +9 -0
- package/types/index.d.ts +2357 -0
- package/types/jqlite.d.ts +558 -0
- package/vite.config.js +14 -0
|
@@ -0,0 +1,1518 @@
|
|
|
1
|
+
import { publishExternalAPI } from "../../../src/public";
|
|
2
|
+
import { createInjector } from "../../../src/injector";
|
|
3
|
+
import { dealoc, jqLite } from "../../../src/jqLite";
|
|
4
|
+
import { FormController } from "../../../src/directive/form";
|
|
5
|
+
import { Angular } from "../../../src/loader";
|
|
6
|
+
|
|
7
|
+
describe("form", () => {
|
|
8
|
+
let doc;
|
|
9
|
+
let control;
|
|
10
|
+
let scope;
|
|
11
|
+
let $compile;
|
|
12
|
+
let injector;
|
|
13
|
+
|
|
14
|
+
beforeEach(() => {
|
|
15
|
+
publishExternalAPI().decorator("$exceptionHandler", function () {
|
|
16
|
+
return (exception, cause) => {
|
|
17
|
+
throw new Error(exception);
|
|
18
|
+
};
|
|
19
|
+
});
|
|
20
|
+
injector = createInjector([
|
|
21
|
+
"ng",
|
|
22
|
+
($compileProvider) => {
|
|
23
|
+
$compileProvider.directive("storeModelCtrl", () => ({
|
|
24
|
+
require: "ngModel",
|
|
25
|
+
link(scope, elm, attr, ctrl) {
|
|
26
|
+
control = ctrl;
|
|
27
|
+
},
|
|
28
|
+
}));
|
|
29
|
+
},
|
|
30
|
+
]);
|
|
31
|
+
|
|
32
|
+
injector.invoke((_$compile_, $rootScope) => {
|
|
33
|
+
$compile = _$compile_;
|
|
34
|
+
scope = $rootScope.$new();
|
|
35
|
+
});
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
afterEach(() => {
|
|
39
|
+
dealoc(doc);
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
it("should instantiate form and attach it to DOM", () => {
|
|
43
|
+
doc = $compile("<form>")(scope);
|
|
44
|
+
expect(doc.data("$formController")).toBeTruthy();
|
|
45
|
+
expect(doc.data("$formController") instanceof FormController).toBe(true);
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
it("should remove form control references from the form when nested control is removed from the DOM", () => {
|
|
49
|
+
doc = $compile(
|
|
50
|
+
'<form name="myForm">' +
|
|
51
|
+
'<input ng-if="inputPresent" name="alias" ng-model="value" store-model-ctrl/>' +
|
|
52
|
+
"</form>",
|
|
53
|
+
)(scope);
|
|
54
|
+
scope.inputPresent = true;
|
|
55
|
+
scope.$digest();
|
|
56
|
+
|
|
57
|
+
const form = scope.myForm;
|
|
58
|
+
control.$setValidity("required", false);
|
|
59
|
+
|
|
60
|
+
expect(form.alias).toBe(control);
|
|
61
|
+
expect(form.$error.required).toEqual([control]);
|
|
62
|
+
|
|
63
|
+
// remove nested control
|
|
64
|
+
scope.inputPresent = false;
|
|
65
|
+
scope.$apply();
|
|
66
|
+
|
|
67
|
+
expect(form.$error.required).toBeFalsy();
|
|
68
|
+
expect(form.alias).toBeUndefined();
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
it("should ignore changes in manually removed controls", () => {
|
|
72
|
+
doc = $compile(
|
|
73
|
+
'<form name="myForm">' +
|
|
74
|
+
'<input name="control" ng-maxlength="1" ng-model="value" store-model-ctrl/>' +
|
|
75
|
+
"</form>",
|
|
76
|
+
)(scope);
|
|
77
|
+
|
|
78
|
+
const form = scope.myForm;
|
|
79
|
+
|
|
80
|
+
const input = doc.find("input").eq(0);
|
|
81
|
+
const inputController = input.controller("ngModel");
|
|
82
|
+
|
|
83
|
+
input[0].setAttribute("value", "ab");
|
|
84
|
+
input[0].dispatchEvent(new Event("change"));
|
|
85
|
+
|
|
86
|
+
scope.$apply();
|
|
87
|
+
|
|
88
|
+
expect(form.$error.maxlength).toBeTruthy();
|
|
89
|
+
expect(form.$dirty).toBe(true);
|
|
90
|
+
expect(form.$error.maxlength[0].$name).toBe("control");
|
|
91
|
+
|
|
92
|
+
// remove control
|
|
93
|
+
form.$removeControl(form.control);
|
|
94
|
+
expect(form.control).toBeUndefined();
|
|
95
|
+
expect(form.$error.maxlength).toBeFalsy();
|
|
96
|
+
|
|
97
|
+
inputController.$setPristine();
|
|
98
|
+
expect(form.$dirty).toBe(true);
|
|
99
|
+
|
|
100
|
+
form.$setPristine();
|
|
101
|
+
|
|
102
|
+
input[0].setAttribute("value", "ab");
|
|
103
|
+
input[0].dispatchEvent(new Event("change"));
|
|
104
|
+
scope.$apply();
|
|
105
|
+
|
|
106
|
+
expect(form.$error.maxlength).toBeFalsy();
|
|
107
|
+
expect(form.$dirty).toBe(false);
|
|
108
|
+
});
|
|
109
|
+
|
|
110
|
+
it("should react to validation changes in manually added controls", () => {
|
|
111
|
+
doc = $compile(
|
|
112
|
+
'<form name="myForm">' +
|
|
113
|
+
'<input name="control" ng-maxlength="1" ng-model="value" store-model-ctrl/>' +
|
|
114
|
+
"</form>",
|
|
115
|
+
)(scope);
|
|
116
|
+
|
|
117
|
+
scope.$digest();
|
|
118
|
+
|
|
119
|
+
const form = scope.myForm;
|
|
120
|
+
|
|
121
|
+
const input = doc.find("input").eq(0);
|
|
122
|
+
|
|
123
|
+
// remove control and invalidate it
|
|
124
|
+
form.$removeControl(control);
|
|
125
|
+
expect(form.control).toBeUndefined();
|
|
126
|
+
|
|
127
|
+
input[0].setAttribute("value", "abc");
|
|
128
|
+
input[0].dispatchEvent(new Event("change"));
|
|
129
|
+
expect(control.$error.maxlength).toBe(true);
|
|
130
|
+
expect(control.$dirty).toBe(true);
|
|
131
|
+
expect(form.$error.maxlength).toBeFalsy();
|
|
132
|
+
expect(form.$dirty).toBe(false);
|
|
133
|
+
|
|
134
|
+
// re-add the control; its current validation state is not propagated
|
|
135
|
+
form.$addControl(control);
|
|
136
|
+
expect(form.control).toBe(control);
|
|
137
|
+
expect(form.$error.maxlength).toBeFalsy();
|
|
138
|
+
expect(form.$dirty).toBe(false);
|
|
139
|
+
|
|
140
|
+
// Only when the input changes again its validation state is propagated
|
|
141
|
+
input[0].setAttribute("value", "abcd");
|
|
142
|
+
input[0].dispatchEvent(new Event("change"));
|
|
143
|
+
expect(form.$error.maxlength[0]).toBe(control);
|
|
144
|
+
expect(form.$dirty).toBe(false);
|
|
145
|
+
});
|
|
146
|
+
|
|
147
|
+
it("should use the correct parent when renaming and removing dynamically added controls", () => {
|
|
148
|
+
scope.controlName = "childControl";
|
|
149
|
+
scope.hasChildControl = true;
|
|
150
|
+
|
|
151
|
+
doc = $compile(
|
|
152
|
+
'<form name="myForm">' +
|
|
153
|
+
'<div ng-if="hasChildControl">' +
|
|
154
|
+
'<input name="{{controlName}}" ng-maxlength="1" ng-model="value"/>' +
|
|
155
|
+
"</div>" +
|
|
156
|
+
"</form>" +
|
|
157
|
+
'<form name="otherForm"></form>',
|
|
158
|
+
)(scope);
|
|
159
|
+
|
|
160
|
+
scope.$digest();
|
|
161
|
+
|
|
162
|
+
const form = scope.myForm;
|
|
163
|
+
const { otherForm } = scope;
|
|
164
|
+
const { childControl } = form;
|
|
165
|
+
|
|
166
|
+
// remove child form and add it to another form
|
|
167
|
+
form.$removeControl(childControl);
|
|
168
|
+
otherForm.$addControl(childControl);
|
|
169
|
+
|
|
170
|
+
expect(form.childControl).toBeUndefined();
|
|
171
|
+
expect(otherForm.childControl).toBe(childControl);
|
|
172
|
+
|
|
173
|
+
// rename the childControl
|
|
174
|
+
scope.controlName = "childControlMoved";
|
|
175
|
+
scope.$digest();
|
|
176
|
+
|
|
177
|
+
expect(form.childControlMoved).toBeUndefined();
|
|
178
|
+
expect(otherForm.childControl).toBeUndefined();
|
|
179
|
+
expect(otherForm.childControlMoved).toBe(childControl);
|
|
180
|
+
|
|
181
|
+
scope.hasChildControl = false;
|
|
182
|
+
scope.$digest();
|
|
183
|
+
|
|
184
|
+
expect(form.childControlMoved).toBeUndefined();
|
|
185
|
+
expect(otherForm.childControlMoved).toBeUndefined();
|
|
186
|
+
});
|
|
187
|
+
|
|
188
|
+
it("should remove scope reference when form with no parent form is removed from the DOM", () => {
|
|
189
|
+
let formController;
|
|
190
|
+
scope.ctrl = {};
|
|
191
|
+
doc = $compile(
|
|
192
|
+
'<div><form name="ctrl.myForm" ng-if="formPresent">' +
|
|
193
|
+
'<input name="alias" ng-model="value" />' +
|
|
194
|
+
"</form></div>",
|
|
195
|
+
)(scope);
|
|
196
|
+
|
|
197
|
+
scope.$digest();
|
|
198
|
+
expect(scope.ctrl.myForm).toBeUndefined();
|
|
199
|
+
|
|
200
|
+
scope.$apply("formPresent = true");
|
|
201
|
+
expect(scope.ctrl.myForm).toBeDefined();
|
|
202
|
+
|
|
203
|
+
formController = doc.find("form").controller("form");
|
|
204
|
+
expect(scope.ctrl.myForm == formController).toBeTrue();
|
|
205
|
+
|
|
206
|
+
scope.$apply("formPresent = false");
|
|
207
|
+
expect(doc[0].innerText).toBe("");
|
|
208
|
+
});
|
|
209
|
+
|
|
210
|
+
it("should use ngForm value as form name", () => {
|
|
211
|
+
doc = $compile(
|
|
212
|
+
'<div ng-form="myForm">' +
|
|
213
|
+
'<input type="text" name="alias" ng-model="value"/>' +
|
|
214
|
+
"</div>",
|
|
215
|
+
)(scope);
|
|
216
|
+
|
|
217
|
+
expect(scope.myForm).toBeDefined();
|
|
218
|
+
expect(scope.myForm.alias).toBeDefined();
|
|
219
|
+
});
|
|
220
|
+
|
|
221
|
+
it("should use ngForm value as form name when nested inside form", () => {
|
|
222
|
+
doc = $compile(
|
|
223
|
+
'<form name="myForm">' +
|
|
224
|
+
'<div ng-form="nestedForm"><input type="text" name="alias" ng-model="value"/></div>' +
|
|
225
|
+
"</form>",
|
|
226
|
+
)(scope);
|
|
227
|
+
|
|
228
|
+
expect(scope.myForm).toBeDefined();
|
|
229
|
+
expect(scope.myForm.nestedForm).toBeDefined();
|
|
230
|
+
expect(scope.myForm.nestedForm.alias).toBeDefined();
|
|
231
|
+
});
|
|
232
|
+
|
|
233
|
+
it("should publish form to scope when name attr is defined", () => {
|
|
234
|
+
doc = $compile('<form name="myForm"></form>')(scope);
|
|
235
|
+
expect(scope.myForm).toBeTruthy();
|
|
236
|
+
expect(doc.data("$formController")).toBeTruthy();
|
|
237
|
+
expect(doc.data("$formController")).toEqual(scope.myForm);
|
|
238
|
+
});
|
|
239
|
+
|
|
240
|
+
it("should support expression in form name", () => {
|
|
241
|
+
doc = $compile('<form name="obj.myForm"></form>')(scope);
|
|
242
|
+
|
|
243
|
+
expect(scope.obj).toBeDefined();
|
|
244
|
+
expect(scope.obj.myForm).toBeTruthy();
|
|
245
|
+
});
|
|
246
|
+
|
|
247
|
+
it("should support two forms on a single scope", () => {
|
|
248
|
+
doc = $compile(`
|
|
249
|
+
<div>
|
|
250
|
+
<form name="formA">
|
|
251
|
+
<input name="firstName" ng-model="firstName" required>
|
|
252
|
+
</form>
|
|
253
|
+
<form name="formB">
|
|
254
|
+
<input name="lastName" ng-model="lastName" required>
|
|
255
|
+
</form>
|
|
256
|
+
</div>
|
|
257
|
+
`)(scope);
|
|
258
|
+
scope.$digest();
|
|
259
|
+
expect(scope.formA.$error.required.length).toBe(1);
|
|
260
|
+
expect(scope.formA.$error.required).toEqual([scope.formA.firstName]);
|
|
261
|
+
expect(scope.formB.$error.required.length).toBe(1);
|
|
262
|
+
expect(scope.formB.$error.required).toEqual([scope.formB.lastName]);
|
|
263
|
+
|
|
264
|
+
const inputA = doc.find("input").eq(0);
|
|
265
|
+
const inputB = doc.find("input").eq(1);
|
|
266
|
+
|
|
267
|
+
inputA[0].setAttribute("value", "val1");
|
|
268
|
+
inputA[0].dispatchEvent(new Event("change"));
|
|
269
|
+
inputB[0].setAttribute("value", "val2");
|
|
270
|
+
inputB[0].dispatchEvent(new Event("change"));
|
|
271
|
+
|
|
272
|
+
expect(scope.firstName).toBe("val1");
|
|
273
|
+
expect(scope.lastName).toBe("val2");
|
|
274
|
+
|
|
275
|
+
expect(scope.formA.$error.required).toBeFalsy();
|
|
276
|
+
expect(scope.formB.$error.required).toBeFalsy();
|
|
277
|
+
});
|
|
278
|
+
|
|
279
|
+
it("should publish widgets", () => {
|
|
280
|
+
doc = jqLite(
|
|
281
|
+
'<form name="form"><input type="text" name="w1" ng-model="some" /></form>',
|
|
282
|
+
);
|
|
283
|
+
$compile(doc)(scope);
|
|
284
|
+
|
|
285
|
+
const widget = scope.form.w1;
|
|
286
|
+
expect(widget).toBeDefined();
|
|
287
|
+
expect(widget.$pristine).toBe(true);
|
|
288
|
+
expect(widget.$dirty).toBe(false);
|
|
289
|
+
expect(widget.$valid).toBe(true);
|
|
290
|
+
expect(widget.$invalid).toBe(false);
|
|
291
|
+
});
|
|
292
|
+
|
|
293
|
+
it('should throw an exception if an input has name="hasOwnProperty"', () => {
|
|
294
|
+
doc = jqLite(
|
|
295
|
+
'<form name="form">' +
|
|
296
|
+
'<input name="hasOwnProperty" ng-model="some" />' +
|
|
297
|
+
'<input name="other" ng-model="someOther" />' +
|
|
298
|
+
"</form>",
|
|
299
|
+
);
|
|
300
|
+
expect(() => {
|
|
301
|
+
$compile(doc)(scope);
|
|
302
|
+
}).toThrowError();
|
|
303
|
+
});
|
|
304
|
+
|
|
305
|
+
describe("triggering commit value on submit", () => {
|
|
306
|
+
it("should trigger update on form submit", () => {
|
|
307
|
+
const form = $compile(
|
|
308
|
+
'<form name="test" ng-model-options="{ updateOn: \'submit\' }" >' +
|
|
309
|
+
'<input type="text" ng-model="name" />' +
|
|
310
|
+
"</form>",
|
|
311
|
+
)(scope);
|
|
312
|
+
scope.$digest();
|
|
313
|
+
|
|
314
|
+
const inputElm = form.find("input").eq(0);
|
|
315
|
+
|
|
316
|
+
inputElm[0].setAttribute("value", "a");
|
|
317
|
+
inputElm[0].dispatchEvent(new Event("change"));
|
|
318
|
+
expect(scope.name).toEqual(undefined);
|
|
319
|
+
|
|
320
|
+
form[0].dispatchEvent(new Event("submit"));
|
|
321
|
+
expect(scope.name).toEqual("a");
|
|
322
|
+
dealoc(form);
|
|
323
|
+
});
|
|
324
|
+
|
|
325
|
+
it("should trigger update on form submit with nested forms", () => {
|
|
326
|
+
const form = $compile(
|
|
327
|
+
'<form name="test" ng-model-options="{ updateOn: \'submit\' }" >' +
|
|
328
|
+
'<div class="ng-form" name="child">' +
|
|
329
|
+
'<input type="text" ng-model="name" />' +
|
|
330
|
+
"</div>" +
|
|
331
|
+
"</form>",
|
|
332
|
+
)(scope);
|
|
333
|
+
scope.$digest();
|
|
334
|
+
|
|
335
|
+
const inputElm = form.find("input").eq(0);
|
|
336
|
+
inputElm[0].setAttribute("value", "a");
|
|
337
|
+
inputElm[0].dispatchEvent(new Event("change"));
|
|
338
|
+
expect(scope.name).toEqual(undefined);
|
|
339
|
+
// browserTrigger(form, "submit");
|
|
340
|
+
form[0].dispatchEvent(new Event("submit"));
|
|
341
|
+
expect(scope.name).toEqual("a");
|
|
342
|
+
dealoc(form);
|
|
343
|
+
});
|
|
344
|
+
|
|
345
|
+
it("should trigger update before ng-submit is invoked", () => {
|
|
346
|
+
const form = $compile(
|
|
347
|
+
'<form name="test" ng-submit="submit()" ' +
|
|
348
|
+
"ng-model-options=\"{ updateOn: 'submit' }\" >" +
|
|
349
|
+
'<input type="text" ng-model="name" />' +
|
|
350
|
+
"</form>",
|
|
351
|
+
)(scope);
|
|
352
|
+
scope.$digest();
|
|
353
|
+
|
|
354
|
+
const inputElm = form.find("input").eq(0);
|
|
355
|
+
inputElm[0].setAttribute("value", "a");
|
|
356
|
+
inputElm[0].dispatchEvent(new Event("change"));
|
|
357
|
+
scope.submit = jasmine.createSpy("submit").and.callFake(() => {
|
|
358
|
+
expect(scope.name).toEqual("a");
|
|
359
|
+
});
|
|
360
|
+
// browserTrigger(form, "submit");
|
|
361
|
+
form[0].dispatchEvent(new Event("submit"));
|
|
362
|
+
expect(scope.submit).toHaveBeenCalled();
|
|
363
|
+
dealoc(form);
|
|
364
|
+
});
|
|
365
|
+
});
|
|
366
|
+
|
|
367
|
+
describe("rollback view value", () => {
|
|
368
|
+
it("should trigger rollback on form controls", () => {
|
|
369
|
+
const form = $compile(
|
|
370
|
+
'<form name="test" ng-model-options="{ updateOn: \'submit\' }" >' +
|
|
371
|
+
'<input type="text" ng-model="name" />' +
|
|
372
|
+
'<button ng-click="test.$rollbackViewValue()" />' +
|
|
373
|
+
"</form>",
|
|
374
|
+
)(scope);
|
|
375
|
+
scope.$digest();
|
|
376
|
+
|
|
377
|
+
const inputElm = form.find("input").eq(0);
|
|
378
|
+
inputElm[0].setAttribute("value", "a");
|
|
379
|
+
inputElm[0].dispatchEvent(new Event("click"));
|
|
380
|
+
expect(inputElm.val()).toBe("a");
|
|
381
|
+
// browserTrigger(form.find("button"), "click");
|
|
382
|
+
form.find("button")[0].click();
|
|
383
|
+
expect(inputElm.val()).toBe("");
|
|
384
|
+
dealoc(form);
|
|
385
|
+
});
|
|
386
|
+
|
|
387
|
+
it("should trigger rollback on form controls with nested forms", () => {
|
|
388
|
+
const form = $compile(
|
|
389
|
+
'<form name="test" ng-model-options="{ updateOn: \'submit\' }" >' +
|
|
390
|
+
'<div class="ng-form" name="child">' +
|
|
391
|
+
'<input type="text" ng-model="name" />' +
|
|
392
|
+
"</div>" +
|
|
393
|
+
'<button ng-click="test.$rollbackViewValue()" />' +
|
|
394
|
+
"</form>",
|
|
395
|
+
)(scope);
|
|
396
|
+
scope.$digest();
|
|
397
|
+
|
|
398
|
+
const inputElm = form.find("input").eq(0);
|
|
399
|
+
inputElm[0].setAttribute("value", "a");
|
|
400
|
+
inputElm[0].dispatchEvent(new Event("click"));
|
|
401
|
+
expect(inputElm.val()).toBe("a");
|
|
402
|
+
form.find("button")[0].click();
|
|
403
|
+
expect(inputElm.val()).toBe("");
|
|
404
|
+
dealoc(form);
|
|
405
|
+
});
|
|
406
|
+
});
|
|
407
|
+
|
|
408
|
+
describe("preventing default submission", () => {
|
|
409
|
+
it("should prevent form submission", (done) => {
|
|
410
|
+
let nextTurn = false;
|
|
411
|
+
let submitted = false;
|
|
412
|
+
let reloadPrevented;
|
|
413
|
+
|
|
414
|
+
doc = jqLite(
|
|
415
|
+
'<form ng-submit="submitMe()">' +
|
|
416
|
+
'<input type="submit" value="submit">' +
|
|
417
|
+
"</form>",
|
|
418
|
+
);
|
|
419
|
+
// Support: Chrome 60+ (on Windows)
|
|
420
|
+
// We need to add the form to the DOM in order for `submit` events to be properly fired.
|
|
421
|
+
window.document.body.appendChild(doc[0]);
|
|
422
|
+
|
|
423
|
+
const assertPreventDefaultListener = function (e) {
|
|
424
|
+
reloadPrevented = e.defaultPrevented || e.returnValue === false;
|
|
425
|
+
};
|
|
426
|
+
|
|
427
|
+
$compile(doc)(scope);
|
|
428
|
+
|
|
429
|
+
scope.submitMe = function () {
|
|
430
|
+
submitted = true;
|
|
431
|
+
};
|
|
432
|
+
|
|
433
|
+
doc[0].addEventListener("submit", assertPreventDefaultListener);
|
|
434
|
+
doc.find("input")[0].click();
|
|
435
|
+
|
|
436
|
+
// let the browser process all events (and potentially reload the page)
|
|
437
|
+
window.setTimeout(() => {
|
|
438
|
+
expect(reloadPrevented).toBe(true);
|
|
439
|
+
expect(submitted).toBe(true);
|
|
440
|
+
|
|
441
|
+
// prevent mem leak in test
|
|
442
|
+
doc[0].removeEventListener("submit", assertPreventDefaultListener);
|
|
443
|
+
done();
|
|
444
|
+
}, 100);
|
|
445
|
+
});
|
|
446
|
+
|
|
447
|
+
it("should prevent the default when the form is destroyed by a submission via a click event", (done) => {
|
|
448
|
+
doc = jqLite(
|
|
449
|
+
"<div>" +
|
|
450
|
+
'<form ng-submit="submitMe()">' +
|
|
451
|
+
'<button type="submit" ng-click="destroy()"></button>' +
|
|
452
|
+
"</form>" +
|
|
453
|
+
"</div>",
|
|
454
|
+
);
|
|
455
|
+
|
|
456
|
+
const form = doc.find("form");
|
|
457
|
+
let destroyed = false;
|
|
458
|
+
let nextTurn = false;
|
|
459
|
+
let submitted = false;
|
|
460
|
+
let reloadPrevented = "never called";
|
|
461
|
+
|
|
462
|
+
scope.destroy = function () {
|
|
463
|
+
// yes, I know, scope methods should not do direct DOM manipulation, but I wanted to keep
|
|
464
|
+
// this test small. Imagine that the destroy action will cause a model change (e.g.
|
|
465
|
+
// $location change) that will cause some directive to destroy the dom (e.g. ngView+$route)
|
|
466
|
+
doc.empty();
|
|
467
|
+
destroyed = true;
|
|
468
|
+
};
|
|
469
|
+
|
|
470
|
+
scope.submitMe = function () {
|
|
471
|
+
submitted = true;
|
|
472
|
+
};
|
|
473
|
+
|
|
474
|
+
const assertPreventDefaultListener = function (e) {
|
|
475
|
+
reloadPrevented = e.defaultPrevented || e.returnValue === false;
|
|
476
|
+
};
|
|
477
|
+
|
|
478
|
+
$compile(doc)(scope);
|
|
479
|
+
|
|
480
|
+
form[0].addEventListener("submit", assertPreventDefaultListener);
|
|
481
|
+
|
|
482
|
+
form.find("button")[0].click();
|
|
483
|
+
|
|
484
|
+
// let the browser process all events (and potentially reload the page)
|
|
485
|
+
window.setTimeout(() => {
|
|
486
|
+
nextTurn = true;
|
|
487
|
+
expect(doc.html()).toBe("");
|
|
488
|
+
expect(destroyed).toBe(true);
|
|
489
|
+
expect(submitted).toBe(false);
|
|
490
|
+
// this is known corner-case that is not currently handled
|
|
491
|
+
// the issue is that the submit listener is destroyed before
|
|
492
|
+
// the event propagates there. we can fix this if we see
|
|
493
|
+
// the issue in the wild, I'm not going to bother to do it
|
|
494
|
+
// now. (i)
|
|
495
|
+
|
|
496
|
+
// Support: Chrome 60+ (on Windows)
|
|
497
|
+
// Chrome 60+ on Windows does not fire `submit` events when the form is not attached to
|
|
498
|
+
// the DOM. Verify that the `submit` listener was either never fired or (if fired) the
|
|
499
|
+
// reload was prevented.
|
|
500
|
+
expect(reloadPrevented).not.toBe(false);
|
|
501
|
+
|
|
502
|
+
// prevent mem leak in test
|
|
503
|
+
form[0].removeEventListener("submit", assertPreventDefaultListener);
|
|
504
|
+
done();
|
|
505
|
+
}, 100);
|
|
506
|
+
});
|
|
507
|
+
|
|
508
|
+
it("should NOT prevent form submission if action attribute present", () => {
|
|
509
|
+
const callback = jasmine.createSpy("submit").and.callFake((event) => {
|
|
510
|
+
expect(event.isDefaultPrevented()).toBe(false);
|
|
511
|
+
event.preventDefault();
|
|
512
|
+
});
|
|
513
|
+
|
|
514
|
+
doc = $compile('<form action="some.py"></form>')(scope);
|
|
515
|
+
doc.on("submit", callback);
|
|
516
|
+
|
|
517
|
+
//browserTrigger(doc, "submit");
|
|
518
|
+
doc[0].dispatchEvent(new Event("submit"));
|
|
519
|
+
expect(callback).toHaveBeenCalled();
|
|
520
|
+
});
|
|
521
|
+
});
|
|
522
|
+
|
|
523
|
+
describe("nested forms", () => {
|
|
524
|
+
it("should chain nested forms", () => {
|
|
525
|
+
doc = jqLite(
|
|
526
|
+
'<ng:form name="parent">' +
|
|
527
|
+
'<ng:form name="child">' +
|
|
528
|
+
'<input ng:model="modelA" name="inputA">' +
|
|
529
|
+
'<input ng:model="modelB" name="inputB">' +
|
|
530
|
+
"</ng:form>" +
|
|
531
|
+
"</ng:form>",
|
|
532
|
+
);
|
|
533
|
+
$compile(doc)(scope);
|
|
534
|
+
|
|
535
|
+
const { parent } = scope;
|
|
536
|
+
const { child } = scope;
|
|
537
|
+
const { inputA } = child;
|
|
538
|
+
const { inputB } = child;
|
|
539
|
+
|
|
540
|
+
inputA.$setValidity("MyError", false);
|
|
541
|
+
inputB.$setValidity("MyError", false);
|
|
542
|
+
expect(parent.$error.MyError).toEqual([child]);
|
|
543
|
+
expect(child.$error.MyError).toEqual([inputA, inputB]);
|
|
544
|
+
|
|
545
|
+
inputA.$setValidity("MyError", true);
|
|
546
|
+
expect(parent.$error.MyError).toEqual([child]);
|
|
547
|
+
expect(child.$error.MyError).toEqual([inputB]);
|
|
548
|
+
|
|
549
|
+
inputB.$setValidity("MyError", true);
|
|
550
|
+
expect(parent.$error.MyError).toBeFalsy();
|
|
551
|
+
expect(child.$error.MyError).toBeFalsy();
|
|
552
|
+
|
|
553
|
+
child.$setDirty();
|
|
554
|
+
expect(parent.$dirty).toBeTruthy();
|
|
555
|
+
|
|
556
|
+
child.$setSubmitted();
|
|
557
|
+
expect(parent.$submitted).toBeTruthy();
|
|
558
|
+
});
|
|
559
|
+
|
|
560
|
+
it("should set $submitted to true on child forms when parent is submitted", () => {
|
|
561
|
+
doc = jqLite(
|
|
562
|
+
'<ng-form name="parent">' +
|
|
563
|
+
'<ng-form name="child">' +
|
|
564
|
+
'<input ng:model="modelA" name="inputA">' +
|
|
565
|
+
'<input ng:model="modelB" name="inputB">' +
|
|
566
|
+
"</ng-form>" +
|
|
567
|
+
"</ng-form>",
|
|
568
|
+
);
|
|
569
|
+
$compile(doc)(scope);
|
|
570
|
+
|
|
571
|
+
const { parent } = scope;
|
|
572
|
+
const { child } = scope;
|
|
573
|
+
|
|
574
|
+
parent.$setSubmitted();
|
|
575
|
+
expect(parent.$submitted).toBeTruthy();
|
|
576
|
+
expect(child.$submitted).toBeTruthy();
|
|
577
|
+
});
|
|
578
|
+
|
|
579
|
+
it("should not propagate $submitted state on removed child forms when parent is submitted", () => {
|
|
580
|
+
doc = jqLite(
|
|
581
|
+
'<ng-form name="parent">' +
|
|
582
|
+
'<ng-form name="child">' +
|
|
583
|
+
'<ng-form name="grandchild">' +
|
|
584
|
+
'<input ng:model="modelA" name="inputA">' +
|
|
585
|
+
"</ng-form>" +
|
|
586
|
+
"</ng-form>" +
|
|
587
|
+
"</ng-form>",
|
|
588
|
+
);
|
|
589
|
+
$compile(doc)(scope);
|
|
590
|
+
|
|
591
|
+
const { parent } = scope;
|
|
592
|
+
const { child } = scope;
|
|
593
|
+
const { grandchild } = scope;
|
|
594
|
+
const ggchild = scope.greatgrandchild;
|
|
595
|
+
|
|
596
|
+
parent.$removeControl(child);
|
|
597
|
+
|
|
598
|
+
parent.$setSubmitted();
|
|
599
|
+
expect(parent.$submitted).toBeTruthy();
|
|
600
|
+
expect(child.$submitted).not.toBeTruthy();
|
|
601
|
+
expect(grandchild.$submitted).not.toBeTruthy();
|
|
602
|
+
|
|
603
|
+
parent.$addControl(child);
|
|
604
|
+
|
|
605
|
+
expect(parent.$submitted).toBeTruthy();
|
|
606
|
+
expect(child.$submitted).not.toBeTruthy();
|
|
607
|
+
expect(grandchild.$submitted).not.toBeTruthy();
|
|
608
|
+
|
|
609
|
+
parent.$setSubmitted();
|
|
610
|
+
expect(parent.$submitted).toBeTruthy();
|
|
611
|
+
expect(child.$submitted).toBeTruthy();
|
|
612
|
+
expect(grandchild.$submitted).toBeTruthy();
|
|
613
|
+
|
|
614
|
+
parent.$removeControl(child);
|
|
615
|
+
|
|
616
|
+
expect(parent.$submitted).toBeTruthy();
|
|
617
|
+
expect(child.$submitted).toBeTruthy();
|
|
618
|
+
expect(grandchild.$submitted).toBeTruthy();
|
|
619
|
+
|
|
620
|
+
parent.$setPristine(); // sets $submitted to false
|
|
621
|
+
expect(parent.$submitted).not.toBeTruthy();
|
|
622
|
+
expect(child.$submitted).toBeTruthy();
|
|
623
|
+
expect(grandchild.$submitted).toBeTruthy();
|
|
624
|
+
|
|
625
|
+
grandchild.$setPristine();
|
|
626
|
+
expect(grandchild.$submitted).not.toBeTruthy();
|
|
627
|
+
|
|
628
|
+
child.$setSubmitted();
|
|
629
|
+
expect(parent.$submitted).not.toBeTruthy();
|
|
630
|
+
expect(child.$submitted).toBeTruthy();
|
|
631
|
+
expect(grandchild.$submitted).toBeTruthy();
|
|
632
|
+
|
|
633
|
+
child.$setPristine();
|
|
634
|
+
expect(parent.$submitted).not.toBeTruthy();
|
|
635
|
+
expect(child.$submitted).not.toBeTruthy();
|
|
636
|
+
expect(grandchild.$submitted).not.toBeTruthy();
|
|
637
|
+
|
|
638
|
+
// Test upwards submission setting
|
|
639
|
+
grandchild.$setSubmitted();
|
|
640
|
+
expect(parent.$submitted).not.toBeTruthy();
|
|
641
|
+
expect(child.$submitted).toBeTruthy();
|
|
642
|
+
expect(grandchild.$submitted).toBeTruthy();
|
|
643
|
+
});
|
|
644
|
+
|
|
645
|
+
it("should set $submitted to true on child and parent forms when form is submitted", () => {
|
|
646
|
+
doc = jqLite(
|
|
647
|
+
'<ng-form name="parent">' +
|
|
648
|
+
'<ng-form name="child">' +
|
|
649
|
+
'<ng-form name="grandchild">' +
|
|
650
|
+
'<input ng:model="modelA" name="inputA">' +
|
|
651
|
+
'<input ng:model="modelB" name="inputB">' +
|
|
652
|
+
"</ng-form>" +
|
|
653
|
+
"</ng-form>" +
|
|
654
|
+
"</ng-form>",
|
|
655
|
+
);
|
|
656
|
+
$compile(doc)(scope);
|
|
657
|
+
|
|
658
|
+
const { parent } = scope;
|
|
659
|
+
const { child } = scope;
|
|
660
|
+
const { grandchild } = scope;
|
|
661
|
+
|
|
662
|
+
child.$setSubmitted();
|
|
663
|
+
|
|
664
|
+
expect(parent.$submitted).toBeTruthy();
|
|
665
|
+
expect(child.$submitted).toBeTruthy();
|
|
666
|
+
expect(grandchild.$submitted).toBeTruthy();
|
|
667
|
+
});
|
|
668
|
+
|
|
669
|
+
it("should deregister a child form when its DOM is removed", function () {
|
|
670
|
+
doc = jqLite(
|
|
671
|
+
'<form name="parent">' +
|
|
672
|
+
'<div class="ng-form" name="child">' +
|
|
673
|
+
'<input ng:model="modelA" name="inputA" required>' +
|
|
674
|
+
"</div>" +
|
|
675
|
+
"</form>",
|
|
676
|
+
);
|
|
677
|
+
$compile(doc)(scope);
|
|
678
|
+
scope.$apply();
|
|
679
|
+
|
|
680
|
+
var parent = scope.parent,
|
|
681
|
+
child = scope.child;
|
|
682
|
+
|
|
683
|
+
expect(parent).toBeDefined();
|
|
684
|
+
expect(child).toBeDefined();
|
|
685
|
+
expect(parent.$error.required).toEqual([child]);
|
|
686
|
+
doc.children().remove(); //remove child
|
|
687
|
+
|
|
688
|
+
expect(parent.child).toBeUndefined();
|
|
689
|
+
expect(scope.child).toBeUndefined();
|
|
690
|
+
expect(parent.$error.required).toBeFalsy();
|
|
691
|
+
});
|
|
692
|
+
|
|
693
|
+
it("should deregister a child form whose name is an expression when its DOM is removed", () => {
|
|
694
|
+
doc = jqLite(
|
|
695
|
+
'<form name="parent">' +
|
|
696
|
+
'<div class="ng-form" name="child.form">' +
|
|
697
|
+
'<input ng:model="modelA" name="inputA" required>' +
|
|
698
|
+
"</div>" +
|
|
699
|
+
"</form>",
|
|
700
|
+
);
|
|
701
|
+
$compile(doc)(scope);
|
|
702
|
+
scope.$apply();
|
|
703
|
+
|
|
704
|
+
const { parent } = scope;
|
|
705
|
+
const child = scope.child.form;
|
|
706
|
+
|
|
707
|
+
expect(parent).toBeDefined();
|
|
708
|
+
expect(child).toBeDefined();
|
|
709
|
+
expect(parent.$error.required).toEqual([child]);
|
|
710
|
+
doc.children().remove(); // remove child
|
|
711
|
+
|
|
712
|
+
expect(parent.child).toBeUndefined();
|
|
713
|
+
expect(scope.child.form).toBeUndefined();
|
|
714
|
+
expect(parent.$error.required).toBeFalsy();
|
|
715
|
+
});
|
|
716
|
+
|
|
717
|
+
it("should deregister a input when it is removed from DOM", () => {
|
|
718
|
+
doc = jqLite(
|
|
719
|
+
'<form name="parent">' +
|
|
720
|
+
'<div class="ng-form" name="child">' +
|
|
721
|
+
'<input ng-if="inputPresent" ng-model="modelA" name="inputA" required maxlength="10">' +
|
|
722
|
+
"</div>" +
|
|
723
|
+
"</form>",
|
|
724
|
+
);
|
|
725
|
+
$compile(doc)(scope);
|
|
726
|
+
scope.inputPresent = true;
|
|
727
|
+
scope.$apply();
|
|
728
|
+
|
|
729
|
+
const { parent } = scope;
|
|
730
|
+
const { child } = scope;
|
|
731
|
+
const input = child.inputA;
|
|
732
|
+
|
|
733
|
+
expect(parent).toBeDefined();
|
|
734
|
+
expect(child).toBeDefined();
|
|
735
|
+
|
|
736
|
+
expect(parent.$error.required).toEqual([child]);
|
|
737
|
+
expect(parent.$$success.maxlength).toEqual([child]);
|
|
738
|
+
|
|
739
|
+
expect(child.$error.required).toEqual([input]);
|
|
740
|
+
expect(child.$$success.maxlength).toEqual([input]);
|
|
741
|
+
|
|
742
|
+
expect(doc[0].classList.contains("ng-invalid")).toBe(true);
|
|
743
|
+
expect(doc[0].classList.contains("ng-invalid-required")).toBe(true);
|
|
744
|
+
expect(doc[0].classList.contains("ng-valid-maxlength")).toBe(true);
|
|
745
|
+
expect(doc.find("div")[0].classList.contains("ng-invalid")).toBe(true);
|
|
746
|
+
expect(doc.find("div")[0].classList.contains("ng-invalid-required")).toBe(
|
|
747
|
+
true,
|
|
748
|
+
);
|
|
749
|
+
expect(doc.find("div")[0].classList.contains("ng-valid-maxlength")).toBe(
|
|
750
|
+
true,
|
|
751
|
+
);
|
|
752
|
+
|
|
753
|
+
// remove child input
|
|
754
|
+
scope.$apply("inputPresent = false");
|
|
755
|
+
|
|
756
|
+
expect(parent.$error.required).toBeFalsy();
|
|
757
|
+
expect(parent.$$success.maxlength).toBeFalsy();
|
|
758
|
+
|
|
759
|
+
expect(child.$error.required).toBeFalsy();
|
|
760
|
+
expect(child.$$success.maxlength).toBeFalsy();
|
|
761
|
+
|
|
762
|
+
expect(doc[0].classList.contains("ng-valid")).toBe(true);
|
|
763
|
+
expect(doc[0].classList.contains("ng-valid-required")).toBe(false);
|
|
764
|
+
expect(doc[0].classList.contains("ng-invalid-required")).toBe(false);
|
|
765
|
+
expect(doc[0].classList.contains("ng-valid-maxlength")).toBe(false);
|
|
766
|
+
expect(doc[0].classList.contains("ng-invalid-maxlength")).toBe(false);
|
|
767
|
+
|
|
768
|
+
expect(doc.find("div")[0].classList.contains("ng-valid")).toBe(true);
|
|
769
|
+
expect(doc.find("div")[0].classList.contains("ng-valid-required")).toBe(
|
|
770
|
+
false,
|
|
771
|
+
);
|
|
772
|
+
expect(doc.find("div")[0].classList.contains("ng-invalid-required")).toBe(
|
|
773
|
+
false,
|
|
774
|
+
);
|
|
775
|
+
expect(doc.find("div")[0].classList.contains("ng-valid-maxlength")).toBe(
|
|
776
|
+
false,
|
|
777
|
+
);
|
|
778
|
+
expect(
|
|
779
|
+
doc.find("div")[0].classList.contains("ng-invalid-maxlength"),
|
|
780
|
+
).toBe(false);
|
|
781
|
+
});
|
|
782
|
+
|
|
783
|
+
it("should deregister a input that is $pending when it is removed from DOM", () => {
|
|
784
|
+
doc = jqLite(
|
|
785
|
+
'<form name="parent">' +
|
|
786
|
+
'<div class="ng-form" name="child">' +
|
|
787
|
+
'<input ng-if="inputPresent" ng-model="modelA" name="inputA">' +
|
|
788
|
+
"</div>" +
|
|
789
|
+
"</form>",
|
|
790
|
+
);
|
|
791
|
+
$compile(doc)(scope);
|
|
792
|
+
scope.$apply("inputPresent = true");
|
|
793
|
+
|
|
794
|
+
const { parent } = scope;
|
|
795
|
+
const { child } = scope;
|
|
796
|
+
const input = child.inputA;
|
|
797
|
+
|
|
798
|
+
scope.$apply(child.inputA.$setValidity("fake", undefined));
|
|
799
|
+
|
|
800
|
+
expect(parent).toBeDefined();
|
|
801
|
+
expect(child).toBeDefined();
|
|
802
|
+
|
|
803
|
+
expect(parent.$pending.fake).toEqual([child]);
|
|
804
|
+
expect(child.$pending.fake).toEqual([input]);
|
|
805
|
+
|
|
806
|
+
expect(doc[0].classList.contains("ng-pending")).toBe(true);
|
|
807
|
+
expect(doc.find("div")[0].classList.contains("ng-pending")).toBe(true);
|
|
808
|
+
|
|
809
|
+
// remove child input
|
|
810
|
+
scope.$apply("inputPresent = false");
|
|
811
|
+
|
|
812
|
+
expect(parent.$pending).toBeUndefined();
|
|
813
|
+
expect(child.$pending).toBeUndefined();
|
|
814
|
+
|
|
815
|
+
expect(doc[0].classList.contains("ng-pending")).toBe(false);
|
|
816
|
+
expect(doc.find("div")[0].classList.contains("ng-pending")).toBe(false);
|
|
817
|
+
});
|
|
818
|
+
|
|
819
|
+
it("should leave the parent form invalid when deregister a removed input", () => {
|
|
820
|
+
doc = jqLite(
|
|
821
|
+
'<form name="parent">' +
|
|
822
|
+
'<div class="ng-form" name="child">' +
|
|
823
|
+
'<input ng-if="inputPresent" ng-model="modelA" name="inputA" required>' +
|
|
824
|
+
'<input ng-model="modelB" name="inputB" required>' +
|
|
825
|
+
"</div>" +
|
|
826
|
+
"</form>",
|
|
827
|
+
);
|
|
828
|
+
$compile(doc)(scope);
|
|
829
|
+
scope.inputPresent = true;
|
|
830
|
+
scope.$apply();
|
|
831
|
+
|
|
832
|
+
const { parent } = scope;
|
|
833
|
+
const { child } = scope;
|
|
834
|
+
const { inputA } = child;
|
|
835
|
+
const { inputB } = child;
|
|
836
|
+
|
|
837
|
+
expect(parent).toBeDefined();
|
|
838
|
+
expect(child).toBeDefined();
|
|
839
|
+
expect(parent.$error.required).toEqual([child]);
|
|
840
|
+
expect(child.$error.required).toEqual([inputB, inputA]);
|
|
841
|
+
|
|
842
|
+
// remove child input
|
|
843
|
+
scope.inputPresent = false;
|
|
844
|
+
scope.$apply();
|
|
845
|
+
|
|
846
|
+
expect(parent.$error.required).toEqual([child]);
|
|
847
|
+
expect(child.$error.required).toEqual([inputB]);
|
|
848
|
+
});
|
|
849
|
+
|
|
850
|
+
it("should ignore changes in manually removed child forms", () => {
|
|
851
|
+
doc = $compile(
|
|
852
|
+
'<form name="myForm">' +
|
|
853
|
+
'<ng-form name="childform">' +
|
|
854
|
+
'<input name="childformcontrol" ng-maxlength="1" ng-model="value"/>' +
|
|
855
|
+
"</ng-form>" +
|
|
856
|
+
"</form>",
|
|
857
|
+
)(scope);
|
|
858
|
+
|
|
859
|
+
const form = scope.myForm;
|
|
860
|
+
const childformController = doc.find("ng-form").eq(0).controller("form");
|
|
861
|
+
|
|
862
|
+
const input = doc.find("input").eq(0);
|
|
863
|
+
const inputController = input.controller("ngModel");
|
|
864
|
+
|
|
865
|
+
// changeInputValue(input, "ab");
|
|
866
|
+
input[0].setAttribute("value", "ab");
|
|
867
|
+
input[0].dispatchEvent(new Event("change"));
|
|
868
|
+
|
|
869
|
+
scope.$apply();
|
|
870
|
+
|
|
871
|
+
expect(form.$dirty).toBe(true);
|
|
872
|
+
expect(form.$error.maxlength).toBeTruthy();
|
|
873
|
+
expect(form.$error.maxlength[0].$name).toBe("childform");
|
|
874
|
+
|
|
875
|
+
inputController.$setPristine();
|
|
876
|
+
expect(form.$dirty).toBe(true);
|
|
877
|
+
|
|
878
|
+
form.$setPristine();
|
|
879
|
+
|
|
880
|
+
// remove child form
|
|
881
|
+
form.$removeControl(childformController);
|
|
882
|
+
expect(form.childform).toBeUndefined();
|
|
883
|
+
expect(form.$error.maxlength).toBeFalsy();
|
|
884
|
+
|
|
885
|
+
// changeInputValue(input, "abc");
|
|
886
|
+
input[0].setAttribute("value", "abc");
|
|
887
|
+
input[0].dispatchEvent(new Event("change"));
|
|
888
|
+
scope.$apply();
|
|
889
|
+
|
|
890
|
+
expect(form.$error.maxlength).toBeFalsy();
|
|
891
|
+
expect(form.$dirty).toBe(false);
|
|
892
|
+
});
|
|
893
|
+
|
|
894
|
+
it("should react to changes in manually added child forms", () => {
|
|
895
|
+
doc = $compile(
|
|
896
|
+
'<form name="myForm">' +
|
|
897
|
+
'<ng-form name="childForm">' +
|
|
898
|
+
'<input name="childformcontrol" ng-maxlength="1" ng-model="value" />' +
|
|
899
|
+
"</ng-form>" +
|
|
900
|
+
"</form>",
|
|
901
|
+
)(scope);
|
|
902
|
+
|
|
903
|
+
const form = scope.myForm;
|
|
904
|
+
const childFormController = doc.find("ng-form").eq(0).controller("form");
|
|
905
|
+
|
|
906
|
+
const input = doc.find("input").eq(0);
|
|
907
|
+
|
|
908
|
+
// remove child form so we can add it manually
|
|
909
|
+
form.$removeControl(childFormController);
|
|
910
|
+
// changeInputValue(input, "ab");
|
|
911
|
+
input[0].setAttribute("value", "ab");
|
|
912
|
+
input[0].dispatchEvent(new Event("change"));
|
|
913
|
+
|
|
914
|
+
expect(form.childForm).toBeUndefined();
|
|
915
|
+
expect(form.$dirty).toBe(false);
|
|
916
|
+
expect(form.$error.maxlength).toBeFalsy();
|
|
917
|
+
|
|
918
|
+
// re-add the child form; its current validation state is not propagated
|
|
919
|
+
form.$addControl(childFormController);
|
|
920
|
+
expect(form.childForm).toBe(childFormController);
|
|
921
|
+
expect(form.$error.maxlength).toBeFalsy();
|
|
922
|
+
expect(form.$dirty).toBe(false);
|
|
923
|
+
|
|
924
|
+
// Only when the input inside the child form changes, the validation state is propagated
|
|
925
|
+
// changeInputValue(input, "abc");
|
|
926
|
+
input[0].setAttribute("value", "abc");
|
|
927
|
+
input[0].dispatchEvent(new Event("change"));
|
|
928
|
+
expect(form.$error.maxlength[0]).toBe(childFormController);
|
|
929
|
+
expect(form.$dirty).toBe(false);
|
|
930
|
+
});
|
|
931
|
+
|
|
932
|
+
it("should use the correct parent when renaming and removing dynamically added forms", () => {
|
|
933
|
+
scope.formName = "childForm";
|
|
934
|
+
scope.hasChildForm = true;
|
|
935
|
+
|
|
936
|
+
doc = $compile(
|
|
937
|
+
'<form name="myForm">' +
|
|
938
|
+
'<div ng-if="hasChildForm">' +
|
|
939
|
+
'<ng-form name="{{formName}}">' +
|
|
940
|
+
'<input name="childformcontrol" ng-maxlength="1" ng-model="value"/>' +
|
|
941
|
+
"</ng-form>" +
|
|
942
|
+
"</div>" +
|
|
943
|
+
"</form>" +
|
|
944
|
+
'<form name="otherForm"></form>',
|
|
945
|
+
)(scope);
|
|
946
|
+
|
|
947
|
+
scope.$digest();
|
|
948
|
+
|
|
949
|
+
const form = scope.myForm;
|
|
950
|
+
const { otherForm } = scope;
|
|
951
|
+
const { childForm } = form;
|
|
952
|
+
|
|
953
|
+
// remove child form and add it to another form
|
|
954
|
+
form.$removeControl(childForm);
|
|
955
|
+
otherForm.$addControl(childForm);
|
|
956
|
+
|
|
957
|
+
expect(form.childForm).toBeUndefined();
|
|
958
|
+
expect(otherForm.childForm).toBe(childForm);
|
|
959
|
+
|
|
960
|
+
// rename the childForm
|
|
961
|
+
scope.formName = "childFormMoved";
|
|
962
|
+
scope.$digest();
|
|
963
|
+
|
|
964
|
+
expect(form.childFormMoved).toBeUndefined();
|
|
965
|
+
expect(otherForm.childForm).toBeUndefined();
|
|
966
|
+
expect(otherForm.childFormMoved).toBe(childForm);
|
|
967
|
+
|
|
968
|
+
scope.hasChildForm = false;
|
|
969
|
+
scope.$digest();
|
|
970
|
+
|
|
971
|
+
expect(form.childFormMoved).toBeUndefined();
|
|
972
|
+
expect(otherForm.childFormMoved).toBeUndefined();
|
|
973
|
+
});
|
|
974
|
+
|
|
975
|
+
it("should chain nested forms in repeater", () => {
|
|
976
|
+
doc = jqLite(
|
|
977
|
+
"<ng:form name=parent>" +
|
|
978
|
+
'<ng:form ng:repeat="f in forms" name=child>' +
|
|
979
|
+
"<input type=text ng:model=text name=text>" +
|
|
980
|
+
"</ng:form>" +
|
|
981
|
+
"</ng:form>",
|
|
982
|
+
);
|
|
983
|
+
$compile(doc)(scope);
|
|
984
|
+
|
|
985
|
+
scope.$apply(() => {
|
|
986
|
+
scope.forms = [1];
|
|
987
|
+
});
|
|
988
|
+
|
|
989
|
+
const { parent } = scope;
|
|
990
|
+
const { child } = doc.find("input").scope();
|
|
991
|
+
const input = child.text;
|
|
992
|
+
|
|
993
|
+
expect(parent).toBeDefined();
|
|
994
|
+
expect(child).toBeDefined();
|
|
995
|
+
expect(input).toBeDefined();
|
|
996
|
+
|
|
997
|
+
input.$setValidity("myRule", false);
|
|
998
|
+
expect(input.$error.myRule).toEqual(true);
|
|
999
|
+
expect(child.$error.myRule).toEqual([input]);
|
|
1000
|
+
expect(parent.$error.myRule).toEqual([child]);
|
|
1001
|
+
|
|
1002
|
+
input.$setValidity("myRule", true);
|
|
1003
|
+
expect(parent.$error.myRule).toBeFalsy();
|
|
1004
|
+
expect(child.$error.myRule).toBeFalsy();
|
|
1005
|
+
});
|
|
1006
|
+
});
|
|
1007
|
+
|
|
1008
|
+
describe("validation", () => {
|
|
1009
|
+
beforeEach(() => {
|
|
1010
|
+
doc = $compile(
|
|
1011
|
+
'<form name="form">' +
|
|
1012
|
+
'<input ng-model="name" name="name" store-model-ctrl/>' +
|
|
1013
|
+
"</form>",
|
|
1014
|
+
)(scope);
|
|
1015
|
+
|
|
1016
|
+
scope.$digest();
|
|
1017
|
+
});
|
|
1018
|
+
|
|
1019
|
+
it("should have ng-valid/ng-invalid css class", () => {
|
|
1020
|
+
expect(doc[0].classList.contains("ng-valid")).toBeTrue();
|
|
1021
|
+
|
|
1022
|
+
control.$setValidity("error", false);
|
|
1023
|
+
scope.$digest();
|
|
1024
|
+
expect(doc[0].classList.contains("ng-invalid")).toBeTrue();
|
|
1025
|
+
expect(doc[0].classList.contains("ng-valid-error")).toBe(false);
|
|
1026
|
+
expect(doc[0].classList.contains("ng-invalid-error")).toBe(true);
|
|
1027
|
+
|
|
1028
|
+
control.$setValidity("another", false);
|
|
1029
|
+
scope.$digest();
|
|
1030
|
+
expect(doc[0].classList.contains("ng-valid-error")).toBe(false);
|
|
1031
|
+
expect(doc[0].classList.contains("ng-invalid-error")).toBe(true);
|
|
1032
|
+
expect(doc[0].classList.contains("ng-valid-another")).toBe(false);
|
|
1033
|
+
expect(doc[0].classList.contains("ng-invalid-another")).toBe(true);
|
|
1034
|
+
|
|
1035
|
+
control.$setValidity("error", true);
|
|
1036
|
+
scope.$digest();
|
|
1037
|
+
expect(doc[0].classList.contains("ng-invalid")).toBeTrue();
|
|
1038
|
+
expect(doc[0].classList.contains("ng-valid-error")).toBe(true);
|
|
1039
|
+
expect(doc[0].classList.contains("ng-invalid-error")).toBe(false);
|
|
1040
|
+
expect(doc[0].classList.contains("ng-valid-another")).toBe(false);
|
|
1041
|
+
expect(doc[0].classList.contains("ng-invalid-another")).toBe(true);
|
|
1042
|
+
|
|
1043
|
+
control.$setValidity("another", true);
|
|
1044
|
+
scope.$digest();
|
|
1045
|
+
expect(doc[0].classList.contains("ng-valid")).toBeTrue();
|
|
1046
|
+
expect(doc[0].classList.contains("ng-valid-error")).toBe(true);
|
|
1047
|
+
expect(doc[0].classList.contains("ng-invalid-error")).toBe(false);
|
|
1048
|
+
expect(doc[0].classList.contains("ng-valid-another")).toBe(true);
|
|
1049
|
+
expect(doc[0].classList.contains("ng-invalid-another")).toBe(false);
|
|
1050
|
+
|
|
1051
|
+
// validators are skipped, e.g. because of a parser error
|
|
1052
|
+
control.$setValidity("error", null);
|
|
1053
|
+
control.$setValidity("another", null);
|
|
1054
|
+
scope.$digest();
|
|
1055
|
+
expect(doc[0].classList.contains("ng-valid-error")).toBe(false);
|
|
1056
|
+
expect(doc[0].classList.contains("ng-invalid-error")).toBe(false);
|
|
1057
|
+
expect(doc[0].classList.contains("ng-valid-another")).toBe(false);
|
|
1058
|
+
expect(doc[0].classList.contains("ng-invalid-another")).toBe(false);
|
|
1059
|
+
});
|
|
1060
|
+
|
|
1061
|
+
it("should have ng-pristine/ng-dirty css class", () => {
|
|
1062
|
+
expect(doc[0].classList.contains("ng-pristine")).toBeTrue();
|
|
1063
|
+
expect(doc[0].classList.contains("ng-dirty")).toBeFalse();
|
|
1064
|
+
|
|
1065
|
+
control.$setViewValue("");
|
|
1066
|
+
scope.$apply();
|
|
1067
|
+
expect(doc[0].classList.contains("ng-pristine")).toBeFalse();
|
|
1068
|
+
expect(doc[0].classList.contains("ng-dirty")).toBeTrue();
|
|
1069
|
+
});
|
|
1070
|
+
});
|
|
1071
|
+
|
|
1072
|
+
describe("$pending", () => {
|
|
1073
|
+
beforeEach(() => {
|
|
1074
|
+
doc = $compile('<form name="form"></form>')(scope);
|
|
1075
|
+
scope.$digest();
|
|
1076
|
+
});
|
|
1077
|
+
|
|
1078
|
+
it("should set valid and invalid to undefined when a validation error state is set as pending", () => {
|
|
1079
|
+
let defer;
|
|
1080
|
+
const form = doc.data("$formController");
|
|
1081
|
+
|
|
1082
|
+
const ctrl = {};
|
|
1083
|
+
form.$setValidity("matias", undefined, ctrl);
|
|
1084
|
+
|
|
1085
|
+
expect(form.$valid).toBeUndefined();
|
|
1086
|
+
expect(form.$invalid).toBeUndefined();
|
|
1087
|
+
expect(form.$pending.matias).toEqual([ctrl]);
|
|
1088
|
+
|
|
1089
|
+
form.$setValidity("matias", true, ctrl);
|
|
1090
|
+
|
|
1091
|
+
expect(form.$valid).toBe(true);
|
|
1092
|
+
expect(form.$invalid).toBe(false);
|
|
1093
|
+
expect(form.$pending).toBeUndefined();
|
|
1094
|
+
|
|
1095
|
+
form.$setValidity("matias", false, ctrl);
|
|
1096
|
+
|
|
1097
|
+
expect(form.$valid).toBe(false);
|
|
1098
|
+
expect(form.$invalid).toBe(true);
|
|
1099
|
+
expect(form.$pending).toBeUndefined();
|
|
1100
|
+
});
|
|
1101
|
+
});
|
|
1102
|
+
|
|
1103
|
+
describe("$setPristine", () => {
|
|
1104
|
+
it("should reset pristine state of form and controls", () => {
|
|
1105
|
+
doc = $compile(
|
|
1106
|
+
'<form name="testForm">' +
|
|
1107
|
+
'<input ng-model="named1" name="foo">' +
|
|
1108
|
+
'<input ng-model="named2" name="bar">' +
|
|
1109
|
+
"</form>",
|
|
1110
|
+
)(scope);
|
|
1111
|
+
|
|
1112
|
+
scope.$digest();
|
|
1113
|
+
|
|
1114
|
+
const form = doc;
|
|
1115
|
+
const formCtrl = scope.testForm;
|
|
1116
|
+
const input1 = form.find("input").eq(0);
|
|
1117
|
+
const input1Ctrl = input1.controller("ngModel");
|
|
1118
|
+
const input2 = form.find("input").eq(1);
|
|
1119
|
+
const input2Ctrl = input2.controller("ngModel");
|
|
1120
|
+
|
|
1121
|
+
input1Ctrl.$setViewValue("xx");
|
|
1122
|
+
input2Ctrl.$setViewValue("yy");
|
|
1123
|
+
scope.$apply();
|
|
1124
|
+
expect(form[0].classList.contains("ng-dirty")).toBeTrue();
|
|
1125
|
+
expect(input1[0].classList.contains("ng-dirty")).toBeTrue();
|
|
1126
|
+
expect(input2[0].classList.contains("ng-dirty")).toBeTrue();
|
|
1127
|
+
|
|
1128
|
+
formCtrl.$setPristine();
|
|
1129
|
+
scope.$digest();
|
|
1130
|
+
|
|
1131
|
+
expect(form[0].classList.contains("ng-pristine")).toBeTrue();
|
|
1132
|
+
expect(form[0].classList.contains("ng-dirty")).toBeFalse();
|
|
1133
|
+
expect(formCtrl.$pristine).toBe(true);
|
|
1134
|
+
expect(formCtrl.$dirty).toBe(false);
|
|
1135
|
+
|
|
1136
|
+
expect(input1[0].classList.contains("ng-pristine")).toBeTrue();
|
|
1137
|
+
expect(input1Ctrl.$pristine).toBe(true);
|
|
1138
|
+
expect(input1Ctrl.$dirty).toBe(false);
|
|
1139
|
+
expect(input2[0].classList.contains("ng-pristine")).toBeTrue();
|
|
1140
|
+
expect(input2Ctrl.$pristine).toBe(true);
|
|
1141
|
+
expect(input2Ctrl.$dirty).toBe(false);
|
|
1142
|
+
});
|
|
1143
|
+
|
|
1144
|
+
it("should reset pristine state of anonymous form controls", () => {
|
|
1145
|
+
doc = $compile(
|
|
1146
|
+
'<form name="testForm">' + '<input ng-model="anonymous">' + "</form>",
|
|
1147
|
+
)(scope);
|
|
1148
|
+
|
|
1149
|
+
scope.$digest();
|
|
1150
|
+
|
|
1151
|
+
const form = doc;
|
|
1152
|
+
const formCtrl = scope.testForm;
|
|
1153
|
+
const input = form.find("input").eq(0);
|
|
1154
|
+
const inputCtrl = input.controller("ngModel");
|
|
1155
|
+
|
|
1156
|
+
inputCtrl.$setViewValue("xx");
|
|
1157
|
+
scope.$apply();
|
|
1158
|
+
expect(form[0].classList.contains("ng-dirty")).toBeTrue();
|
|
1159
|
+
expect(input[0].classList.contains("ng-dirty")).toBeTrue();
|
|
1160
|
+
|
|
1161
|
+
formCtrl.$setPristine();
|
|
1162
|
+
scope.$digest();
|
|
1163
|
+
expect(form[0].classList.contains("ng-pristine")).toBeTrue();
|
|
1164
|
+
expect(formCtrl.$pristine).toBe(true);
|
|
1165
|
+
expect(formCtrl.$dirty).toBe(false);
|
|
1166
|
+
expect(input[0].classList.contains("ng-pristine")).toBeTrue();
|
|
1167
|
+
expect(inputCtrl.$pristine).toBe(true);
|
|
1168
|
+
expect(inputCtrl.$dirty).toBe(false);
|
|
1169
|
+
});
|
|
1170
|
+
|
|
1171
|
+
it("should reset pristine state of nested forms", () => {
|
|
1172
|
+
doc = $compile(
|
|
1173
|
+
'<form name="testForm">' +
|
|
1174
|
+
"<div ng-form>" +
|
|
1175
|
+
'<input ng-model="named" name="foo">' +
|
|
1176
|
+
"</div>" +
|
|
1177
|
+
"</form>",
|
|
1178
|
+
)(scope);
|
|
1179
|
+
|
|
1180
|
+
scope.$digest();
|
|
1181
|
+
|
|
1182
|
+
const form = doc;
|
|
1183
|
+
const formCtrl = scope.testForm;
|
|
1184
|
+
const nestedForm = form.find("div");
|
|
1185
|
+
const nestedFormCtrl = nestedForm.controller("form");
|
|
1186
|
+
const nestedInput = form.find("input").eq(0);
|
|
1187
|
+
const nestedInputCtrl = nestedInput.controller("ngModel");
|
|
1188
|
+
|
|
1189
|
+
nestedInputCtrl.$setViewValue("xx");
|
|
1190
|
+
scope.$apply();
|
|
1191
|
+
expect(form[0].classList.contains("ng-dirty")).toBeTrue();
|
|
1192
|
+
expect(nestedForm[0].classList.contains("ng-dirty")).toBeTrue();
|
|
1193
|
+
expect(nestedInput[0].classList.contains("ng-dirty")).toBeTrue();
|
|
1194
|
+
|
|
1195
|
+
formCtrl.$setPristine();
|
|
1196
|
+
scope.$digest();
|
|
1197
|
+
expect(form[0].classList.contains("ng-pristine")).toBeTrue();
|
|
1198
|
+
scope.$digest();
|
|
1199
|
+
|
|
1200
|
+
expect(formCtrl.$pristine).toBe(true);
|
|
1201
|
+
expect(formCtrl.$dirty).toBe(false);
|
|
1202
|
+
expect(nestedForm[0].classList.contains("ng-pristine")).toBeTrue();
|
|
1203
|
+
expect(nestedFormCtrl.$pristine).toBe(true);
|
|
1204
|
+
expect(nestedFormCtrl.$dirty).toBe(false);
|
|
1205
|
+
expect(nestedInput[0].classList.contains("ng-pristine")).toBeTrue();
|
|
1206
|
+
expect(nestedInputCtrl.$pristine).toBe(true);
|
|
1207
|
+
expect(nestedInputCtrl.$dirty).toBe(false);
|
|
1208
|
+
});
|
|
1209
|
+
});
|
|
1210
|
+
|
|
1211
|
+
describe("$setUntouched", () => {
|
|
1212
|
+
it("should trigger setUntouched on form controls", () => {
|
|
1213
|
+
const form = $compile(
|
|
1214
|
+
'<form name="myForm">' +
|
|
1215
|
+
'<input name="alias" type="text" ng-model="name" />' +
|
|
1216
|
+
"</form>",
|
|
1217
|
+
)(scope);
|
|
1218
|
+
scope.$digest();
|
|
1219
|
+
|
|
1220
|
+
scope.myForm.alias.$setTouched();
|
|
1221
|
+
expect(scope.myForm.alias.$touched).toBe(true);
|
|
1222
|
+
scope.myForm.$setUntouched();
|
|
1223
|
+
expect(scope.myForm.alias.$touched).toBe(false);
|
|
1224
|
+
dealoc(form);
|
|
1225
|
+
});
|
|
1226
|
+
|
|
1227
|
+
it("should trigger setUntouched on form controls with nested forms", () => {
|
|
1228
|
+
const form = $compile(
|
|
1229
|
+
'<form name="myForm">' +
|
|
1230
|
+
'<div class="ng-form" name="childForm">' +
|
|
1231
|
+
'<input name="alias" type="text" ng-model="name" />' +
|
|
1232
|
+
"</div>" +
|
|
1233
|
+
"</form>",
|
|
1234
|
+
)(scope);
|
|
1235
|
+
scope.$digest();
|
|
1236
|
+
|
|
1237
|
+
scope.myForm.childForm.alias.$setTouched();
|
|
1238
|
+
expect(scope.myForm.childForm.alias.$touched).toBe(true);
|
|
1239
|
+
scope.myForm.$setUntouched();
|
|
1240
|
+
expect(scope.myForm.childForm.alias.$touched).toBe(false);
|
|
1241
|
+
dealoc(form);
|
|
1242
|
+
});
|
|
1243
|
+
});
|
|
1244
|
+
|
|
1245
|
+
describe("$getControls", () => {
|
|
1246
|
+
it("should return an empty array if the controller has no controls", () => {
|
|
1247
|
+
doc = $compile('<form name="testForm"></form>')(scope);
|
|
1248
|
+
|
|
1249
|
+
scope.$digest();
|
|
1250
|
+
|
|
1251
|
+
const formCtrl = scope.testForm;
|
|
1252
|
+
|
|
1253
|
+
expect(formCtrl.$getControls()).toEqual([]);
|
|
1254
|
+
});
|
|
1255
|
+
|
|
1256
|
+
it("should return a shallow copy of the form controls", () => {
|
|
1257
|
+
doc = $compile(
|
|
1258
|
+
'<form name="testForm">' +
|
|
1259
|
+
'<input ng-model="named" name="foo">' +
|
|
1260
|
+
"<div ng-form>" +
|
|
1261
|
+
'<input ng-model="named" name="foo">' +
|
|
1262
|
+
"</div>" +
|
|
1263
|
+
"</form>",
|
|
1264
|
+
)(scope);
|
|
1265
|
+
|
|
1266
|
+
scope.$digest();
|
|
1267
|
+
|
|
1268
|
+
const form = doc;
|
|
1269
|
+
const formCtrl = scope.testForm;
|
|
1270
|
+
const formInput = form.children("input").eq(0);
|
|
1271
|
+
const formInputCtrl = formInput.controller("ngModel");
|
|
1272
|
+
const nestedForm = form.find("div");
|
|
1273
|
+
const nestedFormCtrl = nestedForm.controller("form");
|
|
1274
|
+
const nestedInput = nestedForm.children("input").eq(0);
|
|
1275
|
+
const nestedInputCtrl = nestedInput.controller("ngModel");
|
|
1276
|
+
|
|
1277
|
+
const controls = formCtrl.$getControls();
|
|
1278
|
+
|
|
1279
|
+
expect(controls).not.toBe(formCtrl.$$controls);
|
|
1280
|
+
|
|
1281
|
+
controls.push("something");
|
|
1282
|
+
expect(formCtrl.$$controls).not.toContain("something");
|
|
1283
|
+
|
|
1284
|
+
expect(controls[0]).toBe(formInputCtrl);
|
|
1285
|
+
expect(controls[1]).toBe(nestedFormCtrl);
|
|
1286
|
+
|
|
1287
|
+
const nestedControls = controls[1].$getControls();
|
|
1288
|
+
|
|
1289
|
+
expect(nestedControls[0]).toBe(nestedInputCtrl);
|
|
1290
|
+
});
|
|
1291
|
+
});
|
|
1292
|
+
|
|
1293
|
+
it("should rename nested form controls when interpolated name changes", () => {
|
|
1294
|
+
scope.idA = "A";
|
|
1295
|
+
scope.idB = "X";
|
|
1296
|
+
|
|
1297
|
+
doc = $compile(
|
|
1298
|
+
'<form name="form">' +
|
|
1299
|
+
'<div ng-form="nested{{idA}}">' +
|
|
1300
|
+
'<div ng-form name="nested{{idB}}"' +
|
|
1301
|
+
"</div>" +
|
|
1302
|
+
"</div>" +
|
|
1303
|
+
"</form>",
|
|
1304
|
+
)(scope);
|
|
1305
|
+
|
|
1306
|
+
scope.$digest();
|
|
1307
|
+
const formA = scope.form.nestedA;
|
|
1308
|
+
expect(formA).toBeDefined();
|
|
1309
|
+
expect(formA.$name).toBe("nestedA");
|
|
1310
|
+
|
|
1311
|
+
const formX = formA.nestedX;
|
|
1312
|
+
expect(formX).toBeDefined();
|
|
1313
|
+
expect(formX.$name).toBe("nestedX");
|
|
1314
|
+
|
|
1315
|
+
scope.idA = "B";
|
|
1316
|
+
scope.idB = "Y";
|
|
1317
|
+
scope.$digest();
|
|
1318
|
+
|
|
1319
|
+
expect(scope.form.nestedA).toBeUndefined();
|
|
1320
|
+
expect(scope.form.nestedB).toBe(formA);
|
|
1321
|
+
expect(formA.nestedX).toBeUndefined();
|
|
1322
|
+
expect(formA.nestedY).toBe(formX);
|
|
1323
|
+
});
|
|
1324
|
+
|
|
1325
|
+
it("should rename forms with no parent when interpolated name changes", () => {
|
|
1326
|
+
const element = $compile('<form name="name{{nameID}}"></form>')(scope);
|
|
1327
|
+
const element2 = $compile('<div ng-form="ngform{{nameID}}"></div>')(scope);
|
|
1328
|
+
scope.nameID = "A";
|
|
1329
|
+
scope.$digest();
|
|
1330
|
+
const form = element.controller("form");
|
|
1331
|
+
const form2 = element2.controller("form");
|
|
1332
|
+
expect(scope.nameA).toBe(form);
|
|
1333
|
+
expect(scope.ngformA).toBe(form2);
|
|
1334
|
+
expect(form.$name).toBe("nameA");
|
|
1335
|
+
expect(form2.$name).toBe("ngformA");
|
|
1336
|
+
|
|
1337
|
+
scope.nameID = "B";
|
|
1338
|
+
scope.$digest();
|
|
1339
|
+
expect(scope.nameA).toBeUndefined();
|
|
1340
|
+
expect(scope.ngformA).toBeUndefined();
|
|
1341
|
+
expect(scope.nameB).toBe(form);
|
|
1342
|
+
expect(scope.ngformB).toBe(form2);
|
|
1343
|
+
expect(form.$name).toBe("nameB");
|
|
1344
|
+
expect(form2.$name).toBe("ngformB");
|
|
1345
|
+
});
|
|
1346
|
+
|
|
1347
|
+
it("should rename forms with an initially blank name", () => {
|
|
1348
|
+
const element = $compile('<form name="{{name}}"></form>')(scope);
|
|
1349
|
+
scope.$digest();
|
|
1350
|
+
const form = element.controller("form");
|
|
1351
|
+
expect(scope[""]).toBe(form);
|
|
1352
|
+
expect(form.$name).toBe("");
|
|
1353
|
+
scope.name = "foo";
|
|
1354
|
+
scope.$digest();
|
|
1355
|
+
expect(scope.foo).toBe(form);
|
|
1356
|
+
expect(form.$name).toBe("foo");
|
|
1357
|
+
expect(scope.foo).toBe(form);
|
|
1358
|
+
});
|
|
1359
|
+
|
|
1360
|
+
describe("$setSubmitted", () => {
|
|
1361
|
+
beforeEach(() => {
|
|
1362
|
+
doc = $compile(
|
|
1363
|
+
'<form name="form" ng-submit="submitted = true">' +
|
|
1364
|
+
'<input type="text" ng-model="name" required />' +
|
|
1365
|
+
'<input type="submit" />' +
|
|
1366
|
+
"</form>",
|
|
1367
|
+
)(scope);
|
|
1368
|
+
|
|
1369
|
+
scope.$digest();
|
|
1370
|
+
});
|
|
1371
|
+
|
|
1372
|
+
it("should not init in submitted state", () => {
|
|
1373
|
+
expect(scope.form.$submitted).toBe(false);
|
|
1374
|
+
});
|
|
1375
|
+
|
|
1376
|
+
it("should be in submitted state when submitted", () => {
|
|
1377
|
+
// browserTrigger(doc, "submit");
|
|
1378
|
+
doc[0].dispatchEvent(new Event("submit"));
|
|
1379
|
+
expect(scope.form.$submitted).toBe(true);
|
|
1380
|
+
});
|
|
1381
|
+
|
|
1382
|
+
it("should revert submitted back to false when $setPristine is called on the form", () => {
|
|
1383
|
+
scope.form.$submitted = true;
|
|
1384
|
+
scope.form.$setPristine();
|
|
1385
|
+
expect(scope.form.$submitted).toBe(false);
|
|
1386
|
+
});
|
|
1387
|
+
});
|
|
1388
|
+
|
|
1389
|
+
describe("form animations", () => {
|
|
1390
|
+
function assertValidAnimation(
|
|
1391
|
+
animation,
|
|
1392
|
+
event,
|
|
1393
|
+
classNameAdded,
|
|
1394
|
+
classNameRemoved,
|
|
1395
|
+
) {
|
|
1396
|
+
expect(animation.event).toBe(event);
|
|
1397
|
+
expect(animation.args[1]).toBe(classNameAdded);
|
|
1398
|
+
expect(animation.args[2]).toBe(classNameRemoved);
|
|
1399
|
+
}
|
|
1400
|
+
|
|
1401
|
+
let form;
|
|
1402
|
+
let $animate;
|
|
1403
|
+
let myModule;
|
|
1404
|
+
|
|
1405
|
+
beforeEach(() => {
|
|
1406
|
+
let dummy = window.document.getElementById("dummy");
|
|
1407
|
+
doc = jqLite('<form name="myForm"></form>');
|
|
1408
|
+
jqLite(dummy).append(doc);
|
|
1409
|
+
let angular = new Angular();
|
|
1410
|
+
publishExternalAPI();
|
|
1411
|
+
myModule = window.angular.module("myModule", ["ngAnimate"]);
|
|
1412
|
+
|
|
1413
|
+
injector = angular.bootstrap(dummy, ["myModule"]);
|
|
1414
|
+
injector.invoke((_$compile_, $rootScope, _$animate_) => {
|
|
1415
|
+
$compile = _$compile_;
|
|
1416
|
+
scope = $rootScope.$new();
|
|
1417
|
+
$animate = _$animate_;
|
|
1418
|
+
});
|
|
1419
|
+
form = scope.myForm;
|
|
1420
|
+
});
|
|
1421
|
+
|
|
1422
|
+
afterEach(() => {
|
|
1423
|
+
dealoc(doc);
|
|
1424
|
+
dealoc(dummy);
|
|
1425
|
+
window.document.getElementById("dummy").innerHTML = "";
|
|
1426
|
+
});
|
|
1427
|
+
|
|
1428
|
+
it("should trigger an animation when invalid", (done) => {
|
|
1429
|
+
form.$setValidity("required", false);
|
|
1430
|
+
setTimeout(() => {
|
|
1431
|
+
// assertValidAnimation($animate.queue[0], "removeClass", "ng-valid");
|
|
1432
|
+
// assertValidAnimation($animate.queue[1], "addClass", "ng-invalid");
|
|
1433
|
+
// assertValidAnimation($animate.queue[2], "addClass", "ng-invalid-required");
|
|
1434
|
+
expect(doc[0].classList.contains("ng-valid")).toBeTrue();
|
|
1435
|
+
expect(doc[0].classList.contains("ng-invalid-add")).toBeTrue();
|
|
1436
|
+
expect(doc[0].classList.contains("ng-invalid-required-add")).toBeTrue();
|
|
1437
|
+
done();
|
|
1438
|
+
}, 100);
|
|
1439
|
+
});
|
|
1440
|
+
|
|
1441
|
+
it("should trigger an animation when valid", (done) => {
|
|
1442
|
+
form.$setValidity("required", false);
|
|
1443
|
+
|
|
1444
|
+
form.$setValidity("required", true);
|
|
1445
|
+
|
|
1446
|
+
setTimeout(() => {
|
|
1447
|
+
// assertValidAnimation($animate.queue[0], "addClass", "ng-valid");
|
|
1448
|
+
// assertValidAnimation($animate.queue[1], "removeClass", "ng-invalid");
|
|
1449
|
+
// assertValidAnimation($animate.queue[2], "addClass", "ng-valid-required");
|
|
1450
|
+
expect(doc[0].classList.contains("ng-valid")).toBeTrue();
|
|
1451
|
+
expect(doc[0].classList.contains("ng-invalid-add")).toBeTrue();
|
|
1452
|
+
expect(doc[0].classList.contains("ng-invalid-required-add")).toBeTrue();
|
|
1453
|
+
done();
|
|
1454
|
+
}, 100);
|
|
1455
|
+
});
|
|
1456
|
+
|
|
1457
|
+
it("should trigger an animation when dirty", (done) => {
|
|
1458
|
+
form.$setDirty();
|
|
1459
|
+
setTimeout(() => {
|
|
1460
|
+
// assertValidAnimation($animate.queue[0], "removeClass", "ng-pristine");
|
|
1461
|
+
// assertValidAnimation($animate.queue[1], "addClass", "ng-dirty");
|
|
1462
|
+
expect(doc[0].classList.contains("ng-pristine")).toBeTrue();
|
|
1463
|
+
expect(doc[0].classList.contains("ng-dirty-add")).toBeTrue();
|
|
1464
|
+
done();
|
|
1465
|
+
}, 100);
|
|
1466
|
+
});
|
|
1467
|
+
|
|
1468
|
+
it("should trigger an animation when pristine", (done) => {
|
|
1469
|
+
form.$setDirty();
|
|
1470
|
+
form.$setPristine();
|
|
1471
|
+
setTimeout(() => {
|
|
1472
|
+
// assertValidAnimation($animate.queue[0], "removeClass", "ng-pristine");
|
|
1473
|
+
// assertValidAnimation($animate.queue[1], "addClass", "ng-dirty");
|
|
1474
|
+
expect(doc[0].classList.contains("ng-pristine")).toBeTrue();
|
|
1475
|
+
expect(doc[0].classList.contains("ng-dirty-add")).toBeTrue();
|
|
1476
|
+
done();
|
|
1477
|
+
}, 100);
|
|
1478
|
+
});
|
|
1479
|
+
|
|
1480
|
+
it("should trigger custom errors as addClass/removeClass when invalid/valid", (done) => {
|
|
1481
|
+
form.$setValidity("custom-error", false);
|
|
1482
|
+
|
|
1483
|
+
setTimeout(() => {
|
|
1484
|
+
// assertValidAnimation($animate.queue[0], "removeClass", "ng-valid");
|
|
1485
|
+
// assertValidAnimation($animate.queue[1], "addClass", "ng-invalid");
|
|
1486
|
+
// assertValidAnimation(
|
|
1487
|
+
// $animate.queue[2],
|
|
1488
|
+
// "addClass",
|
|
1489
|
+
// "ng-invalid-custom-error",
|
|
1490
|
+
// );
|
|
1491
|
+
expect(doc[0].classList.contains("ng-valid")).toBeTrue();
|
|
1492
|
+
expect(doc[0].classList.contains("ng-invalid-add")).toBeTrue();
|
|
1493
|
+
expect(
|
|
1494
|
+
doc[0].classList.contains("ng-invalid-custom-error-add"),
|
|
1495
|
+
).toBeTrue();
|
|
1496
|
+
}, 100);
|
|
1497
|
+
|
|
1498
|
+
// $animate.queue = [];
|
|
1499
|
+
form.$setValidity("custom-error", true);
|
|
1500
|
+
|
|
1501
|
+
setTimeout(() => {
|
|
1502
|
+
// assertValidAnimation($animate.queue[0], "removeClass", "ng-valid");
|
|
1503
|
+
// assertValidAnimation($animate.queue[1], "addClass", "ng-invalid");
|
|
1504
|
+
// assertValidAnimation(
|
|
1505
|
+
// $animate.queue[2],
|
|
1506
|
+
// "addClass",
|
|
1507
|
+
// "ng-invalid-custom-error",
|
|
1508
|
+
// );
|
|
1509
|
+
expect(doc[0].classList.contains("ng-valid")).toBeTrue();
|
|
1510
|
+
expect(doc[0].classList.contains("ng-invalid-add")).toBeTrue();
|
|
1511
|
+
expect(
|
|
1512
|
+
doc[0].classList.contains("ng-valid-custom-error-add"),
|
|
1513
|
+
).toBeTrue();
|
|
1514
|
+
done();
|
|
1515
|
+
}, 300);
|
|
1516
|
+
});
|
|
1517
|
+
});
|
|
1518
|
+
});
|