Remove LegacyCallHandler singleton (#34086)

* Remove dead code

* Remove LegacyCallHandler singleton

Route via SDKContext to cut import cycles

* Remove unused setting

* Fix tests

* Fix tests

* Cascade SDKContext through PersistedElement

* Improve coverage

* Iterate

* Improve coverage

* Improve coverage
This commit is contained in:
Michael Telatynski
2026-07-07 12:29:54 +00:00
committed by GitHub
parent 48bd69a8b6
commit 4ec8f1edcd
40 changed files with 639 additions and 348 deletions
@@ -40,6 +40,7 @@ import SettingsStore from "../../src/settings/SettingsStore";
import { UIFeature } from "../../src/settings/UIFeature";
import { createAudioContext } from "../../src/audio/compat";
import * as ManagedHybrid from "../../src/widgets/ManagedHybrid";
import { TestSDKContext } from "./TestSDKContext.ts";
jest.mock("../../src/Modal");
@@ -165,7 +166,7 @@ describe("LegacyCallHandler", () => {
});
};
callHandler = new LegacyCallHandler();
callHandler = new LegacyCallHandler(new TestSDKContext());
callHandler.start();
mocked(getFunctionalMembers).mockReturnValue([FUNCTIONAL_USER]);
@@ -238,8 +239,6 @@ describe("LegacyCallHandler", () => {
callHandler.stop();
// @ts-ignore
DMRoomMap.setShared(null);
// @ts-ignore
window.mxLegacyCallHandler = null;
MatrixClientPeg.unset();
document.body.removeChild(audioElement);
@@ -373,7 +372,7 @@ describe("LegacyCallHandler without third party protocols", () => {
};
mocked(createAudioContext).mockReturnValue(mockAudioContext as unknown as AudioContext);
callHandler = new LegacyCallHandler();
callHandler = new LegacyCallHandler(new TestSDKContext());
callHandler.start();
const nativeRoomAlice = mkStubDM(NATIVE_ROOM_ALICE, NATIVE_ALICE);
@@ -423,8 +422,6 @@ describe("LegacyCallHandler without third party protocols", () => {
callHandler.stop();
// @ts-ignore
DMRoomMap.setShared(null);
// @ts-ignore
window.mxLegacyCallHandler = null;
MatrixClientPeg.unset();
document.body.removeChild(audioElement);
@@ -18,6 +18,7 @@ import { type SpaceStoreClass } from "../../src/stores/spaces/SpaceStore";
import { type WidgetLayoutStore } from "../../src/stores/widgets/WidgetLayoutStore";
import { type WidgetPermissionStore } from "../../src/stores/widgets/WidgetPermissionStore";
import type WidgetStore from "../../src/stores/WidgetStore";
import type LegacyCallHandler from "../../src/LegacyCallHandler.tsx";
/**
* A class which provides the same API as SDKContextClass but adds additional unsafe setters which can
@@ -34,6 +35,7 @@ export class TestSDKContext extends SDKContextClass {
declare public _PosthogAnalytics?: PosthogAnalytics;
declare public _SlidingSyncManager?: SlidingSyncManager;
declare public _SpaceStore?: SpaceStoreClass;
declare public _LegacyCallHandler?: LegacyCallHandler;
constructor() {
super();
@@ -6,12 +6,13 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com
Please see LICENSE files in the repository root for full details.
*/
import { type MatrixClient, type MatrixEvent, EventType } from "matrix-js-sdk/src/matrix";
import { type MatrixClient, MatrixEvent, EventType } from "matrix-js-sdk/src/matrix";
import { CallState } from "matrix-js-sdk/src/webrtc/call";
import { stubClient } from "../../../test-utils";
import { MatrixClientPeg } from "../../../../src/MatrixClientPeg";
import LegacyCallEventGrouper from "../../../../src/components/structures/LegacyCallEventGrouper";
import { SDKContextClass } from "../../../../src/contexts/SDKContextClass.ts";
const MY_USER_ID = "@me:here";
const THEIR_USER_ID = "@they:here";
@@ -145,4 +146,83 @@ describe("LegacyCallEventGrouper", () => {
expect(grouper.isVoice).toBe(false);
});
it("should be able to answer call", () => {
const grouper = new LegacyCallEventGrouper();
grouper.add(
new MatrixEvent({
content: {
call_id: "callId",
},
type: EventType.CallInvite,
sender: THEIR_USER_ID,
room_id: "!room:server",
}),
);
jest.spyOn(SDKContextClass.instance.legacyCallHandler, "answerCall");
grouper.answerCall();
expect(SDKContextClass.instance.legacyCallHandler.answerCall).toHaveBeenCalledWith("!room:server");
});
it("should be able to reject call", () => {
const grouper = new LegacyCallEventGrouper();
grouper.add(
new MatrixEvent({
content: {
call_id: "callId",
},
type: EventType.CallInvite,
sender: THEIR_USER_ID,
room_id: "!room:server",
}),
);
jest.spyOn(SDKContextClass.instance.legacyCallHandler, "hangupOrReject");
grouper.rejectCall();
expect(SDKContextClass.instance.legacyCallHandler.hangupOrReject).toHaveBeenCalledWith("!room:server", true);
});
it("should be able to callback call", () => {
const grouper = new LegacyCallEventGrouper();
grouper.add(
new MatrixEvent({
content: {
call_id: "callId",
},
type: EventType.CallHangup,
sender: THEIR_USER_ID,
room_id: "!room:server",
}),
);
jest.spyOn(SDKContextClass.instance.legacyCallHandler, "placeCall");
grouper.callBack();
expect(SDKContextClass.instance.legacyCallHandler.placeCall).toHaveBeenCalledWith("!room:server", "video");
});
it("should be able to toggle call silenced", () => {
const grouper = new LegacyCallEventGrouper();
grouper.add(
new MatrixEvent({
content: {
call_id: "callId",
},
type: EventType.CallHangup,
sender: THEIR_USER_ID,
room_id: "!room:server",
}),
);
jest.spyOn(SDKContextClass.instance.legacyCallHandler, "unSilenceCall");
jest.spyOn(SDKContextClass.instance.legacyCallHandler, "silenceCall");
jest.spyOn(SDKContextClass.instance.legacyCallHandler, "isCallSilenced").mockReturnValue(false);
grouper.toggleSilenced();
expect(SDKContextClass.instance.legacyCallHandler.silenceCall).toHaveBeenCalledWith("callId");
jest.spyOn(SDKContextClass.instance.legacyCallHandler, "isCallSilenced").mockReturnValue(true);
grouper.toggleSilenced();
expect(SDKContextClass.instance.legacyCallHandler.unSilenceCall).toHaveBeenCalledWith("callId");
});
});
@@ -46,7 +46,6 @@ import {
} from "../../../test-utils";
import * as leaveRoomUtils from "../../../../src/utils/leave-behaviour";
import { OidcClientError } from "../../../../src/utils/oidc/error";
import LegacyCallHandler from "../../../../src/LegacyCallHandler";
import { CallStore } from "../../../../src/stores/CallStore";
import { type Call } from "../../../../src/models/Call";
import { PosthogAnalytics } from "../../../../src/PosthogAnalytics";
@@ -1109,7 +1108,7 @@ describe("<MatrixChat />", () => {
beforeEach(() => {
// stub out various cleanup functions
jest.spyOn(LegacyCallHandler.instance, "hangupAllCalls")
jest.spyOn(SDKContextClass.instance.legacyCallHandler, "hangupAllCalls")
.mockClear()
.mockImplementation(() => {});
jest.spyOn(PosthogAnalytics.instance, "logout").mockImplementation(() => {});
@@ -1134,7 +1133,7 @@ describe("<MatrixChat />", () => {
it("should hangup all legacy calls", async () => {
await getComponentAndWaitForReady();
await dispatchLogoutAndWait();
expect(LegacyCallHandler.instance.hangupAllCalls).toHaveBeenCalled();
expect(SDKContextClass.instance.legacyCallHandler.hangupAllCalls).toHaveBeenCalled();
});
it("should disconnect all calls", async () => {
@@ -39,7 +39,7 @@ import { WidgetMessagingStore } from "../../../../../src/stores/widgets/WidgetMe
import { ModuleRunner } from "../../../../../src/modules/ModuleRunner";
import { ModuleApi } from "../../../../../src/modules/Api";
import { RoomPermalinkCreator } from "../../../../../src/utils/permalinks/Permalinks";
import { SDKContextClass } from "../../../../../src/contexts/SDKContextClass";
import { TestSDKContext } from "../../../TestSDKContext.ts";
jest.mock("../../../../../src/stores/OwnProfileStore", () => ({
OwnProfileStore: {
@@ -56,7 +56,7 @@ const realGetValue = SettingsStore.getValue;
describe("AppTile", () => {
let cli: MatrixClient;
let sdkContext: SDKContextClass;
let sdkContext: TestSDKContext;
let r1: Room;
let r2: Room;
const resizeNotifier = new ResizeNotifier();
@@ -118,18 +118,19 @@ describe("AppTile", () => {
beforeEach(async () => {
// Do not carry across settings from previous tests
SettingsStore.reset();
sdkContext = new SDKContextClass();
sdkContext = new TestSDKContext();
sdkContext._client = cli;
// @ts-ignore
await WidgetMessagingStore.instance.onReady();
// Wake up various stores we rely on
WidgetLayoutStore.instance.useUnitTestClient(cli);
sdkContext.widgetLayoutStore.useUnitTestClient(cli);
// @ts-ignore
await WidgetLayoutStore.instance.onReady();
await sdkContext.widgetLayoutStore.onReady();
RightPanelStore.instance.useUnitTestClient(cli);
sdkContext.rightPanelStore.useUnitTestClient(cli);
// @ts-ignore
await RightPanelStore.instance.onReady();
await sdkContext.rightPanelStore.onReady();
});
afterEach(async () => {
@@ -162,13 +163,12 @@ describe("AppTile", () => {
// Run initial render with room 1, and also running lifecycle methods
const renderResult = render(
<MatrixClientContext.Provider value={cli}>
<RightPanel
room={r1}
resizeNotifier={resizeNotifier}
permalinkCreator={new RoomPermalinkCreator(r1, r1.roomId)}
/>
</MatrixClientContext.Provider>,
<RightPanel
room={r1}
resizeNotifier={resizeNotifier}
permalinkCreator={new RoomPermalinkCreator(r1, r1.roomId)}
/>,
clientAndSDKContextRenderOptions(cli, sdkContext),
);
act(() =>
dis.dispatch({
@@ -232,13 +232,12 @@ describe("AppTile", () => {
// Run initial render with room 1, and also running lifecycle methods
const renderResult = render(
<MatrixClientContext.Provider value={cli}>
<RightPanel
room={r1}
resizeNotifier={resizeNotifier}
permalinkCreator={new RoomPermalinkCreator(r1, r1.roomId)}
/>
</MatrixClientContext.Provider>,
<RightPanel
room={r1}
resizeNotifier={resizeNotifier}
permalinkCreator={new RoomPermalinkCreator(r1, r1.roomId)}
/>,
clientAndSDKContextRenderOptions(cli, sdkContext),
);
act(() =>
dis.dispatch({
@@ -341,6 +340,47 @@ describe("AppTile", () => {
expect(ActiveWidgetStore.instance.isLive("1", "r1")).toBe(true);
});
it("should hangup Jitsi call when room is left", async () => {
const app: IApp = {
id: "3",
eventId: "jitsi1",
roomId: "r2",
type: MatrixWidgetType.JitsiMeet,
url: "https://jitsi.example.com",
name: "Jitsi Conference",
creatorUserId: cli.getSafeUserId(),
avatar_url: undefined,
};
const { queryByRole, getByText } = render(
<AppTile key={app.id} app={app} room={r2} />,
clientAndSDKContextRenderOptions(cli, sdkContext),
);
await waitForElementToBeRemoved(() => queryByRole("progressbar"));
expect(getByText("Jitsi Conference")).toBeInTheDocument();
// Switch to room 1
dis.dispatch(
{
action: Action.ViewRoom,
room_id: "r1",
},
true,
);
jest.spyOn(ActiveWidgetStore.instance, "getWidgetPersistence").mockReturnValue(true);
jest.spyOn(sdkContext.legacyCallHandler, "hangupCallApp");
dis.dispatch(
{
action: Action.AfterLeaveRoom,
room_id: "r2",
},
true,
);
expect(sdkContext.legacyCallHandler.hangupCallApp).toHaveBeenCalledWith(app.roomId);
});
describe("for a pinned widget", () => {
let moveToContainerSpy: jest.SpyInstance<void, [room: Room, widget: IWidget, toContainer: Container]>;
beforeEach(async () => {
@@ -349,9 +389,8 @@ describe("AppTile", () => {
it("should render", async () => {
const renderResult = render(
<MatrixClientContext.Provider value={cli}>
<AppTile key={app1.id} app={app1} room={r1} />
</MatrixClientContext.Provider>,
<AppTile key={app1.id} app={app1} room={r1} />,
clientAndSDKContextRenderOptions(cli, sdkContext),
);
await waitForElementToBeRemoved(() => renderResult.queryByRole("progressbar"));
const { asFragment } = renderResult;
@@ -361,9 +400,8 @@ describe("AppTile", () => {
it("should not display the »Popout widget« button", async () => {
const renderResult = render(
<MatrixClientContext.Provider value={cli}>
<AppTile key={app1.id} app={app1} room={r1} />
</MatrixClientContext.Provider>,
<AppTile key={app1.id} app={app1} room={r1} />,
clientAndSDKContextRenderOptions(cli, sdkContext),
);
await waitForElementToBeRemoved(() => renderResult.queryByRole("progressbar"));
expect(renderResult.queryByLabelText("Popout widget")).not.toBeInTheDocument();
@@ -371,20 +409,34 @@ describe("AppTile", () => {
it("clicking 'minimise' should send the widget to the right", async () => {
const renderResult = render(
<MatrixClientContext.Provider value={cli}>
<AppTile key={app1.id} app={app1} room={r1} />
</MatrixClientContext.Provider>,
<AppTile key={app1.id} app={app1} room={r1} />,
clientAndSDKContextRenderOptions(cli, sdkContext),
);
await waitForElementToBeRemoved(() => renderResult.queryByRole("progressbar"));
await userEvent.click(renderResult.getByLabelText("Minimise"));
expect(moveToContainerSpy).toHaveBeenCalledWith(r1, app1, "right");
});
it("should close right panel timeline when minimising widget", async () => {
const renderResult = render(
<AppTile key={app1.id} app={app1} room={r1} />,
clientAndSDKContextRenderOptions(cli, sdkContext),
);
await waitForElementToBeRemoved(() => renderResult.queryByRole("progressbar"));
jest.spyOn(sdkContext.rightPanelStore, "currentCardForRoom").mockReturnValue({
phase: RightPanelPhases.Timeline,
});
jest.spyOn(sdkContext.rightPanelStore, "popCard");
await userEvent.click(renderResult.getByLabelText("Minimise"));
expect(sdkContext.rightPanelStore.popCard).toHaveBeenCalledWith(r1.roomId);
});
it("clicking 'maximise' should send the widget to the center", async () => {
const renderResult = render(
<MatrixClientContext.Provider value={cli}>
<AppTile key={app1.id} app={app1} room={r1} />
</MatrixClientContext.Provider>,
<AppTile key={app1.id} app={app1} room={r1} />,
clientAndSDKContextRenderOptions(cli, sdkContext),
);
await waitForElementToBeRemoved(() => renderResult.queryByRole("progressbar"));
await userEvent.click(renderResult.getByLabelText("Maximise"));
@@ -400,9 +452,8 @@ describe("AppTile", () => {
// userId and creatorUserId are different
const { container, asFragment, queryByRole } = render(
<MatrixClientContext.Provider value={cli}>
<AppTile key={app1.id} app={app1} room={r1} userId="@user1" creatorUserId="@userAnother" />
</MatrixClientContext.Provider>,
<AppTile key={app1.id} app={app1} room={r1} userId="@user1" creatorUserId="@userAnother" />,
clientAndSDKContextRenderOptions(cli, sdkContext),
);
expect(container.querySelector(".mx_Spinner")).toBeFalsy();
expect(queryByRole("button", { name: "Continue" })).toBeInTheDocument();
@@ -418,9 +469,8 @@ describe("AppTile", () => {
// userId and creatorUserId are different
const renderResult = render(
<MatrixClientContext.Provider value={cli}>
<AppTile key={app1.id} app={app1} room={r1} userId="@user1" creatorUserId="@userAnother" />
</MatrixClientContext.Provider>,
<AppTile key={app1.id} app={app1} room={r1} userId="@user1" creatorUserId="@userAnother" />,
clientAndSDKContextRenderOptions(cli, sdkContext),
);
await waitForElementToBeRemoved(() => renderResult.queryByRole("progressbar"));
@@ -440,9 +490,8 @@ describe("AppTile", () => {
// userId and creatorUserId are different so legacy path would show "Continue"
const renderResult = render(
<MatrixClientContext.Provider value={cli}>
<AppTile key={app1.id} app={app1} room={r1} userId="@user1" creatorUserId="@userAnother" />
</MatrixClientContext.Provider>,
<AppTile key={app1.id} app={app1} room={r1} userId="@user1" creatorUserId="@userAnother" />,
clientAndSDKContextRenderOptions(cli, sdkContext),
);
// The new API runs async in componentDidMount, so wait for it to take effect
@@ -466,9 +515,8 @@ describe("AppTile", () => {
it("clicking 'un-maximise' should send the widget to the top", async () => {
const renderResult = render(
<MatrixClientContext.Provider value={cli}>
<AppTile key={app1.id} app={app1} room={r1} />
</MatrixClientContext.Provider>,
<AppTile key={app1.id} app={app1} room={r1} />,
clientAndSDKContextRenderOptions(cli, sdkContext),
);
await waitForElementToBeRemoved(() => renderResult.queryByRole("progressbar"));
await userEvent.click(renderResult.getByLabelText("Un-maximise"));
@@ -496,9 +544,8 @@ describe("AppTile", () => {
it("should display the »Popout widget« button", async () => {
const renderResult = render(
<MatrixClientContext.Provider value={cli}>
<AppTile key={app1.id} app={app1} room={r1} />
</MatrixClientContext.Provider>,
<AppTile key={app1.id} app={app1} room={r1} />,
clientAndSDKContextRenderOptions(cli, sdkContext),
);
await waitForElementToBeRemoved(() => renderResult.queryByRole("progressbar"));
expect(renderResult.getByLabelText("Popout widget")).toBeInTheDocument();
@@ -509,9 +556,8 @@ describe("AppTile", () => {
describe("for a persistent app", () => {
it("should render", async () => {
const { asFragment, queryByRole } = render(
<MatrixClientContext.Provider value={cli}>
<AppTile key={app1.id} app={app1} room={r1} fullWidth={true} miniMode={true} showMenubar={false} />
</MatrixClientContext.Provider>,
<AppTile key={app1.id} app={app1} room={r1} fullWidth={true} miniMode={true} showMenubar={false} />,
clientAndSDKContextRenderOptions(cli, sdkContext),
);
await waitForElementToBeRemoved(() => queryByRole("progressbar"));
expect(asFragment()).toMatchSnapshot();
@@ -46,7 +46,6 @@ import { ScopedRoomContextProvider } from "../../../../../../src/contexts/Scoped
import RoomContext, { type RoomContextType } from "../../../../../../src/contexts/RoomContext";
import RightPanelStore from "../../../../../../src/stores/right-panel/RightPanelStore";
import { RightPanelPhases } from "../../../../../../src/stores/right-panel/RightPanelStorePhases";
import LegacyCallHandler from "../../../../../../src/LegacyCallHandler";
import SettingsStore from "../../../../../../src/settings/SettingsStore";
import SdkConfig from "../../../../../../src/SdkConfig";
import dispatcher from "../../../../../../src/dispatcher/dispatcher";
@@ -60,6 +59,7 @@ import WidgetStore, { type IApp } from "../../../../../../src/stores/WidgetStore
import { UIFeature } from "../../../../../../src/settings/UIFeature";
import { SettingLevel } from "../../../../../../src/settings/SettingLevel";
import { ElementCallMemberEventType } from "../../../../../../src/call-types";
import { SDKContextClass } from "../../../../../../src/contexts/SDKContextClass.ts";
jest.mock("../../../../../../src/utils/ShieldUtils");
jest.mock("../../../../../../src/hooks/right-panel/useCurrentPhase", () => ({
@@ -365,7 +365,7 @@ describe("RoomHeader", () => {
expect(voiceButton).not.toHaveAttribute("aria-disabled", "true");
expect(videoButton).not.toHaveAttribute("aria-disabled", "true");
const placeCallSpy = jest.spyOn(LegacyCallHandler.instance, "placeCall");
const placeCallSpy = jest.spyOn(SDKContextClass.instance.legacyCallHandler, "placeCall");
await user.click(voiceButton);
expect(placeCallSpy).toHaveBeenLastCalledWith(room.roomId, CallType.Voice);
@@ -376,7 +376,7 @@ describe("RoomHeader", () => {
it("you can't call if there's already a call", () => {
mockRoomMembers(room, 2);
jest.spyOn(LegacyCallHandler.instance, "getCallForRoom").mockReturnValue(
jest.spyOn(SDKContextClass.instance.legacyCallHandler, "getCallForRoom").mockReturnValue(
// The JS-SDK does not export the class `MatrixCall` only the type
{} as MatrixCall,
);
@@ -508,7 +508,7 @@ describe("RoomHeader", () => {
it("disables calling if there's a jitsi call", () => {
mockRoomMembers(room, 2);
jest.spyOn(LegacyCallHandler.instance, "getCallForRoom").mockReturnValue(
jest.spyOn(SDKContextClass.instance.legacyCallHandler, "getCallForRoom").mockReturnValue(
// The JS-SDK does not export the class `MatrixCall` only the type
{} as MatrixCall,
);
@@ -532,7 +532,7 @@ describe("RoomHeader", () => {
expect(voiceButton).not.toHaveAttribute("aria-disabled", "true");
expect(videoButton).not.toHaveAttribute("aria-disabled", "true");
const placeCallSpy = jest.spyOn(LegacyCallHandler.instance, "placeCall");
const placeCallSpy = jest.spyOn(SDKContextClass.instance.legacyCallHandler, "placeCall");
await user.click(voiceButton);
expect(placeCallSpy).toHaveBeenLastCalledWith(room.roomId, CallType.Voice);
@@ -554,7 +554,7 @@ describe("RoomHeader", () => {
const videoButton = screen.getByRole("button", { name: "Video call" });
expect(videoButton).not.toHaveAttribute("aria-disabled", "true");
const placeCallSpy = jest.spyOn(LegacyCallHandler.instance, "placeCall");
const placeCallSpy = jest.spyOn(SDKContextClass.instance.legacyCallHandler, "placeCall");
await user.click(videoButton);
expect(placeCallSpy).toHaveBeenLastCalledWith(room.roomId, CallType.Video);
});
@@ -15,6 +15,8 @@ import { shouldShowComponent } from "../../../../../../src/customisations/helper
import { MetaSpace } from "../../../../../../src/stores/spaces";
import { LandmarkNavigation } from "../../../../../../src/accessibility/LandmarkNavigation";
import { ReleaseAnnouncementStore } from "../../../../../../src/stores/ReleaseAnnouncementStore";
import { clientAndSDKContextRenderOptions, createTestClient } from "../../../../../test-utils";
import { TestSDKContext } from "../../../../TestSDKContext.ts";
jest.mock("../../../../../../src/customisations/helpers/UIComponents", () => ({
shouldShowComponent: jest.fn(),
@@ -34,8 +36,15 @@ jest.mock("../../../../../../src/accessibility/LandmarkNavigation", () => ({
jest.spyOn(ReleaseAnnouncementStore.instance, "getReleaseAnnouncement").mockReturnValue(null);
describe("<RoomListPanel />", () => {
const client = createTestClient();
const sdkContext = new TestSDKContext();
sdkContext._client = client;
function renderComponent() {
return render(<RoomListPanel activeSpace={MetaSpace.Home} />);
return render(
<RoomListPanel activeSpace={MetaSpace.Home} />,
clientAndSDKContextRenderOptions(client, sdkContext),
);
}
beforeEach(() => {
@@ -12,7 +12,8 @@ import { mocked } from "jest-mock";
import { RoomListSearch } from "../../../../../../src/components/views/rooms/RoomListPanel/RoomListSearch";
import { MetaSpace } from "../../../../../../src/stores/spaces";
import { shouldShowComponent } from "../../../../../../src/customisations/helpers/UIComponents";
import LegacyCallHandler from "../../../../../../src/LegacyCallHandler";
import { SDKContextClass } from "../../../../../../src/contexts/SDKContextClass.ts";
import { clientAndSDKContextRenderOptions, createTestClient } from "../../../../../test-utils";
jest.mock("../../../../../../src/customisations/helpers/UIComponents", () => ({
shouldShowComponent: jest.fn(),
@@ -20,13 +21,16 @@ jest.mock("../../../../../../src/customisations/helpers/UIComponents", () => ({
describe("<RoomListSearch />", () => {
function renderComponent(activeSpace = MetaSpace.Home) {
return render(<RoomListSearch activeSpace={activeSpace} />);
return render(
<RoomListSearch activeSpace={activeSpace} />,
clientAndSDKContextRenderOptions(createTestClient(), SDKContextClass.instance),
);
}
beforeEach(() => {
// By default, we consider shouldShowComponent(UIComponent.ExploreRooms) should return true
mocked(shouldShowComponent).mockReturnValue(true);
jest.spyOn(LegacyCallHandler.instance, "getSupportsPstnProtocol").mockReturnValue(false);
jest.spyOn(SDKContextClass.instance.legacyCallHandler, "getSupportsPstnProtocol").mockReturnValue(false);
});
it("renders", () => {
@@ -26,6 +26,7 @@ import {
MockedCall,
setupAsyncStoreWithClient,
useMockMediaDevices,
clientAndSDKContextRenderOptions,
} from "../../../../test-utils";
import { MatrixClientPeg } from "../../../../../src/MatrixClientPeg";
import { CallView as _CallView } from "../../../../../src/components/views/voip/CallView";
@@ -33,6 +34,7 @@ import { WidgetMessagingStore } from "../../../../../src/stores/widgets/WidgetMe
import { CallStore } from "../../../../../src/stores/CallStore";
import DMRoomMap from "../../../../../src/utils/DMRoomMap";
import { type WidgetMessaging } from "../../../../../src/stores/widgets/WidgetMessaging";
import { TestSDKContext } from "../../../TestSDKContext.ts";
const CallView = wrapInMatrixClientContext(_CallView);
@@ -41,6 +43,7 @@ describe("CallView", () => {
jest.spyOn(HTMLMediaElement.prototype, "play").mockImplementation(async () => {});
let client: Mocked<MatrixClient>;
let sdkContext: TestSDKContext;
let room: Room;
let alice: RoomMember;
let call: MockedCall;
@@ -51,6 +54,8 @@ describe("CallView", () => {
stubClient();
client = mocked(MatrixClientPeg.safeGet());
sdkContext = new TestSDKContext();
sdkContext._client = client;
DMRoomMap.makeShared(client);
room = new Room("!1:example.org", client, "@alice:example.org", {
@@ -88,7 +93,10 @@ describe("CallView", () => {
});
const renderView = async (role: string | undefined = undefined): Promise<void> => {
render(<CallView room={room} resizing={false} role={role} onClose={() => {}} />);
render(
<CallView room={room} resizing={false} role={role} onClose={() => {}} />,
clientAndSDKContextRenderOptions(client, sdkContext),
);
await act(() => Promise.resolve()); // Let effects settle
};
@@ -6,16 +6,21 @@ Please see LICENSE files in the repository root for full details.
*/
import React from "react";
import { render } from "jest-matrix-react";
import { render, fireEvent } from "jest-matrix-react";
import { type MatrixCall } from "matrix-js-sdk/src/matrix";
import { type CallFeed } from "matrix-js-sdk/src/webrtc/callFeed";
import { SDPStreamMetadataPurpose } from "matrix-js-sdk/src/webrtc/callEventTypes";
import LegacyCallView from "../../../../../src/components/views/voip/LegacyCallView";
import { stubClient } from "../../../../test-utils";
import { clientAndSDKContextRenderOptions, createTestClient, stubClient } from "../../../../test-utils";
import DMRoomMap from "../../../../../src/utils/DMRoomMap";
import { TestSDKContext } from "../../../TestSDKContext.ts";
describe("LegacyCallView", () => {
const cli = stubClient();
const sdkContext = new TestSDKContext();
sdkContext._client = cli;
it("should exit full screen on unmount", () => {
const element = document.createElement("div");
// @ts-expect-error
@@ -35,7 +40,10 @@ describe("LegacyCallView", () => {
isScreensharing: jest.fn().mockReturnValue(false),
} as unknown as MatrixCall;
const { unmount } = render(<LegacyCallView call={call} sidebarShown={false} />);
const { unmount } = render(
<LegacyCallView call={call} sidebarShown={false} />,
clientAndSDKContextRenderOptions(cli, sdkContext),
);
expect(document.exitFullscreen).not.toHaveBeenCalled();
unmount();
expect(document.exitFullscreen).toHaveBeenCalled();
@@ -75,7 +83,10 @@ describe("LegacyCallView", () => {
getUserIdForRoomId: jest.fn().mockReturnValue("test-user"),
} as unknown as DMRoomMap);
const { container, rerender } = render(<LegacyCallView call={call} sidebarShown={true} />);
const { container, rerender } = render(
<LegacyCallView call={call} sidebarShown={true} />,
clientAndSDKContextRenderOptions(cli, sdkContext),
);
expect(container.querySelector(".mx_LegacyCallViewSidebar")).toBeTruthy();
rerender(<LegacyCallView call={call} sidebarShown={true} />);
expect(container.querySelector(".mx_LegacyCallViewSidebar")).toBeTruthy();
@@ -98,7 +109,102 @@ describe("LegacyCallView", () => {
getUserIdForRoomId: jest.fn().mockReturnValue("test-user"),
} as unknown as DMRoomMap);
const { container } = render(<LegacyCallView call={call} sidebarShown={false} pipMode={true} />);
const { container } = render(
<LegacyCallView call={call} sidebarShown={false} pipMode={true} />,
clientAndSDKContextRenderOptions(cli, sdkContext),
);
expect(container.querySelector(".mx_LegacyCallViewButtons_button_sidebar")).toBeFalsy();
});
it("should allow user to resume held call", async () => {
const client = createTestClient();
const sdkContext = new TestSDKContext();
sdkContext._client = client;
const call = {
roomId: "test-room",
on: jest.fn(),
removeListener: jest.fn(),
getFeeds: jest.fn().mockReturnValue(
[{ local: true }, { local: false }, { local: true, screenshare: true }].map(
(x, i) =>
({
stream: { id: "test-" + i },
addListener: jest.fn(),
removeListener: jest.fn(),
getMember: jest.fn(),
isAudioMuted: jest.fn().mockReturnValue(true),
isVideoMuted: jest.fn().mockReturnValue(true),
isLocal: jest.fn().mockReturnValue(x.local),
purpose: x.screenshare && SDPStreamMetadataPurpose.Screenshare,
}) as unknown as CallFeed,
),
),
isLocalOnHold: jest.fn().mockReturnValue(false),
isRemoteOnHold: jest.fn().mockReturnValue(true),
isMicrophoneMuted: jest.fn().mockReturnValue(true),
isLocalVideoMuted: jest.fn().mockReturnValue(true),
isScreensharing: jest.fn().mockReturnValue(true),
noIncomingFeeds: jest.fn().mockReturnValue(false),
opponentSupportsSDPStreamMetadata: jest.fn().mockReturnValue(true),
getOpponentMember: jest.fn(),
} as unknown as MatrixCall;
jest.spyOn(sdkContext.legacyCallHandler, "roomIdForCall").mockReturnValue(call.roomId);
jest.spyOn(sdkContext.legacyCallHandler, "setActiveCallRoomId");
const { getByText } = render(
<LegacyCallView call={call} sidebarShown />,
clientAndSDKContextRenderOptions(client, sdkContext),
);
fireEvent.click(getByText("Resume"));
expect(sdkContext.legacyCallHandler.setActiveCallRoomId).toHaveBeenCalledWith(call.roomId);
});
it("should allow user to hangup call", async () => {
const client = createTestClient();
const sdkContext = new TestSDKContext();
sdkContext._client = client;
const call = {
roomId: "test-room",
on: jest.fn(),
removeListener: jest.fn(),
getFeeds: jest.fn().mockReturnValue(
[{ local: true }, { local: false }, { local: true, screenshare: true }].map(
(x, i) =>
({
stream: { id: "test-" + i },
addListener: jest.fn(),
removeListener: jest.fn(),
getMember: jest.fn(),
isAudioMuted: jest.fn().mockReturnValue(true),
isVideoMuted: jest.fn().mockReturnValue(true),
isLocal: jest.fn().mockReturnValue(x.local),
purpose: x.screenshare && SDPStreamMetadataPurpose.Screenshare,
}) as unknown as CallFeed,
),
),
isLocalOnHold: jest.fn().mockReturnValue(false),
isRemoteOnHold: jest.fn().mockReturnValue(false),
isMicrophoneMuted: jest.fn().mockReturnValue(true),
isLocalVideoMuted: jest.fn().mockReturnValue(true),
isScreensharing: jest.fn().mockReturnValue(true),
noIncomingFeeds: jest.fn().mockReturnValue(false),
opponentSupportsSDPStreamMetadata: jest.fn().mockReturnValue(true),
getOpponentMember: jest.fn(),
} as unknown as MatrixCall;
jest.spyOn(sdkContext.legacyCallHandler, "roomIdForCall").mockReturnValue(call.roomId);
jest.spyOn(sdkContext.legacyCallHandler, "hangupOrReject");
const { getByLabelText } = render(
<LegacyCallView call={call} sidebarShown />,
clientAndSDKContextRenderOptions(client, sdkContext),
);
fireEvent.click(getByLabelText("Hangup"));
expect(sdkContext.legacyCallHandler.hangupOrReject).toHaveBeenCalledWith(call.roomId);
});
});
@@ -12,29 +12,29 @@ import { CallEventHandlerEvent } from "matrix-js-sdk/src/webrtc/callEventHandler
import LegacyCallView from "../../../../../src/components/views/voip/LegacyCallView";
import LegacyCallViewForRoom from "../../../../../src/components/views/voip/LegacyCallViewForRoom";
import { mkStubRoom, stubClient } from "../../../../test-utils";
import { clientAndSDKContextRenderOptions, mkStubRoom, stubClient } from "../../../../test-utils";
import DMRoomMap from "../../../../../src/utils/DMRoomMap";
import { MatrixClientPeg } from "../../../../../src/MatrixClientPeg";
import LegacyCallHandler from "../../../../../src/LegacyCallHandler";
import { SDKContext } from "../../../../../src/contexts/SDKContext";
import { SDKContextClass } from "../../../../../src/contexts/SDKContextClass";
import { TestSDKContext } from "../../../TestSDKContext.ts";
jest.mock("../../../../../src/components/views/voip/LegacyCallView", () => jest.fn(() => "LegacyCallView"));
describe("LegacyCallViewForRoom", () => {
const LegacyCallViewMock = LegacyCallView as unknown as jest.Mock;
let sdkContext: SDKContextClass;
let sdkContext: TestSDKContext;
beforeEach(() => {
stubClient();
sdkContext = new SDKContextClass();
sdkContext = new TestSDKContext();
sdkContext._client = stubClient();
LegacyCallViewMock.mockClear();
});
it("should remember sidebar state, defaulting to shown", async () => {
const callHandler = new LegacyCallHandler();
const callHandler = new LegacyCallHandler(sdkContext);
callHandler.start();
jest.spyOn(LegacyCallHandler, "instance", "get").mockImplementation(() => callHandler);
sdkContext._LegacyCallHandler = callHandler;
const call = new MatrixCall({
client: MatrixClientPeg.safeGet(),
@@ -49,7 +49,10 @@ describe("LegacyCallViewForRoom", () => {
const cli = MatrixClientPeg.safeGet();
cli.emit(CallEventHandlerEvent.Incoming, call);
const { rerender } = render(<LegacyCallViewForRoom roomId={call.roomId} />);
const { rerender } = render(
<LegacyCallViewForRoom roomId={call.roomId} />,
clientAndSDKContextRenderOptions(cli, sdkContext),
);
let props = LegacyCallViewMock.mock.lastCall![0];
expect(props.sidebarShown).toBeTruthy(); // Sidebar defaults to shown
@@ -84,9 +87,7 @@ describe("LegacyCallViewForRoom", () => {
addListener: jest.fn(),
removeListener: jest.fn(),
};
jest.spyOn(LegacyCallHandler, "instance", "get").mockImplementation(
() => callHandler as unknown as LegacyCallHandler,
);
sdkContext._LegacyCallHandler = callHandler as unknown as LegacyCallHandler;
jest.spyOn(sdkContext.resizeNotifier, "startResizing");
jest.spyOn(sdkContext.resizeNotifier, "stopResizing");
@@ -14,9 +14,9 @@ import { type MatrixClient, Room } from "matrix-js-sdk/src/matrix";
import * as AvatarModule from "../../../../../src/Avatar";
import VideoFeed from "../../../../../src/components/views/voip/VideoFeed";
import { stubClient, useMockedCalls } from "../../../../test-utils";
import type LegacyCallHandler from "../../../../../src/LegacyCallHandler";
import { clientAndSDKContextRenderOptions, stubClient, useMockedCalls } from "../../../../test-utils";
import DMRoomMap from "../../../../../src/utils/DMRoomMap";
import { TestSDKContext } from "../../../TestSDKContext.ts";
const FAKE_AVATAR_URL = "http://fakeurl.dummy/fake.png";
@@ -24,9 +24,12 @@ describe("VideoFeed", () => {
useMockedCalls();
let client: MatrixClient;
let sdkContext: TestSDKContext;
beforeAll(() => {
client = stubClient();
sdkContext = new TestSDKContext();
sdkContext._client = client;
(AvatarModule as any).avatarUrlForRoom = jest.fn().mockReturnValue(FAKE_AVATAR_URL);
const dmRoomMap = new DMRoomMap(client);
@@ -39,9 +42,7 @@ describe("VideoFeed", () => {
});
it("Displays the room avatar when no video is available", () => {
window.mxLegacyCallHandler = {
roomIdForCall: jest.fn().mockReturnValue("!this:room.here"),
} as unknown as LegacyCallHandler;
jest.spyOn(sdkContext.legacyCallHandler, "roomIdForCall").mockReturnValue("!this:room.here");
const mockCall = {
room: new Room("!room:example.com", client, client.getSafeUserId()),
@@ -53,7 +54,10 @@ describe("VideoFeed", () => {
addListener: jest.fn(),
removeListener: jest.fn(),
};
render(<VideoFeed feed={feed as unknown as CallFeed} call={mockCall as unknown as MatrixCall} />);
render(
<VideoFeed feed={feed as unknown as CallFeed} call={mockCall as unknown as MatrixCall} />,
clientAndSDKContextRenderOptions(client, sdkContext),
);
const avatarImg = screen.getByRole("presentation");
expect(avatarImg).toHaveAttribute("src", FAKE_AVATAR_URL);
});
@@ -34,6 +34,7 @@ import {
setupAsyncStoreWithClient,
resetAsyncStoreWithClient,
mkEvent,
clientAndSDKContextRenderOptions,
} from "../../test-utils";
import defaultDispatcher from "../../../src/dispatcher/dispatcher";
import { Action } from "../../../src/dispatcher/actions";
@@ -47,9 +48,10 @@ import {
getNotificationEventSendTs,
IncomingCallToast,
} from "../../../src/toasts/IncomingCallToast";
import LegacyCallHandler, { AudioID } from "../../../src/LegacyCallHandler";
import { AudioID } from "../../../src/LegacyCallHandler";
import { CallEvent } from "../../../src/models/Call";
import { type WidgetMessaging } from "../../../src/stores/widgets/WidgetMessaging";
import { TestSDKContext } from "../TestSDKContext.ts";
function makeNotificationEvent(room: Room, content: IContent = {}): MatrixEvent {
const ts = Date.now();
@@ -76,6 +78,7 @@ describe("IncomingCallToast", () => {
useMockedCalls();
let client: Mocked<MatrixClient>;
let sdkContext: TestSDKContext;
let room: Room;
let alice: RoomMember;
@@ -92,6 +95,8 @@ describe("IncomingCallToast", () => {
beforeEach(async () => {
stubClient();
client = mocked(MatrixClientPeg.safeGet());
sdkContext = new TestSDKContext();
sdkContext._client = client;
const audio = document.createElement("audio");
audio.id = AudioID.Ring;
@@ -146,6 +151,7 @@ describe("IncomingCallToast", () => {
notificationEvent={notificationEvent}
toastKey={getIncomingCallToastKey(callId, room.roomId)}
/>,
clientAndSDKContextRenderOptions(client, sdkContext),
);
return callId;
};
@@ -196,8 +202,11 @@ describe("IncomingCallToast", () => {
it("start ringing on ring notify event", () => {
const notificationEvent = makeNotificationEvent(room, { notification_type: "ring" });
const playMock = jest.spyOn(LegacyCallHandler.instance, "play");
render(<IncomingCallToast notificationEvent={notificationEvent} toastKey="" />);
const playMock = jest.spyOn(sdkContext.legacyCallHandler, "play");
render(
<IncomingCallToast notificationEvent={notificationEvent} toastKey="" />,
clientAndSDKContextRenderOptions(client, sdkContext),
);
expect(playMock).toHaveBeenCalled();
});
@@ -10,10 +10,15 @@ import { LOCAL_NOTIFICATION_SETTINGS_PREFIX, MatrixEvent, Room } from "matrix-js
import { MatrixCall } from "matrix-js-sdk/src/webrtc/call";
import React from "react";
import LegacyCallHandler from "../../../src/LegacyCallHandler";
import IncomingLegacyCallToast from "../../../src/toasts/IncomingLegacyCallToast";
import DMRoomMap from "../../../src/utils/DMRoomMap";
import { getMockClientWithEventEmitter, mockClientMethodsServer, mockClientMethodsUser } from "../../test-utils";
import {
clientAndSDKContextRenderOptions,
getMockClientWithEventEmitter,
mockClientMethodsServer,
mockClientMethodsUser,
} from "../../test-utils";
import { SDKContextClass } from "../../../src/contexts/SDKContextClass.ts";
describe("<IncomingLegacyCallToast />", () => {
const userId = "@alice:server.org";
@@ -41,16 +46,24 @@ describe("<IncomingLegacyCallToast />", () => {
jest.clearAllMocks();
mockClient.getAccountData.mockReturnValue(undefined);
mockClient.getRoom.mockReturnValue(mockRoom);
// @ts-ignore
SDKContextClass.instance._client = mockClient;
});
it("renders when silence button when call is not silenced", () => {
const { getByLabelText } = render(getComponent());
const { getByLabelText } = render(
getComponent(),
clientAndSDKContextRenderOptions(mockClient, SDKContextClass.instance),
);
expect(getByLabelText("Silence call")).toMatchSnapshot();
});
it("renders sound on button when call is silenced", () => {
LegacyCallHandler.instance.silenceCall(call.callId);
const { getByLabelText } = render(getComponent());
SDKContextClass.instance.legacyCallHandler.silenceCall(call.callId);
const { getByLabelText } = render(
getComponent(),
clientAndSDKContextRenderOptions(mockClient, SDKContextClass.instance),
);
expect(getByLabelText("Sound on")).toMatchSnapshot();
});
@@ -66,7 +79,10 @@ describe("<IncomingLegacyCallToast />", () => {
});
}
});
const { getByLabelText } = render(getComponent());
const { getByLabelText } = render(
getComponent(),
clientAndSDKContextRenderOptions(mockClient, SDKContextClass.instance),
);
expect(getByLabelText("Notifications silenced")).toMatchSnapshot();
});
});
@@ -13,6 +13,7 @@ import { shouldShowComponent } from "../../../src/customisations/helpers/UICompo
import defaultDispatcher from "../../../src/dispatcher/dispatcher";
import { Action } from "../../../src/dispatcher/actions";
import LegacyCallHandler, { LegacyCallHandlerEvent } from "../../../src/LegacyCallHandler";
import { TestSDKContext } from "../../unit-tests/TestSDKContext.ts";
jest.mock("../../../src/customisations/helpers/UIComponents", () => ({
shouldShowComponent: jest.fn(),
@@ -23,9 +24,12 @@ jest.mock("../../../src/PosthogTrackers", () => ({
}));
describe("RoomListSearchViewModel", () => {
const context = new TestSDKContext();
beforeEach(() => {
mocked(shouldShowComponent).mockReturnValue(true);
jest.spyOn(LegacyCallHandler.instance, "getSupportsPstnProtocol").mockReturnValue(false);
context._LegacyCallHandler = new LegacyCallHandler(context);
jest.spyOn(context._LegacyCallHandler, "getSupportsPstnProtocol").mockReturnValue(false);
});
afterEach(() => {
@@ -35,35 +39,50 @@ describe("RoomListSearchViewModel", () => {
describe("snapshot", () => {
it("should show explore button in Home space when UIComponent.ExploreRooms is enabled", () => {
mocked(shouldShowComponent).mockReturnValue(true);
const vm = new RoomListSearchViewModel({ activeSpace: MetaSpace.Home });
const vm = new RoomListSearchViewModel({
activeSpace: MetaSpace.Home,
legacyCallHandler: context.legacyCallHandler,
});
expect(vm.getSnapshot().displayExploreButton).toBe(true);
});
it("should hide explore button when not in Home space", () => {
mocked(shouldShowComponent).mockReturnValue(true);
const vm = new RoomListSearchViewModel({ activeSpace: MetaSpace.VideoRooms });
const vm = new RoomListSearchViewModel({
activeSpace: MetaSpace.VideoRooms,
legacyCallHandler: context.legacyCallHandler,
});
expect(vm.getSnapshot().displayExploreButton).toBe(false);
});
it("should hide explore button when UIComponent.ExploreRooms is disabled", () => {
mocked(shouldShowComponent).mockReturnValue(false);
const vm = new RoomListSearchViewModel({ activeSpace: MetaSpace.Home });
const vm = new RoomListSearchViewModel({
activeSpace: MetaSpace.Home,
legacyCallHandler: context.legacyCallHandler,
});
expect(vm.getSnapshot().displayExploreButton).toBe(false);
});
it("should show dial button when PSTN protocol is supported", () => {
jest.spyOn(LegacyCallHandler.instance, "getSupportsPstnProtocol").mockReturnValue(true);
const vm = new RoomListSearchViewModel({ activeSpace: MetaSpace.Home });
jest.spyOn(context.legacyCallHandler, "getSupportsPstnProtocol").mockReturnValue(true);
const vm = new RoomListSearchViewModel({
activeSpace: MetaSpace.Home,
legacyCallHandler: context.legacyCallHandler,
});
expect(vm.getSnapshot().displayDialButton).toBe(true);
});
it("should hide dial button when PSTN protocol is not supported", () => {
jest.spyOn(LegacyCallHandler.instance, "getSupportsPstnProtocol").mockReturnValue(false);
const vm = new RoomListSearchViewModel({ activeSpace: MetaSpace.Home });
jest.spyOn(context.legacyCallHandler, "getSupportsPstnProtocol").mockReturnValue(false);
const vm = new RoomListSearchViewModel({
activeSpace: MetaSpace.Home,
legacyCallHandler: context.legacyCallHandler,
});
expect(vm.getSnapshot().displayDialButton).toBe(false);
});
@@ -72,7 +91,10 @@ describe("RoomListSearchViewModel", () => {
describe("actions", () => {
it("should fire OpenSpotlight action when onSearchClick is called", () => {
const fireSpy = jest.spyOn(defaultDispatcher, "fire");
const vm = new RoomListSearchViewModel({ activeSpace: MetaSpace.Home });
const vm = new RoomListSearchViewModel({
activeSpace: MetaSpace.Home,
legacyCallHandler: context.legacyCallHandler,
});
vm.onSearchClick();
expect(fireSpy).toHaveBeenCalledWith(Action.OpenSpotlight);
@@ -80,7 +102,10 @@ describe("RoomListSearchViewModel", () => {
it("should fire OpenDialPad action when onDialPadClick is called", () => {
const fireSpy = jest.spyOn(defaultDispatcher, "fire");
const vm = new RoomListSearchViewModel({ activeSpace: MetaSpace.Home });
const vm = new RoomListSearchViewModel({
activeSpace: MetaSpace.Home,
legacyCallHandler: context.legacyCallHandler,
});
vm.onDialPadClick();
expect(fireSpy).toHaveBeenCalledWith(Action.OpenDialPad);
@@ -88,7 +113,10 @@ describe("RoomListSearchViewModel", () => {
it("should fire ViewRoomDirectory action and track interaction when onExploreClick is called", () => {
const fireSpy = jest.spyOn(defaultDispatcher, "fire");
const vm = new RoomListSearchViewModel({ activeSpace: MetaSpace.Home });
const vm = new RoomListSearchViewModel({
activeSpace: MetaSpace.Home,
legacyCallHandler: context.legacyCallHandler,
});
const mockEvent = {} as React.MouseEvent<HTMLButtonElement>;
vm.onExploreClick(mockEvent);
@@ -98,14 +126,17 @@ describe("RoomListSearchViewModel", () => {
});
it("should update snapshot when PSTN protocol support changes", () => {
jest.spyOn(LegacyCallHandler.instance, "getSupportsPstnProtocol").mockReturnValue(false);
const vm = new RoomListSearchViewModel({ activeSpace: MetaSpace.Home });
jest.spyOn(context.legacyCallHandler, "getSupportsPstnProtocol").mockReturnValue(false);
const vm = new RoomListSearchViewModel({
activeSpace: MetaSpace.Home,
legacyCallHandler: context.legacyCallHandler,
});
expect(vm.getSnapshot().displayDialButton).toBe(false);
// Simulate PSTN protocol support change
jest.spyOn(LegacyCallHandler.instance, "getSupportsPstnProtocol").mockReturnValue(true);
LegacyCallHandler.instance.emit(LegacyCallHandlerEvent.ProtocolSupport);
jest.spyOn(context.legacyCallHandler, "getSupportsPstnProtocol").mockReturnValue(true);
context.legacyCallHandler.emit(LegacyCallHandlerEvent.ProtocolSupport);
expect(vm.getSnapshot().displayDialButton).toBe(true);