diff --git a/labs/behaviors/constraint-validation.ts b/labs/behaviors/constraint-validation.ts index 748e27abad..897ffd6318 100644 --- a/labs/behaviors/constraint-validation.ts +++ b/labs/behaviors/constraint-validation.ts @@ -235,7 +235,7 @@ export function mixinConstraintValidation< const {validity, validationMessage: nonCustomValidationMessage} = this[privateValidator].getValidity(); - const customError = !!this[privateCustomValidationMessage]; + const customError = !!this[privateCustomValidationMessage] || validity.customError; const validationMessage = this[privateCustomValidationMessage] || nonCustomValidationMessage; diff --git a/labs/behaviors/constraint-validation_test.ts b/labs/behaviors/constraint-validation_test.ts index 5114070d6c..5e73a5460c 100644 --- a/labs/behaviors/constraint-validation_test.ts +++ b/labs/behaviors/constraint-validation_test.ts @@ -17,6 +17,8 @@ import { import {mixinElementInternals} from './element-internals.js'; import {getFormValue, mixinFormAssociated} from './form-associated.js'; import {CheckboxValidator} from './validators/checkbox-validator.js'; +import {Validator} from './validators/validator.js'; +import {SelectState} from './validators/select-validator.js'; describe('mixinConstraintValidation()', () => { const baseClass = mixinConstraintValidation( @@ -45,6 +47,52 @@ describe('mixinConstraintValidation()', () => { } } + /** + * A validator that set customError flag to true + */ + class CustomErrorValidator extends Validator { + private control?: HTMLInputElement; + + protected override computeValidity(state: SelectState) { + if (!this.control) { + this.control = document.createElement('input'); + } + this.control.setCustomValidity('validator custom error'); + return { + validity: this.control.validity, + validationMessage: this.control.validationMessage, + }; + } + + protected override equals(prev: SelectState, next: SelectState) { + return prev.value === next.value + } + + protected override copy({ value, required }: SelectState) { + return { value, required }; + } + } + + @customElement('test-custom-error-constraint-validation') + class TestCustomErrorConstraintValidation extends baseClass { + @property() value = ''; + @property({ type: Boolean }) required = false; + override render() { + return html`
`; + } + [createValidator]() { + return new CustomErrorValidator(() => this); + } + + [getValidityAnchor]() { + return this.shadowRoot?.querySelector('#root') ?? null; + } + + [getFormValue]() { + return String(this.value); + } + } + describe('validity', () => { it('should return a ValidityState value', () => { const control = new TestConstraintValidation(); @@ -174,4 +222,26 @@ describe('mixinConstraintValidation()', () => { .toBe('Error'); }); }); + + describe('customError', () => { + it('should set customError to true when validator has customError', () => { + const control = new TestCustomErrorConstraintValidation(); + expect(control.validity.customError) + .withContext('validity.customError') + .toBeTrue(); + }); + it('should dispatch invalid event when validator has customError', () => { + const control = new TestCustomErrorConstraintValidation(); + const invalidListener = jasmine.createSpy('invalidListener'); + control.addEventListener('invalid', invalidListener); + control.reportValidity(); + expect(invalidListener).toHaveBeenCalledWith(jasmine.any(Event)); + }); + it('should report custom validation message over other validation messages', () => { + const control = new TestCustomErrorConstraintValidation(); + expect(control.validationMessage) + .withContext('validationMessage') + .toBe('validator custom error'); + }); + }) });