Skip to content

Commit

Permalink
Merge remote-tracking branch 'origin/livekit' into hs/disambiguate-di…
Browse files Browse the repository at this point in the history
…splaynames
  • Loading branch information
Half-Shot committed Dec 23, 2024
2 parents 9abc180 + abf2ecd commit 959ced6
Show file tree
Hide file tree
Showing 30 changed files with 1,852 additions and 1,185 deletions.
168 changes: 93 additions & 75 deletions src/button/ReactionToggleButton.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,150 +5,168 @@ SPDX-License-Identifier: AGPL-3.0-only
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 { TooltipProvider } from "@vector-im/compound-web";
import { userEvent } from "@testing-library/user-event";
import { type ReactNode } from "react";
import { type MatrixRTCSession } from "matrix-js-sdk/src/matrixrtc/MatrixRTCSession";

import {
MockRoom,
MockRTCSession,
TestReactionsWrapper,
} from "../utils/testReactions";
import { ReactionToggleButton } from "./ReactionToggleButton";
import { ElementCallReactionEventType } from "../reactions";
import { type CallViewModel } from "../state/CallViewModel";
import { getBasicCallViewModelEnvironment } from "../utils/test-viewmodel";
import { alice, local, localRtcMember } from "../utils/test-fixtures";
import { type MockRTCSession } from "../utils/test";
import { ReactionsSenderProvider } from "../reactions/useReactionsSender";

const memberUserIdAlice = "@alice:example.org";
const memberEventAlice = "$membership-alice:example.org";

const membership: Record<string, string> = {
[memberEventAlice]: memberUserIdAlice,
};
const localIdent = `${localRtcMember.sender}:${localRtcMember.deviceId}`;

function TestComponent({
rtcSession,
vm,
}: {
rtcSession: MockRTCSession;
vm: CallViewModel;
}): ReactNode {
return (
<TooltipProvider>
<TestReactionsWrapper rtcSession={rtcSession}>
<ReactionToggleButton userId={memberUserIdAlice} />
</TestReactionsWrapper>
<ReactionsSenderProvider
vm={vm}
rtcSession={rtcSession as unknown as MatrixRTCSession}
>
<ReactionToggleButton vm={vm} identifier={localIdent} />
</ReactionsSenderProvider>
</TooltipProvider>
);
}

test("Can open menu", async () => {
const user = userEvent.setup();
const room = new MockRoom(memberUserIdAlice);
const rtcSession = new MockRTCSession(room, membership);
const { vm, rtcSession } = getBasicCallViewModelEnvironment([alice]);
const { getByLabelText, container } = render(
<TestComponent rtcSession={rtcSession} />,
<TestComponent vm={vm} rtcSession={rtcSession} />,
);
await user.click(getByLabelText("common.reactions"));
expect(container).toMatchSnapshot();
});

test("Can raise hand", async () => {
const user = userEvent.setup();
const room = new MockRoom(memberUserIdAlice);
const rtcSession = new MockRTCSession(room, membership);
const { vm, rtcSession, handRaisedSubject$ } =
getBasicCallViewModelEnvironment([local, alice]);
const { getByLabelText, container } = render(
<TestComponent rtcSession={rtcSession} />,
<TestComponent vm={vm} rtcSession={rtcSession} />,
);
await user.click(getByLabelText("common.reactions"));
await user.click(getByLabelText("action.raise_hand"));
expect(room.testSentEvents).toEqual([
[
undefined,
"m.reaction",
{
"m.relates_to": {
event_id: memberEventAlice,
key: "🖐️",
rel_type: "m.annotation",
},
expect(rtcSession.room.client.sendEvent).toHaveBeenCalledWith(
rtcSession.room.roomId,
"m.reaction",
{
"m.relates_to": {
event_id: localRtcMember.eventId,
key: "🖐️",
rel_type: "m.annotation",
},
},
);
act(() => {
// Mock receiving a reaction.
handRaisedSubject$.next({
[localIdent]: {
time: new Date(),
reactionEventId: "",
membershipEventId: localRtcMember.eventId!,
},
],
]);
});
});
expect(container).toMatchSnapshot();
});

test("Can lower hand", async () => {
const reactionEventId = "$my-reaction-event:example.org";
const user = userEvent.setup();
const room = new MockRoom(memberUserIdAlice);
const rtcSession = new MockRTCSession(room, membership);
const { vm, rtcSession, handRaisedSubject$ } =
getBasicCallViewModelEnvironment([local, alice]);
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("action.raise_hand"));
act(() => {
handRaisedSubject$.next({
[localIdent]: {
time: new Date(),
reactionEventId,
membershipEventId: localRtcMember.eventId!,
},
});
});
await user.click(getByLabelText("common.reactions"));
await user.click(getByLabelText("action.lower_hand"));
expect(room.testRedactedEvents).toEqual([[undefined, reactionEvent]]);
expect(rtcSession.room.client.redactEvent).toHaveBeenCalledWith(
rtcSession.room.roomId,
reactionEventId,
);
act(() => {
// Mock receiving a redacted reaction.
handRaisedSubject$.next({});
});
expect(container).toMatchSnapshot();
});

test("Can react with emoji", async () => {
const user = userEvent.setup();
const room = new MockRoom(memberUserIdAlice);
const rtcSession = new MockRTCSession(room, membership);
const { vm, rtcSession } = getBasicCallViewModelEnvironment([local, alice]);
const { getByLabelText, getByText } = render(
<TestComponent rtcSession={rtcSession} />,
<TestComponent vm={vm} rtcSession={rtcSession} />,
);
await user.click(getByLabelText("common.reactions"));
await user.click(getByText("🐶"));
expect(room.testSentEvents).toEqual([
[
undefined,
ElementCallReactionEventType,
{
"m.relates_to": {
event_id: memberEventAlice,
rel_type: "m.reference",
},
name: "dog",
emoji: "🐶",
expect(rtcSession.room.client.sendEvent).toHaveBeenCalledWith(
rtcSession.room.roomId,
ElementCallReactionEventType,
{
"m.relates_to": {
event_id: localRtcMember.eventId,
rel_type: "m.reference",
},
],
]);
name: "dog",
emoji: "🐶",
},
);
});

test("Can fully expand emoji picker", async () => {
const user = userEvent.setup();
const room = new MockRoom(memberUserIdAlice);
const rtcSession = new MockRTCSession(room, membership);
const { getByText, container, getByLabelText } = render(
<TestComponent rtcSession={rtcSession} />,
const { vm, rtcSession } = getBasicCallViewModelEnvironment([local, alice]);
const { getByLabelText, container, getByText } = render(
<TestComponent vm={vm} rtcSession={rtcSession} />,
);
await user.click(getByLabelText("common.reactions"));
await user.click(getByLabelText("action.show_more"));
expect(container).toMatchSnapshot();
await user.click(getByText("🦗"));

expect(room.testSentEvents).toEqual([
[
undefined,
ElementCallReactionEventType,
{
"m.relates_to": {
event_id: memberEventAlice,
rel_type: "m.reference",
},
name: "crickets",
emoji: "🦗",
expect(rtcSession.room.client.sendEvent).toHaveBeenCalledWith(
rtcSession.room.roomId,
ElementCallReactionEventType,
{
"m.relates_to": {
event_id: localRtcMember.eventId,
rel_type: "m.reference",
},
],
]);
name: "crickets",
emoji: "🦗",
},
);
});

test("Can close reaction dialog", async () => {
const user = userEvent.setup();
const room = new MockRoom(memberUserIdAlice);
const rtcSession = new MockRTCSession(room, membership);
const { vm, rtcSession } = getBasicCallViewModelEnvironment([local, alice]);
const { getByLabelText, container } = render(
<TestComponent rtcSession={rtcSession} />,
<TestComponent vm={vm} rtcSession={rtcSession} />,
);
await user.click(getByLabelText("common.reactions"));
await user.click(getByLabelText("action.show_more"));
Expand Down
28 changes: 18 additions & 10 deletions src/button/ReactionToggleButton.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -24,15 +24,18 @@ import {
import { useTranslation } from "react-i18next";
import { logger } from "matrix-js-sdk/src/logger";
import classNames from "classnames";
import { useObservableState } from "observable-hooks";
import { map } from "rxjs";

import { useReactions } from "../useReactions";
import { useReactionsSender } from "../reactions/useReactionsSender";
import styles from "./ReactionToggleButton.module.css";
import {
type ReactionOption,
ReactionSet,
ReactionsRowSize,
} from "../reactions";
import { Modal } from "../Modal";
import { type CallViewModel } from "../state/CallViewModel";

interface InnerButtonProps extends ComponentPropsWithoutRef<"button"> {
raised: boolean;
Expand Down Expand Up @@ -162,22 +165,27 @@ export function ReactionPopupMenu({
}

interface ReactionToggleButtonProps extends ComponentPropsWithoutRef<"button"> {
userId: string;
identifier: string;
vm: CallViewModel;
}

export function ReactionToggleButton({
userId,
identifier,
vm,
...props
}: ReactionToggleButtonProps): ReactNode {
const { t } = useTranslation();
const { raisedHands, toggleRaisedHand, sendReaction, reactions } =
useReactions();
const { toggleRaisedHand, sendReaction } = useReactionsSender();
const [busy, setBusy] = useState(false);
const [showReactionsMenu, setShowReactionsMenu] = useState(false);
const [errorText, setErrorText] = useState<string>();

const isHandRaised = !!raisedHands[userId];
const canReact = !reactions[userId];
const isHandRaised = useObservableState(
vm.handsRaised$.pipe(map((v) => !!v[identifier])),
);
const canReact = useObservableState(
vm.reactions$.pipe(map((v) => !v[identifier])),
);

useEffect(() => {
// Clear whenever the reactions menu state changes.
Expand Down Expand Up @@ -223,7 +231,7 @@ export function ReactionToggleButton({
<InnerButton
disabled={busy}
onClick={() => setShowReactionsMenu((show) => !show)}
raised={isHandRaised}
raised={!!isHandRaised}
open={showReactionsMenu}
{...props}
/>
Expand All @@ -237,8 +245,8 @@ export function ReactionToggleButton({
>
<ReactionPopupMenu
errorText={errorText}
isHandRaised={isHandRaised}
canReact={!busy && canReact}
isHandRaised={!!isHandRaised}
canReact={!busy && !!canReact}
sendReaction={(reaction) => void sendRelation(reaction)}
toggleRaisedHand={wrappedToggleRaisedHand}
/>
Expand Down
20 changes: 10 additions & 10 deletions src/button/__snapshots__/ReactionToggleButton.test.tsx.snap
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ exports[`Can close reaction dialog 1`] = `
aria-disabled="false"
aria-expanded="true"
aria-haspopup="true"
aria-labelledby=":r9l:"
aria-labelledby=":rav:"
class="_button_i91xf_17 _has-icon_i91xf_66 _icon-only_i91xf_59"
data-kind="primary"
data-size="lg"
Expand Down Expand Up @@ -43,7 +43,7 @@ exports[`Can fully expand emoji picker 1`] = `
aria-disabled="false"
aria-expanded="true"
aria-haspopup="true"
aria-labelledby=":r6c:"
aria-labelledby=":r7m:"
class="_button_i91xf_17 _has-icon_i91xf_66 _icon-only_i91xf_59"
data-kind="primary"
data-size="lg"
Expand Down Expand Up @@ -75,8 +75,8 @@ exports[`Can lower hand 1`] = `
aria-expanded="false"
aria-haspopup="true"
aria-labelledby=":r36:"
class="_button_i91xf_17 raisedButton _has-icon_i91xf_66 _icon-only_i91xf_59"
data-kind="primary"
class="_button_i91xf_17 _has-icon_i91xf_66 _icon-only_i91xf_59"
data-kind="secondary"
data-size="lg"
role="button"
tabindex="0"
Expand All @@ -90,7 +90,9 @@ exports[`Can lower hand 1`] = `
xmlns="http://www.w3.org/2000/svg"
>
<path
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"
clip-rule="evenodd"
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>
</button>
Expand Down Expand Up @@ -138,8 +140,8 @@ exports[`Can raise hand 1`] = `
aria-expanded="false"
aria-haspopup="true"
aria-labelledby=":r1j:"
class="_button_i91xf_17 _has-icon_i91xf_66 _icon-only_i91xf_59"
data-kind="secondary"
class="_button_i91xf_17 raisedButton _has-icon_i91xf_66 _icon-only_i91xf_59"
data-kind="primary"
data-size="lg"
role="button"
tabindex="0"
Expand All @@ -153,9 +155,7 @@ exports[`Can raise hand 1`] = `
xmlns="http://www.w3.org/2000/svg"
>
<path
clip-rule="evenodd"
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"
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"
/>
</svg>
</button>
Expand Down
Loading

0 comments on commit 959ced6

Please sign in to comment.