diff --git a/src/livekit/MatrixAudioRenderer.test.tsx b/src/livekit/MatrixAudioRenderer.test.tsx index 4fe7d333..e2464eb8 100644 --- a/src/livekit/MatrixAudioRenderer.test.tsx +++ b/src/livekit/MatrixAudioRenderer.test.tsx @@ -7,7 +7,6 @@ Please see LICENSE in the repository root for full details. import { afterEach, beforeEach, expect, it, vi } from "vitest"; import { render } from "@testing-library/react"; -import { type CallMembership } from "matrix-js-sdk/lib/matrixrtc"; import { getTrackReferenceId, type TrackReference, @@ -15,11 +14,19 @@ import { import { type RemoteAudioTrack } from "livekit-client"; import { type ReactNode } from "react"; import { useTracks } from "@livekit/components-react"; +import { of } from "rxjs"; import { testAudioContext } from "../useAudioContext.test"; import * as MediaDevicesContext from "../MediaDevicesContext"; import { LivekitRoomAudioRenderer } from "./MatrixAudioRenderer"; -import { mockMediaDevices, mockTrack } from "../utils/test"; +import { + mockLivekitRoom, + mockMatrixRoomMember, + mockMediaDevices, + mockRtcMembership, + mockTrack +} from "../utils/test"; + export const TestAudioContextConstructor = vi.fn(() => testAudioContext); @@ -52,10 +59,26 @@ const tracks = [mockTrack("test:123")]; vi.mocked(useTracks).mockReturnValue(tracks); it("should render for member", () => { + // TODO this is duplicated test setup in all tests + const localRtcMember = mockRtcMembership("@carol:example.org", "CCCC"); + const carol = mockMatrixRoomMember(localRtcMember); + const p = { + id: "test:123", + participant: undefined, + member: carol + } + const livekitRoom = mockLivekitRoom( + {}, + { + remoteParticipants$: of([]), + }, + ); const { container, queryAllByTestId } = render( , ); @@ -64,12 +87,29 @@ it("should render for member", () => { }); it("should not render without member", () => { - const memberships = [ - { sender: "othermember", deviceId: "123" }, - ] as CallMembership[]; + // const memberships = [ + // { sender: "othermember", deviceId: "123" }, + // ] as CallMembership[]; + const localRtcMember = mockRtcMembership("@carol:example.org", "CCCC"); + const carol = mockMatrixRoomMember(localRtcMember); + const p = { + id: "test:123", + participant: undefined, + member: carol + } + const livekitRoom = mockLivekitRoom( + {}, + { + remoteParticipants$: of([]), + }, + ); const { container, queryAllByTestId } = render( - + , ); expect(container).toBeTruthy(); @@ -77,10 +117,25 @@ it("should not render without member", () => { }); it("should not setup audioContext gain and pan if there is no need to.", () => { + const localRtcMember = mockRtcMembership("@carol:example.org", "CCCC"); + const carol = mockMatrixRoomMember(localRtcMember); + const p = { + id: "test:123", + participant: undefined, + member: carol + } + const livekitRoom = mockLivekitRoom( + {}, + { + remoteParticipants$: of([]), + }, + ); render( , ); @@ -100,11 +155,25 @@ it("should setup audioContext gain and pan", () => { pan: 1, volume: 0.1, }); + const localRtcMember = mockRtcMembership("@carol:example.org", "CCCC"); + const carol = mockMatrixRoomMember(localRtcMember); + const p = { + id: "test:123", + participant: undefined, + member: carol + } + const livekitRoom = mockLivekitRoom( + {}, + { + remoteParticipants$: of([]), + }, + ); render( + participants={[p]} + url={""} + livekitRoom={livekitRoom} /> , ); diff --git a/src/livekit/MatrixAudioRenderer.tsx b/src/livekit/MatrixAudioRenderer.tsx index f402b32d..76c206c7 100644 --- a/src/livekit/MatrixAudioRenderer.tsx +++ b/src/livekit/MatrixAudioRenderer.tsx @@ -33,7 +33,9 @@ export interface MatrixAudioRendererProps { * that are not expected to be in the rtc session. */ participants: { - participant: Participant; + id: string; + // TODO it appears to be optional as per InCallView? but what does that mean here? a rtc member not yet joined in livekit? + participant: Participant | undefined; member: RoomMember; }[]; /** @@ -82,7 +84,7 @@ export function LivekitRoomAudioRenderer({ if (loggedInvalidIdentities.current.has(identity)) return; logger.warn( `[MatrixAudioRenderer] Audio track ${identity} from ${url} has no matching matrix call member`, - `current members: ${participants.map((p) => p.participant.identity)}`, + `current members: ${participants.map((p) => p.participant?.identity)}`, `track will not get rendered`, ); loggedInvalidIdentities.current.add(identity); diff --git a/src/main.tsx b/src/main.tsx index e795a13c..f27b55a4 100644 --- a/src/main.tsx +++ b/src/main.tsx @@ -11,7 +11,6 @@ Please see LICENSE in the repository root for full details. // dependency references. import "matrix-js-sdk/lib/browser-index"; -import { StrictMode } from "react"; import { createRoot } from "react-dom/client"; import "./index.css"; import { logger } from "matrix-js-sdk/lib/logger"; diff --git a/src/room/CallEventAudioRenderer.test.tsx b/src/room/CallEventAudioRenderer.test.tsx index 40b79da4..e7d7e85a 100644 --- a/src/room/CallEventAudioRenderer.test.tsx +++ b/src/room/CallEventAudioRenderer.test.tsx @@ -155,7 +155,8 @@ test("plays one sound when a hand is raised", () => { act(() => { handRaisedSubject$.next({ - [bobRtcMember.callId]: { + // TODO: What is this string supposed to be? + [`${bobRtcMember.sender}:${bobRtcMember.deviceId}`]: { time: new Date(), membershipEventId: "", reactionEventId: "", diff --git a/src/room/GroupCallView.test.tsx b/src/room/GroupCallView.test.tsx index b8bc2f53..22d99b31 100644 --- a/src/room/GroupCallView.test.tsx +++ b/src/room/GroupCallView.test.tsx @@ -26,7 +26,6 @@ import { type RelationsContainer } from "matrix-js-sdk/lib/models/relations-cont import { useState } from "react"; import { TooltipProvider } from "@vector-im/compound-web"; -import { type MuteStates } from "./MuteStates"; import { prefetchSounds } from "../soundUtils"; import { useAudioContext } from "../useAudioContext"; import { ActiveCall } from "./InCallView"; @@ -47,6 +46,7 @@ import { ProcessorProvider } from "../livekit/TrackProcessorContext"; import { MediaDevicesContext } from "../MediaDevicesContext"; import { HeaderStyle } from "../UrlParams"; import { constant } from "../state/Behavior"; +import { type MuteStates } from "../state/MuteStates.ts"; vi.mock("../soundUtils"); vi.mock("../useAudioContext"); @@ -150,7 +150,7 @@ function createGroupCallView( const muteState = { audio: { enabled: false }, video: { enabled: false }, - } as MuteStates; + } as unknown as MuteStates; const { getByText } = render( @@ -164,9 +164,10 @@ function createGroupCallView( skipLobby={false} header={HeaderStyle.Standard} rtcSession={rtcSession as unknown as MatrixRTCSession} - isJoined={joined} muteStates={muteState} widget={widget} + joined={true} + setJoined={function(value: boolean): void { }} /> diff --git a/src/room/InCallView.test.tsx b/src/room/InCallView.test.tsx index 6d2aaf0a..d2694120 100644 --- a/src/room/InCallView.test.tsx +++ b/src/room/InCallView.test.tsx @@ -24,7 +24,6 @@ import { TooltipProvider } from "@vector-im/compound-web"; import { 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, @@ -48,6 +47,7 @@ import { useRoomEncryptionSystem } from "../e2ee/sharedKeyManagement"; import { LivekitRoomAudioRenderer } from "../livekit/MatrixAudioRenderer"; import { MediaDevicesContext } from "../MediaDevicesContext"; import { HeaderStyle } from "../UrlParams"; +import { type MuteStates } from "../state/MuteStates.ts"; // vi.hoisted(() => { // localStorage = {} as unknown as Storage; @@ -136,7 +136,7 @@ function createInCallView(): RenderResult & { const muteState = { audio: { enabled: false }, video: { enabled: false }, - } as MuteStates; + } as unknown as MuteStates; const livekitRoom = mockLivekitRoom( { localParticipant, @@ -176,11 +176,6 @@ function createInCallView(): RenderResult & { }, }} matrixRoom={room} - livekitRoom={livekitRoom} - participantCount={0} - onLeft={function (): void { - throw new Error("Function not implemented."); - }} onShareClick={null} /> diff --git a/src/room/InCallView.tsx b/src/room/InCallView.tsx index 57873b40..8474c2fd 100644 --- a/src/room/InCallView.tsx +++ b/src/room/InCallView.tsx @@ -23,7 +23,7 @@ import useMeasure from "react-use-measure"; import { type MatrixRTCSession } from "matrix-js-sdk/lib/matrixrtc"; import classNames from "classnames"; import { BehaviorSubject, map } from "rxjs"; -import { useObservable, useObservableEagerState } from "observable-hooks"; +import { useObservable } from "observable-hooks"; import { logger } from "matrix-js-sdk/lib/logger"; import { RoomAndToDeviceEvents } from "matrix-js-sdk/lib/matrixrtc/RoomAndToDeviceKeyTransport"; import { @@ -112,7 +112,6 @@ import { prefetchSounds } from "../soundUtils"; import { useAudioContext } from "../useAudioContext"; import ringtoneMp3 from "../sound/ringtone.mp3?url"; import ringtoneOgg from "../sound/ringtone.ogg?url"; -import { ConnectionLostError } from "../utils/errors.ts"; import { useTrackProcessorObservable$ } from "../livekit/TrackProcessorContext.tsx"; const maxTapDurationMs = 400; @@ -206,7 +205,8 @@ export const InCallView: FC = ({ useReactionsSender(); useWakeLock(); - const connectionState = useObservableEagerState(vm.livekitConnectionState$); + // TODO multi-sfu This is unused now?? + // const connectionState = useObservableEagerState(vm.livekitConnectionState$); // annoyingly we don't get the disconnection reason this way, // only by listening for the emitted event diff --git a/src/room/VideoPreview.test.tsx b/src/room/VideoPreview.test.tsx index 717333ee..17a05e34 100644 --- a/src/room/VideoPreview.test.tsx +++ b/src/room/VideoPreview.test.tsx @@ -5,12 +5,11 @@ SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial Please see LICENSE in the repository root for full details. */ -import { expect, describe, it, vi, beforeAll } from "vitest"; +import { expect, describe, it, beforeAll } from "vitest"; import { render } from "@testing-library/react"; import { type MatrixInfo, VideoPreview } from "./VideoPreview"; import { E2eeType } from "../e2ee/e2eeType"; -import { mockMuteStates } from "../utils/test"; describe("VideoPreview", () => { const matrixInfo: MatrixInfo = { @@ -42,7 +41,7 @@ describe("VideoPreview", () => { const { queryByRole } = render( } />, @@ -54,7 +53,7 @@ describe("VideoPreview", () => { const { queryByRole } = render( } />, diff --git a/src/rtcSessionHelpers.test.ts b/src/rtcSessionHelpers.test.ts index 2ef9e3f1..1058628f 100644 --- a/src/rtcSessionHelpers.test.ts +++ b/src/rtcSessionHelpers.test.ts @@ -23,37 +23,38 @@ vi.mock("./widget", () => ({ ...actualWidget, widget: { api: { - setAlwaysOnScreen: (): void => {}, - transport: { send: vi.fn(), reply: vi.fn(), stop: vi.fn() }, + setAlwaysOnScreen: (): void => { + }, + transport: { send: vi.fn(), reply: vi.fn(), stop: vi.fn() } }, - lazyActions: new EventEmitter(), - }, + lazyActions: new EventEmitter() + } })); test("It joins the correct Session", async () => { const focusFromOlderMembership = { type: "livekit", livekit_service_url: "http://my-oldest-member-service-url.com", - livekit_alias: "my-oldest-member-service-alias", + livekit_alias: "my-oldest-member-service-alias" }; const focusConfigFromWellKnown = { type: "livekit", - livekit_service_url: "http://my-well-known-service-url.com", + livekit_service_url: "http://my-well-known-service-url.com" }; const focusConfigFromWellKnown2 = { type: "livekit", - livekit_service_url: "http://my-well-known-service-url2.com", + livekit_service_url: "http://my-well-known-service-url2.com" }; const clientWellKnown = { "org.matrix.msc4143.rtc_foci": [ focusConfigFromWellKnown, - focusConfigFromWellKnown2, - ], + focusConfigFromWellKnown2 + ] }; mockConfig({ - livekit: { livekit_service_url: "http://my-default-service-url.com" }, + livekit: { livekit_service_url: "http://my-default-service-url.com" } }); vi.spyOn(AutoDiscovery, "getRawClientConfig").mockImplementation( @@ -62,7 +63,7 @@ test("It joins the correct Session", async () => { return Promise.resolve(clientWellKnown); } return Promise.resolve({}); - }, + } ); const mockedSession = vi.mocked({ @@ -74,58 +75,64 @@ test("It joins the correct Session", async () => { access_token: "ACCCESS_TOKEN", token_type: "Bearer", matrix_server_name: "localhost", - expires_in: 10000, - }), - }, + expires_in: 10000 + }) + } }, memberships: [], getFocusInUse: vi.fn().mockReturnValue(focusFromOlderMembership), getOldestMembership: vi.fn().mockReturnValue({ - getPreferredFoci: vi.fn().mockReturnValue([focusFromOlderMembership]), + getPreferredFoci: vi.fn().mockReturnValue([focusFromOlderMembership]) }), - joinRoomSession: vi.fn(), + joinRoomSession: vi.fn() }) as unknown as MatrixRTCSession; - await enterRTCSession(mockedSession, false); + + await enterRTCSession(mockedSession, { + livekit_alias: "roomId", + livekit_service_url: "http://my-well-known-service-url.com", + type: "livekit" + }, + true); expect(mockedSession.joinRoomSession).toHaveBeenLastCalledWith( [ { livekit_alias: "my-oldest-member-service-alias", livekit_service_url: "http://my-oldest-member-service-url.com", - type: "livekit", + type: "livekit" }, { livekit_alias: "roomId", livekit_service_url: "http://my-well-known-service-url.com", - type: "livekit", + type: "livekit" }, { livekit_alias: "roomId", livekit_service_url: "http://my-well-known-service-url2.com", - type: "livekit", + type: "livekit" }, { livekit_alias: "roomId", livekit_service_url: "http://my-default-service-url.com", - type: "livekit", - }, + type: "livekit" + } ], { focus_selection: "oldest_membership", - type: "livekit", + type: "livekit" }, { manageMediaKeys: false, useLegacyMemberEvents: false, useNewMembershipManager: true, - useExperimentalToDeviceTransport: false, - }, + useExperimentalToDeviceTransport: false + } ); }); async function testLeaveRTCSession( cause: "user" | "error", - expectClose: boolean, + expectClose: boolean ): Promise { vi.clearAllMocks(); const session = { leaveRoomSession: vi.fn() } as unknown as MatrixRTCSession; @@ -133,18 +140,18 @@ async function testLeaveRTCSession( expect(session.leaveRoomSession).toHaveBeenCalled(); expect(widget!.api.transport.send).toHaveBeenCalledWith( ElementWidgetActions.HangupCall, - expect.anything(), + expect.anything() ); if (expectClose) { expect(widget!.api.transport.send).toHaveBeenCalledWith( ElementWidgetActions.Close, - expect.anything(), + expect.anything() ); expect(widget!.api.transport.stop).toHaveBeenCalled(); } else { expect(widget!.api.transport.send).not.toHaveBeenCalledWith( ElementWidgetActions.Close, - expect.anything(), + expect.anything() ); expect(widget!.api.transport.stop).not.toHaveBeenCalled(); } @@ -172,16 +179,24 @@ test("It fails with configuration error if no live kit url config is set in fall room: { roomId: "roomId", client: { - getDomain: vi.fn().mockReturnValue("example.org"), - }, + getDomain: vi.fn().mockReturnValue("example.org") + } }, memberships: [], getFocusInUse: vi.fn(), - joinRoomSession: vi.fn(), + joinRoomSession: vi.fn() }) as unknown as MatrixRTCSession; - await expect(enterRTCSession(mockedSession, false)).rejects.toThrowError( - expect.objectContaining({ code: ErrorCode.MISSING_MATRIX_RTC_FOCUS }), + await expect(enterRTCSession( + mockedSession, + { + livekit_alias: "roomId", + livekit_service_url: "http://my-well-known-service-url.com", + type: "livekit" + }, + true + )).rejects.toThrowError( + expect.objectContaining({ code: ErrorCode.MISSING_MATRIX_RTC_TRANSPORT }) ); }); @@ -191,9 +206,9 @@ test("It should not fail with configuration error if homeserver config has livek "org.matrix.msc4143.rtc_foci": [ { type: "livekit", - livekit_service_url: "http://my-well-known-service-url.com", - }, - ], + livekit_service_url: "http://my-well-known-service-url.com" + } + ] }); const mockedSession = vi.mocked({ @@ -205,14 +220,19 @@ test("It should not fail with configuration error if homeserver config has livek access_token: "ACCCESS_TOKEN", token_type: "Bearer", matrix_server_name: "localhost", - expires_in: 10000, - }), - }, + expires_in: 10000 + }) + } }, memberships: [], getFocusInUse: vi.fn(), - joinRoomSession: vi.fn(), + joinRoomSession: vi.fn() }) as unknown as MatrixRTCSession; - await enterRTCSession(mockedSession, false); + await enterRTCSession(mockedSession, { + livekit_alias: "roomId", + livekit_service_url: "http://my-well-known-service-url.com", + type: "livekit" + }, + true); }); diff --git a/src/state/Async.ts b/src/state/Async.ts index 2baa674c..45676759 100644 --- a/src/state/Async.ts +++ b/src/state/Async.ts @@ -9,12 +9,13 @@ import { catchError, from, map, - Observable, + type Observable, of, - startWith, - switchMap, + startWith } from "rxjs"; +// TODO where are all the comments? ::cry:: +// There used to be an unitialized state!, a state might not start in loading export type Async = | { state: "loading" } | { state: "error"; value: Error } @@ -24,21 +25,22 @@ export const loading: Async = { state: "loading" }; export function error(value: Error): Async { return { state: "error", value }; } + export function ready(value: A): Async { return { state: "ready", value }; } -export function async(promise: Promise): Observable> { +export function async$(promise: Promise): Observable> { return from(promise).pipe( map(ready), startWith(loading), - catchError((e) => of(error(e))), + catchError((e: unknown) => of(error(e as Error ?? new Error("Unknown error")))), ); } export function mapAsync( async: Async, - project: (value: A) => B, + project: (value: A) => B ): Async { return async.state === "ready" ? ready(project(async.value)) : async; } diff --git a/src/state/CallViewModel.test.ts b/src/state/CallViewModel.test.ts index 07c78ef6..d9cad2b7 100644 --- a/src/state/CallViewModel.test.ts +++ b/src/state/CallViewModel.test.ts @@ -68,7 +68,7 @@ import { type ECConnectionState, } from "../livekit/useECConnectionState"; import { E2eeType } from "../e2ee/e2eeType"; -import type { RaisedHandInfo } from "../reactions"; +import type { RaisedHandInfo, ReactionInfo } from "../reactions"; import { alice, aliceDoppelganger, @@ -95,6 +95,7 @@ import { ObservableScope } from "./ObservableScope"; import { MediaDevices } from "./MediaDevices"; import { getValue } from "../utils/observable"; import { type Behavior, constant } from "./Behavior"; +import type { ProcessorState } from "../livekit/TrackProcessorContext.tsx"; const getUrlParams = vi.hoisted(() => vi.fn(() => ({}))); vi.mock("../UrlParams", () => ({ getUrlParams })); @@ -341,6 +342,7 @@ function withCallViewModel( .mockImplementation((_room, _eventType) => of()); const muteStates = mockMuteStates(); const raisedHands$ = new BehaviorSubject>({}); + const reactions$ = new BehaviorSubject>({}); const vm = new CallViewModel( rtcSession as unknown as MatrixRTCSession, @@ -349,7 +351,8 @@ function withCallViewModel( muteStates, options, raisedHands$, - new BehaviorSubject({}), + reactions$, + new BehaviorSubject({ processor: undefined, supported: undefined }), ); onTestFinished(() => { diff --git a/src/state/CallViewModel.ts b/src/state/CallViewModel.ts index 2c02521e..4b8ff879 100644 --- a/src/state/CallViewModel.ts +++ b/src/state/CallViewModel.ts @@ -132,7 +132,7 @@ import { getUrlParams } from "../UrlParams"; import { type ProcessorState } from "../livekit/TrackProcessorContext"; import { ElementWidgetActions, widget } from "../widget"; import { PublishConnection } from "./PublishConnection.ts"; -import { type Async, async, mapAsync, ready } from "./Async"; +import { type Async, async$, mapAsync, ready } from "./Async"; export interface CallViewModelOptions { encryptionSystem: EncryptionSystem; @@ -520,7 +520,7 @@ export class CallViewModel extends ViewModel { joined ? combineLatest( [ - async(this.preferredTransport), + async$(this.preferredTransport), this.memberships$, multiSfu.value$, ], @@ -1953,7 +1953,10 @@ export class CallViewModel extends ViewModel { .subscribe(({ start, stop }) => { for (const c of stop) { logger.info(`Disconnecting from ${c.localTransport.livekit_service_url}`); - c.stop(); + c.stop().catch((err) => { + // TODO: better error handling + logger.error("MuteState: handler error", err); + });; } for (const c of start) { c.start().then( diff --git a/src/state/Connection.test.ts b/src/state/Connection.test.ts index 07a38d7d..74a61515 100644 --- a/src/state/Connection.test.ts +++ b/src/state/Connection.test.ts @@ -6,7 +6,6 @@ Please see LICENSE in the repository root for full details. */ import { afterEach, describe, expect, it, type Mock, type MockedObject, vi } from "vitest"; -import type { CallMembership, LivekitTransport } from "matrix-js-sdk/lib/matrixrtc"; import { BehaviorSubject, of } from "rxjs"; import { ConnectionState, @@ -18,8 +17,8 @@ import { import fetchMock from "fetch-mock"; import EventEmitter from "events"; import { type IOpenIDToken } from "matrix-js-sdk"; -import { type BackgroundOptions, type ProcessorWrapper } from "@livekit/track-processors"; +import type { CallMembership, LivekitTransport } from "matrix-js-sdk/lib/matrixrtc"; import { type ConnectionOpts, type FocusConnectionState, RemoteConnection } from "./Connection.ts"; import { ObservableScope } from "./ObservableScope.ts"; import { type OpenIDClientParts } from "../livekit/openIDSFU.ts"; @@ -29,7 +28,6 @@ import { mockMediaDevices, mockMuteStates } from "../utils/test.ts"; import type { ProcessorState } from "../livekit/TrackProcessorContext.tsx"; import { type MuteStates } from "./MuteStates.ts"; - let testScope: ObservableScope; let client: MockedObject; @@ -551,7 +549,7 @@ describe("Publishing participants observations", () => { }); - it("should be scoped to parent scope", async () => { + it("should be scoped to parent scope", (): void => { setupTest(); const connection = setupRemoteConnection(); @@ -613,7 +611,7 @@ describe("PublishConnection", () => { let roomFactoryMock: Mock<() => LivekitRoom>; let muteStates: MockedObject; - function setUpPublishConnection() { + function setUpPublishConnection(): void { setupTest(); roomFactoryMock = vi.fn().mockReturnValue(fakeLivekitRoom); @@ -673,9 +671,13 @@ describe("PublishConnection", () => { } }; + // TODO understand what is wrong with our mocking that requires ts-expect-error const fakeDevices = mockMediaDevices({ + // @ts-expect-error Mocking only audioInput, + // @ts-expect-error Mocking only videoInput, + // @ts-expect-error Mocking only audioOutput }); diff --git a/src/state/MuteStates.ts b/src/state/MuteStates.ts index 07bc5665..8a025882 100644 --- a/src/state/MuteStates.ts +++ b/src/state/MuteStates.ts @@ -88,7 +88,10 @@ class MuteState { } else { subscriber.next(enabled); syncing = true; - sync(); + sync().catch((err) => { + // TODO: better error handling + logger.error("MuteState: handler error", err); + }); } } }; @@ -97,7 +100,10 @@ class MuteState { latestDesired = desired; if (syncing === false) { syncing = true; - sync(); + sync().catch((err) => { + // TODO: better error handling + logger.error("MuteState: handler error", err); + }); } }); return (): void => s.unsubscribe(); @@ -132,6 +138,7 @@ class MuteState { ) {} } +// TODO there is another MuteStates in src/room/MuteStates.tsx ?? why export class MuteStates { public readonly audio = new MuteState( this.scope, diff --git a/src/tile/MediaView.test.tsx b/src/tile/MediaView.test.tsx index 672f3334..57be00ef 100644 --- a/src/tile/MediaView.test.tsx +++ b/src/tile/MediaView.test.tsx @@ -19,7 +19,7 @@ import { type ComponentProps } from "react"; import { MediaView } from "./MediaView"; import { EncryptionStatus } from "../state/MediaViewModel"; -import { mockLocalParticipant } from "../utils/test"; +import { mockLocalParticipant, mockMatrixRoomMember, mockRtcMembership } from "../utils/test"; describe("MediaView", () => { const participant = mockLocalParticipant({}); @@ -45,7 +45,10 @@ describe("MediaView", () => { mirror: false, unencryptedWarning: false, video: trackReference, - member: undefined, + member: mockMatrixRoomMember( + mockRtcMembership("@alice:example.org", "CCCC"), + { name: "some name" }, + ), localParticipant: false, focusable: true, }; diff --git a/src/utils/test.ts b/src/utils/test.ts index 519fdd50..b77e63c0 100644 --- a/src/utils/test.ts +++ b/src/utils/test.ts @@ -6,7 +6,7 @@ Please see LICENSE in the repository root for full details. */ import { map, type Observable, of, type SchedulerLike } from "rxjs"; import { type RunHelpers, TestScheduler } from "rxjs/testing"; -import { expect, vi, vitest } from "vitest"; +import { expect, type MockedObject, vi, vitest } from "vitest"; import { type RoomMember, type Room as MatrixRoom, @@ -205,6 +205,9 @@ export function mockMatrixRoomMember( return { ...mockEmitter(), userId: rtcMembership.sender, + getMxcAvatarUrl(): string | undefined { + return undefined; + }, ...member, } as RoomMember; } @@ -416,13 +419,13 @@ export const deviceStub = { select(): void {}, }; -export function mockMediaDevices(data: Partial): MediaDevices { - return { +export function mockMediaDevices(data: Partial): MockedObject { + return vi.mocked({ audioInput: deviceStub, audioOutput: deviceStub, videoInput: deviceStub, ...data, - } as MediaDevices; + } as MediaDevices); } export function mockMuteStates(