From 9b9e78951f01da387463ce7ee9e869a762bab84e Mon Sep 17 00:00:00 2001 From: gerjanvangeest Date: Mon, 5 Feb 2024 16:26:44 +0100 Subject: [PATCH] fix(checkbox-group): add role=list and role=listitem to checkbox-indeterminate and its children --- .changeset/forty-teachers-sleep.md | 5 + .../src/LionCheckboxIndeterminate.js | 19 +- .../CheckboxIndeterminate.suite.js | 831 +++++++++--------- ...heckbox-indeterminate-integrations.test.js | 3 + 4 files changed, 460 insertions(+), 398 deletions(-) create mode 100644 .changeset/forty-teachers-sleep.md diff --git a/.changeset/forty-teachers-sleep.md b/.changeset/forty-teachers-sleep.md new file mode 100644 index 0000000000..8ed4b062c4 --- /dev/null +++ b/.changeset/forty-teachers-sleep.md @@ -0,0 +1,5 @@ +--- +'@lion/ui': patch +--- + +[checkbox-group] add role="list" and role="listitem" to checkbox-indeterminate and its children diff --git a/packages/ui/components/checkbox-group/src/LionCheckboxIndeterminate.js b/packages/ui/components/checkbox-group/src/LionCheckboxIndeterminate.js index 4f8699ca44..bc264fd8c2 100644 --- a/packages/ui/components/checkbox-group/src/LionCheckboxIndeterminate.js +++ b/packages/ui/components/checkbox-group/src/LionCheckboxIndeterminate.js @@ -197,19 +197,34 @@ export class LionCheckboxIndeterminate extends LionCheckbox { // eslint-disable-next-line class-methods-use-this _afterTemplate() { return html` -
+
`; } /** + * @param {Event} ev * @protected */ - _onRequestToAddFormElement() { + _onRequestToAddFormElement(ev) { + if (!(/** @type {HTMLElement} */ (ev.target).hasAttribute('role'))) { + /** @type {HTMLElement} */ (ev.target)?.setAttribute('role', 'listitem'); + } this._setOwnCheckedState(); } + /** + * @param {Event} ev + * @protected + */ + // eslint-disable-next-line class-methods-use-this + _onRequestToRemoveFormElement(ev) { + if (/** @type {HTMLElement} */ (ev.target).getAttribute('role') === 'listitem') { + /** @type {HTMLElement} */ (ev.target)?.removeAttribute('role'); + } + } + constructor() { super(); this.indeterminate = false; diff --git a/packages/ui/components/checkbox-group/test-suites/CheckboxIndeterminate.suite.js b/packages/ui/components/checkbox-group/test-suites/CheckboxIndeterminate.suite.js index 68691a1d4d..98e5e6a224 100644 --- a/packages/ui/components/checkbox-group/test-suites/CheckboxIndeterminate.suite.js +++ b/packages/ui/components/checkbox-group/test-suites/CheckboxIndeterminate.suite.js @@ -79,11 +79,10 @@ export function runCheckboxIndeterminateSuite(customConfig) { // 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` + 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"> @@ -93,17 +92,17 @@ export function runCheckboxIndeterminateSuite(customConfig) { `); - const elIndeterminate = /** @type {LionCheckboxIndeterminate} */ ( - el.querySelector(`${cfg.tagString}`) - ); - // Assert - expect(elIndeterminate?.hasAttribute('indeterminate')).to.be.true; - }); + 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` + 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> @@ -112,20 +111,20 @@ export function runCheckboxIndeterminateSuite(customConfig) { `) - ); - const elIndeterminate = /** @type {LionCheckboxIndeterminate} */ ( - el.querySelector(`${cfg.tagString}`) - ); - - // Assert - expect(elIndeterminate?.hasAttribute('indeterminate')).to.be.false; - expect(elIndeterminate?.checked).to.be.true; - }); + ); + const elIndeterminate = /** @type {LionCheckboxIndeterminate} */ ( + el.querySelector(`${cfg.tagString}`) + ); - it('should become indeterminate if one child is checked', async () => { - // Arrange - const el = /** @type {LionCheckboxGroup} */ ( - await fixture(html` + // 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"> @@ -134,25 +133,25 @@ export function runCheckboxIndeterminateSuite(customConfig) { `) - ); - const elIndeterminate = /** @type {LionCheckboxIndeterminate} */ ( - el.querySelector(`${cfg.tagString}`) - ); + ); + const elIndeterminate = /** @type {LionCheckboxIndeterminate} */ ( + el.querySelector(`${cfg.tagString}`) + ); - const { _subCheckboxes } = getCheckboxIndeterminateMembers(elIndeterminate); + const { _subCheckboxes } = getCheckboxIndeterminateMembers(elIndeterminate); - // Act - _subCheckboxes[0].checked = true; - await el.updateComplete; + // Act + _subCheckboxes[0].checked = true; + await el.updateComplete; - // Assert - expect(elIndeterminate?.hasAttribute('indeterminate')).to.be.true; - }); + // 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` + 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"> @@ -161,27 +160,27 @@ export function runCheckboxIndeterminateSuite(customConfig) { `) - ); - 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; - }); + ); + 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; - it('should become indeterminate if all children except disabled ones are checked', async () => { - // Arrange - const el = /** @type {LionCheckboxGroup} */ ( - await fixture(html` + // 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"> @@ -190,26 +189,26 @@ export function runCheckboxIndeterminateSuite(customConfig) { `) - ); - 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.false; - }); + ); + const elIndeterminate = /** @type {LionCheckboxIndeterminate} */ ( + el.querySelector(`${cfg.tagString}`) + ); + const { _subCheckboxes } = getCheckboxIndeterminateMembers(elIndeterminate); - it('should sync all children when parent is checked (from indeterminate to checked)', async () => { - // Arrange - const el = /** @type {LionCheckboxGroup} */ ( - await fixture(html` + // 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.false; + }); + + 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"> @@ -218,27 +217,27 @@ export function runCheckboxIndeterminateSuite(customConfig) { `) - ); - 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; - }); + ); + const elIndeterminate = /** @type {LionCheckboxIndeterminate} */ ( + el.querySelector(`${cfg.tagString}`) + ); + const { _subCheckboxes, _inputNode } = getCheckboxIndeterminateMembers(elIndeterminate); + + // Act + _inputNode.click(); + await elIndeterminate.updateComplete; - it('should not sync any disabled children when parent is checked (from indeterminate to checked)', async () => { - // Arrange - const el = /** @type {LionCheckboxGroup} */ ( - await fixture(html` + // 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"> @@ -247,27 +246,27 @@ export function runCheckboxIndeterminateSuite(customConfig) { `) - ); - 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; - }); + ); + 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` + 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> @@ -276,28 +275,28 @@ export function runCheckboxIndeterminateSuite(customConfig) { `) - ); - 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; - }); + ); + 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` + 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> @@ -306,28 +305,28 @@ export function runCheckboxIndeterminateSuite(customConfig) { `) - ); - 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; - }); + ); + 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` + 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"> @@ -336,27 +335,27 @@ export function runCheckboxIndeterminateSuite(customConfig) { `) - ); - 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; - }); + ); + const elIndeterminate = /** @type {LionCheckboxIndeterminate} */ ( + el.querySelector(`${cfg.tagString}`) + ); + const { _subCheckboxes, _inputNode } = getCheckboxIndeterminateMembers(elIndeterminate); - it('should sync all children when parent is checked (from checked to unchecked)', async () => { - // Arrange - const el = /** @type {LionCheckboxGroup} */ ( - await fixture(html` + // 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> @@ -365,27 +364,27 @@ export function runCheckboxIndeterminateSuite(customConfig) { `) - ); - 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; - }); + ); + const elIndeterminate = /** @type {LionCheckboxIndeterminate} */ ( + el.querySelector(`${cfg.tagString}`) + ); + const { _subCheckboxes, _inputNode } = getCheckboxIndeterminateMembers(elIndeterminate); - it('should work as expected with siblings checkbox-indeterminate', async () => { - // Arrange - const el = /** @type {LionCheckboxGroup} */ ( - await fixture(html` + // 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 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" @@ -420,38 +419,38 @@ export function runCheckboxIndeterminateSuite(customConfig) { `) - ); - const elFirstIndeterminate = /** @type {LionCheckboxIndeterminate} */ ( - el.querySelector('#first-checkbox-indeterminate') - ); + ); + const elFirstIndeterminate = /** @type {LionCheckboxIndeterminate} */ ( + el.querySelector('#first-checkbox-indeterminate') + ); - const elSecondIndeterminate = /** @type {LionCheckboxIndeterminate} */ ( - el.querySelector('#second-checkbox-indeterminate') - ); + const elSecondIndeterminate = /** @type {LionCheckboxIndeterminate} */ ( + el.querySelector('#second-checkbox-indeterminate') + ); - const elFirstSubCheckboxes = getCheckboxIndeterminateMembers(elFirstIndeterminate); - const elSecondSubCheckboxes = getCheckboxIndeterminateMembers(elSecondIndeterminate); + const elFirstSubCheckboxes = getCheckboxIndeterminateMembers(elFirstIndeterminate); + const elSecondSubCheckboxes = getCheckboxIndeterminateMembers(elSecondIndeterminate); - // Act - check the first sibling - elFirstSubCheckboxes._inputNode.click(); - await elFirstIndeterminate.updateComplete; - await elSecondIndeterminate.updateComplete; + // Act - check the first sibling + elFirstSubCheckboxes._inputNode.click(); + await elFirstIndeterminate.updateComplete; + await elSecondIndeterminate.updateComplete; - // Assert - the second sibling should not be affected + // 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(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; - }); + expect(elSecondSubCheckboxes._subCheckboxes[0].hasAttribute('checked')).to.be.false; + expect(elSecondSubCheckboxes._subCheckboxes[1].hasAttribute('checked')).to.be.false; + }); - it('should work as expected with nested indeterminate checkboxes', async () => { - // Arrange - const el = /** @type {LionCheckboxGroup} */ ( - await fixture(html` + 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} @@ -484,62 +483,62 @@ export function runCheckboxIndeterminateSuite(customConfig) { `) - ); - 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) { + ); + 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 - elParentSubCheckboxes._subCheckboxes[0]._inputNode.click(); - } - if (elParentIndeterminate) { + if (elNestedIndeterminate) elNestedSubCheckboxes._subCheckboxes[1]._inputNode.click(); // @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; - }); + if (elNestedIndeterminate) elNestedSubCheckboxes._subCheckboxes[2]._inputNode.click(); + await el.updateComplete; - it('should work as expected if extra html', async () => { - // Arrange - const el = /** @type {LionCheckboxGroup} */ ( - await fixture(html` + // 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[]">
Let's have some fun @@ -555,27 +554,27 @@ export function runCheckboxIndeterminateSuite(customConfig) {
Too much fun, stop it !
`) - ); - 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; - }); + ); + const elIndeterminate = /** @type {LionCheckboxIndeterminate} */ ( + el.querySelector(`${cfg.tagString}`) + ); + const { _subCheckboxes } = getCheckboxIndeterminateMembers(elIndeterminate); - // 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` + // 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> @@ -584,35 +583,35 @@ export function runCheckboxIndeterminateSuite(customConfig) { `); - const elIndeterminate = /** @type {LionCheckboxIndeterminate} */ ( - el.querySelector(`${cfg.tagString}`) - ); + 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; + 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.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.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; - }); + // @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` + 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> @@ -621,33 +620,33 @@ export function runCheckboxIndeterminateSuite(customConfig) { `); - const elIndeterminate = /** @type {LionCheckboxIndeterminate} */ ( - el.querySelector(`${cfg.tagString}`) - ); - const checkboxEls = /** @type {LionCheckbox[]} */ ( - Array.from(el.querySelectorAll(`${cfg.childTagString}`)) - ); + 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]); + 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([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([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]); - }); + // @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` + 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> @@ -656,67 +655,107 @@ export function runCheckboxIndeterminateSuite(customConfig) { `); - const elIndeterminate = /** @type {LionCheckboxIndeterminate} */ ( - el.querySelector(`${cfg.tagString}`) - ); - const checkboxEls = /** @type {LionCheckbox[]} */ ( - Array.from(el.querySelectorAll(`${cfg.childTagString}`)) - ); + 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. + // 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 + 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.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.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; + // @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. + // 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; + 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; - 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.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; + }); + }); - // @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} label="Francis Bacon"> + <${childTag} label="Marie Curie"> + + + `) + ); + 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} label="Francis Bacon"> + <${childTag} label="Marie Curie"> + + + `); + 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'); + }); }); }); } diff --git a/packages/ui/components/checkbox-group/test/lion-checkbox-indeterminate-integrations.test.js b/packages/ui/components/checkbox-group/test/lion-checkbox-indeterminate-integrations.test.js index ec076ada7c..158b94093d 100644 --- a/packages/ui/components/checkbox-group/test/lion-checkbox-indeterminate-integrations.test.js +++ b/packages/ui/components/checkbox-group/test/lion-checkbox-indeterminate-integrations.test.js @@ -1,4 +1,7 @@ import '@lion/ui/define/lion-checkbox-indeterminate.js'; +import '@lion/ui/define/lion-checkbox-group.js'; +import '@lion/ui/define/lion-checkbox.js'; + import { runChoiceInputMixinSuite } from '@lion/ui/form-core-test-suites.js'; import { runCheckboxIndeterminateSuite } from '../test-suites/CheckboxIndeterminate.suite.js';