summaryrefslogtreecommitdiffstats
path: root/testing/web-platform/tests/css/selectors/user-invalid.html
blob: 5f8fa5811aff1b0cf333f5dc5bd173c6507b8810 (plain)
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
<!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>
  <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 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);
  // 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(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");

  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(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");

  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(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");

  // 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(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");
}, ":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.');
</script>