mirror of
https://github.com/Novaloop-AG/healthchat-web.git
synced 2026-11-06 14:38:21 +00:00
Implement the member list with virtuoso (#29869)
* implement basic scrolling and keyboard navigation * Update focus style and improve keyboard navigation * lint * Use avatar tootltip for the title rather than the whole button It's more performant and feels less glitchy than the button tooltip moving around when you scroll. * lint * Add tooltip for invite buttons active state As we have for other icon based buttons in the right panel/app * Fix location of scrollToIndex and add useCallback * Improve voiceover experience - As well as stylng cells, set the tabIndex(roving) - Natively focus the div with .focus() so screen reader actually moves over the cells - improve labels and roles * Fix jest tests * Add aria index/counts and remove repeating "Open" string in label * update snapshot * Add the rest of the keyboard navigation and handle the case when the list looses focus. * lint and update snapshot * lint * Only focus first/lastFocsed cell if focus.currentTarget is the overall list. So it isn't erroneously called during onClick of an item. * Put back overscan and fix formatting * Extract ListView out of MemberList * lint and fix e2e test * Update screenshot It looks like it is slightly better center aligned in the new list, as if maybe it was 1 px to high with the old one. * Fix default overscan value and add ListView tests * Just leave the avatar as it was * We removed the tooltip that showed power level. Removing string. * Use key rather than index to track focus. * Remove overscan, fix typos, fix scrollToItem logic * Use listbox role for member list and correct position/count values to account for the separator * Fix inadvertant scrolling of the timeline when using pageUp/pageDown * Always set the roving tab index regardless of whether we are actually focused. Fixes the issue of not being able to shift+t * Add aria-hidden to items within the option to avoid the SR calling it a group. Also * Make sure there is a roving tab set if the last one has been removed from the list. * Update snapshot
This commit is contained in:
@@ -35,7 +35,7 @@ describe("MemberTileView", () => {
|
||||
});
|
||||
|
||||
it("should not display an E2EIcon when the e2E status = normal", () => {
|
||||
const { container } = render(<RoomMemberTileView member={member} />);
|
||||
const { container } = render(<RoomMemberTileView member={member} index={0} memberCount={1} />);
|
||||
const e2eIcon = container.querySelector(".mx_E2EIconView");
|
||||
expect(e2eIcon).toBeNull();
|
||||
expect(container).toMatchSnapshot();
|
||||
@@ -47,7 +47,7 @@ describe("MemberTileView", () => {
|
||||
wasCrossSigningVerified: jest.fn().mockReturnValue(true),
|
||||
} as unknown as UserVerificationStatus);
|
||||
|
||||
const { container } = render(<RoomMemberTileView member={member} />);
|
||||
const { container } = render(<RoomMemberTileView member={member} index={0} memberCount={1} />);
|
||||
await waitFor(async () => {
|
||||
await userEvent.hover(container.querySelector(".mx_E2EIcon")!);
|
||||
expect(screen.getByText("This user has not verified all of their sessions.")).toBeInTheDocument();
|
||||
@@ -68,7 +68,7 @@ describe("MemberTileView", () => {
|
||||
crossSigningVerified: true,
|
||||
} as DeviceVerificationStatus);
|
||||
|
||||
const { container } = render(<RoomMemberTileView member={member} />);
|
||||
const { container } = render(<RoomMemberTileView member={member} index={0} memberCount={1} />);
|
||||
|
||||
await waitFor(async () => {
|
||||
await userEvent.hover(container.querySelector(".mx_E2EIcon")!);
|
||||
@@ -81,15 +81,15 @@ describe("MemberTileView", () => {
|
||||
|
||||
it("renders user labels correctly", async () => {
|
||||
member.powerLevel = 50;
|
||||
const { container: container1 } = render(<RoomMemberTileView member={member} />);
|
||||
const { container: container1 } = render(<RoomMemberTileView member={member} index={0} memberCount={1} />);
|
||||
expect(container1).toHaveTextContent("Moderator");
|
||||
|
||||
member.powerLevel = 100;
|
||||
const { container: container2 } = render(<RoomMemberTileView member={member} />);
|
||||
const { container: container2 } = render(<RoomMemberTileView member={member} index={0} memberCount={1} />);
|
||||
expect(container2).toHaveTextContent("Admin");
|
||||
|
||||
member.isInvite = true;
|
||||
const { container: container3 } = render(<RoomMemberTileView member={member} />);
|
||||
const { container: container3 } = render(<RoomMemberTileView member={member} index={0} memberCount={1} />);
|
||||
expect(container3).toHaveTextContent("Invited");
|
||||
});
|
||||
});
|
||||
@@ -109,7 +109,9 @@ describe("MemberTileView", () => {
|
||||
|
||||
it("renders ThreePidInvite correctly", async () => {
|
||||
const [{ threePidInvite }] = getPending3PidInvites(room);
|
||||
const { container } = render(<ThreePidInviteTileView threePidInvite={threePidInvite!} />);
|
||||
const { container } = render(
|
||||
<ThreePidInviteTileView threePidInvite={threePidInvite!} memberIndex={0} memberCount={1} />,
|
||||
);
|
||||
expect(container).toMatchSnapshot();
|
||||
});
|
||||
});
|
||||
|
||||
+33
-13
@@ -4,12 +4,15 @@ exports[`MemberTileView RoomMemberTileView should display an verified E2EIcon wh
|
||||
<div>
|
||||
<div>
|
||||
<div
|
||||
aria-label="@userId:matrix.org (power 0)"
|
||||
aria-label="@userId:matrix.org"
|
||||
aria-posinset="1"
|
||||
aria-setsize="1"
|
||||
class="mx_AccessibleButton mx_MemberTileView"
|
||||
role="button"
|
||||
tabindex="-1"
|
||||
role="option"
|
||||
tabindex="0"
|
||||
>
|
||||
<div
|
||||
aria-hidden="true"
|
||||
class="mx_MemberTileView_left"
|
||||
>
|
||||
<div
|
||||
@@ -33,6 +36,7 @@ exports[`MemberTileView RoomMemberTileView should display an verified E2EIcon wh
|
||||
>
|
||||
<div
|
||||
class="mx_DisambiguatedProfile"
|
||||
title="@userId:matrix.org (@userId:matrix.org)"
|
||||
>
|
||||
<span
|
||||
class=""
|
||||
@@ -44,10 +48,11 @@ exports[`MemberTileView RoomMemberTileView should display an verified E2EIcon wh
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
aria-hidden="true"
|
||||
class="mx_MemberTileView_right"
|
||||
>
|
||||
<div
|
||||
aria-labelledby="«ri»"
|
||||
aria-labelledby="«r6»"
|
||||
class="mx_E2EIconView"
|
||||
>
|
||||
<svg
|
||||
@@ -73,12 +78,15 @@ exports[`MemberTileView RoomMemberTileView should display an warning E2EIcon whe
|
||||
<div>
|
||||
<div>
|
||||
<div
|
||||
aria-label="@userId:matrix.org (power 0)"
|
||||
aria-label="@userId:matrix.org"
|
||||
aria-posinset="1"
|
||||
aria-setsize="1"
|
||||
class="mx_AccessibleButton mx_MemberTileView"
|
||||
role="button"
|
||||
tabindex="-1"
|
||||
role="option"
|
||||
tabindex="0"
|
||||
>
|
||||
<div
|
||||
aria-hidden="true"
|
||||
class="mx_MemberTileView_left"
|
||||
>
|
||||
<div
|
||||
@@ -102,6 +110,7 @@ exports[`MemberTileView RoomMemberTileView should display an warning E2EIcon whe
|
||||
>
|
||||
<div
|
||||
class="mx_DisambiguatedProfile"
|
||||
title="@userId:matrix.org (@userId:matrix.org)"
|
||||
>
|
||||
<span
|
||||
class=""
|
||||
@@ -113,10 +122,11 @@ exports[`MemberTileView RoomMemberTileView should display an warning E2EIcon whe
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
aria-hidden="true"
|
||||
class="mx_MemberTileView_right"
|
||||
>
|
||||
<div
|
||||
aria-labelledby="«r8»"
|
||||
aria-labelledby="«r0»"
|
||||
class="mx_E2EIconView"
|
||||
>
|
||||
<svg
|
||||
@@ -142,12 +152,15 @@ exports[`MemberTileView RoomMemberTileView should not display an E2EIcon when th
|
||||
<div>
|
||||
<div>
|
||||
<div
|
||||
aria-label="@userId:matrix.org (power 0)"
|
||||
aria-label="@userId:matrix.org"
|
||||
aria-posinset="1"
|
||||
aria-setsize="1"
|
||||
class="mx_AccessibleButton mx_MemberTileView"
|
||||
role="button"
|
||||
tabindex="-1"
|
||||
role="option"
|
||||
tabindex="0"
|
||||
>
|
||||
<div
|
||||
aria-hidden="true"
|
||||
class="mx_MemberTileView_left"
|
||||
>
|
||||
<div
|
||||
@@ -171,6 +184,7 @@ exports[`MemberTileView RoomMemberTileView should not display an E2EIcon when th
|
||||
>
|
||||
<div
|
||||
class="mx_DisambiguatedProfile"
|
||||
title="@userId:matrix.org (@userId:matrix.org)"
|
||||
>
|
||||
<span
|
||||
class=""
|
||||
@@ -182,6 +196,7 @@ exports[`MemberTileView RoomMemberTileView should not display an E2EIcon when th
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
aria-hidden="true"
|
||||
class="mx_MemberTileView_right"
|
||||
/>
|
||||
</div>
|
||||
@@ -193,11 +208,15 @@ exports[`MemberTileView ThreePidInviteTileView renders ThreePidInvite correctly
|
||||
<div>
|
||||
<div>
|
||||
<div
|
||||
aria-label="Foobar"
|
||||
aria-posinset="1"
|
||||
aria-setsize="1"
|
||||
class="mx_AccessibleButton mx_MemberTileView"
|
||||
role="button"
|
||||
tabindex="-1"
|
||||
role="option"
|
||||
tabindex="0"
|
||||
>
|
||||
<div
|
||||
aria-hidden="true"
|
||||
class="mx_MemberTileView_left"
|
||||
>
|
||||
<div
|
||||
@@ -223,6 +242,7 @@ exports[`MemberTileView ThreePidInviteTileView renders ThreePidInvite correctly
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
aria-hidden="true"
|
||||
class="mx_MemberTileView_right"
|
||||
>
|
||||
<div
|
||||
|
||||
@@ -9,6 +9,7 @@ Please see LICENSE files in the repository root for full details.
|
||||
|
||||
import React, { act } from "react";
|
||||
import { render, type RenderResult, waitFor } from "jest-matrix-react";
|
||||
import { VirtuosoMockContext } from "react-virtuoso";
|
||||
import {
|
||||
Room,
|
||||
type MatrixClient,
|
||||
@@ -121,6 +122,13 @@ export async function renderMemberList(
|
||||
<MemberListView roomId={memberListRoom.roomId} onClose={() => {}} />
|
||||
</SDKContext.Provider>
|
||||
</MatrixClientContext.Provider>,
|
||||
{
|
||||
wrapper: ({ children }) => (
|
||||
<VirtuosoMockContext.Provider value={{ viewportHeight: 600, itemHeight: 56 }}>
|
||||
{children}
|
||||
</VirtuosoMockContext.Provider>
|
||||
),
|
||||
},
|
||||
);
|
||||
await waitFor(async () => {
|
||||
expect(root.container.querySelectorAll(".mx_MemberTileView")).toHaveLength(usersPerLevel * 3);
|
||||
|
||||
Reference in New Issue
Block a user