mirror of
https://github.com/vector-im/element-call.git
synced 2026-08-29 21:15:19 +00:00
All the test bits and pieces
This commit is contained in:
@@ -5,47 +5,45 @@ SPDX-License-Identifier: AGPL-3.0-only
|
|||||||
Please see LICENSE in the repository root for full details.
|
Please see LICENSE in the repository root for full details.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { render } from "@testing-library/react";
|
import { act, render } from "@testing-library/react";
|
||||||
import { expect, test } from "vitest";
|
import { expect, test } from "vitest";
|
||||||
import { TooltipProvider } from "@vector-im/compound-web";
|
import { TooltipProvider } from "@vector-im/compound-web";
|
||||||
import { userEvent } from "@testing-library/user-event";
|
import { userEvent } from "@testing-library/user-event";
|
||||||
import { ReactNode } from "react";
|
import { ReactNode } from "react";
|
||||||
|
|
||||||
import {
|
import { MockRoom } from "../utils/testReactions";
|
||||||
MockRoom,
|
|
||||||
MockRTCSession,
|
|
||||||
TestReactionsWrapper,
|
|
||||||
} from "../utils/testReactions";
|
|
||||||
import { ReactionToggleButton } from "./ReactionToggleButton";
|
import { ReactionToggleButton } from "./ReactionToggleButton";
|
||||||
import { ElementCallReactionEventType } from "../reactions";
|
import { ElementCallReactionEventType } from "../reactions";
|
||||||
|
import { CallViewModel } from "../state/CallViewModel";
|
||||||
|
import { getBasicCallViewModelEnvironment } from "../utils/test-viewmodel";
|
||||||
|
import { alice, local, localRtcMember } from "../utils/test-fixtures";
|
||||||
|
import { MockRTCSession } from "../utils/test";
|
||||||
|
import { ReactionsProvider } from "../useReactions";
|
||||||
|
import { MatrixRTCSession } from "matrix-js-sdk/src/matrixrtc/MatrixRTCSession";
|
||||||
|
|
||||||
const memberUserIdAlice = "@alice:example.org";
|
const localIdent = `${localRtcMember.sender}:${localRtcMember.deviceId}`;
|
||||||
const memberEventAlice = "$membership-alice:example.org";
|
|
||||||
|
|
||||||
const membership: Record<string, string> = {
|
|
||||||
[memberEventAlice]: memberUserIdAlice,
|
|
||||||
};
|
|
||||||
|
|
||||||
function TestComponent({
|
function TestComponent({
|
||||||
rtcSession,
|
rtcSession,
|
||||||
|
vm,
|
||||||
}: {
|
}: {
|
||||||
rtcSession: MockRTCSession;
|
rtcSession: MockRTCSession;
|
||||||
|
vm: CallViewModel;
|
||||||
}): ReactNode {
|
}): ReactNode {
|
||||||
return (
|
return (
|
||||||
<TooltipProvider>
|
<TooltipProvider>
|
||||||
<TestReactionsWrapper rtcSession={rtcSession}>
|
<ReactionsProvider rtcSession={rtcSession as unknown as MatrixRTCSession}>
|
||||||
<ReactionToggleButton userId={memberUserIdAlice} />
|
<ReactionToggleButton vm={vm} identifier={localIdent} />
|
||||||
</TestReactionsWrapper>
|
</ReactionsProvider>
|
||||||
</TooltipProvider>
|
</TooltipProvider>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
test("Can open menu", async () => {
|
test("Can open menu", async () => {
|
||||||
const user = userEvent.setup();
|
const user = userEvent.setup();
|
||||||
const room = new MockRoom(memberUserIdAlice);
|
const { vm, rtcSession } = getBasicCallViewModelEnvironment([alice]);
|
||||||
const rtcSession = new MockRTCSession(room, membership);
|
|
||||||
const { getByLabelText, container } = render(
|
const { getByLabelText, container } = render(
|
||||||
<TestComponent rtcSession={rtcSession} />,
|
<TestComponent vm={vm} rtcSession={rtcSession} />,
|
||||||
);
|
);
|
||||||
await user.click(getByLabelText("common.reactions"));
|
await user.click(getByLabelText("common.reactions"));
|
||||||
expect(container).toMatchSnapshot();
|
expect(container).toMatchSnapshot();
|
||||||
@@ -53,40 +51,68 @@ test("Can open menu", async () => {
|
|||||||
|
|
||||||
test("Can raise hand", async () => {
|
test("Can raise hand", async () => {
|
||||||
const user = userEvent.setup();
|
const user = userEvent.setup();
|
||||||
const room = new MockRoom(memberUserIdAlice);
|
const { vm, rtcSession } = getBasicCallViewModelEnvironment([local, alice]);
|
||||||
const rtcSession = new MockRTCSession(room, membership);
|
|
||||||
const { getByLabelText, container } = render(
|
const { getByLabelText, container } = render(
|
||||||
<TestComponent rtcSession={rtcSession} />,
|
<TestComponent vm={vm} rtcSession={rtcSession} />,
|
||||||
);
|
);
|
||||||
await user.click(getByLabelText("common.reactions"));
|
await user.click(getByLabelText("common.reactions"));
|
||||||
await user.click(getByLabelText("action.raise_hand"));
|
await user.click(getByLabelText("action.raise_hand"));
|
||||||
expect(room.testSentEvents).toEqual([
|
expect(rtcSession.room.client.sendEvent).toHaveBeenCalledWith(
|
||||||
[
|
undefined,
|
||||||
undefined,
|
"m.reaction",
|
||||||
"m.reaction",
|
{
|
||||||
{
|
"m.relates_to": {
|
||||||
"m.relates_to": {
|
event_id: localRtcMember.eventId,
|
||||||
event_id: memberEventAlice,
|
key: "🖐️",
|
||||||
key: "🖐️",
|
rel_type: "m.annotation",
|
||||||
rel_type: "m.annotation",
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
],
|
},
|
||||||
]);
|
);
|
||||||
|
await act(() => {
|
||||||
|
vm.updateReactions({
|
||||||
|
raisedHands: {
|
||||||
|
[localIdent]: new Date(),
|
||||||
|
},
|
||||||
|
reactions: {},
|
||||||
|
});
|
||||||
|
});
|
||||||
expect(container).toMatchSnapshot();
|
expect(container).toMatchSnapshot();
|
||||||
});
|
});
|
||||||
|
|
||||||
test("Can lower hand", async () => {
|
test.only("Can lower hand", async () => {
|
||||||
const user = userEvent.setup();
|
const user = userEvent.setup();
|
||||||
const room = new MockRoom(memberUserIdAlice);
|
const { vm, rtcSession } = getBasicCallViewModelEnvironment([local, alice]);
|
||||||
const rtcSession = new MockRTCSession(room, membership);
|
|
||||||
const { getByLabelText, container } = render(
|
const { getByLabelText, container } = render(
|
||||||
<TestComponent rtcSession={rtcSession} />,
|
<TestComponent vm={vm} rtcSession={rtcSession} />,
|
||||||
);
|
);
|
||||||
const reactionEvent = room.testSendHandRaise(memberEventAlice, membership);
|
|
||||||
await user.click(getByLabelText("common.reactions"));
|
await user.click(getByLabelText("common.reactions"));
|
||||||
|
await user.click(getByLabelText("action.raise_hand"));
|
||||||
|
await act(() => {
|
||||||
|
vm.updateReactions({
|
||||||
|
raisedHands: {
|
||||||
|
[localIdent]: new Date(),
|
||||||
|
},
|
||||||
|
reactions: {},
|
||||||
|
});
|
||||||
|
});
|
||||||
await user.click(getByLabelText("action.lower_hand"));
|
await user.click(getByLabelText("action.lower_hand"));
|
||||||
expect(room.testRedactedEvents).toEqual([[undefined, reactionEvent]]);
|
expect(rtcSession.room.client.redactEvent).toHaveBeenCalledWith(
|
||||||
|
undefined,
|
||||||
|
"m.reaction",
|
||||||
|
{
|
||||||
|
"m.relates_to": {
|
||||||
|
event_id: localRtcMember.eventId,
|
||||||
|
key: "🖐️",
|
||||||
|
rel_type: "m.annotation",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
);
|
||||||
|
await act(() => {
|
||||||
|
vm.updateReactions({
|
||||||
|
raisedHands: {},
|
||||||
|
reactions: {},
|
||||||
|
});
|
||||||
|
});
|
||||||
expect(container).toMatchSnapshot();
|
expect(container).toMatchSnapshot();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -137,9 +137,9 @@ exports[`Can raise hand 1`] = `
|
|||||||
aria-disabled="false"
|
aria-disabled="false"
|
||||||
aria-expanded="false"
|
aria-expanded="false"
|
||||||
aria-haspopup="true"
|
aria-haspopup="true"
|
||||||
aria-labelledby=":r1j:"
|
aria-labelledby=":r0:"
|
||||||
class="_button_i91xf_17 _has-icon_i91xf_66 _icon-only_i91xf_59"
|
class="_button_i91xf_17 raisedButton _has-icon_i91xf_66 _icon-only_i91xf_59"
|
||||||
data-kind="secondary"
|
data-kind="primary"
|
||||||
data-size="lg"
|
data-size="lg"
|
||||||
role="button"
|
role="button"
|
||||||
tabindex="0"
|
tabindex="0"
|
||||||
@@ -153,9 +153,7 @@ exports[`Can raise hand 1`] = `
|
|||||||
xmlns="http://www.w3.org/2000/svg"
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
>
|
>
|
||||||
<path
|
<path
|
||||||
clip-rule="evenodd"
|
d="M11 3a1 1 0 1 1 2 0v8.5a.5.5 0 0 0 1 0V4a1 1 0 1 1 2 0v10.2l3.284-2.597a1.081 1.081 0 0 1 1.47 1.577c-.613.673-1.214 1.367-1.818 2.064-1.267 1.463-2.541 2.934-3.944 4.235A6 6 0 0 1 5 15V7a1 1 0 0 1 2 0v5.5a.5.5 0 0 0 1 0V4a1 1 0 0 1 2 0v7.5a.5.5 0 0 0 1 0V3Z"
|
||||||
d="M12 22c5.523 0 10-4.477 10-10S17.523 2 12 2 2 6.477 2 12s4.477 10 10 10Zm3.536-6.464a1 1 0 0 0-1.415-1.415A2.988 2.988 0 0 1 12 15a2.988 2.988 0 0 1-2.121-.879 1 1 0 1 0-1.414 1.415A4.987 4.987 0 0 0 12 17c1.38 0 2.632-.56 3.536-1.464ZM10 10.5a1.5 1.5 0 1 1-3 0 1.5 1.5 0 0 1 3 0Zm5.5 1.5a1.5 1.5 0 1 0 0-3 1.5 1.5 0 0 0 0 3Z"
|
|
||||||
fill-rule="evenodd"
|
|
||||||
/>
|
/>
|
||||||
</svg>
|
</svg>
|
||||||
</button>
|
</button>
|
||||||
|
|||||||
@@ -14,44 +14,24 @@ import {
|
|||||||
test,
|
test,
|
||||||
vitest,
|
vitest,
|
||||||
} from "vitest";
|
} from "vitest";
|
||||||
import { MatrixClient } from "matrix-js-sdk/src/client";
|
|
||||||
import { ConnectionState } from "livekit-client";
|
|
||||||
import { BehaviorSubject, of } from "rxjs";
|
|
||||||
import { afterEach } from "node:test";
|
import { afterEach } from "node:test";
|
||||||
import { act, ReactNode } from "react";
|
import { act } from "react";
|
||||||
import {
|
import { CallMembership } from "matrix-js-sdk/src/matrixrtc";
|
||||||
CallMembership,
|
|
||||||
type MatrixRTCSession,
|
|
||||||
} from "matrix-js-sdk/src/matrixrtc";
|
|
||||||
import { RoomMember } from "matrix-js-sdk/src/matrix";
|
|
||||||
|
|
||||||
import {
|
import { mockRtcMembership } from "../utils/test";
|
||||||
mockLivekitRoom,
|
|
||||||
mockLocalParticipant,
|
|
||||||
mockMatrixRoom,
|
|
||||||
mockMatrixRoomMember,
|
|
||||||
mockRemoteParticipant,
|
|
||||||
mockRtcMembership,
|
|
||||||
MockRTCSession,
|
|
||||||
} from "../utils/test";
|
|
||||||
import { E2eeType } from "../e2ee/e2eeType";
|
|
||||||
import { CallViewModel } from "../state/CallViewModel";
|
|
||||||
import {
|
import {
|
||||||
CallEventAudioRenderer,
|
CallEventAudioRenderer,
|
||||||
MAX_PARTICIPANT_COUNT_FOR_SOUND,
|
MAX_PARTICIPANT_COUNT_FOR_SOUND,
|
||||||
} from "./CallEventAudioRenderer";
|
} from "./CallEventAudioRenderer";
|
||||||
import { useAudioContext } from "../useAudioContext";
|
import { useAudioContext } from "../useAudioContext";
|
||||||
import { TestReactionsWrapper } from "../utils/testReactions";
|
|
||||||
import { prefetchSounds } from "../soundUtils";
|
import { prefetchSounds } from "../soundUtils";
|
||||||
|
import { getBasicCallViewModelEnvironment } from "../utils/test-viewmodel";
|
||||||
const localRtcMember = mockRtcMembership("@carol:example.org", "CCCC");
|
import {
|
||||||
const local = mockMatrixRoomMember(localRtcMember);
|
alice,
|
||||||
const aliceRtcMember = mockRtcMembership("@alice:example.org", "AAAA");
|
aliceRtcMember,
|
||||||
const alice = mockMatrixRoomMember(aliceRtcMember);
|
bobRtcMember,
|
||||||
const bobRtcMember = mockRtcMembership("@bob:example.org", "BBBB");
|
local,
|
||||||
const localParticipant = mockLocalParticipant({ identity: "" });
|
} from "../utils/test-fixtures";
|
||||||
const aliceId = `${alice.userId}:${aliceRtcMember.deviceId}`;
|
|
||||||
const aliceParticipant = mockRemoteParticipant({ identity: aliceId });
|
|
||||||
|
|
||||||
vitest.mock("../useAudioContext");
|
vitest.mock("../useAudioContext");
|
||||||
vitest.mock("../soundUtils");
|
vitest.mock("../soundUtils");
|
||||||
@@ -78,66 +58,6 @@ beforeEach(() => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
function TestComponent({
|
|
||||||
rtcSession,
|
|
||||||
vm,
|
|
||||||
}: {
|
|
||||||
rtcSession: MockRTCSession;
|
|
||||||
vm: CallViewModel;
|
|
||||||
}): ReactNode {
|
|
||||||
return (
|
|
||||||
<TestReactionsWrapper
|
|
||||||
rtcSession={rtcSession as unknown as MatrixRTCSession}
|
|
||||||
>
|
|
||||||
<CallEventAudioRenderer vm={vm} />
|
|
||||||
</TestReactionsWrapper>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
function getMockEnv(
|
|
||||||
members: RoomMember[],
|
|
||||||
initialRemoteRtcMemberships: CallMembership[] = [aliceRtcMember],
|
|
||||||
): {
|
|
||||||
vm: CallViewModel;
|
|
||||||
session: MockRTCSession;
|
|
||||||
remoteRtcMemberships: BehaviorSubject<CallMembership[]>;
|
|
||||||
} {
|
|
||||||
const matrixRoomMembers = new Map(members.map((p) => [p.userId, p]));
|
|
||||||
const remoteParticipants = of([aliceParticipant]);
|
|
||||||
const liveKitRoom = mockLivekitRoom(
|
|
||||||
{ localParticipant },
|
|
||||||
{ remoteParticipants },
|
|
||||||
);
|
|
||||||
const matrixRoom = mockMatrixRoom({
|
|
||||||
client: {
|
|
||||||
getUserId: () => localRtcMember.sender,
|
|
||||||
getDeviceId: () => localRtcMember.deviceId,
|
|
||||||
on: vitest.fn(),
|
|
||||||
off: vitest.fn(),
|
|
||||||
} as Partial<MatrixClient> as MatrixClient,
|
|
||||||
getMember: (userId) => matrixRoomMembers.get(userId) ?? null,
|
|
||||||
});
|
|
||||||
|
|
||||||
const remoteRtcMemberships = new BehaviorSubject<CallMembership[]>(
|
|
||||||
initialRemoteRtcMemberships,
|
|
||||||
);
|
|
||||||
|
|
||||||
const session = new MockRTCSession(
|
|
||||||
matrixRoom,
|
|
||||||
localRtcMember,
|
|
||||||
).withMemberships(remoteRtcMemberships);
|
|
||||||
|
|
||||||
const vm = new CallViewModel(
|
|
||||||
session as unknown as MatrixRTCSession,
|
|
||||||
liveKitRoom,
|
|
||||||
{
|
|
||||||
kind: E2eeType.PER_PARTICIPANT,
|
|
||||||
},
|
|
||||||
of(ConnectionState.Connected),
|
|
||||||
);
|
|
||||||
return { vm, session, remoteRtcMemberships };
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* We don't want to play a sound when loading the call state
|
* We don't want to play a sound when loading the call state
|
||||||
* because typically this occurs in two stages. We first join
|
* because typically this occurs in two stages. We first join
|
||||||
@@ -146,8 +66,12 @@ function getMockEnv(
|
|||||||
* a noise every time.
|
* a noise every time.
|
||||||
*/
|
*/
|
||||||
test("plays one sound when entering a call", () => {
|
test("plays one sound when entering a call", () => {
|
||||||
const { session, vm, remoteRtcMemberships } = getMockEnv([local, alice]);
|
const { vm, remoteRtcMemberships } = getBasicCallViewModelEnvironment([
|
||||||
render(<TestComponent rtcSession={session} vm={vm} />);
|
local,
|
||||||
|
alice,
|
||||||
|
]);
|
||||||
|
render(<CallEventAudioRenderer vm={vm} />);
|
||||||
|
|
||||||
// Joining a call usually means remote participants are added later.
|
// Joining a call usually means remote participants are added later.
|
||||||
act(() => {
|
act(() => {
|
||||||
remoteRtcMemberships.next([aliceRtcMember, bobRtcMember]);
|
remoteRtcMemberships.next([aliceRtcMember, bobRtcMember]);
|
||||||
@@ -155,10 +79,12 @@ test("plays one sound when entering a call", () => {
|
|||||||
expect(playSound).toHaveBeenCalledOnce();
|
expect(playSound).toHaveBeenCalledOnce();
|
||||||
});
|
});
|
||||||
|
|
||||||
// TODO: Same test?
|
|
||||||
test("plays a sound when a user joins", () => {
|
test("plays a sound when a user joins", () => {
|
||||||
const { session, vm, remoteRtcMemberships } = getMockEnv([local, alice]);
|
const { vm, remoteRtcMemberships } = getBasicCallViewModelEnvironment([
|
||||||
render(<TestComponent rtcSession={session} vm={vm} />);
|
local,
|
||||||
|
alice,
|
||||||
|
]);
|
||||||
|
render(<CallEventAudioRenderer vm={vm} />);
|
||||||
|
|
||||||
act(() => {
|
act(() => {
|
||||||
remoteRtcMemberships.next([aliceRtcMember, bobRtcMember]);
|
remoteRtcMemberships.next([aliceRtcMember, bobRtcMember]);
|
||||||
@@ -168,8 +94,11 @@ test("plays a sound when a user joins", () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
test("plays a sound when a user leaves", () => {
|
test("plays a sound when a user leaves", () => {
|
||||||
const { session, vm, remoteRtcMemberships } = getMockEnv([local, alice]);
|
const { vm, remoteRtcMemberships } = getBasicCallViewModelEnvironment([
|
||||||
render(<TestComponent rtcSession={session} vm={vm} />);
|
local,
|
||||||
|
alice,
|
||||||
|
]);
|
||||||
|
render(<CallEventAudioRenderer vm={vm} />);
|
||||||
|
|
||||||
act(() => {
|
act(() => {
|
||||||
remoteRtcMemberships.next([]);
|
remoteRtcMemberships.next([]);
|
||||||
@@ -185,12 +114,12 @@ test("plays no sound when the participant list is more than the maximum size", (
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
const { session, vm, remoteRtcMemberships } = getMockEnv(
|
const { vm, remoteRtcMemberships } = getBasicCallViewModelEnvironment(
|
||||||
[local, alice],
|
[local, alice],
|
||||||
mockRtcMemberships,
|
mockRtcMemberships,
|
||||||
);
|
);
|
||||||
|
|
||||||
render(<TestComponent rtcSession={session} vm={vm} />);
|
render(<CallEventAudioRenderer vm={vm} />);
|
||||||
expect(playSound).not.toBeCalled();
|
expect(playSound).not.toBeCalled();
|
||||||
act(() => {
|
act(() => {
|
||||||
remoteRtcMemberships.next(
|
remoteRtcMemberships.next(
|
||||||
@@ -201,9 +130,9 @@ test("plays no sound when the participant list is more than the maximum size", (
|
|||||||
});
|
});
|
||||||
|
|
||||||
test("plays one sound when a hand is raised", () => {
|
test("plays one sound when a hand is raised", () => {
|
||||||
const { session, vm } = getMockEnv([local, alice]);
|
const { vm } = getBasicCallViewModelEnvironment([local, alice]);
|
||||||
render(<TestComponent rtcSession={session} vm={vm} />);
|
render(<CallEventAudioRenderer vm={vm} />);
|
||||||
// Joining a call usually means remote participants are added later.
|
|
||||||
act(() => {
|
act(() => {
|
||||||
vm.updateReactions({
|
vm.updateReactions({
|
||||||
raisedHands: {
|
raisedHands: {
|
||||||
@@ -216,9 +145,9 @@ test("plays one sound when a hand is raised", () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
test("should not play a sound when a hand raise is retracted", () => {
|
test("should not play a sound when a hand raise is retracted", () => {
|
||||||
const { session, vm } = getMockEnv([local, alice]);
|
const { vm } = getBasicCallViewModelEnvironment([local, alice]);
|
||||||
render(<TestComponent rtcSession={session} vm={vm} />);
|
render(<CallEventAudioRenderer vm={vm} />);
|
||||||
// Joining a call usually means remote participants are added later.
|
|
||||||
act(() => {
|
act(() => {
|
||||||
vm.updateReactions({
|
vm.updateReactions({
|
||||||
raisedHands: {
|
raisedHands: {
|
||||||
|
|||||||
@@ -7,45 +7,19 @@ Please see LICENSE in the repository root for full details.
|
|||||||
|
|
||||||
import { render } from "@testing-library/react";
|
import { render } from "@testing-library/react";
|
||||||
import { expect, test } from "vitest";
|
import { expect, test } from "vitest";
|
||||||
import { TooltipProvider } from "@vector-im/compound-web";
|
import { act } from "react";
|
||||||
import { act, ReactNode } from "react";
|
|
||||||
import { afterEach } from "node:test";
|
import { afterEach } from "node:test";
|
||||||
|
|
||||||
import {
|
|
||||||
MockRoom,
|
|
||||||
MockRTCSession,
|
|
||||||
TestReactionsWrapper,
|
|
||||||
} from "../utils/testReactions";
|
|
||||||
import { showReactions } from "../settings/settings";
|
import { showReactions } from "../settings/settings";
|
||||||
import { ReactionsOverlay } from "./ReactionsOverlay";
|
import { ReactionsOverlay } from "./ReactionsOverlay";
|
||||||
import { ReactionSet } from "../reactions";
|
import { ReactionSet } from "../reactions";
|
||||||
|
import {
|
||||||
const memberUserIdAlice = "@alice:example.org";
|
local,
|
||||||
const memberUserIdBob = "@bob:example.org";
|
alice,
|
||||||
const memberUserIdCharlie = "@charlie:example.org";
|
aliceRtcMember,
|
||||||
const memberEventAlice = "$membership-alice:example.org";
|
bobRtcMember,
|
||||||
const memberEventBob = "$membership-bob:example.org";
|
} from "../utils/test-fixtures";
|
||||||
const memberEventCharlie = "$membership-charlie:example.org";
|
import { getBasicCallViewModelEnvironment } from "../utils/test-viewmodel";
|
||||||
|
|
||||||
const membership: Record<string, string> = {
|
|
||||||
[memberEventAlice]: memberUserIdAlice,
|
|
||||||
[memberEventBob]: memberUserIdBob,
|
|
||||||
[memberEventCharlie]: memberUserIdCharlie,
|
|
||||||
};
|
|
||||||
|
|
||||||
function TestComponent({
|
|
||||||
rtcSession,
|
|
||||||
}: {
|
|
||||||
rtcSession: MockRTCSession;
|
|
||||||
}): ReactNode {
|
|
||||||
return (
|
|
||||||
<TooltipProvider>
|
|
||||||
<TestReactionsWrapper rtcSession={rtcSession}>
|
|
||||||
<ReactionsOverlay />
|
|
||||||
</TestReactionsWrapper>
|
|
||||||
</TooltipProvider>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
afterEach(() => {
|
afterEach(() => {
|
||||||
showReactions.setValue(showReactions.defaultValue);
|
showReactions.setValue(showReactions.defaultValue);
|
||||||
@@ -53,22 +27,21 @@ afterEach(() => {
|
|||||||
|
|
||||||
test("defaults to showing no reactions", () => {
|
test("defaults to showing no reactions", () => {
|
||||||
showReactions.setValue(true);
|
showReactions.setValue(true);
|
||||||
const rtcSession = new MockRTCSession(
|
const { vm } = getBasicCallViewModelEnvironment([local, alice]);
|
||||||
new MockRoom(memberUserIdAlice),
|
const { container } = render(<ReactionsOverlay vm={vm} />);
|
||||||
membership,
|
|
||||||
);
|
|
||||||
const { container } = render(<TestComponent rtcSession={rtcSession} />);
|
|
||||||
expect(container.getElementsByTagName("span")).toHaveLength(0);
|
expect(container.getElementsByTagName("span")).toHaveLength(0);
|
||||||
});
|
});
|
||||||
|
|
||||||
test("shows a reaction when sent", () => {
|
test("shows a reaction when sent", () => {
|
||||||
showReactions.setValue(true);
|
showReactions.setValue(true);
|
||||||
|
const { vm } = getBasicCallViewModelEnvironment([local, alice]);
|
||||||
|
const { getByRole } = render(<ReactionsOverlay vm={vm} />);
|
||||||
const reaction = ReactionSet[0];
|
const reaction = ReactionSet[0];
|
||||||
const room = new MockRoom(memberUserIdAlice);
|
|
||||||
const rtcSession = new MockRTCSession(room, membership);
|
|
||||||
const { getByRole } = render(<TestComponent rtcSession={rtcSession} />);
|
|
||||||
act(() => {
|
act(() => {
|
||||||
room.testSendReaction(memberEventAlice, reaction, membership);
|
vm.updateReactions({
|
||||||
|
reactions: { [aliceRtcMember.deviceId]: reaction },
|
||||||
|
raisedHands: {},
|
||||||
|
});
|
||||||
});
|
});
|
||||||
const span = getByRole("presentation");
|
const span = getByRole("presentation");
|
||||||
expect(getByRole("presentation")).toBeTruthy();
|
expect(getByRole("presentation")).toBeTruthy();
|
||||||
@@ -78,29 +51,33 @@ test("shows a reaction when sent", () => {
|
|||||||
test("shows two of the same reaction when sent", () => {
|
test("shows two of the same reaction when sent", () => {
|
||||||
showReactions.setValue(true);
|
showReactions.setValue(true);
|
||||||
const reaction = ReactionSet[0];
|
const reaction = ReactionSet[0];
|
||||||
const room = new MockRoom(memberUserIdAlice);
|
const { vm } = getBasicCallViewModelEnvironment([local, alice]);
|
||||||
const rtcSession = new MockRTCSession(room, membership);
|
const { getAllByRole } = render(<ReactionsOverlay vm={vm} />);
|
||||||
const { getAllByRole } = render(<TestComponent rtcSession={rtcSession} />);
|
|
||||||
act(() => {
|
act(() => {
|
||||||
room.testSendReaction(memberEventAlice, reaction, membership);
|
vm.updateReactions({
|
||||||
});
|
reactions: {
|
||||||
act(() => {
|
[aliceRtcMember.deviceId]: reaction,
|
||||||
room.testSendReaction(memberEventBob, reaction, membership);
|
[bobRtcMember.deviceId]: reaction,
|
||||||
|
},
|
||||||
|
raisedHands: {},
|
||||||
|
});
|
||||||
});
|
});
|
||||||
expect(getAllByRole("presentation")).toHaveLength(2);
|
expect(getAllByRole("presentation")).toHaveLength(2);
|
||||||
});
|
});
|
||||||
|
|
||||||
test("shows two different reactions when sent", () => {
|
test("shows two different reactions when sent", () => {
|
||||||
showReactions.setValue(true);
|
showReactions.setValue(true);
|
||||||
const room = new MockRoom(memberUserIdAlice);
|
|
||||||
const rtcSession = new MockRTCSession(room, membership);
|
|
||||||
const [reactionA, reactionB] = ReactionSet;
|
const [reactionA, reactionB] = ReactionSet;
|
||||||
const { getAllByRole } = render(<TestComponent rtcSession={rtcSession} />);
|
const { vm } = getBasicCallViewModelEnvironment([local, alice]);
|
||||||
|
const { getAllByRole } = render(<ReactionsOverlay vm={vm} />);
|
||||||
act(() => {
|
act(() => {
|
||||||
room.testSendReaction(memberEventAlice, reactionA, membership);
|
vm.updateReactions({
|
||||||
});
|
reactions: {
|
||||||
act(() => {
|
[aliceRtcMember.deviceId]: reactionA,
|
||||||
room.testSendReaction(memberEventBob, reactionB, membership);
|
[bobRtcMember.deviceId]: reactionB,
|
||||||
|
},
|
||||||
|
raisedHands: {},
|
||||||
|
});
|
||||||
});
|
});
|
||||||
const [reactionElementA, reactionElementB] = getAllByRole("presentation");
|
const [reactionElementA, reactionElementB] = getAllByRole("presentation");
|
||||||
expect(reactionElementA.innerHTML).toEqual(reactionA.emoji);
|
expect(reactionElementA.innerHTML).toEqual(reactionA.emoji);
|
||||||
@@ -110,11 +87,13 @@ test("shows two different reactions when sent", () => {
|
|||||||
test("hides reactions when reaction animations are disabled", () => {
|
test("hides reactions when reaction animations are disabled", () => {
|
||||||
showReactions.setValue(false);
|
showReactions.setValue(false);
|
||||||
const reaction = ReactionSet[0];
|
const reaction = ReactionSet[0];
|
||||||
const room = new MockRoom(memberUserIdAlice);
|
const { vm } = getBasicCallViewModelEnvironment([local, alice]);
|
||||||
const rtcSession = new MockRTCSession(room, membership);
|
const { container } = render(<ReactionsOverlay vm={vm} />);
|
||||||
act(() => {
|
act(() => {
|
||||||
room.testSendReaction(memberEventAlice, reaction, membership);
|
vm.updateReactions({
|
||||||
|
reactions: { [aliceRtcMember.deviceId]: reaction },
|
||||||
|
raisedHands: {},
|
||||||
|
});
|
||||||
});
|
});
|
||||||
const { container } = render(<TestComponent rtcSession={rtcSession} />);
|
|
||||||
expect(container.getElementsByTagName("span")).toHaveLength(0);
|
expect(container.getElementsByTagName("span")).toHaveLength(0);
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
|||||||
Please see LICENSE in the repository root for full details.
|
Please see LICENSE in the repository root for full details.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { test, vi, onTestFinished, it } from "vitest";
|
import { test, vi, onTestFinished, it, vitest } from "vitest";
|
||||||
import {
|
import {
|
||||||
combineLatest,
|
combineLatest,
|
||||||
debounceTime,
|
debounceTime,
|
||||||
@@ -14,6 +14,7 @@ import {
|
|||||||
Observable,
|
Observable,
|
||||||
of,
|
of,
|
||||||
switchMap,
|
switchMap,
|
||||||
|
tap,
|
||||||
} from "rxjs";
|
} from "rxjs";
|
||||||
import { MatrixClient } from "matrix-js-sdk/src/matrix";
|
import { MatrixClient } from "matrix-js-sdk/src/matrix";
|
||||||
import {
|
import {
|
||||||
@@ -684,3 +685,64 @@ it("should show at least one tile per MatrixRTCSession", () => {
|
|||||||
);
|
);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// TODO: Add presenters and speakers?
|
||||||
|
it("should rank raised hands above video feeds and below speakers and presenters", () => {
|
||||||
|
withTestScheduler(({ schedule, expectObservable }) => {
|
||||||
|
// There should always be one tile for each MatrixRTCSession
|
||||||
|
const expectedLayoutMarbles = "a";
|
||||||
|
|
||||||
|
withCallViewModel(
|
||||||
|
of([aliceParticipant, bobParticipant]),
|
||||||
|
of([aliceRtcMember, bobRtcMember]),
|
||||||
|
of(ConnectionState.Connected),
|
||||||
|
new Map(),
|
||||||
|
(vm) => {
|
||||||
|
schedule("ah", {
|
||||||
|
a: () => {
|
||||||
|
// We imagine that only three tiles (the first three) will be visible
|
||||||
|
// on screen at a time
|
||||||
|
vm.layout.subscribe((layout) => {
|
||||||
|
console.log(layout);
|
||||||
|
if (layout.type === "grid") {
|
||||||
|
for (let i = 0; i < layout.grid.length; i++)
|
||||||
|
layout.grid[i].setVisible(i <= 1);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
},
|
||||||
|
h: () => {
|
||||||
|
vm.updateReactions({
|
||||||
|
reactions: {},
|
||||||
|
raisedHands: {
|
||||||
|
[`${bobRtcMember.sender}:${bobRtcMember.deviceId}`]: new Date(),
|
||||||
|
},
|
||||||
|
});
|
||||||
|
},
|
||||||
|
});
|
||||||
|
expectObservable(summarizeLayout(vm.layout)).toBe(
|
||||||
|
expectedLayoutMarbles,
|
||||||
|
{
|
||||||
|
a: {
|
||||||
|
type: "grid",
|
||||||
|
spotlight: undefined,
|
||||||
|
grid: [
|
||||||
|
"local:0",
|
||||||
|
"@bob:example.org:BBBB:0",
|
||||||
|
"@alice:example.org:AAAA:0",
|
||||||
|
],
|
||||||
|
},
|
||||||
|
h: {
|
||||||
|
type: "grid",
|
||||||
|
spotlight: undefined,
|
||||||
|
grid: [
|
||||||
|
"local:0",
|
||||||
|
"@bob:example.org:BBBB:0",
|
||||||
|
"@alice:example.org:AAAA:0",
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|||||||
@@ -42,6 +42,7 @@ import {
|
|||||||
switchMap,
|
switchMap,
|
||||||
switchScan,
|
switchScan,
|
||||||
take,
|
take,
|
||||||
|
tap,
|
||||||
timer,
|
timer,
|
||||||
withLatestFrom,
|
withLatestFrom,
|
||||||
} from "rxjs";
|
} from "rxjs";
|
||||||
@@ -635,13 +636,14 @@ export class CallViewModel extends ViewModel {
|
|||||||
[
|
[
|
||||||
m.speaker,
|
m.speaker,
|
||||||
m.presenter,
|
m.presenter,
|
||||||
m.vm.handRaised,
|
|
||||||
m.vm.videoEnabled,
|
m.vm.videoEnabled,
|
||||||
m.vm instanceof LocalUserMediaViewModel
|
m.vm instanceof LocalUserMediaViewModel
|
||||||
? m.vm.alwaysShow
|
? m.vm.alwaysShow
|
||||||
: of(false),
|
: of(false),
|
||||||
|
m.vm.handRaised,
|
||||||
],
|
],
|
||||||
(speaker, presenter, handRaised, video, alwaysShow) => {
|
(speaker, presenter, video, alwaysShow, handRaised) => {
|
||||||
|
console.log(m.vm.id, handRaised);
|
||||||
let bin: SortingBin;
|
let bin: SortingBin;
|
||||||
if (m.vm.local)
|
if (m.vm.local)
|
||||||
bin = alwaysShow
|
bin = alwaysShow
|
||||||
@@ -664,6 +666,12 @@ export class CallViewModel extends ViewModel {
|
|||||||
bins.sort(([, bin1], [, bin2]) => bin1 - bin2).map(([m]) => m.vm),
|
bins.sort(([, bin1], [, bin2]) => bin1 - bin2).map(([m]) => m.vm),
|
||||||
);
|
);
|
||||||
}),
|
}),
|
||||||
|
tap((v) =>
|
||||||
|
console.log(
|
||||||
|
"final grid",
|
||||||
|
v.map((v) => v.id),
|
||||||
|
),
|
||||||
|
),
|
||||||
);
|
);
|
||||||
|
|
||||||
private readonly spotlight: Observable<MediaViewModel[]> =
|
private readonly spotlight: Observable<MediaViewModel[]> =
|
||||||
@@ -1116,10 +1124,12 @@ export class CallViewModel extends ViewModel {
|
|||||||
this.scope.state(),
|
this.scope.state(),
|
||||||
);
|
);
|
||||||
|
|
||||||
private readonly handsRaisedSubject = new Subject<Record<string, Date>>();
|
private readonly handsRaisedSubject = new BehaviorSubject<
|
||||||
private readonly reactionsSubject = new Subject<
|
Record<string, Date>
|
||||||
|
>({});
|
||||||
|
private readonly reactionsSubject = new BehaviorSubject<
|
||||||
Record<string, ReactionOption>
|
Record<string, ReactionOption>
|
||||||
>();
|
>({});
|
||||||
|
|
||||||
public readonly handsRaised = this.handsRaisedSubject.asObservable();
|
public readonly handsRaised = this.handsRaisedSubject.asObservable();
|
||||||
public readonly reactions = this.reactionsSubject.asObservable();
|
public readonly reactions = this.reactionsSubject.asObservable();
|
||||||
|
|||||||
@@ -15,7 +15,7 @@ import {
|
|||||||
createHandRaisedReaction,
|
createHandRaisedReaction,
|
||||||
createRedaction,
|
createRedaction,
|
||||||
MockRoom,
|
MockRoom,
|
||||||
MockRTCSession,
|
ReactionsMockRTCSession,
|
||||||
TestReactionsWrapper,
|
TestReactionsWrapper,
|
||||||
} from "./utils/testReactions";
|
} from "./utils/testReactions";
|
||||||
|
|
||||||
@@ -55,7 +55,7 @@ const TestComponent: FC = () => {
|
|||||||
|
|
||||||
describe("useReactions", () => {
|
describe("useReactions", () => {
|
||||||
test("starts with an empty list", () => {
|
test("starts with an empty list", () => {
|
||||||
const rtcSession = new MockRTCSession(
|
const rtcSession = new ReactionsMockRTCSession(
|
||||||
new MockRoom(memberUserIdAlice),
|
new MockRoom(memberUserIdAlice),
|
||||||
membership,
|
membership,
|
||||||
);
|
);
|
||||||
@@ -68,7 +68,7 @@ describe("useReactions", () => {
|
|||||||
});
|
});
|
||||||
test("handles incoming raised hand", async () => {
|
test("handles incoming raised hand", async () => {
|
||||||
const room = new MockRoom(memberUserIdAlice);
|
const room = new MockRoom(memberUserIdAlice);
|
||||||
const rtcSession = new MockRTCSession(room, membership);
|
const rtcSession = new ReactionsMockRTCSession(room, membership);
|
||||||
const { queryByRole } = render(
|
const { queryByRole } = render(
|
||||||
<TestReactionsWrapper rtcSession={rtcSession}>
|
<TestReactionsWrapper rtcSession={rtcSession}>
|
||||||
<TestComponent />
|
<TestComponent />
|
||||||
@@ -81,7 +81,7 @@ describe("useReactions", () => {
|
|||||||
});
|
});
|
||||||
test("handles incoming unraised hand", async () => {
|
test("handles incoming unraised hand", async () => {
|
||||||
const room = new MockRoom(memberUserIdAlice);
|
const room = new MockRoom(memberUserIdAlice);
|
||||||
const rtcSession = new MockRTCSession(room, membership);
|
const rtcSession = new ReactionsMockRTCSession(room, membership);
|
||||||
const { queryByRole } = render(
|
const { queryByRole } = render(
|
||||||
<TestReactionsWrapper rtcSession={rtcSession}>
|
<TestReactionsWrapper rtcSession={rtcSession}>
|
||||||
<TestComponent />
|
<TestComponent />
|
||||||
@@ -105,7 +105,7 @@ describe("useReactions", () => {
|
|||||||
const room = new MockRoom(memberUserIdAlice, [
|
const room = new MockRoom(memberUserIdAlice, [
|
||||||
createHandRaisedReaction(memberEventAlice, membership),
|
createHandRaisedReaction(memberEventAlice, membership),
|
||||||
]);
|
]);
|
||||||
const rtcSession = new MockRTCSession(room, membership);
|
const rtcSession = new ReactionsMockRTCSession(room, membership);
|
||||||
const { queryByRole } = render(
|
const { queryByRole } = render(
|
||||||
<TestReactionsWrapper rtcSession={rtcSession}>
|
<TestReactionsWrapper rtcSession={rtcSession}>
|
||||||
<TestComponent />
|
<TestComponent />
|
||||||
@@ -119,7 +119,7 @@ describe("useReactions", () => {
|
|||||||
const room = new MockRoom(memberUserIdAlice, [
|
const room = new MockRoom(memberUserIdAlice, [
|
||||||
createHandRaisedReaction(memberEventAlice, membership),
|
createHandRaisedReaction(memberEventAlice, membership),
|
||||||
]);
|
]);
|
||||||
const rtcSession = new MockRTCSession(room, membership);
|
const rtcSession = new ReactionsMockRTCSession(room, membership);
|
||||||
const { queryByRole } = render(
|
const { queryByRole } = render(
|
||||||
<TestReactionsWrapper rtcSession={rtcSession}>
|
<TestReactionsWrapper rtcSession={rtcSession}>
|
||||||
<TestComponent />
|
<TestComponent />
|
||||||
@@ -133,7 +133,7 @@ describe("useReactions", () => {
|
|||||||
const room = new MockRoom(memberUserIdAlice, [
|
const room = new MockRoom(memberUserIdAlice, [
|
||||||
createHandRaisedReaction(memberEventAlice, membership),
|
createHandRaisedReaction(memberEventAlice, membership),
|
||||||
]);
|
]);
|
||||||
const rtcSession = new MockRTCSession(room, membership);
|
const rtcSession = new ReactionsMockRTCSession(room, membership);
|
||||||
const { queryByRole } = render(
|
const { queryByRole } = render(
|
||||||
<TestReactionsWrapper rtcSession={rtcSession}>
|
<TestReactionsWrapper rtcSession={rtcSession}>
|
||||||
<TestComponent />
|
<TestComponent />
|
||||||
@@ -151,7 +151,7 @@ describe("useReactions", () => {
|
|||||||
const room = new MockRoom(memberUserIdAlice, [
|
const room = new MockRoom(memberUserIdAlice, [
|
||||||
createHandRaisedReaction(memberEventAlice, memberUserIdBob),
|
createHandRaisedReaction(memberEventAlice, memberUserIdBob),
|
||||||
]);
|
]);
|
||||||
const rtcSession = new MockRTCSession(room, membership);
|
const rtcSession = new ReactionsMockRTCSession(room, membership);
|
||||||
const { queryByRole } = render(
|
const { queryByRole } = render(
|
||||||
<TestReactionsWrapper rtcSession={rtcSession}>
|
<TestReactionsWrapper rtcSession={rtcSession}>
|
||||||
<TestComponent />
|
<TestComponent />
|
||||||
@@ -161,7 +161,7 @@ describe("useReactions", () => {
|
|||||||
});
|
});
|
||||||
test("ignores invalid sender for new event", async () => {
|
test("ignores invalid sender for new event", async () => {
|
||||||
const room = new MockRoom(memberUserIdAlice);
|
const room = new MockRoom(memberUserIdAlice);
|
||||||
const rtcSession = new MockRTCSession(room, membership);
|
const rtcSession = new ReactionsMockRTCSession(room, membership);
|
||||||
const { queryByRole } = render(
|
const { queryByRole } = render(
|
||||||
<TestReactionsWrapper rtcSession={rtcSession}>
|
<TestReactionsWrapper rtcSession={rtcSession}>
|
||||||
<TestComponent />
|
<TestComponent />
|
||||||
|
|||||||
@@ -349,6 +349,7 @@ export const ReactionsProvider = ({
|
|||||||
]);
|
]);
|
||||||
|
|
||||||
const toggleRaisedHand = useCallback(async () => {
|
const toggleRaisedHand = useCallback(async () => {
|
||||||
|
console.log("toggleRaisedHand", myMembershipIdentifier);
|
||||||
if (!myMembershipIdentifier) {
|
if (!myMembershipIdentifier) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|||||||
17
src/utils/test-fixtures.ts
Normal file
17
src/utils/test-fixtures.ts
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
import {
|
||||||
|
mockRtcMembership,
|
||||||
|
mockMatrixRoomMember,
|
||||||
|
mockRemoteParticipant,
|
||||||
|
mockLocalParticipant,
|
||||||
|
} from "./test";
|
||||||
|
|
||||||
|
export const aliceRtcMember = mockRtcMembership("@alice:example.org", "AAAA");
|
||||||
|
export const alice = mockMatrixRoomMember(aliceRtcMember);
|
||||||
|
export const aliceId = `${alice.userId}:${aliceRtcMember.deviceId}`;
|
||||||
|
export const aliceParticipant = mockRemoteParticipant({ identity: aliceId });
|
||||||
|
|
||||||
|
export const localRtcMember = mockRtcMembership("@carol:example.org", "CCCC");
|
||||||
|
export const local = mockMatrixRoomMember(localRtcMember);
|
||||||
|
export const localParticipant = mockLocalParticipant({ identity: "" });
|
||||||
|
|
||||||
|
export const bobRtcMember = mockRtcMembership("@bob:example.org", "BBBB");
|
||||||
71
src/utils/test-viewmodel.ts
Normal file
71
src/utils/test-viewmodel.ts
Normal file
@@ -0,0 +1,71 @@
|
|||||||
|
import { ConnectionState } from "livekit-client";
|
||||||
|
import { MatrixClient } from "matrix-js-sdk/src/client";
|
||||||
|
import { RoomMember } from "matrix-js-sdk/src/matrix";
|
||||||
|
import { CallMembership, MatrixRTCSession } from "matrix-js-sdk/src/matrixrtc";
|
||||||
|
import { BehaviorSubject, of } from "rxjs";
|
||||||
|
import { vitest } from "vitest";
|
||||||
|
import { E2eeType } from "../e2ee/e2eeType";
|
||||||
|
import { CallViewModel } from "../state/CallViewModel";
|
||||||
|
import { mockLivekitRoom, mockMatrixRoom, MockRTCSession } from "./test";
|
||||||
|
import {
|
||||||
|
aliceRtcMember,
|
||||||
|
aliceParticipant,
|
||||||
|
localParticipant,
|
||||||
|
localRtcMember,
|
||||||
|
} from "./test-fixtures";
|
||||||
|
import { RelationsContainer } from "matrix-js-sdk/src/models/relations-container";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Construct a basic CallViewModel to test components that make use of it.
|
||||||
|
* @param members
|
||||||
|
* @param initialRemoteRtcMemberships
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
|
export function getBasicCallViewModelEnvironment(
|
||||||
|
members: RoomMember[],
|
||||||
|
initialRemoteRtcMemberships: CallMembership[] = [aliceRtcMember],
|
||||||
|
): {
|
||||||
|
vm: CallViewModel;
|
||||||
|
remoteRtcMemberships: BehaviorSubject<CallMembership[]>;
|
||||||
|
rtcSession: MockRTCSession;
|
||||||
|
} {
|
||||||
|
const matrixRoomMembers = new Map(members.map((p) => [p.userId, p]));
|
||||||
|
const remoteParticipants = of([aliceParticipant]);
|
||||||
|
const liveKitRoom = mockLivekitRoom(
|
||||||
|
{ localParticipant },
|
||||||
|
{ remoteParticipants },
|
||||||
|
);
|
||||||
|
const matrixRoom = mockMatrixRoom({
|
||||||
|
relations: {
|
||||||
|
getChildEventsForEvent: vitest.fn(),
|
||||||
|
} as Partial<RelationsContainer> as RelationsContainer,
|
||||||
|
client: {
|
||||||
|
getUserId: () => localRtcMember.sender,
|
||||||
|
getDeviceId: () => localRtcMember.deviceId,
|
||||||
|
sendEvent: vitest.fn().mockResolvedValue({ event_id: "$fake:event" }),
|
||||||
|
redactEvent: vitest.fn().mockResolvedValue({ event_id: "$fake:event" }),
|
||||||
|
on: vitest.fn(),
|
||||||
|
off: vitest.fn(),
|
||||||
|
} as Partial<MatrixClient> as MatrixClient,
|
||||||
|
getMember: (userId) => matrixRoomMembers.get(userId) ?? null,
|
||||||
|
});
|
||||||
|
|
||||||
|
const remoteRtcMemberships = new BehaviorSubject<CallMembership[]>(
|
||||||
|
initialRemoteRtcMemberships,
|
||||||
|
);
|
||||||
|
|
||||||
|
const rtcSession = new MockRTCSession(
|
||||||
|
matrixRoom,
|
||||||
|
localRtcMember,
|
||||||
|
).withMemberships(remoteRtcMemberships);
|
||||||
|
|
||||||
|
const vm = new CallViewModel(
|
||||||
|
rtcSession as unknown as MatrixRTCSession,
|
||||||
|
liveKitRoom,
|
||||||
|
{
|
||||||
|
kind: E2eeType.PER_PARTICIPANT,
|
||||||
|
},
|
||||||
|
of(ConnectionState.Connected),
|
||||||
|
);
|
||||||
|
return { vm, remoteRtcMemberships, rtcSession };
|
||||||
|
}
|
||||||
@@ -4,19 +4,21 @@ Copyright 2023, 2024 New Vector Ltd.
|
|||||||
SPDX-License-Identifier: AGPL-3.0-only
|
SPDX-License-Identifier: AGPL-3.0-only
|
||||||
Please see LICENSE in the repository root for full details.
|
Please see LICENSE in the repository root for full details.
|
||||||
*/
|
*/
|
||||||
import { map, Observable, of, SchedulerLike } from "rxjs";
|
import { BehaviorSubject, map, Observable, of, SchedulerLike } from "rxjs";
|
||||||
import { RunHelpers, TestScheduler } from "rxjs/testing";
|
import { RunHelpers, TestScheduler } from "rxjs/testing";
|
||||||
import { expect, vi } from "vitest";
|
import { expect, vi, vitest } from "vitest";
|
||||||
import {
|
import {
|
||||||
RoomMember,
|
RoomMember,
|
||||||
Room as MatrixRoom,
|
Room as MatrixRoom,
|
||||||
MatrixEvent,
|
MatrixEvent,
|
||||||
Room,
|
Room,
|
||||||
TypedEventEmitter,
|
TypedEventEmitter,
|
||||||
|
MatrixClient,
|
||||||
} from "matrix-js-sdk/src/matrix";
|
} from "matrix-js-sdk/src/matrix";
|
||||||
import {
|
import {
|
||||||
CallMembership,
|
CallMembership,
|
||||||
Focus,
|
Focus,
|
||||||
|
MatrixRTCSession,
|
||||||
MatrixRTCSessionEvent,
|
MatrixRTCSessionEvent,
|
||||||
MatrixRTCSessionEventHandlerMap,
|
MatrixRTCSessionEventHandlerMap,
|
||||||
SessionMembershipData,
|
SessionMembershipData,
|
||||||
@@ -27,6 +29,7 @@ import {
|
|||||||
RemoteParticipant,
|
RemoteParticipant,
|
||||||
RemoteTrackPublication,
|
RemoteTrackPublication,
|
||||||
Room as LivekitRoom,
|
Room as LivekitRoom,
|
||||||
|
ConnectionState,
|
||||||
} from "livekit-client";
|
} from "livekit-client";
|
||||||
|
|
||||||
import {
|
import {
|
||||||
@@ -36,6 +39,14 @@ import {
|
|||||||
import { E2eeType } from "../e2ee/e2eeType";
|
import { E2eeType } from "../e2ee/e2eeType";
|
||||||
import { DEFAULT_CONFIG, ResolvedConfigOptions } from "../config/ConfigOptions";
|
import { DEFAULT_CONFIG, ResolvedConfigOptions } from "../config/ConfigOptions";
|
||||||
import { Config } from "../config/Config";
|
import { Config } from "../config/Config";
|
||||||
|
import { CallViewModel } from "../state/CallViewModel";
|
||||||
|
import {
|
||||||
|
aliceParticipant,
|
||||||
|
aliceRtcMember,
|
||||||
|
localParticipant,
|
||||||
|
localRtcMember,
|
||||||
|
} from "./test-fixtures";
|
||||||
|
import { randomUUID } from "crypto";
|
||||||
|
|
||||||
export function withFakeTimers(continuation: () => void): void {
|
export function withFakeTimers(continuation: () => void): void {
|
||||||
vi.useFakeTimers();
|
vi.useFakeTimers();
|
||||||
@@ -129,6 +140,7 @@ export function mockRtcMembership(
|
|||||||
};
|
};
|
||||||
const event = new MatrixEvent({
|
const event = new MatrixEvent({
|
||||||
sender: typeof user === "string" ? user : user.userId,
|
sender: typeof user === "string" ? user : user.userId,
|
||||||
|
event_id: `$-ev-${randomUUID()}:example.org`,
|
||||||
});
|
});
|
||||||
return new CallMembership(event, data);
|
return new CallMembership(event, data);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -27,53 +27,41 @@ import {
|
|||||||
ElementCallReactionEventType,
|
ElementCallReactionEventType,
|
||||||
ReactionOption,
|
ReactionOption,
|
||||||
} from "../reactions";
|
} from "../reactions";
|
||||||
|
import { MockRTCSession } from "./test";
|
||||||
|
|
||||||
export const TestReactionsWrapper = ({
|
// export class ReactionsMockRTCSession extends EventEmitter {
|
||||||
rtcSession,
|
// public memberships: {
|
||||||
children,
|
// sender: string;
|
||||||
}: PropsWithChildren<{
|
// eventId: string;
|
||||||
rtcSession: MockRTCSession | MatrixRTCSession;
|
// createdTs: () => Date;
|
||||||
}>): ReactNode => {
|
// }[];
|
||||||
return (
|
|
||||||
<ReactionsProvider rtcSession={rtcSession as unknown as MatrixRTCSession}>
|
|
||||||
{children}
|
|
||||||
</ReactionsProvider>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export class MockRTCSession extends EventEmitter {
|
// public constructor(
|
||||||
public memberships: {
|
// public readonly room: MockRoom,
|
||||||
sender: string;
|
// membership: Record<string, string>,
|
||||||
eventId: string;
|
// ) {
|
||||||
createdTs: () => Date;
|
// super();
|
||||||
}[];
|
// this.memberships = Object.entries(membership).map(([eventId, sender]) => ({
|
||||||
|
// sender,
|
||||||
|
// eventId,
|
||||||
|
// createdTs: (): Date => new Date(),
|
||||||
|
// }));
|
||||||
|
// }
|
||||||
|
|
||||||
public constructor(
|
// public testRemoveMember(userId: string): void {
|
||||||
public readonly room: MockRoom,
|
// this.memberships = this.memberships.filter((u) => u.sender !== userId);
|
||||||
membership: Record<string, string>,
|
// this.emit(MatrixRTCSessionEvent.MembershipsChanged);
|
||||||
) {
|
// }
|
||||||
super();
|
|
||||||
this.memberships = Object.entries(membership).map(([eventId, sender]) => ({
|
|
||||||
sender,
|
|
||||||
eventId,
|
|
||||||
createdTs: (): Date => new Date(),
|
|
||||||
}));
|
|
||||||
}
|
|
||||||
|
|
||||||
public testRemoveMember(userId: string): void {
|
// public testAddMember(sender: string): void {
|
||||||
this.memberships = this.memberships.filter((u) => u.sender !== userId);
|
// this.memberships.push({
|
||||||
this.emit(MatrixRTCSessionEvent.MembershipsChanged);
|
// sender,
|
||||||
}
|
// eventId: `!fake-${randomUUID()}:event`,
|
||||||
|
// createdTs: (): Date => new Date(),
|
||||||
public testAddMember(sender: string): void {
|
// });
|
||||||
this.memberships.push({
|
// this.emit(MatrixRTCSessionEvent.MembershipsChanged);
|
||||||
sender,
|
// }
|
||||||
eventId: `!fake-${randomUUID()}:event`,
|
// }
|
||||||
createdTs: (): Date => new Date(),
|
|
||||||
});
|
|
||||||
this.emit(MatrixRTCSessionEvent.MembershipsChanged);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export function createHandRaisedReaction(
|
export function createHandRaisedReaction(
|
||||||
parentMemberEvent: string,
|
parentMemberEvent: string,
|
||||||
@@ -126,6 +114,7 @@ export class MockRoom extends EventEmitter {
|
|||||||
public get client(): MatrixClient {
|
public get client(): MatrixClient {
|
||||||
return {
|
return {
|
||||||
getUserId: (): string => this.ownUserId,
|
getUserId: (): string => this.ownUserId,
|
||||||
|
getDeviceId: (): string => "ABCDEF",
|
||||||
sendEvent: async (
|
sendEvent: async (
|
||||||
...props: Parameters<MatrixClient["sendEvent"]>
|
...props: Parameters<MatrixClient["sendEvent"]>
|
||||||
): ReturnType<MatrixClient["sendEvent"]> => {
|
): ReturnType<MatrixClient["sendEvent"]> => {
|
||||||
|
|||||||
Reference in New Issue
Block a user