review. Dont pass full vm to developer settings

This commit is contained in:
Timo K.
2026-08-28 15:20:49 +02:00
parent 06b9105cee
commit 1146d3820a
8 changed files with 187 additions and 43 deletions

View File

@@ -122,12 +122,13 @@ function createInCallView(args: CreateInCallViewArgs = {}): RenderResult & {
remoteParticipants$: of([remoteParticipant]), remoteParticipants$: of([remoteParticipant]),
}, },
); );
const { vm, footerVm, rtcSession } = getBasicCallViewModelEnvironment( const { vm, footerVm, developerSettingsVm, rtcSession } =
[local, alice], getBasicCallViewModelEnvironment(
undefined, [local, alice],
mediaDevices, undefined,
args.callViewModelOptions, mediaDevices,
); args.callViewModelOptions,
);
rtcSession.joined = true; rtcSession.joined = true;
const room = rtcSession.room; const room = rtcSession.room;
@@ -140,6 +141,7 @@ function createInCallView(args: CreateInCallViewArgs = {}): RenderResult & {
muteStates={muteState} muteStates={muteState}
vm={vm} vm={vm}
footerVm={footerVm} footerVm={footerVm}
developerSettingsVm={developerSettingsVm}
matrixInfo={{ matrixInfo={{
userId: "", userId: "",
displayName: "", displayName: "",

View File

@@ -83,6 +83,8 @@ import { ObservableScope } from "../state/ObservableScope.ts";
import { CallFooter, type FooterSnapshot } from "../components/CallFooter.tsx"; import { CallFooter, type FooterSnapshot } from "../components/CallFooter.tsx";
import { SettingsIconButton } from "../button/Button.tsx"; import { SettingsIconButton } from "../button/Button.tsx";
import { createCallFooterViewModel } from "../components/CallFooterViewModel.tsx"; import { createCallFooterViewModel } from "../components/CallFooterViewModel.tsx";
import { createDeveloperSettingsTabViewModel } from "../settings/DeveloperSettingsTabViewModel.ts";
import { type DeveloperSettingsSnapshot } from "../settings/DeveloperSettingsTab.tsx";
import { type ViewModel } from "../state/ViewModel.ts"; import { type ViewModel } from "../state/ViewModel.ts";
import { RingingStatus } from "../tile/RingingStatus.tsx"; import { RingingStatus } from "../tile/RingingStatus.tsx";
import { RingingAudioRenderer } from "./RingingAudioRenderer.tsx"; import { RingingAudioRenderer } from "./RingingAudioRenderer.tsx";
@@ -96,7 +98,7 @@ declare module "react" {
export interface ActiveCallProps extends Omit< export interface ActiveCallProps extends Omit<
InCallViewProps, InCallViewProps,
"vm" | "livekitRoom" | "connState" | "footerVm" "vm" | "livekitRoom" | "connState" | "footerVm" | "developerSettingsVm"
> { > {
e2eeSystem: EncryptionSystem; e2eeSystem: EncryptionSystem;
// TODO refactor those reasons into an enum // TODO refactor those reasons into an enum
@@ -110,6 +112,9 @@ export const ActiveCall: FC<ActiveCallProps> = (props) => {
const [footerVm, setFooterVm] = useState<ViewModel<FooterSnapshot> | null>( const [footerVm, setFooterVm] = useState<ViewModel<FooterSnapshot> | null>(
null, null,
); );
const [developerSettingsVm, setDeveloperSettingsVm] =
useState<ViewModel<DeveloperSettingsSnapshot> | null>(null);
const urlParams = useUrlParams(); const urlParams = useUrlParams();
const mediaDevices = useMediaDevices(); const mediaDevices = useMediaDevices();
const trackProcessorState$ = useTrackProcessorObservable$(); const trackProcessorState$ = useTrackProcessorObservable$();
@@ -168,7 +173,9 @@ export const ActiveCall: FC<ActiveCallProps> = (props) => {
mediaDevices, mediaDevices,
`${props.client.getUserId()}:${props.client.getDeviceId()}`, `${props.client.getUserId()}:${props.client.getDeviceId()}`,
); );
const developerSettingsVm = createDeveloperSettingsTabViewModel(scope, vm);
setFooterVm(footerVm); setFooterVm(footerVm);
setDeveloperSettingsVm(developerSettingsVm);
return (): void => { return (): void => {
scope.end(); scope.end();
@@ -188,10 +195,16 @@ export const ActiveCall: FC<ActiveCallProps> = (props) => {
if (vm === null) return null; if (vm === null) return null;
if (footerVm === null) return null; if (footerVm === null) return null;
if (developerSettingsVm === null) return null;
return ( return (
<ReactionsSenderProvider vm={vm} rtcSession={props.rtcSession}> <ReactionsSenderProvider vm={vm} rtcSession={props.rtcSession}>
<InCallView {...props} vm={vm} footerVm={footerVm} /> <InCallView
{...props}
vm={vm}
footerVm={footerVm}
developerSettingsVm={developerSettingsVm}
/>
</ReactionsSenderProvider> </ReactionsSenderProvider>
); );
}; };
@@ -200,6 +213,7 @@ export interface InCallViewProps {
client: MatrixClient; client: MatrixClient;
vm: CallViewModel; vm: CallViewModel;
footerVm: ViewModel<FooterSnapshot>; footerVm: ViewModel<FooterSnapshot>;
developerSettingsVm: ViewModel<DeveloperSettingsSnapshot>;
matrixInfo: MatrixInfo; matrixInfo: MatrixInfo;
rtcSession: MatrixRTCSession; rtcSession: MatrixRTCSession;
matrixRoom: MatrixRoom; matrixRoom: MatrixRoom;
@@ -211,6 +225,7 @@ export const InCallView: FC<InCallViewProps> = ({
client, client,
vm, vm,
footerVm, footerVm,
developerSettingsVm,
matrixInfo, matrixInfo,
matrixRoom, matrixRoom,
muteStates, muteStates,
@@ -632,7 +647,7 @@ export const InCallView: FC<InCallViewProps> = ({
onDismiss={(): void => setSettingsOpen(false)} onDismiss={(): void => setSettingsOpen(false)}
tab={settingsTab} tab={settingsTab}
onTabChange={setSettingsTab} onTabChange={setSettingsTab}
vm={vm} developerSettingsVm={developerSettingsVm}
livekitRooms={allConnections livekitRooms={allConnections
.getConnections() .getConnections()
.map((connectionItem) => ({ .map((connectionItem) => ({

View File

@@ -9,11 +9,15 @@ import { afterEach, describe, expect, it, type Mock, vi } from "vitest";
import { render, waitFor, screen } from "@testing-library/react"; import { render, waitFor, screen } from "@testing-library/react";
import userEvent from "@testing-library/user-event"; import userEvent from "@testing-library/user-event";
import { TooltipProvider } from "@vector-im/compound-web"; import { TooltipProvider } from "@vector-im/compound-web";
import { BehaviorSubject } from "rxjs";
import type { MatrixClient } from "matrix-js-sdk"; import type { MatrixClient } from "matrix-js-sdk";
import type { Room as LivekitRoom } from "livekit-client"; import type { Room as LivekitRoom } from "livekit-client";
import { DeveloperSettingsTab } from "./DeveloperSettingsTab"; import {
DeveloperSettingsTab,
type DeveloperSettingsSnapshot,
} from "./DeveloperSettingsTab";
import { outOfCallDeveloperSettingsTabViewModel } from "./DeveloperSettingsTabViewModel";
import { createStaticViewModel } from "../state/ViewModel";
import { getSFUConfigWithOpenID } from "../livekit/openIDSFU"; import { getSFUConfigWithOpenID } from "../livekit/openIDSFU";
import { import {
customLivekitUrl as customLivekitUrlSetting, customLivekitUrl as customLivekitUrlSetting,
@@ -108,6 +112,7 @@ describe("DeveloperSettingsTab", () => {
roomId={"#room:example.org"} roomId={"#room:example.org"}
livekitRooms={livekitRooms} livekitRooms={livekitRooms}
env={{ MY_MOCK_ENV: 10, ENV: "test" } as unknown as ImportMetaEnv} env={{ MY_MOCK_ENV: 10, ENV: "test" } as unknown as ImportMetaEnv}
vm={outOfCallDeveloperSettingsTabViewModel}
/>, />,
); );
@@ -137,6 +142,7 @@ describe("DeveloperSettingsTab", () => {
<DeveloperSettingsTab <DeveloperSettingsTab
client={client} client={client}
env={{} as unknown as ImportMetaEnv} env={{} as unknown as ImportMetaEnv}
vm={outOfCallDeveloperSettingsTabViewModel}
/> />
</TooltipProvider>, </TooltipProvider>,
); );
@@ -160,6 +166,7 @@ describe("DeveloperSettingsTab", () => {
client={client} client={client}
roomId="#testRoom" roomId="#testRoom"
env={{} as unknown as ImportMetaEnv} env={{} as unknown as ImportMetaEnv}
vm={outOfCallDeveloperSettingsTabViewModel}
/> />
</TooltipProvider>, </TooltipProvider>,
); );
@@ -182,6 +189,7 @@ describe("DeveloperSettingsTab", () => {
client={client} client={client}
roomId="#testRoom" roomId="#testRoom"
env={{} as unknown as ImportMetaEnv} env={{} as unknown as ImportMetaEnv}
vm={outOfCallDeveloperSettingsTabViewModel}
/> />
</TooltipProvider>, </TooltipProvider>,
); );
@@ -207,6 +215,7 @@ describe("DeveloperSettingsTab", () => {
client={client} client={client}
roomId="#testRoom" roomId="#testRoom"
env={{} as unknown as ImportMetaEnv} env={{} as unknown as ImportMetaEnv}
vm={outOfCallDeveloperSettingsTabViewModel}
/> />
</TooltipProvider>, </TooltipProvider>,
); );
@@ -237,6 +246,7 @@ describe("DeveloperSettingsTab", () => {
client={client} client={client}
roomId="#testRoom" roomId="#testRoom"
env={{} as unknown as ImportMetaEnv} env={{} as unknown as ImportMetaEnv}
vm={outOfCallDeveloperSettingsTabViewModel}
/> />
</TooltipProvider>, </TooltipProvider>,
); );
@@ -274,6 +284,7 @@ describe("DeveloperSettingsTab", () => {
<DeveloperSettingsTab <DeveloperSettingsTab
client={client} client={client}
env={{} as unknown as ImportMetaEnv} env={{} as unknown as ImportMetaEnv}
vm={outOfCallDeveloperSettingsTabViewModel}
/> />
</TooltipProvider>, </TooltipProvider>,
); );
@@ -306,6 +317,7 @@ describe("DeveloperSettingsTab", () => {
<DeveloperSettingsTab <DeveloperSettingsTab
client={client} client={client}
env={{} as unknown as ImportMetaEnv} env={{} as unknown as ImportMetaEnv}
vm={outOfCallDeveloperSettingsTabViewModel}
/> />
</TooltipProvider>, </TooltipProvider>,
); );
@@ -350,6 +362,7 @@ describe("DeveloperSettingsTab", () => {
<DeveloperSettingsTab <DeveloperSettingsTab
client={client} client={client}
env={{} as unknown as ImportMetaEnv} env={{} as unknown as ImportMetaEnv}
vm={outOfCallDeveloperSettingsTabViewModel}
/> />
</TooltipProvider>, </TooltipProvider>,
); );
@@ -389,6 +402,7 @@ describe("DeveloperSettingsTab", () => {
<DeveloperSettingsTab <DeveloperSettingsTab
client={client} client={client}
env={{} as unknown as ImportMetaEnv} env={{} as unknown as ImportMetaEnv}
vm={outOfCallDeveloperSettingsTabViewModel}
/> />
</TooltipProvider>, </TooltipProvider>,
); );
@@ -417,17 +431,16 @@ describe("DeveloperSettingsTab", () => {
describe("KeyRotationStatus", () => { describe("KeyRotationStatus", () => {
it("displays active status when key rotation is not suppressed", async () => { it("displays active status when key rotation is not suppressed", async () => {
const client = createMockMatrixClient(); const client = createMockMatrixClient();
const mockVm = { const vm = createStaticViewModel<DeveloperSettingsSnapshot>({
keyRotationSuppressed$: new BehaviorSubject(false), keyRotation: { suppressed: false, participantCount: 5 },
participantCount$: new BehaviorSubject(5), });
} as unknown as any;
render( render(
<TooltipProvider> <TooltipProvider>
<DeveloperSettingsTab <DeveloperSettingsTab
client={client} client={client}
env={{} as unknown as ImportMetaEnv} env={{} as unknown as ImportMetaEnv}
vm={mockVm} vm={vm}
/> />
</TooltipProvider>, </TooltipProvider>,
); );
@@ -443,17 +456,16 @@ describe("DeveloperSettingsTab", () => {
it("displays suppressed status when key rotation is suppressed", async () => { it("displays suppressed status when key rotation is suppressed", async () => {
const client = createMockMatrixClient(); const client = createMockMatrixClient();
const mockVm = { const vm = createStaticViewModel<DeveloperSettingsSnapshot>({
keyRotationSuppressed$: new BehaviorSubject(true), keyRotation: { suppressed: true, participantCount: 50 },
participantCount$: new BehaviorSubject(50), });
} as unknown as any;
render( render(
<TooltipProvider> <TooltipProvider>
<DeveloperSettingsTab <DeveloperSettingsTab
client={client} client={client}
env={{} as unknown as ImportMetaEnv} env={{} as unknown as ImportMetaEnv}
vm={mockVm} vm={vm}
/> />
</TooltipProvider>, </TooltipProvider>,
); );
@@ -469,7 +481,7 @@ describe("DeveloperSettingsTab", () => {
).toBeInTheDocument(); ).toBeInTheDocument();
}); });
it("does not render KeyRotationStatus when vm is not provided", async () => { it("does not render KeyRotationStatus when not in a call", async () => {
const client = createMockMatrixClient(); const client = createMockMatrixClient();
render( render(
@@ -477,6 +489,7 @@ describe("DeveloperSettingsTab", () => {
<DeveloperSettingsTab <DeveloperSettingsTab
client={client} client={client}
env={{} as unknown as ImportMetaEnv} env={{} as unknown as ImportMetaEnv}
vm={outOfCallDeveloperSettingsTabViewModel}
/> />
</TooltipProvider>, </TooltipProvider>,
); );

View File

@@ -67,25 +67,40 @@ import styles from "./DeveloperSettingsTab.module.css";
import settingsStyles from "./SettingsModal.module.css"; import settingsStyles from "./SettingsModal.module.css";
import { Slider } from "../Slider"; import { Slider } from "../Slider";
import { useUrlParams } from "../UrlParams"; import { useUrlParams } from "../UrlParams";
import { type CallViewModel } from "../state/CallViewModel/CallViewModel.ts";
import { getSFUConfigWithOpenID } from "../livekit/openIDSFU"; import { getSFUConfigWithOpenID } from "../livekit/openIDSFU";
import { useBehavior } from "../useBehavior"; import { useBehavior } from "../useBehavior";
import { type ViewModel } from "../state/ViewModel.ts";
/**
* The state of MatrixRTC's media key rotation.
*/
export interface KeyRotationInfo {
/** Whether the call is large enough that MatrixRTC has stopped rotating the media key. */
suppressed: boolean;
participantCount: number;
}
/**
* The Snapshot combines all fields the developer settings tab needs from the
* surrounding call. Everything else in this tab is read from the settings store
* or the environment directly.
*/
export interface DeveloperSettingsSnapshot {
/** The media key rotation state, or `null` when we are not in a call. */
keyRotation: KeyRotationInfo | null;
}
/** /**
* Shows whether the call is large enough that MatrixRTC has stopped rotating the media key. * Shows whether the call is large enough that MatrixRTC has stopped rotating the media key.
*/ */
const KeyRotationStatus: FC<{ vm: CallViewModel }> = ({ vm }) => { const KeyRotationStatus: FC<{ info: KeyRotationInfo }> = ({ info }) => (
const suppressed = useBehavior(vm.keyRotationSuppressed$); <p>
const participantCount = useBehavior(vm.participantCount$); Media key rotation:{" "}
return ( {info.suppressed
<p> ? `suppressed, participant limit reached (${info.participantCount} participants)`
Media key rotation:{" "} : `active (${info.participantCount} participants)`}
{suppressed </p>
? `suppressed, participant limit reached (${participantCount} participants)` );
: `active (${participantCount} participants)`}
</p>
);
};
interface Props { interface Props {
client: MatrixClient; client: MatrixClient;
@@ -97,8 +112,7 @@ interface Props {
livekitAlias?: string; livekitAlias?: string;
}[]; }[];
env: ImportMetaEnv; env: ImportMetaEnv;
/** Only available while in a call. */ vm: ViewModel<DeveloperSettingsSnapshot>;
vm?: CallViewModel;
} }
export const DeveloperSettingsTab: FC<Props> = ({ export const DeveloperSettingsTab: FC<Props> = ({
@@ -109,6 +123,7 @@ export const DeveloperSettingsTab: FC<Props> = ({
vm, vm,
}) => { }) => {
const { t } = useTranslation(); const { t } = useTranslation();
const keyRotation = useBehavior(vm.keyRotation$);
const [duplicateTiles, setDuplicateTiles] = useSetting(duplicateTilesSetting); const [duplicateTiles, setDuplicateTiles] = useSetting(duplicateTilesSetting);
const [debugTileLayout, setDebugTileLayout] = useSetting( const [debugTileLayout, setDebugTileLayout] = useSetting(
debugTileLayoutSetting, debugTileLayoutSetting,
@@ -385,7 +400,7 @@ export const DeveloperSettingsTab: FC<Props> = ({
id: client.getDeviceId() || "unknown", id: client.getDeviceId() || "unknown",
})} })}
</p> </p>
{vm && <KeyRotationStatus vm={vm} />} {keyRotation !== null && <KeyRotationStatus info={keyRotation} />}
<Separator /> <Separator />
<FieldRow> <FieldRow>
<InputField <InputField

View File

@@ -0,0 +1,48 @@
/*
Copyright 2026 Element Creations Ltd.
SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial
Please see LICENSE in the repository root for full details.
*/
import { describe, expect, it } from "vitest";
import { BehaviorSubject } from "rxjs";
import { testScope } from "../utils/test";
import { type CallViewModel } from "../state/CallViewModel/CallViewModel";
import {
createDeveloperSettingsTabViewModel,
outOfCallDeveloperSettingsTabViewModel,
} from "./DeveloperSettingsTabViewModel";
describe("createDeveloperSettingsTabViewModel", () => {
it("projects the key rotation state of the call", () => {
const keyRotationSuppressed$ = new BehaviorSubject(false);
const participantCount$ = new BehaviorSubject(5);
const vm = createDeveloperSettingsTabViewModel(testScope(), {
keyRotationSuppressed$,
participantCount$,
} as unknown as CallViewModel);
expect(vm.keyRotation$.value).toEqual({
suppressed: false,
participantCount: 5,
});
participantCount$.next(50);
keyRotationSuppressed$.next(true);
expect(vm.keyRotation$.value).toEqual({
suppressed: true,
participantCount: 50,
});
});
});
describe("outOfCallDeveloperSettingsTabViewModel", () => {
it("has no key rotation state", () => {
expect(outOfCallDeveloperSettingsTabViewModel.keyRotation$.value).toBe(
null,
);
});
});

View File

@@ -0,0 +1,43 @@
/*
Copyright 2026 Element Creations Ltd.
SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial
Please see LICENSE in the repository root for full details.
*/
import { combineLatest } from "rxjs";
import { type CallViewModel } from "../state/CallViewModel/CallViewModel.ts";
import { type ObservableScope } from "../state/ObservableScope.ts";
import { createStaticViewModel, type ViewModel } from "../state/ViewModel.ts";
import { type DeveloperSettingsSnapshot } from "./DeveloperSettingsTab.tsx";
/**
* Creates the ViewModel for the developer settings tab while in a call.
*
* Only the call state the tab actually renders is projected here, so that the
* tab does not need to know about the CallViewModel.
*
* @param scope - ObservableScope that bounds the lifetime of derived behaviors.
* @param callModel - The root CallViewModel; provides the key rotation state.
*/
export function createDeveloperSettingsTabViewModel(
scope: ObservableScope,
callModel: CallViewModel,
): ViewModel<DeveloperSettingsSnapshot> {
return {
keyRotation$: scope.behavior(
combineLatest(
[callModel.keyRotationSuppressed$, callModel.participantCount$],
(suppressed, participantCount) => ({ suppressed, participantCount }),
),
),
};
}
/**
* The ViewModel for the developer settings tab outside of a call (lobby, user
* menu), where no call state exists. All call specific fields are `null`.
*/
export const outOfCallDeveloperSettingsTabViewModel: ViewModel<DeveloperSettingsSnapshot> =
createStaticViewModel<DeveloperSettingsSnapshot>({ keyRotation: null });

View File

@@ -29,12 +29,16 @@ import { PreferencesSettingsTab } from "./PreferencesSettingsTab";
import { Slider } from "../Slider"; import { Slider } from "../Slider";
import { DeviceSelection } from "./DeviceSelection"; import { DeviceSelection } from "./DeviceSelection";
import { useTrackProcessor } from "../livekit/TrackProcessorContext"; import { useTrackProcessor } from "../livekit/TrackProcessorContext";
import { DeveloperSettingsTab } from "./DeveloperSettingsTab"; import {
DeveloperSettingsTab,
type DeveloperSettingsSnapshot,
} from "./DeveloperSettingsTab";
import { FieldRow, InputField } from "../input/Input"; import { FieldRow, InputField } from "../input/Input";
import { useSubmitRageshake } from "./submit-rageshake"; import { useSubmitRageshake } from "./submit-rageshake";
import { useUrlParams } from "../UrlParams"; import { useUrlParams } from "../UrlParams";
import { useBehavior } from "../useBehavior"; import { useBehavior } from "../useBehavior";
import { type CallViewModel } from "../state/CallViewModel/CallViewModel.ts"; import { type ViewModel } from "../state/ViewModel.ts";
import { outOfCallDeveloperSettingsTabViewModel } from "./DeveloperSettingsTabViewModel";
type SettingsTab = type SettingsTab =
| "audio" | "audio"
@@ -58,7 +62,7 @@ interface Props {
isLocal?: boolean; isLocal?: boolean;
}[]; }[];
/** Only available while in a call. Used by the developer tab. */ /** Only available while in a call. Used by the developer tab. */
vm?: CallViewModel; developerSettingsVm?: ViewModel<DeveloperSettingsSnapshot>;
} }
export const defaultSettingsTab: SettingsTab = "audio"; export const defaultSettingsTab: SettingsTab = "audio";
@@ -71,7 +75,7 @@ export const SettingsModal: FC<Props> = ({
client, client,
roomId, roomId,
livekitRooms, livekitRooms,
vm, developerSettingsVm,
}) => { }) => {
const { t } = useTranslation(); const { t } = useTranslation();
@@ -224,7 +228,7 @@ export const SettingsModal: FC<Props> = ({
client={client} client={client}
livekitRooms={livekitRooms} livekitRooms={livekitRooms}
roomId={roomId} roomId={roomId}
vm={vm} vm={developerSettingsVm ?? outOfCallDeveloperSettingsTabViewModel}
/> />
), ),
}; };

View File

@@ -42,6 +42,8 @@ import { MatrixRTCMode } from "../config/ConfigOptions";
import { createCallFooterViewModel } from "../components/CallFooterViewModel"; import { createCallFooterViewModel } from "../components/CallFooterViewModel";
import { type FooterSnapshot } from "../components/CallFooter"; import { type FooterSnapshot } from "../components/CallFooter";
import { type ViewModel } from "../state/ViewModel"; import { type ViewModel } from "../state/ViewModel";
import { createDeveloperSettingsTabViewModel } from "../settings/DeveloperSettingsTabViewModel";
import { type DeveloperSettingsSnapshot } from "../settings/DeveloperSettingsTab";
mockConfig({ livekit: { livekit_service_url: "https://example.com" } }); mockConfig({ livekit: { livekit_service_url: "https://example.com" } });
@@ -140,6 +142,7 @@ export function getBasicCallViewModelEnvironment(
): { ): {
vm: CallViewModel; vm: CallViewModel;
footerVm: ViewModel<FooterSnapshot>; footerVm: ViewModel<FooterSnapshot>;
developerSettingsVm: ViewModel<DeveloperSettingsSnapshot>;
rtcMemberships$: BehaviorSubject<CallMembership[]>; rtcMemberships$: BehaviorSubject<CallMembership[]>;
rtcSession: MockRTCSession; rtcSession: MockRTCSession;
handRaisedSubject$: BehaviorSubject<Record<string, RaisedHandInfo>>; handRaisedSubject$: BehaviorSubject<Record<string, RaisedHandInfo>>;
@@ -188,6 +191,7 @@ export function getBasicCallViewModelEnvironment(
return { return {
vm, vm,
footerVm, footerVm,
developerSettingsVm: createDeveloperSettingsTabViewModel(testScope(), vm),
rtcMemberships$, rtcMemberships$,
rtcSession, rtcSession,
handRaisedSubject$: handRaisedSubject$, handRaisedSubject$: handRaisedSubject$,