From 258b0c57a71138c30269cb4e2f6abaef9b51dc9d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Niels=20Lyngs=C3=B8?= Date: Tue, 26 Nov 2024 17:17:45 +0100 Subject: [PATCH 01/28] only care about the composedPath when selectableTarget is this. --- packages/uui-base/lib/mixins/SelectableMixin.ts | 16 +++++++++++----- .../lib/uui-color-swatch.element.ts | 6 ++++++ 2 files changed, 17 insertions(+), 5 deletions(-) diff --git a/packages/uui-base/lib/mixins/SelectableMixin.ts b/packages/uui-base/lib/mixins/SelectableMixin.ts index af40771f9..4f515ab98 100644 --- a/packages/uui-base/lib/mixins/SelectableMixin.ts +++ b/packages/uui-base/lib/mixins/SelectableMixin.ts @@ -94,11 +94,17 @@ export const SelectableMixin = >( }; readonly #onClick = (e: Event) => { - const composePath = e.composedPath(); - if ( - (this._selectable || (this.deselectable && this.selected)) && - composePath.indexOf(this.selectableTarget) === 0 - ) { + const isSelectable = + this._selectable || (this.deselectable && this.selected); + + if (isSelectable === false) return; + + if (this.selectableTarget === this) { + // If target is this, then only allow selection if the click is on the element itself. + if (e.composedPath().indexOf(this.selectableTarget) === 0) { + this.#toggleSelect(); + } + } else { this.#toggleSelect(); } }; diff --git a/packages/uui-color-swatch/lib/uui-color-swatch.element.ts b/packages/uui-color-swatch/lib/uui-color-swatch.element.ts index 233cba458..44dc55ef1 100644 --- a/packages/uui-color-swatch/lib/uui-color-swatch.element.ts +++ b/packages/uui-color-swatch/lib/uui-color-swatch.element.ts @@ -1,6 +1,7 @@ import { defineElement } from '@umbraco-ui/uui-base/lib/registration'; import { property } from 'lit/decorators.js'; import { css, html, LitElement, nothing } from 'lit'; +import { ref } from 'lit/directives/ref.js'; import { iconCheck } from '@umbraco-ui/uui-icon-registry-essential/lib/svgs'; import { ActiveMixin, @@ -109,10 +110,15 @@ export class UUIColorSwatchElement extends LabelMixin( } } + #selectButtonChanged(button?: Element | undefined) { + this.selectableTarget = button || this; + } + render() { return html` ` : ''} - ${this.href ? this._renderLabelAsAnchor() : this._renderLabelAsButton()} + ${this.href && !this.selectOnly + ? this._renderLabelAsAnchor() + : this._renderLabelAsButton()}
- ${this.selectOnly === false - ? html`` - : ''} + ${this.loading ? html`` : ''} From 3287111305a02990f54ca0fcb78d68e071a9c331 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Niels=20Lyngs=C3=B8?= Date: Wed, 27 Nov 2024 12:45:26 +0100 Subject: [PATCH 21/28] clean up tests --- .../uui-menu-item/lib/uui-menu-item.test.ts | 36 +++++-------------- 1 file changed, 8 insertions(+), 28 deletions(-) diff --git a/packages/uui-menu-item/lib/uui-menu-item.test.ts b/packages/uui-menu-item/lib/uui-menu-item.test.ts index d86102763..f701ab528 100644 --- a/packages/uui-menu-item/lib/uui-menu-item.test.ts +++ b/packages/uui-menu-item/lib/uui-menu-item.test.ts @@ -116,7 +116,7 @@ describe('UUIMenuItemElement', () => { element.addEventListener(UUISelectableEvent.SELECTED, e => { e.preventDefault(); }); - const listener = oneEvent(element, UUISelectableEvent.SELECTED, false); + const listener = oneEvent(element, UUISelectableEvent.SELECTED); labelElement.click(); const event = await listener; expect(event).to.exist; @@ -136,11 +136,7 @@ describe('UUIMenuItemElement', () => { element.addEventListener(UUISelectableEvent.DESELECTED, e => { e.preventDefault(); }); - const listener = oneEvent( - element, - UUISelectableEvent.DESELECTED, - false, - ); + const listener = oneEvent(element, UUISelectableEvent.DESELECTED); labelElement.click(); const event = await listener; expect(event).to.exist; @@ -156,11 +152,7 @@ describe('UUIMenuItemElement', () => { const labelElement = element.shadowRoot!.querySelector( '#caret-button', ) as HTMLElement; - const listener = oneEvent( - element, - UUIMenuItemEvent.SHOW_CHILDREN, - false, - ); + const listener = oneEvent(element, UUIMenuItemEvent.SHOW_CHILDREN); labelElement.click(); const event = await listener; expect(event).to.exist; @@ -176,11 +168,7 @@ describe('UUIMenuItemElement', () => { element.addEventListener(UUIMenuItemEvent.SHOW_CHILDREN, e => { e.preventDefault(); }); - const listener = oneEvent( - element, - UUIMenuItemEvent.SHOW_CHILDREN, - false, - ); + const listener = oneEvent(element, UUIMenuItemEvent.SHOW_CHILDREN); labelElement.click(); const event = await listener; expect(event).to.exist; @@ -197,11 +185,7 @@ describe('UUIMenuItemElement', () => { const labelElement = element.shadowRoot!.querySelector( '#caret-button', ) as HTMLElement; - const listener = oneEvent( - element, - UUIMenuItemEvent.HIDE_CHILDREN, - false, - ); + const listener = oneEvent(element, UUIMenuItemEvent.HIDE_CHILDREN); labelElement.click(); const event = await listener; expect(event).to.exist; @@ -218,11 +202,7 @@ describe('UUIMenuItemElement', () => { element.addEventListener(UUIMenuItemEvent.HIDE_CHILDREN, e => { e.preventDefault(); }); - const listener = oneEvent( - element, - UUIMenuItemEvent.HIDE_CHILDREN, - false, - ); + const listener = oneEvent(element, UUIMenuItemEvent.HIDE_CHILDREN); labelElement.click(); const event = await listener; expect(event).to.exist; @@ -263,7 +243,7 @@ describe('UUIMenuItemElement', () => { it('emits a show-children event when expand icon is clicked', async () => { element.setAttribute('has-children', 'true'); await elementUpdated(element); - const listener = oneEvent(element, 'show-children', false); + const listener = oneEvent(element, 'show-children'); const caretIconElement: HTMLElement | null = element.shadowRoot!.querySelector('#caret-button'); caretIconElement?.click(); @@ -277,7 +257,7 @@ describe('UUIMenuItemElement', () => { element.setAttribute('has-children', 'true'); element.setAttribute('show-children', 'true'); await elementUpdated(element); - const listener = oneEvent(element, 'hide-children', false); + const listener = oneEvent(element, 'hide-children'); const caretIconElement: HTMLElement | null = element.shadowRoot!.querySelector('#caret-button'); caretIconElement?.click(); From 076392e7dedd5d56c11f236b657b41b9e82bb35e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Niels=20Lyngs=C3=B8?= Date: Wed, 27 Nov 2024 12:46:18 +0100 Subject: [PATCH 22/28] clean up --- packages/uui-menu-item/lib/uui-menu-item.test.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/uui-menu-item/lib/uui-menu-item.test.ts b/packages/uui-menu-item/lib/uui-menu-item.test.ts index f701ab528..240b5841f 100644 --- a/packages/uui-menu-item/lib/uui-menu-item.test.ts +++ b/packages/uui-menu-item/lib/uui-menu-item.test.ts @@ -90,7 +90,7 @@ describe('UUIMenuItemElement', () => { describe('events', () => { it('emits a click-label event when button is clicked', async () => { - const listener = oneEvent(element, UUIMenuItemEvent.CLICK_LABEL, false); + const listener = oneEvent(element, UUIMenuItemEvent.CLICK_LABEL); const buttonElement = element.shadowRoot!.querySelector( 'button#label-button', From 6bda176cb5d56029f8ea365b6adf91f7ce81139e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Niels=20Lyngs=C3=B8?= Date: Wed, 27 Nov 2024 13:17:54 +0100 Subject: [PATCH 23/28] only set tabindex on this targets --- .../uui-base/lib/mixins/SelectableMixin.ts | 22 ++++++++++++------- 1 file changed, 14 insertions(+), 8 deletions(-) diff --git a/packages/uui-base/lib/mixins/SelectableMixin.ts b/packages/uui-base/lib/mixins/SelectableMixin.ts index a14c5d13f..2fa734f16 100644 --- a/packages/uui-base/lib/mixins/SelectableMixin.ts +++ b/packages/uui-base/lib/mixins/SelectableMixin.ts @@ -56,10 +56,13 @@ export const SelectableMixin = >( this._selectable = newVal; // Potentially problematic as a component might need focus for another feature when not selectable: - //if (this.#selectableTarget === this) { - // If the selectable target, then make it self selectable. (A different selectable target should be made focusable by the component itself) - this.#selectableTarget.setAttribute('tabindex', `${newVal ? '0' : '-1'}`); - //} + if (this.#selectableTarget === this) { + // If the selectable target, then make it self selectable. (A different selectable target should be made focusable by the component itself) + this.#selectableTarget.setAttribute( + 'tabindex', + `${newVal ? '0' : '-1'}`, + ); + } this.requestUpdate('selectable', oldVal); } @@ -88,10 +91,13 @@ export const SelectableMixin = >( ); this.#selectableTarget = target as Element; - this.#selectableTarget.setAttribute( - 'tabindex', - this._selectable ? '0' : '-1', - ); + if (this.#selectableTarget === this) { + // If the selectable target, then make it self selectable. (A different selectable target should be made focusable by the component itself) + this.#selectableTarget.setAttribute( + 'tabindex', + this._selectable ? '0' : '-1', + ); + } target.addEventListener('click', this.#onClick); target.addEventListener('keydown', this.#onKeydown as EventListener); } From 0a9c15c754111dbba8b9024a7940ab7f51801eaf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Niels=20Lyngs=C3=B8?= Date: Wed, 27 Nov 2024 13:18:09 +0100 Subject: [PATCH 24/28] prevent click-label when select only --- packages/uui-menu-item/lib/uui-menu-item.element.ts | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/packages/uui-menu-item/lib/uui-menu-item.element.ts b/packages/uui-menu-item/lib/uui-menu-item.element.ts index 5c39465b5..ce25e25d4 100644 --- a/packages/uui-menu-item/lib/uui-menu-item.element.ts +++ b/packages/uui-menu-item/lib/uui-menu-item.element.ts @@ -163,10 +163,11 @@ export class UUIMenuItemElement extends SelectOnlyMixin( this.showChildren = !this.showChildren; }; - private _onLabelClicked = () => { + #onLabelClicked() { + if (this.selectOnly) return; const event = new UUIMenuItemEvent(UUIMenuItemEvent.CLICK_LABEL); this.dispatchEvent(event); - }; + } private _renderLabelInside() { return html` ${this._renderLabelInside()} @@ -206,7 +207,7 @@ export class UUIMenuItemElement extends SelectOnlyMixin( return html` ` : ''} - ${this.href && !this.selectOnly + ${this.href && this.selectOnly !== true && this.selectable !== true ? this._renderLabelAsAnchor() : this._renderLabelAsButton()} From 625585611eb0fdd74d2c22aed1f9ba7f26c04fef Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Niels=20Lyngs=C3=B8?= Date: Wed, 27 Nov 2024 13:18:23 +0100 Subject: [PATCH 25/28] story and tests --- .../uui-menu-item/lib/uui-menu-item.story.ts | 6 ++ .../uui-menu-item/lib/uui-menu-item.test.ts | 65 +++++++++++++++++++ 2 files changed, 71 insertions(+) diff --git a/packages/uui-menu-item/lib/uui-menu-item.story.ts b/packages/uui-menu-item/lib/uui-menu-item.story.ts index 525fca885..3c08cc08b 100644 --- a/packages/uui-menu-item/lib/uui-menu-item.story.ts +++ b/packages/uui-menu-item/lib/uui-menu-item.story.ts @@ -163,6 +163,12 @@ export const Selectable: Story = { selectable: true, }, }; +export const SelectOnly: Story = { + args: { + selectable: true, + selectOnly: true, + }, +}; export const Anchor: Story = { args: { diff --git a/packages/uui-menu-item/lib/uui-menu-item.test.ts b/packages/uui-menu-item/lib/uui-menu-item.test.ts index 240b5841f..4c4b632d6 100644 --- a/packages/uui-menu-item/lib/uui-menu-item.test.ts +++ b/packages/uui-menu-item/lib/uui-menu-item.test.ts @@ -304,6 +304,71 @@ describe('UUIMenuItemElement', () => { labelElement?.click(); expect(element.selected).to.be.false; }); + + it('can expand', async () => { + element.setAttribute('has-children', 'true'); + await elementUpdated(element); + const listener = oneEvent(element, 'show-children'); + const caretIconElement: HTMLElement | null = + element.shadowRoot!.querySelector('#caret-button'); + caretIconElement?.click(); + const event = await listener; + expect(event).to.exist; + expect(event.type).to.equal('show-children'); + expect(element.hasAttribute('show-children')).to.equal(true); + }); + }); + + describe('selectable & selectOnly', () => { + let labelElement: HTMLElement | null; + + beforeEach(async () => { + labelElement = element.shadowRoot!.querySelector('#label-button'); + element.selectable = true; + element.selectOnly = true; + }); + + it('label element is defined', () => { + expect(labelElement).to.be.instanceOf(HTMLElement); + }); + + it('label is rendered as a button tag', async () => { + await elementUpdated(element); + expect(labelElement?.nodeName).to.be.equal('BUTTON'); + }); + + it('can be selected when selectable', async () => { + await elementUpdated(element); + labelElement?.click(); + expect(element.selected).to.be.true; + }); + + it('can not be selected when not selectable', async () => { + element.selectable = false; + await elementUpdated(element); + labelElement?.click(); + expect(element.selected).to.be.false; + }); + + it('can be selected when selectable', async () => { + element.disabled = true; + await elementUpdated(element); + labelElement?.click(); + expect(element.selected).to.be.false; + }); + + it('can expand', async () => { + element.setAttribute('has-children', 'true'); + await elementUpdated(element); + const listener = oneEvent(element, 'show-children'); + const caretIconElement: HTMLElement | null = + element.shadowRoot!.querySelector('#caret-button'); + caretIconElement?.click(); + const event = await listener; + expect(event).to.exist; + expect(event.type).to.equal('show-children'); + expect(element.hasAttribute('show-children')).to.equal(true); + }); }); describe('HREF', () => { From 41014f8a49e82510c2f612394e62aaee8409860b Mon Sep 17 00:00:00 2001 From: Mads Rasmussen Date: Wed, 27 Nov 2024 13:24:21 +0100 Subject: [PATCH 26/28] use send mouse --- .../uui-menu-item/lib/uui-menu-item.test.ts | 39 +++++++++++++++---- 1 file changed, 32 insertions(+), 7 deletions(-) diff --git a/packages/uui-menu-item/lib/uui-menu-item.test.ts b/packages/uui-menu-item/lib/uui-menu-item.test.ts index 4c4b632d6..e2c779b8a 100644 --- a/packages/uui-menu-item/lib/uui-menu-item.test.ts +++ b/packages/uui-menu-item/lib/uui-menu-item.test.ts @@ -10,6 +10,7 @@ import '@umbraco-ui/uui-loader-bar/lib'; import { UUIMenuItemElement } from './uui-menu-item.element'; import { UUIMenuItemEvent } from './UUIMenuItemEvent'; import { UUISelectableEvent } from '@umbraco-ui/uui-base/lib/events'; +import { sendMouse } from '@web/test-runner-commands'; describe('UUIMenuItemElement', () => { let element: UUIMenuItemElement; @@ -287,21 +288,33 @@ describe('UUIMenuItemElement', () => { it('can be selected when selectable', async () => { await elementUpdated(element); - labelElement?.click(); + await sendMouse({ + type: 'click', + position: [75, 30], + button: 'left', + }); expect(element.selected).to.be.true; }); it('can not be selected when not selectable', async () => { element.selectable = false; await elementUpdated(element); - labelElement?.click(); + await sendMouse({ + type: 'click', + position: [75, 30], + button: 'left', + }); expect(element.selected).to.be.false; }); it('can be selected when selectable', async () => { element.disabled = true; await elementUpdated(element); - labelElement?.click(); + await sendMouse({ + type: 'click', + position: [75, 30], + button: 'left', + }); expect(element.selected).to.be.false; }); @@ -339,21 +352,33 @@ describe('UUIMenuItemElement', () => { it('can be selected when selectable', async () => { await elementUpdated(element); - labelElement?.click(); + await sendMouse({ + type: 'click', + position: [75, 30], + button: 'left', + }); expect(element.selected).to.be.true; }); it('can not be selected when not selectable', async () => { element.selectable = false; await elementUpdated(element); - labelElement?.click(); + await sendMouse({ + type: 'click', + position: [75, 30], + button: 'left', + }); expect(element.selected).to.be.false; }); - it('can be selected when selectable', async () => { + it('can not be selected when disabled', async () => { element.disabled = true; await elementUpdated(element); - labelElement?.click(); + await sendMouse({ + type: 'click', + position: [75, 30], + button: 'left', + }); expect(element.selected).to.be.false; }); From 605dcab7280a15d1b580aba438cafcc6bcae926b Mon Sep 17 00:00:00 2001 From: Mads Rasmussen Date: Wed, 27 Nov 2024 13:24:52 +0100 Subject: [PATCH 27/28] update title --- packages/uui-menu-item/lib/uui-menu-item.test.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/uui-menu-item/lib/uui-menu-item.test.ts b/packages/uui-menu-item/lib/uui-menu-item.test.ts index e2c779b8a..0be1ac91e 100644 --- a/packages/uui-menu-item/lib/uui-menu-item.test.ts +++ b/packages/uui-menu-item/lib/uui-menu-item.test.ts @@ -307,7 +307,7 @@ describe('UUIMenuItemElement', () => { expect(element.selected).to.be.false; }); - it('can be selected when selectable', async () => { + it('can not be selected when disabled', async () => { element.disabled = true; await elementUpdated(element); await sendMouse({ From fd83e1b70a68c8194a6fece6f7281960be937204 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Niels=20Lyngs=C3=B8?= Date: Wed, 27 Nov 2024 13:47:31 +0100 Subject: [PATCH 28/28] wrap tests --- .../uui-menu-item/lib/uui-menu-item.test.ts | 640 +++++++++--------- 1 file changed, 321 insertions(+), 319 deletions(-) diff --git a/packages/uui-menu-item/lib/uui-menu-item.test.ts b/packages/uui-menu-item/lib/uui-menu-item.test.ts index 0be1ac91e..dfc2aa15b 100644 --- a/packages/uui-menu-item/lib/uui-menu-item.test.ts +++ b/packages/uui-menu-item/lib/uui-menu-item.test.ts @@ -13,386 +13,388 @@ import { UUISelectableEvent } from '@umbraco-ui/uui-base/lib/events'; import { sendMouse } from '@web/test-runner-commands'; describe('UUIMenuItemElement', () => { - let element: UUIMenuItemElement; - - beforeEach(async () => { - element = await fixture( - html``, - ); - }); - - it('is defined', () => { - expect(element).to.be.instanceOf(UUIMenuItemElement); - }); - - it('passes the a11y audit', async () => { - await expect(element).shadowDom.to.be.accessible(); - }); - - it('passes the a11y audit with nesting', async () => { - element = await fixture( - html` - - - `, - ); - await expect(element).shadowDom.to.be.accessible(); - }); + describe('element', () => { + let element: UUIMenuItemElement; - describe('properties', () => { - it('has a disabled property', () => { - expect(element).to.have.property('disabled'); - }); - it('disable property defaults to false', () => { - expect(element.disabled).to.false; + beforeEach(async () => { + element = await fixture( + html``, + ); }); - it('has a showChildren property', () => { - expect(element).to.have.property('showChildren'); - }); - it('showChildren property defaults to false', () => { - expect(element.showChildren).to.false; + it('is defined', () => { + expect(element).to.be.instanceOf(UUIMenuItemElement); }); - it('has a hasChildren property', () => { - expect(element).to.have.property('hasChildren'); - }); - it('hasChildren property defaults to false', () => { - expect(element.hasChildren).to.false; + it('passes the a11y audit', async () => { + await expect(element).shadowDom.to.be.accessible(); }); - it('has a loading property', () => { - expect(element).to.have.property('loading'); - }); - it('loading property defaults to false', () => { - expect(element.loading).to.false; + it('passes the a11y audit with nesting', async () => { + element = await fixture( + html` + + + `, + ); + await expect(element).shadowDom.to.be.accessible(); }); - it('has a href property', () => { - expect(element).to.have.property('href'); - }); + describe('properties', () => { + it('has a disabled property', () => { + expect(element).to.have.property('disabled'); + }); + it('disable property defaults to false', () => { + expect(element.disabled).to.false; + }); - it('has a target property', () => { - expect(element).to.have.property('target'); - }); + it('has a showChildren property', () => { + expect(element).to.have.property('showChildren'); + }); + it('showChildren property defaults to false', () => { + expect(element.showChildren).to.false; + }); - it('has a rel property', () => { - expect(element).to.have.property('rel'); - }); + it('has a hasChildren property', () => { + expect(element).to.have.property('hasChildren'); + }); + it('hasChildren property defaults to false', () => { + expect(element.hasChildren).to.false; + }); - it('has a select-mode property', () => { - expect(element).to.have.property('selectMode'); - }); + it('has a loading property', () => { + expect(element).to.have.property('loading'); + }); + it('loading property defaults to false', () => { + expect(element.loading).to.false; + }); - it('select-mode property defaults to undefined', () => { - expect(element.selectMode).to.undefined; - }); - }); + it('has a href property', () => { + expect(element).to.have.property('href'); + }); - describe('events', () => { - it('emits a click-label event when button is clicked', async () => { - const listener = oneEvent(element, UUIMenuItemEvent.CLICK_LABEL); - - const buttonElement = element.shadowRoot!.querySelector( - 'button#label-button', - ) as HTMLButtonElement; - expect(buttonElement).to.exist; - buttonElement.click(); - - const event = await listener; - expect(event).to.exist; - expect(event.type).to.equal(UUIMenuItemEvent.CLICK_LABEL); - expect(event.bubbles).to.be.false; - expect(event.composed).to.be.false; - expect(event!.target).to.equal(element); - }); + it('has a target property', () => { + expect(element).to.have.property('target'); + }); - describe('select', async () => { - it('emits a cancelable selected event when selectable', async () => { - element.selectable = true; - await elementUpdated(element); - const labelElement = element.shadowRoot!.querySelector( - '#label-button', - ) as HTMLElement; - element.addEventListener(UUISelectableEvent.SELECTED, e => { - e.preventDefault(); - }); - const listener = oneEvent(element, UUISelectableEvent.SELECTED); - labelElement.click(); - const event = await listener; - expect(event).to.exist; - expect(event.type).to.equal(UUISelectableEvent.SELECTED); - expect(element.selected).to.be.false; + it('has a rel property', () => { + expect(element).to.have.property('rel'); }); - }); - describe('deselect', async () => { - it('emits a cancelable deselected event when preselected', async () => { - element.selectable = true; - element.selected = true; - await elementUpdated(element); - const labelElement = element.shadowRoot!.querySelector( - '#label-button', - ) as HTMLElement; - element.addEventListener(UUISelectableEvent.DESELECTED, e => { - e.preventDefault(); - }); - const listener = oneEvent(element, UUISelectableEvent.DESELECTED); - labelElement.click(); - const event = await listener; - expect(event).to.exist; - expect(event.type).to.equal(UUISelectableEvent.DESELECTED); - expect(element.selected).to.be.true; + it('has a select-mode property', () => { + expect(element).to.have.property('selectMode'); + }); + + it('select-mode property defaults to undefined', () => { + expect(element.selectMode).to.undefined; }); }); - describe('show-children', async () => { - it('emits a show-children event when expanded', async () => { - element.hasChildren = true; - await elementUpdated(element); - const labelElement = element.shadowRoot!.querySelector( - '#caret-button', - ) as HTMLElement; - const listener = oneEvent(element, UUIMenuItemEvent.SHOW_CHILDREN); - labelElement.click(); + describe('events', () => { + it('emits a click-label event when button is clicked', async () => { + const listener = oneEvent(element, UUIMenuItemEvent.CLICK_LABEL); + + const buttonElement = element.shadowRoot!.querySelector( + 'button#label-button', + ) as HTMLButtonElement; + expect(buttonElement).to.exist; + buttonElement.click(); + const event = await listener; expect(event).to.exist; - expect(event.type).to.equal(UUIMenuItemEvent.SHOW_CHILDREN); - expect(element.showChildren).to.be.true; + expect(event.type).to.equal(UUIMenuItemEvent.CLICK_LABEL); + expect(event.bubbles).to.be.false; + expect(event.composed).to.be.false; + expect(event!.target).to.equal(element); }); - it('emits a cancelable show-children event when expanded', async () => { - element.hasChildren = true; - await elementUpdated(element); - const labelElement = element.shadowRoot!.querySelector( - '#caret-button', - ) as HTMLElement; - element.addEventListener(UUIMenuItemEvent.SHOW_CHILDREN, e => { - e.preventDefault(); + + describe('select', async () => { + it('emits a cancelable selected event when selectable', async () => { + element.selectable = true; + await elementUpdated(element); + const labelElement = element.shadowRoot!.querySelector( + '#label-button', + ) as HTMLElement; + element.addEventListener(UUISelectableEvent.SELECTED, e => { + e.preventDefault(); + }); + const listener = oneEvent(element, UUISelectableEvent.SELECTED); + labelElement.click(); + const event = await listener; + expect(event).to.exist; + expect(event.type).to.equal(UUISelectableEvent.SELECTED); + expect(element.selected).to.be.false; }); - const listener = oneEvent(element, UUIMenuItemEvent.SHOW_CHILDREN); - labelElement.click(); - const event = await listener; - expect(event).to.exist; - expect(event.type).to.equal(UUIMenuItemEvent.SHOW_CHILDREN); - expect(element.showChildren).to.be.false; }); - }); - describe('hide-children', async () => { - it('emits a hide-children event when collapsed', async () => { - element.hasChildren = true; - element.showChildren = true; - await elementUpdated(element); - const labelElement = element.shadowRoot!.querySelector( - '#caret-button', - ) as HTMLElement; - const listener = oneEvent(element, UUIMenuItemEvent.HIDE_CHILDREN); - labelElement.click(); - const event = await listener; - expect(event).to.exist; - expect(event.type).to.equal(UUIMenuItemEvent.HIDE_CHILDREN); - expect(element.showChildren).to.be.false; + describe('deselect', async () => { + it('emits a cancelable deselected event when preselected', async () => { + element.selectable = true; + element.selected = true; + await elementUpdated(element); + const labelElement = element.shadowRoot!.querySelector( + '#label-button', + ) as HTMLElement; + element.addEventListener(UUISelectableEvent.DESELECTED, e => { + e.preventDefault(); + }); + const listener = oneEvent(element, UUISelectableEvent.DESELECTED); + labelElement.click(); + const event = await listener; + expect(event).to.exist; + expect(event.type).to.equal(UUISelectableEvent.DESELECTED); + expect(element.selected).to.be.true; + }); }); - it('emits a cancelable hide-children event when collapsed', async () => { - element.hasChildren = true; - element.showChildren = true; - await elementUpdated(element); - const labelElement = element.shadowRoot!.querySelector( - '#caret-button', - ) as HTMLElement; - element.addEventListener(UUIMenuItemEvent.HIDE_CHILDREN, e => { - e.preventDefault(); + + describe('show-children', async () => { + it('emits a show-children event when expanded', async () => { + element.hasChildren = true; + await elementUpdated(element); + const labelElement = element.shadowRoot!.querySelector( + '#caret-button', + ) as HTMLElement; + const listener = oneEvent(element, UUIMenuItemEvent.SHOW_CHILDREN); + labelElement.click(); + const event = await listener; + expect(event).to.exist; + expect(event.type).to.equal(UUIMenuItemEvent.SHOW_CHILDREN); + expect(element.showChildren).to.be.true; + }); + it('emits a cancelable show-children event when expanded', async () => { + element.hasChildren = true; + await elementUpdated(element); + const labelElement = element.shadowRoot!.querySelector( + '#caret-button', + ) as HTMLElement; + element.addEventListener(UUIMenuItemEvent.SHOW_CHILDREN, e => { + e.preventDefault(); + }); + const listener = oneEvent(element, UUIMenuItemEvent.SHOW_CHILDREN); + labelElement.click(); + const event = await listener; + expect(event).to.exist; + expect(event.type).to.equal(UUIMenuItemEvent.SHOW_CHILDREN); + expect(element.showChildren).to.be.false; }); - const listener = oneEvent(element, UUIMenuItemEvent.HIDE_CHILDREN); - labelElement.click(); - const event = await listener; - expect(event).to.exist; - expect(event.type).to.equal(UUIMenuItemEvent.HIDE_CHILDREN); - expect(element.showChildren).to.be.true; }); - }); - }); - describe('template', () => { - it('renders a default slot', () => { - const slot = element.shadowRoot!.querySelector('slot')!; - expect(slot).to.exist; + describe('hide-children', async () => { + it('emits a hide-children event when collapsed', async () => { + element.hasChildren = true; + element.showChildren = true; + await elementUpdated(element); + const labelElement = element.shadowRoot!.querySelector( + '#caret-button', + ) as HTMLElement; + const listener = oneEvent(element, UUIMenuItemEvent.HIDE_CHILDREN); + labelElement.click(); + const event = await listener; + expect(event).to.exist; + expect(event.type).to.equal(UUIMenuItemEvent.HIDE_CHILDREN); + expect(element.showChildren).to.be.false; + }); + it('emits a cancelable hide-children event when collapsed', async () => { + element.hasChildren = true; + element.showChildren = true; + await elementUpdated(element); + const labelElement = element.shadowRoot!.querySelector( + '#caret-button', + ) as HTMLElement; + element.addEventListener(UUIMenuItemEvent.HIDE_CHILDREN, e => { + e.preventDefault(); + }); + const listener = oneEvent(element, UUIMenuItemEvent.HIDE_CHILDREN); + labelElement.click(); + const event = await listener; + expect(event).to.exist; + expect(event.type).to.equal(UUIMenuItemEvent.HIDE_CHILDREN); + expect(element.showChildren).to.be.true; + }); + }); }); - it('renders an icon slot', () => { - const slot = element.shadowRoot!.querySelector('slot[name=icon]')!; - expect(slot).to.exist; - }); + describe('template', () => { + it('renders a default slot', () => { + const slot = element.shadowRoot!.querySelector('slot')!; + expect(slot).to.exist; + }); - it('renders an actions slot', () => { - const slot = element.shadowRoot!.querySelector('slot[name=actions]')!; - expect(slot).to.exist; - }); + it('renders an icon slot', () => { + const slot = element.shadowRoot!.querySelector('slot[name=icon]')!; + expect(slot).to.exist; + }); - it('renders a button', () => { - const slot = element.shadowRoot!.querySelector('button')!; - expect(slot).to.exist; - }); - it('renders a anchor tag when href is defined', () => { - element.setAttribute('href', 'https://www.umbraco.com'); - const slot = element.shadowRoot!.querySelector('button')!; - expect(slot).to.exist; - }); - }); + it('renders an actions slot', () => { + const slot = element.shadowRoot!.querySelector('slot[name=actions]')!; + expect(slot).to.exist; + }); - describe('expand', () => { - it('emits a show-children event when expand icon is clicked', async () => { - element.setAttribute('has-children', 'true'); - await elementUpdated(element); - const listener = oneEvent(element, 'show-children'); - const caretIconElement: HTMLElement | null = - element.shadowRoot!.querySelector('#caret-button'); - caretIconElement?.click(); - const event = await listener; - expect(event).to.exist; - expect(event.type).to.equal('show-children'); - expect(element.hasAttribute('show-children')).to.equal(true); + it('renders a button', () => { + const slot = element.shadowRoot!.querySelector('button')!; + expect(slot).to.exist; + }); + it('renders a anchor tag when href is defined', () => { + element.setAttribute('href', 'https://www.umbraco.com'); + const slot = element.shadowRoot!.querySelector('button')!; + expect(slot).to.exist; + }); }); - it('emits a hide-children event when collapse icon is clicked', async () => { - element.setAttribute('has-children', 'true'); - element.setAttribute('show-children', 'true'); - await elementUpdated(element); - const listener = oneEvent(element, 'hide-children'); - const caretIconElement: HTMLElement | null = - element.shadowRoot!.querySelector('#caret-button'); - caretIconElement?.click(); - const event = await listener; - expect(event).to.exist; - expect(event.type).to.equal('hide-children'); - expect(element.hasAttribute('show-children')).to.equal(false); + describe('expand', () => { + it('emits a show-children event when expand icon is clicked', async () => { + element.setAttribute('has-children', 'true'); + await elementUpdated(element); + const listener = oneEvent(element, 'show-children'); + const caretIconElement: HTMLElement | null = + element.shadowRoot!.querySelector('#caret-button'); + caretIconElement?.click(); + const event = await listener; + expect(event).to.exist; + expect(event.type).to.equal('show-children'); + expect(element.hasAttribute('show-children')).to.equal(true); + }); + + it('emits a hide-children event when collapse icon is clicked', async () => { + element.setAttribute('has-children', 'true'); + element.setAttribute('show-children', 'true'); + await elementUpdated(element); + const listener = oneEvent(element, 'hide-children'); + const caretIconElement: HTMLElement | null = + element.shadowRoot!.querySelector('#caret-button'); + caretIconElement?.click(); + const event = await listener; + expect(event).to.exist; + expect(event.type).to.equal('hide-children'); + expect(element.hasAttribute('show-children')).to.equal(false); + }); }); - }); - describe('selectable', () => { - let labelElement: HTMLElement | null; + describe('selectable', () => { + let labelElement: HTMLElement | null; - beforeEach(async () => { - labelElement = element.shadowRoot!.querySelector('#label-button'); - element.selectable = true; - }); + beforeEach(async () => { + labelElement = element.shadowRoot!.querySelector('#label-button'); + element.selectable = true; + }); - it('label element is defined', () => { - expect(labelElement).to.be.instanceOf(HTMLElement); - }); + it('label element is defined', () => { + expect(labelElement).to.be.instanceOf(HTMLElement); + }); - it('label is rendered as a button tag', async () => { - await elementUpdated(element); - expect(labelElement?.nodeName).to.be.equal('BUTTON'); - }); + it('label is rendered as a button tag', async () => { + await elementUpdated(element); + expect(labelElement?.nodeName).to.be.equal('BUTTON'); + }); - it('can be selected when selectable', async () => { - await elementUpdated(element); - await sendMouse({ - type: 'click', - position: [75, 30], - button: 'left', + it('can be selected when selectable', async () => { + await elementUpdated(element); + await sendMouse({ + type: 'click', + position: [75, 30], + button: 'left', + }); + expect(element.selected).to.be.true; }); - expect(element.selected).to.be.true; - }); - it('can not be selected when not selectable', async () => { - element.selectable = false; - await elementUpdated(element); - await sendMouse({ - type: 'click', - position: [75, 30], - button: 'left', + it('can not be selected when not selectable', async () => { + element.selectable = false; + await elementUpdated(element); + await sendMouse({ + type: 'click', + position: [75, 30], + button: 'left', + }); + expect(element.selected).to.be.false; }); - expect(element.selected).to.be.false; - }); - it('can not be selected when disabled', async () => { - element.disabled = true; - await elementUpdated(element); - await sendMouse({ - type: 'click', - position: [75, 30], - button: 'left', + it('can not be selected when disabled', async () => { + element.disabled = true; + await elementUpdated(element); + await sendMouse({ + type: 'click', + position: [75, 30], + button: 'left', + }); + expect(element.selected).to.be.false; }); - expect(element.selected).to.be.false; - }); - it('can expand', async () => { - element.setAttribute('has-children', 'true'); - await elementUpdated(element); - const listener = oneEvent(element, 'show-children'); - const caretIconElement: HTMLElement | null = - element.shadowRoot!.querySelector('#caret-button'); - caretIconElement?.click(); - const event = await listener; - expect(event).to.exist; - expect(event.type).to.equal('show-children'); - expect(element.hasAttribute('show-children')).to.equal(true); + it('can expand', async () => { + element.setAttribute('has-children', 'true'); + await elementUpdated(element); + const listener = oneEvent(element, 'show-children'); + const caretIconElement: HTMLElement | null = + element.shadowRoot!.querySelector('#caret-button'); + caretIconElement?.click(); + const event = await listener; + expect(event).to.exist; + expect(event.type).to.equal('show-children'); + expect(element.hasAttribute('show-children')).to.equal(true); + }); }); - }); - describe('selectable & selectOnly', () => { - let labelElement: HTMLElement | null; + describe('selectable & selectOnly', () => { + let labelElement: HTMLElement | null; - beforeEach(async () => { - labelElement = element.shadowRoot!.querySelector('#label-button'); - element.selectable = true; - element.selectOnly = true; - }); + beforeEach(async () => { + labelElement = element.shadowRoot!.querySelector('#label-button'); + element.selectable = true; + element.selectOnly = true; + }); - it('label element is defined', () => { - expect(labelElement).to.be.instanceOf(HTMLElement); - }); + it('label element is defined', () => { + expect(labelElement).to.be.instanceOf(HTMLElement); + }); - it('label is rendered as a button tag', async () => { - await elementUpdated(element); - expect(labelElement?.nodeName).to.be.equal('BUTTON'); - }); + it('label is rendered as a button tag', async () => { + await elementUpdated(element); + expect(labelElement?.nodeName).to.be.equal('BUTTON'); + }); - it('can be selected when selectable', async () => { - await elementUpdated(element); - await sendMouse({ - type: 'click', - position: [75, 30], - button: 'left', + it('can be selected when selectable', async () => { + await elementUpdated(element); + await sendMouse({ + type: 'click', + position: [75, 30], + button: 'left', + }); + expect(element.selected).to.be.true; }); - expect(element.selected).to.be.true; - }); - it('can not be selected when not selectable', async () => { - element.selectable = false; - await elementUpdated(element); - await sendMouse({ - type: 'click', - position: [75, 30], - button: 'left', + it('can not be selected when not selectable', async () => { + element.selectable = false; + await elementUpdated(element); + await sendMouse({ + type: 'click', + position: [75, 30], + button: 'left', + }); + expect(element.selected).to.be.false; }); - expect(element.selected).to.be.false; - }); - it('can not be selected when disabled', async () => { - element.disabled = true; - await elementUpdated(element); - await sendMouse({ - type: 'click', - position: [75, 30], - button: 'left', + it('can not be selected when disabled', async () => { + element.disabled = true; + await elementUpdated(element); + await sendMouse({ + type: 'click', + position: [75, 30], + button: 'left', + }); + expect(element.selected).to.be.false; }); - expect(element.selected).to.be.false; - }); - it('can expand', async () => { - element.setAttribute('has-children', 'true'); - await elementUpdated(element); - const listener = oneEvent(element, 'show-children'); - const caretIconElement: HTMLElement | null = - element.shadowRoot!.querySelector('#caret-button'); - caretIconElement?.click(); - const event = await listener; - expect(event).to.exist; - expect(event.type).to.equal('show-children'); - expect(element.hasAttribute('show-children')).to.equal(true); + it('can expand', async () => { + element.setAttribute('has-children', 'true'); + await elementUpdated(element); + const listener = oneEvent(element, 'show-children'); + const caretIconElement: HTMLElement | null = + element.shadowRoot!.querySelector('#caret-button'); + caretIconElement?.click(); + const event = await listener; + expect(event).to.exist; + expect(event.type).to.equal('show-children'); + expect(element.hasAttribute('show-children')).to.equal(true); + }); }); });