From 9d93c86847d47083036434280373f5eceb821b93 Mon Sep 17 00:00:00 2001 From: R Midhun Suresh Date: Sat, 14 Dec 2024 16:12:45 +0530 Subject: [PATCH] More work --- .../viewmodels/MemberListViewModel.tsx | 4 +- .../viewmodels/MemberTileViewModel.tsx | 4 +- .../views/rooms/MemberListHeaderView.tsx | 10 +- src/i18n/strings/en_EN.json | 8 +- test/test-utils/test-utils.ts | 2 + .../views/rooms/MemberTileView-test.tsx | 114 +++++++++ .../MemberTileView-test.tsx.snap | 239 ++++++++++++++++++ 7 files changed, 371 insertions(+), 10 deletions(-) create mode 100644 test/unit-tests/components/views/rooms/MemberTileView-test.tsx create mode 100644 test/unit-tests/components/views/rooms/__snapshots__/MemberTileView-test.tsx.snap diff --git a/src/components/viewmodels/MemberListViewModel.tsx b/src/components/viewmodels/MemberListViewModel.tsx index a20b09c5f0..2e3a84ed05 100644 --- a/src/components/viewmodels/MemberListViewModel.tsx +++ b/src/components/viewmodels/MemberListViewModel.tsx @@ -50,7 +50,7 @@ import { useTypedEventEmitter } from "../../hooks/useEventEmitter"; type Member = XOR<{ member: RoomMember }, { threePidInvite: ThreePIDInvite }>; -function getPending3PidInvites(room: Room, searchQuery?: string): Member[] { +export function getPending3PidInvites(room: Room, searchQuery?: string): Member[] { // include 3pid invites (m.room.third_party_invite) state events. // The HS may have already converted these into m.room.member invites so // we shouldn't add them if the 3pid invite state key (token) is in the @@ -76,7 +76,7 @@ function getPending3PidInvites(room: Room, searchQuery?: string): Member[] { return invites; } -function sdkRoomMemberToRoomMember(member: SDKRoomMember): Member { +export function sdkRoomMemberToRoomMember(member: SDKRoomMember): Member { const displayUserId = UserIdentifierCustomisations.getDisplayUserIdentifier(member.userId, { roomId: member.roomId, diff --git a/src/components/viewmodels/MemberTileViewModel.tsx b/src/components/viewmodels/MemberTileViewModel.tsx index 23c29391a1..2269c9b913 100644 --- a/src/components/viewmodels/MemberTileViewModel.tsx +++ b/src/components/viewmodels/MemberTileViewModel.tsx @@ -56,7 +56,7 @@ export enum PowerStatus { const PowerLabel: Record = { [PowerStatus.Admin]: _td("power_level|admin"), - [PowerStatus.Moderator]: _td("power_level|mod"), + [PowerStatus.Moderator]: _td("power_level|moderator"), }; export interface ThreePidTileViewState { @@ -190,7 +190,7 @@ export function useMemberTileViewModel(props: MemberTileViewModelProps): MemberT userLabel = _t(PowerLabel[powerStatus]); } if (props.member.isInvite) { - userLabel = "(Invited)"; + userLabel = `(${_t("member_list|invited_label")})`; } return { diff --git a/src/components/views/rooms/MemberListHeaderView.tsx b/src/components/views/rooms/MemberListHeaderView.tsx index b05ba27d5e..1835ba6a86 100644 --- a/src/components/views/rooms/MemberListHeaderView.tsx +++ b/src/components/views/rooms/MemberListHeaderView.tsx @@ -47,16 +47,16 @@ function getHeaderLabelJSX(vm: MemberListViewState): React.ReactNode { if (vm.isLoading) { return ( - Loading... + {_t("common|loading")} ); } const filteredMemberCount = vm.members.length; if (filteredMemberCount === 0) { - return "No matches"; + return _t("member_list|no_matches"); } - return `${filteredMemberCount} Members`; + return _t("member_list|count", { count: filteredMemberCount }); } /** @@ -79,7 +79,7 @@ const MemberListHeaderView: React.FC = (props: Props) => { disabled={!vm.canInvite} onClick={vm.onInviteButtonClick} > - Invite + {_t("action|invite")} ) : ( @@ -87,7 +87,7 @@ const MemberListHeaderView: React.FC = (props: Props) => { vm.search((e as React.ChangeEvent).target.value)} /> diff --git a/src/i18n/strings/en_EN.json b/src/i18n/strings/en_EN.json index 4a524db97c..fd8d6f703b 100644 --- a/src/i18n/strings/en_EN.json +++ b/src/i18n/strings/en_EN.json @@ -1590,9 +1590,15 @@ "toggle_attribution": "Toggle attribution" }, "member_list": { - "filter_placeholder": "Filter room members", + "count": { + "one": "%(count)s Member", + "other": "%(count)s Members" + }, + "filter_placeholder": "Filter People...", "invite_button_no_perms_tooltip": "You do not have permission to invite users", + "invited_label": "Invited", "invited_list_heading": "Invited", + "no_matches": "No matches", "power_label": "%(userName)s (power %(powerLevelNumber)s)" }, "member_list_back_action_label": "Room members", diff --git a/test/test-utils/test-utils.ts b/test/test-utils/test-utils.ts index 78481c2fd0..97f90f553a 100644 --- a/test/test-utils/test-utils.ts +++ b/test/test-utils/test-utils.ts @@ -798,6 +798,8 @@ export const mkThirdPartyInviteEvent = (user: string, displayName: string, room: type: EventType.RoomThirdPartyInvite, content: { display_name: displayName, + public_key: "foo", + key_validity_url: "bar", }, skey: "test" + Math.random(), user, diff --git a/test/unit-tests/components/views/rooms/MemberTileView-test.tsx b/test/unit-tests/components/views/rooms/MemberTileView-test.tsx new file mode 100644 index 0000000000..70d62510a0 --- /dev/null +++ b/test/unit-tests/components/views/rooms/MemberTileView-test.tsx @@ -0,0 +1,114 @@ +/* + * Copyright 2024 New Vector Ltd. + * Copyright 2023 The Matrix.org Foundation C.I.C. + * + * SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only + * Please see LICENSE files in the repository root for full details. + */ + +import React from "react"; +import { render, screen, waitFor } from "jest-matrix-react"; +import { MatrixClient, RoomMember as SdkRoomMember, Device, Room } from "matrix-js-sdk/src/matrix"; +import { UserVerificationStatus, DeviceVerificationStatus } from "matrix-js-sdk/src/crypto-api"; +import { mocked } from "jest-mock"; +import userEvent from "@testing-library/user-event"; + +import * as TestUtils from "../../../../test-utils"; +import { RoomMember } from "../../../../../src/models/rooms/RoomMember"; +import { + getPending3PidInvites, + sdkRoomMemberToRoomMember, +} from "../../../../../src/components/viewmodels/MemberListViewModel"; +import { RoomMemberTileView, ThreePidInviteTileView } from "../../../../../src/components/views/rooms/MemberTileView"; + +describe("MemberTileView", () => { + describe("RoomMemberTileView", () => { + let matrixClient: MatrixClient; + let member: RoomMember; + + beforeEach(() => { + matrixClient = TestUtils.stubClient(); + mocked(matrixClient.isRoomEncrypted).mockReturnValue(true); + const sdkMember = new SdkRoomMember("roomId", matrixClient.getUserId()!); + member = sdkRoomMemberToRoomMember(sdkMember)!.member!; + }); + + it("should not display an E2EIcon when the e2E status = normal", () => { + const { container } = render(); + const e2eIcon = container.querySelector(".mx_E2EIconView"); + expect(e2eIcon).toBeNull(); + expect(container).toMatchSnapshot(); + }); + + it("should display an warning E2EIcon when the e2E status = Warning", async () => { + mocked(matrixClient.getCrypto()!.getUserVerificationStatus).mockResolvedValue({ + isCrossSigningVerified: jest.fn().mockReturnValue(false), + wasCrossSigningVerified: jest.fn().mockReturnValue(true), + } as unknown as UserVerificationStatus); + + const { container } = render(); + await waitFor(async () => { + await userEvent.hover(container.querySelector(".mx_E2EIcon")!); + expect(screen.getByText("This user has not verified all of their sessions.")).toBeInTheDocument(); + }); + expect(container).toMatchSnapshot(); + }); + + it("should display an verified E2EIcon when the e2E status = Verified", async () => { + // Mock all the required crypto methods + const deviceMap = new Map>(); + deviceMap.set(member.userId, new Map([["deviceId", {} as Device]])); + // Return a DeviceMap = Map> + mocked(matrixClient.getCrypto()!.getUserDeviceInfo).mockResolvedValue(deviceMap); + mocked(matrixClient.getCrypto()!.getUserVerificationStatus).mockResolvedValue({ + isCrossSigningVerified: jest.fn().mockReturnValue(true), + } as unknown as UserVerificationStatus); + mocked(matrixClient.getCrypto()!.getDeviceVerificationStatus).mockResolvedValue({ + crossSigningVerified: true, + } as DeviceVerificationStatus); + + const { container } = render(); + + await waitFor(async () => { + await userEvent.hover(container.querySelector(".mx_E2EIcon")!); + expect( + screen.getByText("You have verified this user. This user has verified all of their sessions."), + ).toBeInTheDocument(); + }); + expect(container).toMatchSnapshot(); + }); + + it("renders user labels correctly", async () => { + member.powerLevel = 50; + const { container: container1 } = render(); + expect(container1).toHaveTextContent("Moderator"); + + member.powerLevel = 100; + const { container: container2 } = render(); + expect(container2).toHaveTextContent("Admin"); + + member.isInvite = true; + const { container: container3 } = render(); + expect(container3).toHaveTextContent("Invited"); + }); + }); + + describe("ThreePidInviteTileView", () => { + let cli: MatrixClient; + let room: Room; + + beforeEach(() => { + cli = TestUtils.stubClient(); + room = new Room("!mytestroom:foo.org", cli, cli.getSafeUserId()); + room.getLiveTimeline().addEvent( + TestUtils.mkThirdPartyInviteEvent(cli.getSafeUserId(), "Foobar", room.roomId), + ); + }); + + it("renders ThreePidInvite correctly", async () => { + const [{ threePidInvite }] = getPending3PidInvites(room); + const { container } = render(); + expect(container).toMatchSnapshot(); + }); + }); +}); diff --git a/test/unit-tests/components/views/rooms/__snapshots__/MemberTileView-test.tsx.snap b/test/unit-tests/components/views/rooms/__snapshots__/MemberTileView-test.tsx.snap new file mode 100644 index 0000000000..c086ae5c2a --- /dev/null +++ b/test/unit-tests/components/views/rooms/__snapshots__/MemberTileView-test.tsx.snap @@ -0,0 +1,239 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`MemberTileView RoomMemberTileView should display an verified E2EIcon when the e2E status = Verified 1`] = ` +
+
+
+
+
+ + u + + +
+
+
+ + @userId:matrix.org + +
+
+
+
+ +
+ + + +
+
+
+
+
+
+`; + +exports[`MemberTileView RoomMemberTileView should display an warning E2EIcon when the e2E status = Warning 1`] = ` +
+
+
+
+
+ + u + + +
+
+
+ + @userId:matrix.org + +
+
+
+
+ +
+ + + +
+
+
+
+
+
+`; + +exports[`MemberTileView RoomMemberTileView should not display an E2EIcon when the e2E status = normal 1`] = ` +
+
+
+
+
+ + u + + +
+
+
+ + @userId:matrix.org + +
+
+
+
+
+
+
+`; + +exports[`MemberTileView ThreePidInviteTileView renders ThreePidInvite correctly 1`] = ` +
+
+
+
+
+ + +
+
+ Foobar +
+
+
+
+
+
+`;