diff options
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.html | 356 |
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> |