diff --git a/.changeset/tall-doors-smoke.md b/.changeset/tall-doors-smoke.md new file mode 100644 index 000000000..2cde93810 --- /dev/null +++ b/.changeset/tall-doors-smoke.md @@ -0,0 +1,5 @@ +--- +'@lion/ui': patch +--- + +[validate-mixin] determine if a required validator or result validator has been registered based on characteristics diff --git a/packages/ui/components/form-core/src/validate/ValidateMixin.js b/packages/ui/components/form-core/src/validate/ValidateMixin.js index da9c50c24..0e595596e 100644 --- a/packages/ui/components/form-core/src/validate/ValidateMixin.js +++ b/packages/ui/components/form-core/src/validate/ValidateMixin.js @@ -8,11 +8,11 @@ import { AsyncQueue } from '../utils/AsyncQueue.js'; import { pascalCase } from '../utils/pascalCase.js'; import { SyncUpdatableMixin } from '../utils/SyncUpdatableMixin.js'; import { LionValidationFeedback } from './LionValidationFeedback.js'; -import { ResultValidator as MetaValidator } from './ResultValidator.js'; import { Unparseable } from './Unparseable.js'; -import { Required } from './validators/Required.js'; import { FormControlMixin } from '../FormControlMixin.js'; // eslint-disable-next-line no-unused-vars +import { ResultValidator as MetaValidator } from './ResultValidator.js'; +// eslint-disable-next-line no-unused-vars import { Validator } from './Validator.js'; // TODO: [v1] make all @readOnly => @readonly and actually make sure those values cannot be set @@ -134,7 +134,7 @@ export const ValidateMixinImplementation = superclass => /** * Combination of validators provided by Application Developer and the default validators - * @type {Validator[]} + * @type {(Validator | MetaValidator)[]} * @protected */ get _allValidators() { @@ -480,9 +480,9 @@ export const ValidateMixinImplementation = superclass => const asyncValidators = /** @type {Validator[]} */ []; for (const v of this._allValidators) { - if (v instanceof MetaValidator) { - metaValidators.push(v); - } else if (v instanceof Required) { + if (/** @type {MetaValidator} */ (v)?.executeOnResults) { + metaValidators.push(/** @type {MetaValidator} */ (v)); + } else if (/** @type {typeof Validator} */ (v.constructor)?.validatorName === 'Required') { // Required validator was already handled } else if (/** @type {typeof Validator} */ (v.constructor).async) { asyncValidators.push(v); diff --git a/packages/ui/components/form-core/test-suites/ValidateMixin.suite.js b/packages/ui/components/form-core/test-suites/ValidateMixin.suite.js index 517b3822c..af8914abd 100644 --- a/packages/ui/components/form-core/test-suites/ValidateMixin.suite.js +++ b/packages/ui/components/form-core/test-suites/ValidateMixin.suite.js @@ -130,6 +130,121 @@ export function runValidateMixinSuite(customConfig) { expect(el.hasFeedbackFor).to.deep.equal(['error']); }); + it('determines whether the "Required" validator was already handled by judging the validatorName', async () => { + class BundledValidator extends EventTarget { + static ['_$isValidator$'] = true; + + static validatorName = ''; + + constructor() { + super(); + + this.type = 'error'; + } + + get config() { + // simplified version of the actual config + return {}; + } + + execute() { + // simplified version of the action execution + return true; + } + + onFormControlConnect() {} + + onFormControlDisconnect() {} + + async _getMessage() { + // simplified version of the actual logic + return 'You need to enter something.'; + } + } + + class BundledRequired extends BundledValidator { + static get validatorName() { + return 'Required'; + } + } + + const el = /** @type {ValidateElement} */ ( + await fixture(html` + <${tag} + .validators=${[new BundledRequired()]} + .modelValue=${'myValue'} + >${lightDom}${tag}> + `) + ); + + expect(el.hasFeedbackFor).to.deep.equal([]); + }); + + it('determines whether the passed Validators are ResultValidators judging by the presence of "executeOnResults"', async () => { + class ValidateElementWithSuccessType extends ValidateElement { + static get validationTypes() { + return ['error', 'success']; + } + } + + const elTagString = defineCE(ValidateElementWithSuccessType); + const elTag = unsafeStatic(elTagString); + + class BundledValidator extends EventTarget { + static ['_$isValidator$'] = true; + + static validatorName = ''; + + constructor() { + super(); + + this.type = 'error'; + } + + get config() { + // simplified version of the actual config + return {}; + } + + execute() { + // simplified version of the action execution + return true; + } + + onFormControlConnect() {} + + onFormControlDisconnect() {} + + async _getMessage() { + // simplified version of the actual logic + return 'Success message.'; + } + } + + class BundledDefaultSuccess extends BundledValidator { + constructor() { + super(); + + this.type = 'success'; + } + + executeOnResults() { + return true; + } + } + + const el = /** @type {ValidateElement} */ ( + await fixture(html` + <${elTag} + .validators=${[new Required(), new BundledDefaultSuccess()]} + .modelValue=${'myValue'} + >${lightDom}${elTag}> + `) + ); + + expect(el.hasFeedbackFor).to.deep.equal(['success']); + }); + it('revalidates when ".modelValue" changes', async () => { const el = /** @type {ValidateElement} */ ( await fixture(html` @@ -791,7 +906,7 @@ export function runValidateMixinSuite(customConfig) { // @ts-expect-error new EqualsLength(4, { getMessage: () => html`