From e9d0b48b90f5f0ed1a8b73b7bafdf4201664ce4a Mon Sep 17 00:00:00 2001 From: Iliana Bobeva Date: Mon, 3 Nov 2025 16:19:18 +0200 Subject: [PATCH 1/5] feat(ui5-avatar): implement accessibilityInfo getter --- packages/main/cypress/specs/Avatar.cy.tsx | 82 ++++++++++++++++++++++- packages/main/src/Avatar.ts | 14 +++- 2 files changed, 94 insertions(+), 2 deletions(-) diff --git a/packages/main/cypress/specs/Avatar.cy.tsx b/packages/main/cypress/specs/Avatar.cy.tsx index 1c956c632729..0875e08a69ee 100644 --- a/packages/main/cypress/specs/Avatar.cy.tsx +++ b/packages/main/cypress/specs/Avatar.cy.tsx @@ -21,6 +21,86 @@ describe("Accessibility", () => { .should("have.attr", "aria-label", expectedLabel); }); + it("should return accessibilityInfo object when avatar is interactive", () => { + const INITIALS = "JD"; + const hasPopup = "menu"; + const customLabel = "John Doe Avatar"; + + cy.mount( + + ); + + cy.get("#interactive-info").then($avatar => { + const avatar = $avatar[0] as any; + + // Check accessibilityInfo properties + expect(avatar.accessibilityInfo).to.exist; + expect(avatar.accessibilityInfo.role).to.equal("button"); + expect(avatar.accessibilityInfo.type).to.equal(hasPopup); + expect(avatar.accessibilityInfo.description).to.equal(customLabel); + }); + }); + + it("should return undefined for accessibilityInfo when avatar is not interactive", () => { + cy.mount( + + ); + + cy.get("#non-interactive-info").then($avatar => { + const avatar = $avatar[0] as any; + + // Check that accessibilityInfo is undefined + expect(avatar.accessibilityInfo).to.be.undefined; + }); + }); + + it("should return undefined for accessibilityInfo when avatar is interactive but disabled", () => { + cy.mount( + + ); + + cy.get("#disabled-interactive-info").then($avatar => { + const avatar = $avatar[0] as any; + + // Check that accessibilityInfo is undefined because disabled overrides interactive + expect(avatar.accessibilityInfo).to.be.undefined; + }); + }); + + it("should use default label for accessibilityInfo description when no custom label is provided", () => { + const INITIALS = "AB"; + + cy.mount( + + ); + + cy.get("#default-label-info").then($avatar => { + const avatar = $avatar[0] as any; + + // Check that accessibilityInfo uses the default label format that includes initials + expect(avatar.accessibilityInfo).to.exist; + expect(avatar.accessibilityInfo.description).to.equal(`Avatar ${INITIALS}`); + }); + }); + it("checks if accessible-name is correctly passed to the icon", () => { const ACCESSIBLE_NAME = "Supplier Icon"; const ICON_NAME = "supplier"; @@ -500,4 +580,4 @@ describe("Avatar Rendering and Interaction", () => { cy.get("@clickStub") .should("have.been.calledOnce"); }); -}); \ No newline at end of file +}); diff --git a/packages/main/src/Avatar.ts b/packages/main/src/Avatar.ts index 6d18d798d759..1c863ad2c6f2 100644 --- a/packages/main/src/Avatar.ts +++ b/packages/main/src/Avatar.ts @@ -5,7 +5,7 @@ import slot from "@ui5/webcomponents-base/dist/decorators/slot.js"; import event from "@ui5/webcomponents-base/dist/decorators/event-strict.js"; import i18n from "@ui5/webcomponents-base/dist/decorators/i18n.js"; import jsxRenderer from "@ui5/webcomponents-base/dist/renderer/JsxRenderer.js"; -import type { AccessibilityAttributes } from "@ui5/webcomponents-base/dist/types.js"; +import type { AccessibilityAttributes, AriaRole } from "@ui5/webcomponents-base/dist/types.js"; import type I18nBundle from "@ui5/webcomponents-base/dist/i18nBundle.js"; import type { ITabbable } from "@ui5/webcomponents-base/dist/delegate/ItemNavigation.js"; import ResizeHandler from "@ui5/webcomponents-base/dist/delegate/ResizeHandler.js"; @@ -493,6 +493,18 @@ class Avatar extends UI5Element implements ITabbable, IAvatarGroupItem { } this._imageLoadError = true; } + + get accessibilityInfo() { + if (this._interactive) { + return { + role: this._role as AriaRole, + type: this._ariaHasPopup, + description: this.accessibleNameText, + }; + } + + return undefined; + } } Avatar.define(); From 524e9f41b3b25602c15778304f46f4528975c76e Mon Sep 17 00:00:00 2001 From: Iliana Bobeva Date: Mon, 17 Nov 2025 16:42:46 +0200 Subject: [PATCH 2/5] chore: make type of avatar translatable and return the info object always --- packages/main/cypress/specs/Avatar.cy.tsx | 30 ++++----------- packages/main/src/Avatar.ts | 38 ++++++++++++++----- .../main/src/i18n/messagebundle.properties | 15 ++++++++ 3 files changed, 52 insertions(+), 31 deletions(-) diff --git a/packages/main/cypress/specs/Avatar.cy.tsx b/packages/main/cypress/specs/Avatar.cy.tsx index 0875e08a69ee..67b2c5ab5faf 100644 --- a/packages/main/cypress/specs/Avatar.cy.tsx +++ b/packages/main/cypress/specs/Avatar.cy.tsx @@ -21,7 +21,7 @@ describe("Accessibility", () => { .should("have.attr", "aria-label", expectedLabel); }); - it("should return accessibilityInfo object when avatar is interactive", () => { + it("should return correct accessibilityInfo object when avatar is interactive", () => { const INITIALS = "JD"; const hasPopup = "menu"; const customLabel = "John Doe Avatar"; @@ -42,12 +42,13 @@ describe("Accessibility", () => { // Check accessibilityInfo properties expect(avatar.accessibilityInfo).to.exist; expect(avatar.accessibilityInfo.role).to.equal("button"); - expect(avatar.accessibilityInfo.type).to.equal(hasPopup); + // Type contains the i18n text + expect(avatar.accessibilityInfo.type).to.equal("Menu"); expect(avatar.accessibilityInfo.description).to.equal(customLabel); }); }); - it("should return undefined for accessibilityInfo when avatar is not interactive", () => { + it("should return correct accessibilityInfo object when avatar is not interactive", () => { cy.mount( { const avatar = $avatar[0] as any; // Check that accessibilityInfo is undefined - expect(avatar.accessibilityInfo).to.be.undefined; - }); - }); - - it("should return undefined for accessibilityInfo when avatar is interactive but disabled", () => { - cy.mount( - - ); - - cy.get("#disabled-interactive-info").then($avatar => { - const avatar = $avatar[0] as any; - - // Check that accessibilityInfo is undefined because disabled overrides interactive - expect(avatar.accessibilityInfo).to.be.undefined; + expect(avatar.accessibilityInfo).to.exist; + expect(avatar.accessibilityInfo.role).to.equal("img"); + expect(avatar.accessibilityInfo.type).to.equal(""); + expect(avatar.accessibilityInfo.description).to.equal("Avatar JD"); }); }); diff --git a/packages/main/src/Avatar.ts b/packages/main/src/Avatar.ts index 1c863ad2c6f2..3596bb8c0c00 100644 --- a/packages/main/src/Avatar.ts +++ b/packages/main/src/Avatar.ts @@ -17,7 +17,13 @@ import type { IAvatarGroupItem } from "./AvatarGroup.js"; // Template import AvatarTemplate from "./AvatarTemplate.js"; -import { AVATAR_TOOLTIP } from "./generated/i18n/i18n-defaults.js"; +import { AVATAR_TOOLTIP, + ARIA_HASPOPUP_DIALOG, + ARIA_HASPOPUP_GRID, + ARIA_HASPOPUP_LISTBOX, + ARIA_HASPOPUP_MENU, + ARIA_HASPOPUP_TREE + } from "./generated/i18n/i18n-defaults.js"; // Styles import AvatarCss from "./generated/themes/Avatar.css.js"; @@ -494,16 +500,30 @@ class Avatar extends UI5Element implements ITabbable, IAvatarGroupItem { this._imageLoadError = true; } - get accessibilityInfo() { - if (this._interactive) { - return { - role: this._role as AriaRole, - type: this._ariaHasPopup, - description: this.accessibleNameText, - }; + _getAriaTypeDescription() { + switch (this._ariaHasPopup) { + case "dialog": + return Avatar.i18nBundle.getText(ARIA_HASPOPUP_DIALOG); + case "grid": + return Avatar.i18nBundle.getText(ARIA_HASPOPUP_GRID); + case "listbox": + return Avatar.i18nBundle.getText(ARIA_HASPOPUP_LISTBOX); + case "menu": + return Avatar.i18nBundle.getText(ARIA_HASPOPUP_MENU); + case "tree": + return Avatar.i18nBundle.getText(ARIA_HASPOPUP_TREE); + default: + return ""; } + } - return undefined; + get accessibilityInfo() { + return { + role: this._role as AriaRole, + type: this._getAriaTypeDescription(), + description: this.accessibleNameText, + disabled: this.disabled + }; } } diff --git a/packages/main/src/i18n/messagebundle.properties b/packages/main/src/i18n/messagebundle.properties index 4ec85cc590c2..21ea18b168e6 100644 --- a/packages/main/src/i18n/messagebundle.properties +++ b/packages/main/src/i18n/messagebundle.properties @@ -13,6 +13,21 @@ ARIA_ROLEDESCRIPTION_CARD_HEADER=Card Header #XBUT: Card Header aria-roledescription interactive text ARIA_ROLEDESCRIPTION_INTERACTIVE_CARD_HEADER=Interactive Card Header +#XACT: ARIA hasPopup description for dialog popup type +ARIA_HASPOPUP_DIALOG=Dialog + +#XACT: ARIA hasPopup description for grid popup type +ARIA_HASPOPUP_GRID=Grid + +#XACT: ARIA hasPopup description for listbox popup type +ARIA_HASPOPUP_LISTBOX=Listbox + +#XACT: ARIA hasPopup description for menu popup type +ARIA_HASPOPUP_MENU=Menu + +#XACT: ARIA hasPopup description for tree popup type +ARIA_HASPOPUP_TREE=Tree + #XACT: ARIA announcement for the Avatar default tooltip AVATAR_TOOLTIP=Avatar From 2696a7ff480052170f19d7453b47eee8b06eca6b Mon Sep 17 00:00:00 2001 From: Iliana Bobeva Date: Mon, 17 Nov 2025 16:54:38 +0200 Subject: [PATCH 3/5] chore: fix lint errors --- packages/main/src/Avatar.ts | 33 +++++++++++++++++---------------- 1 file changed, 17 insertions(+), 16 deletions(-) diff --git a/packages/main/src/Avatar.ts b/packages/main/src/Avatar.ts index 3596bb8c0c00..7cdb31701acd 100644 --- a/packages/main/src/Avatar.ts +++ b/packages/main/src/Avatar.ts @@ -17,13 +17,14 @@ import type { IAvatarGroupItem } from "./AvatarGroup.js"; // Template import AvatarTemplate from "./AvatarTemplate.js"; -import { AVATAR_TOOLTIP, +import { + AVATAR_TOOLTIP, ARIA_HASPOPUP_DIALOG, ARIA_HASPOPUP_GRID, ARIA_HASPOPUP_LISTBOX, ARIA_HASPOPUP_MENU, - ARIA_HASPOPUP_TREE - } from "./generated/i18n/i18n-defaults.js"; + ARIA_HASPOPUP_TREE, +} from "./generated/i18n/i18n-defaults.js"; // Styles import AvatarCss from "./generated/themes/Avatar.css.js"; @@ -502,18 +503,18 @@ class Avatar extends UI5Element implements ITabbable, IAvatarGroupItem { _getAriaTypeDescription() { switch (this._ariaHasPopup) { - case "dialog": - return Avatar.i18nBundle.getText(ARIA_HASPOPUP_DIALOG); - case "grid": - return Avatar.i18nBundle.getText(ARIA_HASPOPUP_GRID); - case "listbox": - return Avatar.i18nBundle.getText(ARIA_HASPOPUP_LISTBOX); - case "menu": - return Avatar.i18nBundle.getText(ARIA_HASPOPUP_MENU); - case "tree": - return Avatar.i18nBundle.getText(ARIA_HASPOPUP_TREE); - default: - return ""; + case "dialog": + return Avatar.i18nBundle.getText(ARIA_HASPOPUP_DIALOG); + case "grid": + return Avatar.i18nBundle.getText(ARIA_HASPOPUP_GRID); + case "listbox": + return Avatar.i18nBundle.getText(ARIA_HASPOPUP_LISTBOX); + case "menu": + return Avatar.i18nBundle.getText(ARIA_HASPOPUP_MENU); + case "tree": + return Avatar.i18nBundle.getText(ARIA_HASPOPUP_TREE); + default: + return ""; } } @@ -522,7 +523,7 @@ class Avatar extends UI5Element implements ITabbable, IAvatarGroupItem { role: this._role as AriaRole, type: this._getAriaTypeDescription(), description: this.accessibleNameText, - disabled: this.disabled + disabled: this.disabled, }; } } From 1d7da4188f6d009710ecfcc9cf97ed8081e0db19 Mon Sep 17 00:00:00 2001 From: Iliana Bobeva Date: Mon, 24 Nov 2025 12:21:10 +0200 Subject: [PATCH 4/5] correct type of avatar is returned --- packages/main/cypress/specs/Avatar.cy.tsx | 5 ++-- packages/main/src/Avatar.ts | 26 +++---------------- .../main/src/i18n/messagebundle.properties | 21 +++++---------- 3 files changed, 11 insertions(+), 41 deletions(-) diff --git a/packages/main/cypress/specs/Avatar.cy.tsx b/packages/main/cypress/specs/Avatar.cy.tsx index 67b2c5ab5faf..5888ebddbddd 100644 --- a/packages/main/cypress/specs/Avatar.cy.tsx +++ b/packages/main/cypress/specs/Avatar.cy.tsx @@ -42,8 +42,7 @@ describe("Accessibility", () => { // Check accessibilityInfo properties expect(avatar.accessibilityInfo).to.exist; expect(avatar.accessibilityInfo.role).to.equal("button"); - // Type contains the i18n text - expect(avatar.accessibilityInfo.type).to.equal("Menu"); + expect(avatar.accessibilityInfo.type).to.equal("Button"); expect(avatar.accessibilityInfo.description).to.equal(customLabel); }); }); @@ -62,7 +61,7 @@ describe("Accessibility", () => { // Check that accessibilityInfo is undefined expect(avatar.accessibilityInfo).to.exist; expect(avatar.accessibilityInfo.role).to.equal("img"); - expect(avatar.accessibilityInfo.type).to.equal(""); + expect(avatar.accessibilityInfo.type).to.equal("Image"); expect(avatar.accessibilityInfo.description).to.equal("Avatar JD"); }); }); diff --git a/packages/main/src/Avatar.ts b/packages/main/src/Avatar.ts index 7cdb31701acd..b6700d0a9a88 100644 --- a/packages/main/src/Avatar.ts +++ b/packages/main/src/Avatar.ts @@ -19,11 +19,8 @@ import AvatarTemplate from "./AvatarTemplate.js"; import { AVATAR_TOOLTIP, - ARIA_HASPOPUP_DIALOG, - ARIA_HASPOPUP_GRID, - ARIA_HASPOPUP_LISTBOX, - ARIA_HASPOPUP_MENU, - ARIA_HASPOPUP_TREE, + AVATAR_TYPE_BUTTON, + AVATAR_TYPE_IMAGE, } from "./generated/i18n/i18n-defaults.js"; // Styles @@ -501,27 +498,10 @@ class Avatar extends UI5Element implements ITabbable, IAvatarGroupItem { this._imageLoadError = true; } - _getAriaTypeDescription() { - switch (this._ariaHasPopup) { - case "dialog": - return Avatar.i18nBundle.getText(ARIA_HASPOPUP_DIALOG); - case "grid": - return Avatar.i18nBundle.getText(ARIA_HASPOPUP_GRID); - case "listbox": - return Avatar.i18nBundle.getText(ARIA_HASPOPUP_LISTBOX); - case "menu": - return Avatar.i18nBundle.getText(ARIA_HASPOPUP_MENU); - case "tree": - return Avatar.i18nBundle.getText(ARIA_HASPOPUP_TREE); - default: - return ""; - } - } - get accessibilityInfo() { return { role: this._role as AriaRole, - type: this._getAriaTypeDescription(), + type: this.interactive ? Avatar.i18nBundle.getText(AVATAR_TYPE_BUTTON) : Avatar.i18nBundle.getText(AVATAR_TYPE_IMAGE), description: this.accessibleNameText, disabled: this.disabled, }; diff --git a/packages/main/src/i18n/messagebundle.properties b/packages/main/src/i18n/messagebundle.properties index 21ea18b168e6..72216cb1f5ac 100644 --- a/packages/main/src/i18n/messagebundle.properties +++ b/packages/main/src/i18n/messagebundle.properties @@ -13,24 +13,15 @@ ARIA_ROLEDESCRIPTION_CARD_HEADER=Card Header #XBUT: Card Header aria-roledescription interactive text ARIA_ROLEDESCRIPTION_INTERACTIVE_CARD_HEADER=Interactive Card Header -#XACT: ARIA hasPopup description for dialog popup type -ARIA_HASPOPUP_DIALOG=Dialog - -#XACT: ARIA hasPopup description for grid popup type -ARIA_HASPOPUP_GRID=Grid - -#XACT: ARIA hasPopup description for listbox popup type -ARIA_HASPOPUP_LISTBOX=Listbox - -#XACT: ARIA hasPopup description for menu popup type -ARIA_HASPOPUP_MENU=Menu - -#XACT: ARIA hasPopup description for tree popup type -ARIA_HASPOPUP_TREE=Tree - #XACT: ARIA announcement for the Avatar default tooltip AVATAR_TOOLTIP=Avatar +#XACT: ARIA type description for interactive Avatar (when the Avatar is a button) +AVATAR_TYPE_BUTTON=Button + +#XACT: ARIA type description for non-interactive Avatar (when the Avatar is an image) +AVATAR_TYPE_IMAGE=Image + #XACT: ARIA announcement for the Avatar default tooltip AVATAR_GROUP_DISPLAYED_HIDDEN_LABEL={0} displayed, {1} hidden. From 5958f3894ec72605b60968f65513b5d32bd03fe5 Mon Sep 17 00:00:00 2001 From: Iliana Bobeva Date: Mon, 24 Nov 2025 12:25:40 +0200 Subject: [PATCH 5/5] fix lint error --- packages/main/src/Avatar.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/main/src/Avatar.ts b/packages/main/src/Avatar.ts index b6700d0a9a88..9a80c0d1e93f 100644 --- a/packages/main/src/Avatar.ts +++ b/packages/main/src/Avatar.ts @@ -501,7 +501,7 @@ class Avatar extends UI5Element implements ITabbable, IAvatarGroupItem { get accessibilityInfo() { return { role: this._role as AriaRole, - type: this.interactive ? Avatar.i18nBundle.getText(AVATAR_TYPE_BUTTON) : Avatar.i18nBundle.getText(AVATAR_TYPE_IMAGE), + type: this.interactive ? Avatar.i18nBundle.getText(AVATAR_TYPE_BUTTON) : Avatar.i18nBundle.getText(AVATAR_TYPE_IMAGE), description: this.accessibleNameText, disabled: this.disabled, };