diff --git a/packages/fieldset/test/lion-fieldset.test.js b/packages/fieldset/test/lion-fieldset.test.js index d9f003ad4..88521726f 100644 --- a/packages/fieldset/test/lion-fieldset.test.js +++ b/packages/fieldset/test/lion-fieldset.test.js @@ -9,8 +9,6 @@ import { } from '@open-wc/testing'; import sinon from 'sinon'; import { localizeTearDown } from '@lion/localize/test-helpers.js'; -import '@lion/input/lion-input.js'; - import '../lion-fieldset.js'; const tagString = 'lion-fieldset'; @@ -293,6 +291,38 @@ describe('', () => { el.formElements.color.modelValue = 'cat'; expect(el.error).to.deep.equal({}); }); + + it('validates on children (de)registration', async () => { + function hasEvenNumberOfChildren(modelValue) { + return { even: Object.keys(modelValue).length % 2 === 0 }; + } + const el = await fixture(html` + <${tag} .errorValidators=${[[hasEvenNumberOfChildren]]}> + + + `); + const child2 = await fixture( + html` + + `, + ); + + await nextFrame(); + expect(el.error.even).to.equal(true); + + el.appendChild(child2); + await nextFrame(); + expect(el.error.even).to.equal(undefined); + + el.removeChild(child2); + await nextFrame(); + expect(el.error.even).to.equal(true); + + // Edge case: remove all children + el.removeChild(el.querySelector('[id=c1]')); + await nextFrame(); + expect(el.error.even).to.equal(undefined); + }); }); describe('interaction states', () => { diff --git a/packages/validate/src/ValidateMixin.js b/packages/validate/src/ValidateMixin.js index d0d1d7059..0de305129 100644 --- a/packages/validate/src/ValidateMixin.js +++ b/packages/validate/src/ValidateMixin.js @@ -385,7 +385,10 @@ export const ValidateMixin = dedupeMixin( * Other transitions (from Warning/Info) are not followed by a success message */ validate() { - if (this.modelValue === undefined) return; + if (this.modelValue === undefined) { + this.__resetValidationStates(); + return; + } this.__oldValidationStates = this.getValidationStates(); this.constructor.validationTypes.forEach(type => { this.validateType(type); @@ -393,6 +396,13 @@ export const ValidateMixin = dedupeMixin( this.dispatchEvent(new CustomEvent('validation-done', { bubbles: true, composed: true })); } + __resetValidationStates() { + this.constructor.validationTypes.forEach(type => { + this[`${type}State`] = false; + this[type] = {}; + }); + } + /** * Override if needed */ diff --git a/packages/validate/test/ValidateMixin.test.js b/packages/validate/test/ValidateMixin.test.js index 1324c350c..99dd36d44 100644 --- a/packages/validate/test/ValidateMixin.test.js +++ b/packages/validate/test/ValidateMixin.test.js @@ -501,6 +501,37 @@ describe('ValidateMixin', () => { expect(cbInfo.callCount).to.equal(2); expect(cbSuccess.callCount).to.equal(1); }); + + it('removes invalid states whenever modelValue becomes undefined', async () => { + const el = await fixture(html` + <${tag} + .errorValidators=${[[minLength, { min: 3 }]]} + .warningValidators=${[[minLength, { min: 5 }]]} + .infoValidators=${[[minLength, { min: 7 }]]} + .successValidators=${[[alwaysFalse]]} + >${lightDom} + `); + + el.modelValue = 'a'; + expect(el.errorState).to.equal(true); + expect(el.warningState).to.equal(true); + expect(el.infoState).to.equal(true); + expect(el.successState).to.equal(true); + expect(el.error).to.not.eql({}); + expect(el.warning).to.not.eql({}); + expect(el.info).to.not.eql({}); + expect(el.success).to.not.eql({}); + + el.modelValue = undefined; + expect(el.errorState).to.equal(false); + expect(el.warningState).to.equal(false); + expect(el.infoState).to.equal(false); + expect(el.successState).to.equal(false); + expect(el.error).to.eql({}); + expect(el.warning).to.eql({}); + expect(el.info).to.eql({}); + expect(el.success).to.eql({}); + }); }); describe(`Accessibility ${suffixName}`, () => { @@ -690,6 +721,9 @@ describe('ValidateMixin', () => { const errorRenderer = defineCE( class extends HTMLElement { renderFeedback(validationStates, message) { + if (!message.list.length) { + return; + } const validator = message.list[0].data.validatorName; const showError = validationStates.error; this.innerText = showError ? `ERROR on ${validator}` : ''; @@ -982,6 +1016,9 @@ describe('ValidateMixin', () => { const errorRenderer = defineCE( class extends HTMLElement { renderFeedback(validationStates, message) { + if (!message.list.length) { + return; + } const validator = message.list[0].data.validatorName; const hide = message.list[0].data.validatorConfig &&