Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Refactor reactions / hand raised to use rxjs and start ordering tiles based on hand raised. #2885

Draft
wants to merge 12 commits into
base: livekit
Choose a base branch
from
104 changes: 65 additions & 39 deletions src/button/ReactionToggleButton.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,88 +5,114 @@
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 {
MockRoom,
MockRTCSession,
TestReactionsWrapper,
} from "../utils/testReactions";
import { MockRoom } from "../utils/testReactions";
import { ReactionToggleButton } from "./ReactionToggleButton";
import { ElementCallReactionEventType } from "../reactions";
import { CallViewModel } from "../state/CallViewModel";

Check failure on line 17 in src/button/ReactionToggleButton.test.tsx

View workflow job for this annotation

GitHub Actions / Lint, format & type check

All imports in the declaration are only used as types. Use `import type`
import { getBasicCallViewModelEnvironment } from "../utils/test-viewmodel";
import { alice, local, localRtcMember } from "../utils/test-fixtures";
import { MockRTCSession } from "../utils/test";
import { ReactionsProvider } from "../useReactions";

Check failure on line 21 in src/button/ReactionToggleButton.test.tsx

View workflow job for this annotation

GitHub Actions / Lint, format & type check

There should be at least one empty line between import groups
import { MatrixRTCSession } from "matrix-js-sdk/src/matrixrtc/MatrixRTCSession";

Check failure on line 22 in src/button/ReactionToggleButton.test.tsx

View workflow job for this annotation

GitHub Actions / Lint, format & type check

All imports in the declaration are only used as types. Use `import type`

Check failure on line 22 in src/button/ReactionToggleButton.test.tsx

View workflow job for this annotation

GitHub Actions / Lint, format & type check

`matrix-js-sdk/src/matrixrtc/MatrixRTCSession` import should occur before import of `../utils/testReactions`

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>
<ReactionsProvider rtcSession={rtcSession as unknown as MatrixRTCSession}>
<ReactionToggleButton vm={vm} identifier={localIdent} />
</ReactionsProvider>
</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 } = 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(
undefined,
"m.reaction",
{
"m.relates_to": {
event_id: localRtcMember.eventId,
key: "🖐️",
rel_type: "m.annotation",
},
],
]);
},
);
await act(() => {

Check failure on line 71 in src/button/ReactionToggleButton.test.tsx

View workflow job for this annotation

GitHub Actions / Lint, format & type check

Unexpected `await` of a non-Promise (non-"Thenable") value
vm.updateReactions({
raisedHands: {
[localIdent]: new Date(),
},
reactions: {},
});
});
expect(container).toMatchSnapshot();
});

test("Can lower hand", async () => {
test.only("Can lower hand", 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} />,
);
const reactionEvent = room.testSendHandRaise(memberEventAlice, membership);
await user.click(getByLabelText("common.reactions"));
await user.click(getByLabelText("action.raise_hand"));
await act(() => {

Check failure on line 90 in src/button/ReactionToggleButton.test.tsx

View workflow job for this annotation

GitHub Actions / Lint, format & type check

Unexpected `await` of a non-Promise (non-"Thenable") value
vm.updateReactions({
raisedHands: {
[localIdent]: new Date(),
},
reactions: {},
});
});
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(() => {

Check failure on line 110 in src/button/ReactionToggleButton.test.tsx

View workflow job for this annotation

GitHub Actions / Lint, format & type check

Unexpected `await` of a non-Promise (non-"Thenable") value
vm.updateReactions({
raisedHands: {},
reactions: {},
});
});
expect(container).toMatchSnapshot();
});

Expand Down
26 changes: 17 additions & 9 deletions src/button/ReactionToggleButton.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,8 @@
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 styles from "./ReactionToggleButton.module.css";
Expand All @@ -33,6 +35,7 @@
ReactionsRowSize,
} from "../reactions";
import { Modal } from "../Modal";
import { CallViewModel } from "../state/CallViewModel";

Check failure on line 38 in src/button/ReactionToggleButton.tsx

View workflow job for this annotation

GitHub Actions / Lint, format & type check

All imports in the declaration are only used as types. Use `import type`

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

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 } = useReactions();
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 @@
<InnerButton
disabled={busy}
onClick={() => setShowReactionsMenu((show) => !show)}
raised={isHandRaised}
raised={!!isHandRaised}
open={showReactionsMenu}
{...props}
/>
Expand All @@ -237,8 +245,8 @@
>
<ReactionPopupMenu
errorText={errorText}
isHandRaised={isHandRaised}
canReact={!busy && canReact}
isHandRaised={!!isHandRaised}
canReact={!busy && !!canReact}
sendReaction={(reaction) => void sendRelation(reaction)}
toggleRaisedHand={wrappedToggleRaisedHand}
/>
Expand Down
10 changes: 4 additions & 6 deletions src/button/__snapshots__/ReactionToggleButton.test.tsx.snap
Original file line number Diff line number Diff line change
Expand Up @@ -137,9 +137,9 @@ exports[`Can raise hand 1`] = `
aria-disabled="false"
aria-expanded="false"
aria-haspopup="true"
aria-labelledby=":r1j:"
class="_button_i91xf_17 _has-icon_i91xf_66 _icon-only_i91xf_59"
data-kind="secondary"
aria-labelledby=":r0:"
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 +153,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
Loading