From 83f12fcba07ccbdfd2b494c2b3898b9db6c2dc7d Mon Sep 17 00:00:00 2001 From: Richard van der Hoff <1389908+richvdh@users.noreply.github.com> Date: Fri, 21 Apr 2023 14:48:27 +0100 Subject: [PATCH] More tests for UserInfo (#10677) * UserInfo-test: move mocking to `beforeEach` ... so that changes to the mocks do not leak between tests * Add some more tests for UserInfo --- .../views/right_panel/UserInfo-test.tsx | 262 ++++++++++++------ test/test-utils/utilities.ts | 2 +- 2 files changed, 171 insertions(+), 93 deletions(-) diff --git a/test/components/views/right_panel/UserInfo-test.tsx b/test/components/views/right_panel/UserInfo-test.tsx index 68274d9bf8..8e53857723 100644 --- a/test/components/views/right_panel/UserInfo-test.tsx +++ b/test/components/views/right_panel/UserInfo-test.tsx @@ -15,9 +15,9 @@ limitations under the License. */ import React from "react"; -import { fireEvent, render, screen, waitFor, cleanup, act } from "@testing-library/react"; +import { fireEvent, render, screen, waitFor, cleanup, act, within } from "@testing-library/react"; import userEvent from "@testing-library/user-event"; -import { mocked } from "jest-mock"; +import { Mocked, mocked } from "jest-mock"; import { Room, User, MatrixClient, RoomMember, MatrixEvent, EventType } from "matrix-js-sdk/src/matrix"; import { Phase, VerificationRequest } from "matrix-js-sdk/src/crypto/verification/request/VerificationRequest"; import { DeviceTrustLevel, UserTrustLevel } from "matrix-js-sdk/src/crypto/CrossSigning"; @@ -72,73 +72,78 @@ jest.mock("../../../../src/utils/DMRoomMap", () => { }; }); -const mockRoom = mocked({ - roomId: "!fkfk", - getType: jest.fn().mockReturnValue(undefined), - isSpaceRoom: jest.fn().mockReturnValue(false), - getMember: jest.fn().mockReturnValue(undefined), - getMxcAvatarUrl: jest.fn().mockReturnValue("mock-avatar-url"), - name: "test room", - on: jest.fn(), - off: jest.fn(), - currentState: { - getStateEvents: jest.fn(), - on: jest.fn(), - }, - getEventReadUpTo: jest.fn(), -} as unknown as Room); - -const mockSpace = mocked({ - roomId: "!fkfk", - getType: jest.fn().mockReturnValue("m.space"), - isSpaceRoom: jest.fn().mockReturnValue(true), - getMember: jest.fn().mockReturnValue(undefined), - getMxcAvatarUrl: jest.fn().mockReturnValue("mock-avatar-url"), - name: "test room", - on: jest.fn(), - off: jest.fn(), - currentState: { - getStateEvents: jest.fn(), - on: jest.fn(), - }, - getEventReadUpTo: jest.fn(), -} as unknown as Room); - -const mockClient = mocked({ - getUser: jest.fn(), - isGuest: jest.fn().mockReturnValue(false), - isUserIgnored: jest.fn(), - getIgnoredUsers: jest.fn(), - setIgnoredUsers: jest.fn(), - isCryptoEnabled: jest.fn(), - getUserId: jest.fn(), - getSafeUserId: jest.fn(), - on: jest.fn(), - off: jest.fn(), - isSynapseAdministrator: jest.fn().mockResolvedValue(false), - isRoomEncrypted: jest.fn().mockReturnValue(false), - doesServerSupportUnstableFeature: jest.fn().mockReturnValue(false), - mxcUrlToHttp: jest.fn().mockReturnValue("mock-mxcUrlToHttp"), - removeListener: jest.fn(), - currentState: { - on: jest.fn(), - }, - checkDeviceTrust: jest.fn(), - checkUserTrust: jest.fn(), - getRoom: jest.fn(), - credentials: {}, - setPowerLevel: jest.fn(), -} as unknown as MatrixClient); - +const defaultRoomId = "!fkfk"; const defaultUserId = "@user:example.com"; const defaultUser = new User(defaultUserId); -beforeEach(() => { - jest.spyOn(MatrixClientPeg, "get").mockReturnValue(mockClient); -}); +let mockRoom: Mocked; +let mockSpace: Mocked; +let mockClient: Mocked; -afterEach(() => { - mockClient.getUser.mockClear().mockReturnValue({} as unknown as User); +beforeEach(() => { + mockRoom = mocked({ + roomId: defaultRoomId, + getType: jest.fn().mockReturnValue(undefined), + isSpaceRoom: jest.fn().mockReturnValue(false), + getMember: jest.fn().mockReturnValue(undefined), + getMxcAvatarUrl: jest.fn().mockReturnValue("mock-avatar-url"), + name: "test room", + on: jest.fn(), + off: jest.fn(), + currentState: { + getStateEvents: jest.fn(), + on: jest.fn(), + off: jest.fn(), + }, + getEventReadUpTo: jest.fn(), + } as unknown as Room); + + mockSpace = mocked({ + roomId: defaultRoomId, + getType: jest.fn().mockReturnValue("m.space"), + isSpaceRoom: jest.fn().mockReturnValue(true), + getMember: jest.fn().mockReturnValue(undefined), + getMxcAvatarUrl: jest.fn().mockReturnValue("mock-avatar-url"), + name: "test room", + on: jest.fn(), + off: jest.fn(), + currentState: { + getStateEvents: jest.fn(), + on: jest.fn(), + off: jest.fn(), + }, + getEventReadUpTo: jest.fn(), + } as unknown as Room); + + mockClient = mocked({ + getUser: jest.fn(), + isGuest: jest.fn().mockReturnValue(false), + isUserIgnored: jest.fn(), + getIgnoredUsers: jest.fn(), + setIgnoredUsers: jest.fn(), + isCryptoEnabled: jest.fn(), + getUserId: jest.fn(), + getSafeUserId: jest.fn(), + on: jest.fn(), + off: jest.fn(), + isSynapseAdministrator: jest.fn().mockResolvedValue(false), + isRoomEncrypted: jest.fn().mockReturnValue(false), + doesServerSupportUnstableFeature: jest.fn().mockReturnValue(false), + mxcUrlToHttp: jest.fn().mockReturnValue("mock-mxcUrlToHttp"), + removeListener: jest.fn(), + currentState: { + on: jest.fn(), + }, + checkDeviceTrust: jest.fn(), + checkUserTrust: jest.fn(), + getRoom: jest.fn(), + credentials: {}, + setPowerLevel: jest.fn(), + downloadKeys: jest.fn(), + getStoredDevicesForUser: jest.fn(), + } as unknown as MatrixClient); + + jest.spyOn(MatrixClientPeg, "get").mockReturnValue(mockClient); }); describe("", () => { @@ -241,14 +246,76 @@ describe("", () => { expect(screen.getByText(/try with a different client/i)).toBeInTheDocument(); }); }); + + describe("with crypto enabled", () => { + beforeEach(() => { + mockClient.isCryptoEnabled.mockReturnValue(true); + mockClient.checkUserTrust.mockReturnValue(new UserTrustLevel(false, false, false)); + mockClient.checkDeviceTrust.mockReturnValue(new DeviceTrustLevel(false, false, false, false)); + + const device1 = DeviceInfo.fromStorage( + { + unsigned: { device_display_name: "my device" }, + }, + "d1", + ); + mockClient.getStoredDevicesForUser.mockReturnValue([device1]); + }); + + it("renders a device list which can be expanded", async () => { + renderComponent(); + await act(flushPromises); + + // check the button exists with the expected text + const devicesButton = screen.getByRole("button", { name: "1 session" }); + + // click it + await userEvent.click(devicesButton); + + // there should now be a button with the device id ... + const deviceButton = screen.getByRole("button", { description: "d1" }); + + // ... which should contain the device name + expect(within(deviceButton).getByText("my device")).toBeInTheDocument(); + }); + }); + + describe("with an encrypted room", () => { + beforeEach(() => { + mockClient.isCryptoEnabled.mockReturnValue(true); + mockClient.isRoomEncrypted.mockReturnValue(true); + }); + + it("renders unverified user info", async () => { + mockClient.checkUserTrust.mockReturnValue(new UserTrustLevel(false, false, false)); + renderComponent({ room: mockRoom }); + await act(flushPromises); + + const userHeading = screen.getByRole("heading", { name: defaultUserId }); + + // there should be a "normal" E2E padlock + expect(userHeading.getElementsByClassName("mx_E2EIcon_normal")).toHaveLength(1); + }); + + it("renders verified user info", async () => { + mockClient.checkUserTrust.mockReturnValue(new UserTrustLevel(true, false, false)); + renderComponent({ room: mockRoom }); + await act(flushPromises); + + const userHeading = screen.getByRole("heading", { name: defaultUserId }); + + // there should be a "verified" E2E padlock + expect(userHeading.getElementsByClassName("mx_E2EIcon_verified")).toHaveLength(1); + }); + }); }); describe("", () => { - const defaultMember = new RoomMember(mockRoom.roomId, defaultUserId); + const defaultMember = new RoomMember(defaultRoomId, defaultUserId); const defaultProps = { member: defaultMember, - roomId: mockRoom.roomId, + roomId: defaultRoomId, }; const renderComponent = (props = {}) => { @@ -399,7 +466,7 @@ describe("", () => { }); describe("", () => { - const member = new RoomMember(mockRoom.roomId, defaultUserId); + const member = new RoomMember(defaultRoomId, defaultUserId); const defaultProps = { member, isIgnored: false, canInvite: false, isSpace: false }; const renderComponent = (props = {}) => { @@ -644,17 +711,20 @@ describe("", () => { }); describe("", () => { - const defaultMember = new RoomMember(mockRoom.roomId, defaultUserId); + const defaultMember = new RoomMember(defaultRoomId, defaultUserId); - const defaultProps = { - user: defaultMember, - room: mockRoom, - roomPermissions: { - modifyLevelMax: 100, - canEdit: false, - canInvite: false, - }, - }; + let defaultProps: Parameters[0]; + beforeEach(() => { + defaultProps = { + user: defaultMember, + room: mockRoom, + roomPermissions: { + modifyLevelMax: 100, + canEdit: false, + canInvite: false, + }, + }; + }); const renderComponent = (props = {}) => { const Wrapper = (wrapperProps = {}) => { @@ -705,11 +775,14 @@ describe("", () => { }); describe("", () => { - const defaultMember = new RoomMember(mockRoom.roomId, defaultUserId); + const defaultMember = new RoomMember(defaultRoomId, defaultUserId); const memberWithInviteMembership = { ...defaultMember, membership: "invite" }; const memberWithJoinMembership = { ...defaultMember, membership: "join" }; - const defaultProps = { room: mockRoom, member: defaultMember, startUpdating: jest.fn(), stopUpdating: jest.fn() }; + let defaultProps: Parameters[0]; + beforeEach(() => { + defaultProps = { room: mockRoom, member: defaultMember, startUpdating: jest.fn(), stopUpdating: jest.fn() }; + }); const renderComponent = (props = {}) => { const Wrapper = (wrapperProps = {}) => { @@ -805,10 +878,12 @@ describe("", () => { }); describe("", () => { - const defaultMember = new RoomMember(mockRoom.roomId, defaultUserId); + const defaultMember = new RoomMember(defaultRoomId, defaultUserId); const memberWithBanMembership = { ...defaultMember, membership: "ban" }; - - const defaultProps = { room: mockRoom, member: defaultMember, startUpdating: jest.fn(), stopUpdating: jest.fn() }; + let defaultProps: Parameters[0]; + beforeEach(() => { + defaultProps = { room: mockRoom, member: defaultMember, startUpdating: jest.fn(), stopUpdating: jest.fn() }; + }); const renderComponent = (props = {}) => { const Wrapper = (wrapperProps = {}) => { @@ -927,16 +1002,19 @@ describe("", () => { }); describe("", () => { - const defaultMember = new RoomMember(mockRoom.roomId, defaultUserId); + const defaultMember = new RoomMember(defaultRoomId, defaultUserId); defaultMember.membership = "invite"; - const defaultProps = { - room: mockRoom, - member: defaultMember, - startUpdating: jest.fn(), - stopUpdating: jest.fn(), - powerLevels: {}, - }; + let defaultProps: Parameters[0]; + beforeEach(() => { + defaultProps = { + room: mockRoom, + member: defaultMember, + startUpdating: jest.fn(), + stopUpdating: jest.fn(), + powerLevels: {}, + }; + }); const renderComponent = (props = {}) => { const Wrapper = (wrapperProps = {}) => { @@ -1039,7 +1117,7 @@ describe("disambiguateDevices", () => { describe("isMuted", () => { // this member has a power level of 0 - const isMutedMember = new RoomMember(mockRoom.roomId, defaultUserId); + const isMutedMember = new RoomMember(defaultRoomId, defaultUserId); it("returns false if either argument is falsy", () => { // @ts-ignore to let us purposely pass incorrect args diff --git a/test/test-utils/utilities.ts b/test/test-utils/utilities.ts index 6590ef5230..514a9091f1 100644 --- a/test/test-utils/utilities.ts +++ b/test/test-utils/utilities.ts @@ -127,7 +127,7 @@ export function untilEmission( }); } -export const flushPromises = async () => await new Promise((resolve) => window.setTimeout(resolve)); +export const flushPromises = async () => await new Promise((resolve) => window.setTimeout(resolve)); // with jest's modern fake timers process.nextTick is also mocked, // flushing promises in the normal way then waits for some advancement