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
207 changes: 173 additions & 34 deletions packages/main/cypress/specs/AvatarGroup.cy.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -266,11 +266,13 @@ describe("AvatarGroup Rendering and Events", () => {

it("tests click event avatar group with type group is clicked", () => {
cy.mount(
<AvatarGroup type="Group">
<Avatar size="M" initials="M"></Avatar>
<Avatar size="M" initials="M"></Avatar>
<Avatar size="M" icon="home"></Avatar>
</AvatarGroup>
<div style={{ width: "60px" }}>
<AvatarGroup type="Group">
<Avatar size="M" initials="M"></Avatar>
<Avatar size="M" initials="M"></Avatar>
<Avatar size="M" icon="home"></Avatar>
</AvatarGroup>
</div>
);

cy.get("[ui5-avatar-group]")
Expand Down Expand Up @@ -330,19 +332,23 @@ describe("AvatarGroup Rendering and Events", () => {

it("tests if click event is firing only once", () => {
cy.mount(
<div style={{width: "400px"}}>
<AvatarGroup type="Individual">
<Avatar size="XL" interactive icon="home" initials="XL"></Avatar>
<Avatar size="XL" interactive initials="XL"></Avatar>
<Avatar size="XL" interactive icon="home"></Avatar>
<Avatar size="XL" interactive initials="XL"></Avatar>
</AvatarGroup>
<AvatarGroup type="Group">
<Avatar size="XL" interactive icon="home" initials="XL"></Avatar>
<Avatar size="XL" interactive initials="XL"></Avatar>
<Avatar size="XL" interactive icon="home"></Avatar>
<Avatar size="XL" interactive initials="XL"></Avatar>
</AvatarGroup>
<div>
<div style={{width: "300px"}}>
<AvatarGroup type="Individual">
<Avatar size="XL" interactive icon="home" initials="XL"></Avatar>
<Avatar size="XL" interactive initials="XL"></Avatar>
<Avatar size="XL" interactive icon="home"></Avatar>
<Avatar size="XL" interactive initials="XL"></Avatar>
</AvatarGroup>
</div>
<div style={{width: "150px"}}>
<AvatarGroup type="Group">
<Avatar size="XL" interactive icon="home" initials="XL"></Avatar>
<Avatar size="XL" interactive initials="XL"></Avatar>
<Avatar size="XL" interactive icon="home"></Avatar>
<Avatar size="XL" interactive initials="XL"></Avatar>
</AvatarGroup>
</div>
</div>
);

Expand Down Expand Up @@ -485,13 +491,30 @@ describe("AvatarGroup ARIA Attributes", () => {
});

describe("Type Group", () => {
it("role is correct", () => {
it("role is 'group' when no overflow", () => {
cy.mount(
<AvatarGroup type="Group">
<Avatar size="M" initials="M"></Avatar>
<Avatar size="M" initials="M"></Avatar>
<Avatar size="M" initials="M"></Avatar>
</AvatarGroup>
<div style={{ width: "600px" }}>
<AvatarGroup type="Group">
<Avatar size="M" initials="M"></Avatar>
<Avatar size="M" initials="M"></Avatar>
<Avatar size="M" initials="M"></Avatar>
</AvatarGroup>
</div>
);

cy.get("[ui5-avatar-group]")
.should("have.prop", "_role", "group");
});

it("role is 'button' when overflow is present", () => {
cy.mount(
<div style={{ width: "60px" }}>
<AvatarGroup type="Group">
<Avatar size="M" initials="M"></Avatar>
<Avatar size="M" initials="M"></Avatar>
<Avatar size="M" initials="M"></Avatar>
</AvatarGroup>
</div>
);

cy.get("[ui5-avatar-group]")
Expand Down Expand Up @@ -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(
<AvatarGroup type="Group">
<Avatar size="M" initials="M"></Avatar>
<Avatar size="M" initials="M"></Avatar>
<Avatar size="M" initials="M"></Avatar>
</AvatarGroup>
<div style={{ width: "600px" }}>
<AvatarGroup type="Group">
<Avatar size="M" initials="M"></Avatar>
<Avatar size="M" initials="M"></Avatar>
<Avatar size="M" initials="M"></Avatar>
</AvatarGroup>
</div>
);

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(
<div style={{ width: "60px" }}>
<AvatarGroup type="Group">
<Avatar size="M" initials="M"></Avatar>
<Avatar size="M" initials="M"></Avatar>
<Avatar size="M" initials="M"></Avatar>
</AvatarGroup>
</div>
);

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(
<div style={{ width: "600px" }}>
<AvatarGroup type="Group">
<Avatar size="M" initials="M"></Avatar>
<Avatar size="M" initials="M"></Avatar>
<Avatar size="M" initials="M"></Avatar>
</AvatarGroup>
</div>
);

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(
<div style={{ width: "600px" }}>
<AvatarGroup type="Group">
<Avatar size="M" initials="M"></Avatar>
<Avatar size="M" initials="M"></Avatar>
<Avatar size="M" initials="M"></Avatar>
</AvatarGroup>
</div>
);

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(
<div style={{ width: "600px" }}>
<AvatarGroup type="Group">
<Avatar size="M" initials="M"></Avatar>
<Avatar size="M" initials="M"></Avatar>
<Avatar size="M" initials="M"></Avatar>
</AvatarGroup>
</div>
);

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(
<div style={{ width: "600px" }}>
<AvatarGroup type="Group">
<Avatar size="M" initials="M"></Avatar>
<Avatar size="M" initials="M"></Avatar>
<Avatar size="M" initials="M"></Avatar>
</AvatarGroup>
</div>
);

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");
});
});
24 changes: 16 additions & 8 deletions packages/main/src/AvatarGroup.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}
Expand All @@ -330,7 +331,7 @@ class AvatarGroup extends UI5Element {
}

get _containerAriaHasPopup() {
return this._isGroup ? this._getAriaHasPopup() : undefined;
return this._isInteractiveGroup ? this._getAriaHasPopup() : undefined;
}

get _overflowButtonAccAttributes() {
Expand All @@ -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() {
Expand Down Expand Up @@ -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();
Expand All @@ -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();
}
Expand All @@ -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) {
Expand Down
Loading