From bc7a023459c2b18d90120651d8b096155745c6f1 Mon Sep 17 00:00:00 2001 From: Nikola Anachkov Date: Thu, 18 Jun 2026 11:38:49 +0300 Subject: [PATCH] fix(ui5-avatar-group): remove role=button when no overflow and no hasPopup set --- .../main/cypress/specs/AvatarGroup.cy.tsx | 207 +++++++++++++++--- packages/main/src/AvatarGroup.ts | 24 +- 2 files changed, 189 insertions(+), 42 deletions(-) diff --git a/packages/main/cypress/specs/AvatarGroup.cy.tsx b/packages/main/cypress/specs/AvatarGroup.cy.tsx index 6c83ebb3637d..82f7140ee809 100644 --- a/packages/main/cypress/specs/AvatarGroup.cy.tsx +++ b/packages/main/cypress/specs/AvatarGroup.cy.tsx @@ -266,11 +266,13 @@ describe("AvatarGroup Rendering and Events", () => { it("tests click event avatar group with type group is clicked", () => { cy.mount( - - - - - +
+ + + + + +
); cy.get("[ui5-avatar-group]") @@ -330,19 +332,23 @@ describe("AvatarGroup Rendering and Events", () => { it("tests if click event is firing only once", () => { cy.mount( -
- - - - - - - - - - - - +
+
+ + + + + + +
+
+ + + + + + +
); @@ -485,13 +491,30 @@ describe("AvatarGroup ARIA Attributes", () => { }); describe("Type Group", () => { - it("role is correct", () => { + it("role is 'group' when no overflow", () => { cy.mount( - - - - - +
+ + + + + +
+ ); + + cy.get("[ui5-avatar-group]") + .should("have.prop", "_role", "group"); + }); + + it("role is 'button' when overflow is present", () => { + cy.mount( +
+ + + + + +
); cy.get("[ui5-avatar-group]") @@ -523,27 +546,143 @@ describe("AvatarGroup ARIA Attributes", () => { }); }); - it("aria-label is correct", () => { + it("aria-label has no 'Activate' suffix when no overflow (static)", () => { cy.mount( - - - - - +
+ + + + + +
); cy.get("[ui5-avatar-group]") .then(($avatarGroup) => { const avatarGroup = $avatarGroup[0] as AvatarGroup; const ariaLabel = avatarGroup._ariaLabelText; - const overflowButtonLabel = avatarGroup._overflowButtonAriaLabelText; expect(ariaLabel).to.include("Conjoined avatars"); - expect(ariaLabel).to.include("Activate for complete list"); + expect(ariaLabel).to.not.include("Activate for complete list"); expect(ariaLabel).to.not.include("Press ARROW keys"); - - expect(overflowButtonLabel).to.be.undefined; + expect(avatarGroup._overflowButtonAriaLabelText).to.be.undefined; }); }); + + it("aria-label includes 'Activate' suffix when overflow is present", () => { + cy.mount( +
+ + + + + +
+ ); + + cy.get("[ui5-avatar-group]") + .then(($avatarGroup) => { + const avatarGroup = $avatarGroup[0] as AvatarGroup; + const ariaLabel = avatarGroup._ariaLabelText; + + expect(ariaLabel).to.include("Conjoined avatars"); + expect(ariaLabel).to.include("Activate for complete list"); + }); + }); + }); +}); + +describe("AvatarGroup - role and interactivity based on overflow state", () => { + it("static Group (no overflow) has tabindex=0 so it remains keyboard-discoverable", () => { + cy.mount( +
+ + + + + +
+ ); + + cy.get("[ui5-avatar-group]") + .shadow() + .find(".ui5-avatar-group-items") + .should("have.attr", "tabindex", "0"); + }); + + it("static Group (no overflow) does not fire ui5-click on Space", () => { + cy.mount( +
+ + + + + +
+ ); + + cy.get("[ui5-avatar-group]") + .then(($avatarGroup) => { + $avatarGroup[0].addEventListener("ui5-click", cy.stub().as("clickStub")); + }); + + cy.get("[ui5-avatar-group]").realPress("Space"); + + cy.get("@clickStub").should("not.have.been.called"); + }); + + it("static Group (no overflow) does not fire ui5-click on Enter", () => { + cy.mount( +
+ + + + + +
+ ); + + cy.get("[ui5-avatar-group]") + .then(($avatarGroup) => { + $avatarGroup[0].addEventListener("ui5-click", cy.stub().as("clickStub")); + }); + + cy.get("[ui5-avatar-group]").realPress("Enter"); + + cy.get("@clickStub").should("not.have.been.called"); + }); + + it("static Group with hasPopup set keeps role='button' and fires ui5-click", () => { + cy.mount( +
+ + + + + +
+ ); + + cy.get("[ui5-avatar-group]").then(($avatarGroup) => { + ($avatarGroup[0] as AvatarGroup).accessibilityAttributes = { hasPopup: "dialog" }; + }); + + cy.get("[ui5-avatar-group]") + .then(($avatarGroup) => { + expect(($avatarGroup[0] as AvatarGroup)._role).to.equal("button"); + }); + + cy.get("[ui5-avatar-group]") + .then(($avatarGroup) => { + $avatarGroup[0].addEventListener("ui5-click", cy.stub().as("clickStub")); + }); + + cy.get("[ui5-avatar-group]") + .shadow() + .find(".ui5-avatar-group-items") + .focus() + .realPress("Space"); + + cy.get("@clickStub").should("have.been.calledOnce"); }); }); \ No newline at end of file diff --git a/packages/main/src/AvatarGroup.ts b/packages/main/src/AvatarGroup.ts index 7804a6d60844..e812a17d4f45 100644 --- a/packages/main/src/AvatarGroup.ts +++ b/packages/main/src/AvatarGroup.ts @@ -314,13 +314,14 @@ class AvatarGroup extends UI5Element { // add displayed-hidden avatars label text += ` ${AvatarGroup.i18nBundle.getText(AVATAR_GROUP_DISPLAYED_HIDDEN_LABEL, this._itemsCount - hiddenItemsCount, hiddenItemsCount)}`; - if (this._isGroup) { + if (this._isInteractiveGroup) { // the container role is "button", add the message for complete list activation text += ` ${AvatarGroup.i18nBundle.getText(AVATAR_GROUP_SHOW_COMPLETE_LIST_LABEL)}`; - } else { - // the container role is "group", add the "how to navigate" message + } else if (!this._isGroup) { + // Individual: add the "how to navigate" message text += ` ${AvatarGroup.i18nBundle.getText(AVATAR_GROUP_MOVE)}`; } + // Static Group: no action suffix — just the count is announced return text; } @@ -330,7 +331,7 @@ class AvatarGroup extends UI5Element { } get _containerAriaHasPopup() { - return this._isGroup ? this._getAriaHasPopup() : undefined; + return this._isInteractiveGroup ? this._getAriaHasPopup() : undefined; } get _overflowButtonAccAttributes() { @@ -339,8 +340,15 @@ class AvatarGroup extends UI5Element { }; } + get _isInteractiveGroup() { + return this._isGroup && (this._hiddenItems > 0 || this._getAriaHasPopup() !== undefined); + } + get _role() { - return this._isGroup ? "button" : "group"; + if (!this._isGroup) { + return "group"; + } + return this._isInteractiveGroup ? "button" : "group"; } get _hiddenStartIndex() { @@ -432,7 +440,7 @@ class AvatarGroup extends UI5Element { _onkeydown(e: KeyboardEvent) { if (this._isGroup) { - if (isEnter(e)) { + if (isEnter(e) && this._isInteractiveGroup) { this._fireGroupEvent(e.target as HTMLElement); } else if (isSpace(e)) { e.preventDefault(); @@ -441,7 +449,7 @@ class AvatarGroup extends UI5Element { } _onkeyup(e: KeyboardEvent) { - if (!e.shiftKey && isSpace(e) && this._isGroup) { + if (!e.shiftKey && isSpace(e) && this._isInteractiveGroup) { this._fireGroupEvent(e.target as HTMLElement); e.preventDefault(); } @@ -458,7 +466,7 @@ class AvatarGroup extends UI5Element { _onClick(e: MouseEvent) { e.stopPropagation(); - this._isGroup && this._fireGroupEvent(e.target as HTMLElement); + this._isInteractiveGroup && this._fireGroupEvent(e.target as HTMLElement); } onAvatarClick(e: MouseEvent) {