1180 lines
45 KiB
JavaScript
1180 lines
45 KiB
JavaScript
/* eslint-disable no-unused-vars, no-param-reassign */
|
|
import { expect, fixture, html, unsafeStatic, defineCE, aTimeout } from '@open-wc/testing';
|
|
import sinon from 'sinon';
|
|
import { LionLitElement } from '@lion/core/src/LionLitElement.js';
|
|
import { localizeTearDown } from '@lion/localize/test-helpers.js';
|
|
import { localize } from '@lion/localize';
|
|
|
|
import { ValidateMixin } from '../src/ValidateMixin.js';
|
|
import { Unparseable } from '../src/Unparseable.js';
|
|
|
|
// element, lightDom, errorShowPrerequisite, warningShowPrerequisite, infoShowPrerequisite,
|
|
// successShowPrerequisite
|
|
|
|
const externalVariables = {};
|
|
const suffixName = '';
|
|
const lightDom = '';
|
|
|
|
const tagString = defineCE(
|
|
class extends ValidateMixin(LionLitElement) {
|
|
static get properties() {
|
|
return {
|
|
...super.properties,
|
|
modelValue: {
|
|
type: String,
|
|
},
|
|
};
|
|
}
|
|
},
|
|
);
|
|
|
|
const defaultRequiredFn = modelValue => ({ required: modelValue !== '' });
|
|
|
|
const tag = unsafeStatic(tagString);
|
|
|
|
beforeEach(() => {
|
|
localizeTearDown();
|
|
});
|
|
|
|
describe('ValidateMixin', () => {
|
|
it('supports multiple validator types: error, warning, info and success [spec-to-be-implemented]', () => {
|
|
// TODO: implement spec
|
|
});
|
|
|
|
/**
|
|
* Terminology
|
|
*
|
|
* - *validatable-field*
|
|
* The element ('this') the ValidateMixin is applied on.
|
|
*
|
|
* - *input-element*
|
|
* The 'this.inputElement' property (usually a getter) that returns/contains a reference to an
|
|
* interaction element that receives focus, displays the input value, interaction states are
|
|
* derived from, aria properties are put on and setCustomValidity (if applicable) is called on.
|
|
* Can be input, textarea, my-custom-slider etc.
|
|
*
|
|
* - *feedback-element*
|
|
* The 'this._messageElement' property (usually a getter) that returns/contains a reference to
|
|
* the output container for validation feedback. Messages will be written to this element
|
|
* based on user defined or default validity feedback visibility conditions.
|
|
*
|
|
* - *show-{type}-feedback-condition*
|
|
* The 'this.show-{'error'|'warning'|'info'|'success'}' value that stores whether the
|
|
* feedback for the particular validation type should be shown to the end user.
|
|
*/
|
|
it('validates immediately (once form field has bootstrapped/initialized)', async () => {
|
|
function alwaysFalse() {
|
|
return { alwaysFalse: false };
|
|
}
|
|
const el = await fixture(html`
|
|
<${tag}
|
|
.errorValidators=${[[alwaysFalse]]}
|
|
.warningValidators=${[[alwaysFalse]]}
|
|
.infoValidators=${[[alwaysFalse]]}
|
|
.successValidators=${[[alwaysFalse]]}
|
|
.modelValue=${'trigger initial validation'}
|
|
>${lightDom}</${tag}>
|
|
`);
|
|
|
|
expect(el.errorState).to.equal(true);
|
|
expect(el.warningState).to.equal(true);
|
|
expect(el.infoState).to.equal(true);
|
|
expect(el.successState).to.equal(true);
|
|
});
|
|
|
|
it('revalidates when value changes', async () => {
|
|
function alwaysTrue() {
|
|
return { alwaysTrue: true };
|
|
}
|
|
const el = await fixture(html`
|
|
<${tag}
|
|
.errorValidators=${[[alwaysTrue]]}
|
|
.modelValue=${'myValue'}
|
|
>${lightDom}</${tag}>
|
|
`);
|
|
|
|
const validateSpy = sinon.spy(el, 'validate');
|
|
el.modelValue = 'x';
|
|
expect(validateSpy.callCount).to.equal(1);
|
|
});
|
|
|
|
// TODO: fix and renable
|
|
it.skip('reconsiders feedback visibility when interaction states changed', async () => {
|
|
// see https://codeburst.io/javascript-async-await-with-foreach-b6ba62bbf404
|
|
async function asyncForEach(array, callback) {
|
|
for (let i = 0; i < array.length; i += 1) {
|
|
// we explicitly want to run it one after each other
|
|
await callback(array[i], i, array); // eslint-disable-line no-await-in-loop
|
|
}
|
|
}
|
|
|
|
const el = await fixture(html`
|
|
<${tag}
|
|
.errorValidators=${[
|
|
[
|
|
() => {
|
|
true;
|
|
},
|
|
],
|
|
]}
|
|
.modelValue=${'myValue'}
|
|
>${lightDom}</${tag}>
|
|
`);
|
|
|
|
const messageSpy = sinon.spy(el, '_createMessageAndRenderFeedback');
|
|
await asyncForEach(['dirty', 'touched', 'prefilled', 'submitted'], async (state, i) => {
|
|
el[state] = false;
|
|
await el.updateComplete;
|
|
el[state] = true;
|
|
await el.updateComplete;
|
|
expect(messageSpy.callCount).to.equal(i + 1);
|
|
});
|
|
});
|
|
|
|
it('works with viewValue when input is not parseable', async () => {
|
|
const otherValidatorSpy = sinon.spy(value => ({ otherValidator: false }));
|
|
await fixture(html`
|
|
<${tag}
|
|
.errorValidators=${[['required'], [otherValidatorSpy]]}
|
|
.__isRequired=${defaultRequiredFn}
|
|
.modelValue=${new Unparseable('foo')}
|
|
>${lightDom}</${tag}>
|
|
`);
|
|
expect(otherValidatorSpy.calledWith('foo')).to.equal(true);
|
|
});
|
|
|
|
describe(`Validators ${suffixName}`, () => {
|
|
function isCat(modelValue, opts) {
|
|
const validateString = opts && opts.number ? `cat${opts.number}` : 'cat';
|
|
return { isCat: modelValue === validateString };
|
|
}
|
|
|
|
it('is plain js function returning { validatorName: true/false }', async () => {
|
|
const el = await fixture(html`
|
|
<${tag}
|
|
.modelValue=${'cat'}
|
|
.errorValidators=${[[isCat]]}
|
|
>${lightDom}</${tag}>
|
|
`);
|
|
expect(typeof el.errorValidators[0][0]).to.equal('function');
|
|
expect(el.errorValidators[0][0]('cat').isCat).to.equal(true);
|
|
expect(el.errorValidators[0][0]('dog').isCat).to.equal(false);
|
|
});
|
|
|
|
it('accepts additional parameters as a second argument (e.g. options)', async () => {
|
|
expect(isCat('cat').isCat).to.equal(true);
|
|
expect(isCat('cat', { number: 5 }).isCat).to.equal(false);
|
|
expect(isCat('cat5', { number: 5 }).isCat).to.equal(true);
|
|
|
|
const el = await fixture(html`
|
|
<${tag}
|
|
.modelValue=${'cat'}
|
|
.errorValidators=${[[isCat, { number: 5 }]]}
|
|
>${lightDom}</${tag}>
|
|
`);
|
|
|
|
expect(el.errorValidators[0][1]).to.deep.equal({ number: 5 });
|
|
expect(el.errorState).to.equal(true);
|
|
el.errorValidators = [[isCat]];
|
|
el.modelValue = 'cat';
|
|
expect(el.errorState).to.equal(false);
|
|
});
|
|
|
|
it('will not trigger on empty values', async () => {
|
|
const el = await fixture(html`
|
|
<${tag}
|
|
.errorValidators=${[[isCat]]}
|
|
>${lightDom}</${tag}>
|
|
`);
|
|
|
|
el.modelValue = 'cat';
|
|
expect(el.error.isCat).to.be.undefined;
|
|
el.modelValue = 'dog';
|
|
expect(el.error.isCat).to.be.true;
|
|
el.modelValue = '';
|
|
expect(el.error.isCat).to.be.undefined;
|
|
});
|
|
|
|
it('gets retriggered on parameter change [to-be-investigated]', async () => {
|
|
// TODO: find way to technically implement this,
|
|
// e.g. define validator params as props on <lion-field>, just like native validators
|
|
});
|
|
|
|
it(`replaces native validators (required, minlength, maxlength, min, max, pattern, step,
|
|
type(email/date/number/...) etc.) [to-be-investigated]`, async () => {
|
|
// TODO: this could also become: "can be used in conjunction with"
|
|
});
|
|
|
|
it('can have multiple validators per type', async () => {
|
|
function containsLowercaseA(modelValue) {
|
|
return { containsLowercaseA: modelValue.indexOf('a') > -1 };
|
|
}
|
|
|
|
const spyIsCat = sinon.spy(isCat);
|
|
const spyContainsLowercaseA = sinon.spy(containsLowercaseA);
|
|
|
|
const multipleValidators = await fixture(html`
|
|
<${tag}
|
|
.label=${'myField'}
|
|
.modelValue=${'cat'}
|
|
.errorValidators=${[[spyIsCat], [spyContainsLowercaseA]]}
|
|
>${lightDom}</${tag}>
|
|
`);
|
|
|
|
expect(multipleValidators.errorValidators.length).to.equal(2);
|
|
expect(spyIsCat.callCount).to.equal(1);
|
|
expect(spyContainsLowercaseA.callCount).to.equal(1);
|
|
expect(multipleValidators.errorState).to.equal(false);
|
|
|
|
multipleValidators.modelValue = 'dog';
|
|
expect(spyIsCat.callCount).to.equal(2);
|
|
expect(spyContainsLowercaseA.callCount).to.equal(2);
|
|
expect(multipleValidators.errorState).to.equal(true);
|
|
});
|
|
});
|
|
|
|
describe(`Required validator`, () => {
|
|
it('has a string notation', async () => {
|
|
const el = await fixture(html`
|
|
<${tag}
|
|
.errorValidators=${[['required']]}
|
|
.__isRequired="${defaultRequiredFn}"
|
|
.modelValue=${'foo'}
|
|
>${lightDom}</${tag}>
|
|
`);
|
|
const requiredValidatorSpy = sinon.spy(el, '__isRequired');
|
|
el.modelValue = '';
|
|
expect(requiredValidatorSpy.callCount).to.equal(1);
|
|
expect(el.error.required).to.equal(true);
|
|
});
|
|
|
|
it('can have different implementations for different form controls', async () => {
|
|
const el = await fixture(html`
|
|
<${tag}
|
|
.errorValidators=${[['required']]}
|
|
.__isRequired=${modelValue => ({ required: modelValue.model !== '' })}
|
|
.modelValue=${{ model: 'foo' }}
|
|
>${lightDom}</${tag}>
|
|
`);
|
|
const requiredValidatorSpy = sinon.spy(el, '__isRequired');
|
|
el.modelValue = { model: '' };
|
|
expect(requiredValidatorSpy.callCount).to.equal(1);
|
|
expect(el.error.required).to.equal(true);
|
|
});
|
|
|
|
it('prevents other validators from being called when required validator returns false', async () => {
|
|
const alwaysFalseSpy = sinon.spy(() => ({ alwaysFalse: false }));
|
|
const el = await fixture(html`
|
|
<${tag}
|
|
.errorValidators=${[['required'], [alwaysFalseSpy]]}
|
|
.__isRequired=${defaultRequiredFn}
|
|
.modelValue=${''}
|
|
>${lightDom}</${tag}>
|
|
`);
|
|
expect(alwaysFalseSpy.callCount).to.equal(0); // __isRequired returned false (invalid)
|
|
el.modelValue = 'foo';
|
|
expect(alwaysFalseSpy.callCount).to.equal(1); // __isRequired returned true (valid)
|
|
});
|
|
});
|
|
|
|
describe(`Element Validation States ${suffixName}`, () => {
|
|
const alwaysFalse = () => ({ alwaysFalse: false });
|
|
const minLength = (modelValue, { min }) => ({ minLength: modelValue.length >= min });
|
|
const containsLowercaseA = modelValue => ({ containsLowercaseA: modelValue.indexOf('a') > -1 });
|
|
const containsLowercaseB = modelValue => ({ containsLowercaseB: modelValue.indexOf('b') > -1 });
|
|
|
|
it('stores current state of every type in this.(error|warning|info|success)State', async () => {
|
|
const validationState = await fixture(html`
|
|
<${tag}
|
|
.errorValidators=${[[minLength, { min: 3 }]]}
|
|
.warningValidators=${[[minLength, { min: 5 }]]}
|
|
.infoValidators=${[[minLength, { min: 7 }]]}
|
|
.successValidators=${[[alwaysFalse]]}
|
|
>${lightDom}</${tag}>
|
|
`);
|
|
|
|
validationState.modelValue = 'a';
|
|
expect(validationState.errorState).to.equal(true);
|
|
expect(validationState.warningState).to.equal(true);
|
|
expect(validationState.infoState).to.equal(true);
|
|
expect(validationState.successState).to.equal(true);
|
|
|
|
validationState.modelValue = 'abc';
|
|
expect(validationState.errorState).to.equal(false);
|
|
expect(validationState.warningState).to.equal(true);
|
|
expect(validationState.infoState).to.equal(true);
|
|
expect(validationState.successState).to.equal(true);
|
|
|
|
validationState.modelValue = 'abcde';
|
|
expect(validationState.errorState).to.equal(false);
|
|
expect(validationState.warningState).to.equal(false);
|
|
expect(validationState.infoState).to.equal(true);
|
|
expect(validationState.successState).to.equal(true);
|
|
|
|
validationState.modelValue = 'abcdefg';
|
|
expect(validationState.errorState).to.equal(false);
|
|
expect(validationState.warningState).to.equal(false);
|
|
expect(validationState.infoState).to.equal(false);
|
|
expect(validationState.successState).to.equal(true);
|
|
});
|
|
|
|
it('fires "(error|warning|info|success)-changed" event when {state} changes', async () => {
|
|
const validationState = await fixture(html`
|
|
<${tag}
|
|
.errorValidators=${[[minLength, { min: 3 }], [containsLowercaseA], [containsLowercaseB]]}
|
|
>${lightDom}</${tag}>
|
|
`);
|
|
|
|
const cbError = sinon.spy();
|
|
validationState.addEventListener('error-changed', cbError);
|
|
|
|
validationState.modelValue = 'a';
|
|
expect(cbError.callCount).to.equal(1);
|
|
|
|
validationState.modelValue = 'aa';
|
|
expect(cbError.callCount).to.equal(1);
|
|
|
|
validationState.modelValue = 'aaa';
|
|
expect(cbError.callCount).to.equal(2);
|
|
|
|
validationState.modelValue = 'aba';
|
|
expect(cbError.callCount).to.equal(3);
|
|
});
|
|
|
|
it(`sets a class "state-(error|warning|info|success)" when the component has a
|
|
corresponding state`, async () => {
|
|
const element = await fixture(html`
|
|
<${tag}
|
|
.errorValidators=${[[minLength, { min: 3 }]]}
|
|
.warningValidators=${[[minLength, { min: 5 }]]}
|
|
.infoValidators=${[[minLength, { min: 7 }]]}
|
|
.successValidators=${[[alwaysFalse]]}
|
|
>${lightDom}</${tag}>`);
|
|
|
|
element.modelValue = 'a';
|
|
await element.updateComplete;
|
|
|
|
expect(element.classList.contains('state-error')).to.equal(true, 'has state-error');
|
|
expect(element.classList.contains('state-warning')).to.equal(true, 'has state-warning');
|
|
expect(element.classList.contains('state-info')).to.equal(true, 'has state-info');
|
|
expect(element.classList.contains('state-success')).to.equal(true, 'has state-success');
|
|
|
|
element.modelValue = 'abc';
|
|
await element.updateComplete;
|
|
expect(element.classList.contains('state-error')).to.equal(false, 'has no state-error');
|
|
expect(element.classList.contains('state-warning')).to.equal(true, 'has state-warning');
|
|
expect(element.classList.contains('state-info')).to.equal(true, 'has state-info');
|
|
expect(element.classList.contains('state-success')).to.equal(true, 'has state-success');
|
|
|
|
element.modelValue = 'abcde';
|
|
await element.updateComplete;
|
|
expect(element.classList.contains('state-error')).to.equal(false, 'has no state-error');
|
|
expect(element.classList.contains('state-warning')).to.equal(false, 'has no state-warning');
|
|
expect(element.classList.contains('state-info')).to.equal(true, 'has state-info');
|
|
expect(element.classList.contains('state-success')).to.equal(true, 'has state-success');
|
|
|
|
element.modelValue = 'abcdefg';
|
|
await element.updateComplete;
|
|
expect(element.classList.contains('state-error')).to.equal(false, 'has no state-error');
|
|
expect(element.classList.contains('state-warning')).to.equal(false, 'has no state-warning');
|
|
expect(element.classList.contains('state-info')).to.equal(false, 'has no state-info');
|
|
expect(element.classList.contains('state-success')).to.equal(true, 'has state-success');
|
|
});
|
|
|
|
it(`stores validity of validator for every type in
|
|
this.(error|warning|info|success).{validatorName}`, async () => {
|
|
const validationState = await fixture(html`
|
|
<${tag}
|
|
.modelValue=${'a'}
|
|
.errorValidators=${[[minLength, { min: 3 }], [alwaysFalse]]}
|
|
>${lightDom}</${tag}>`);
|
|
|
|
expect(validationState.error.minLength).to.equal(true);
|
|
expect(validationState.error.alwaysFalse).to.equal(true);
|
|
|
|
validationState.modelValue = 'abc';
|
|
|
|
expect(typeof validationState.error.minLength).to.equal('undefined');
|
|
expect(validationState.error.alwaysFalse).to.equal(true);
|
|
});
|
|
|
|
it(`sets a class "state-(error|warning|info|success)-show" when the component has
|
|
a corresponding state and "show{type}Condition()" is met`, async () => {
|
|
const validationState = await fixture(html`
|
|
<${tag}
|
|
.errorValidators=${[[minLength, { min: 3 }]]}
|
|
.warningValidators=${[[minLength, { min: 5 }]]}
|
|
.infoValidators=${[[minLength, { min: 7 }]]}
|
|
.successValidators=${[[alwaysFalse]]}
|
|
>${lightDom}</${tag}>`);
|
|
|
|
if (externalVariables.errorShowPrerequisite) {
|
|
externalVariables.errorShowPrerequisite(validationState);
|
|
externalVariables.warningShowPrerequisite(validationState);
|
|
externalVariables.infoShowPrerequisite(validationState);
|
|
externalVariables.successShowPrerequisite(validationState);
|
|
}
|
|
|
|
validationState.modelValue = 'a';
|
|
await validationState.updateComplete;
|
|
expect(validationState.classList.contains('state-error-show')).to.equal(true);
|
|
expect(validationState.classList.contains('state-warning-show')).to.equal(false);
|
|
expect(validationState.classList.contains('state-info-show')).to.equal(false);
|
|
expect(validationState.classList.contains('state-success-show')).to.equal(false);
|
|
|
|
validationState.modelValue = 'abc';
|
|
await validationState.updateComplete;
|
|
expect(validationState.classList.contains('state-error-show')).to.equal(false);
|
|
expect(validationState.classList.contains('state-warning-show')).to.equal(true);
|
|
expect(validationState.classList.contains('state-info-show')).to.equal(false);
|
|
expect(validationState.classList.contains('state-success-show')).to.equal(false);
|
|
|
|
validationState.modelValue = 'abcde';
|
|
await validationState.updateComplete;
|
|
expect(validationState.classList.contains('state-error-show')).to.equal(false);
|
|
expect(validationState.classList.contains('state-warning-show')).to.equal(false);
|
|
expect(validationState.classList.contains('state-info-show')).to.equal(true);
|
|
expect(validationState.classList.contains('state-success-show')).to.equal(false);
|
|
|
|
validationState.modelValue = 'abcdefg';
|
|
await validationState.updateComplete;
|
|
expect(validationState.classList.contains('state-error-show')).to.equal(false);
|
|
expect(validationState.classList.contains('state-warning-show')).to.equal(false);
|
|
expect(validationState.classList.contains('state-info-show')).to.equal(false);
|
|
expect(validationState.classList.contains('state-success-show')).to.equal(false);
|
|
|
|
validationState.modelValue = 'a';
|
|
await validationState.updateComplete;
|
|
expect(validationState.classList.contains('state-error-show')).to.equal(true);
|
|
expect(validationState.classList.contains('state-warning-show')).to.equal(false);
|
|
expect(validationState.classList.contains('state-info-show')).to.equal(false);
|
|
expect(validationState.classList.contains('state-success-show')).to.equal(false);
|
|
|
|
validationState.modelValue = 'abcdefg';
|
|
await validationState.updateComplete;
|
|
expect(validationState.classList.contains('state-error-show')).to.equal(false);
|
|
expect(validationState.classList.contains('state-warning-show')).to.equal(false);
|
|
expect(validationState.classList.contains('state-info-show')).to.equal(false);
|
|
expect(validationState.classList.contains('state-success-show')).to.equal(true);
|
|
});
|
|
|
|
it('fires "(error|warning|info|success)-state-changed" event when state changes', async () => {
|
|
const el = await fixture(html`
|
|
<${tag}
|
|
.errorValidators=${[[minLength, { min: 7 }]]}
|
|
.warningValidators=${[[minLength, { min: 5 }]]}
|
|
.infoValidators=${[[minLength, { min: 3 }]]}
|
|
.successValidators=${[[alwaysFalse]]}
|
|
>${lightDom}</${tag}>
|
|
`);
|
|
const cbInfo = sinon.spy();
|
|
const cbWarning = sinon.spy();
|
|
const cbError = sinon.spy();
|
|
const cbSuccess = sinon.spy();
|
|
|
|
el.addEventListener('error-state-changed', cbError);
|
|
el.addEventListener('warning-state-changed', cbWarning);
|
|
el.addEventListener('info-state-changed', cbInfo);
|
|
el.addEventListener('success-state-changed', cbSuccess);
|
|
|
|
el.modelValue = 'a';
|
|
expect(cbError.callCount).to.equal(1);
|
|
expect(cbWarning.callCount).to.equal(1);
|
|
expect(cbInfo.callCount).to.equal(1);
|
|
expect(cbSuccess.callCount).to.equal(1);
|
|
|
|
el.modelValue = 'abc';
|
|
expect(cbError.callCount).to.equal(1);
|
|
expect(cbWarning.callCount).to.equal(1);
|
|
expect(cbInfo.callCount).to.equal(2);
|
|
expect(cbSuccess.callCount).to.equal(1);
|
|
|
|
el.modelValue = 'abcde';
|
|
expect(cbError.callCount).to.equal(1);
|
|
expect(cbWarning.callCount).to.equal(2);
|
|
expect(cbInfo.callCount).to.equal(2);
|
|
expect(cbSuccess.callCount).to.equal(1);
|
|
|
|
el.modelValue = 'abcdefg';
|
|
expect(cbError.callCount).to.equal(2);
|
|
expect(cbWarning.callCount).to.equal(2);
|
|
expect(cbInfo.callCount).to.equal(2);
|
|
expect(cbSuccess.callCount).to.equal(1);
|
|
});
|
|
});
|
|
|
|
describe(`Accessibility ${suffixName}`, () => {
|
|
it(`sets property "aria-invalid" to *input-element* once errors should be shown
|
|
to user(*show-error-feedback-condition* is true) [to-be-implemented]`, async () => {});
|
|
|
|
it('sets *input-element*.setCustomValidity(errorMessage) [to-be-implemented]', async () => {
|
|
// TODO: test how and if this affects a11y
|
|
});
|
|
|
|
it(`removes validity message from DOM instead of toggling "display:none", to trigger Jaws
|
|
and VoiceOver [to-be-implemented]`, async () => {});
|
|
});
|
|
|
|
describe(`Validity Feedback ${suffixName}`, () => {
|
|
function alwaysFalse() {
|
|
return { alwaysFalse: false };
|
|
}
|
|
function minLength(modelValue, opts) {
|
|
return { minLength: modelValue.length >= opts.min };
|
|
}
|
|
function containsLowercaseA(modelValue) {
|
|
return { containsLowercaseA: modelValue.indexOf('a') > -1 };
|
|
}
|
|
function containsCat(modelValue) {
|
|
return { containsCat: modelValue.indexOf('cat') > -1 };
|
|
}
|
|
|
|
const defaultElement = defineCE(
|
|
class extends ValidateMixin(LionLitElement) {
|
|
static get properties() {
|
|
return {
|
|
...super.properties,
|
|
modelValue: {
|
|
type: String,
|
|
},
|
|
};
|
|
}
|
|
},
|
|
);
|
|
const defaultElementName = unsafeStatic(defaultElement);
|
|
|
|
beforeEach(() => {
|
|
// Reset and preload validation translations
|
|
localizeTearDown();
|
|
localize.addData('en-GB', 'lion-validate', {
|
|
error: {
|
|
alwaysFalse: 'This is error message for alwaysFalse',
|
|
minLength: 'This is error message for minLength',
|
|
containsLowercaseA: 'This is error message for containsLowercaseA',
|
|
containsCat: 'This is error message for containsCat',
|
|
},
|
|
warning: {
|
|
alwaysFalse: 'This is warning message for alwaysFalse',
|
|
minLength: 'This is warning message for minLength',
|
|
containsLowercaseA: 'This is warning message for containsLowercaseA',
|
|
containsCat: 'This is warning message for containsCat',
|
|
},
|
|
info: {
|
|
alwaysFalse: 'This is info message for alwaysFalse',
|
|
minLength: 'This is info message for minLength',
|
|
containsLowercaseA: 'This is info message for containsLowercaseA',
|
|
containsCat: 'This is info message for containsCat',
|
|
},
|
|
success: {
|
|
alwaysFalse: 'This is success message for alwaysFalse',
|
|
minLength: 'This is success message for minLength',
|
|
containsLowercaseA: 'This is success message for containsLowercaseA',
|
|
containsCat: 'This is success message for containsCat',
|
|
},
|
|
});
|
|
});
|
|
|
|
it('has configurable feedback display condition', async () => {
|
|
let showErrors = false;
|
|
const el = await fixture(html`
|
|
<${tag}
|
|
.showErrorCondition=${newStates => showErrors && newStates.error}
|
|
.modelValue=${'cat'}
|
|
.errorValidators=${[[alwaysFalse]]}
|
|
>${lightDom}</${tag}>
|
|
`);
|
|
|
|
expect(el.$$slot('feedback').innerText).to.equal('');
|
|
|
|
showErrors = true;
|
|
el.validate();
|
|
await el.updateComplete;
|
|
|
|
expect(el.$$slot('feedback').innerText).to.equal('This is error message for alwaysFalse');
|
|
});
|
|
|
|
it('writes validation outcome to *feedback-element*, if present', async () => {
|
|
const feedbackResult = await fixture(html`
|
|
<${tag}
|
|
.modelValue=${'cat'}
|
|
.errorValidators=${[[alwaysFalse]]}
|
|
>${lightDom}</${tag}>
|
|
`);
|
|
expect(feedbackResult.$$slot('feedback').innerText).to.equal(
|
|
'This is error message for alwaysFalse',
|
|
);
|
|
});
|
|
|
|
it('rerenders validation outcome to *feedback-element*, when dependent on async resources', async () => {
|
|
const alwaysFalseAsyncTransl = () => ({ alwaysFalseAsyncTransl: false });
|
|
const feedbackResult = await fixture(html`
|
|
<${tag}
|
|
.modelValue=${'cat'}
|
|
.errorValidators=${[[alwaysFalseAsyncTransl]]}
|
|
>${lightDom}</${tag}>
|
|
`);
|
|
|
|
expect(feedbackResult.$$slot('feedback').innerText).to.equal('');
|
|
// locale changed or smth
|
|
localize.reset();
|
|
localize.addData('en-GB', 'lion-validate', {
|
|
error: { alwaysFalseAsyncTransl: 'error:alwaysFalseAsyncTransl' },
|
|
});
|
|
|
|
feedbackResult.onLocaleUpdated();
|
|
expect(feedbackResult.$$slot('feedback').innerText).to.equal('error:alwaysFalseAsyncTransl');
|
|
});
|
|
|
|
it('allows to overwrite the way messages are translated', async () => {
|
|
const customTranslations = await fixture(html`
|
|
<${tag}
|
|
.translateMessage=${(keys, data) => {
|
|
switch (data.validatorName) {
|
|
case 'alwaysFalse':
|
|
return 'You can not pass';
|
|
case 'containsLowercaseA':
|
|
return 'You should have a lowercase a';
|
|
default:
|
|
return '';
|
|
}
|
|
}}
|
|
.modelValue=${'dog'}
|
|
.errorValidators=${[[containsLowercaseA], [alwaysFalse]]}
|
|
>${lightDom}</${tag}>
|
|
`);
|
|
|
|
expect(customTranslations.$$slot('feedback').innerText).to.equal(
|
|
'You should have a lowercase a',
|
|
);
|
|
|
|
customTranslations.modelValue = 'cat';
|
|
await customTranslations.updateComplete;
|
|
expect(customTranslations.$$slot('feedback').innerText).to.equal('You can not pass');
|
|
});
|
|
|
|
it('allows to overwrite the way messages are rendered/added to dom', async () => {
|
|
const element = defineCE(
|
|
class extends ValidateMixin(LionLitElement) {
|
|
static get properties() {
|
|
return {
|
|
...super.properties,
|
|
modelValue: {
|
|
type: String,
|
|
},
|
|
};
|
|
}
|
|
|
|
renderFeedback(validationStates, message) {
|
|
const validator = message.list[0].data.validatorName;
|
|
const showError = validationStates.error;
|
|
this.innerHTML = showError ? `ERROR on ${validator}` : '';
|
|
}
|
|
},
|
|
);
|
|
const elem = unsafeStatic(element);
|
|
const ownTranslations = await fixture(html`
|
|
<${elem}
|
|
.modelValue=${'dog'}
|
|
.errorValidators=${[[containsLowercaseA], [alwaysFalse]]}
|
|
>${lightDom}</${elem}>
|
|
`);
|
|
await ownTranslations.updateComplete;
|
|
expect(ownTranslations.innerHTML).to.equal('ERROR on containsLowercaseA');
|
|
|
|
ownTranslations.modelValue = 'cat';
|
|
await ownTranslations.updateComplete;
|
|
expect(ownTranslations.innerHTML).to.equal('ERROR on alwaysFalse');
|
|
});
|
|
|
|
it('supports custom element to render feedback', async () => {
|
|
const errorRenderer = defineCE(
|
|
class extends HTMLElement {
|
|
renderFeedback(validationStates, message) {
|
|
const validator = message.list[0].data.validatorName;
|
|
const showError = validationStates.error;
|
|
this.innerText = showError ? `ERROR on ${validator}` : '';
|
|
}
|
|
},
|
|
);
|
|
const errorRendererName = unsafeStatic(errorRenderer);
|
|
// TODO: refactor to support integration via externalDependencies.element
|
|
const element = await fixture(html`
|
|
<${defaultElementName}
|
|
.errorValidators=${[[containsLowercaseA], [alwaysFalse]]}>
|
|
<${errorRendererName} slot="feedback"><${errorRendererName}>
|
|
</${defaultElementName}>
|
|
`);
|
|
|
|
element.modelValue = 'dog';
|
|
await element.updateComplete;
|
|
expect(element.$$slot('feedback').innerText).to.equal('ERROR on containsLowercaseA');
|
|
|
|
element.modelValue = 'cat';
|
|
await element.updateComplete;
|
|
expect(element.$$slot('feedback').innerText).to.equal('ERROR on alwaysFalse');
|
|
});
|
|
|
|
it('allows to create a custom feedback renderer via the template [to-be-implemented]', async () => {
|
|
// TODO: implement
|
|
});
|
|
|
|
it('shows only highest priority validation message type (1. error, 2. warning, 3. info)', async () => {
|
|
// TODO: refactor to support integration via externalDependencies.element
|
|
const validityFeedback = await fixture(html`
|
|
<${defaultElementName}
|
|
.errorValidators=${[[minLength, { min: 3 }]]}
|
|
.warningValidators=${[[minLength, { min: 5 }]]}
|
|
.infoValidators=${[[minLength, { min: 7 }]]}
|
|
.successValidators=${[[alwaysFalse]]}
|
|
>${lightDom}</${defaultElementName}>
|
|
`);
|
|
|
|
validityFeedback.modelValue = 'a';
|
|
await validityFeedback.updateComplete;
|
|
expect(validityFeedback.$$slot('feedback').innerText).to.equal(
|
|
'This is error message for minLength',
|
|
);
|
|
|
|
validityFeedback.modelValue = 'abc';
|
|
await validityFeedback.updateComplete;
|
|
expect(validityFeedback.$$slot('feedback').innerText).to.equal(
|
|
'This is warning message for minLength',
|
|
);
|
|
|
|
validityFeedback.modelValue = 'abcde';
|
|
await validityFeedback.updateComplete;
|
|
expect(validityFeedback.$$slot('feedback').innerText).to.equal(
|
|
'This is info message for minLength',
|
|
);
|
|
});
|
|
|
|
it('shows success message after fixing an error', async () => {
|
|
// TODO: refactor to support integration via externalDependencies.element
|
|
const validityFeedback = await fixture(html`
|
|
<${defaultElementName}
|
|
.errorValidators=${[[minLength, { min: 3 }]]}
|
|
.successValidators=${[[alwaysFalse]]}
|
|
>${lightDom}</${defaultElementName}>
|
|
`);
|
|
|
|
validityFeedback.modelValue = 'a';
|
|
await validityFeedback.updateComplete;
|
|
expect(validityFeedback.$$slot('feedback').innerText).to.equal(
|
|
'This is error message for minLength',
|
|
);
|
|
|
|
validityFeedback.modelValue = 'abcd';
|
|
await validityFeedback.updateComplete;
|
|
expect(validityFeedback.$$slot('feedback').innerText).to.equal(
|
|
'This is success message for alwaysFalse',
|
|
);
|
|
});
|
|
|
|
it(`shows only highest priority validation message determined by order of assignment of
|
|
validators`, async () => {
|
|
// TODO: refactor to support integration via externalDependencies.element
|
|
const validityFeedback = await fixture(html`
|
|
<${defaultElementName}
|
|
.errorValidators=${[[containsCat], [minLength, { min: 4 }]]}
|
|
>${lightDom}</${defaultElementName}>
|
|
`);
|
|
validityFeedback.modelValue = 'dog and dog';
|
|
await validityFeedback.updateComplete;
|
|
expect(validityFeedback.$$slot('feedback').innerText).to.equal(
|
|
'This is error message for containsCat',
|
|
);
|
|
|
|
validityFeedback.modelValue = 'dog';
|
|
await validityFeedback.updateComplete;
|
|
expect(validityFeedback.$$slot('feedback').innerText).to.equal(
|
|
'This is error message for containsCat',
|
|
);
|
|
|
|
validityFeedback.modelValue = 'cat';
|
|
await validityFeedback.updateComplete;
|
|
expect(validityFeedback.$$slot('feedback').innerText).to.equal(
|
|
'This is error message for minLength',
|
|
);
|
|
|
|
validityFeedback.modelValue = 'dog and cat';
|
|
await validityFeedback.updateComplete;
|
|
expect(validityFeedback.$$slot('feedback').innerText).to.equal('');
|
|
});
|
|
|
|
it('supports randomized selection of multiple messages for the same validator', async () => {
|
|
const randomTranslationsElement = defineCE(
|
|
class extends ValidateMixin(LionLitElement) {
|
|
static get properties() {
|
|
return {
|
|
...super.properties,
|
|
modelValue: {
|
|
type: String,
|
|
},
|
|
};
|
|
}
|
|
|
|
translateMessage(rawKeys) {
|
|
const translationData = {
|
|
error: {
|
|
containsLowercaseA: 'You should have a lowercase a',
|
|
},
|
|
success: {
|
|
randomAlwaysFalse: 'success.a, success.b, success.c, success.d',
|
|
a: 'Good job!',
|
|
b: 'You did great!',
|
|
c: 'Looks good!',
|
|
d: 'nice!',
|
|
},
|
|
};
|
|
const keys = !Array.isArray(rawKeys) ? [rawKeys] : rawKeys;
|
|
|
|
for (let i = 0; i < keys.length; i += 1) {
|
|
const key = keys[i].split(':')[1];
|
|
const found = key.split('.').reduce((o, j) => o[j], translationData);
|
|
if (found) {
|
|
return found;
|
|
}
|
|
}
|
|
return '';
|
|
}
|
|
},
|
|
);
|
|
const mathRandom = Math.random;
|
|
Math.random = () => 0;
|
|
|
|
function randomAlwaysFalse() {
|
|
return { randomAlwaysFalse: false };
|
|
}
|
|
|
|
const randomTranslationsName = unsafeStatic(randomTranslationsElement);
|
|
|
|
const randomTranslations = await fixture(html`
|
|
<${randomTranslationsName}
|
|
.modelValue=${'dog'}
|
|
.errorValidators=${[[containsLowercaseA]]}
|
|
.successValidators=${[[randomAlwaysFalse]]}
|
|
></${randomTranslationsName}>
|
|
`);
|
|
|
|
expect(
|
|
randomTranslations.translateMessage('random-translations:error.containsLowercaseA'),
|
|
).to.equal('You should have a lowercase a');
|
|
expect(randomTranslations.translateMessage('random-translations:success.a')).to.equal(
|
|
'Good job!',
|
|
);
|
|
|
|
expect(randomTranslations.$$slot('feedback').innerText).to.equal(
|
|
'You should have a lowercase a',
|
|
);
|
|
|
|
randomTranslations.modelValue = 'cat';
|
|
await randomTranslations.updateComplete;
|
|
expect(randomTranslations.$$slot('feedback').innerText).to.equal('Good job!');
|
|
|
|
Math.random = () => 0.25;
|
|
randomTranslations.__lastGetSuccessResult = false;
|
|
randomTranslations.modelValue = 'dog';
|
|
randomTranslations.modelValue = 'cat';
|
|
await randomTranslations.updateComplete;
|
|
|
|
expect(randomTranslations.$$slot('feedback').innerText).to.equal('You did great!');
|
|
|
|
Math.random = mathRandom; // manually restore
|
|
});
|
|
|
|
it('translates validity messages', async () => {
|
|
localize.reset();
|
|
localize.addData('en-GB', 'lion-validate', {
|
|
error: { minLength: 'You need to enter at least {validatorParams.min} characters.' },
|
|
});
|
|
localize.addData('de-DE', 'lion-validate', {
|
|
error: {
|
|
minLength: 'Es müssen mindestens {validatorParams.min} Zeichen eingegeben werden.',
|
|
},
|
|
});
|
|
|
|
const validityFeedback = await fixture(
|
|
html`
|
|
<${defaultElementName}
|
|
.modelValue=${'cat'}
|
|
.errorValidators=${[[minLength, { min: 4 }]]}
|
|
>${lightDom}</${defaultElementName}>
|
|
`,
|
|
() => ({
|
|
modelValue: 'cat',
|
|
errorValidators: [[minLength, { min: 4 }]],
|
|
}),
|
|
);
|
|
expect(validityFeedback.$$slot('feedback').innerText).to.equal(
|
|
'You need to enter at least 4 characters.',
|
|
);
|
|
|
|
localize.locale = 'de-DE';
|
|
await validityFeedback.updateComplete;
|
|
expect(validityFeedback.$$slot('feedback').innerText).to.equal(
|
|
'Es müssen mindestens 4 Zeichen eingegeben werden.',
|
|
);
|
|
});
|
|
|
|
describe('Field name', () => {
|
|
beforeEach(() => {
|
|
localizeTearDown();
|
|
localize.addData('en-GB', 'lion-validate', {
|
|
error: { minLength: '{fieldName} needs more characters' },
|
|
});
|
|
});
|
|
|
|
it('allows to use field name in messages', async () => {
|
|
const el = await fixture(html`
|
|
<${tag}
|
|
.label=${'myField'}
|
|
.errorValidators=${[[minLength, { min: 4 }]]}
|
|
.modelValue=${'cat'}
|
|
>${lightDom}</${tag}>
|
|
`);
|
|
expect(el.$$slot('feedback').innerText).to.equal('myField needs more characters');
|
|
});
|
|
|
|
it('allows to configure field name for every validator message', async () => {
|
|
const elNameStatic = { d: `${tagString}` };
|
|
const validityFeedback = await fixture(html`
|
|
<${elNameStatic} .label="${'myField'}" .name="${'myName'}"
|
|
.errorValidators=${[
|
|
[minLength, { min: 4, fieldName: 'overrideName' }],
|
|
]} .modelValue=${'cat'}
|
|
>${lightDom}
|
|
</${elNameStatic}>`);
|
|
expect(validityFeedback.$$slot('feedback').innerText).to.equal(
|
|
'overrideName needs more characters',
|
|
);
|
|
});
|
|
|
|
it('constructs field name from label or name (in this priority order)', async () => {
|
|
const elNameStatic = { d: `${tagString}` };
|
|
|
|
// As seen in test above, configuring fieldName on validator level takes highest precedence
|
|
const validityFeedback = await fixture(html`
|
|
<${elNameStatic} .label="${'myField'}" .name="${'myName'}"
|
|
.errorValidators=${[[minLength, { min: 4 }]]} .modelValue=${'cat'}
|
|
>${lightDom}
|
|
</${elNameStatic}>`);
|
|
expect(validityFeedback.$$slot('feedback').innerText).to.equal(
|
|
'myField needs more characters',
|
|
);
|
|
|
|
const validityFeedback2 = await fixture(html`
|
|
<${elNameStatic} .name="${'myName'}"
|
|
.errorValidators=${[[minLength, { min: 4 }]]} .modelValue=${'cat'}
|
|
>${lightDom}
|
|
</${elNameStatic}>`);
|
|
expect(validityFeedback2.$$slot('feedback').innerText).to.equal(
|
|
'myName needs more characters',
|
|
);
|
|
});
|
|
});
|
|
|
|
describe('Configuration meta data', () => {
|
|
// In some cases, for instance in a group elememnt like fieldset, the validity state of the
|
|
// children inputs need to be reflected on group level when asked for imperatively, although
|
|
// the feedback needs to be displayed on input level and not on group level for these kind
|
|
// of validators.
|
|
it('allows for opting out of visibly rendering feedback via "hideFeedback"', async () => {
|
|
const errorRenderer = defineCE(
|
|
class extends HTMLElement {
|
|
renderFeedback(validationStates, message) {
|
|
const validator = message.list[0].data.validatorName;
|
|
const hide =
|
|
message.list[0].data.validatorConfig &&
|
|
message.list[0].data.validatorConfig.hideFeedback;
|
|
if (validationStates.error && !hide) {
|
|
this.innerText = `ERROR on ${validator}`;
|
|
} else {
|
|
this.innerText = '';
|
|
}
|
|
}
|
|
},
|
|
);
|
|
|
|
const errorRendererName = unsafeStatic(errorRenderer);
|
|
|
|
// TODO: refactor to support integration via externalDependencies.element
|
|
const element = await fixture(html`
|
|
<${defaultElementName}
|
|
.errorValidators=${[[containsLowercaseA], [alwaysFalse, {}, { hideFeedback: true }]]}>
|
|
<${errorRendererName} slot="feedback"><${errorRendererName}>
|
|
</${defaultElementName}>
|
|
`);
|
|
|
|
element.modelValue = 'dog';
|
|
await element.updateComplete;
|
|
expect(element.$$slot('feedback').innerText).to.equal('ERROR on containsLowercaseA');
|
|
|
|
element.modelValue = 'cat';
|
|
await element.updateComplete;
|
|
expect(element.$$slot('feedback').innerText).to.equal('');
|
|
});
|
|
});
|
|
|
|
/**
|
|
* Order of keys should be like this
|
|
*
|
|
* ['lion-input-email', 'lion-validate'].forEach((namespace) => {
|
|
* 1. ${namespace}+${validatorName}:${type}.${validatorName}
|
|
* 2. ${namespace}:${type}.${validatorName}
|
|
* });
|
|
*
|
|
* Example:
|
|
* <lion-input-email name="partner" />
|
|
*
|
|
* 1. lion-input-email+isEmail:error:isEmail
|
|
* 2. lion-input-email:error:isEmail
|
|
* 3. lion-validate+isEmail:error.isEmail
|
|
* 4. lion-validate:error.isEmail
|
|
*/
|
|
describe('Localize Priority', () => {
|
|
it('adds a default namespace `lion-validate`', () => {
|
|
expect(customElements.get(tagString).localizeNamespaces[0]).to.include.keys(
|
|
'lion-validate',
|
|
);
|
|
});
|
|
|
|
it(`searches for the message in a specific order:
|
|
1. lion-validate+$validatorName:$type.$validatorName
|
|
2. lion-validate:$type.$validatorName
|
|
`, async () => {
|
|
// Tests are in 'reversed order', so we can increase prio by filling up localize storage
|
|
const orderValidator = () => [() => ({ orderValidator: false })];
|
|
|
|
const el = await fixture(html`
|
|
<${tag}
|
|
.name=${'foo'}
|
|
.errorValidators=${[orderValidator()]}
|
|
.modelValue=${'10'}>
|
|
${lightDom}
|
|
</${tag}>
|
|
`);
|
|
|
|
// reset the storage so that we can fill it in for each of 2 cases step by step
|
|
localize.reset();
|
|
|
|
// 2. lion-validate
|
|
localize.addData('en-GB', 'lion-validate', {
|
|
error: {
|
|
orderValidator: 'lion-validate : orderValidator',
|
|
},
|
|
});
|
|
el._createMessageAndRenderFeedback();
|
|
expect(el.$$slot('feedback').innerText).to.equal('lion-validate : orderValidator');
|
|
|
|
// 1. lion-validate+orderValidator
|
|
localize.addData('en-GB', 'lion-validate+orderValidator', {
|
|
error: {
|
|
orderValidator: 'lion-validate+orderValidator : orderValidator',
|
|
},
|
|
});
|
|
el._createMessageAndRenderFeedback();
|
|
expect(el.$$slot('feedback').innerText).to.equal(
|
|
'lion-validate+orderValidator : orderValidator',
|
|
);
|
|
});
|
|
|
|
it(`searches for the message in a specific order (when there is an extra namespace):
|
|
1. my-custom-namespace+$validatorName:$type.$validatorName
|
|
2. my-custom-namespace:$type.$validatorName
|
|
3. lion-validate+$validatorName:$type.$validatorName
|
|
4. lion-validate:$type.$validatorName
|
|
`, async () => {
|
|
// Tests are in 'reversed order', so we can increase prio by filling up localize storage
|
|
const is12Validator = () => [modelValue => ({ is12Validator: modelValue === 12 })];
|
|
const orderName = defineCE(
|
|
class extends ValidateMixin(LionLitElement) {
|
|
static get properties() {
|
|
return { ...super.properties, modelValue: { type: String } };
|
|
}
|
|
|
|
static get localizeNamespaces() {
|
|
return [
|
|
{ 'my-custom-namespace': () => Promise.resolve({}) },
|
|
...super.localizeNamespaces,
|
|
];
|
|
}
|
|
},
|
|
);
|
|
|
|
const tagOrderName = unsafeStatic(orderName);
|
|
|
|
const el = await fixture(html`
|
|
<${tagOrderName}
|
|
.name=${'bar'}
|
|
.errorValidators=${[is12Validator()]}
|
|
.modelValue=${'10'}>
|
|
${lightDom}
|
|
</${tagOrderName}>
|
|
`);
|
|
|
|
// reset the storage so that we can fill it in for each of 4 cases step by step
|
|
localize.reset();
|
|
|
|
// 4. lion-validate
|
|
localize.addData('en-GB', 'lion-validate', {
|
|
error: {
|
|
is12Validator: 'lion-validate : is12Validator',
|
|
},
|
|
});
|
|
el._createMessageAndRenderFeedback();
|
|
expect(el.$$slot('feedback').innerText).to.equal('lion-validate : is12Validator');
|
|
|
|
// 3. lion-validate+is12Validator
|
|
localize.addData('en-GB', 'lion-validate+is12Validator', {
|
|
error: {
|
|
is12Validator: 'lion-validate+is12Validator : is12Validator',
|
|
},
|
|
});
|
|
el._createMessageAndRenderFeedback();
|
|
expect(el.$$slot('feedback').innerText).to.equal(
|
|
'lion-validate+is12Validator : is12Validator',
|
|
);
|
|
|
|
// 2. my-custom-namespace
|
|
localize.addData('en-GB', 'my-custom-namespace', {
|
|
error: {
|
|
is12Validator: 'my-custom-namespace : is12Validator',
|
|
},
|
|
});
|
|
el._createMessageAndRenderFeedback();
|
|
expect(el.$$slot('feedback').innerText).to.equal('my-custom-namespace : is12Validator');
|
|
|
|
// 1. my-custom-namespace+is12Validator
|
|
localize.addData('en-GB', 'my-custom-namespace+is12Validator', {
|
|
error: {
|
|
is12Validator: 'my-custom-namespace+is12Validator : is12Validator',
|
|
},
|
|
});
|
|
el._createMessageAndRenderFeedback();
|
|
expect(el.$$slot('feedback').innerText).to.equal(
|
|
'my-custom-namespace+is12Validator : is12Validator',
|
|
);
|
|
});
|
|
});
|
|
});
|
|
|
|
describe(`Asynchronous validation [to-be-implemented] ${suffixName}`, () => {
|
|
it('handles promises as custom validator functions [to-be-implemented]', async () => {});
|
|
|
|
it('sets a class "state-pending" when validation is in progress [to-be-implemented]', async () => {});
|
|
|
|
it('debounces async validation for performance [to-be-implemented]', async () => {});
|
|
|
|
it('cancels and reschedules async validation on value change [to-be-implemented]', async () => {});
|
|
|
|
it('blocks input when option "block-on-pending" is set [to-be-implemented]', async () => {
|
|
// This might also be styles on state-pending => disable pointer-events and style as blocked
|
|
});
|
|
|
|
it('lets developer configure condition for asynchronous validation [to-be-implemented]', async () => {
|
|
// This can be blur when input needs to be blocked.
|
|
// Can be implemented as validateAsyncCondition(), returning boolean
|
|
// Will first look at <validatable-field>, then at <validatable-field>.form
|
|
});
|
|
});
|
|
});
|