mirror of
https://github.com/vector-im/element-call.git
synced 2026-07-30 19:39:22 +00:00
Add test for the media switch button:
- is it available in the bottom bar - does pressing it trigger the expected callback
This commit is contained in:
@@ -15,20 +15,17 @@ import {
|
|||||||
vi,
|
vi,
|
||||||
} from "vitest";
|
} from "vitest";
|
||||||
import { render, type RenderResult } from "@testing-library/react";
|
import { render, type RenderResult } from "@testing-library/react";
|
||||||
import { type MatrixClient, JoinRule, type RoomState } from "matrix-js-sdk";
|
|
||||||
import { type RelationsContainer } from "matrix-js-sdk/lib/models/relations-container";
|
|
||||||
import { type LocalParticipant } from "livekit-client";
|
import { type LocalParticipant } from "livekit-client";
|
||||||
import { of } from "rxjs";
|
import { BehaviorSubject, of } from "rxjs";
|
||||||
import { BrowserRouter } from "react-router-dom";
|
import { BrowserRouter } from "react-router-dom";
|
||||||
import { TooltipProvider } from "@vector-im/compound-web";
|
import { TooltipProvider } from "@vector-im/compound-web";
|
||||||
import { RoomContext, useLocalParticipant } from "@livekit/components-react";
|
import { RoomContext, useLocalParticipant } from "@livekit/components-react";
|
||||||
|
import userEvent from "@testing-library/user-event";
|
||||||
|
|
||||||
import { InCallView } from "./InCallView";
|
import { InCallView } from "./InCallView";
|
||||||
import {
|
import {
|
||||||
mockLivekitRoom,
|
mockLivekitRoom,
|
||||||
mockLocalParticipant,
|
mockLocalParticipant,
|
||||||
mockMatrixRoom,
|
|
||||||
mockMatrixRoomMember,
|
|
||||||
mockMediaDevices,
|
mockMediaDevices,
|
||||||
mockMuteStates,
|
mockMuteStates,
|
||||||
mockRemoteParticipant,
|
mockRemoteParticipant,
|
||||||
@@ -43,7 +40,9 @@ 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 MediaDevices as ECMediaDevices } from "../state/MediaDevices";
|
||||||
import { initializeWidget } from "../widget";
|
import { initializeWidget } from "../widget";
|
||||||
|
|
||||||
initializeWidget();
|
initializeWidget();
|
||||||
vi.hoisted(
|
vi.hoisted(
|
||||||
() =>
|
() =>
|
||||||
@@ -71,10 +70,7 @@ const localParticipant = mockLocalParticipant({
|
|||||||
const remoteParticipant = mockRemoteParticipant({
|
const remoteParticipant = mockRemoteParticipant({
|
||||||
identity: "@alice:example.org:AAAAAA",
|
identity: "@alice:example.org:AAAAAA",
|
||||||
});
|
});
|
||||||
const carol = mockMatrixRoomMember(localRtcMember);
|
|
||||||
const roomMembers = new Map([carol].map((p) => [p.userId, p]));
|
|
||||||
|
|
||||||
const roomId = "!foo:bar";
|
|
||||||
let useRoomEncryptionSystemMock: MockedFunction<typeof useRoomEncryptionSystem>;
|
let useRoomEncryptionSystemMock: MockedFunction<typeof useRoomEncryptionSystem>;
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
@@ -99,36 +95,12 @@ beforeEach(() => {
|
|||||||
useRoomEncryptionSystem as typeof useRoomEncryptionSystemMock;
|
useRoomEncryptionSystem as typeof useRoomEncryptionSystemMock;
|
||||||
useRoomEncryptionSystemMock.mockReturnValue({ kind: E2eeType.NONE });
|
useRoomEncryptionSystemMock.mockReturnValue({ kind: E2eeType.NONE });
|
||||||
});
|
});
|
||||||
|
interface CreateInCallViewArgs {
|
||||||
function createInCallView(): RenderResult & {
|
mediaDevices?: ECMediaDevices;
|
||||||
|
}
|
||||||
|
function createInCallView(args: CreateInCallViewArgs = {}): RenderResult & {
|
||||||
rtcSession: MockRTCSession;
|
rtcSession: MockRTCSession;
|
||||||
} {
|
} {
|
||||||
const client = {
|
|
||||||
getUser: () => null,
|
|
||||||
getUserId: () => localRtcMember.userId,
|
|
||||||
getDeviceId: () => localRtcMember.deviceId,
|
|
||||||
getRoom: (rId) => (rId === roomId ? room : null),
|
|
||||||
getDomain: () => "example.com",
|
|
||||||
} as Partial<MatrixClient> as MatrixClient;
|
|
||||||
const room = mockMatrixRoom({
|
|
||||||
relations: {
|
|
||||||
getChildEventsForEvent: () =>
|
|
||||||
vi.mocked({
|
|
||||||
getRelations: () => [],
|
|
||||||
}),
|
|
||||||
} as unknown as RelationsContainer,
|
|
||||||
client,
|
|
||||||
roomId,
|
|
||||||
// getMember: (userId) => roomMembers.get(userId) ?? null,
|
|
||||||
getMembers: () => Array.from(roomMembers.values()),
|
|
||||||
getMxcAvatarUrl: () => null,
|
|
||||||
hasEncryptionStateEvent: vi.fn().mockReturnValue(true),
|
|
||||||
getCanonicalAlias: () => null,
|
|
||||||
currentState: {
|
|
||||||
getJoinRule: () => JoinRule.Invite,
|
|
||||||
} as Partial<RoomState> as RoomState,
|
|
||||||
});
|
|
||||||
|
|
||||||
const muteState = mockMuteStates();
|
const muteState = mockMuteStates();
|
||||||
const livekitRoom = mockLivekitRoom(
|
const livekitRoom = mockLivekitRoom(
|
||||||
{
|
{
|
||||||
@@ -138,12 +110,19 @@ function createInCallView(): RenderResult & {
|
|||||||
remoteParticipants$: of([remoteParticipant]),
|
remoteParticipants$: of([remoteParticipant]),
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
const { vm, rtcSession } = getBasicCallViewModelEnvironment([local, alice]);
|
const { vm, rtcSession } = getBasicCallViewModelEnvironment(
|
||||||
|
[local, alice],
|
||||||
|
undefined,
|
||||||
|
{},
|
||||||
|
args.mediaDevices,
|
||||||
|
);
|
||||||
|
|
||||||
rtcSession.joined = true;
|
rtcSession.joined = true;
|
||||||
|
const room = rtcSession.room;
|
||||||
|
const client = room.client;
|
||||||
const renderResult = render(
|
const renderResult = render(
|
||||||
<BrowserRouter>
|
<BrowserRouter>
|
||||||
<MediaDevicesContext value={mockMediaDevices({})}>
|
<MediaDevicesContext value={args.mediaDevices ?? mockMediaDevices({})}>
|
||||||
<ReactionsSenderProvider
|
<ReactionsSenderProvider
|
||||||
vm={vm}
|
vm={vm}
|
||||||
rtcSession={rtcSession.asMockedSession()}
|
rtcSession={rtcSession.asMockedSession()}
|
||||||
@@ -190,4 +169,41 @@ describe("InCallView", () => {
|
|||||||
expect(container).toMatchSnapshot();
|
expect(container).toMatchSnapshot();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
describe("audioOutputSwitcher", () => {
|
||||||
|
it("is visible and can be clicked", async () => {
|
||||||
|
const user = userEvent.setup();
|
||||||
|
const switchFn = vi.fn();
|
||||||
|
// Create mediaDevices with a speaker and an earpiece available,
|
||||||
|
// with the speaker currently selected.
|
||||||
|
// This is needed so that the audio switcher button is visible
|
||||||
|
const available$ = new BehaviorSubject(
|
||||||
|
new Map<string, { type: "speaker" } | { type: "earpiece" }>([
|
||||||
|
["speaker-id", { type: "speaker" }],
|
||||||
|
["earpiece-id", { type: "earpiece" }],
|
||||||
|
]),
|
||||||
|
);
|
||||||
|
const selected$ = new BehaviorSubject<
|
||||||
|
{ id: string; virtualEarpiece: boolean } | undefined
|
||||||
|
>({ id: "speaker-id", virtualEarpiece: false });
|
||||||
|
|
||||||
|
const mediaDevices = mockMediaDevices({
|
||||||
|
audioOutput: {
|
||||||
|
available$,
|
||||||
|
selected$,
|
||||||
|
select: switchFn,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const { getByRole } = createInCallView({ mediaDevices });
|
||||||
|
// The button should be visible. When current output is "speaker",
|
||||||
|
// the switcher targets "earpiece", so the tooltip label is "Handset".
|
||||||
|
const audioOutputBtn = getByRole("button", { name: "Handset" });
|
||||||
|
expect(audioOutputBtn).toBeVisible();
|
||||||
|
|
||||||
|
await user.click(audioOutputBtn);
|
||||||
|
|
||||||
|
// Clicking the button should call select -> switchFn with the earpiece device id
|
||||||
|
expect(switchFn).toHaveBeenCalledWith("earpiece-id");
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -632,8 +632,8 @@ export const InCallView: FC<InCallViewProps> = ({
|
|||||||
return (
|
return (
|
||||||
<Tooltip label={label}>
|
<Tooltip label={label}>
|
||||||
<IconButton
|
<IconButton
|
||||||
|
key="audio_output_switcher"
|
||||||
onClick={(e) => {
|
onClick={(e) => {
|
||||||
e.preventDefault();
|
|
||||||
audioOutputSwitcher.switch();
|
audioOutputSwitcher.switch();
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
|
|||||||
@@ -34,6 +34,7 @@ import {
|
|||||||
MockRTCSession,
|
MockRTCSession,
|
||||||
testScope,
|
testScope,
|
||||||
} from "./test";
|
} from "./test";
|
||||||
|
import { MediaDevices } from "../state/MediaDevices";
|
||||||
import { aliceRtcMember, localRtcMember } from "./test-fixtures";
|
import { aliceRtcMember, localRtcMember } from "./test-fixtures";
|
||||||
import { type RaisedHandInfo, type ReactionInfo } from "../reactions";
|
import { type RaisedHandInfo, type ReactionInfo } from "../reactions";
|
||||||
import { constant } from "../state/Behavior";
|
import { constant } from "../state/Behavior";
|
||||||
@@ -63,6 +64,7 @@ export function getBasicRTCSession(
|
|||||||
getDeviceId: () => localRtcMember.deviceId,
|
getDeviceId: () => localRtcMember.deviceId,
|
||||||
getSyncState: () => SyncState.Syncing,
|
getSyncState: () => SyncState.Syncing,
|
||||||
getDomain: () => null,
|
getDomain: () => null,
|
||||||
|
getAccessToken: () => "fake-token",
|
||||||
sendEvent: vitest.fn().mockResolvedValue({ event_id: "$fake:event" }),
|
sendEvent: vitest.fn().mockResolvedValue({ event_id: "$fake:event" }),
|
||||||
redactEvent: vitest.fn().mockResolvedValue({ event_id: "$fake:event" }),
|
redactEvent: vitest.fn().mockResolvedValue({ event_id: "$fake:event" }),
|
||||||
decryptEventIfNeeded: vitest.fn().mockResolvedValue(undefined),
|
decryptEventIfNeeded: vitest.fn().mockResolvedValue(undefined),
|
||||||
@@ -131,6 +133,7 @@ export function getBasicCallViewModelEnvironment(
|
|||||||
members: RoomMember[],
|
members: RoomMember[],
|
||||||
initialRtcMemberships: CallMembership[] = [localRtcMember, aliceRtcMember],
|
initialRtcMemberships: CallMembership[] = [localRtcMember, aliceRtcMember],
|
||||||
callViewModelOptions: Partial<CallViewModelOptions> = {},
|
callViewModelOptions: Partial<CallViewModelOptions> = {},
|
||||||
|
mediaDevicesOverride?: MediaDevices,
|
||||||
): {
|
): {
|
||||||
vm: CallViewModel;
|
vm: CallViewModel;
|
||||||
rtcMemberships$: BehaviorSubject<CallMembership[]>;
|
rtcMemberships$: BehaviorSubject<CallMembership[]>;
|
||||||
@@ -151,7 +154,7 @@ export function getBasicCallViewModelEnvironment(
|
|||||||
testScope(),
|
testScope(),
|
||||||
rtcSession.asMockedSession(),
|
rtcSession.asMockedSession(),
|
||||||
matrixRoom,
|
matrixRoom,
|
||||||
mockMediaDevices({}),
|
mediaDevicesOverride ?? mockMediaDevices({}),
|
||||||
mockMuteStates(),
|
mockMuteStates(),
|
||||||
{
|
{
|
||||||
encryptionSystem: { kind: E2eeType.PER_PARTICIPANT },
|
encryptionSystem: { kind: E2eeType.PER_PARTICIPANT },
|
||||||
|
|||||||
Reference in New Issue
Block a user