summaryrefslogtreecommitdiffstats
path: root/testing/web-platform/tests/custom-elements/form-associated/ElementInternals-validation.html
diff options
context:
space:
mode:
Diffstat (limited to 'testing/web-platform/tests/custom-elements/form-associated/ElementInternals-validation.html')
-rw-r--r--testing/web-platform/tests/custom-elements/form-associated/ElementInternals-validation.html356
1 files changed, 356 insertions, 0 deletions
diff --git a/testing/web-platform/tests/custom-elements/form-associated/ElementInternals-validation.html b/testing/web-platform/tests/custom-elements/form-associated/ElementInternals-validation.html
new file mode 100644
index 0000000000..8114a5dbd8
--- /dev/null
+++ b/testing/web-platform/tests/custom-elements/form-associated/ElementInternals-validation.html
@@ -0,0 +1,356 @@
+<!DOCTYPE html>
+<title>Form validation features of ElementInternals, and :valid :invalid pseudo classes</title>
+<body>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<div id="container"></div>
+<script>
+class MyControl extends HTMLElement {
+ static get formAssociated() { return true; }
+
+ constructor() {
+ super();
+ this.internals_ = this.attachInternals();
+ }
+ get i() { return this.internals_; }
+}
+customElements.define('my-control', MyControl);
+
+class NotFormAssociatedElement extends HTMLElement {
+ constructor() {
+ super();
+ this.internals_ = this.attachInternals();
+ }
+ get i() { return this.internals_; }
+}
+customElements.define('not-form-associated-element', NotFormAssociatedElement);
+
+test(() => {
+ const control = new MyControl();
+ assert_true(control.i.willValidate, 'default value is true');
+
+ const datalist = document.createElement('datalist');
+ datalist.appendChild(control);
+ assert_false(control.i.willValidate, 'false in DATALIST');
+ datalist.removeChild(control);
+ assert_true(control.i.willValidate, 'remove from DATALIST');
+
+ const fieldset = document.createElement('fieldset');
+ fieldset.disabled = true;
+ fieldset.appendChild(control);
+ assert_false(control.i.willValidate, 'append to disabled FIELDSET');
+ fieldset.disabled = false;
+ assert_true(control.i.willValidate, 'FIELDSET becomes enabled');
+ fieldset.disabled = true;
+ assert_false(control.i.willValidate, 'FIELDSET becomes disabled');
+ fieldset.removeChild(control);
+ assert_true(control.i.willValidate, 'remove from disabled FIELDSET');
+
+ control.setAttribute('disabled', '');
+ assert_false(control.i.willValidate, 'with disabled attribute');
+ control.removeAttribute('disabled');
+ assert_true(control.i.willValidate, 'without disabled attribute');
+
+ control.setAttribute('readonly', '');
+ assert_false(control.i.willValidate, 'with readonly attribute');
+ control.removeAttribute('readonly');
+ assert_true(control.i.willValidate, 'without readonly attribute');
+}, 'willValidate');
+
+test(() => {
+ const container = document.getElementById("container");
+ container.innerHTML = '<will-be-defined></will-be-defined>' +
+ '<will-be-defined disabled></will-be-defined>' +
+ '<will-be-defined readonly></will-be-defined>' +
+ '<datalist><will-be-defined></will-be-defined></datalist>' +
+ '<fieldset disabled><will-be-defined></will-be-defined></fieldset>';
+
+ class WillBeDefined extends HTMLElement {
+ static get formAssociated() { return true; }
+
+ constructor() {
+ super();
+ this.internals_ = this.attachInternals();
+ }
+ get i() { return this.internals_; }
+ }
+ customElements.define('will-be-defined', WillBeDefined);
+ customElements.upgrade(container);
+
+ const controls = document.querySelectorAll('will-be-defined');
+ assert_true(controls[0].i.willValidate, 'default value');
+ assert_false(controls[1].i.willValidate, 'with disabled attribute');
+ assert_false(controls[2].i.willValidate, 'with readOnly attribute');
+ assert_false(controls[3].i.willValidate, 'in datalist');
+ assert_false(controls[4].i.willValidate, 'in disabled fieldset');
+}, 'willValidate after upgrade');
+
+test(t => {
+ const control = document.createElement('will-be-defined-2');
+
+ customElements.define('will-be-defined-2', class extends HTMLElement {
+ static get formAssociated() { return true; }
+ });
+
+ container.append(control);
+ t.add_cleanup(() => { container.innerHTML = ''; });
+
+ const i = control.attachInternals();
+ assert_true(i.willValidate);
+}, 'willValidate after upgrade (document.createElement)');
+
+test(() => {
+ const element = new NotFormAssociatedElement();
+ assert_throws_dom('NotSupportedError', () => element.i.willValidate);
+}, "willValidate should throw NotSupportedError if the target element is not a form-associated custom element");
+
+test(() => {
+ const element = new NotFormAssociatedElement();
+ assert_throws_dom('NotSupportedError', () => element.i.validity);
+}, "validity should throw NotSupportedError if the target element is not a form-associated custom element");
+
+test(() => {
+ const element = new NotFormAssociatedElement();
+ assert_throws_dom('NotSupportedError', () => element.i.validationMessage);
+}, "validationMessage should throw NotSupportedError if the target element is not a form-associated custom element");
+
+test(() => {
+ const element = new NotFormAssociatedElement();
+ assert_throws_dom('NotSupportedError', () => element.i.setValidity());
+}, "setValidity() should throw NotSupportedError if the target element is not a form-associated custom element");
+
+test(() => {
+ const control = document.createElement('my-control');
+ const validity = control.i.validity;
+ assert_false(validity.valueMissing, 'default valueMissing');
+ assert_false(validity.typeMismatch, 'default typeMismatch');
+ assert_false(validity.patternMismatch, 'default patternMismatch');
+ assert_false(validity.tooLong, 'default tooLong');
+ assert_false(validity.tooShort, 'default tooShort');
+ assert_false(validity.rangeUnderflow, 'default rangeUnderflow');
+ assert_false(validity.rangeOverflow, 'default rangeOverflow');
+ assert_false(validity.stepMismatch, 'default stepMismatch');
+ assert_false(validity.badInput, 'default badInput');
+ assert_false(validity.customError, 'default customError');
+ assert_true(validity.valid, 'default valid');
+
+ control.i.setValidity({valueMissing: true}, 'valueMissing message');
+ assert_true(validity.valueMissing);
+ assert_false(validity.valid);
+ assert_equals(control.i.validationMessage, 'valueMissing message');
+
+ control.i.setValidity({typeMismatch: true}, 'typeMismatch message');
+ assert_true(validity.typeMismatch);
+ assert_false(validity.valueMissing);
+ assert_false(validity.valid);
+ assert_equals(control.i.validationMessage, 'typeMismatch message');
+
+ control.i.setValidity({patternMismatch: true}, 'patternMismatch message');
+ assert_true(validity.patternMismatch);
+ assert_false(validity.valid);
+ assert_equals(control.i.validationMessage, 'patternMismatch message');
+
+ control.i.setValidity({tooLong: true}, 'tooLong message');
+ assert_true(validity.tooLong);
+ assert_false(validity.valid);
+ assert_equals(control.i.validationMessage, 'tooLong message');
+
+ control.i.setValidity({tooShort: true}, 'tooShort message');
+ assert_true(validity.tooShort);
+ assert_false(validity.valid);
+ assert_equals(control.i.validationMessage, 'tooShort message');
+
+ control.i.setValidity({rangeUnderflow: true}, 'rangeUnderflow message');
+ assert_true(validity.rangeUnderflow);
+ assert_false(validity.valid);
+ assert_equals(control.i.validationMessage, 'rangeUnderflow message');
+
+ control.i.setValidity({rangeOverflow: true}, 'rangeOverflow message');
+ assert_true(validity.rangeOverflow);
+ assert_false(validity.valid);
+ assert_equals(control.i.validationMessage, 'rangeOverflow message');
+
+ control.i.setValidity({stepMismatch: true}, 'stepMismatch message');
+ assert_true(validity.stepMismatch);
+ assert_false(validity.valid);
+ assert_equals(control.i.validationMessage, 'stepMismatch message');
+
+ control.i.setValidity({badInput: true}, 'badInput message');
+ assert_true(validity.badInput);
+ assert_false(validity.valid);
+ assert_equals(control.i.validationMessage, 'badInput message');
+
+ control.i.setValidity({customError: true}, 'customError message');
+ assert_true(validity.customError, 'customError should be true.');
+ assert_false(validity.valid);
+ assert_equals(control.i.validationMessage, 'customError message');
+
+ // Set multiple flags
+ control.i.setValidity({badInput: true, customError: true}, 'multiple errors');
+ assert_true(validity.badInput);
+ assert_true(validity.customError);
+ assert_false(validity.valid);
+ assert_equals(control.i.validationMessage, 'multiple errors');
+
+ // Clear flags
+ control.i.setValidity({}, 'unnecessary message');
+ assert_false(validity.badInput);
+ assert_false(validity.customError);
+ assert_true(validity.valid);
+ assert_equals(control.i.validationMessage, '');
+
+ assert_throws_js(TypeError, () => { control.i.setValidity({valueMissing: true}); },
+ 'setValidity() requires the second argument if the first argument contains true');
+}, 'validity and setValidity()');
+
+test(() => {
+ document.body.insertAdjacentHTML('afterbegin', '<my-control><light-child></my-control>');
+ let control = document.body.firstChild;
+ const flags = {valueMissing: true};
+ const m = 'non-empty message';
+
+ assert_throws_dom('NotFoundError', () => {
+ control.i.setValidity(flags, m, document.body);
+ }, 'Not a descendant');
+
+ assert_throws_dom('NotFoundError', () => {
+ control.i.setValidity(flags, m, control);
+ }, 'Self');
+
+ let notHTMLElement = document.createElementNS('some-random-namespace', 'foo');
+ control.appendChild(notHTMLElement);
+ assert_throws_js(TypeError, () => {
+ control.i.setValidity(flags, m, notHTMLElement);
+ }, 'Not a HTMLElement');
+ notHTMLElement.remove();
+
+ // A descendant
+ control.i.setValidity(flags, m, control.querySelector('light-child'));
+
+ // An element in the direct shadow tree
+ let shadow1 = control.attachShadow({mode: 'open'});
+ let shadowChild = document.createElement('div');
+ shadow1.appendChild(shadowChild);
+ control.i.setValidity(flags, m, shadowChild);
+
+ // An element in an nested shadow trees
+ let shadow2 = shadowChild.attachShadow({mode: 'closed'});
+ let nestedShadowChild = document.createElement('span');
+ shadow2.appendChild(nestedShadowChild);
+ control.i.setValidity(flags, m, nestedShadowChild);
+}, '"anchor" argument of setValidity()');
+
+test(() => {
+ const element = new NotFormAssociatedElement();
+ assert_throws_dom('NotSupportedError', () => element.i.checkValidity());
+}, "checkValidity() should throw NotSupportedError if the target element is not a form-associated custom element");
+
+test(() => {
+ const control = document.createElement('my-control');
+ let invalidCount = 0;
+ control.addEventListener('invalid', e => {
+ assert_equals(e.target, control);
+ assert_true(e.cancelable);
+ ++invalidCount;
+ });
+
+ assert_true(control.i.checkValidity(), 'default state');
+ assert_equals(invalidCount, 0);
+
+ control.i.setValidity({customError:true}, 'foo');
+ assert_false(control.i.checkValidity());
+ assert_equals(invalidCount, 1);
+}, 'checkValidity()');
+
+test(() => {
+ const element = new NotFormAssociatedElement();
+ assert_throws_dom('NotSupportedError', () => element.i.reportValidity());
+}, "reportValidity() should throw NotSupportedError if the target element is not a form-associated custom element");
+
+test(() => {
+ const control = document.createElement('my-control');
+ document.body.appendChild(control);
+ control.tabIndex = 0;
+ let invalidCount = 0;
+ control.addEventListener('invalid', e => {
+ assert_equals(e.target, control);
+ assert_true(e.cancelable);
+ ++invalidCount;
+ });
+
+ assert_true(control.i.reportValidity(), 'default state');
+ assert_equals(invalidCount, 0);
+
+ control.i.setValidity({customError:true}, 'foo');
+ assert_false(control.i.reportValidity());
+ assert_equals(invalidCount, 1);
+
+ control.blur();
+ control.addEventListener('invalid', e => e.preventDefault());
+ assert_false(control.i.reportValidity());
+}, 'reportValidity()');
+
+test(() => {
+ const container = document.getElementById('container');
+ container.innerHTML = '<form><input type=submit><my-control>';
+ const form = container.querySelector('form');
+ const control = container.querySelector('my-control');
+ control.tabIndex = 0;
+ let invalidCount = 0;
+ control.addEventListener('invalid', e => { ++invalidCount; });
+
+ assert_true(control.i.checkValidity());
+ assert_true(form.checkValidity());
+ control.i.setValidity({valueMissing: true}, 'Please fill out this field');
+ assert_false(form.checkValidity());
+ assert_equals(invalidCount, 1);
+
+ assert_false(form.reportValidity());
+ assert_equals(invalidCount, 2);
+
+ container.querySelector('input[type=submit]').click();
+ assert_equals(invalidCount, 3);
+}, 'Custom control affects validation at the owner form');
+
+function isValidAccordingToCSS(element, comment) {
+ assert_true(element.matches(':valid'), comment ? (comment + ' - :valid') : undefined);
+ assert_false(element.matches(':invalid'), comment ? (comment + ' - :invalid') : undefined);
+}
+
+function isInvalidAccordingToCSS(element, comment) {
+ assert_false(element.matches(':valid'), comment ? (comment + ' - :valid') : undefined);
+ assert_true(element.matches(':invalid'), comment ? (comment + ' - :invalid') : undefined);
+}
+
+test(() => {
+ const container = document.getElementById('container');
+ container.innerHTML = '<form><fieldset><my-control>';
+ const form = container.querySelector('form');
+ const fieldset = container.querySelector('fieldset');
+ const control = container.querySelector('my-control');
+
+ isValidAccordingToCSS(control);
+ isValidAccordingToCSS(form);
+ isValidAccordingToCSS(fieldset);
+
+ control.i.setValidity({typeMismatch: true}, 'Invalid format');
+ isInvalidAccordingToCSS(control);
+ isInvalidAccordingToCSS(form);
+ isInvalidAccordingToCSS(fieldset);
+
+ control.remove();
+ isInvalidAccordingToCSS(control);
+ isValidAccordingToCSS(form);
+ isValidAccordingToCSS(fieldset);
+
+ fieldset.appendChild(control);
+ isInvalidAccordingToCSS(form);
+ isInvalidAccordingToCSS(fieldset);
+
+ control.i.setValidity({});
+ isValidAccordingToCSS(control);
+ isValidAccordingToCSS(form);
+ isValidAccordingToCSS(fieldset);
+}, 'Custom control affects :valid :invalid for FORM and FIELDSET');
+</script>
+</body>