import { expect, fixture, html, aTimeout, defineCE, unsafeStatic } from '@open-wc/testing'; import sinon from 'sinon'; import { LitElement } from '@lion/core'; import { Unparseable, Validator } from '@lion/validate'; import { FormatMixin } from '../src/FormatMixin.js'; function mimicUserInput(formControl, newViewValue) { formControl.value = newViewValue; // eslint-disable-line no-param-reassign formControl._inputNode.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` `; } set value(newValue) { this._inputNode.value = newValue; } get value() { return this._inputNode.value; } get _inputNode() { 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}" > `); fooFormat = await fixture(html` <${elem} .formatter="${value => `foo: ${value}`}" .parser="${value => value.replace('foo: ', '')}" .serializer="${value => `[foo] ${value}`}" .deserializer="${value => value.replace('[foo] ', '')}" > `); }); it('fires `model-value-changed` for every change on the input', async () => { const formatEl = await fixture(html`<${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}>`); 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 /