chore: add and apply integration suite FormatMixin
This commit is contained in:
parent
af1535b2f5
commit
232a9597fc
11 changed files with 488 additions and 318 deletions
401
packages/field/test-suites/FormatMixin.suite.js
Normal file
401
packages/field/test-suites/FormatMixin.suite.js
Normal file
|
|
@ -0,0 +1,401 @@
|
|||
import { expect, fixture, html, aTimeout, defineCE, unsafeStatic } from '@open-wc/testing';
|
||||
import sinon from 'sinon';
|
||||
|
||||
import { LitElement } from '@lion/core';
|
||||
import { Unparseable } from '@lion/validate';
|
||||
import { FormatMixin } from '../src/FormatMixin.js';
|
||||
|
||||
function mimicUserInput(formControl, newViewValue) {
|
||||
formControl.value = newViewValue; // eslint-disable-line no-param-reassign
|
||||
formControl.inputElement.dispatchEvent(new CustomEvent('input', { bubbles: true }));
|
||||
}
|
||||
|
||||
export function runFormatMixinSuite(customConfig) {
|
||||
// TODO: Maybe remove suffix
|
||||
const cfg = {
|
||||
tagString: null,
|
||||
modelValueType: String,
|
||||
suffix: '',
|
||||
...customConfig,
|
||||
};
|
||||
|
||||
/**
|
||||
* Mocks a value for you based on the data type
|
||||
* Optionally toggles you a different value
|
||||
* for needing to mimic a value-change.
|
||||
*
|
||||
* TODO: The FormatMixin can know about platform types like
|
||||
* Date, but not about modelValue of input-iban etc.
|
||||
* Make this concept expandable by allowing 'non standard'
|
||||
* modelValues to hook into this.
|
||||
*/
|
||||
function generateValueBasedOnType(opts = {}) {
|
||||
const options = { type: cfg.modelValueType, toggleValue: false, viewValue: false, ...opts };
|
||||
|
||||
switch (options.type) {
|
||||
case Number:
|
||||
return !options.toggleValue ? '123' : '456';
|
||||
case Date:
|
||||
// viewValue instead of modelValue, since a Date object is unparseable.
|
||||
// Note: this viewValue needs to point to the same date as the
|
||||
// default returned modelValue.
|
||||
if (options.viewValue) {
|
||||
return !options.toggleValue ? '5-5-2005' : '10-10-2010';
|
||||
}
|
||||
return !options.toggleValue ? new Date('5-5-2005') : new Date('10-10-2010');
|
||||
case Array:
|
||||
return !options.toggleValue ? ['foo', 'bar'] : ['baz', 'yay'];
|
||||
case Object:
|
||||
return !options.toggleValue ? { foo: 'bar' } : { bar: 'foo' };
|
||||
case Boolean:
|
||||
return !options.toggleValue;
|
||||
case 'email':
|
||||
return !options.toggleValue ? 'some-user@ing.com' : 'another-user@ing.com';
|
||||
case 'iban':
|
||||
return !options.toggleValue ? 'SE3550000000054910000003' : 'CH9300762011623852957';
|
||||
default:
|
||||
return !options.toggleValue ? 'foo' : 'bar';
|
||||
}
|
||||
}
|
||||
|
||||
describe(`FormatMixin ${cfg.suffix ? `(${cfg.suffix})` : ''}`, async () => {
|
||||
let elem;
|
||||
let nonFormat;
|
||||
let fooFormat;
|
||||
|
||||
before(async () => {
|
||||
if (!cfg.tagString) {
|
||||
cfg.tagString = defineCE(
|
||||
class extends FormatMixin(LitElement) {
|
||||
render() {
|
||||
return html`
|
||||
<slot name="input"></slot>
|
||||
`;
|
||||
}
|
||||
|
||||
set value(newValue) {
|
||||
this.inputElement.value = newValue;
|
||||
}
|
||||
|
||||
get value() {
|
||||
return this.inputElement.value;
|
||||
}
|
||||
|
||||
get inputElement() {
|
||||
return this.querySelector('input');
|
||||
}
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
elem = unsafeStatic(cfg.tagString);
|
||||
nonFormat = await fixture(html`<${elem}
|
||||
.formatter="${v => v}"
|
||||
.parser="${v => v}"
|
||||
.serializer="${v => v}"
|
||||
.deserializer="${v => v}"
|
||||
><input slot="input">
|
||||
</${elem}>`);
|
||||
fooFormat = await fixture(html`
|
||||
<${elem}
|
||||
.formatter="${value => `foo: ${value}`}"
|
||||
.parser="${value => value.replace('foo: ', '')}"
|
||||
.serializer="${value => `[foo] ${value}`}"
|
||||
.deserializer="${value => value.replace('[foo] ', '')}"
|
||||
><input slot="input">
|
||||
</${elem}>`);
|
||||
});
|
||||
|
||||
it('fires `model-value-changed` for every change on the input', async () => {
|
||||
const formatEl = await fixture(html`<${elem}><input slot="input"></${elem}>`);
|
||||
|
||||
let counter = 0;
|
||||
formatEl.addEventListener('model-value-changed', () => {
|
||||
counter += 1;
|
||||
});
|
||||
|
||||
mimicUserInput(formatEl, generateValueBasedOnType());
|
||||
expect(counter).to.equal(1);
|
||||
|
||||
// Counter offset +1 for Date because parseDate created a new Date object
|
||||
// when the user changes the value.
|
||||
// This will result in a model-value-changed trigger even if the user value was the same
|
||||
// TODO: a proper solution would be to add `hasChanged` to input-date, like isSameDate()
|
||||
// from calendar utils
|
||||
const counterOffset = cfg.modelValueType === Date ? 1 : 0;
|
||||
|
||||
mimicUserInput(formatEl, generateValueBasedOnType());
|
||||
expect(counter).to.equal(1 + counterOffset);
|
||||
|
||||
mimicUserInput(formatEl, generateValueBasedOnType({ toggleValue: true }));
|
||||
expect(counter).to.equal(2 + counterOffset);
|
||||
});
|
||||
|
||||
it('fires `model-value-changed` for every modelValue change', async () => {
|
||||
const el = await fixture(html`<${elem}><input slot="input"></${elem}>`);
|
||||
let counter = 0;
|
||||
el.addEventListener('model-value-changed', () => {
|
||||
counter += 1;
|
||||
});
|
||||
|
||||
el.modelValue = 'one';
|
||||
expect(counter).to.equal(1);
|
||||
|
||||
// no change means no event
|
||||
el.modelValue = 'one';
|
||||
expect(counter).to.equal(1);
|
||||
|
||||
el.modelValue = 'two';
|
||||
expect(counter).to.equal(2);
|
||||
});
|
||||
|
||||
it('has modelValue, formattedValue and serializedValue which are computed synchronously', async () => {
|
||||
expect(nonFormat.modelValue).to.equal('', 'modelValue initially');
|
||||
expect(nonFormat.formattedValue).to.equal('', 'formattedValue initially');
|
||||
expect(nonFormat.serializedValue).to.equal('', 'serializedValue initially');
|
||||
const generatedValue = generateValueBasedOnType();
|
||||
nonFormat.modelValue = generatedValue;
|
||||
expect(nonFormat.modelValue).to.equal(generatedValue, 'modelValue as provided');
|
||||
expect(nonFormat.formattedValue).to.equal(generatedValue, 'formattedValue synchronized');
|
||||
expect(nonFormat.serializedValue).to.equal(generatedValue, 'serializedValue synchronized');
|
||||
});
|
||||
|
||||
it('has an input node (like <input>/<textarea>) which holds the formatted (view) value', async () => {
|
||||
fooFormat.modelValue = 'string';
|
||||
expect(fooFormat.formattedValue).to.equal('foo: string');
|
||||
expect(fooFormat.value).to.equal('foo: string');
|
||||
expect(fooFormat.inputElement.value).to.equal('foo: string');
|
||||
});
|
||||
|
||||
it('converts modelValue => formattedValue (via this.formatter)', async () => {
|
||||
fooFormat.modelValue = 'string';
|
||||
expect(fooFormat.formattedValue).to.equal('foo: string');
|
||||
expect(fooFormat.serializedValue).to.equal('[foo] string');
|
||||
});
|
||||
|
||||
it('converts modelValue => serializedValue (via this.serializer)', async () => {
|
||||
fooFormat.modelValue = 'string';
|
||||
expect(fooFormat.serializedValue).to.equal('[foo] string');
|
||||
});
|
||||
|
||||
it('converts formattedValue => modelValue (via this.parser)', async () => {
|
||||
fooFormat.formattedValue = 'foo: string';
|
||||
expect(fooFormat.modelValue).to.equal('string');
|
||||
});
|
||||
|
||||
it('converts serializedValue => modelValue (via this.deserializer)', async () => {
|
||||
fooFormat.serializedValue = '[foo] string';
|
||||
expect(fooFormat.modelValue).to.equal('string');
|
||||
});
|
||||
|
||||
it('synchronizes inputElement.value as a fallback mechanism', async () => {
|
||||
// Note that in lion-field, the attribute would be put on <lion-field>, not on <input>
|
||||
const formatElem = await fixture(html`
|
||||
<${elem}
|
||||
value="string",
|
||||
.formatter=${value => `foo: ${value}`}
|
||||
.parser=${value => value.replace('foo: ', '')}
|
||||
.serializer=${value => `[foo] ${value}`}
|
||||
.deserializer=${value => value.replace('[foo] ', '')}
|
||||
><input slot="input" value="string"/></${elem}>`);
|
||||
// Now check if the format/parse/serialize loop has been triggered
|
||||
await formatElem.updateComplete;
|
||||
expect(formatElem.formattedValue).to.equal('foo: string');
|
||||
|
||||
expect(formatElem.inputElement.value).to.equal('foo: string');
|
||||
|
||||
expect(formatElem.serializedValue).to.equal('[foo] string');
|
||||
expect(formatElem.modelValue).to.equal('string');
|
||||
});
|
||||
|
||||
it('reflects back formatted value to user on leave', async () => {
|
||||
const formatEl = await fixture(html`
|
||||
<${elem} .formatter="${value => `foo: ${value}`}">
|
||||
<input slot="input" />
|
||||
</${elem}>
|
||||
`);
|
||||
|
||||
const generatedViewValue = generateValueBasedOnType({ viewValue: true });
|
||||
const generatedModelValue = generateValueBasedOnType();
|
||||
mimicUserInput(formatEl, generatedViewValue);
|
||||
expect(formatEl.inputElement.value).to.not.equal(`foo: ${generatedModelValue}`);
|
||||
|
||||
// user leaves field
|
||||
formatEl.inputElement.dispatchEvent(new CustomEvent(formatEl.formatOn, { bubbles: true }));
|
||||
await aTimeout();
|
||||
expect(formatEl.inputElement.value).to.equal(`foo: ${generatedModelValue}`);
|
||||
});
|
||||
|
||||
it('reflects back .formattedValue immediately when .modelValue changed imperatively', async () => {
|
||||
const el = await fixture(html`
|
||||
<${elem} .formatter="${value => `foo: ${value}`}">
|
||||
<input slot="input" />
|
||||
</${elem}>
|
||||
`);
|
||||
// The FormatMixin can be used in conjunction with the ValidateMixin, in which case
|
||||
// it can hold errorState (affecting the formatting)
|
||||
el.errorState = true;
|
||||
|
||||
// users types value 'test'
|
||||
mimicUserInput(el, 'test');
|
||||
expect(el.inputElement.value).to.not.equal('foo: test');
|
||||
|
||||
// Now see the difference for an imperative change
|
||||
el.modelValue = 'test2';
|
||||
expect(el.inputElement.value).to.equal('foo: test2');
|
||||
});
|
||||
|
||||
it('works if there is no underlying inputElement', async () => {
|
||||
const tagNoInputString = defineCE(class extends FormatMixin(LitElement) {});
|
||||
const tagNoInput = unsafeStatic(tagNoInputString);
|
||||
expect(async () => {
|
||||
await fixture(html`<${tagNoInput}></${tagNoInput}>`);
|
||||
}).to.not.throw();
|
||||
});
|
||||
|
||||
describe('parsers/formatters/serializers', () => {
|
||||
it('should call the parser|formatter|serializer provided by user', async () => {
|
||||
const formatterSpy = sinon.spy(value => `foo: ${value}`);
|
||||
const parserSpy = sinon.spy(value => value.replace('foo: ', ''));
|
||||
const serializerSpy = sinon.spy(value => `[foo] ${value}`);
|
||||
const el = await fixture(html`
|
||||
<${elem}
|
||||
.formatter=${formatterSpy}
|
||||
.parser=${parserSpy}
|
||||
.serializer=${serializerSpy}
|
||||
.modelValue=${'test'}
|
||||
>
|
||||
<input slot="input">
|
||||
</${elem}>
|
||||
`);
|
||||
expect(formatterSpy.called).to.equal(true);
|
||||
expect(serializerSpy.called).to.equal(true);
|
||||
|
||||
el.formattedValue = 'raw';
|
||||
expect(parserSpy.called).to.equal(true);
|
||||
});
|
||||
|
||||
it('should have formatOptions available in formatter', async () => {
|
||||
const formatterSpy = sinon.spy(value => `foo: ${value}`);
|
||||
const generatedViewValue = generateValueBasedOnType({ viewValue: true });
|
||||
await fixture(html`
|
||||
<${elem}
|
||||
value="${generatedViewValue}"
|
||||
.formatter="${formatterSpy}"
|
||||
.formatOptions="${{ locale: 'en-GB', decimalSeparator: '-' }}">
|
||||
<input slot="input" value="${generatedViewValue}">
|
||||
</${elem}>
|
||||
`);
|
||||
expect(formatterSpy.args[0][1].locale).to.equal('en-GB');
|
||||
expect(formatterSpy.args[0][1].decimalSeparator).to.equal('-');
|
||||
});
|
||||
|
||||
it('will only call the parser for defined values', async () => {
|
||||
const generatedValue = generateValueBasedOnType();
|
||||
const parserSpy = sinon.spy();
|
||||
const el = await fixture(html`
|
||||
<${elem} .parser="${parserSpy}">
|
||||
<input slot="input" value="${generatedValue}">
|
||||
</${elem}>
|
||||
`);
|
||||
|
||||
expect(parserSpy.callCount).to.equal(1);
|
||||
// This could happen for instance in a reset
|
||||
el.modelValue = undefined;
|
||||
expect(parserSpy.callCount).to.equal(1);
|
||||
// This could happen when the user erases the input value
|
||||
mimicUserInput(el, '');
|
||||
expect(parserSpy.callCount).to.equal(1);
|
||||
});
|
||||
|
||||
it('will not return Unparseable when empty strings are inputted', async () => {
|
||||
const el = await fixture(html`
|
||||
<${elem}>
|
||||
<input slot="input" value="string">
|
||||
</${elem}>
|
||||
`);
|
||||
// This could happen when the user erases the input value
|
||||
mimicUserInput(el, '');
|
||||
// For backwards compatibility, we keep the modelValue an empty string here.
|
||||
// Undefined would be more appropriate 'conceptually', however
|
||||
expect(el.modelValue).to.equal('');
|
||||
});
|
||||
|
||||
it('will only call the formatter for valid values on `user-input-changed` ', async () => {
|
||||
const formatterSpy = sinon.spy(value => `foo: ${value}`);
|
||||
|
||||
const generatedModelValue = generateValueBasedOnType();
|
||||
const generatedViewValue = generateValueBasedOnType({ viewValue: true });
|
||||
const generatedViewValueAlt = generateValueBasedOnType({
|
||||
viewValue: true,
|
||||
toggleValue: true,
|
||||
});
|
||||
|
||||
const el = await fixture(html`
|
||||
<${elem} .formatter=${formatterSpy}>
|
||||
<input slot="input" value="${generatedViewValue}">
|
||||
</${elem}>
|
||||
`);
|
||||
expect(formatterSpy.callCount).to.equal(1);
|
||||
|
||||
el.errorState = true;
|
||||
// Ensure errorState is always true by putting a validator on it that always returns false.
|
||||
// Setting errorState = true is not enough if the element has errorValidators (uses ValidateMixin)
|
||||
// that set errorState back to false when the user input is mimicked.
|
||||
const alwaysInvalidator = () => ({ 'always-invalid': false });
|
||||
el.errorValidators = [alwaysInvalidator];
|
||||
mimicUserInput(el, generatedViewValueAlt);
|
||||
|
||||
expect(formatterSpy.callCount).to.equal(1);
|
||||
// Due to errorState, the formatter should not have ran.
|
||||
expect(el.formattedValue).to.equal(generatedViewValueAlt);
|
||||
|
||||
el.errorState = false;
|
||||
el.errorValidators = [];
|
||||
mimicUserInput(el, generatedViewValue);
|
||||
expect(formatterSpy.callCount).to.equal(2);
|
||||
|
||||
expect(el.formattedValue).to.equal(`foo: ${generatedModelValue}`);
|
||||
});
|
||||
});
|
||||
|
||||
describe('Unparseable values', () => {
|
||||
it('should convert to Unparseable when wrong value inputted by user', async () => {
|
||||
const el = await fixture(html`
|
||||
<${elem}
|
||||
.parser=${viewValue => Number(viewValue) || undefined}
|
||||
>
|
||||
<input slot="input">
|
||||
</${elem}>
|
||||
`);
|
||||
mimicUserInput(el, 'test');
|
||||
expect(el.modelValue).to.be.an.instanceof(Unparseable);
|
||||
});
|
||||
|
||||
it('should preserve the viewValue when not parseable', async () => {
|
||||
const el = await fixture(html`
|
||||
<${elem}
|
||||
.parser=${viewValue => Number(viewValue) || undefined}
|
||||
>
|
||||
<input slot="input">
|
||||
</${elem}>
|
||||
`);
|
||||
mimicUserInput(el, 'test');
|
||||
expect(el.formattedValue).to.equal('test');
|
||||
expect(el.value).to.equal('test');
|
||||
});
|
||||
|
||||
it('should display the viewValue when modelValue is of type Unparseable', async () => {
|
||||
const el = await fixture(html`
|
||||
<${elem}
|
||||
.parser=${viewValue => Number(viewValue) || undefined}
|
||||
>
|
||||
<input slot="input">
|
||||
</${elem}>
|
||||
`);
|
||||
el.modelValue = new Unparseable('foo');
|
||||
expect(el.value).to.equal('foo');
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
|
@ -1,313 +1,3 @@
|
|||
import { expect, fixture, html, aTimeout, defineCE, unsafeStatic } from '@open-wc/testing';
|
||||
import sinon from 'sinon';
|
||||
import { runFormatMixinSuite } from '../test-suites/FormatMixin.suite.js';
|
||||
|
||||
import { LitElement } from '@lion/core';
|
||||
import { Unparseable } from '@lion/validate';
|
||||
import { FormatMixin } from '../src/FormatMixin.js';
|
||||
|
||||
function mimicUserInput(formControl, newViewValue) {
|
||||
formControl.value = newViewValue; // eslint-disable-line no-param-reassign
|
||||
formControl.inputElement.dispatchEvent(new CustomEvent('input', { bubbles: true }));
|
||||
}
|
||||
|
||||
describe('FormatMixin', () => {
|
||||
let elem;
|
||||
let nonFormat;
|
||||
let fooFormat;
|
||||
|
||||
before(async () => {
|
||||
const tagString = defineCE(
|
||||
class extends FormatMixin(LitElement) {
|
||||
render() {
|
||||
return html`
|
||||
<slot name="input"></slot>
|
||||
`;
|
||||
}
|
||||
|
||||
set value(newValue) {
|
||||
this.inputElement.value = newValue;
|
||||
}
|
||||
|
||||
get value() {
|
||||
return this.inputElement.value;
|
||||
}
|
||||
|
||||
get inputElement() {
|
||||
return this.querySelector('input');
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
elem = unsafeStatic(tagString);
|
||||
nonFormat = await fixture(html`<${elem}><input slot="input"></${elem}>`);
|
||||
fooFormat = await fixture(html`
|
||||
<${elem}
|
||||
.formatter="${value => `foo: ${value}`}"
|
||||
.parser="${value => value.replace('foo: ', '')}"
|
||||
.serializer="${value => `[foo] ${value}`}"
|
||||
.deserializer="${value => value.replace('[foo] ', '')}"
|
||||
><input slot="input">
|
||||
</${elem}>`);
|
||||
});
|
||||
|
||||
it('fires `model-value-changed` for every change on the input', async () => {
|
||||
const formatEl = await fixture(html`<${elem}><input slot="input"></${elem}>`);
|
||||
let counter = 0;
|
||||
formatEl.addEventListener('model-value-changed', () => {
|
||||
counter += 1;
|
||||
});
|
||||
|
||||
mimicUserInput(formatEl, 'one');
|
||||
expect(counter).to.equal(1);
|
||||
|
||||
// no change means no event
|
||||
mimicUserInput(formatEl, 'one');
|
||||
expect(counter).to.equal(1);
|
||||
|
||||
mimicUserInput(formatEl, 'two');
|
||||
expect(counter).to.equal(2);
|
||||
});
|
||||
|
||||
it('fires `model-value-changed` for every modelValue change', async () => {
|
||||
const el = await fixture(html`<${elem}><input slot="input"></${elem}>`);
|
||||
let counter = 0;
|
||||
el.addEventListener('model-value-changed', () => {
|
||||
counter += 1;
|
||||
});
|
||||
|
||||
el.modelValue = 'one';
|
||||
expect(counter).to.equal(1);
|
||||
|
||||
// no change means no event
|
||||
el.modelValue = 'one';
|
||||
expect(counter).to.equal(1);
|
||||
|
||||
el.modelValue = 'two';
|
||||
expect(counter).to.equal(2);
|
||||
});
|
||||
|
||||
it('has modelValue, formattedValue and serializedValue which are computed synchronously', async () => {
|
||||
expect(nonFormat.modelValue).to.equal('', 'modelValue initially');
|
||||
expect(nonFormat.formattedValue).to.equal('', 'formattedValue initially');
|
||||
expect(nonFormat.serializedValue).to.equal('', 'serializedValue initially');
|
||||
nonFormat.modelValue = 'string';
|
||||
expect(nonFormat.modelValue).to.equal('string', 'modelValue as provided');
|
||||
expect(nonFormat.formattedValue).to.equal('string', 'formattedValue synchronized');
|
||||
expect(nonFormat.serializedValue).to.equal('string', 'serializedValue synchronized');
|
||||
});
|
||||
|
||||
it('has an input node (like <input>/<textarea>) which holds the formatted (view) value', async () => {
|
||||
fooFormat.modelValue = 'string';
|
||||
expect(fooFormat.formattedValue).to.equal('foo: string');
|
||||
expect(fooFormat.value).to.equal('foo: string');
|
||||
expect(fooFormat.inputElement.value).to.equal('foo: string');
|
||||
});
|
||||
|
||||
it('converts modelValue => formattedValue (via this.formatter)', async () => {
|
||||
fooFormat.modelValue = 'string';
|
||||
expect(fooFormat.formattedValue).to.equal('foo: string');
|
||||
expect(fooFormat.serializedValue).to.equal('[foo] string');
|
||||
});
|
||||
|
||||
it('converts modelValue => serializedValue (via this.serializer)', async () => {
|
||||
fooFormat.modelValue = 'string';
|
||||
expect(fooFormat.serializedValue).to.equal('[foo] string');
|
||||
});
|
||||
|
||||
it('converts formattedValue => modelValue (via this.parser)', async () => {
|
||||
fooFormat.formattedValue = 'foo: string';
|
||||
expect(fooFormat.modelValue).to.equal('string');
|
||||
});
|
||||
|
||||
it('converts serializedValue => modelValue (via this.deserializer)', async () => {
|
||||
fooFormat.serializedValue = '[foo] string';
|
||||
expect(fooFormat.modelValue).to.equal('string');
|
||||
});
|
||||
|
||||
it('synchronizes inputElement.value as a fallback mechanism', async () => {
|
||||
// Note that in lion-field, the attribute would be put on <lion-field>, not on <input>
|
||||
const formatElem = await fixture(html`
|
||||
<${elem}
|
||||
value="string",
|
||||
.formatter=${value => `foo: ${value}`}
|
||||
.parser=${value => value.replace('foo: ', '')}
|
||||
.serializer=${value => `[foo] ${value}`}
|
||||
.deserializer=${value => value.replace('[foo] ', '')}
|
||||
><input slot="input" value="string"/></${elem}>`);
|
||||
// Now check if the format/parse/serialize loop has been triggered
|
||||
await aTimeout();
|
||||
expect(formatElem.formattedValue).to.equal('foo: string');
|
||||
expect(formatElem.inputElement.value).to.equal('foo: string');
|
||||
expect(formatElem.serializedValue).to.equal('[foo] string');
|
||||
expect(formatElem.modelValue).to.equal('string');
|
||||
});
|
||||
|
||||
it('reflects back formatted value to user on leave', async () => {
|
||||
const formatEl = await fixture(html`
|
||||
<${elem} .formatter="${value => `foo: ${value}`}">
|
||||
<input slot="input" />
|
||||
</${elem}>
|
||||
`);
|
||||
// users types value 'test'
|
||||
mimicUserInput(formatEl, 'test');
|
||||
expect(formatEl.inputElement.value).to.not.equal('foo: test');
|
||||
// user leaves field
|
||||
formatEl.inputElement.dispatchEvent(new CustomEvent(formatEl.formatOn, { bubbles: true }));
|
||||
await aTimeout();
|
||||
expect(formatEl.inputElement.value).to.equal('foo: test');
|
||||
});
|
||||
|
||||
it('reflects back .formattedValue immediately when .modelValue changed imperatively', async () => {
|
||||
const el = await fixture(html`
|
||||
<${elem} .formatter="${value => `foo: ${value}`}">
|
||||
<input slot="input" />
|
||||
</${elem}>
|
||||
`);
|
||||
// The FormatMixin can be used in conjunction with the ValidateMixin, in which case
|
||||
// it can hold errorState (affecting the formatting)
|
||||
el.errorState = true;
|
||||
|
||||
// users types value 'test'
|
||||
mimicUserInput(el, 'test');
|
||||
expect(el.inputElement.value).to.not.equal('foo: test');
|
||||
|
||||
// Now see the difference for an imperative change
|
||||
el.modelValue = 'test2';
|
||||
expect(el.inputElement.value).to.equal('foo: test2');
|
||||
});
|
||||
|
||||
it('works if there is no underlying inputElement', async () => {
|
||||
const tagNoInputString = defineCE(class extends FormatMixin(LitElement) {});
|
||||
const tagNoInput = unsafeStatic(tagNoInputString);
|
||||
expect(async () => {
|
||||
await fixture(html`<${tagNoInput}></${tagNoInput}>`);
|
||||
}).to.not.throw();
|
||||
});
|
||||
|
||||
describe('parsers/formatters/serializers', () => {
|
||||
it('should call the parser|formatter|serializer provided by user', async () => {
|
||||
const formatterSpy = sinon.spy(value => `foo: ${value}`);
|
||||
const parserSpy = sinon.spy(value => value.replace('foo: ', ''));
|
||||
const serializerSpy = sinon.spy(value => `[foo] ${value}`);
|
||||
const el = await fixture(html`
|
||||
<${elem}
|
||||
.formatter=${formatterSpy}
|
||||
.parser=${parserSpy}
|
||||
.serializer=${serializerSpy}
|
||||
.modelValue=${'test'}
|
||||
>
|
||||
<input slot="input">
|
||||
</${elem}>
|
||||
`);
|
||||
expect(formatterSpy.called).to.equal(true);
|
||||
expect(serializerSpy.called).to.equal(true);
|
||||
|
||||
el.formattedValue = 'raw';
|
||||
expect(parserSpy.called).to.equal(true);
|
||||
});
|
||||
|
||||
it('should have formatOptions available in formatter', async () => {
|
||||
const formatterSpy = sinon.spy(value => `foo: ${value}`);
|
||||
await fixture(html`
|
||||
<${elem}
|
||||
value="string",
|
||||
.formatter="${formatterSpy}"
|
||||
.formatOptions="${{ locale: 'en-GB', decimalSeparator: '-' }}">
|
||||
<input slot="input" value="string">
|
||||
</${elem}>`);
|
||||
await aTimeout();
|
||||
expect(formatterSpy.args[0][1].locale).to.equal('en-GB');
|
||||
expect(formatterSpy.args[0][1].decimalSeparator).to.equal('-');
|
||||
});
|
||||
|
||||
it('will only call the parser for defined values', async () => {
|
||||
const parserSpy = sinon.spy();
|
||||
const el = await fixture(html`
|
||||
<${elem} .parser="${parserSpy}">
|
||||
<input slot="input" value="string">
|
||||
</${elem}>
|
||||
`);
|
||||
el.modelValue = 'foo';
|
||||
expect(parserSpy.callCount).to.equal(1);
|
||||
// This could happen for instance in a reset
|
||||
el.modelValue = undefined;
|
||||
expect(parserSpy.callCount).to.equal(1);
|
||||
// This could happen when the user erases the input value
|
||||
mimicUserInput(el, '');
|
||||
expect(parserSpy.callCount).to.equal(1);
|
||||
});
|
||||
|
||||
it('will not return Unparseable when empty strings are inputted', async () => {
|
||||
const el = await fixture(html`
|
||||
<${elem}>
|
||||
<input slot="input" value="string">
|
||||
</${elem}>
|
||||
`);
|
||||
// This could happen when the user erases the input value
|
||||
mimicUserInput(el, '');
|
||||
// For backwards compatibility, we keep the modelValue an empty string here.
|
||||
// Undefined would be more appropriate 'conceptually', however
|
||||
expect(el.modelValue).to.equal('');
|
||||
});
|
||||
|
||||
it('will only call the formatter for valid values on `user-input-changed` ', async () => {
|
||||
const formatterSpy = sinon.spy(value => `foo: ${value}`);
|
||||
const el = await fixture(html`
|
||||
<${elem} .formatter=${formatterSpy}>
|
||||
<input slot="input" value="init-string">
|
||||
</${elem}>
|
||||
`);
|
||||
expect(formatterSpy.callCount).to.equal(1);
|
||||
|
||||
el.errorState = true;
|
||||
mimicUserInput(el, 'bar');
|
||||
expect(formatterSpy.callCount).to.equal(1);
|
||||
expect(el.formattedValue).to.equal('bar');
|
||||
|
||||
el.errorState = false;
|
||||
mimicUserInput(el, 'bar2');
|
||||
expect(formatterSpy.callCount).to.equal(2);
|
||||
|
||||
expect(el.formattedValue).to.equal('foo: bar2');
|
||||
});
|
||||
});
|
||||
|
||||
describe('Unparseable values', () => {
|
||||
it('should convert to Unparseable when wrong value inputted by user', async () => {
|
||||
const el = await fixture(html`
|
||||
<${elem}
|
||||
.parser=${viewValue => Number(viewValue) || undefined}
|
||||
>
|
||||
<input slot="input">
|
||||
</${elem}>
|
||||
`);
|
||||
mimicUserInput(el, 'test');
|
||||
expect(el.modelValue).to.be.an.instanceof(Unparseable);
|
||||
});
|
||||
|
||||
it('should preserve the viewValue when not parseable', async () => {
|
||||
const el = await fixture(html`
|
||||
<${elem}
|
||||
.parser=${viewValue => Number(viewValue) || undefined}
|
||||
>
|
||||
<input slot="input">
|
||||
</${elem}>
|
||||
`);
|
||||
mimicUserInput(el, 'test');
|
||||
expect(el.formattedValue).to.equal('test');
|
||||
expect(el.value).to.equal('test');
|
||||
});
|
||||
|
||||
it('should display the viewValue when modelValue is of type Unparseable', async () => {
|
||||
const el = await fixture(html`
|
||||
<${elem}
|
||||
.parser=${viewValue => Number(viewValue) || undefined}
|
||||
>
|
||||
<input slot="input">
|
||||
</${elem}>
|
||||
`);
|
||||
el.modelValue = new Unparseable('foo');
|
||||
expect(el.value).to.equal('foo');
|
||||
});
|
||||
});
|
||||
});
|
||||
runFormatMixinSuite();
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
import { defineCE } from '@open-wc/testing';
|
||||
import { runInteractionStateMixinSuite } from '../test-suites/InteractionStateMixin.suite.js';
|
||||
import '../lion-field.js';
|
||||
import { runFormatMixinSuite } from '../test-suites/FormatMixin.suite.js';
|
||||
|
||||
const fieldTagString = defineCE(
|
||||
class extends customElements.get('lion-field') {
|
||||
|
|
@ -19,4 +20,8 @@ describe('<lion-field> integrations', () => {
|
|||
tagString: fieldTagString,
|
||||
suffix: 'lion-field',
|
||||
});
|
||||
|
||||
runFormatMixinSuite({
|
||||
tagString: fieldTagString,
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -0,0 +1,18 @@
|
|||
import { runInteractionStateMixinSuite } from '@lion/field/test-suites/InteractionStateMixin.suite.js';
|
||||
import { runFormatMixinSuite } from '@lion/field/test-suites/FormatMixin.suite.js';
|
||||
import '../lion-input-amount.js';
|
||||
|
||||
const tagString = 'lion-input-amount';
|
||||
|
||||
describe('<lion-input-amount> integrations', () => {
|
||||
runInteractionStateMixinSuite({
|
||||
tagString,
|
||||
suffix: 'lion-input-amount',
|
||||
allowedModelValueTypes: [Number],
|
||||
});
|
||||
|
||||
runFormatMixinSuite({
|
||||
tagString,
|
||||
modelValueType: Number,
|
||||
});
|
||||
});
|
||||
|
|
@ -1,10 +1,18 @@
|
|||
import { runInteractionStateMixinSuite } from '@lion/field/test-suites/InteractionStateMixin.suite.js';
|
||||
import { runFormatMixinSuite } from '@lion/field/test-suites/FormatMixin.suite.js';
|
||||
import '../lion-input-date.js';
|
||||
|
||||
const tagString = 'lion-input-date';
|
||||
|
||||
describe('<lion-input-date> integrations', () => {
|
||||
runInteractionStateMixinSuite({
|
||||
tagString: 'lion-input-date',
|
||||
suffix: 'lion-input-date',
|
||||
tagString,
|
||||
suffix: tagString,
|
||||
allowedModelValueTypes: [Date],
|
||||
});
|
||||
|
||||
runFormatMixinSuite({
|
||||
tagString,
|
||||
modelValueType: Date,
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -1,10 +1,17 @@
|
|||
import { runInteractionStateMixinSuite } from '@lion/field/test-suites/InteractionStateMixin.suite.js';
|
||||
import { runFormatMixinSuite } from '@lion/field/test-suites/FormatMixin.suite.js';
|
||||
import '../lion-input-datepicker.js';
|
||||
|
||||
const tagString = 'lion-input-datepicker';
|
||||
describe('<lion-input-datepicker> integrations', () => {
|
||||
runInteractionStateMixinSuite({
|
||||
tagString: 'lion-input-datepicker',
|
||||
suffix: 'lion-input-datepicker',
|
||||
tagString,
|
||||
suffix: tagString,
|
||||
allowedModelValueTypes: [Date],
|
||||
});
|
||||
|
||||
runFormatMixinSuite({
|
||||
tagString,
|
||||
modelValueType: Date,
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -0,0 +1,11 @@
|
|||
import { runFormatMixinSuite } from '@lion/field/test-suites/FormatMixin.suite.js';
|
||||
import '../lion-input-email.js';
|
||||
|
||||
const tagString = 'lion-input-email';
|
||||
|
||||
describe('<lion-input-email> integrations', () => {
|
||||
runFormatMixinSuite({
|
||||
tagString,
|
||||
modelValueType: 'email',
|
||||
});
|
||||
});
|
||||
|
|
@ -1,6 +1,8 @@
|
|||
import { isValidIBAN } from '@bundled-es-modules/ibantools/ibantools.js';
|
||||
|
||||
export const isIBAN = value => isValidIBAN(value);
|
||||
export const isIBAN = value => {
|
||||
return isValidIBAN(value);
|
||||
};
|
||||
|
||||
export const isIBANValidator = () => [(...params) => ({ isIBAN: isIBAN(...params) })];
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,11 @@
|
|||
import { runFormatMixinSuite } from '@lion/field/test-suites/FormatMixin.suite.js';
|
||||
import '../lion-input-iban.js';
|
||||
|
||||
const tagString = 'lion-input-iban';
|
||||
|
||||
describe('<lion-input-iban> integrations', () => {
|
||||
runFormatMixinSuite({
|
||||
tagString,
|
||||
modelValueType: 'iban',
|
||||
});
|
||||
});
|
||||
|
|
@ -1,9 +1,16 @@
|
|||
import { runInteractionStateMixinSuite } from '@lion/field/test-suites/InteractionStateMixin.suite.js';
|
||||
import { runFormatMixinSuite } from '@lion/field/test-suites/FormatMixin.suite.js';
|
||||
import '../lion-input.js';
|
||||
|
||||
const tagString = 'lion-input';
|
||||
|
||||
describe('<lion-input> integrations', () => {
|
||||
runInteractionStateMixinSuite({
|
||||
tagString: 'lion-input',
|
||||
tagString,
|
||||
suffix: 'lion-input',
|
||||
});
|
||||
|
||||
runFormatMixinSuite({
|
||||
tagString,
|
||||
});
|
||||
});
|
||||
|
|
|
|||
10
packages/textarea/test/lion-input-iban-integrations.test.js
Normal file
10
packages/textarea/test/lion-input-iban-integrations.test.js
Normal file
|
|
@ -0,0 +1,10 @@
|
|||
import { runFormatMixinSuite } from '@lion/field/test-suites/FormatMixin.suite.js';
|
||||
import '../lion-textarea.js';
|
||||
|
||||
const tagString = 'lion-textarea';
|
||||
|
||||
describe('<lion-textarea> integrations', () => {
|
||||
runFormatMixinSuite({
|
||||
tagString,
|
||||
});
|
||||
});
|
||||
Loading…
Reference in a new issue