Skip to content
Open
Show file tree
Hide file tree
Changes from 2 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
82 changes: 81 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,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(
<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");
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(
<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.be.undefined;
});
});

it("should return undefined for accessibilityInfo when avatar is interactive but disabled", () => {
cy.mount(
<Avatar
id="disabled-interactive-info"
initials="JD"
interactive
disabled
></Avatar>
);

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(
<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 +580,4 @@ describe("Avatar Rendering and Interaction", () => {
cy.get("@clickStub")
.should("have.been.calledOnce");
});
});
});
14 changes: 13 additions & 1 deletion 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 Down Expand Up @@ -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,
Copy link
Contributor

Choose a reason for hiding this comment

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

As mentioned in the other change, just like in UI5, the type should be translatable.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Hello @aborjinik ,
thank you for your comments. Changes are still in progress.

I wonder regarding the roles - should they also be translatable? Like in this case role could be "button" or "img". But "img" should be read out as "Image" (best case) and there is no such AriaRole? What should we do in this case?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Nevermind. I think I got it - table (and also customlistitem) will use only type, NOT ROLE, when they construct the string description of all acccessibilityInfo properties of a component.
In case component does not have specific "type" (like CheckBox has role=checkbox and that's it, "CheckBox" will be also returned as type but translatable). When component have different "type" than "role" (like in Avatar - it's role is Button or Img, but "type" can come from hasPopup /menu, listbox, etc/ - "type" will be the translatable value of "hasPopup". Or Select - "role" is "combobox", but more specific type is "listbox" -> listbox is returned as type, translatable).

description: this.accessibleNameText,
};
}

return undefined;
Copy link
Contributor

Choose a reason for hiding this comment

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

That means if the avatar is not interactive and placed within a table cell, it will be announced as "Empty". I'm not sure whether this is the expected behavior from an accessibility perspective.

}
}

Avatar.define();
Expand Down
Loading