mirror of
https://github.com/vector-im/element-call.git
synced 2026-07-15 18:49:21 +00:00
Add tests for ReactionAudioRenderer
This commit is contained in:
@@ -103,7 +103,7 @@ test("Can can lower hand", () => {
|
|||||||
const { getByRole, getByText, container } = render(
|
const { getByRole, getByText, container } = render(
|
||||||
<TestComponent rtcSession={rtcSession} room={room} />,
|
<TestComponent rtcSession={rtcSession} room={room} />,
|
||||||
);
|
);
|
||||||
const reactionEvent = room.testSendReaction(memberEventAlice, membership);
|
const reactionEvent = room.testSendHandRaise(memberEventAlice, membership);
|
||||||
act(() => {
|
act(() => {
|
||||||
getByRole("button").click();
|
getByRole("button").click();
|
||||||
});
|
});
|
||||||
@@ -185,7 +185,7 @@ test("Can search for and send emoji with the keyboard", async () => {
|
|||||||
const user = userEvent.setup();
|
const user = userEvent.setup();
|
||||||
const room = new MockRoom(memberUserIdAlice);
|
const room = new MockRoom(memberUserIdAlice);
|
||||||
const rtcSession = new MockRTCSession(room, membership);
|
const rtcSession = new MockRTCSession(room, membership);
|
||||||
const { getByText, getByRole, getByPlaceholderText, container } = render(
|
const { getByRole, getByPlaceholderText, container } = render(
|
||||||
<TestComponent rtcSession={rtcSession} room={room} />,
|
<TestComponent rtcSession={rtcSession} room={room} />,
|
||||||
);
|
);
|
||||||
act(() => {
|
act(() => {
|
||||||
|
|||||||
@@ -21,12 +21,9 @@ import {
|
|||||||
ChangeEventHandler,
|
ChangeEventHandler,
|
||||||
ComponentPropsWithoutRef,
|
ComponentPropsWithoutRef,
|
||||||
FC,
|
FC,
|
||||||
FormEventHandler,
|
|
||||||
KeyboardEvent,
|
|
||||||
KeyboardEventHandler,
|
KeyboardEventHandler,
|
||||||
ReactNode,
|
ReactNode,
|
||||||
useCallback,
|
useCallback,
|
||||||
useEffect,
|
|
||||||
useMemo,
|
useMemo,
|
||||||
useState,
|
useState,
|
||||||
} from "react";
|
} from "react";
|
||||||
|
|||||||
@@ -236,7 +236,7 @@ export const InCallView: FC<InCallViewProps> = ({
|
|||||||
containerRef1,
|
containerRef1,
|
||||||
toggleMicrophone,
|
toggleMicrophone,
|
||||||
toggleCamera,
|
toggleCamera,
|
||||||
(muted) => muteStates.audio.setEnabled?.(!muteprefered),
|
(muted) => muteStates.audio.setEnabled?.(!muted),
|
||||||
);
|
);
|
||||||
|
|
||||||
const mobile = boundsValid && bounds.width <= 660;
|
const mobile = boundsValid && bounds.width <= 660;
|
||||||
|
|||||||
101
src/room/ReactionAudioRenderer.test.tsx
Normal file
101
src/room/ReactionAudioRenderer.test.tsx
Normal file
@@ -0,0 +1,101 @@
|
|||||||
|
/*
|
||||||
|
Copyright 2024 New Vector Ltd.
|
||||||
|
|
||||||
|
SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
Please see LICENSE in the repository root for full details.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { act, render } from "@testing-library/react";
|
||||||
|
import { expect, test } from "vitest";
|
||||||
|
import { TooltipProvider } from "@vector-im/compound-web";
|
||||||
|
|
||||||
|
import {
|
||||||
|
MockRoom,
|
||||||
|
MockRTCSession,
|
||||||
|
TestReactionsWrapper,
|
||||||
|
} from "../utils/testReactions";
|
||||||
|
import { ReactionsAudioRenderer } from "./ReactionAudioRenderer";
|
||||||
|
import { ReactionSet } from "../reactions";
|
||||||
|
|
||||||
|
const memberUserIdAlice = "@alice:example.org";
|
||||||
|
const memberUserIdBob = "@bob:example.org";
|
||||||
|
const memberUserIdCharlie = "@charlie:example.org";
|
||||||
|
const memberEventAlice = "$membership-alice:example.org";
|
||||||
|
const memberEventBob = "$membership-bob:example.org";
|
||||||
|
const memberEventCharlie = "$membership-charlie:example.org";
|
||||||
|
|
||||||
|
const membership: Record<string, string> = {
|
||||||
|
[memberEventAlice]: memberUserIdAlice,
|
||||||
|
[memberEventBob]: memberUserIdBob,
|
||||||
|
[memberEventCharlie]: memberUserIdCharlie,
|
||||||
|
};
|
||||||
|
|
||||||
|
function TestComponent({ rtcSession }: { rtcSession: MockRTCSession }) {
|
||||||
|
return (
|
||||||
|
<TooltipProvider>
|
||||||
|
<TestReactionsWrapper rtcSession={rtcSession}>
|
||||||
|
<ReactionsAudioRenderer />
|
||||||
|
</TestReactionsWrapper>
|
||||||
|
</TooltipProvider>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
test("defaults to no audio elements", async () => {
|
||||||
|
const rtcSession = new MockRTCSession(
|
||||||
|
new MockRoom(memberUserIdAlice),
|
||||||
|
membership,
|
||||||
|
);
|
||||||
|
const { container } = render(<TestComponent rtcSession={rtcSession} />);
|
||||||
|
expect(container.getElementsByTagName("audio")).toHaveLength(0);
|
||||||
|
});
|
||||||
|
|
||||||
|
test("will play an audio sound when there is a reaction", async () => {
|
||||||
|
const room = new MockRoom(memberUserIdAlice);
|
||||||
|
const rtcSession = new MockRTCSession(room, membership);
|
||||||
|
const { container } = render(<TestComponent rtcSession={rtcSession} />);
|
||||||
|
|
||||||
|
// Find the first reaction with a sound effect
|
||||||
|
const chosenReaction = ReactionSet.find((r) => !!r.sound);
|
||||||
|
if (!chosenReaction) {
|
||||||
|
throw Error(
|
||||||
|
"No reactions have sounds configured, this test cannot succeed",
|
||||||
|
);
|
||||||
|
}
|
||||||
|
act(() =>
|
||||||
|
room.testSendReaction(memberEventAlice, chosenReaction, membership),
|
||||||
|
);
|
||||||
|
const elements = container.getElementsByTagName("audio");
|
||||||
|
expect(elements).toHaveLength(1);
|
||||||
|
const audioElement = elements[0];
|
||||||
|
|
||||||
|
expect(audioElement.autoplay).toBe(true);
|
||||||
|
|
||||||
|
const sources = audioElement.getElementsByTagName("source");
|
||||||
|
expect(sources).toHaveLength(2);
|
||||||
|
|
||||||
|
// The element will be the full URL, whereas the chosenReaction will have the path.
|
||||||
|
expect(sources[0].src).toContain(chosenReaction.sound?.ogg);
|
||||||
|
expect(sources[1].src).toContain(chosenReaction.sound?.mp3);
|
||||||
|
});
|
||||||
|
|
||||||
|
test("will play multiple audio sounds when there are multiple different reactions", async () => {
|
||||||
|
const room = new MockRoom(memberUserIdAlice);
|
||||||
|
const rtcSession = new MockRTCSession(room, membership);
|
||||||
|
const { container } = render(<TestComponent rtcSession={rtcSession} />);
|
||||||
|
|
||||||
|
// Find the first reaction with a sound effect
|
||||||
|
const [reaction1, reaction2] = ReactionSet.filter((r) => !!r.sound);
|
||||||
|
if (!reaction1 || !reaction2) {
|
||||||
|
throw Error(
|
||||||
|
"No reactions have sounds configured, this test cannot succeed",
|
||||||
|
);
|
||||||
|
}
|
||||||
|
act(() => {
|
||||||
|
room.testSendReaction(memberEventAlice, reaction1, membership);
|
||||||
|
room.testSendReaction(memberEventBob, reaction2, membership);
|
||||||
|
room.testSendReaction(memberEventCharlie, reaction1, membership);
|
||||||
|
});
|
||||||
|
const elements = container.getElementsByTagName("audio");
|
||||||
|
// Do not play the same reaction twice.
|
||||||
|
expect(elements).toHaveLength(2);
|
||||||
|
});
|
||||||
@@ -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 { ReactNode } from "react";
|
import { ReactNode, useMemo } from "react";
|
||||||
|
|
||||||
import { useReactions } from "../useReactions";
|
import { useReactions } from "../useReactions";
|
||||||
import { playReactionsSound, useSetting } from "../settings/settings";
|
import { playReactionsSound, useSetting } from "../settings/settings";
|
||||||
@@ -14,9 +14,20 @@ export function ReactionsAudioRenderer(): ReactNode {
|
|||||||
const { reactions } = useReactions();
|
const { reactions } = useReactions();
|
||||||
const [shouldPlay] = useSetting(playReactionsSound);
|
const [shouldPlay] = useSetting(playReactionsSound);
|
||||||
|
|
||||||
const expectedReactions = shouldPlay
|
const expectedReactions = useMemo(() => {
|
||||||
? [...new Set([...Object.values(reactions)])]
|
if (!shouldPlay) {
|
||||||
: [];
|
return [];
|
||||||
|
}
|
||||||
|
const reactionsToPlayNames = new Set();
|
||||||
|
return Object.values(reactions).filter((r) => {
|
||||||
|
if (reactionsToPlayNames.has(r.name)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
reactionsToPlayNames.add(r.name);
|
||||||
|
return true;
|
||||||
|
});
|
||||||
|
}, [shouldPlay, reactions]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
{expectedReactions.map(
|
{expectedReactions.map(
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ import { RoomEvent } from "matrix-js-sdk/src/matrix";
|
|||||||
|
|
||||||
import { useReactions } from "./useReactions";
|
import { useReactions } from "./useReactions";
|
||||||
import {
|
import {
|
||||||
createReaction,
|
createHandRaisedReaction,
|
||||||
createRedaction,
|
createRedaction,
|
||||||
MockRoom,
|
MockRoom,
|
||||||
MockRTCSession,
|
MockRTCSession,
|
||||||
@@ -75,7 +75,7 @@ describe("useReactions", () => {
|
|||||||
<TestComponent />
|
<TestComponent />
|
||||||
</TestReactionsWrapper>,
|
</TestReactionsWrapper>,
|
||||||
);
|
);
|
||||||
await act(() => room.testSendReaction(memberEventAlice, membership));
|
await act(() => room.testSendHandRaise(memberEventAlice, membership));
|
||||||
expect(queryByText("Local reaction")).toBeTruthy();
|
expect(queryByText("Local reaction")).toBeTruthy();
|
||||||
});
|
});
|
||||||
test("handles incoming raised hand", async () => {
|
test("handles incoming raised hand", async () => {
|
||||||
@@ -86,9 +86,9 @@ describe("useReactions", () => {
|
|||||||
<TestComponent />
|
<TestComponent />
|
||||||
</TestReactionsWrapper>,
|
</TestReactionsWrapper>,
|
||||||
);
|
);
|
||||||
await act(() => room.testSendReaction(memberEventAlice, membership));
|
await act(() => room.testSendHandRaise(memberEventAlice, membership));
|
||||||
expect(queryByRole("list")?.children).to.have.lengthOf(1);
|
expect(queryByRole("list")?.children).to.have.lengthOf(1);
|
||||||
await act(() => room.testSendReaction(memberEventBob, membership));
|
await act(() => room.testSendHandRaise(memberEventBob, membership));
|
||||||
expect(queryByRole("list")?.children).to.have.lengthOf(2);
|
expect(queryByRole("list")?.children).to.have.lengthOf(2);
|
||||||
});
|
});
|
||||||
test("handles incoming unraised hand", async () => {
|
test("handles incoming unraised hand", async () => {
|
||||||
@@ -100,7 +100,7 @@ describe("useReactions", () => {
|
|||||||
</TestReactionsWrapper>,
|
</TestReactionsWrapper>,
|
||||||
);
|
);
|
||||||
const reactionEventId = await act(() =>
|
const reactionEventId = await act(() =>
|
||||||
room.testSendReaction(memberEventAlice, membership),
|
room.testSendHandRaise(memberEventAlice, membership),
|
||||||
);
|
);
|
||||||
expect(queryByRole("list")?.children).to.have.lengthOf(1);
|
expect(queryByRole("list")?.children).to.have.lengthOf(1);
|
||||||
await act(() =>
|
await act(() =>
|
||||||
@@ -115,7 +115,7 @@ describe("useReactions", () => {
|
|||||||
});
|
});
|
||||||
test("handles loading prior raised hand events", () => {
|
test("handles loading prior raised hand events", () => {
|
||||||
const room = new MockRoom(memberUserIdAlice, [
|
const room = new MockRoom(memberUserIdAlice, [
|
||||||
createReaction(memberEventAlice, membership),
|
createHandRaisedReaction(memberEventAlice, membership),
|
||||||
]);
|
]);
|
||||||
const rtcSession = new MockRTCSession(room, membership);
|
const rtcSession = new MockRTCSession(room, membership);
|
||||||
const { queryByRole } = render(
|
const { queryByRole } = render(
|
||||||
@@ -129,7 +129,7 @@ describe("useReactions", () => {
|
|||||||
// the raised hand event.
|
// the raised hand event.
|
||||||
test("will remove reaction when a member leaves the call", () => {
|
test("will remove reaction when a member leaves the call", () => {
|
||||||
const room = new MockRoom(memberUserIdAlice, [
|
const room = new MockRoom(memberUserIdAlice, [
|
||||||
createReaction(memberEventAlice, membership),
|
createHandRaisedReaction(memberEventAlice, membership),
|
||||||
]);
|
]);
|
||||||
const rtcSession = new MockRTCSession(room, membership);
|
const rtcSession = new MockRTCSession(room, membership);
|
||||||
const { queryByRole } = render(
|
const { queryByRole } = render(
|
||||||
@@ -143,7 +143,7 @@ describe("useReactions", () => {
|
|||||||
});
|
});
|
||||||
test("will remove reaction when a member joins via a new event", () => {
|
test("will remove reaction when a member joins via a new event", () => {
|
||||||
const room = new MockRoom(memberUserIdAlice, [
|
const room = new MockRoom(memberUserIdAlice, [
|
||||||
createReaction(memberEventAlice, membership),
|
createHandRaisedReaction(memberEventAlice, membership),
|
||||||
]);
|
]);
|
||||||
const rtcSession = new MockRTCSession(room, membership);
|
const rtcSession = new MockRTCSession(room, membership);
|
||||||
const { queryByRole } = render(
|
const { queryByRole } = render(
|
||||||
@@ -161,7 +161,7 @@ describe("useReactions", () => {
|
|||||||
});
|
});
|
||||||
test("ignores invalid sender for historic event", () => {
|
test("ignores invalid sender for historic event", () => {
|
||||||
const room = new MockRoom(memberUserIdAlice, [
|
const room = new MockRoom(memberUserIdAlice, [
|
||||||
createReaction(memberEventAlice, memberUserIdBob),
|
createHandRaisedReaction(memberEventAlice, memberUserIdBob),
|
||||||
]);
|
]);
|
||||||
const rtcSession = new MockRTCSession(room, membership);
|
const rtcSession = new MockRTCSession(room, membership);
|
||||||
const { queryByRole } = render(
|
const { queryByRole } = render(
|
||||||
@@ -179,7 +179,7 @@ describe("useReactions", () => {
|
|||||||
<TestComponent />
|
<TestComponent />
|
||||||
</TestReactionsWrapper>,
|
</TestReactionsWrapper>,
|
||||||
);
|
);
|
||||||
await act(() => room.testSendReaction(memberEventAlice, memberUserIdBob));
|
await act(() => room.testSendHandRaise(memberEventAlice, memberUserIdBob));
|
||||||
expect(queryByRole("list")?.children).to.have.lengthOf(0);
|
expect(queryByRole("list")?.children).to.have.lengthOf(0);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ import { PropsWithChildren, ReactNode } from "react";
|
|||||||
import { randomUUID } from "crypto";
|
import { randomUUID } from "crypto";
|
||||||
import EventEmitter from "events";
|
import EventEmitter from "events";
|
||||||
import { MatrixClient } from "matrix-js-sdk/src/client";
|
import { MatrixClient } from "matrix-js-sdk/src/client";
|
||||||
import { EventType, RoomEvent } from "matrix-js-sdk/src/matrix";
|
import { EventType, RoomEvent, RelationType } from "matrix-js-sdk/src/matrix";
|
||||||
import {
|
import {
|
||||||
MatrixEvent,
|
MatrixEvent,
|
||||||
EventTimeline,
|
EventTimeline,
|
||||||
@@ -22,6 +22,11 @@ import {
|
|||||||
} from "matrix-js-sdk/src/matrixrtc";
|
} from "matrix-js-sdk/src/matrixrtc";
|
||||||
|
|
||||||
import { ReactionsProvider } from "../useReactions";
|
import { ReactionsProvider } from "../useReactions";
|
||||||
|
import {
|
||||||
|
ECallReactionEventContent,
|
||||||
|
ElementCallReactionEventType,
|
||||||
|
ReactionOption,
|
||||||
|
} from "../reactions";
|
||||||
|
|
||||||
export const TestReactionsWrapper = ({
|
export const TestReactionsWrapper = ({
|
||||||
rtcSession,
|
rtcSession,
|
||||||
@@ -70,7 +75,7 @@ export class MockRTCSession extends EventEmitter {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export function createReaction(
|
export function createHandRaisedReaction(
|
||||||
parentMemberEvent: string,
|
parentMemberEvent: string,
|
||||||
membershipOrOverridenSender: Record<string, string> | string,
|
membershipOrOverridenSender: Record<string, string> | string,
|
||||||
): MatrixEvent {
|
): MatrixEvent {
|
||||||
@@ -149,19 +154,43 @@ export class MockRoom extends EventEmitter {
|
|||||||
} as unknown as Room["relations"];
|
} as unknown as Room["relations"];
|
||||||
}
|
}
|
||||||
|
|
||||||
public testSendReaction(
|
public testSendHandRaise(
|
||||||
parentMemberEvent: string,
|
|
||||||
overridenSender: string,
|
|
||||||
): string;
|
|
||||||
public testSendReaction(
|
|
||||||
parentMemberEvent: string,
|
|
||||||
membershipOrOverridenSender: Record<string, string>,
|
|
||||||
): string;
|
|
||||||
public testSendReaction(
|
|
||||||
parentMemberEvent: string,
|
parentMemberEvent: string,
|
||||||
membershipOrOverridenSender: Record<string, string> | string,
|
membershipOrOverridenSender: Record<string, string> | string,
|
||||||
): string {
|
): string {
|
||||||
const evt = createReaction(parentMemberEvent, membershipOrOverridenSender);
|
const evt = createHandRaisedReaction(
|
||||||
|
parentMemberEvent,
|
||||||
|
membershipOrOverridenSender,
|
||||||
|
);
|
||||||
|
this.emit(RoomEvent.Timeline, evt, this, undefined, false, {
|
||||||
|
timeline: new EventTimeline(new EventTimelineSet(undefined)),
|
||||||
|
});
|
||||||
|
return evt.getId()!;
|
||||||
|
}
|
||||||
|
|
||||||
|
public testSendReaction(
|
||||||
|
parentMemberEvent: string,
|
||||||
|
reaction: ReactionOption,
|
||||||
|
membershipOrOverridenSender: Record<string, string> | string,
|
||||||
|
): string {
|
||||||
|
const evt = new MatrixEvent({
|
||||||
|
sender:
|
||||||
|
typeof membershipOrOverridenSender === "string"
|
||||||
|
? membershipOrOverridenSender
|
||||||
|
: membershipOrOverridenSender[parentMemberEvent],
|
||||||
|
type: ElementCallReactionEventType,
|
||||||
|
origin_server_ts: new Date().getTime(),
|
||||||
|
content: {
|
||||||
|
"m.relates_to": {
|
||||||
|
rel_type: RelationType.Reference,
|
||||||
|
event_id: parentMemberEvent,
|
||||||
|
},
|
||||||
|
emoji: reaction.emoji,
|
||||||
|
name: reaction.name,
|
||||||
|
} satisfies ECallReactionEventContent,
|
||||||
|
event_id: randomUUID(),
|
||||||
|
});
|
||||||
|
|
||||||
this.emit(RoomEvent.Timeline, evt, this, undefined, false, {
|
this.emit(RoomEvent.Timeline, evt, this, undefined, false, {
|
||||||
timeline: new EventTimeline(new EventTimelineSet(undefined)),
|
timeline: new EventTimeline(new EventTimelineSet(undefined)),
|
||||||
});
|
});
|
||||||
|
|||||||
Reference in New Issue
Block a user