user-invalid.html (11926B)
1 <!doctype html> 2 <title>Support for the :user-invalid pseudo-class</title> 3 <link rel="author" title="Tim Nguyen" href="https://github.com/nt1m"> 4 <link rel="help" href="https://drafts.csswg.org/selectors/#user-pseudos"> 5 <link rel="help" href="https://html.spec.whatwg.org/#selector-user-invalid"> 6 <script src="/resources/testharness.js"></script> 7 <script src="/resources/testharnessreport.js"></script> 8 <script src="/resources/testdriver.js"></script> 9 <script src="/resources/testdriver-vendor.js"></script> 10 11 <style> 12 :is(input:not([type=submit], [type=reset]), textarea) { 13 border: 2px solid black; 14 } 15 16 :is(input:not([type=submit], [type=reset]), textarea):user-valid { 17 border-color: green; 18 } 19 20 :is(input:not([type=submit], [type=reset]), textarea):user-invalid { 21 border-color: red; 22 } 23 </style> 24 25 <input id="initially-invalid" type="email" value="foo"> 26 27 <p>Test form interactions (reset / submit):</p> 28 <form id="form"> 29 <input placeholder="Required field" required id="required-input"><br> 30 <textarea placeholder="Required field" required id="required-textarea"></textarea><br> 31 <input type="checkbox" required id="required-checkbox"><br> 32 <input type="date" required id="required-date"><br> 33 <input type="submit" id="submit-button"> 34 <input type="reset" id="reset-button"> 35 </form> 36 37 <script> 38 promise_test(async () => { 39 const input = document.querySelector("#initially-invalid"); 40 assert_false(input.validity.valid, "Should be invalid"); 41 // The selector can't match because no interaction has happened. 42 assert_false(input.matches(':user-invalid')); 43 44 assert_false(input.matches(":user-valid"), "Initially does not match :user-valid"); 45 assert_false(input.matches(":user-invalid"), "Initially does not match :user-invalid"); 46 47 await test_driver.click(input); 48 input.blur(); 49 50 assert_false(input.matches(":user-valid"), "No change happened, still does not match :user-valid"); 51 assert_false(input.matches(":user-invalid"), "No change happened, still does not match :user-invalid"); 52 53 input.value = "not an email"; 54 55 assert_false(input.matches(":user-valid"), "Programatically set value, :user-valid should not match"); 56 assert_false(input.matches(":user-invalid"), "Programatically set value, :user-invalid should not match"); 57 58 input.value = ""; 59 60 assert_false(input.matches(":user-valid"), "Programatically cleared value, :user-valid should not match"); 61 assert_false(input.matches(":user-invalid"), "Programatically cleared value, :user-invalid should not match"); 62 63 await test_driver.click(input); 64 await test_driver.send_keys(input, "not an email"); 65 input.blur(); 66 67 assert_true(input.matches(":user-invalid"), "Typed an invalid email, :user-invalid now matches"); 68 assert_false(input.matches(":user-valid"), "Typed an invalid email, :user-valid does not match"); 69 70 input.value = ""; 71 await test_driver.click(input); 72 await test_driver.send_keys(input, "test@example.com"); 73 input.blur(); 74 75 assert_true(input.matches(":user-valid"), "Put a valid email, :user-valid now matches"); 76 assert_false(input.matches(":user-invalid"), "Put an valid email, :user-invalid no longer matches"); 77 }, ':user-invalid selector should respond to user action'); 78 79 promise_test(async () => { 80 const form = document.querySelector("#form"); 81 const requiredInput = document.querySelector("#required-input"); 82 const requiredTextarea = document.querySelector("#required-textarea"); 83 const requiredCheckbox = document.querySelector("#required-checkbox"); 84 const requiredDate = document.querySelector("#required-date"); 85 const submitButton = document.querySelector("#submit-button"); 86 const resetButton = document.querySelector("#reset-button"); 87 88 assert_false(requiredInput.validity.valid); 89 assert_false(requiredTextarea.validity.valid); 90 assert_false(requiredCheckbox.validity.valid); 91 assert_false(requiredDate.validity.valid); 92 // The selector can't match because no interaction has happened. 93 assert_false(requiredInput.matches(":user-valid"), "Initially does not match :user-valid"); 94 assert_false(requiredInput.matches(":user-invalid"), "Initially does not match :user-invalid"); 95 96 assert_false(requiredTextarea.matches(":user-valid"), "Initially does not match :user-valid"); 97 assert_false(requiredTextarea.matches(":user-invalid"), "Initially does not match :user-invalid"); 98 99 assert_false(requiredCheckbox.matches(":user-valid"), "Initially does not match :user-valid"); 100 assert_false(requiredCheckbox.matches(":user-invalid"), "Initially does not match :user-invalid"); 101 102 assert_false(requiredDate.matches(":user-valid"), "Initially does not match :user-valid"); 103 assert_false(requiredDate.matches(":user-invalid"), "Initially does not match :user-invalid"); 104 105 submitButton.click(); 106 107 assert_true(requiredInput.matches(":user-invalid"), "Submitted the form, input is validated"); 108 assert_false(requiredInput.matches(":user-valid"), "Submitted the form, input is validated"); 109 110 assert_true(requiredTextarea.matches(":user-invalid"), "Submitted the form, textarea is validated"); 111 assert_false(requiredTextarea.matches(":user-valid"), "Submitted the form, textarea is validated"); 112 113 assert_true(requiredCheckbox.matches(":user-invalid"), "Submitted the form, checkbox is validated"); 114 assert_false(requiredCheckbox.matches(":user-valid"), "Submitted the form, checkbox is validated"); 115 116 assert_true(requiredDate.matches(":user-invalid"), "Submitted the form, date input is validated"); 117 assert_false(requiredDate.matches(":user-valid"), "Submitted the form, date input is validated"); 118 119 resetButton.click(); 120 121 assert_false(requiredInput.matches(":user-valid"), "Reset the form, user-interacted flag is reset"); 122 assert_false(requiredInput.matches(":user-invalid"), "Reset the form, user-interacted flag is reset"); 123 124 assert_false(requiredTextarea.matches(":user-valid"), "Reset the form, user-interacted flag is reset"); 125 assert_false(requiredTextarea.matches(":user-invalid"), "Reset the form, user-interacted flag is reset"); 126 127 assert_false(requiredCheckbox.matches(":user-valid"), "Reset the form, user-interacted flag is reset"); 128 assert_false(requiredCheckbox.matches(":user-invalid"), "Reset the form, user-interacted flag is reset"); 129 130 assert_false(requiredDate.matches(":user-valid"), "Reset the form, user-interacted flag is reset"); 131 assert_false(requiredDate.matches(":user-invalid"), "Reset the form, user-interacted flag is reset"); 132 133 // Test programmatic form submission with constraint validation. 134 form.requestSubmit(); 135 136 assert_true(requiredInput.matches(":user-invalid"), "Called form.requestSubmit(), input is validated"); 137 assert_false(requiredInput.matches(":user-valid"), "Called form.requestSubmit(), input is validated"); 138 139 assert_true(requiredTextarea.matches(":user-invalid"), "Called form.requestSubmit(), textarea is validated"); 140 assert_false(requiredTextarea.matches(":user-valid"), "Called form.requestSubmit(), textarea is validated"); 141 142 assert_true(requiredCheckbox.matches(":user-invalid"), "Called form.requestSubmit(), checkbox is validated"); 143 assert_false(requiredCheckbox.matches(":user-valid"), "Called form.requestSubmit(), checkbox is validated"); 144 145 assert_true(requiredDate.matches(":user-invalid"), "Called form.requestSubmit(), date input is validated"); 146 assert_false(requiredDate.matches(":user-valid"), "Called form.requestSubmit(), date input is validated"); 147 }, ":user-invalid selector properly interacts with submit & reset buttons"); 148 149 // historical: https://github.com/w3c/csswg-drafts/issues/1329 150 test(() => { 151 const input = document.querySelector('input'); 152 // matches() will throw if the selector isn't suppported 153 assert_throws_dom("SyntaxError", () => input.matches(':user-error')); 154 }, ':user-error selector should not be supported'); 155 156 ['required-input', 'required-textarea'].forEach(elementId => { 157 promise_test(async () => { 158 const resetButton = document.getElementById('reset-button'); 159 const element = document.getElementById(elementId); 160 161 element.value = ''; 162 resetButton.click(); 163 assert_false(element.matches(':user-invalid'), 164 'Element should not match :user-invalid at the start of the test.'); 165 assert_false(element.matches(':user-valid'), 166 'Element should not match :user-valid at the start of the test.'); 167 168 const backspace = '\uE003'; 169 element.focus(); 170 await test_driver.send_keys(element, 'a'); 171 await test_driver.send_keys(element, backspace); 172 assert_false(element.matches(':user-invalid'), 173 'Element should not match :user-invalid before blurring.'); 174 assert_false(element.matches(':user-valid'), 175 'Element should not match :user-valid before blurring.'); 176 177 element.blur(); 178 assert_true(element.matches(':user-invalid'), 179 'Element should match :user-invalid after typing text and deleting it.'); 180 assert_false(element.matches(':user-valid'), 181 'Element should not match :user-valid after the test.'); 182 }, `${elementId}: A required input or textarea should match :user-invalid if a user types into it and then clears it before blurring.`); 183 }); 184 185 promise_test(async () => { 186 const checkbox = document.getElementById('required-checkbox'); 187 188 const resetButton = document.getElementById('reset-button'); 189 resetButton.click(); 190 assert_false(checkbox.matches(':user-invalid'), 191 'Checkbox should not match :user-invalid at the start of the test.'); 192 assert_false(checkbox.checked, 193 'Checkbox should not be checked at the start of the test.'); 194 195 checkbox.checked = true; 196 assert_false(checkbox.matches(':user-invalid'), 197 'Checkbox should not match :user-invalid after programatically changing value.'); 198 checkbox.checked = false; 199 assert_false(checkbox.matches(':user-invalid'), 200 'Checkbox should not match :user-invalid after programatically changing value.'); 201 202 await test_driver.click(checkbox); 203 assert_true(checkbox.checked, 'Checkbox should be checked after clicking once.'); 204 assert_false(checkbox.matches(':user-invalid'), 205 'Checkbox should not match :user-invalid after checking it.'); 206 await test_driver.click(checkbox); 207 assert_false(checkbox.checked, 'Checkbox should not be checked after clicking twice.'); 208 assert_true(checkbox.matches(':user-invalid'), 209 'Checkbox should match :user-invalid after clicking twice.'); 210 }, 'A required checkbox should match :user-invalid if the user unchecks it and blurs.'); 211 212 promise_test(async () => { 213 const date = document.getElementById('required-date'); 214 215 const resetButton = document.getElementById('reset-button'); 216 resetButton.click(); 217 assert_false(date.matches(':user-invalid'), 218 'date input should not match :user-invalid at the start of the test.'); 219 assert_equals(date.value, '', 220 'date input should not have a value at the start of the test.'); 221 222 date.value = '2024-04-15'; 223 assert_false(date.matches(':user-invalid'), 224 'date should not match :user-invalid after programatically changing value.'); 225 date.value = ''; 226 assert_false(date.matches(':user-invalid'), 227 'date should not match :user-invalid after programatically changing value.'); 228 229 const tabKey = '\uE004'; 230 const backspace = '\uE003'; 231 date.focus(); 232 // Press tab twice at the end to make sure that focus has left the input. 233 await test_driver.send_keys(date, `1${tabKey}1${tabKey}1234${tabKey}${tabKey}`); 234 assert_not_equals(document.activeElement, date, 235 'Pressing tab twice after typing in the date should have blurred the input.'); 236 assert_equals(date.value, '1234-01-01', 237 'Date input value should match the testdriver input.'); 238 date.focus(); 239 await test_driver.send_keys(date, backspace); 240 assert_equals(date.value, '', 241 'Date input value should be cleared when deleting one of the sub-values.'); 242 assert_true(date.matches(':user-invalid'), 243 'Date input should match :user-invalid after typing in an invalid value.'); 244 }, 'A required date should match :user-invalid if the user unchecks it and blurs.'); 245 </script>