From 06077170db48d9285e17fd4ef847869885bdfc07 Mon Sep 17 00:00:00 2001 From: Timo Date: Tue, 13 May 2025 20:58:50 +0200 Subject: [PATCH] Add tests --- src/room/InCallView.test.tsx | 243 ++++++++++++++++++ .../__snapshots__/InCallView.test.tsx.snap | 181 +++++++++++++ src/utils/test.ts | 8 +- 3 files changed, 430 insertions(+), 2 deletions(-) create mode 100644 src/room/InCallView.test.tsx create mode 100644 src/room/__snapshots__/InCallView.test.tsx.snap diff --git a/src/room/InCallView.test.tsx b/src/room/InCallView.test.tsx new file mode 100644 index 00000000..3f8dd346 --- /dev/null +++ b/src/room/InCallView.test.tsx @@ -0,0 +1,243 @@ +/* +Copyright 2025 New Vector Ltd. + +SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial +Please see LICENSE in the repository root for full details. +*/ + +import { beforeEach, expect, it, type MockedFunction, test, vi } from "vitest"; +import { act, render, RenderResult } from "@testing-library/react"; +import { type MatrixClient, JoinRule, type RoomState } from "matrix-js-sdk"; +import { type MatrixRTCSession } from "matrix-js-sdk/lib/matrixrtc"; +import { type RelationsContainer } from "matrix-js-sdk/lib/models/relations-container"; +import { ConnectionState, type LocalParticipant } from "livekit-client"; +import { of } from "rxjs"; +import { BrowserRouter } from "react-router-dom"; +import { TooltipProvider } from "@vector-im/compound-web"; +import { + RoomAudioRenderer, + RoomContext, + useLocalParticipant, +} from "@livekit/components-react"; +import { RoomAndToDeviceEvents } from "matrix-js-sdk/lib/matrixrtc/RoomAndToDeviceKeyTransport"; + +import { type MuteStates } from "./MuteStates"; +import { InCallView } from "./InCallView"; +import { + mockLivekitRoom, + mockLocalParticipant, + mockMatrixRoom, + mockMatrixRoomMember, + mockRemoteParticipant, + mockRtcMembership, + type MockRTCSession, +} from "../utils/test"; +import { E2eeType } from "../e2ee/e2eeType"; +import { getBasicCallViewModelEnvironment } from "../utils/test-viewmodel"; +import { alice, local } from "../utils/test-fixtures"; +import { useExperimentalToDeviceTransportSetting } from "../settings/settings"; +import { ReactionsSenderProvider } from "../reactions/useReactionsSender"; +import { useRoomEncryptionSystem } from "../e2ee/sharedKeyManagement"; +import { describe } from "node:test"; + +// vi.hoisted(() => { +// localStorage = {} as unknown as Storage; +// }); +vi.hoisted( + () => + (global.ImageData = class MockImageData { + public data: number[] = []; + } as unknown as typeof ImageData), +); + +vi.mock("../soundUtils"); +vi.mock("../useAudioContext"); +vi.mock("../tile/GridTile"); +vi.mock("../tile/SpotlightTile"); +vi.mock("@livekit/components-react"); +vi.mock("../e2ee/sharedKeyManagement"); +vi.mock("react-use-measure", () => ({ + default: (): [() => void, object] => [(): void => {}, {}], +})); + +const localRtcMember = mockRtcMembership("@carol:example.org", "CCCC"); +const localParticipant = mockLocalParticipant({ + identity: "@local:example.org:AAAAAA", +}); +const remoteParticipant = mockRemoteParticipant({ + identity: "@alice:example.org:AAAAAA", +}); +const carol = mockMatrixRoomMember(localRtcMember); +const roomMembers = new Map([carol].map((p) => [p.userId, p])); + +const roomId = "!foo:bar"; +let useRoomEncryptionSystemMock: MockedFunction; +beforeEach(() => { + vi.clearAllMocks(); + // RoomAudioRenderer is tested separately. + ( + RoomAudioRenderer as MockedFunction + ).mockImplementation((_props) => { + return
mocked: RoomAudioRenderer
; + }); + ( + useLocalParticipant as MockedFunction + ).mockImplementation( + () => + ({ + isScreenShareEnabled: false, + localParticipant: localRtcMember as unknown as LocalParticipant, + }) as unknown as ReturnType, + ); + + useRoomEncryptionSystemMock = + useRoomEncryptionSystem as typeof useRoomEncryptionSystemMock; + useRoomEncryptionSystemMock.mockReturnValue({ kind: E2eeType.NONE }); +}); + +function createInCallView(): RenderResult & { + rtcSession: MockRTCSession; +} { + const client = { + getUser: () => null, + getUserId: () => localRtcMember.sender, + getDeviceId: () => localRtcMember.deviceId, + getRoom: (rId) => (rId === roomId ? room : null), + } as Partial as MatrixClient; + const room = mockMatrixRoom({ + relations: { + getChildEventsForEvent: () => + vi.mocked({ + getRelations: () => [], + }), + } as unknown as RelationsContainer, + client, + roomId, + getMember: (userId) => roomMembers.get(userId) ?? null, + getMxcAvatarUrl: () => null, + hasEncryptionStateEvent: vi.fn().mockReturnValue(true), + getCanonicalAlias: () => null, + currentState: { + getJoinRule: () => JoinRule.Invite, + } as Partial as RoomState, + }); + + const muteState = { + audio: { enabled: false }, + video: { enabled: false }, + } as MuteStates; + const livekitRoom = mockLivekitRoom( + { + localParticipant, + }, + { + remoteParticipants$: of([remoteParticipant]), + }, + ); + const { vm, rtcSession } = getBasicCallViewModelEnvironment([local, alice]); + + rtcSession.joined = true; + const renderResult = render( + + + + + + + + + , + ); + return { + ...renderResult, + rtcSession, + }; +} + +void describe("InCallView", async () => { + await describe("rendering", () => { + it("renders", () => { + const { container } = createInCallView(); + expect(container).toMatchSnapshot(); + }); + }); + await describe("toDevice label", () => { + it("is shown if setting activated and room encrypted", () => { + useRoomEncryptionSystemMock.mockReturnValue({ + kind: E2eeType.PER_PARTICIPANT, + }); + useExperimentalToDeviceTransportSetting.setValue(true); + const { getByText } = createInCallView(); + expect(getByText("using to Device key transport")).toBeInTheDocument(); + }); + + it("is not shown in unenecrypted room", () => { + useRoomEncryptionSystemMock.mockReturnValue({ + kind: E2eeType.NONE, + }); + useExperimentalToDeviceTransportSetting.setValue(true); + const { queryByText } = createInCallView(); + expect( + queryByText("using to Device key transport"), + ).not.toBeInTheDocument(); + }); + + it("is hidden once fallback was triggered", async () => { + useRoomEncryptionSystemMock.mockReturnValue({ + kind: E2eeType.PER_PARTICIPANT, + }); + useExperimentalToDeviceTransportSetting.setValue(true); + const { rtcSession, queryByText } = createInCallView(); + expect(queryByText("using to Device key transport")).toBeInTheDocument(); + expect(rtcSession).toBeDefined(); + await act(() => + rtcSession.emit(RoomAndToDeviceEvents.EnabledTransportsChanged, { + toDevice: true, + room: true, + }), + ); + expect( + queryByText("using to Device key transport"), + ).not.toBeInTheDocument(); + }); + it("is not shown if setting is disabled", () => { + useExperimentalToDeviceTransportSetting.setValue(false); + + useRoomEncryptionSystemMock.mockReturnValue({ + kind: E2eeType.PER_PARTICIPANT, + }); + const { queryByText } = createInCallView(); + expect( + queryByText("using to Device key transport"), + ).not.toBeInTheDocument(); + }); + }); +}); diff --git a/src/room/__snapshots__/InCallView.test.tsx.snap b/src/room/__snapshots__/InCallView.test.tsx.snap new file mode 100644 index 00000000..7d0e6e54 --- /dev/null +++ b/src/room/__snapshots__/InCallView.test.tsx.snap @@ -0,0 +1,181 @@ +// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html + +exports[`renders 1`] = ` +
+
+
+
+ mocked: RoomAudioRenderer +
+
+
+
+
+
+
+
+
+
+
+
+ +
+
+`; diff --git a/src/utils/test.ts b/src/utils/test.ts index 039b6983..897e731b 100644 --- a/src/utils/test.ts +++ b/src/utils/test.ts @@ -40,6 +40,10 @@ import { type ResolvedConfigOptions, } from "../config/ConfigOptions"; import { Config } from "../config/Config"; +import { + RoomAndToDeviceEvents, + RoomAndToDeviceEventsHandlerMap, +} from "matrix-js-sdk/lib/matrixrtc/RoomAndToDeviceKeyTransport"; export function withFakeTimers(continuation: () => void): void { vi.useFakeTimers(); @@ -269,8 +273,8 @@ export function mockConfig(config: Partial = {}): void { } export class MockRTCSession extends TypedEventEmitter< - MatrixRTCSessionEvent, - MatrixRTCSessionEventHandlerMap + MatrixRTCSessionEvent | RoomAndToDeviceEvents, + MatrixRTCSessionEventHandlerMap & RoomAndToDeviceEventsHandlerMap > { public readonly statistics = { counters: {},