From fc6f0db782f567f4a716a2c6c671144e46eec7ff Mon Sep 17 00:00:00 2001 From: Half-Shot Date: Tue, 10 Dec 2024 10:48:48 +0000 Subject: [PATCH] Test widget mode too. --- src/room/GroupCallView.test.tsx | 62 +++++++++++++++++++++++++-------- src/room/GroupCallView.tsx | 27 +++++++++----- src/room/RoomPage.tsx | 1 + src/utils/test.ts | 8 ++++- 4 files changed, 73 insertions(+), 25 deletions(-) diff --git a/src/room/GroupCallView.test.tsx b/src/room/GroupCallView.test.tsx index c9e6f366..354f87a6 100644 --- a/src/room/GroupCallView.test.tsx +++ b/src/room/GroupCallView.test.tsx @@ -26,36 +26,36 @@ import { MockRTCSession, } from "../utils/test"; import { GroupCallView } from "./GroupCallView"; +import { leaveRTCSession } from "../rtcSessionHelpers"; +import { WidgetHelpers } from "../widget"; +import { LazyEventEmitter } from "../LazyEventEmitter"; vitest.mock("../soundUtils"); vitest.mock("../useAudioContext"); vitest.mock("./InCallView"); +vitest.mock("../rtcSessionHelpers", async (importOriginal) => { + const orig = await importOriginal(); + vitest.spyOn(orig, "leaveRTCSession"); + return orig; +}); + let playSound: MockedFunction< NonNullable>["playSound"] >; const localRtcMember = mockRtcMembership("@carol:example.org", "CCCC"); -const aliceRtcMember = mockRtcMembership("@alice:example.org", "AAAA"); -const bobRtcMember = mockRtcMembership("@bob:example.org", "BBBB"); -const daveRtcMember = mockRtcMembership("@dave:example.org", "DDDD"); - -const alice = mockMatrixRoomMember(aliceRtcMember); -const bob = mockMatrixRoomMember(bobRtcMember); const carol = mockMatrixRoomMember(localRtcMember); -const dave = mockMatrixRoomMember(daveRtcMember); +const roomMembers = new Map([carol].map((p) => [p.userId, p])); const roomId = "!foo:bar"; - -const roomMembers = new Map( - [alice, bob, carol, dave].map((p) => [p.userId, p]), -); +const soundPromise = Promise.resolve(true); beforeEach(() => { (prefetchSounds as MockedFunction).mockResolvedValue({ sound: new ArrayBuffer(0), }); - playSound = vitest.fn().mockResolvedValue(undefined); + playSound = vitest.fn().mockReturnValue(soundPromise); (useAudioContext as MockedFunction).mockReturnValue({ playSound, }); @@ -71,8 +71,10 @@ beforeEach(() => { ); }); -test("a leave sound should be played when the user leaves the call", async () => { - const user = userEvent.setup(); +function createGroupCallView(widget: WidgetHelpers | null): { + rtcSession: MockRTCSession; + getByText: ReturnType["getByText"]; +} { const history = createBrowserHistory(); const client = { getUser: () => null, @@ -94,7 +96,7 @@ test("a leave sound should be played when the user leaves the call", async () => room, localRtcMember, [], - ).withMemberships(of([aliceRtcMember, bobRtcMember])); + ).withMemberships(of([])); const muteState = { audio: { enabled: false }, video: { enabled: false }, @@ -110,10 +112,40 @@ test("a leave sound should be played when the user leaves the call", async () => hideHeader={true} rtcSession={rtcSession as unknown as MatrixRTCSession} muteStates={muteState} + widget={widget} /> , ); + return { + getByText, + rtcSession, + }; +} + +test("will play a leave sound asynchronously in SPA mode", async () => { + const user = userEvent.setup(); + const { getByText, rtcSession } = createGroupCallView(null); + const leaveButton = getByText("Leave"); + await user.click(leaveButton); + expect(playSound).toHaveBeenCalledWith("left"); + expect(leaveRTCSession).toHaveBeenCalledWith(rtcSession, undefined); + expect(rtcSession.leaveRoomSession).toHaveBeenCalledOnce(); +}); + +test("will play a leave sound synchronously in widget mode", async () => { + const user = userEvent.setup(); + const widget = { + api: { + setAlwaysOnScreen: async () => Promise.resolve(true), + } as Partial, + lazyActions: new LazyEventEmitter(), + }; + const { getByText, rtcSession } = createGroupCallView( + widget as WidgetHelpers, + ); const leaveButton = getByText("Leave"); await user.click(leaveButton); expect(playSound).toHaveBeenCalledWith("left"); + expect(leaveRTCSession).toHaveBeenCalledWith(rtcSession, soundPromise); + expect(rtcSession.leaveRoomSession).toHaveBeenCalledOnce(); }); diff --git a/src/room/GroupCallView.tsx b/src/room/GroupCallView.tsx index 86f9badc..e2476e9f 100644 --- a/src/room/GroupCallView.tsx +++ b/src/room/GroupCallView.tsx @@ -19,7 +19,7 @@ import { Heading, Text } from "@vector-im/compound-web"; import { useTranslation } from "react-i18next"; import type { IWidgetApiRequest } from "matrix-widget-api"; -import { widget, ElementWidgetActions, JoinCallData } from "../widget"; +import { ElementWidgetActions, JoinCallData, WidgetHelpers } from "../widget"; import { FullScreenView } from "../FullScreenView"; import { LobbyView } from "./LobbyView"; import { MatrixInfo } from "./VideoPreview"; @@ -60,6 +60,7 @@ interface Props { hideHeader: boolean; rtcSession: MatrixRTCSession; muteStates: MuteStates; + widget: WidgetHelpers | null; } export const GroupCallView: FC = ({ @@ -71,6 +72,7 @@ export const GroupCallView: FC = ({ hideHeader, rtcSession, muteStates, + widget, }) => { const memberships = useMatrixRTCSessionMemberships(rtcSession); const isJoined = useMatrixRTCSessionJoinState(rtcSession); @@ -193,14 +195,14 @@ export const GroupCallView: FC = ({ ev.detail.data as unknown as JoinCallData, ); await enterRTCSession(rtcSession, perParticipantE2EE); - widget!.api.transport.reply(ev.detail, {}); + widget.api.transport.reply(ev.detail, {}); })().catch((e) => { logger.error("Error joining RTC session", e); }); }; widget.lazyActions.on(ElementWidgetActions.JoinCall, onJoin); return (): void => { - widget!.lazyActions.off(ElementWidgetActions.JoinCall, onJoin); + widget.lazyActions.off(ElementWidgetActions.JoinCall, onJoin); }; } else { // No lobby and no preload: we enter the rtc session right away @@ -214,7 +216,7 @@ export const GroupCallView: FC = ({ void enterRTCSession(rtcSession, perParticipantE2EE); } } - }, [rtcSession, preload, skipLobby, perParticipantE2EE]); + }, [widget, rtcSession, preload, skipLobby, perParticipantE2EE]); const [left, setLeft] = useState(false); const [leaveError, setLeaveError] = useState(undefined); @@ -254,18 +256,25 @@ export const GroupCallView: FC = ({ logger.error("Error leaving RTC session", e); }); }, - [rtcSession, isPasswordlessUser, confineToRoom, leaveSoundContext, history], + [ + widget, + rtcSession, + isPasswordlessUser, + confineToRoom, + leaveSoundContext, + history, + ], ); useEffect(() => { if (widget && isJoined) { // set widget to sticky once joined. - widget!.api.setAlwaysOnScreen(true).catch((e) => { + widget.api.setAlwaysOnScreen(true).catch((e) => { logger.error("Error calling setAlwaysOnScreen(true)", e); }); const onHangup = (ev: CustomEvent): void => { - widget!.api.transport.reply(ev.detail, {}); + widget.api.transport.reply(ev.detail, {}); // Only sends matrix leave event. The Livekit session will disconnect once the ActiveCall-view unmounts. leaveRTCSession(rtcSession).catch((e) => { logger.error("Failed to leave RTC session", e); @@ -273,10 +282,10 @@ export const GroupCallView: FC = ({ }; widget.lazyActions.once(ElementWidgetActions.HangupCall, onHangup); return (): void => { - widget!.lazyActions.off(ElementWidgetActions.HangupCall, onHangup); + widget.lazyActions.off(ElementWidgetActions.HangupCall, onHangup); }; } - }, [isJoined, rtcSession]); + }, [widget, isJoined, rtcSession]); const onReconnect = useCallback(() => { setLeft(false); diff --git a/src/room/RoomPage.tsx b/src/room/RoomPage.tsx index 49d594bb..ce71f3aa 100644 --- a/src/room/RoomPage.tsx +++ b/src/room/RoomPage.tsx @@ -98,6 +98,7 @@ export const RoomPage: FC = () => { case "loaded": return ( { + public readonly statistics = { + counters: {}, + }; + + public leaveRoomSession = vitest.fn().mockResolvedValue(undefined); + public constructor( public readonly room: Room, private localMembership: CallMembership,