-
Notifications
You must be signed in to change notification settings - Fork 3.7k
Expand file tree
/
Copy pathuser-invalid.html
More file actions
355 lines (285 loc) · 18.9 KB
/
user-invalid.html
File metadata and controls
355 lines (285 loc) · 18.9 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
<!doctype html>
<title>Support for the :user-invalid pseudo-class</title>
<link rel="author" title="Tim Nguyen" href="https://github.com/nt1m">
<link rel="help" href="https://drafts.csswg.org/selectors/#user-pseudos">
<link rel="help" href="https://html.spec.whatwg.org/#selector-user-invalid">
<script src="/resources/testharness.js"></script>
<script src="/resources/testharnessreport.js"></script>
<script src="/resources/testdriver.js"></script>
<script src="/resources/testdriver-vendor.js"></script>
<style>
:is(input:not([type=submit], [type=reset]), textarea) {
border: 2px solid black;
}
:is(input:not([type=submit], [type=reset]), textarea):user-valid {
border-color: green;
}
:is(input:not([type=submit], [type=reset]), textarea):user-invalid {
border-color: red;
}
</style>
<input id="initially-invalid" type="email" value="foo">
<p>Test form interactions (reset / submit):</p>
<form id="form">
<input placeholder="Required field" required id="required-input"><br>
<textarea placeholder="Required field" required id="required-textarea"></textarea><br>
<input type="checkbox" required id="required-checkbox"><br>
<!-- Radio buttons with the same name will also have the same state of :user-valid and :user-invalid -->
<input type="radio" required id="required-radio" name="required-radio"><br>
<input type="radio" required id="required-radio-same-name" name="required-radio"><br>
<input type="radio" required id="required-radio-different-name" name="required-radio-different-name"><br>
<input type="date" required id="required-date"><br>
<input type="submit" id="submit-button">
<input type="reset" id="reset-button">
</form>
<script>
promise_test(async () => {
const input = document.querySelector("#initially-invalid");
assert_false(input.validity.valid, "Should be invalid");
// The selector can't match because no interaction has happened.
assert_false(input.matches(':user-invalid'));
assert_false(input.matches(":user-valid"), "Initially does not match :user-valid");
assert_false(input.matches(":user-invalid"), "Initially does not match :user-invalid");
await test_driver.click(input);
input.blur();
assert_false(input.matches(":user-valid"), "No change happened, still does not match :user-valid");
assert_false(input.matches(":user-invalid"), "No change happened, still does not match :user-invalid");
input.value = "not an email";
assert_false(input.matches(":user-valid"), "Programatically set value, :user-valid should not match");
assert_false(input.matches(":user-invalid"), "Programatically set value, :user-invalid should not match");
input.value = "";
assert_false(input.matches(":user-valid"), "Programatically cleared value, :user-valid should not match");
assert_false(input.matches(":user-invalid"), "Programatically cleared value, :user-invalid should not match");
await test_driver.click(input);
await test_driver.send_keys(input, "not an email");
input.blur();
assert_true(input.matches(":user-invalid"), "Typed an invalid email, :user-invalid now matches");
assert_false(input.matches(":user-valid"), "Typed an invalid email, :user-valid does not match");
input.value = "";
await test_driver.click(input);
await test_driver.send_keys(input, "test@example.com");
input.blur();
assert_true(input.matches(":user-valid"), "Put a valid email, :user-valid now matches");
assert_false(input.matches(":user-invalid"), "Put an valid email, :user-invalid no longer matches");
}, ':user-invalid selector should respond to user action');
promise_test(async () => {
const form = document.querySelector("#form");
const requiredInput = document.querySelector("#required-input");
const requiredTextarea = document.querySelector("#required-textarea");
const requiredCheckbox = document.querySelector("#required-checkbox");
const requiredRadio = document.querySelector("#required-radio");
const requiredRadioWithSameName = document.querySelector("#required-radio-same-name");
const requiredRadioWithDifferentName = document.querySelector("#required-radio-different-name");
const requiredDate = document.querySelector("#required-date");
const submitButton = document.querySelector("#submit-button");
const resetButton = document.querySelector("#reset-button");
assert_false(requiredInput.validity.valid);
assert_false(requiredTextarea.validity.valid);
assert_false(requiredCheckbox.validity.valid);
assert_false(requiredRadio.validity.valid);
assert_false(requiredRadioWithSameName.validity.valid);
assert_false(requiredRadioWithDifferentName.validity.valid);
assert_false(requiredDate.validity.valid);
// The selector can't match because no interaction has happened.
assert_false(requiredInput.matches(":user-valid"), "Initially does not match :user-valid");
assert_false(requiredInput.matches(":user-invalid"), "Initially does not match :user-invalid");
assert_false(requiredRadio.matches(":user-valid"), "Initially does not match :user-valid");
assert_false(requiredRadio.matches(":user-invalid"), "Initially does not match :user-invalid");
assert_false(requiredRadioWithSameName.matches(":user-valid"), "Initially does not match :user-valid");
assert_false(requiredRadioWithSameName.matches(":user-invalid"), "Initially does not match :user-invalid");
assert_false(requiredRadioWithDifferentName.matches(":user-valid"), "Initially does not match :user-valid");
assert_false(requiredRadioWithDifferentName.matches(":user-invalid"), "Initially does not match :user-invalid");
assert_false(requiredTextarea.matches(":user-valid"), "Initially does not match :user-valid");
assert_false(requiredTextarea.matches(":user-invalid"), "Initially does not match :user-invalid");
assert_false(requiredCheckbox.matches(":user-valid"), "Initially does not match :user-valid");
assert_false(requiredCheckbox.matches(":user-invalid"), "Initially does not match :user-invalid");
assert_false(requiredDate.matches(":user-valid"), "Initially does not match :user-valid");
assert_false(requiredDate.matches(":user-invalid"), "Initially does not match :user-invalid");
submitButton.click();
assert_true(requiredInput.matches(":user-invalid"), "Submitted the form, input is validated");
assert_false(requiredInput.matches(":user-valid"), "Submitted the form, input is validated");
assert_true(requiredRadio.matches(":user-invalid"), "Submitted the form, radio is validated");
assert_false(requiredRadio.matches(":user-valid"), "Submitted the form, radio is validated");
assert_true(requiredRadioWithSameName.matches(":user-invalid"), "Submitted the form, radio is validated");
assert_false(requiredRadioWithSameName.matches(":user-valid"), "Submitted the form, radio is validated");
assert_true(requiredRadioWithDifferentName.matches(":user-invalid"), "Submitted the form, radio is validated");
assert_false(requiredRadioWithDifferentName.matches(":user-valid"), "Submitted the form, radio is validated");
assert_true(requiredTextarea.matches(":user-invalid"), "Submitted the form, textarea is validated");
assert_false(requiredTextarea.matches(":user-valid"), "Submitted the form, textarea is validated");
assert_true(requiredCheckbox.matches(":user-invalid"), "Submitted the form, checkbox is validated");
assert_false(requiredCheckbox.matches(":user-valid"), "Submitted the form, checkbox is validated");
assert_true(requiredDate.matches(":user-invalid"), "Submitted the form, date input is validated");
assert_false(requiredDate.matches(":user-valid"), "Submitted the form, date input is validated");
resetButton.click();
assert_false(requiredInput.matches(":user-valid"), "Reset the form, user-interacted flag is reset");
assert_false(requiredInput.matches(":user-invalid"), "Reset the form, user-interacted flag is reset");
assert_false(requiredRadio.matches(":user-valid"), "Reset the form, user-interacted flag is reset");
assert_false(requiredRadio.matches(":user-invalid"), "Reset the form, user-interacted flag is reset");
assert_false(requiredRadioWithSameName.matches(":user-valid"), "Reset the form, user-interacted flag is reset");
assert_false(requiredRadioWithSameName.matches(":user-invalid"), "Reset the form, user-interacted flag is reset");
assert_false(requiredRadioWithDifferentName.matches(":user-valid"), "Reset the form, user-interacted flag is reset");
assert_false(requiredRadioWithDifferentName.matches(":user-invalid"), "Reset the form, user-interacted flag is reset");
assert_false(requiredTextarea.matches(":user-valid"), "Reset the form, user-interacted flag is reset");
assert_false(requiredTextarea.matches(":user-invalid"), "Reset the form, user-interacted flag is reset");
assert_false(requiredCheckbox.matches(":user-valid"), "Reset the form, user-interacted flag is reset");
assert_false(requiredCheckbox.matches(":user-invalid"), "Reset the form, user-interacted flag is reset");
assert_false(requiredDate.matches(":user-valid"), "Reset the form, user-interacted flag is reset");
assert_false(requiredDate.matches(":user-invalid"), "Reset the form, user-interacted flag is reset");
// Test programmatic form submission with constraint validation.
form.requestSubmit();
assert_true(requiredInput.matches(":user-invalid"), "Called form.requestSubmit(), input is validated");
assert_false(requiredInput.matches(":user-valid"), "Called form.requestSubmit(), input is validated");
assert_true(requiredRadio.matches(":user-invalid"), "Called form.requestSubmit(), radio is validated");
assert_false(requiredRadio.matches(":user-valid"), "Called form.requestSubmit(), radio is validated");
assert_true(requiredRadioWithSameName.matches(":user-invalid"), "Called form.requestSubmit(), radio is validated");
assert_false(requiredRadioWithSameName.matches(":user-valid"), "Called form.requestSubmit(), radio is validated");
assert_true(requiredRadioWithDifferentName.matches(":user-invalid"), "Called form.requestSubmit(), radio is validated");
assert_false(requiredRadioWithDifferentName.matches(":user-valid"), "Called form.requestSubmit(), radio is validated");
assert_true(requiredTextarea.matches(":user-invalid"), "Called form.requestSubmit(), textarea is validated");
assert_false(requiredTextarea.matches(":user-valid"), "Called form.requestSubmit(), textarea is validated");
assert_true(requiredCheckbox.matches(":user-invalid"), "Called form.requestSubmit(), checkbox is validated");
assert_false(requiredCheckbox.matches(":user-valid"), "Called form.requestSubmit(), checkbox is validated");
assert_true(requiredDate.matches(":user-invalid"), "Called form.requestSubmit(), date input is validated");
assert_false(requiredDate.matches(":user-valid"), "Called form.requestSubmit(), date input is validated");
}, ":user-invalid selector properly interacts with submit & reset buttons");
// historical: https://github.com/w3c/csswg-drafts/issues/1329
test(() => {
const input = document.querySelector('input');
// matches() will throw if the selector isn't suppported
assert_throws_dom("SyntaxError", () => input.matches(':user-error'));
}, ':user-error selector should not be supported');
['required-input', 'required-textarea'].forEach(elementId => {
promise_test(async () => {
const resetButton = document.getElementById('reset-button');
const element = document.getElementById(elementId);
element.value = '';
resetButton.click();
assert_false(element.matches(':user-invalid'),
'Element should not match :user-invalid at the start of the test.');
assert_false(element.matches(':user-valid'),
'Element should not match :user-valid at the start of the test.');
const backspace = '\uE003';
element.focus();
await test_driver.send_keys(element, 'a');
await test_driver.send_keys(element, backspace);
assert_false(element.matches(':user-invalid'),
'Element should not match :user-invalid before blurring.');
assert_false(element.matches(':user-valid'),
'Element should not match :user-valid before blurring.');
element.blur();
assert_true(element.matches(':user-invalid'),
'Element should match :user-invalid after typing text and deleting it.');
assert_false(element.matches(':user-valid'),
'Element should not match :user-valid after the test.');
}, `${elementId}: A required input or textarea should match :user-invalid if a user types into it and then clears it before blurring.`);
});
promise_test(async () => {
const checkbox = document.getElementById('required-checkbox');
const resetButton = document.getElementById('reset-button');
resetButton.click();
assert_false(checkbox.matches(':user-invalid'),
'Checkbox should not match :user-invalid at the start of the test.');
assert_false(checkbox.checked,
'Checkbox should not be checked at the start of the test.');
checkbox.checked = true;
assert_false(checkbox.matches(':user-invalid'),
'Checkbox should not match :user-invalid after programatically changing value.');
checkbox.checked = false;
assert_false(checkbox.matches(':user-invalid'),
'Checkbox should not match :user-invalid after programatically changing value.');
await test_driver.click(checkbox);
assert_true(checkbox.checked, 'Checkbox should be checked after clicking once.');
assert_false(checkbox.matches(':user-invalid'),
'Checkbox should not match :user-invalid after checking it.');
await test_driver.click(checkbox);
assert_false(checkbox.checked, 'Checkbox should not be checked after clicking twice.');
assert_true(checkbox.matches(':user-invalid'),
'Checkbox should match :user-invalid after clicking twice.');
}, 'A required checkbox should match :user-invalid if the user unchecks it and blurs.');
promise_test(async () => {
const radio = document.getElementById('required-radio');
const radioWithSameName = document.getElementById('required-radio-same-name');
const radioWithDifferentName = document.getElementById('required-radio-different-name');
const submitButton = document.getElementById("submit-button");
const resetButton = document.getElementById('reset-button');
resetButton.click();
assert_false(radio.matches(':user-invalid'),
'Radio should not match :user-invalid at the start of the test.');
assert_false(radioWithSameName.matches(':user-invalid'),
'Radio should not match :user-invalid at the start of the test.');
assert_false(radioWithDifferentName.matches(':user-invalid'),
'Radio should not match :user-invalid at the start of the test.');
assert_false(radio.checked,
'Radio should not be checked at the start of the test.');
assert_false(radioWithSameName.checked,
'Radio should not be checked at the start of the test.');
assert_false(radioWithDifferentName.checked,
'Radio should not be checked at the start of the test.');
radio.checked = true;
assert_false(radio.matches(':user-invalid'),
'Radio should not match :user-invalid after programatically changing value.');
assert_false(radioWithSameName.matches(':user-invalid'),
'Radio should not match :user-invalid after programatically changing value.');
assert_false(radioWithDifferentName.matches(':user-invalid'),
'Radio should not match :user-invalid after programatically changing value.');
radio.checked = false;
assert_false(radio.matches(':user-invalid'),
'Radio should not match :user-invalid after programatically changing value.');
assert_false(radioWithSameName.matches(':user-invalid'),
'Radio should not match :user-invalid after programatically changing value.');
assert_false(radioWithDifferentName.matches(':user-invalid'),
'Radio should not match :user-invalid after programatically changing value.');
await test_driver.click(radio);
assert_true(radio.checked, 'Radio should be checked after clicking once.');
assert_false(radio.matches(':user-invalid'),
'Radio should not match :user-invalid after checking it.');
// Test radios with the same name and different name
resetButton.click();
submitButton.click();
assert_true(radio.matches(":user-invalid"), "Submitted the form, radio is validated");
assert_false(radio.matches(":user-valid"), "Submitted the form, radio is validated");
assert_true(radioWithSameName.matches(":user-invalid"), "Submitted the form, radioWithSameName is validated");
assert_false(radioWithSameName.matches(":user-valid"), "Submitted the form, radioWithSameName is validated");
assert_true(radioWithDifferentName.matches(":user-invalid"), "Submitted the form, radioWithDifferentName is validated");
assert_false(radioWithDifferentName.matches(":user-valid"), "Submitted the form, radioWithDifferentName is validated");
await test_driver.click(radioWithSameName);
assert_false(radioWithSameName.matches(":user-invalid"), "Submitted the form, radioWithSameName is validated");
assert_true(radioWithSameName.matches(":user-valid"), "Submitted the form, radioWithSameName is validated");
// Since radio has the same name as radioWithSameName, it should be has the same validation result as radioWithSameName.
assert_false(radio.matches(":user-invalid"), "Submitted the form, radio is validated");
assert_true(radio.matches(":user-valid"), "Submitted the form, radio is validated");
// Since radioWithDifferentName has a different name, its validation result won't change.
assert_true(radioWithDifferentName.matches(":user-invalid"), "Submitted the form, radioWithDifferentName is validated");
assert_false(radioWithDifferentName.matches(":user-valid"), "Submitted the form, radioWithDifferentName is validated");
}, 'A required radio should match :user-invalid if it is not checked.');
promise_test(async () => {
const date = document.getElementById('required-date');
const resetButton = document.getElementById('reset-button');
resetButton.click();
assert_false(date.matches(':user-invalid'),
'date input should not match :user-invalid at the start of the test.');
assert_equals(date.value, '',
'date input should not have a value at the start of the test.');
date.value = '2024-04-15';
assert_false(date.matches(':user-invalid'),
'date should not match :user-invalid after programatically changing value.');
date.value = '';
assert_false(date.matches(':user-invalid'),
'date should not match :user-invalid after programatically changing value.');
const tabKey = '\uE004';
const backspace = '\uE003';
date.focus();
// Press tab twice at the end to make sure that focus has left the input.
await test_driver.send_keys(date, `1${tabKey}1${tabKey}1234${tabKey}${tabKey}`);
assert_not_equals(document.activeElement, date,
'Pressing tab twice after typing in the date should have blurred the input.');
assert_equals(date.value, '1234-01-01',
'Date input value should match the testdriver input.');
date.focus();
await test_driver.send_keys(date, backspace);
assert_equals(date.value, '',
'Date input value should be cleared when deleting one of the sub-values.');
assert_true(date.matches(':user-invalid'),
'Date input should match :user-invalid after typing in an invalid value.');
}, 'A required date should match :user-invalid if the user unchecks it and blurs.');
</script>