Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
68 changes: 67 additions & 1 deletion packages/main/cypress/specs/Avatar.cy.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,72 @@ describe("Accessibility", () => {
.should("have.attr", "aria-label", expectedLabel);
});

it("should return correct accessibilityInfo object when avatar is interactive", () => {
const INITIALS = "JD";
const hasPopup = "menu";
const customLabel = "John Doe Avatar";

cy.mount(
<Avatar
id="interactive-info"
initials={INITIALS}
interactive
accessibleName={customLabel}
accessibilityAttributes={{hasPopup}}
></Avatar>
);

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");
// Type contains the i18n text
expect(avatar.accessibilityInfo.type).to.equal("Menu");
expect(avatar.accessibilityInfo.description).to.equal(customLabel);
});
});

it("should return correct accessibilityInfo object when avatar is not interactive", () => {
cy.mount(
<Avatar
id="non-interactive-info"
initials="JD"
></Avatar>
);

cy.get("#non-interactive-info").then($avatar => {
const avatar = $avatar[0] as any;

// 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.description).to.equal("Avatar JD");
});
});

it("should use default label for accessibilityInfo description when no custom label is provided", () => {
const INITIALS = "AB";

cy.mount(
<Avatar
id="default-label-info"
initials={INITIALS}
interactive
></Avatar>
);

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";
Expand Down Expand Up @@ -500,4 +566,4 @@ describe("Avatar Rendering and Interaction", () => {
cy.get("@clickStub")
.should("have.been.calledOnce");
});
});
});
37 changes: 35 additions & 2 deletions packages/main/src/Avatar.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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";
Expand All @@ -17,7 +17,14 @@ 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";
Expand Down Expand Up @@ -493,6 +500,32 @@ 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(),
Copy link
Contributor

@aborjinik aborjinik Nov 21, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

An avatar should not be announced as a dialog, grid, or similar. The type should be set according to the component's actual role.

description: this.accessibleNameText,
disabled: this.disabled,
};
}
}

Avatar.define();
Expand Down
15 changes: 15 additions & 0 deletions packages/main/src/i18n/messagebundle.properties
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
Loading