Lint: fix all the lint errors

This commit is contained in:
Valere
2025-10-07 16:00:59 +02:00
parent 597e6782a8
commit c3c0516f0d
16 changed files with 206 additions and 97 deletions

View File

@@ -7,7 +7,6 @@ Please see LICENSE in the repository root for full details.
import { afterEach, beforeEach, expect, it, vi } from "vitest"; import { afterEach, beforeEach, expect, it, vi } from "vitest";
import { render } from "@testing-library/react"; import { render } from "@testing-library/react";
import { type CallMembership } from "matrix-js-sdk/lib/matrixrtc";
import { import {
getTrackReferenceId, getTrackReferenceId,
type TrackReference, type TrackReference,
@@ -15,11 +14,19 @@ import {
import { type RemoteAudioTrack } from "livekit-client"; import { type RemoteAudioTrack } from "livekit-client";
import { type ReactNode } from "react"; import { type ReactNode } from "react";
import { useTracks } from "@livekit/components-react"; import { useTracks } from "@livekit/components-react";
import { of } from "rxjs";
import { testAudioContext } from "../useAudioContext.test"; import { testAudioContext } from "../useAudioContext.test";
import * as MediaDevicesContext from "../MediaDevicesContext"; import * as MediaDevicesContext from "../MediaDevicesContext";
import { LivekitRoomAudioRenderer } from "./MatrixAudioRenderer"; 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); export const TestAudioContextConstructor = vi.fn(() => testAudioContext);
@@ -52,10 +59,26 @@ const tracks = [mockTrack("test:123")];
vi.mocked(useTracks).mockReturnValue(tracks); vi.mocked(useTracks).mockReturnValue(tracks);
it("should render for member", () => { 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( const { container, queryAllByTestId } = render(
<MediaDevicesProvider value={mockMediaDevices({})}> <MediaDevicesProvider value={mockMediaDevices({})}>
<LivekitRoomAudioRenderer <LivekitRoomAudioRenderer
members={[{ sender: "test", deviceId: "123" }] as CallMembership[]} participants={[p]}
livekitRoom={livekitRoom}
url={""}
/> />
</MediaDevicesProvider>, </MediaDevicesProvider>,
); );
@@ -64,12 +87,29 @@ it("should render for member", () => {
}); });
it("should not render without member", () => { it("should not render without member", () => {
const memberships = [ // const memberships = [
{ sender: "othermember", deviceId: "123" }, // { sender: "othermember", deviceId: "123" },
] as CallMembership[]; // ] 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( const { container, queryAllByTestId } = render(
<MediaDevicesProvider value={mockMediaDevices({})}> <MediaDevicesProvider value={mockMediaDevices({})}>
<LivekitRoomAudioRenderer members={memberships} /> <LivekitRoomAudioRenderer
participants={[p]}
livekitRoom={livekitRoom}
url={""}
/>
</MediaDevicesProvider>, </MediaDevicesProvider>,
); );
expect(container).toBeTruthy(); 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.", () => { 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( render(
<MediaDevicesProvider value={mockMediaDevices({})}> <MediaDevicesProvider value={mockMediaDevices({})}>
<LivekitRoomAudioRenderer <LivekitRoomAudioRenderer
members={[{ sender: "test", deviceId: "123" }] as CallMembership[]} participants={[p]}
livekitRoom={livekitRoom}
url={""}
/> />
</MediaDevicesProvider>, </MediaDevicesProvider>,
); );
@@ -100,11 +155,25 @@ it("should setup audioContext gain and pan", () => {
pan: 1, pan: 1,
volume: 0.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( render(
<MediaDevicesProvider value={mockMediaDevices({})}> <MediaDevicesProvider value={mockMediaDevices({})}>
<LivekitRoomAudioRenderer <LivekitRoomAudioRenderer
members={[{ sender: "test", deviceId: "123" }] as CallMembership[]} participants={[p]}
/> url={""}
livekitRoom={livekitRoom} />
</MediaDevicesProvider>, </MediaDevicesProvider>,
); );

View File

@@ -33,7 +33,9 @@ export interface MatrixAudioRendererProps {
* that are not expected to be in the rtc session. * that are not expected to be in the rtc session.
*/ */
participants: { 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; member: RoomMember;
}[]; }[];
/** /**
@@ -82,7 +84,7 @@ export function LivekitRoomAudioRenderer({
if (loggedInvalidIdentities.current.has(identity)) return; if (loggedInvalidIdentities.current.has(identity)) return;
logger.warn( logger.warn(
`[MatrixAudioRenderer] Audio track ${identity} from ${url} has no matching matrix call member`, `[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`, `track will not get rendered`,
); );
loggedInvalidIdentities.current.add(identity); loggedInvalidIdentities.current.add(identity);

View File

@@ -11,7 +11,6 @@ Please see LICENSE in the repository root for full details.
// dependency references. // dependency references.
import "matrix-js-sdk/lib/browser-index"; import "matrix-js-sdk/lib/browser-index";
import { StrictMode } from "react";
import { createRoot } from "react-dom/client"; import { createRoot } from "react-dom/client";
import "./index.css"; import "./index.css";
import { logger } from "matrix-js-sdk/lib/logger"; import { logger } from "matrix-js-sdk/lib/logger";

View File

@@ -155,7 +155,8 @@ test("plays one sound when a hand is raised", () => {
act(() => { act(() => {
handRaisedSubject$.next({ handRaisedSubject$.next({
[bobRtcMember.callId]: { // TODO: What is this string supposed to be?
[`${bobRtcMember.sender}:${bobRtcMember.deviceId}`]: {
time: new Date(), time: new Date(),
membershipEventId: "", membershipEventId: "",
reactionEventId: "", reactionEventId: "",

View File

@@ -26,7 +26,6 @@ import { type RelationsContainer } from "matrix-js-sdk/lib/models/relations-cont
import { useState } from "react"; import { useState } from "react";
import { TooltipProvider } from "@vector-im/compound-web"; import { TooltipProvider } from "@vector-im/compound-web";
import { type MuteStates } from "./MuteStates";
import { prefetchSounds } from "../soundUtils"; import { prefetchSounds } from "../soundUtils";
import { useAudioContext } from "../useAudioContext"; import { useAudioContext } from "../useAudioContext";
import { ActiveCall } from "./InCallView"; import { ActiveCall } from "./InCallView";
@@ -47,6 +46,7 @@ import { ProcessorProvider } from "../livekit/TrackProcessorContext";
import { MediaDevicesContext } from "../MediaDevicesContext"; import { MediaDevicesContext } from "../MediaDevicesContext";
import { HeaderStyle } from "../UrlParams"; import { HeaderStyle } from "../UrlParams";
import { constant } from "../state/Behavior"; import { constant } from "../state/Behavior";
import { type MuteStates } from "../state/MuteStates.ts";
vi.mock("../soundUtils"); vi.mock("../soundUtils");
vi.mock("../useAudioContext"); vi.mock("../useAudioContext");
@@ -150,7 +150,7 @@ function createGroupCallView(
const muteState = { const muteState = {
audio: { enabled: false }, audio: { enabled: false },
video: { enabled: false }, video: { enabled: false },
} as MuteStates; } as unknown as MuteStates;
const { getByText } = render( const { getByText } = render(
<BrowserRouter> <BrowserRouter>
<TooltipProvider> <TooltipProvider>
@@ -164,9 +164,10 @@ function createGroupCallView(
skipLobby={false} skipLobby={false}
header={HeaderStyle.Standard} header={HeaderStyle.Standard}
rtcSession={rtcSession as unknown as MatrixRTCSession} rtcSession={rtcSession as unknown as MatrixRTCSession}
isJoined={joined}
muteStates={muteState} muteStates={muteState}
widget={widget} widget={widget}
joined={true}
setJoined={function(value: boolean): void { }}
/> />
</ProcessorProvider> </ProcessorProvider>
</MediaDevicesContext> </MediaDevicesContext>

View File

@@ -24,7 +24,6 @@ import { TooltipProvider } from "@vector-im/compound-web";
import { RoomContext, useLocalParticipant } from "@livekit/components-react"; import { RoomContext, useLocalParticipant } from "@livekit/components-react";
import { RoomAndToDeviceEvents } from "matrix-js-sdk/lib/matrixrtc/RoomAndToDeviceKeyTransport"; import { RoomAndToDeviceEvents } from "matrix-js-sdk/lib/matrixrtc/RoomAndToDeviceKeyTransport";
import { type MuteStates } from "./MuteStates";
import { InCallView } from "./InCallView"; import { InCallView } from "./InCallView";
import { import {
mockLivekitRoom, mockLivekitRoom,
@@ -48,6 +47,7 @@ import { useRoomEncryptionSystem } from "../e2ee/sharedKeyManagement";
import { LivekitRoomAudioRenderer } from "../livekit/MatrixAudioRenderer"; import { LivekitRoomAudioRenderer } from "../livekit/MatrixAudioRenderer";
import { MediaDevicesContext } from "../MediaDevicesContext"; import { MediaDevicesContext } from "../MediaDevicesContext";
import { HeaderStyle } from "../UrlParams"; import { HeaderStyle } from "../UrlParams";
import { type MuteStates } from "../state/MuteStates.ts";
// vi.hoisted(() => { // vi.hoisted(() => {
// localStorage = {} as unknown as Storage; // localStorage = {} as unknown as Storage;
@@ -136,7 +136,7 @@ function createInCallView(): RenderResult & {
const muteState = { const muteState = {
audio: { enabled: false }, audio: { enabled: false },
video: { enabled: false }, video: { enabled: false },
} as MuteStates; } as unknown as MuteStates;
const livekitRoom = mockLivekitRoom( const livekitRoom = mockLivekitRoom(
{ {
localParticipant, localParticipant,
@@ -176,11 +176,6 @@ function createInCallView(): RenderResult & {
}, },
}} }}
matrixRoom={room} matrixRoom={room}
livekitRoom={livekitRoom}
participantCount={0}
onLeft={function (): void {
throw new Error("Function not implemented.");
}}
onShareClick={null} onShareClick={null}
/> />
</RoomContext> </RoomContext>

View File

@@ -23,7 +23,7 @@ import useMeasure from "react-use-measure";
import { type MatrixRTCSession } from "matrix-js-sdk/lib/matrixrtc"; import { type MatrixRTCSession } from "matrix-js-sdk/lib/matrixrtc";
import classNames from "classnames"; import classNames from "classnames";
import { BehaviorSubject, map } from "rxjs"; 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 { logger } from "matrix-js-sdk/lib/logger";
import { RoomAndToDeviceEvents } from "matrix-js-sdk/lib/matrixrtc/RoomAndToDeviceKeyTransport"; import { RoomAndToDeviceEvents } from "matrix-js-sdk/lib/matrixrtc/RoomAndToDeviceKeyTransport";
import { import {
@@ -112,7 +112,6 @@ import { prefetchSounds } from "../soundUtils";
import { useAudioContext } from "../useAudioContext"; import { useAudioContext } from "../useAudioContext";
import ringtoneMp3 from "../sound/ringtone.mp3?url"; import ringtoneMp3 from "../sound/ringtone.mp3?url";
import ringtoneOgg from "../sound/ringtone.ogg?url"; import ringtoneOgg from "../sound/ringtone.ogg?url";
import { ConnectionLostError } from "../utils/errors.ts";
import { useTrackProcessorObservable$ } from "../livekit/TrackProcessorContext.tsx"; import { useTrackProcessorObservable$ } from "../livekit/TrackProcessorContext.tsx";
const maxTapDurationMs = 400; const maxTapDurationMs = 400;
@@ -206,7 +205,8 @@ export const InCallView: FC<InCallViewProps> = ({
useReactionsSender(); useReactionsSender();
useWakeLock(); 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, // annoyingly we don't get the disconnection reason this way,
// only by listening for the emitted event // only by listening for the emitted event

View File

@@ -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. 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 { render } from "@testing-library/react";
import { type MatrixInfo, VideoPreview } from "./VideoPreview"; import { type MatrixInfo, VideoPreview } from "./VideoPreview";
import { E2eeType } from "../e2ee/e2eeType"; import { E2eeType } from "../e2ee/e2eeType";
import { mockMuteStates } from "../utils/test";
describe("VideoPreview", () => { describe("VideoPreview", () => {
const matrixInfo: MatrixInfo = { const matrixInfo: MatrixInfo = {
@@ -42,7 +41,7 @@ describe("VideoPreview", () => {
const { queryByRole } = render( const { queryByRole } = render(
<VideoPreview <VideoPreview
matrixInfo={matrixInfo} matrixInfo={matrixInfo}
muteStates={mockMuteStates()} videoEnabled={true}
videoTrack={null} videoTrack={null}
children={<></>} children={<></>}
/>, />,
@@ -54,7 +53,7 @@ describe("VideoPreview", () => {
const { queryByRole } = render( const { queryByRole } = render(
<VideoPreview <VideoPreview
matrixInfo={matrixInfo} matrixInfo={matrixInfo}
muteStates={mockMuteStates()} videoEnabled={true}
videoTrack={null} videoTrack={null}
children={<></>} children={<></>}
/>, />,

View File

@@ -23,37 +23,38 @@ vi.mock("./widget", () => ({
...actualWidget, ...actualWidget,
widget: { widget: {
api: { api: {
setAlwaysOnScreen: (): void => {}, setAlwaysOnScreen: (): void => {
transport: { send: vi.fn(), reply: vi.fn(), stop: vi.fn() }, },
transport: { send: vi.fn(), reply: vi.fn(), stop: vi.fn() }
}, },
lazyActions: new EventEmitter(), lazyActions: new EventEmitter()
}, }
})); }));
test("It joins the correct Session", async () => { test("It joins the correct Session", async () => {
const focusFromOlderMembership = { const focusFromOlderMembership = {
type: "livekit", type: "livekit",
livekit_service_url: "http://my-oldest-member-service-url.com", 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 = { const focusConfigFromWellKnown = {
type: "livekit", type: "livekit",
livekit_service_url: "http://my-well-known-service-url.com", livekit_service_url: "http://my-well-known-service-url.com"
}; };
const focusConfigFromWellKnown2 = { const focusConfigFromWellKnown2 = {
type: "livekit", type: "livekit",
livekit_service_url: "http://my-well-known-service-url2.com", livekit_service_url: "http://my-well-known-service-url2.com"
}; };
const clientWellKnown = { const clientWellKnown = {
"org.matrix.msc4143.rtc_foci": [ "org.matrix.msc4143.rtc_foci": [
focusConfigFromWellKnown, focusConfigFromWellKnown,
focusConfigFromWellKnown2, focusConfigFromWellKnown2
], ]
}; };
mockConfig({ 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( vi.spyOn(AutoDiscovery, "getRawClientConfig").mockImplementation(
@@ -62,7 +63,7 @@ test("It joins the correct Session", async () => {
return Promise.resolve(clientWellKnown); return Promise.resolve(clientWellKnown);
} }
return Promise.resolve({}); return Promise.resolve({});
}, }
); );
const mockedSession = vi.mocked({ const mockedSession = vi.mocked({
@@ -74,58 +75,64 @@ test("It joins the correct Session", async () => {
access_token: "ACCCESS_TOKEN", access_token: "ACCCESS_TOKEN",
token_type: "Bearer", token_type: "Bearer",
matrix_server_name: "localhost", matrix_server_name: "localhost",
expires_in: 10000, expires_in: 10000
}), })
}, }
}, },
memberships: [], memberships: [],
getFocusInUse: vi.fn().mockReturnValue(focusFromOlderMembership), getFocusInUse: vi.fn().mockReturnValue(focusFromOlderMembership),
getOldestMembership: vi.fn().mockReturnValue({ getOldestMembership: vi.fn().mockReturnValue({
getPreferredFoci: vi.fn().mockReturnValue([focusFromOlderMembership]), getPreferredFoci: vi.fn().mockReturnValue([focusFromOlderMembership])
}), }),
joinRoomSession: vi.fn(), joinRoomSession: vi.fn()
}) as unknown as MatrixRTCSession; }) 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( expect(mockedSession.joinRoomSession).toHaveBeenLastCalledWith(
[ [
{ {
livekit_alias: "my-oldest-member-service-alias", livekit_alias: "my-oldest-member-service-alias",
livekit_service_url: "http://my-oldest-member-service-url.com", livekit_service_url: "http://my-oldest-member-service-url.com",
type: "livekit", type: "livekit"
}, },
{ {
livekit_alias: "roomId", livekit_alias: "roomId",
livekit_service_url: "http://my-well-known-service-url.com", livekit_service_url: "http://my-well-known-service-url.com",
type: "livekit", type: "livekit"
}, },
{ {
livekit_alias: "roomId", livekit_alias: "roomId",
livekit_service_url: "http://my-well-known-service-url2.com", livekit_service_url: "http://my-well-known-service-url2.com",
type: "livekit", type: "livekit"
}, },
{ {
livekit_alias: "roomId", livekit_alias: "roomId",
livekit_service_url: "http://my-default-service-url.com", livekit_service_url: "http://my-default-service-url.com",
type: "livekit", type: "livekit"
}, }
], ],
{ {
focus_selection: "oldest_membership", focus_selection: "oldest_membership",
type: "livekit", type: "livekit"
}, },
{ {
manageMediaKeys: false, manageMediaKeys: false,
useLegacyMemberEvents: false, useLegacyMemberEvents: false,
useNewMembershipManager: true, useNewMembershipManager: true,
useExperimentalToDeviceTransport: false, useExperimentalToDeviceTransport: false
}, }
); );
}); });
async function testLeaveRTCSession( async function testLeaveRTCSession(
cause: "user" | "error", cause: "user" | "error",
expectClose: boolean, expectClose: boolean
): Promise<void> { ): Promise<void> {
vi.clearAllMocks(); vi.clearAllMocks();
const session = { leaveRoomSession: vi.fn() } as unknown as MatrixRTCSession; const session = { leaveRoomSession: vi.fn() } as unknown as MatrixRTCSession;
@@ -133,18 +140,18 @@ async function testLeaveRTCSession(
expect(session.leaveRoomSession).toHaveBeenCalled(); expect(session.leaveRoomSession).toHaveBeenCalled();
expect(widget!.api.transport.send).toHaveBeenCalledWith( expect(widget!.api.transport.send).toHaveBeenCalledWith(
ElementWidgetActions.HangupCall, ElementWidgetActions.HangupCall,
expect.anything(), expect.anything()
); );
if (expectClose) { if (expectClose) {
expect(widget!.api.transport.send).toHaveBeenCalledWith( expect(widget!.api.transport.send).toHaveBeenCalledWith(
ElementWidgetActions.Close, ElementWidgetActions.Close,
expect.anything(), expect.anything()
); );
expect(widget!.api.transport.stop).toHaveBeenCalled(); expect(widget!.api.transport.stop).toHaveBeenCalled();
} else { } else {
expect(widget!.api.transport.send).not.toHaveBeenCalledWith( expect(widget!.api.transport.send).not.toHaveBeenCalledWith(
ElementWidgetActions.Close, ElementWidgetActions.Close,
expect.anything(), expect.anything()
); );
expect(widget!.api.transport.stop).not.toHaveBeenCalled(); 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: { room: {
roomId: "roomId", roomId: "roomId",
client: { client: {
getDomain: vi.fn().mockReturnValue("example.org"), getDomain: vi.fn().mockReturnValue("example.org")
}, }
}, },
memberships: [], memberships: [],
getFocusInUse: vi.fn(), getFocusInUse: vi.fn(),
joinRoomSession: vi.fn(), joinRoomSession: vi.fn()
}) as unknown as MatrixRTCSession; }) as unknown as MatrixRTCSession;
await expect(enterRTCSession(mockedSession, false)).rejects.toThrowError( await expect(enterRTCSession(
expect.objectContaining({ code: ErrorCode.MISSING_MATRIX_RTC_FOCUS }), 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": [ "org.matrix.msc4143.rtc_foci": [
{ {
type: "livekit", 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({ 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", access_token: "ACCCESS_TOKEN",
token_type: "Bearer", token_type: "Bearer",
matrix_server_name: "localhost", matrix_server_name: "localhost",
expires_in: 10000, expires_in: 10000
}), })
}, }
}, },
memberships: [], memberships: [],
getFocusInUse: vi.fn(), getFocusInUse: vi.fn(),
joinRoomSession: vi.fn(), joinRoomSession: vi.fn()
}) as unknown as MatrixRTCSession; }) 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);
}); });

View File

@@ -9,12 +9,13 @@ import {
catchError, catchError,
from, from,
map, map,
Observable, type Observable,
of, of,
startWith, startWith
switchMap,
} from "rxjs"; } 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<A> = export type Async<A> =
| { state: "loading" } | { state: "loading" }
| { state: "error"; value: Error } | { state: "error"; value: Error }
@@ -24,21 +25,22 @@ export const loading: Async<never> = { state: "loading" };
export function error(value: Error): Async<never> { export function error(value: Error): Async<never> {
return { state: "error", value }; return { state: "error", value };
} }
export function ready<A>(value: A): Async<A> { export function ready<A>(value: A): Async<A> {
return { state: "ready", value }; return { state: "ready", value };
} }
export function async<A>(promise: Promise<A>): Observable<Async<A>> { export function async$<A>(promise: Promise<A>): Observable<Async<A>> {
return from(promise).pipe( return from(promise).pipe(
map(ready), map(ready),
startWith(loading), startWith(loading),
catchError((e) => of(error(e))), catchError((e: unknown) => of(error(e as Error ?? new Error("Unknown error")))),
); );
} }
export function mapAsync<A, B>( export function mapAsync<A, B>(
async: Async<A>, async: Async<A>,
project: (value: A) => B, project: (value: A) => B
): Async<B> { ): Async<B> {
return async.state === "ready" ? ready(project(async.value)) : async; return async.state === "ready" ? ready(project(async.value)) : async;
} }

View File

@@ -68,7 +68,7 @@ import {
type ECConnectionState, type ECConnectionState,
} from "../livekit/useECConnectionState"; } from "../livekit/useECConnectionState";
import { E2eeType } from "../e2ee/e2eeType"; import { E2eeType } from "../e2ee/e2eeType";
import type { RaisedHandInfo } from "../reactions"; import type { RaisedHandInfo, ReactionInfo } from "../reactions";
import { import {
alice, alice,
aliceDoppelganger, aliceDoppelganger,
@@ -95,6 +95,7 @@ import { ObservableScope } from "./ObservableScope";
import { MediaDevices } from "./MediaDevices"; import { MediaDevices } from "./MediaDevices";
import { getValue } from "../utils/observable"; import { getValue } from "../utils/observable";
import { type Behavior, constant } from "./Behavior"; import { type Behavior, constant } from "./Behavior";
import type { ProcessorState } from "../livekit/TrackProcessorContext.tsx";
const getUrlParams = vi.hoisted(() => vi.fn(() => ({}))); const getUrlParams = vi.hoisted(() => vi.fn(() => ({})));
vi.mock("../UrlParams", () => ({ getUrlParams })); vi.mock("../UrlParams", () => ({ getUrlParams }));
@@ -341,6 +342,7 @@ function withCallViewModel(
.mockImplementation((_room, _eventType) => of()); .mockImplementation((_room, _eventType) => of());
const muteStates = mockMuteStates(); const muteStates = mockMuteStates();
const raisedHands$ = new BehaviorSubject<Record<string, RaisedHandInfo>>({}); const raisedHands$ = new BehaviorSubject<Record<string, RaisedHandInfo>>({});
const reactions$ = new BehaviorSubject<Record<string, ReactionInfo>>({});
const vm = new CallViewModel( const vm = new CallViewModel(
rtcSession as unknown as MatrixRTCSession, rtcSession as unknown as MatrixRTCSession,
@@ -349,7 +351,8 @@ function withCallViewModel(
muteStates, muteStates,
options, options,
raisedHands$, raisedHands$,
new BehaviorSubject({}), reactions$,
new BehaviorSubject<ProcessorState>({ processor: undefined, supported: undefined }),
); );
onTestFinished(() => { onTestFinished(() => {

View File

@@ -132,7 +132,7 @@ import { getUrlParams } from "../UrlParams";
import { type ProcessorState } from "../livekit/TrackProcessorContext"; import { type ProcessorState } from "../livekit/TrackProcessorContext";
import { ElementWidgetActions, widget } from "../widget"; import { ElementWidgetActions, widget } from "../widget";
import { PublishConnection } from "./PublishConnection.ts"; import { PublishConnection } from "./PublishConnection.ts";
import { type Async, async, mapAsync, ready } from "./Async"; import { type Async, async$, mapAsync, ready } from "./Async";
export interface CallViewModelOptions { export interface CallViewModelOptions {
encryptionSystem: EncryptionSystem; encryptionSystem: EncryptionSystem;
@@ -520,7 +520,7 @@ export class CallViewModel extends ViewModel {
joined joined
? combineLatest( ? combineLatest(
[ [
async(this.preferredTransport), async$(this.preferredTransport),
this.memberships$, this.memberships$,
multiSfu.value$, multiSfu.value$,
], ],
@@ -1953,7 +1953,10 @@ export class CallViewModel extends ViewModel {
.subscribe(({ start, stop }) => { .subscribe(({ start, stop }) => {
for (const c of stop) { for (const c of stop) {
logger.info(`Disconnecting from ${c.localTransport.livekit_service_url}`); 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) { for (const c of start) {
c.start().then( c.start().then(

View File

@@ -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 { 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 { BehaviorSubject, of } from "rxjs";
import { import {
ConnectionState, ConnectionState,
@@ -18,8 +17,8 @@ import {
import fetchMock from "fetch-mock"; import fetchMock from "fetch-mock";
import EventEmitter from "events"; import EventEmitter from "events";
import { type IOpenIDToken } from "matrix-js-sdk"; 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 { type ConnectionOpts, type FocusConnectionState, RemoteConnection } from "./Connection.ts";
import { ObservableScope } from "./ObservableScope.ts"; import { ObservableScope } from "./ObservableScope.ts";
import { type OpenIDClientParts } from "../livekit/openIDSFU.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 { ProcessorState } from "../livekit/TrackProcessorContext.tsx";
import { type MuteStates } from "./MuteStates.ts"; import { type MuteStates } from "./MuteStates.ts";
let testScope: ObservableScope; let testScope: ObservableScope;
let client: MockedObject<OpenIDClientParts>; let client: MockedObject<OpenIDClientParts>;
@@ -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(); setupTest();
const connection = setupRemoteConnection(); const connection = setupRemoteConnection();
@@ -613,7 +611,7 @@ describe("PublishConnection", () => {
let roomFactoryMock: Mock<() => LivekitRoom>; let roomFactoryMock: Mock<() => LivekitRoom>;
let muteStates: MockedObject<MuteStates>; let muteStates: MockedObject<MuteStates>;
function setUpPublishConnection() { function setUpPublishConnection(): void {
setupTest(); setupTest();
roomFactoryMock = vi.fn().mockReturnValue(fakeLivekitRoom); 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({ const fakeDevices = mockMediaDevices({
// @ts-expect-error Mocking only
audioInput, audioInput,
// @ts-expect-error Mocking only
videoInput, videoInput,
// @ts-expect-error Mocking only
audioOutput audioOutput
}); });

View File

@@ -88,7 +88,10 @@ class MuteState<Label, Selected> {
} else { } else {
subscriber.next(enabled); subscriber.next(enabled);
syncing = true; syncing = true;
sync(); sync().catch((err) => {
// TODO: better error handling
logger.error("MuteState: handler error", err);
});
} }
} }
}; };
@@ -97,7 +100,10 @@ class MuteState<Label, Selected> {
latestDesired = desired; latestDesired = desired;
if (syncing === false) { if (syncing === false) {
syncing = true; syncing = true;
sync(); sync().catch((err) => {
// TODO: better error handling
logger.error("MuteState: handler error", err);
});
} }
}); });
return (): void => s.unsubscribe(); return (): void => s.unsubscribe();
@@ -132,6 +138,7 @@ class MuteState<Label, Selected> {
) {} ) {}
} }
// TODO there is another MuteStates in src/room/MuteStates.tsx ?? why
export class MuteStates { export class MuteStates {
public readonly audio = new MuteState( public readonly audio = new MuteState(
this.scope, this.scope,

View File

@@ -19,7 +19,7 @@ import { type ComponentProps } from "react";
import { MediaView } from "./MediaView"; import { MediaView } from "./MediaView";
import { EncryptionStatus } from "../state/MediaViewModel"; import { EncryptionStatus } from "../state/MediaViewModel";
import { mockLocalParticipant } from "../utils/test"; import { mockLocalParticipant, mockMatrixRoomMember, mockRtcMembership } from "../utils/test";
describe("MediaView", () => { describe("MediaView", () => {
const participant = mockLocalParticipant({}); const participant = mockLocalParticipant({});
@@ -45,7 +45,10 @@ describe("MediaView", () => {
mirror: false, mirror: false,
unencryptedWarning: false, unencryptedWarning: false,
video: trackReference, video: trackReference,
member: undefined, member: mockMatrixRoomMember(
mockRtcMembership("@alice:example.org", "CCCC"),
{ name: "some name" },
),
localParticipant: false, localParticipant: false,
focusable: true, focusable: true,
}; };

View File

@@ -6,7 +6,7 @@ Please see LICENSE in the repository root for full details.
*/ */
import { map, type Observable, of, type SchedulerLike } from "rxjs"; import { map, type Observable, of, type SchedulerLike } from "rxjs";
import { type RunHelpers, TestScheduler } from "rxjs/testing"; import { type RunHelpers, TestScheduler } from "rxjs/testing";
import { expect, vi, vitest } from "vitest"; import { expect, type MockedObject, vi, vitest } from "vitest";
import { import {
type RoomMember, type RoomMember,
type Room as MatrixRoom, type Room as MatrixRoom,
@@ -205,6 +205,9 @@ export function mockMatrixRoomMember(
return { return {
...mockEmitter(), ...mockEmitter(),
userId: rtcMembership.sender, userId: rtcMembership.sender,
getMxcAvatarUrl(): string | undefined {
return undefined;
},
...member, ...member,
} as RoomMember; } as RoomMember;
} }
@@ -416,13 +419,13 @@ export const deviceStub = {
select(): void {}, select(): void {},
}; };
export function mockMediaDevices(data: Partial<MediaDevices>): MediaDevices { export function mockMediaDevices(data: Partial<MediaDevices>): MockedObject<MediaDevices> {
return { return vi.mocked<MediaDevices>({
audioInput: deviceStub, audioInput: deviceStub,
audioOutput: deviceStub, audioOutput: deviceStub,
videoInput: deviceStub, videoInput: deviceStub,
...data, ...data,
} as MediaDevices; } as MediaDevices);
} }
export function mockMuteStates( export function mockMuteStates(