| <!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); |
| |
| 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'); |
| |
| const fieldset = document.createElement('fieldset'); |
| fieldset.appendChild(control); |
| assert_true(control.i.willValidate, 'In enabled FIELDSET'); |
| fieldset.disabled = true; |
| assert_false(control.i.willValidate, 'In disabled FIELDSET'); |
| fieldset.removeChild(control); |
| |
| control.setAttribute('disabled', ''); |
| assert_false(control.i.willValidate, 'with disabled attribute'); |
| }, 'willValidate'); |
| |
| 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(new 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('NotFoundError', () => { |
| control.i.setValidity(flags, m, document.body); |
| }, 'Not a descendant'); |
| |
| assert_throws('NotFoundError', () => { |
| control.i.setValidity(flags, m, control); |
| }, 'Self'); |
| |
| // 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 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 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> |