From fbe7dbd646ec248852fd29b6957bcb9c19d69c39 Mon Sep 17 00:00:00 2001 From: rizzler Date: Mon, 3 Aug 2026 15:48:29 +0530 Subject: [PATCH] fix member list scroll bug where invited 3PID users replace joined members (#34427) * Update MemberListView.tsx * Update MemberListViewModel.ts * simplify 3PID invite keys to getStateKey and added regression test * Use state_key for 3PID invite keys and set up test state before initial render * Format common.tsx with oxfmt --- .../viewmodels/memberlist/MemberListViewModel.ts | 2 +- .../views/rooms/MemberList/MemberListView.tsx | 2 +- .../rooms/memberlist/MemberListView-test.ts | 16 +++++++++++++++- .../components/views/rooms/memberlist/common.tsx | 9 +++++++-- 4 files changed, 24 insertions(+), 5 deletions(-) diff --git a/apps/web/src/components/viewmodels/memberlist/MemberListViewModel.ts b/apps/web/src/components/viewmodels/memberlist/MemberListViewModel.ts index 41233a4d6f..1c14cc712a 100644 --- a/apps/web/src/components/viewmodels/memberlist/MemberListViewModel.ts +++ b/apps/web/src/components/viewmodels/memberlist/MemberListViewModel.ts @@ -167,7 +167,7 @@ export function useMemberListViewModel(roomId: string): MemberListViewState { // Finally add the third party invites for (const invited of threePidInvited) { - const key = invited.threePidInvite!.event.getContent().display_name; + const key = `threePidInvite-${invited.threePidInvite!.event.getStateKey()}`; newMemberMap.set(key, invited); } diff --git a/apps/web/src/components/views/rooms/MemberList/MemberListView.tsx b/apps/web/src/components/views/rooms/MemberList/MemberListView.tsx index 46a9cd1397..ae7c40d655 100644 --- a/apps/web/src/components/views/rooms/MemberList/MemberListView.tsx +++ b/apps/web/src/components/views/rooms/MemberList/MemberListView.tsx @@ -45,7 +45,7 @@ const MemberListView: React.FC = (props: IProps) => { } else if (item.member) { return `member-${item.member.userId}`; } else { - return `threePidInvite-${item.threePidInvite.event.getContent().public_key}`; + return `threePidInvite-${item.threePidInvite.event.getStateKey()}`; } }, []); diff --git a/apps/web/test/unit-tests/components/views/rooms/memberlist/MemberListView-test.ts b/apps/web/test/unit-tests/components/views/rooms/memberlist/MemberListView-test.ts index bf4f0326d7..1da454ff89 100644 --- a/apps/web/test/unit-tests/components/views/rooms/memberlist/MemberListView-test.ts +++ b/apps/web/test/unit-tests/components/views/rooms/memberlist/MemberListView-test.ts @@ -11,7 +11,7 @@ import { act } from "react"; import { waitFor, fireEvent } from "jest-matrix-react"; import { type Room, type RoomMember, MatrixEvent } from "matrix-js-sdk/src/matrix"; -import { filterConsole } from "../../../../../test-utils"; +import { filterConsole, mkThirdPartyInviteEvent } from "../../../../../test-utils"; import { type Rendered, renderMemberList } from "./common"; jest.mock("../../../../../../src/customisations/helpers/UIComponents", () => ({ @@ -262,4 +262,18 @@ describe("MemberListView and MemberlistHeaderView", () => { }); }); }); + + describe("3PID invites", () => { + it("does not collapse invites with duplicate display names", async () => { + const threePidEvents = [ + mkThirdPartyInviteEvent("@alice:localhost", "user@example.com", "!room:localhost"), + mkThirdPartyInviteEvent("@alice:localhost", "user@example.com", "!room:localhost"), + ]; + const { root } = await renderMemberList(true, undefined, 2, threePidEvents); + + const tiles = root.container.querySelectorAll(".mx_MemberTileView"); + // 6 joined + 2 3PID invites + expect(tiles).toHaveLength(8); + }); + }); }); diff --git a/apps/web/test/unit-tests/components/views/rooms/memberlist/common.tsx b/apps/web/test/unit-tests/components/views/rooms/memberlist/common.tsx index 72bd2b3674..e5e5f4072e 100644 --- a/apps/web/test/unit-tests/components/views/rooms/memberlist/common.tsx +++ b/apps/web/test/unit-tests/components/views/rooms/memberlist/common.tsx @@ -15,6 +15,7 @@ import { VirtuosoMockContext } from "@element-hq/web-shared-components"; import { Room, type MatrixClient, + type MatrixEvent, type RoomState, RoomMember, User, @@ -54,6 +55,7 @@ export async function renderMemberList( enablePresence: boolean, roomSetup?: (room: Room) => void, usersPerLevel: number = 2, + threePidEvents: MatrixEvent[] = [], ): Promise { TestUtils.stubClient(); const client = MatrixClientPeg.safeGet(); @@ -109,7 +111,8 @@ export async function renderMemberList( memberListRoom.currentState = { members: {}, getMember: jest.fn(), - getStateEvents: ((eventType, stateKey) => (stateKey === undefined ? [] : null)) as RoomState["getStateEvents"], // ignore 3pid invites + getStateEvents: TestUtils.mockStateEventImplementation(threePidEvents), + getInviteForThreePidToken: jest.fn().mockReturnValue(null), getInvitedMemberCount: jest.fn().mockReturnValue(0), getJoinedMemberCount: jest .fn() @@ -139,7 +142,9 @@ export async function renderMemberList( }, ); await waitFor(async () => { - expect(root.container.querySelectorAll(".mx_MemberTileView")).toHaveLength(usersPerLevel * 3); + expect(root.container.querySelectorAll(".mx_MemberTileView")).toHaveLength( + usersPerLevel * 3 + threePidEvents.length, + ); }); const reRender = createReRenderFunction(client, memberListRoom);