chore(form-core): make use of ChoiceInput & ChoiceGroup test suites
This commit is contained in:
parent
3944c5e8cf
commit
8f1456c8cb
11 changed files with 603 additions and 367 deletions
|
|
@ -0,0 +1,9 @@
|
||||||
|
import { runChoiceGroupMixinSuite } from '@lion/form-core/test-suites/choice-group/ChoiceGroupMixin.suite.js';
|
||||||
|
import '../lion-checkbox-group.js';
|
||||||
|
import '../lion-checkbox.js';
|
||||||
|
|
||||||
|
runChoiceGroupMixinSuite({
|
||||||
|
parentTagString: 'lion-checkbox-group',
|
||||||
|
childTagString: 'lion-checkbox',
|
||||||
|
choiceType: 'multiple',
|
||||||
|
});
|
||||||
|
|
@ -15,6 +15,51 @@ beforeEach(() => {
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('<lion-checkbox-group>', () => {
|
describe('<lion-checkbox-group>', () => {
|
||||||
|
describe('resetGroup', () => {
|
||||||
|
// TODO move to FormGroupMixin test suite and let CheckboxGroup make use of them
|
||||||
|
it('restores default values of arrays if changes were made', async () => {
|
||||||
|
const el = await fixture(html`
|
||||||
|
<lion-checkbox-group name="scientists[]" label="Favorite scientists">
|
||||||
|
<lion-checkbox label="Archimedes" .choiceValue=${'Archimedes'}></lion-checkbox>
|
||||||
|
<lion-checkbox label="Francis Bacon" .choiceValue=${'Francis Bacon'}></lion-checkbox>
|
||||||
|
<lion-checkbox
|
||||||
|
label="Marie Curie"
|
||||||
|
.modelValue=${{ value: 'Marie Curie', checked: false }}
|
||||||
|
></lion-checkbox>
|
||||||
|
</lion-checkbox-group>
|
||||||
|
`);
|
||||||
|
el.formElements[0].checked = true;
|
||||||
|
expect(el.modelValue).to.deep.equal(['Archimedes']);
|
||||||
|
|
||||||
|
el.resetGroup();
|
||||||
|
expect(el.modelValue).to.deep.equal([]);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('restores default values of arrays if changes were made', async () => {
|
||||||
|
const el = await fixture(html`
|
||||||
|
<lion-checkbox-group name="scientists[]" label="Favorite scientists">
|
||||||
|
<lion-checkbox label="Archimedes" .choiceValue=${'Archimedes'}></lion-checkbox>
|
||||||
|
<lion-checkbox
|
||||||
|
label="Francis Bacon"
|
||||||
|
.modelValue=${{ value: 'Francis Bacon', checked: true }}
|
||||||
|
></lion-checkbox>
|
||||||
|
<lion-checkbox label="Marie Curie" .choiceValue=${'Marie Curie'}></lion-checkbox>
|
||||||
|
</lion-checkbox-group>
|
||||||
|
`);
|
||||||
|
el.formElements[0].checked = true;
|
||||||
|
expect(el.modelValue).to.deep.equal(['Archimedes', 'Francis Bacon']);
|
||||||
|
|
||||||
|
el.resetGroup();
|
||||||
|
expect(el.modelValue).to.deep.equal(['Francis Bacon']);
|
||||||
|
|
||||||
|
el.formElements[2].checked = true;
|
||||||
|
expect(el.modelValue).to.deep.equal(['Francis Bacon', 'Marie Curie']);
|
||||||
|
|
||||||
|
el.resetGroup();
|
||||||
|
expect(el.modelValue).to.deep.equal(['Francis Bacon']);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
it('is accessible', async () => {
|
it('is accessible', async () => {
|
||||||
const el = await fixture(html`
|
const el = await fixture(html`
|
||||||
<lion-checkbox-group name="scientists[]" label="Who are your favorite scientists?">
|
<lion-checkbox-group name="scientists[]" label="Who are your favorite scientists?">
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,4 @@
|
||||||
|
import '../lion-checkbox.js';
|
||||||
|
import { runChoiceInputMixinSuite } from '@lion/form-core/test-suites/choice-group/ChoiceInputMixin.suite.js';
|
||||||
|
|
||||||
|
runChoiceInputMixinSuite({ tagString: 'lion-checkbox' });
|
||||||
39
packages/checkbox-group/test/lion-checkbox.test.js
Normal file
39
packages/checkbox-group/test/lion-checkbox.test.js
Normal file
|
|
@ -0,0 +1,39 @@
|
||||||
|
import { expect, fixture, html } from '@open-wc/testing';
|
||||||
|
import '../lion-checkbox.js';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @typedef {import('../src/LionCheckbox').LionCheckbox} LionCheckbox
|
||||||
|
*/
|
||||||
|
|
||||||
|
describe('<lion-checkbox>', () => {
|
||||||
|
it('should have type = checkbox', async () => {
|
||||||
|
const el = await fixture(html`
|
||||||
|
<lion-checkbox name="checkbox" .choiceValue="${'male'}"></lion-checkbox>
|
||||||
|
`);
|
||||||
|
expect(el.getAttribute('type')).to.equal('checkbox');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('can be reset when unchecked by default', async () => {
|
||||||
|
const el = /** @type {LionCheckbox} */ (await fixture(html`
|
||||||
|
<lion-checkbox name="checkbox" .choiceValue=${'male'}></lion-checkbox>
|
||||||
|
`));
|
||||||
|
expect(el._initialModelValue).to.deep.equal({ value: 'male', checked: false });
|
||||||
|
el.checked = true;
|
||||||
|
expect(el.modelValue).to.deep.equal({ value: 'male', checked: true });
|
||||||
|
|
||||||
|
el.reset();
|
||||||
|
expect(el.modelValue).to.deep.equal({ value: 'male', checked: false });
|
||||||
|
});
|
||||||
|
|
||||||
|
it('can be reset when checked by default', async () => {
|
||||||
|
const el = /** @type {LionCheckbox} */ (await fixture(html`
|
||||||
|
<lion-checkbox name="checkbox" .choiceValue=${'male'} checked></lion-checkbox>
|
||||||
|
`));
|
||||||
|
expect(el._initialModelValue).to.deep.equal({ value: 'male', checked: true });
|
||||||
|
el.checked = false;
|
||||||
|
expect(el.modelValue).to.deep.equal({ value: 'male', checked: false });
|
||||||
|
|
||||||
|
el.reset();
|
||||||
|
expect(el.modelValue).to.deep.equal({ value: 'male', checked: true });
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
@ -13,21 +13,23 @@ class ChoiceGroup extends ChoiceGroupMixin(FormGroupMixin(LitElement)) {}
|
||||||
customElements.define('choice-group', ChoiceGroup);
|
customElements.define('choice-group', ChoiceGroup);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param {{ parentTagString?:string, childTagString?: string}} [config]
|
* @param {{ parentTagString?:string, childTagString?: string, choiceType?: string}} [config]
|
||||||
*/
|
*/
|
||||||
export function runChoiceGroupMixinSuite({ parentTagString, childTagString } = {}) {
|
export function runChoiceGroupMixinSuite({ parentTagString, childTagString, choiceType } = {}) {
|
||||||
const cfg = {
|
const cfg = {
|
||||||
parentTagString: parentTagString || 'choice-group',
|
parentTagString: parentTagString || 'choice-group',
|
||||||
childTagString: childTagString || 'choice-group-input',
|
childTagString: childTagString || 'choice-group-input',
|
||||||
|
choiceType: choiceType || 'single',
|
||||||
};
|
};
|
||||||
|
|
||||||
const parentTag = unsafeStatic(cfg.parentTagString);
|
const parentTag = unsafeStatic(cfg.parentTagString);
|
||||||
const childTag = unsafeStatic(cfg.childTagString);
|
const childTag = unsafeStatic(cfg.childTagString);
|
||||||
|
|
||||||
describe('ChoiceGroupMixin', () => {
|
describe(`ChoiceGroupMixin: ${cfg.parentTagString}`, () => {
|
||||||
|
if (cfg.choiceType === 'single') {
|
||||||
it('has a single modelValue representing the currently checked radio value', async () => {
|
it('has a single modelValue representing the currently checked radio value', async () => {
|
||||||
const el = /** @type {ChoiceGroup} */ (await fixture(html`
|
const el = /** @type {ChoiceGroup} */ (await fixture(html`
|
||||||
<${parentTag} name="gender">
|
<${parentTag} name="gender[]">
|
||||||
<${childTag} .choiceValue=${'male'}></${childTag}>
|
<${childTag} .choiceValue=${'male'}></${childTag}>
|
||||||
<${childTag} .choiceValue=${'female'} checked></${childTag}>
|
<${childTag} .choiceValue=${'female'} checked></${childTag}>
|
||||||
<${childTag} .choiceValue=${'other'}></${childTag}>
|
<${childTag} .choiceValue=${'other'}></${childTag}>
|
||||||
|
|
@ -54,10 +56,11 @@ export function runChoiceGroupMixinSuite({ parentTagString, childTagString } = {
|
||||||
el.formElements[2].checked = true;
|
el.formElements[2].checked = true;
|
||||||
expect(el.formattedValue).to.equal('other');
|
expect(el.formattedValue).to.equal('other');
|
||||||
});
|
});
|
||||||
|
}
|
||||||
|
|
||||||
it('throws if a child element without a modelValue like { value: "foo", checked: false } tries to register', async () => {
|
it('throws if a child element without a modelValue like { value: "foo", checked: false } tries to register', async () => {
|
||||||
const el = /** @type {ChoiceGroup} */ (await fixture(html`
|
const el = /** @type {ChoiceGroup} */ (await fixture(html`
|
||||||
<${parentTag} name="gender">
|
<${parentTag} name="gender[]">
|
||||||
<${childTag} .choiceValue=${'male'}></${childTag}>
|
<${childTag} .choiceValue=${'male'}></${childTag}>
|
||||||
<${childTag} .choiceValue=${'female'} checked></${childTag}>
|
<${childTag} .choiceValue=${'female'} checked></${childTag}>
|
||||||
<${childTag} .choiceValue=${'other'}></${childTag}>
|
<${childTag} .choiceValue=${'other'}></${childTag}>
|
||||||
|
|
@ -70,68 +73,68 @@ export function runChoiceGroupMixinSuite({ parentTagString, childTagString } = {
|
||||||
expect(() => {
|
expect(() => {
|
||||||
el.addFormElement(invalidChild);
|
el.addFormElement(invalidChild);
|
||||||
}).to.throw(
|
}).to.throw(
|
||||||
'The choice-group name="gender" does not allow to register choice-group-input with .modelValue="Lara" - The modelValue should represent an Object { value: "foo", checked: false }',
|
`The ${cfg.parentTagString} name="gender[]" does not allow to register ${cfg.childTagString} with .modelValue="Lara" - The modelValue should represent an Object { value: "foo", checked: false }`,
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('automatically sets the name property of child fields to its own name', async () => {
|
it('automatically sets the name property of child fields to its own name', async () => {
|
||||||
const el = /** @type {ChoiceGroup} */ (await fixture(html`
|
const el = /** @type {ChoiceGroup} */ (await fixture(html`
|
||||||
<${parentTag} name="gender">
|
<${parentTag} name="gender[]">
|
||||||
<${childTag} .choiceValue=${'female'} checked></${childTag}>
|
<${childTag} .choiceValue=${'female'} checked></${childTag}>
|
||||||
<${childTag} .choiceValue=${'other'}></${childTag}>
|
<${childTag} .choiceValue=${'other'}></${childTag}>
|
||||||
</${parentTag}>
|
</${parentTag}>
|
||||||
`));
|
`));
|
||||||
|
|
||||||
expect(el.formElements[0].name).to.equal('gender');
|
expect(el.formElements[0].name).to.equal('gender[]');
|
||||||
expect(el.formElements[1].name).to.equal('gender');
|
expect(el.formElements[1].name).to.equal('gender[]');
|
||||||
|
|
||||||
const validChild = /** @type {ChoiceGroup} */ (await fixture(html`
|
const validChild = /** @type {ChoiceGroup} */ (await fixture(html`
|
||||||
<${childTag} .choiceValue=${'male'}></${childTag}>
|
<${childTag} .choiceValue=${'male'}></${childTag}>
|
||||||
`));
|
`));
|
||||||
el.appendChild(validChild);
|
el.appendChild(validChild);
|
||||||
|
|
||||||
expect(el.formElements[2].name).to.equal('gender');
|
expect(el.formElements[2].name).to.equal('gender[]');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('automatically updates the name property of child fields to its own name', async () => {
|
it('automatically updates the name property of child fields to its own name', async () => {
|
||||||
const el = /** @type {ChoiceGroup} */ (await fixture(html`
|
const el = /** @type {ChoiceGroup} */ (await fixture(html`
|
||||||
<${parentTag} name="gender">
|
<${parentTag} name="gender[]">
|
||||||
<${childTag}></${childTag}>
|
<${childTag}></${childTag}>
|
||||||
<${childTag}></${childTag}>
|
<${childTag}></${childTag}>
|
||||||
</${parentTag}>
|
</${parentTag}>
|
||||||
`));
|
`));
|
||||||
|
|
||||||
expect(el.formElements[0].name).to.equal('gender');
|
expect(el.formElements[0].name).to.equal('gender[]');
|
||||||
expect(el.formElements[1].name).to.equal('gender');
|
expect(el.formElements[1].name).to.equal('gender[]');
|
||||||
|
|
||||||
el.name = 'gender2';
|
el.name = 'gender2[]';
|
||||||
|
|
||||||
await el.updateComplete;
|
await el.updateComplete;
|
||||||
|
|
||||||
expect(el.formElements[0].name).to.equal('gender2');
|
expect(el.formElements[0].name).to.equal('gender2[]');
|
||||||
expect(el.formElements[1].name).to.equal('gender2');
|
expect(el.formElements[1].name).to.equal('gender2[]');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('prevents updating the name property of a child if it is different from its parent', async () => {
|
it('prevents updating the name property of a child if it is different from its parent', async () => {
|
||||||
const el = /** @type {ChoiceGroup} */ (await fixture(html`
|
const el = /** @type {ChoiceGroup} */ (await fixture(html`
|
||||||
<${parentTag} name="gender">
|
<${parentTag} name="gender[]">
|
||||||
<${childTag}></${childTag}>
|
<${childTag}></${childTag}>
|
||||||
<${childTag}></${childTag}>
|
<${childTag}></${childTag}>
|
||||||
</${parentTag}>
|
</${parentTag}>
|
||||||
`));
|
`));
|
||||||
|
|
||||||
expect(el.formElements[0].name).to.equal('gender');
|
expect(el.formElements[0].name).to.equal('gender[]');
|
||||||
expect(el.formElements[1].name).to.equal('gender');
|
expect(el.formElements[1].name).to.equal('gender[]');
|
||||||
|
|
||||||
el.formElements[0].name = 'gender2';
|
el.formElements[0].name = 'gender2[]';
|
||||||
|
|
||||||
await el.formElements[0].updateComplete;
|
await el.formElements[0].updateComplete;
|
||||||
expect(el.formElements[0].name).to.equal('gender');
|
expect(el.formElements[0].name).to.equal('gender[]');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('adjusts the name of a child element if it has a different name than the group', async () => {
|
it('adjusts the name of a child element if it has a different name than the group', async () => {
|
||||||
const el = /** @type {ChoiceGroup} */ (await fixture(html`
|
const el = /** @type {ChoiceGroup} */ (await fixture(html`
|
||||||
<${parentTag} name="gender">
|
<${parentTag} name="gender[]">
|
||||||
<${childTag} .choiceValue=${'female'} checked></${childTag}>
|
<${childTag} .choiceValue=${'female'} checked></${childTag}>
|
||||||
<${childTag} .choiceValue=${'other'}></${childTag}>
|
<${childTag} .choiceValue=${'other'}></${childTag}>
|
||||||
</${parentTag}>
|
</${parentTag}>
|
||||||
|
|
@ -142,45 +145,57 @@ export function runChoiceGroupMixinSuite({ parentTagString, childTagString } = {
|
||||||
`));
|
`));
|
||||||
el.addFormElement(invalidChild);
|
el.addFormElement(invalidChild);
|
||||||
await invalidChild.updateComplete;
|
await invalidChild.updateComplete;
|
||||||
expect(invalidChild.name).to.equal('gender');
|
expect(invalidChild.name).to.equal('gender[]');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('can set initial modelValue on creation', async () => {
|
it('can set initial modelValue on creation', async () => {
|
||||||
const el = /** @type {ChoiceGroup} */ (await fixture(html`
|
const el = /** @type {ChoiceGroup} */ (await fixture(html`
|
||||||
<${parentTag} name="gender" .modelValue=${'other'}>
|
<${parentTag} name="gender[]" .modelValue=${'other'}>
|
||||||
<${childTag} .choiceValue=${'male'}></${childTag}>
|
<${childTag} .choiceValue=${'male'}></${childTag}>
|
||||||
<${childTag} .choiceValue=${'female'}></${childTag}>
|
<${childTag} .choiceValue=${'female'}></${childTag}>
|
||||||
<${childTag} .choiceValue=${'other'}></${childTag}>
|
<${childTag} .choiceValue=${'other'}></${childTag}>
|
||||||
</${parentTag}>
|
</${parentTag}>
|
||||||
`));
|
`));
|
||||||
|
|
||||||
|
if (cfg.choiceType === 'single') {
|
||||||
expect(el.modelValue).to.equal('other');
|
expect(el.modelValue).to.equal('other');
|
||||||
|
} else {
|
||||||
|
expect(el.modelValue).to.deep.equal(['other']);
|
||||||
|
}
|
||||||
expect(el.formElements[2].checked).to.be.true;
|
expect(el.formElements[2].checked).to.be.true;
|
||||||
});
|
});
|
||||||
|
|
||||||
it('can set initial serializedValue on creation', async () => {
|
it('can set initial serializedValue on creation', async () => {
|
||||||
const el = /** @type {ChoiceGroup} */ (await fixture(html`
|
const el = /** @type {ChoiceGroup} */ (await fixture(html`
|
||||||
<${parentTag} name="gender" .serializedValue=${'other'}>
|
<${parentTag} name="gender[]" .serializedValue=${'other'}>
|
||||||
<${childTag} .choiceValue=${'male'}></${childTag}>
|
<${childTag} .choiceValue=${'male'}></${childTag}>
|
||||||
<${childTag} .choiceValue=${'female'}></${childTag}>
|
<${childTag} .choiceValue=${'female'}></${childTag}>
|
||||||
<${childTag} .choiceValue=${'other'}></${childTag}>
|
<${childTag} .choiceValue=${'other'}></${childTag}>
|
||||||
</${parentTag}>
|
</${parentTag}>
|
||||||
`));
|
`));
|
||||||
|
|
||||||
|
if (cfg.choiceType === 'single') {
|
||||||
expect(el.serializedValue).to.equal('other');
|
expect(el.serializedValue).to.equal('other');
|
||||||
|
} else {
|
||||||
|
expect(el.serializedValue).to.deep.equal(['other']);
|
||||||
|
}
|
||||||
expect(el.formElements[2].checked).to.be.true;
|
expect(el.formElements[2].checked).to.be.true;
|
||||||
});
|
});
|
||||||
|
|
||||||
it('can set initial formattedValue on creation', async () => {
|
it('can set initial formattedValue on creation', async () => {
|
||||||
const el = /** @type {ChoiceGroup} */ (await fixture(html`
|
const el = /** @type {ChoiceGroup} */ (await fixture(html`
|
||||||
<${parentTag} name="gender" .formattedValue=${'other'}>
|
<${parentTag} name="gender[]" .formattedValue=${'other'}>
|
||||||
<${childTag} .choiceValue=${'male'}></${childTag}>
|
<${childTag} .choiceValue=${'male'}></${childTag}>
|
||||||
<${childTag} .choiceValue=${'female'}></${childTag}>
|
<${childTag} .choiceValue=${'female'}></${childTag}>
|
||||||
<${childTag} .choiceValue=${'other'}></${childTag}>
|
<${childTag} .choiceValue=${'other'}></${childTag}>
|
||||||
</${parentTag}>
|
</${parentTag}>
|
||||||
`));
|
`));
|
||||||
|
|
||||||
|
if (cfg.choiceType === 'single') {
|
||||||
expect(el.formattedValue).to.equal('other');
|
expect(el.formattedValue).to.equal('other');
|
||||||
|
} else {
|
||||||
|
expect(el.formattedValue).to.deep.equal(['other']);
|
||||||
|
}
|
||||||
expect(el.formElements[2].checked).to.be.true;
|
expect(el.formElements[2].checked).to.be.true;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
@ -188,33 +203,45 @@ export function runChoiceGroupMixinSuite({ parentTagString, childTagString } = {
|
||||||
const date = new Date(2018, 11, 24, 10, 33, 30, 0);
|
const date = new Date(2018, 11, 24, 10, 33, 30, 0);
|
||||||
|
|
||||||
const el = /** @type {ChoiceGroup} */ (await fixture(html`
|
const el = /** @type {ChoiceGroup} */ (await fixture(html`
|
||||||
<${parentTag} name="data">
|
<${parentTag} name="data[]">
|
||||||
<${childTag} .choiceValue=${{ some: 'data' }}></${childTag}>
|
<${childTag} .choiceValue=${{ some: 'data' }}></${childTag}>
|
||||||
<${childTag} .choiceValue=${date} checked></${childTag}>
|
<${childTag} .choiceValue=${date} checked></${childTag}>
|
||||||
</${parentTag}>
|
</${parentTag}>
|
||||||
`));
|
`));
|
||||||
|
|
||||||
|
if (cfg.choiceType === 'single') {
|
||||||
expect(el.modelValue).to.equal(date);
|
expect(el.modelValue).to.equal(date);
|
||||||
el.formElements[0].checked = true;
|
el.formElements[0].checked = true;
|
||||||
expect(el.modelValue).to.deep.equal({ some: 'data' });
|
expect(el.modelValue).to.deep.equal({ some: 'data' });
|
||||||
|
} else {
|
||||||
|
expect(el.modelValue).to.deep.equal([date]);
|
||||||
|
el.formElements[0].checked = true;
|
||||||
|
expect(el.modelValue).to.deep.equal([{ some: 'data' }, date]);
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
it('can handle 0 and empty string as valid values', async () => {
|
it('can handle 0 and empty string as valid values', async () => {
|
||||||
const el = /** @type {ChoiceGroup} */ (await fixture(html`
|
const el = /** @type {ChoiceGroup} */ (await fixture(html`
|
||||||
<${parentTag} name="data">
|
<${parentTag} name="data[]">
|
||||||
<${childTag} .choiceValue=${0} checked></${childTag}>
|
<${childTag} .choiceValue=${0} checked></${childTag}>
|
||||||
<${childTag} .choiceValue=${''}></${childTag}>
|
<${childTag} .choiceValue=${''}></${childTag}>
|
||||||
</${parentTag}>
|
</${parentTag}>
|
||||||
`));
|
`));
|
||||||
|
|
||||||
|
if (cfg.choiceType === 'single') {
|
||||||
expect(el.modelValue).to.equal(0);
|
expect(el.modelValue).to.equal(0);
|
||||||
el.formElements[1].checked = true;
|
el.formElements[1].checked = true;
|
||||||
expect(el.modelValue).to.equal('');
|
expect(el.modelValue).to.equal('');
|
||||||
|
} else {
|
||||||
|
expect(el.modelValue).to.deep.equal([0]);
|
||||||
|
el.formElements[1].checked = true;
|
||||||
|
expect(el.modelValue).to.deep.equal([0, '']);
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
it('can check a radio by supplying an available modelValue', async () => {
|
it('can check a radio by supplying an available modelValue', async () => {
|
||||||
const el = /** @type {ChoiceGroup} */ (await fixture(html`
|
const el = /** @type {ChoiceGroup} */ (await fixture(html`
|
||||||
<${parentTag} name="gender">
|
<${parentTag} name="gender[]">
|
||||||
<${childTag}
|
<${childTag}
|
||||||
.modelValue="${{ value: 'male', checked: false }}"
|
.modelValue="${{ value: 'male', checked: false }}"
|
||||||
></${childTag}>
|
></${childTag}>
|
||||||
|
|
@ -227,7 +254,11 @@ export function runChoiceGroupMixinSuite({ parentTagString, childTagString } = {
|
||||||
</${parentTag}>
|
</${parentTag}>
|
||||||
`));
|
`));
|
||||||
|
|
||||||
|
if (cfg.choiceType === 'single') {
|
||||||
expect(el.modelValue).to.equal('female');
|
expect(el.modelValue).to.equal('female');
|
||||||
|
} else {
|
||||||
|
expect(el.modelValue).to.deep.equal(['female']);
|
||||||
|
}
|
||||||
el.modelValue = 'other';
|
el.modelValue = 'other';
|
||||||
expect(el.formElements[2].checked).to.be.true;
|
expect(el.formElements[2].checked).to.be.true;
|
||||||
});
|
});
|
||||||
|
|
@ -236,7 +267,7 @@ export function runChoiceGroupMixinSuite({ parentTagString, childTagString } = {
|
||||||
let counter = 0;
|
let counter = 0;
|
||||||
const el = /** @type {ChoiceGroup} */ (await fixture(html`
|
const el = /** @type {ChoiceGroup} */ (await fixture(html`
|
||||||
<${parentTag}
|
<${parentTag}
|
||||||
name="gender"
|
name="gender[]"
|
||||||
@model-value-changed=${() => {
|
@model-value-changed=${() => {
|
||||||
counter += 1;
|
counter += 1;
|
||||||
}}
|
}}
|
||||||
|
|
@ -252,26 +283,37 @@ export function runChoiceGroupMixinSuite({ parentTagString, childTagString } = {
|
||||||
counter = 0; // reset after setup which may result in different results
|
counter = 0; // reset after setup which may result in different results
|
||||||
|
|
||||||
el.formElements[0].checked = true;
|
el.formElements[0].checked = true;
|
||||||
expect(counter).to.equal(1); // male becomes checked, female becomes unchecked
|
expect(counter).to.equal(1); // male becomes checked
|
||||||
|
|
||||||
// not changed values trigger no event
|
// not changed values trigger no event
|
||||||
el.formElements[0].checked = true;
|
el.formElements[0].checked = true;
|
||||||
expect(counter).to.equal(1);
|
expect(counter).to.equal(1);
|
||||||
|
|
||||||
el.formElements[2].checked = true;
|
el.formElements[2].checked = true;
|
||||||
expect(counter).to.equal(2); // other becomes checked, male becomes unchecked
|
expect(counter).to.equal(2); // other becomes checked
|
||||||
|
|
||||||
|
if (cfg.choiceType === 'single') {
|
||||||
// not found values trigger no event
|
// not found values trigger no event
|
||||||
el.modelValue = 'foo';
|
el.modelValue = 'foo';
|
||||||
expect(counter).to.equal(2);
|
expect(counter).to.equal(2);
|
||||||
|
|
||||||
el.modelValue = 'male';
|
el.modelValue = 'male';
|
||||||
expect(counter).to.equal(3); // male becomes checked, other becomes unchecked
|
expect(counter).to.equal(3); // male becomes checked, other becomes unchecked
|
||||||
|
}
|
||||||
|
|
||||||
|
if (choiceType === 'multiple') {
|
||||||
|
// not found values trigger no event
|
||||||
|
el.modelValue = ['foo', 'male', 'female', 'other'];
|
||||||
|
expect(counter).to.equal(2);
|
||||||
|
|
||||||
|
el.modelValue = ['female', 'other'];
|
||||||
|
expect(counter).to.equal(3); // male becomes unchecked
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
it('can be required', async () => {
|
it('can be required', async () => {
|
||||||
const el = /** @type {ChoiceGroup} */ (await fixture(html`
|
const el = /** @type {ChoiceGroup} */ (await fixture(html`
|
||||||
<${parentTag} name="gender" .validators=${[new Required()]}>
|
<${parentTag} name="gender[]" .validators=${[new Required()]}>
|
||||||
<${childTag} .choiceValue=${'male'}></${childTag}>
|
<${childTag} .choiceValue=${'male'}></${childTag}>
|
||||||
<${childTag}
|
<${childTag}
|
||||||
.choiceValue=${{ subObject: 'satisfies required' }}
|
.choiceValue=${{ subObject: 'satisfies required' }}
|
||||||
|
|
@ -295,24 +337,32 @@ export function runChoiceGroupMixinSuite({ parentTagString, childTagString } = {
|
||||||
|
|
||||||
it('returns serialized value', async () => {
|
it('returns serialized value', async () => {
|
||||||
const el = /** @type {ChoiceGroup} */ (await fixture(html`
|
const el = /** @type {ChoiceGroup} */ (await fixture(html`
|
||||||
<${parentTag} name="gender">
|
<${parentTag} name="gender[]">
|
||||||
<${childTag} .choiceValue=${'male'}></${childTag}>
|
<${childTag} .choiceValue=${'male'}></${childTag}>
|
||||||
<${childTag} .choiceValue=${'female'}></${childTag}>
|
<${childTag} .choiceValue=${'female'}></${childTag}>
|
||||||
</${parentTag}>
|
</${parentTag}>
|
||||||
`));
|
`));
|
||||||
el.formElements[0].checked = true;
|
el.formElements[0].checked = true;
|
||||||
|
if (cfg.choiceType === 'single') {
|
||||||
expect(el.serializedValue).to.deep.equal('male');
|
expect(el.serializedValue).to.deep.equal('male');
|
||||||
|
} else {
|
||||||
|
expect(el.serializedValue).to.deep.equal(['male']);
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
it('returns serialized value on unchecked state', async () => {
|
it('returns serialized value on unchecked state', async () => {
|
||||||
const el = /** @type {ChoiceGroup} */ (await fixture(html`
|
const el = /** @type {ChoiceGroup} */ (await fixture(html`
|
||||||
<${parentTag} name="gender">
|
<${parentTag} name="gender[]">
|
||||||
<${childTag} .choiceValue=${'male'}></${childTag}>
|
<${childTag} .choiceValue=${'male'}></${childTag}>
|
||||||
<${childTag} .choiceValue=${'female'}></${childTag}>
|
<${childTag} .choiceValue=${'female'}></${childTag}>
|
||||||
</${parentTag}>
|
</${parentTag}>
|
||||||
`));
|
`));
|
||||||
|
|
||||||
|
if (cfg.choiceType === 'single') {
|
||||||
expect(el.serializedValue).to.deep.equal('');
|
expect(el.serializedValue).to.deep.equal('');
|
||||||
|
} else {
|
||||||
|
expect(el.serializedValue).to.deep.equal([]);
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('multipleChoice', () => {
|
describe('multipleChoice', () => {
|
||||||
|
|
@ -403,7 +453,7 @@ export function runChoiceGroupMixinSuite({ parentTagString, childTagString } = {
|
||||||
it('will serialize all children with their serializedValue', async () => {
|
it('will serialize all children with their serializedValue', async () => {
|
||||||
const el = /** @type {ChoiceGroup} */ (await fixture(html`
|
const el = /** @type {ChoiceGroup} */ (await fixture(html`
|
||||||
<lion-fieldset>
|
<lion-fieldset>
|
||||||
<${parentTag} name="gender">
|
<${parentTag} name="gender[]">
|
||||||
<${childTag} .choiceValue=${'male'} checked disabled></${childTag}>
|
<${childTag} .choiceValue=${'male'} checked disabled></${childTag}>
|
||||||
<${childTag} .choiceValue=${'female'} checked></${childTag}>
|
<${childTag} .choiceValue=${'female'} checked></${childTag}>
|
||||||
<${childTag} .choiceValue=${'other'}></${childTag}>
|
<${childTag} .choiceValue=${'other'}></${childTag}>
|
||||||
|
|
@ -411,15 +461,11 @@ export function runChoiceGroupMixinSuite({ parentTagString, childTagString } = {
|
||||||
</lion-fieldset>
|
</lion-fieldset>
|
||||||
`));
|
`));
|
||||||
|
|
||||||
expect(el.serializedValue).to.eql({
|
if (cfg.choiceType === 'single') {
|
||||||
gender: 'female',
|
expect(el.serializedValue).to.deep.equal({ 'gender[]': ['female'] });
|
||||||
});
|
} else {
|
||||||
|
expect(el.serializedValue).to.deep.equal({ 'gender[]': [['female']] });
|
||||||
const choiceGroupEl = /** @type {ChoiceGroup} */ (el.querySelector('[name="gender"]'));
|
}
|
||||||
choiceGroupEl.multipleChoice = true;
|
|
||||||
expect(el.serializedValue).to.eql({
|
|
||||||
gender: ['female'],
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,285 @@
|
||||||
|
import { Required } from '@lion/form-core';
|
||||||
|
import { LionInput } from '@lion/input';
|
||||||
|
import { expect, fixture, html, unsafeStatic } from '@open-wc/testing';
|
||||||
|
import sinon from 'sinon';
|
||||||
|
import { ChoiceInputMixin } from '../../src/choice-group/ChoiceInputMixin.js';
|
||||||
|
|
||||||
|
class ChoiceInput extends ChoiceInputMixin(LionInput) {
|
||||||
|
constructor() {
|
||||||
|
super();
|
||||||
|
this.type = 'checkbox';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
customElements.define('choice-group-input', ChoiceInput);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {{ tagString?:string, tagType?: string}} [config]
|
||||||
|
*/
|
||||||
|
export function runChoiceInputMixinSuite({ tagString } = {}) {
|
||||||
|
const cfg = {
|
||||||
|
tagString: tagString || 'choice-group-input',
|
||||||
|
};
|
||||||
|
|
||||||
|
const tag = unsafeStatic(cfg.tagString);
|
||||||
|
describe(`ChoiceInputMixin: ${tagString}`, () => {
|
||||||
|
it('is hidden when attribute hidden is true', async () => {
|
||||||
|
const el = /** @type {ChoiceInput} */ (await fixture(html`<${tag} hidden></${tag}>`));
|
||||||
|
expect(el).not.to.be.displayed;
|
||||||
|
});
|
||||||
|
|
||||||
|
it('has choiceValue', async () => {
|
||||||
|
const el = /** @type {ChoiceInput} */ (await fixture(
|
||||||
|
html`<${tag} .choiceValue=${'foo'}></${tag}>`,
|
||||||
|
));
|
||||||
|
|
||||||
|
expect(el.choiceValue).to.equal('foo');
|
||||||
|
expect(el.modelValue).to.deep.equal({
|
||||||
|
value: 'foo',
|
||||||
|
checked: false,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('can handle complex data via choiceValue', async () => {
|
||||||
|
const date = new Date(2018, 11, 24, 10, 33, 30, 0);
|
||||||
|
|
||||||
|
const el = /** @type {ChoiceInput} */ (await fixture(
|
||||||
|
html`<${tag} .choiceValue=${date}></${tag}>`,
|
||||||
|
));
|
||||||
|
|
||||||
|
expect(el.choiceValue).to.equal(date);
|
||||||
|
expect(el.modelValue.value).to.equal(date);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('fires one "model-value-changed" event if choiceValue or checked state or modelValue changed', async () => {
|
||||||
|
let counter = 0;
|
||||||
|
const el = /** @type {ChoiceInput} */ (await fixture(html`
|
||||||
|
<${tag}
|
||||||
|
@model-value-changed=${() => {
|
||||||
|
counter += 1;
|
||||||
|
}}
|
||||||
|
.choiceValue=${'foo'}
|
||||||
|
></${tag}>
|
||||||
|
`));
|
||||||
|
expect(counter).to.equal(1); // undefined to set value
|
||||||
|
|
||||||
|
el.checked = true;
|
||||||
|
expect(counter).to.equal(2);
|
||||||
|
|
||||||
|
// no change means no event
|
||||||
|
el.checked = true;
|
||||||
|
el.choiceValue = 'foo';
|
||||||
|
el.modelValue = { value: 'foo', checked: true };
|
||||||
|
expect(counter).to.equal(2);
|
||||||
|
|
||||||
|
el.modelValue = { value: 'foo', checked: false };
|
||||||
|
expect(counter).to.equal(3);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('fires one "user-input-changed" event after user interaction', async () => {
|
||||||
|
let counter = 0;
|
||||||
|
const el = /** @type {ChoiceInput} */ (await fixture(html`
|
||||||
|
<${tag}
|
||||||
|
@user-input-changed="${() => {
|
||||||
|
counter += 1;
|
||||||
|
}}"
|
||||||
|
>
|
||||||
|
<input slot="input" />
|
||||||
|
</${tag}>
|
||||||
|
`));
|
||||||
|
expect(counter).to.equal(0);
|
||||||
|
// Here we try to mimic user interaction by firing browser events
|
||||||
|
const nativeInput = el._inputNode;
|
||||||
|
nativeInput.dispatchEvent(new CustomEvent('input', { bubbles: true })); // fired by (at least) Chrome
|
||||||
|
expect(counter).to.equal(0);
|
||||||
|
nativeInput.dispatchEvent(new CustomEvent('change', { bubbles: true }));
|
||||||
|
expect(counter).to.equal(1);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('can be required', async () => {
|
||||||
|
const el = /** @type {ChoiceInput} */ (await fixture(html`
|
||||||
|
<${tag} .choiceValue=${'foo'} .validators=${[new Required()]}></${tag}>
|
||||||
|
`));
|
||||||
|
expect(el.hasFeedbackFor).to.include('error');
|
||||||
|
expect(el.validationStates.error).to.exist;
|
||||||
|
expect(el.validationStates.error.Required).to.exist;
|
||||||
|
el.checked = true;
|
||||||
|
expect(el.hasFeedbackFor).not.to.include('error');
|
||||||
|
expect(el.validationStates.error).to.exist;
|
||||||
|
expect(el.validationStates.error.Required).not.to.exist;
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('Checked state synchronization', () => {
|
||||||
|
it('synchronizes checked state initially (via attribute or property)', async () => {
|
||||||
|
const el = /** @type {ChoiceInput} */ (await fixture(html`<${tag}></${tag}>`));
|
||||||
|
expect(el.checked).to.equal(false, 'initially unchecked');
|
||||||
|
|
||||||
|
const precheckedElementAttr = /** @type {ChoiceInput} */ (await fixture(html`
|
||||||
|
<${tag} .checked=${true}></${tag}>
|
||||||
|
`));
|
||||||
|
expect(precheckedElementAttr.checked).to.equal(true, 'initially checked via attribute');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('can be checked and unchecked programmatically', async () => {
|
||||||
|
const el = /** @type {ChoiceInput} */ (await fixture(html`<${tag}></${tag}>`));
|
||||||
|
expect(el.checked).to.be.false;
|
||||||
|
el.checked = true;
|
||||||
|
expect(el.checked).to.be.true;
|
||||||
|
|
||||||
|
await el.updateComplete;
|
||||||
|
expect(el._inputNode.checked).to.be.true;
|
||||||
|
});
|
||||||
|
|
||||||
|
it('can be checked and unchecked via user interaction', async () => {
|
||||||
|
const el = /** @type {ChoiceInput} */ (await fixture(html`<${tag}></${tag}>`));
|
||||||
|
el._inputNode.click();
|
||||||
|
expect(el.checked).to.be.true;
|
||||||
|
el._inputNode.click();
|
||||||
|
await el.updateComplete;
|
||||||
|
if (el.type === 'checkbox') {
|
||||||
|
expect(el.checked).to.be.false;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
it('can not toggle the checked state when disabled via user interaction', async () => {
|
||||||
|
const el = /** @type {ChoiceInput} */ (await fixture(html`<${tag} disabled></${tag}>`));
|
||||||
|
el._inputNode.dispatchEvent(new CustomEvent('change', { bubbles: true }));
|
||||||
|
expect(el.checked).to.be.false;
|
||||||
|
});
|
||||||
|
|
||||||
|
it('synchronizes modelValue to checked state and vice versa', async () => {
|
||||||
|
const el = /** @type {ChoiceInput} */ (await fixture(
|
||||||
|
html`<${tag} .choiceValue=${'foo'}></${tag}>`,
|
||||||
|
));
|
||||||
|
expect(el.checked).to.be.false;
|
||||||
|
expect(el.modelValue).to.deep.equal({
|
||||||
|
checked: false,
|
||||||
|
value: 'foo',
|
||||||
|
});
|
||||||
|
el.checked = true;
|
||||||
|
expect(el.checked).to.be.true;
|
||||||
|
expect(el.modelValue).to.deep.equal({
|
||||||
|
checked: true,
|
||||||
|
value: 'foo',
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('ensures optimal synchronize performance by preventing redundant computation steps', async () => {
|
||||||
|
/* we are checking private apis here to make sure we do not have cyclical updates
|
||||||
|
which can be quite common for these type of connected data */
|
||||||
|
const el = /** @type {ChoiceInput} */ (await fixture(
|
||||||
|
html`<${tag} .choiceValue=${'foo'}></${tag}>`,
|
||||||
|
));
|
||||||
|
expect(el.checked).to.be.false;
|
||||||
|
|
||||||
|
const spyModelCheckedToChecked = sinon.spy(el, '__syncModelCheckedToChecked');
|
||||||
|
const spyCheckedToModel = sinon.spy(el, '__syncCheckedToModel');
|
||||||
|
el.checked = true;
|
||||||
|
expect(el.modelValue.checked).to.be.true;
|
||||||
|
expect(spyModelCheckedToChecked.callCount).to.equal(0);
|
||||||
|
expect(spyCheckedToModel.callCount).to.equal(1);
|
||||||
|
|
||||||
|
el.modelValue = { value: 'foo', checked: false };
|
||||||
|
expect(el.checked).to.be.false;
|
||||||
|
expect(spyModelCheckedToChecked.callCount).to.equal(1);
|
||||||
|
expect(spyCheckedToModel.callCount).to.equal(1);
|
||||||
|
|
||||||
|
// not changing values should not trigger any updates
|
||||||
|
el.checked = false;
|
||||||
|
el.modelValue = { value: 'foo', checked: false };
|
||||||
|
expect(spyModelCheckedToChecked.callCount).to.equal(1);
|
||||||
|
expect(spyCheckedToModel.callCount).to.equal(1);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('synchronizes checked state to [checked] attribute for styling purposes', async () => {
|
||||||
|
/** @param {ChoiceInput} el */
|
||||||
|
const hasAttr = el => el.hasAttribute('checked');
|
||||||
|
const el = /** @type {ChoiceInput} */ (await fixture(html`<${tag}></${tag}>`));
|
||||||
|
const elChecked = /** @type {ChoiceInput} */ (await fixture(html`
|
||||||
|
<${tag} .checked=${true}>
|
||||||
|
<input slot="input" />
|
||||||
|
</${tag}>
|
||||||
|
`));
|
||||||
|
|
||||||
|
// Initial values
|
||||||
|
expect(hasAttr(el)).to.equal(false, 'initial unchecked element');
|
||||||
|
expect(hasAttr(elChecked)).to.equal(true, 'initial checked element');
|
||||||
|
|
||||||
|
// Via user interaction
|
||||||
|
el._inputNode.click();
|
||||||
|
elChecked._inputNode.click();
|
||||||
|
await el.updateComplete;
|
||||||
|
expect(el.checked).to.be.true;
|
||||||
|
expect(hasAttr(el)).to.equal(true, 'user click checked');
|
||||||
|
if (el.type === 'checkbox') {
|
||||||
|
expect(hasAttr(elChecked)).to.equal(false, 'user click unchecked');
|
||||||
|
}
|
||||||
|
|
||||||
|
// reset
|
||||||
|
el.checked = false;
|
||||||
|
elChecked.checked = true;
|
||||||
|
|
||||||
|
// Programmatically via checked
|
||||||
|
el.checked = true;
|
||||||
|
elChecked.checked = false;
|
||||||
|
|
||||||
|
await el.updateComplete;
|
||||||
|
expect(hasAttr(el)).to.equal(true, 'programmatically checked');
|
||||||
|
expect(hasAttr(elChecked)).to.equal(false, 'programmatically unchecked');
|
||||||
|
|
||||||
|
// reset
|
||||||
|
el.checked = false;
|
||||||
|
elChecked.checked = true;
|
||||||
|
|
||||||
|
// Programmatically via modelValue
|
||||||
|
el.modelValue = { value: '', checked: true };
|
||||||
|
elChecked.modelValue = { value: '', checked: false };
|
||||||
|
await el.updateComplete;
|
||||||
|
expect(hasAttr(el)).to.equal(true, 'modelValue checked');
|
||||||
|
expect(hasAttr(elChecked)).to.equal(false, 'modelValue unchecked');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('Format/parse/serialize loop', () => {
|
||||||
|
it('creates a modelValue object like { checked: true, value: foo } on init', async () => {
|
||||||
|
const el = /** @type {ChoiceInput} */ (await fixture(
|
||||||
|
html`<${tag} .choiceValue=${'foo'}></${tag}>`,
|
||||||
|
));
|
||||||
|
expect(el.modelValue).deep.equal({ value: 'foo', checked: false });
|
||||||
|
|
||||||
|
const elChecked = /** @type {ChoiceInput} */ (await fixture(html`
|
||||||
|
<${tag} .choiceValue=${'foo'} .checked=${true}></${tag}>
|
||||||
|
`));
|
||||||
|
expect(elChecked.modelValue).deep.equal({ value: 'foo', checked: true });
|
||||||
|
});
|
||||||
|
|
||||||
|
it('creates a formattedValue based on modelValue.value', async () => {
|
||||||
|
const el = /** @type {ChoiceInput} */ (await fixture(html`<${tag}></${tag}>`));
|
||||||
|
expect(el.formattedValue).to.equal('');
|
||||||
|
|
||||||
|
const elementWithValue = /** @type {ChoiceInput} */ (await fixture(html`
|
||||||
|
<${tag} .choiceValue=${'foo'}></${tag}>
|
||||||
|
`));
|
||||||
|
expect(elementWithValue.formattedValue).to.equal('foo');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('can clear the checked state', async () => {
|
||||||
|
const el = /** @type {ChoiceInput} */ (await fixture(html`<${tag}></${tag}>`));
|
||||||
|
el.modelValue = { value: 'foo', checked: true };
|
||||||
|
el.clear();
|
||||||
|
expect(el.modelValue).deep.equal({ value: 'foo', checked: false });
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('Interaction states', () => {
|
||||||
|
it('is considered prefilled when checked and not considered prefilled when unchecked', async () => {
|
||||||
|
const el = /** @type {ChoiceInput} */ (await fixture(
|
||||||
|
html`<${tag} .checked=${true}></${tag}>`,
|
||||||
|
));
|
||||||
|
expect(el.prefilled).equal(true, 'checked element not considered prefilled');
|
||||||
|
|
||||||
|
const elUnchecked = /** @type {ChoiceInput} */ (await fixture(html`<${tag}></${tag}>`));
|
||||||
|
expect(elUnchecked.prefilled).equal(false, 'unchecked element not considered prefilled');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
@ -1,276 +1,3 @@
|
||||||
import { html } from '@lion/core';
|
import { runChoiceInputMixinSuite } from '../../test-suites/choice-group/ChoiceInputMixin.suite.js';
|
||||||
import { Required } from '@lion/form-core';
|
|
||||||
import { LionInput } from '@lion/input';
|
|
||||||
import { expect, fixture } from '@open-wc/testing';
|
|
||||||
import sinon from 'sinon';
|
|
||||||
import { ChoiceInputMixin } from '../../src/choice-group/ChoiceInputMixin.js';
|
|
||||||
|
|
||||||
describe('ChoiceInputMixin', () => {
|
runChoiceInputMixinSuite();
|
||||||
class ChoiceClass extends ChoiceInputMixin(LionInput) {
|
|
||||||
constructor() {
|
|
||||||
super();
|
|
||||||
this.type = 'checkbox'; // could also be 'radio', should be tested in integration test
|
|
||||||
}
|
|
||||||
}
|
|
||||||
customElements.define('choice-input', ChoiceClass);
|
|
||||||
|
|
||||||
it('is hidden when attribute hidden is true', async () => {
|
|
||||||
const el = /** @type {ChoiceClass} */ (await fixture(
|
|
||||||
html`<choice-input hidden></choice-input>`,
|
|
||||||
));
|
|
||||||
expect(el).not.to.be.displayed;
|
|
||||||
});
|
|
||||||
|
|
||||||
it('has choiceValue', async () => {
|
|
||||||
const el = /** @type {ChoiceClass} */ (await fixture(
|
|
||||||
html`<choice-input .choiceValue=${'foo'}></choice-input>`,
|
|
||||||
));
|
|
||||||
|
|
||||||
expect(el.choiceValue).to.equal('foo');
|
|
||||||
expect(el.modelValue).to.deep.equal({
|
|
||||||
value: 'foo',
|
|
||||||
checked: false,
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
it('can handle complex data via choiceValue', async () => {
|
|
||||||
const date = new Date(2018, 11, 24, 10, 33, 30, 0);
|
|
||||||
|
|
||||||
const el = /** @type {ChoiceClass} */ (await fixture(
|
|
||||||
html`<choice-input .choiceValue=${date}></choice-input>`,
|
|
||||||
));
|
|
||||||
|
|
||||||
expect(el.choiceValue).to.equal(date);
|
|
||||||
expect(el.modelValue.value).to.equal(date);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('fires one "model-value-changed" event if choiceValue or checked state or modelValue changed', async () => {
|
|
||||||
let counter = 0;
|
|
||||||
const el = /** @type {ChoiceClass} */ (await fixture(html`
|
|
||||||
<choice-input
|
|
||||||
@model-value-changed=${() => {
|
|
||||||
counter += 1;
|
|
||||||
}}
|
|
||||||
.choiceValue=${'foo'}
|
|
||||||
></choice-input>
|
|
||||||
`));
|
|
||||||
expect(counter).to.equal(1); // undefined to set value
|
|
||||||
|
|
||||||
el.checked = true;
|
|
||||||
expect(counter).to.equal(2);
|
|
||||||
|
|
||||||
// no change means no event
|
|
||||||
el.checked = true;
|
|
||||||
el.choiceValue = 'foo';
|
|
||||||
el.modelValue = { value: 'foo', checked: true };
|
|
||||||
expect(counter).to.equal(2);
|
|
||||||
|
|
||||||
el.modelValue = { value: 'foo', checked: false };
|
|
||||||
expect(counter).to.equal(3);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('fires one "user-input-changed" event after user interaction', async () => {
|
|
||||||
let counter = 0;
|
|
||||||
const el = /** @type {ChoiceClass} */ (await fixture(html`
|
|
||||||
<choice-input
|
|
||||||
@user-input-changed="${() => {
|
|
||||||
counter += 1;
|
|
||||||
}}"
|
|
||||||
>
|
|
||||||
<input slot="input" />
|
|
||||||
</choice-input>
|
|
||||||
`));
|
|
||||||
expect(counter).to.equal(0);
|
|
||||||
// Here we try to mimic user interaction by firing browser events
|
|
||||||
const nativeInput = el._inputNode;
|
|
||||||
nativeInput.dispatchEvent(new CustomEvent('input', { bubbles: true })); // fired by (at least) Chrome
|
|
||||||
expect(counter).to.equal(0);
|
|
||||||
nativeInput.dispatchEvent(new CustomEvent('change', { bubbles: true }));
|
|
||||||
expect(counter).to.equal(1);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('can be required', async () => {
|
|
||||||
const el = /** @type {ChoiceClass} */ (await fixture(html`
|
|
||||||
<choice-input .choiceValue=${'foo'} .validators=${[new Required()]}></choice-input>
|
|
||||||
`));
|
|
||||||
expect(el.hasFeedbackFor).to.include('error');
|
|
||||||
expect(el.validationStates.error).to.exist;
|
|
||||||
expect(el.validationStates.error.Required).to.exist;
|
|
||||||
el.checked = true;
|
|
||||||
expect(el.hasFeedbackFor).not.to.include('error');
|
|
||||||
expect(el.validationStates.error).to.exist;
|
|
||||||
expect(el.validationStates.error.Required).not.to.exist;
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('Checked state synchronization', () => {
|
|
||||||
it('synchronizes checked state initially (via attribute or property)', async () => {
|
|
||||||
const el = /** @type {ChoiceClass} */ (await fixture(`<choice-input></choice-input>`));
|
|
||||||
expect(el.checked).to.equal(false, 'initially unchecked');
|
|
||||||
|
|
||||||
const precheckedElementAttr = /** @type {ChoiceClass} */ (await fixture(html`
|
|
||||||
<choice-input .checked=${true}></choice-input>
|
|
||||||
`));
|
|
||||||
expect(precheckedElementAttr.checked).to.equal(true, 'initially checked via attribute');
|
|
||||||
});
|
|
||||||
|
|
||||||
it('can be checked and unchecked programmatically', async () => {
|
|
||||||
const el = /** @type {ChoiceClass} */ (await fixture(`<choice-input></choice-input>`));
|
|
||||||
expect(el.checked).to.be.false;
|
|
||||||
el.checked = true;
|
|
||||||
expect(el.checked).to.be.true;
|
|
||||||
|
|
||||||
await el.updateComplete;
|
|
||||||
expect(el._inputNode.checked).to.be.true;
|
|
||||||
});
|
|
||||||
|
|
||||||
it('can be checked and unchecked via user interaction', async () => {
|
|
||||||
const el = /** @type {ChoiceClass} */ (await fixture(`<choice-input></choice-input>`));
|
|
||||||
el._inputNode.click();
|
|
||||||
expect(el.checked).to.be.true;
|
|
||||||
el._inputNode.click();
|
|
||||||
expect(el.checked).to.be.false;
|
|
||||||
});
|
|
||||||
|
|
||||||
it('can not toggle the checked state when disabled via user interaction', async () => {
|
|
||||||
const el = /** @type {ChoiceClass} */ (await fixture(
|
|
||||||
html`<choice-input disabled></choice-input>`,
|
|
||||||
));
|
|
||||||
el._inputNode.dispatchEvent(new CustomEvent('change', { bubbles: true }));
|
|
||||||
expect(el.checked).to.be.false;
|
|
||||||
});
|
|
||||||
|
|
||||||
it('synchronizes modelValue to checked state and vice versa', async () => {
|
|
||||||
const el = /** @type {ChoiceClass} */ (await fixture(
|
|
||||||
html`<choice-input .choiceValue=${'foo'}></choice-input>`,
|
|
||||||
));
|
|
||||||
expect(el.checked).to.be.false;
|
|
||||||
expect(el.modelValue).to.deep.equal({
|
|
||||||
checked: false,
|
|
||||||
value: 'foo',
|
|
||||||
});
|
|
||||||
el.checked = true;
|
|
||||||
expect(el.checked).to.be.true;
|
|
||||||
expect(el.modelValue).to.deep.equal({
|
|
||||||
checked: true,
|
|
||||||
value: 'foo',
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
it('ensures optimal synchronize performance by preventing redundant computation steps', async () => {
|
|
||||||
/* we are checking private apis here to make sure we do not have cyclical updates
|
|
||||||
which can be quite common for these type of connected data */
|
|
||||||
const el = /** @type {ChoiceClass} */ (await fixture(
|
|
||||||
html`<choice-input .choiceValue=${'foo'}></choice-input>`,
|
|
||||||
));
|
|
||||||
expect(el.checked).to.be.false;
|
|
||||||
|
|
||||||
const spyModelCheckedToChecked = sinon.spy(el, '__syncModelCheckedToChecked');
|
|
||||||
const spyCheckedToModel = sinon.spy(el, '__syncCheckedToModel');
|
|
||||||
el.checked = true;
|
|
||||||
expect(el.modelValue.checked).to.be.true;
|
|
||||||
expect(spyModelCheckedToChecked.callCount).to.equal(0);
|
|
||||||
expect(spyCheckedToModel.callCount).to.equal(1);
|
|
||||||
|
|
||||||
el.modelValue = { value: 'foo', checked: false };
|
|
||||||
expect(el.checked).to.be.false;
|
|
||||||
expect(spyModelCheckedToChecked.callCount).to.equal(1);
|
|
||||||
expect(spyCheckedToModel.callCount).to.equal(1);
|
|
||||||
|
|
||||||
// not changing values should not trigger any updates
|
|
||||||
el.checked = false;
|
|
||||||
el.modelValue = { value: 'foo', checked: false };
|
|
||||||
expect(spyModelCheckedToChecked.callCount).to.equal(1);
|
|
||||||
expect(spyCheckedToModel.callCount).to.equal(1);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('synchronizes checked state to [checked] attribute for styling purposes', async () => {
|
|
||||||
/** @param {ChoiceClass} el */
|
|
||||||
const hasAttr = el => el.hasAttribute('checked');
|
|
||||||
const el = /** @type {ChoiceClass} */ (await fixture(`<choice-input></choice-input>`));
|
|
||||||
const elChecked = /** @type {ChoiceClass} */ (await fixture(html`
|
|
||||||
<choice-input .checked=${true}>
|
|
||||||
<input slot="input" />
|
|
||||||
</choice-input>
|
|
||||||
`));
|
|
||||||
|
|
||||||
// Initial values
|
|
||||||
expect(hasAttr(el)).to.equal(false, 'initial unchecked element');
|
|
||||||
expect(hasAttr(elChecked)).to.equal(true, 'initial checked element');
|
|
||||||
|
|
||||||
// Programmatically via checked
|
|
||||||
el.checked = true;
|
|
||||||
elChecked.checked = false;
|
|
||||||
|
|
||||||
await el.updateComplete;
|
|
||||||
expect(hasAttr(el)).to.equal(true, 'programmatically checked');
|
|
||||||
expect(hasAttr(elChecked)).to.equal(false, 'programmatically unchecked');
|
|
||||||
|
|
||||||
// reset
|
|
||||||
el.checked = false;
|
|
||||||
elChecked.checked = true;
|
|
||||||
|
|
||||||
// Via user interaction
|
|
||||||
el._inputNode.click();
|
|
||||||
elChecked._inputNode.click();
|
|
||||||
await el.updateComplete;
|
|
||||||
expect(hasAttr(el)).to.equal(true, 'user click checked');
|
|
||||||
expect(hasAttr(elChecked)).to.equal(false, 'user click unchecked');
|
|
||||||
|
|
||||||
// reset
|
|
||||||
el.checked = false;
|
|
||||||
elChecked.checked = true;
|
|
||||||
|
|
||||||
// Programmatically via modelValue
|
|
||||||
el.modelValue = { value: '', checked: true };
|
|
||||||
elChecked.modelValue = { value: '', checked: false };
|
|
||||||
await el.updateComplete;
|
|
||||||
expect(hasAttr(el)).to.equal(true, 'modelValue checked');
|
|
||||||
expect(hasAttr(elChecked)).to.equal(false, 'modelValue unchecked');
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('Format/parse/serialize loop', () => {
|
|
||||||
it('creates a modelValue object like { checked: true, value: foo } on init', async () => {
|
|
||||||
const el = /** @type {ChoiceClass} */ (await fixture(
|
|
||||||
html`<choice-input .choiceValue=${'foo'}></choice-input>`,
|
|
||||||
));
|
|
||||||
expect(el.modelValue).deep.equal({ value: 'foo', checked: false });
|
|
||||||
|
|
||||||
const elChecked = /** @type {ChoiceClass} */ (await fixture(html`
|
|
||||||
<choice-input .choiceValue=${'foo'} .checked=${true}></choice-input>
|
|
||||||
`));
|
|
||||||
expect(elChecked.modelValue).deep.equal({ value: 'foo', checked: true });
|
|
||||||
});
|
|
||||||
|
|
||||||
it('creates a formattedValue based on modelValue.value', async () => {
|
|
||||||
const el = /** @type {ChoiceClass} */ (await fixture(`<choice-input></choice-input>`));
|
|
||||||
expect(el.formattedValue).to.equal('');
|
|
||||||
|
|
||||||
const elementWithValue = /** @type {ChoiceClass} */ (await fixture(html`
|
|
||||||
<choice-input .choiceValue=${'foo'}></choice-input>
|
|
||||||
`));
|
|
||||||
expect(elementWithValue.formattedValue).to.equal('foo');
|
|
||||||
});
|
|
||||||
|
|
||||||
it('can clear the checked state', async () => {
|
|
||||||
const el = /** @type {ChoiceClass} */ (await fixture(`<choice-input></choice-input>`));
|
|
||||||
el.modelValue = { value: 'foo', checked: true };
|
|
||||||
el.clear();
|
|
||||||
expect(el.modelValue).deep.equal({ value: 'foo', checked: false });
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('Interaction states', () => {
|
|
||||||
it('is considered prefilled when checked and not considered prefilled when unchecked', async () => {
|
|
||||||
const el = /** @type {ChoiceClass} */ (await fixture(
|
|
||||||
html`<choice-input .checked=${true}></choice-input>`,
|
|
||||||
));
|
|
||||||
expect(el.prefilled).equal(true, 'checked element not considered prefilled');
|
|
||||||
|
|
||||||
const elUnchecked = /** @type {ChoiceClass} */ (await fixture(
|
|
||||||
`<choice-input></choice-input>`,
|
|
||||||
));
|
|
||||||
expect(elUnchecked.prefilled).equal(false, 'unchecked element not considered prefilled');
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,9 @@
|
||||||
|
import { runChoiceGroupMixinSuite } from '@lion/form-core/test-suites/choice-group/ChoiceGroupMixin.suite.js';
|
||||||
|
import '../lion-radio-group.js';
|
||||||
|
import '../lion-radio.js';
|
||||||
|
|
||||||
|
runChoiceGroupMixinSuite({
|
||||||
|
parentTagString: 'lion-radio-group',
|
||||||
|
childTagString: 'lion-radio',
|
||||||
|
choiceType: 'single',
|
||||||
|
});
|
||||||
|
|
@ -11,6 +11,46 @@ import '../lion-radio.js';
|
||||||
const fixture = /** @type {(arg: TemplateResult) => Promise<LionRadioGroup>} */ (_fixture);
|
const fixture = /** @type {(arg: TemplateResult) => Promise<LionRadioGroup>} */ (_fixture);
|
||||||
|
|
||||||
describe('<lion-radio-group>', () => {
|
describe('<lion-radio-group>', () => {
|
||||||
|
describe('resetGroup', () => {
|
||||||
|
// TODO move to FormGroupMixin test suite and let CheckboxGroup make use of them
|
||||||
|
it('restores to empty modelValue if changes were made', async () => {
|
||||||
|
const el = await fixture(html`
|
||||||
|
<lion-radio-group name="gender" label="Gender">
|
||||||
|
<lion-radio label="Male" .choiceValue=${'male'}></lion-radio>
|
||||||
|
<lion-radio label="Female" .choiceValue=${'female'}></lion-radio>
|
||||||
|
</lion-radio-group>
|
||||||
|
`);
|
||||||
|
el.formElements[0].checked = true;
|
||||||
|
expect(el.modelValue).to.deep.equal('male');
|
||||||
|
|
||||||
|
el.resetGroup();
|
||||||
|
expect(el.modelValue).to.deep.equal('');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('restores default values if changes were made', async () => {
|
||||||
|
const el = await fixture(html`
|
||||||
|
<lion-radio-group name="gender" label="Gender">
|
||||||
|
<lion-radio label="Male" .choiceValue=${'male'}></lion-radio>
|
||||||
|
<lion-radio label="Female" .modelValue=${{ value: 'female', checked: true }}></lion-radio>
|
||||||
|
<lion-radio label="Other" .choiceValue=${'other'}></lion-radio>
|
||||||
|
</lion-radio-group>
|
||||||
|
`);
|
||||||
|
el.formElements[0].checked = true;
|
||||||
|
expect(el.modelValue).to.deep.equal('male');
|
||||||
|
|
||||||
|
el.resetGroup();
|
||||||
|
expect(el.modelValue).to.deep.equal('female');
|
||||||
|
|
||||||
|
el.formElements[2].checked = true;
|
||||||
|
expect(el.modelValue).to.deep.equal('other');
|
||||||
|
|
||||||
|
el.resetGroup();
|
||||||
|
await el.formElements[1].updateComplete;
|
||||||
|
await el.updateComplete;
|
||||||
|
expect(el.modelValue).to.deep.equal('female');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
it('should have role = radiogroup', async () => {
|
it('should have role = radiogroup', async () => {
|
||||||
const el = await fixture(html`
|
const el = await fixture(html`
|
||||||
<lion-radio-group label="Gender" name="gender">
|
<lion-radio-group label="Gender" name="gender">
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,4 @@
|
||||||
|
import '../lion-radio.js';
|
||||||
|
import { runChoiceInputMixinSuite } from '@lion/form-core/test-suites/choice-group/ChoiceInputMixin.suite.js';
|
||||||
|
|
||||||
|
runChoiceInputMixinSuite({ tagString: 'lion-radio' });
|
||||||
|
|
@ -1,11 +1,39 @@
|
||||||
import { expect, fixture } from '@open-wc/testing';
|
import { expect, fixture, html } from '@open-wc/testing';
|
||||||
import '../lion-radio.js';
|
import '../lion-radio.js';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @typedef {import('../src/LionRadio').LionRadio} LionRadio
|
||||||
|
*/
|
||||||
|
|
||||||
describe('<lion-radio>', () => {
|
describe('<lion-radio>', () => {
|
||||||
it('should have type = radio', async () => {
|
it('should have type = radio', async () => {
|
||||||
const el = await fixture(`
|
const el = await fixture(html`
|
||||||
<lion-radio name="radio" value="male"></lion-radio>
|
<lion-radio name="radio" .choiceValue="${'male'}"></lion-radio>
|
||||||
`);
|
`);
|
||||||
expect(el.getAttribute('type')).to.equal('radio');
|
expect(el.getAttribute('type')).to.equal('radio');
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('can be reset when unchecked by default', async () => {
|
||||||
|
const el = /** @type {LionRadio} */ (await fixture(html`
|
||||||
|
<lion-radio name="radio" .choiceValue=${'male'}></lion-radio>
|
||||||
|
`));
|
||||||
|
expect(el._initialModelValue).to.deep.equal({ value: 'male', checked: false });
|
||||||
|
el.checked = true;
|
||||||
|
expect(el.modelValue).to.deep.equal({ value: 'male', checked: true });
|
||||||
|
|
||||||
|
el.reset();
|
||||||
|
expect(el.modelValue).to.deep.equal({ value: 'male', checked: false });
|
||||||
|
});
|
||||||
|
|
||||||
|
it('can be reset when checked by default', async () => {
|
||||||
|
const el = /** @type {LionRadio} */ (await fixture(html`
|
||||||
|
<lion-radio name="radio" .choiceValue=${'male'} checked></lion-radio>
|
||||||
|
`));
|
||||||
|
expect(el._initialModelValue).to.deep.equal({ value: 'male', checked: true });
|
||||||
|
el.checked = false;
|
||||||
|
expect(el.modelValue).to.deep.equal({ value: 'male', checked: false });
|
||||||
|
|
||||||
|
el.reset();
|
||||||
|
expect(el.modelValue).to.deep.equal({ value: 'male', checked: true });
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue