@lion/ui
Version:
A package of extendable web components
953 lines (843 loc) • 37 kB
JavaScript
import { expect, fixture, html, unsafeStatic } from '@open-wc/testing';
import { getFormControlMembers } from '@lion/ui/form-core-test-helpers.js';
/**
* @typedef {import('../src/LionCheckbox.js').LionCheckbox} LionCheckbox
* @typedef {import('../src/LionCheckboxIndeterminate.js').LionCheckboxIndeterminate} LionCheckboxIndeterminate
* @typedef {import('../src/LionCheckboxGroup.js').LionCheckboxGroup} LionCheckboxGroup
*/
/**
* @param {LionCheckboxIndeterminate} el
*/
function getCheckboxIndeterminateMembers(el) {
const obj = getFormControlMembers(el);
return {
...obj,
...{
// @ts-ignore [allow-protected] in test
_subCheckboxes: el._subCheckboxes,
},
};
}
/**
* @param {{tagString?: string, groupTagString?: string, childTagString?: string}} [customConfig]
*/
export function runCheckboxIndeterminateSuite(customConfig) {
const cfg = {
tagString: null,
groupTagString: null,
childTagString: null,
...customConfig,
};
/** @type {{_$litStatic$: any}} */
let tag;
/** @type {{_$litStatic$: any}} */
let groupTag;
/** @type {{_$litStatic$: any}} */
let childTag;
before(async () => {
// @ts-expect-error
tag = unsafeStatic(cfg.tagString);
// @ts-expect-error
groupTag = unsafeStatic(cfg.groupTagString);
// @ts-expect-error
childTag = unsafeStatic(cfg.childTagString);
});
describe('CheckboxIndeterminate', async () => {
/** @type {{_$litStatic$: any}} */
it('should have type = checkbox', async () => {
// Arrange
const el = await fixture(html`
<${tag}
name="checkbox"
.choiceValue="${'male'}"
></${tag}>
`);
// Assert
expect(el.getAttribute('type')).to.equal('checkbox');
});
it('should not be indeterminate by default if all children are unchecked', async () => {
// Arrange
const el = await fixture(html`
<${groupTag} name="scientists[]">
<${tag} label="Favorite scientists">
<${childTag} label="Archimedes"></${childTag}>
<${childTag} label="Francis Bacon"></${childTag}>
<${childTag} label="Marie Curie"></${childTag}>
</${tag}>
</${groupTag}>
`);
const elIndeterminate = /** @type {LionCheckboxIndeterminate} */ (
el.querySelector(`${cfg.tagString}`)
);
// Assert
expect(elIndeterminate?.hasAttribute('indeterminate')).to.be.false;
});
it('should be indeterminate if one child is checked', async () => {
// Arrange
const el = /** @type {LionCheckboxGroup} */ await fixture(html`
<${groupTag} name="scientists[]">
<${tag} label="Favorite scientists">
<${childTag} label="Archimedes"></${childTag}>
<${childTag} label="Francis Bacon" checked></${childTag}>
<${childTag} label="Marie Curie"></${childTag}>
</${tag}>
</${groupTag}>
`);
const elIndeterminate = /** @type {LionCheckboxIndeterminate} */ (
el.querySelector(`${cfg.tagString}`)
);
// Assert
expect(elIndeterminate?.hasAttribute('indeterminate')).to.be.true;
});
it('should be checked if all children are checked', async () => {
// Arrange
const el = /** @type {LionCheckboxGroup} */ (
await fixture(html`
<${groupTag} name="scientists[]">
<${tag} label="Favorite scientists">
<${childTag} label="Archimedes" checked></${childTag}>
<${childTag} label="Francis Bacon" checked></${childTag}>
<${childTag} label="Marie Curie" checked></${childTag}>
</${tag}>
</${groupTag}>
`)
);
const elIndeterminate = /** @type {LionCheckboxIndeterminate} */ (
el.querySelector(`${cfg.tagString}`)
);
// Assert
expect(elIndeterminate?.hasAttribute('indeterminate')).to.be.false;
expect(elIndeterminate?.checked).to.be.true;
});
it('should become indeterminate if one child is checked', async () => {
// Arrange
const el = /** @type {LionCheckboxGroup} */ (
await fixture(html`
<${groupTag} name="scientists[]">
<${tag} label="Favorite scientists">
<${childTag} label="Archimedes"></${childTag}>
<${childTag} label="Francis Bacon"></${childTag}>
<${childTag} label="Marie Curie"></${childTag}>
</${tag}>
</${groupTag}>
`)
);
const elIndeterminate = /** @type {LionCheckboxIndeterminate} */ (
el.querySelector(`${cfg.tagString}`)
);
const { _subCheckboxes } = getCheckboxIndeterminateMembers(elIndeterminate);
// Act
_subCheckboxes[0].checked = true;
await el.updateComplete;
// Assert
expect(elIndeterminate?.hasAttribute('indeterminate')).to.be.true;
});
it('should become checked if all children are checked', async () => {
// Arrange
const el = /** @type {LionCheckboxGroup} */ (
await fixture(html`
<${groupTag} name="scientists[]">
<${tag} label="Favorite scientists">
<${childTag} label="Archimedes"></${childTag}>
<${childTag} label="Francis Bacon"></${childTag}>
<${childTag} label="Marie Curie"></${childTag}>
</${tag}>
</${groupTag}>
`)
);
const elIndeterminate = /** @type {LionCheckboxIndeterminate} */ (
el.querySelector(`${cfg.tagString}`)
);
const { _subCheckboxes } = getCheckboxIndeterminateMembers(elIndeterminate);
// Act
_subCheckboxes[0].checked = true;
_subCheckboxes[1].checked = true;
_subCheckboxes[2].checked = true;
await el.updateComplete;
// Assert
expect(elIndeterminate?.hasAttribute('indeterminate')).to.be.false;
expect(elIndeterminate?.checked).to.be.true;
});
it('should become indeterminate if all children except disabled ones are checked', async () => {
// Arrange
const el = /** @type {LionCheckboxGroup} */ (
await fixture(html`
<${groupTag} name="scientists[]">
<${tag} label="Favorite scientists">
<${childTag} label="Archimedes"></${childTag}>
<${childTag} label="Francis Bacon" disabled></${childTag}>
<${childTag} label="Marie Curie"></${childTag}>
</${tag}>
</${groupTag}>
`)
);
const elIndeterminate = /** @type {LionCheckboxIndeterminate} */ (
el.querySelector(`${cfg.tagString}`)
);
const { _subCheckboxes } = getCheckboxIndeterminateMembers(elIndeterminate);
// Act
_subCheckboxes[0].checked = true;
_subCheckboxes[2].checked = true;
await el.updateComplete;
// Assert
expect(elIndeterminate?.hasAttribute('indeterminate')).to.be.true;
expect(elIndeterminate?.checked).to.be.true;
});
it('should be checked when all children are prechecked', async () => {
// Arrange
const el = /** @type {LionCheckboxGroup} */ await fixture(html`
<${groupTag} name="scientists[]">
<${tag} label="Favorite scientists">
<${childTag} checked label="Archimedes"></${childTag}>
<${childTag} checked label="Francis Bacon"></${childTag}>
<${childTag} checked label="Marie Curie"></${childTag}>
</${tag}>
</${groupTag}>
`);
const elIndeterminate = /** @type {LionCheckboxIndeterminate} */ (
el.querySelector(`${cfg.tagString}`)
);
// Assert
expect(elIndeterminate?.hasAttribute('checked')).to.be.true;
});
it('should be checked when it has one prechecked child', async () => {
// Arrange
const el = /** @type {LionCheckboxGroup} */ await fixture(html`
<${groupTag} name="scientists[]">
<${tag} label="Favorite scientists">
<${childTag} checked label="Archimedes"></${childTag}>
</${tag}>
</${groupTag}>
`);
const elIndeterminate = /** @type {LionCheckboxIndeterminate} */ (
el.querySelector(`${cfg.tagString}`)
);
// Assert
expect(elIndeterminate?.hasAttribute('checked')).to.be.true;
});
it('should be unchecked when it has an disabled checked child and an unchecked child', async () => {
// Arrange
const el = /** @type {LionCheckboxGroup} */ await fixture(html`
<${groupTag} name="scientists[]">
<${tag} label="Favorite scientists">
<${childTag} disabled checked label="Archimedes"></${childTag}>
<${childTag} label="Francis Bacon"></${childTag}>
</${tag}>
</${groupTag}>
`);
const elIndeterminate = /** @type {LionCheckboxIndeterminate} */ (
el.querySelector(`${cfg.tagString}`)
);
// Assert
expect(elIndeterminate?.hasAttribute('checked')).to.be.false;
});
it('should be indeterminated when some of the children are prechecked', async () => {
// Arrange
const el = /** @type {LionCheckboxGroup} */ await fixture(html`
<${groupTag} name="scientists[]">
<${tag} label="Favorite scientists">
<${childTag} checked label="Archimedes"></${childTag}>
<${childTag} checked label="Francis Bacon"></${childTag}>
<${childTag} label="Marie Curie"></${childTag}>
</${tag}>
</${groupTag}>
`);
const elIndeterminate = /** @type {LionCheckboxIndeterminate} */ (
el.querySelector(`${cfg.tagString}`)
);
// Assert
expect(elIndeterminate.hasAttribute('indeterminate')).to.be.true;
});
it('should sync all children when parent is checked (from indeterminate to checked)', async () => {
// Arrange
const el = /** @type {LionCheckboxGroup} */ (
await fixture(html`
<${groupTag} name="scientists[]">
<${tag} label="Favorite scientists">
<${childTag} label="Archimedes"></${childTag}>
<${childTag} label="Francis Bacon" checked></${childTag}>
<${childTag} label="Marie Curie"></${childTag}>
</${tag}>
</${groupTag}>
`)
);
const elIndeterminate = /** @type {LionCheckboxIndeterminate} */ (
el.querySelector(`${cfg.tagString}`)
);
const { _subCheckboxes, _inputNode } = getCheckboxIndeterminateMembers(elIndeterminate);
// Act
_inputNode.click();
await elIndeterminate.updateComplete;
// Assert
expect(elIndeterminate.hasAttribute('indeterminate')).to.be.false;
expect(_subCheckboxes[0].hasAttribute('checked')).to.be.true;
expect(_subCheckboxes[1].hasAttribute('checked')).to.be.true;
expect(_subCheckboxes[2].hasAttribute('checked')).to.be.true;
});
it('should not sync any disabled children when parent is checked (from indeterminate to checked)', async () => {
// Arrange
const el = /** @type {LionCheckboxGroup} */ (
await fixture(html`
<${groupTag} name="scientists[]">
<${tag} label="Favorite scientists">
<${childTag} label="Archimedes"></${childTag}>
<${childTag} label="Francis Bacon" disabled></${childTag}>
<${childTag} label="Marie Curie"></${childTag}>
</${tag}>
</${groupTag}>
`)
);
const elIndeterminate = /** @type {LionCheckboxIndeterminate} */ (
el.querySelector(`${cfg.tagString}`)
);
const { _subCheckboxes, _inputNode } = getCheckboxIndeterminateMembers(elIndeterminate);
// Act
_inputNode.click();
await elIndeterminate.updateComplete;
// Assert
expect(elIndeterminate.hasAttribute('indeterminate')).to.be.true;
expect(_subCheckboxes[0].hasAttribute('checked')).to.be.true;
expect(_subCheckboxes[1].hasAttribute('checked')).to.be.false;
expect(_subCheckboxes[2].hasAttribute('checked')).to.be.true;
});
it('should remain unchecked when parent is clicked and all children are disabled', async () => {
// Arrange
const el = /** @type {LionCheckboxGroup} */ (
await fixture(html`
<${groupTag} name="scientists[]">
<${tag} label="Favorite scientists">
<${childTag} label="Archimedes" disabled></${childTag}>
<${childTag} label="Francis Bacon" disabled></${childTag}>
<${childTag} label="Marie Curie" disabled></${childTag}>
</${tag}>
</${groupTag}>
`)
);
const elIndeterminate = /** @type {LionCheckboxIndeterminate} */ (
el.querySelector(`${cfg.tagString}`)
);
const { _subCheckboxes, _inputNode } = getCheckboxIndeterminateMembers(elIndeterminate);
// Act
_inputNode.click();
await elIndeterminate.updateComplete;
// Assert
expect(elIndeterminate.hasAttribute('indeterminate')).to.be.false;
expect(elIndeterminate.hasAttribute('checked')).to.be.false;
expect(_subCheckboxes[0].hasAttribute('checked')).to.be.false;
expect(_subCheckboxes[1].hasAttribute('checked')).to.be.false;
expect(_subCheckboxes[2].hasAttribute('checked')).to.be.false;
});
it('should remain checked when parent is clicked and all children are disabled and checked', async () => {
// Arrange
const el = /** @type {LionCheckboxGroup} */ (
await fixture(html`
<${groupTag} name="scientists[]">
<${tag} label="Favorite scientists">
<${childTag} label="Archimedes" disabled checked></${childTag}>
<${childTag} label="Francis Bacon" disabled checked></${childTag}>
<${childTag} label="Marie Curie" disabled checked></${childTag}>
</${tag}>
</${groupTag}>
`)
);
const elIndeterminate = /** @type {LionCheckboxIndeterminate} */ (
el.querySelector(`${cfg.tagString}`)
);
const { _subCheckboxes, _inputNode } = getCheckboxIndeterminateMembers(elIndeterminate);
// Act
_inputNode.click();
await elIndeterminate.updateComplete;
// Assert
expect(elIndeterminate.hasAttribute('indeterminate')).to.be.false;
expect(elIndeterminate.hasAttribute('checked')).to.be.true;
expect(_subCheckboxes[0].hasAttribute('checked')).to.be.true;
expect(_subCheckboxes[1].hasAttribute('checked')).to.be.true;
expect(_subCheckboxes[2].hasAttribute('checked')).to.be.true;
});
it('should sync all children when parent is checked (from unchecked to checked)', async () => {
// Arrange
const el = /** @type {LionCheckboxGroup} */ (
await fixture(html`
<${groupTag} name="scientists[]">
<${tag} label="Favorite scientists">
<${childTag} label="Archimedes"></${childTag}>
<${childTag} label="Francis Bacon"></${childTag}>
<${childTag} label="Marie Curie"></${childTag}>
</${tag}>
</${groupTag}>
`)
);
const elIndeterminate = /** @type {LionCheckboxIndeterminate} */ (
el.querySelector(`${cfg.tagString}`)
);
const { _subCheckboxes, _inputNode } = getCheckboxIndeterminateMembers(elIndeterminate);
// Act
_inputNode.click();
await elIndeterminate.updateComplete;
// Assert
expect(elIndeterminate.hasAttribute('indeterminate')).to.be.false;
expect(_subCheckboxes[0].hasAttribute('checked')).to.be.true;
expect(_subCheckboxes[1].hasAttribute('checked')).to.be.true;
expect(_subCheckboxes[2].hasAttribute('checked')).to.be.true;
});
it('should sync all children when parent is checked (from checked to unchecked)', async () => {
// Arrange
const el = /** @type {LionCheckboxGroup} */ (
await fixture(html`
<${groupTag} name="scientists[]">
<${tag} label="Favorite scientists">
<${childTag} label="Archimedes" checked></${childTag}>
<${childTag} label="Francis Bacon" checked></${childTag}>
<${childTag} label="Marie Curie" checked></${childTag}>
</${tag}>
</${groupTag}>
`)
);
const elIndeterminate = /** @type {LionCheckboxIndeterminate} */ (
el.querySelector(`${cfg.tagString}`)
);
const { _subCheckboxes, _inputNode } = getCheckboxIndeterminateMembers(elIndeterminate);
// Act
_inputNode.click();
await elIndeterminate.updateComplete;
// Assert
expect(elIndeterminate?.hasAttribute('indeterminate')).to.be.false;
expect(_subCheckboxes[0].hasAttribute('checked')).to.be.false;
expect(_subCheckboxes[1].hasAttribute('checked')).to.be.false;
expect(_subCheckboxes[2].hasAttribute('checked')).to.be.false;
});
it('should sync all prechecked children when parent is indeterminate and some of the children are disabled (from checked to unchecked)', async () => {
// Arrange
const el = /** @type {LionCheckboxGroup} */ (
await fixture(html`
<${groupTag} name="scientists[]">
<${tag} label="Favorite scientists">
<${childTag} checked label="Archimedes"></${childTag}>
<${childTag} disabled label="Francis Bacon"></${childTag}>
<${childTag} checked label="Marie Curie"></${childTag}>
</${tag}>
</${groupTag}>
`)
);
const elIndeterminate = /** @type {LionCheckboxIndeterminate} */ (
el.querySelector(`${cfg.tagString}`)
);
const { _subCheckboxes, _inputNode } = getCheckboxIndeterminateMembers(elIndeterminate);
// Act
_inputNode.click();
await elIndeterminate.updateComplete;
// Assert
expect(elIndeterminate?.hasAttribute('indeterminate')).to.be.false;
expect(_inputNode?.hasAttribute('indeterminate')).to.be.false;
expect(_subCheckboxes[0].hasAttribute('checked')).to.be.false;
expect(_subCheckboxes[1].hasAttribute('checked')).to.be.false;
expect(_subCheckboxes[2].hasAttribute('checked')).to.be.false;
});
it('should stay as indeterminated after it is clicked, when it is interminated already and some children are disabled', async () => {
// Arrange
const el = /** @type {LionCheckboxGroup} */ (
await fixture(html`
<${groupTag} name="scientists[]">
<${tag} label="Favorite scientists">
<${childTag} checked label="Archimedes"></${childTag}>
<${childTag} disabled label="Francis Bacon"></${childTag}>
<${childTag} label="Marie Curie"></${childTag}>
</${tag}>
</${groupTag}>
`)
);
const elIndeterminate = /** @type {LionCheckboxIndeterminate} */ (
el.querySelector(`${cfg.tagString}`)
);
const { _subCheckboxes, _inputNode } = getCheckboxIndeterminateMembers(elIndeterminate);
// Act
_inputNode.click();
await elIndeterminate.updateComplete;
// Assert
expect(elIndeterminate.hasAttribute('indeterminate')).to.be.true;
expect(_inputNode.indeterminate).to.be.true;
expect(_subCheckboxes[0].hasAttribute('checked')).to.be.true;
expect(_subCheckboxes[2].hasAttribute('checked')).to.be.true;
});
it('should work as expected with siblings checkbox-indeterminate', async () => {
// Arrange
const el = /** @type {LionCheckboxGroup} */ (
await fixture(html`
<${groupTag} name="scientists[]" label="Favorite scientists">
<${tag}
label="Old Greek scientists"
id="first-checkbox-indeterminate"
>
<${childTag}
slot="checkbox"
label="Archimedes"
.choiceValue=${'Archimedes'}
></${childTag}>
<${childTag} label="Plato" .choiceValue=${'Plato'}></${childTag}>
<${childTag}
slot="checkbox"
label="Pythagoras"
.choiceValue=${'Pythagoras'}
></${childTag}>
</${tag}>
<${tag}
label="17th Century scientists"
id="second-checkbox-indeterminate"
>
<${childTag}
slot="checkbox"
label="Isaac Newton"
.choiceValue=${'Isaac Newton'}
></${childTag}>
<${childTag}
slot="checkbox"
label="Galileo Galilei"
.choiceValue=${'Galileo Galilei'}
></${childTag}>
</${tag}>
</${groupTag}>
`)
);
const elFirstIndeterminate = /** @type {LionCheckboxIndeterminate} */ (
el.querySelector('#first-checkbox-indeterminate')
);
const elSecondIndeterminate = /** @type {LionCheckboxIndeterminate} */ (
el.querySelector('#second-checkbox-indeterminate')
);
const elFirstSubCheckboxes = getCheckboxIndeterminateMembers(elFirstIndeterminate);
const elSecondSubCheckboxes = getCheckboxIndeterminateMembers(elSecondIndeterminate);
// Act - check the first sibling
elFirstSubCheckboxes._inputNode.click();
await elFirstIndeterminate.updateComplete;
await elSecondIndeterminate.updateComplete;
// Assert - the second sibling should not be affected
expect(elFirstIndeterminate.hasAttribute('indeterminate')).to.be.false;
expect(elFirstSubCheckboxes._subCheckboxes[0].hasAttribute('checked')).to.be.true;
expect(elFirstSubCheckboxes._subCheckboxes[1].hasAttribute('checked')).to.be.true;
expect(elFirstSubCheckboxes._subCheckboxes[2].hasAttribute('checked')).to.be.true;
expect(elSecondSubCheckboxes._subCheckboxes[0].hasAttribute('checked')).to.be.false;
expect(elSecondSubCheckboxes._subCheckboxes[1].hasAttribute('checked')).to.be.false;
});
it('should work as expected when new checkbox was added', async () => {
// Arrange
const el = /** @type {LionCheckboxGroup} */ await fixture(html`
<${groupTag} name="scientists[]">
<${tag} label="Favorite scientists">
<${childTag} checked label="Archimedes"></${childTag}>
<${childTag} checked label="Francis Bacon" checked></${childTag}>
<${childTag} checked label="Marie Curie"></${childTag}>
</${tag}>
</${groupTag}>
`);
const elIndeterminate = /** @type {LionCheckboxIndeterminate} */ (
el.querySelector(`${cfg.tagString}`)
);
expect(elIndeterminate.hasAttribute('indeterminate')).to.be.false;
// Act
const newChild = document.createElement(/** @type {string} */ (cfg.childTagString));
elIndeterminate.appendChild(newChild);
await elIndeterminate.updateComplete;
// Assert
expect(elIndeterminate.hasAttribute('indeterminate')).to.be.true;
});
it('should work as expected when an existing checkbox was removed', async () => {
// Arrange
const el = /** @type {LionCheckboxGroup} */ await fixture(html`
<${groupTag} name="scientists[]">
<${tag} label="Favorite scientists">
<${childTag} checked label="Archimedes"></${childTag}>
<${childTag} checked label="Francis Bacon" checked></${childTag}>
<${childTag} label="Marie Curie"></${childTag}>
</${tag}>
</${groupTag}>
`);
const elIndeterminate = /** @type {LionCheckboxIndeterminate} */ (
el.querySelector(`${cfg.tagString}`)
);
expect(elIndeterminate.hasAttribute('indeterminate')).to.be.true;
expect(elIndeterminate.checked).to.be.false;
// Act
elIndeterminate.removeChild(/** @type {ChildNode} */ (elIndeterminate.lastChild));
await elIndeterminate.updateComplete;
// Assert
expect(elIndeterminate?.hasAttribute('indeterminate')).to.be.true;
});
it('should work as expected with nested indeterminate checkboxes', async () => {
// Arrange
const el = /** @type {LionCheckboxGroup} */ (
await fixture(html`
<${groupTag} name="scientists[]" label="Favorite scientists">
<${tag} label="Scientists" id="parent-checkbox-indeterminate">
<${childTag}
slot="checkbox"
label="Isaac Newton"
.choiceValue=${'Isaac Newton'}
></${childTag}>
<${childTag}
slot="checkbox"
label="Galileo Galilei"
.choiceValue=${'Galileo Galilei'}
></${childTag}>
<${tag}
slot="checkbox"
label="Old Greek scientists"
id="nested-checkbox-indeterminate"
>
<${childTag}
slot="checkbox"
label="Archimedes"
.choiceValue=${'Archimedes'}
></${childTag}>
<${childTag} label="Plato" .choiceValue=${'Plato'}></${childTag}>
<${childTag}
slot="checkbox"
label="Pythagoras"
.choiceValue=${'Pythagoras'}
></${childTag}>
</${tag}>
</${tag}>
</${groupTag}>
`)
);
const elNestedIndeterminate = /** @type {LionCheckboxIndeterminate} */ (
el.querySelector('#nested-checkbox-indeterminate')
);
const elParentIndeterminate = /** @type {LionCheckboxIndeterminate} */ (
el.querySelector('#parent-checkbox-indeterminate')
);
const elNestedSubCheckboxes = getCheckboxIndeterminateMembers(elNestedIndeterminate);
const elParentSubCheckboxes = getCheckboxIndeterminateMembers(elParentIndeterminate);
// Act - check a nested checkbox
if (elNestedIndeterminate) {
// @ts-ignore [allow-protected] in test
elNestedSubCheckboxes._subCheckboxes[0]._inputNode.click();
}
await el.updateComplete;
// Assert
expect(elNestedIndeterminate?.hasAttribute('indeterminate')).to.be.true;
expect(elParentIndeterminate?.hasAttribute('indeterminate')).to.be.true;
// Act - check all nested checkbox
// @ts-ignore [allow-protected] in test
if (elNestedIndeterminate) elNestedSubCheckboxes._subCheckboxes[1]._inputNode.click();
// @ts-ignore [allow-protected] in test
if (elNestedIndeterminate) elNestedSubCheckboxes._subCheckboxes[2]._inputNode.click();
await el.updateComplete;
// Assert
expect(elNestedIndeterminate?.hasAttribute('checked')).to.be.true;
expect(elNestedIndeterminate?.hasAttribute('indeterminate')).to.be.false;
expect(elParentIndeterminate?.hasAttribute('checked')).to.be.false;
expect(elParentIndeterminate?.hasAttribute('indeterminate')).to.be.true;
// Act - finally check all remaining checkbox
if (elParentIndeterminate) {
// @ts-ignore [allow-protected] in test
elParentSubCheckboxes._subCheckboxes[0]._inputNode.click();
}
if (elParentIndeterminate) {
// @ts-ignore [allow-protected] in test
elParentSubCheckboxes._subCheckboxes[1]._inputNode.click();
}
await el.updateComplete;
// Assert
expect(elNestedIndeterminate?.hasAttribute('checked')).to.be.true;
expect(elNestedIndeterminate?.hasAttribute('indeterminate')).to.be.false;
expect(elParentIndeterminate?.hasAttribute('checked')).to.be.true;
expect(elParentIndeterminate?.hasAttribute('indeterminate')).to.be.false;
});
it('should work as expected if extra html', async () => {
// Arrange
const el = /** @type {LionCheckboxGroup} */ (
await fixture(html`
<${groupTag} name="scientists[]">
<div>
Let's have some fun
<div>Hello I'm a div</div>
<${tag} label="Favorite scientists">
<div>useless div</div>
<${childTag} label="Archimedes"></${childTag}>
<${childTag} label="Francis Bacon"></${childTag}>
<div>absolutely useless</div>
<${childTag} label="Marie Curie"></${childTag}>
</${tag}>
</div>
<div>Too much fun, stop it !</div>
</${groupTag}>
`)
);
const elIndeterminate = /** @type {LionCheckboxIndeterminate} */ (
el.querySelector(`${cfg.tagString}`)
);
const { _subCheckboxes } = getCheckboxIndeterminateMembers(elIndeterminate);
// Act
_subCheckboxes[0].checked = true;
_subCheckboxes[1].checked = true;
_subCheckboxes[2].checked = true;
await el.updateComplete;
// Assert
expect(elIndeterminate?.hasAttribute('indeterminate')).to.be.false;
expect(elIndeterminate?.checked).to.be.true;
});
// https://www.w3.org/TR/wai-aria-practices-1.1/examples/checkbox/checkbox-2/checkbox-2.html
describe('mixed-state', () => {
it('can have a mixed-state (using mixed-state attribute), none -> indeterminate -> all, cycling through', async () => {
const el = await fixture(html`
<${groupTag} name="scientists[]">
<${tag} mixed-state label="Favorite scientists">
<${childTag} label="Archimedes" checked></${childTag}>
<${childTag} label="Francis Bacon"></${childTag}>
<${childTag} label="Marie Curie"></${childTag}>
</${tag}>
</${groupTag}>
`);
const elIndeterminate = /** @type {LionCheckboxIndeterminate} */ (
el.querySelector(`${cfg.tagString}`)
);
expect(elIndeterminate.mixedState).to.be.true;
expect(elIndeterminate.checked).to.be.false;
expect(elIndeterminate.indeterminate).to.be.true;
// @ts-ignore for testing purposes, we access this protected getter
elIndeterminate._inputNode.click();
await elIndeterminate.updateComplete;
expect(elIndeterminate.checked).to.be.true;
expect(elIndeterminate.indeterminate).to.be.false;
// @ts-ignore for testing purposes, we access this protected getter
elIndeterminate._inputNode.click();
await elIndeterminate.updateComplete;
expect(elIndeterminate.checked).to.be.false;
expect(elIndeterminate.indeterminate).to.be.false;
// @ts-ignore for testing purposes, we access this protected getter
elIndeterminate._inputNode.click();
await elIndeterminate.updateComplete;
expect(elIndeterminate.checked).to.be.false;
expect(elIndeterminate.indeterminate).to.be.true;
});
it('should reset to old child checkbox states when reaching indeterminate state', async () => {
const el = await fixture(html`
<${groupTag} name="scientists[]">
<${tag} mixed-state label="Favorite scientists">
<${childTag} label="Archimedes" checked></${childTag}>
<${childTag} label="Francis Bacon"></${childTag}>
<${childTag} label="Marie Curie"></${childTag}>
</${tag}>
</${groupTag}>
`);
const elIndeterminate = /** @type {LionCheckboxIndeterminate} */ (
el.querySelector(`${cfg.tagString}`)
);
const checkboxEls = /** @type {LionCheckbox[]} */ (
Array.from(el.querySelectorAll(`${cfg.childTagString}`))
);
expect(checkboxEls.map(checkboxEl => checkboxEl.checked)).to.eql([true, false, false]);
// @ts-ignore for testing purposes, we access this protected getter
elIndeterminate._inputNode.click();
await elIndeterminate.updateComplete;
expect(checkboxEls.map(checkboxEl => checkboxEl.checked)).to.eql([true, true, true]);
// @ts-ignore for testing purposes, we access this protected getter
elIndeterminate._inputNode.click();
await elIndeterminate.updateComplete;
expect(checkboxEls.map(checkboxEl => checkboxEl.checked)).to.eql([false, false, false]);
// @ts-ignore for testing purposes, we access this protected getter
elIndeterminate._inputNode.click();
await elIndeterminate.updateComplete;
expect(checkboxEls.map(checkboxEl => checkboxEl.checked)).to.eql([true, false, false]);
});
it('should no longer reach indeterminate state if the child boxes are all checked or all unchecked during indeterminate state', async () => {
const el = await fixture(html`
<${groupTag} name="scientists[]">
<${tag} mixed-state label="Favorite scientists">
<${childTag} label="Archimedes" checked></${childTag}>
<${childTag} label="Francis Bacon"></${childTag}>
<${childTag} label="Marie Curie"></${childTag}>
</${tag}>
</${groupTag}>
`);
const elIndeterminate = /** @type {LionCheckboxIndeterminate} */ (
el.querySelector(`${cfg.tagString}`)
);
const checkboxEls = /** @type {LionCheckbox[]} */ (
Array.from(el.querySelectorAll(`${cfg.childTagString}`))
);
// Check when all child boxes in indeterminate state are unchecked
// we don't have a tri-state, but a duo-state.
// @ts-ignore for testing purposes, we access this protected getter
checkboxEls[0]._inputNode.click();
await elIndeterminate.updateComplete;
// @ts-ignore for testing purposes, we access this protected getter
elIndeterminate._inputNode.click();
await elIndeterminate.updateComplete;
expect(elIndeterminate.checked).to.be.true;
expect(elIndeterminate.indeterminate).to.be.false;
// @ts-ignore for testing purposes, we access this protected getter
elIndeterminate._inputNode.click();
await elIndeterminate.updateComplete;
expect(elIndeterminate.checked).to.be.false;
expect(elIndeterminate.indeterminate).to.be.false;
// @ts-ignore for testing purposes, we access this protected getter
elIndeterminate._inputNode.click();
await elIndeterminate.updateComplete;
expect(elIndeterminate.checked).to.be.true;
expect(elIndeterminate.indeterminate).to.be.false;
// Check when all child boxes in indeterminate state are getting checked
// we also don't have a tri-state, but a duo-state.
// @ts-ignore for testing purposes, we access this protected getter
elIndeterminate._inputNode.click(); // unchecked
await elIndeterminate.updateComplete;
for (const checkEl of checkboxEls) {
// @ts-ignore for testing purposes, we access this protected getter
checkEl._inputNode.click();
// Give each checking of the sub checkbox a chance to finish updating
// This means indeterminate state will be true for a bit and the state gets stored
await checkEl.updateComplete;
await elIndeterminate.updateComplete;
}
expect(elIndeterminate.checked).to.be.true;
expect(elIndeterminate.indeterminate).to.be.false;
// @ts-ignore for testing purposes, we access this protected getter
elIndeterminate._inputNode.click();
await elIndeterminate.updateComplete;
expect(elIndeterminate.checked).to.be.false;
expect(elIndeterminate.indeterminate).to.be.false;
// @ts-ignore for testing purposes, we access this protected getter
elIndeterminate._inputNode.click();
await elIndeterminate.updateComplete;
expect(elIndeterminate.checked).to.be.true;
expect(elIndeterminate.indeterminate).to.be.false;
});
});
describe('accessibility', () => {
it('is accessible', async () => {
const el = /** @type {LionCheckboxGroup} */ (
await fixture(html`
<${groupTag} name="scientists[]">
<${tag} label="Favorite scientists">
<${childTag} label="Archimedes" checked></${childTag}>
<${childTag} label="Francis Bacon"></${childTag}>
<${childTag} label="Marie Curie"></${childTag}>
</${tag}>
</${groupTag}>
`)
);
await expect(el).to.be.accessible();
});
it('has role="list" and its children role="listitem"', async () => {
const el = await fixture(html`
<${groupTag} name="scientists[]">
<${tag} label="Favorite scientists">
<${childTag} label="Archimedes" checked></${childTag}>
<${childTag} label="Francis Bacon"></${childTag}>
<${childTag} label="Marie Curie"></${childTag}>
</${tag}>
</${groupTag}>
`);
const elIndeterminate = /** @type {LionCheckboxIndeterminate} */ (
el.querySelector(`${cfg.tagString}`)
);
expect(
elIndeterminate.shadowRoot
?.querySelector('.choice-field__nested-checkboxes')
?.getAttribute('role'),
).to.equal('list');
expect(elIndeterminate.children[0].getAttribute('role')).to.equal('listitem');
});
});
});
}