From ff7da135ca8a7c4f6c3f15dccf6265b20a25b5c1 Mon Sep 17 00:00:00 2001 From: Half-Shot Date: Tue, 29 Oct 2024 16:49:37 +0000 Subject: [PATCH] Tidy up and finish test rewrites --- src/button/RaisedHandToggleButton.tsx | 12 +-- src/settings/PreferencesSettingsTab.tsx | 6 +- src/tile/SpotlightTile.tsx | 2 - src/useReactions.test.tsx | 110 ++++++++++++++---------- src/useReactions.tsx | 28 +++--- 5 files changed, 79 insertions(+), 79 deletions(-) diff --git a/src/button/RaisedHandToggleButton.tsx b/src/button/RaisedHandToggleButton.tsx index fe0c300b..25bb3e70 100644 --- a/src/button/RaisedHandToggleButton.tsx +++ b/src/button/RaisedHandToggleButton.tsx @@ -61,13 +61,8 @@ export function RaiseHandToggleButton({ client, rtcSession, }: RaisedHandToggleButton): ReactNode { - const { - raisedHands, - removeRaisedHand, - addRaisedHand, - myReactionId, - setMyReactionId, - } = useReactions(); + const { raisedHands, removeRaisedHand, addRaisedHand, myReactionId } = + useReactions(); const [busy, setBusy] = useState(false); const userId = client.getUserId()!; const isHandRaised = !!raisedHands[userId]; @@ -81,7 +76,6 @@ export function RaiseHandToggleButton({ .redactEvent(rtcSession.room.roomId, myReactionId) .then(() => { logger.debug("Redacted raise hand event"); - setMyReactionId(null); removeRaisedHand(userId); }) .catch((e) => { @@ -109,7 +103,6 @@ export function RaiseHandToggleButton({ }) .then((reaction) => { logger.debug("Sent raise hand event", reaction.event_id); - setMyReactionId(reaction.event_id); addRaisedHand(userId, { membershipEventId: parentEventId, reactionEventId: reaction.event_id, @@ -131,7 +124,6 @@ export function RaiseHandToggleButton({ rtcSession.room.roomId, addRaisedHand, removeRaisedHand, - setMyReactionId, userId, ]); diff --git a/src/settings/PreferencesSettingsTab.tsx b/src/settings/PreferencesSettingsTab.tsx index c6f26fbd..783825e8 100644 --- a/src/settings/PreferencesSettingsTab.tsx +++ b/src/settings/PreferencesSettingsTab.tsx @@ -15,11 +15,7 @@ import { useSetting, } from "./settings"; -interface Props { - roomId?: string; -} - -export const PreferencesSettingsTab: FC = ({}) => { +export const PreferencesSettingsTab: FC = () => { const { t } = useTranslation(); const [showHandRaisedTimer, setShowHandRaisedTimer] = useSetting( showHandRaisedTimerSetting, diff --git a/src/tile/SpotlightTile.tsx b/src/tile/SpotlightTile.tsx index d01242c4..a37d9cc2 100644 --- a/src/tile/SpotlightTile.tsx +++ b/src/tile/SpotlightTile.tsx @@ -53,7 +53,6 @@ interface SpotlightItemBaseProps { unencryptedWarning: boolean; displayName: string; "aria-hidden"?: boolean; - raisedHand: boolean; } interface SpotlightUserMediaItemBaseProps extends SpotlightItemBaseProps { @@ -158,7 +157,6 @@ const SpotlightItem = forwardRef( unencryptedWarning, displayName, "aria-hidden": ariaHidden, - raisedHand: false, }; return vm instanceof ScreenShareViewModel ? ( diff --git a/src/useReactions.test.tsx b/src/useReactions.test.tsx index 38df4e96..0a8d2b8e 100644 --- a/src/useReactions.test.tsx +++ b/src/useReactions.test.tsx @@ -26,23 +26,31 @@ import { randomUUID } from "crypto"; import { ReactionsProvider, useReactions } from "./useReactions"; -const membership = [ - "@alice:example.org", - "@bob:example.org", - "@charlie:example.org", -]; +const memberUserIdAlice = "@alice:example.org"; +const memberEventAlice = "$membership-alice:example.org"; +const memberUserIdBob = "@bob:example.org"; +const memberEventBob = "$membership-bob:example.org"; + +const membership: Record = { + [memberEventAlice]: memberUserIdAlice, + [memberEventBob]: memberUserIdBob, + "$membership-charlie:example.org": "@charlie:example.org", +}; const TestComponent: FC = () => { - const { raisedHands } = useReactions(); + const { raisedHands, myReactionId } = useReactions(); return ( -
    - {Object.entries(raisedHands).map(([userId, date]) => ( -
  • - {userId} - -
  • - ))} -
+
+
    + {Object.entries(raisedHands).map(([userId, date]) => ( +
  • + {userId} + +
  • + ))} +
+

{myReactionId ? "Local reaction" : "No local reaction"}

+
); }; @@ -59,9 +67,9 @@ const TestComponentWrapper = ({ }; export class MockRTCSession extends EventEmitter { - public memberships = membership.map((sender) => ({ + public memberships = Object.entries(membership).map(([eventId, sender]) => ({ sender, - eventId: `!fake-${randomUUID()}:event`, + eventId, createdTs: (): Date => new Date(), })); @@ -69,12 +77,12 @@ export class MockRTCSession extends EventEmitter { super(); } - public testRemoveMember(userId: string) { + public testRemoveMember(userId: string): void { this.memberships = this.memberships.filter((u) => u.sender !== userId); this.emit(MatrixRTCSessionEvent.MembershipsChanged); } - public testAddMember(sender: string) { + public testAddMember(sender: string): void { this.memberships.push({ sender, eventId: `!fake-${randomUUID()}:event`, @@ -84,25 +92,27 @@ export class MockRTCSession extends EventEmitter { } } -function createReaction(sender: string): MatrixEvent { +function createReaction(parentMemberEvent: string): MatrixEvent { return new MatrixEvent({ - sender, + sender: membership[parentMemberEvent], type: EventType.Reaction, origin_server_ts: new Date().getTime(), content: { "m.relates_to": { key: "🖐️", + event_id: parentMemberEvent, }, }, event_id: randomUUID(), }); } -function createRedaction(sender: string): MatrixEvent { +function createRedaction(sender: string, reactionEventId: string): MatrixEvent { return new MatrixEvent({ sender, type: EventType.RoomRedaction, origin_server_ts: new Date().getTime(), + redacts: reactionEventId, content: {}, event_id: randomUUID(), }); @@ -115,29 +125,27 @@ export class MockRoom extends EventEmitter { public get client(): MatrixClient { return { - getUserId: (): string => "@alice:example.org", + getUserId: (): string => memberUserIdAlice, } as unknown as MatrixClient; } public get relations(): Room["relations"] { return { - getChildEventsForEvent: () => ({ - getRelations: () => this.existingRelations, + getChildEventsForEvent: (membershipEventId: string) => ({ + getRelations: (): MatrixEvent[] => { + const sender = membership[membershipEventId]; + return this.existingRelations.filter((r) => r.getSender() === sender); + }, }), } as unknown as Room["relations"]; } - public testSendReaction(sender: string): void { - this.emit( - RoomEvent.Timeline, - createReaction(sender), - this, - undefined, - false, - { - timeline: new EventTimeline(new EventTimelineSet(undefined)), - }, - ); + public testSendReaction(parentMemberEvent: string): string { + const evt = createReaction(parentMemberEvent); + this.emit(RoomEvent.Timeline, evt, this, undefined, false, { + timeline: new EventTimeline(new EventTimelineSet(undefined)), + }); + return evt.getId()!; } } @@ -149,16 +157,26 @@ describe("useReactions", () => { ); expect(queryByRole("list")?.children).to.have.lengthOf(0); }); + test("handles own raised hand", () => { + const room = new MockRoom(); + const rtcSession = new MockRTCSession(room); + const { queryByText, rerender } = render( + , + ); + room.testSendReaction(memberEventAlice); + rerender(); + expect(queryByText("Local reaction")).toBeTruthy(); + }); test("handles incoming raised hand", () => { const room = new MockRoom(); const rtcSession = new MockRTCSession(room); const { queryByRole, rerender } = render( , ); - room.testSendReaction("@foo:bar"); + room.testSendReaction(memberEventAlice); rerender(); expect(queryByRole("list")?.children).to.have.lengthOf(1); - room.testSendReaction("@baz:bar"); + room.testSendReaction(memberEventBob); rerender(); expect(queryByRole("list")?.children).to.have.lengthOf(2); }); @@ -168,12 +186,12 @@ describe("useReactions", () => { const { queryByRole, rerender } = render( , ); - room.testSendReaction("@foo:bar"); + const reactionEventId = room.testSendReaction(memberEventAlice); rerender(); expect(queryByRole("list")?.children).to.have.lengthOf(1); room.emit( RoomEvent.Redaction, - createRedaction("@foo:bar"), + createRedaction(memberUserIdAlice, reactionEventId), room, undefined, ); @@ -181,33 +199,33 @@ describe("useReactions", () => { expect(queryByRole("list")?.children).to.have.lengthOf(0); }); test("handles loading events from cold", () => { - const room = new MockRoom([createReaction(membership[0])]); + const room = new MockRoom([createReaction(memberEventAlice)]); const rtcSession = new MockRTCSession(room); const { queryByRole } = render( , ); expect(queryByRole("list")?.children).to.have.lengthOf(1); }); - test.only("will remove reaction when a member leaves the call", () => { - const room = new MockRoom([createReaction(membership[0])]); + test("will remove reaction when a member leaves the call", () => { + const room = new MockRoom([createReaction(memberEventAlice)]); const rtcSession = new MockRTCSession(room); const { queryByRole, rerender } = render( , ); expect(queryByRole("list")?.children).to.have.lengthOf(1); - rtcSession.testRemoveMember(membership[0]); + rtcSession.testRemoveMember(memberUserIdAlice); rerender(); expect(queryByRole("list")?.children).to.have.lengthOf(0); }); test("will remove reaction when a member joins via a new event", () => { - const room = new MockRoom([createReaction(membership[0])]); + const room = new MockRoom([createReaction(memberEventAlice)]); const rtcSession = new MockRTCSession(room); const { queryByRole, rerender } = render( , ); expect(queryByRole("list")?.children).to.have.lengthOf(1); - rtcSession.testRemoveMember(membership[0]); - rtcSession.testAddMember(membership[0]); + rtcSession.testRemoveMember(memberUserIdAlice); + rtcSession.testAddMember(memberUserIdAlice); rerender(); expect(queryByRole("list")?.children).to.have.lengthOf(0); }); diff --git a/src/useReactions.tsx b/src/useReactions.tsx index d0680257..a23f13a1 100644 --- a/src/useReactions.tsx +++ b/src/useReactions.tsx @@ -22,10 +22,10 @@ import { useMemo, } from "react"; import { MatrixRTCSession } from "matrix-js-sdk/src/matrixrtc/MatrixRTCSession"; +import { logger } from "matrix-js-sdk/src/logger"; import { useMatrixRTCSessionMemberships } from "./useMatrixRTCSessionMemberships"; import { useClientState } from "./ClientContext"; -import { logger } from "matrix-js-sdk/src/logger"; interface ReactionsContextType { raisedHands: Record; @@ -33,7 +33,6 @@ interface ReactionsContextType { removeRaisedHand: (userId: string) => void; supportsReactions: boolean; myReactionId: string | null; - setMyReactionId: (id: string | null) => void; } const ReactionsContext = createContext( @@ -54,6 +53,9 @@ export const useReactions = (): ReactionsContextType => { return context; }; +/** + * Provider that handles raised hand reactions for a given `rtcSession`. + */ export const ReactionsProvider = ({ children, rtcSession, @@ -64,14 +66,19 @@ export const ReactionsProvider = ({ const [raisedHands, setRaisedHands] = useState< Record >({}); - const [myReactionId, setMyReactionId] = useState(null); const memberships = useMatrixRTCSessionMemberships(rtcSession); const clientState = useClientState(); const supportsReactions = clientState?.state === "valid" && clientState.supportedFeatures.reactions; const room = rtcSession.room; - const myUserId = room.client.getUserId(); + const myReactionId = useMemo((): string | null => { + const myUserId = room.client.getUserId(); + if (myUserId) { + return raisedHands[myUserId]?.reactionEventId; + } + return null; + }, [raisedHands, room]); const addRaisedHand = useCallback( (userId: string, info: RaisedHandInfo) => { @@ -86,9 +93,6 @@ export const ReactionsProvider = ({ const removeRaisedHand = useCallback( (userId: string) => { delete raisedHands[userId]; - if (userId === myUserId) { - setMyReactionId(null); - } setRaisedHands({ ...raisedHands }); }, [raisedHands], @@ -106,7 +110,6 @@ export const ReactionsProvider = ({ return allEvents.length > 0 ? allEvents[0] : undefined; }; - console.log(memberships, raisedHands); // Remove any raised hands for users no longer joined to the call. for (const userId of Object.keys(raisedHands).filter( (rhId) => !memberships.find((u) => u.sender == rhId), @@ -133,19 +136,14 @@ export const ReactionsProvider = ({ if (reaction && reaction.getType() === EventType.Reaction) { const content = reaction.getContent() as ReactionEventContent; if (content?.["m.relates_to"]?.key === "🖐️") { - console.log("found key, raising hand", m.sender); addRaisedHand(m.sender, { membershipEventId: m.eventId, reactionEventId: eventId, time: new Date(reaction.localTimestamp), }); - if (m.sender === room.client.getUserId()) { - setMyReactionId(eventId); - } } } } - console.log("After", raisedHands); // Deliberately ignoring addRaisedHand, raisedHands which was causing looping. // eslint-disable-next-line react-hooks/exhaustive-deps }, [room, memberships]); @@ -186,7 +184,6 @@ export const ReactionsProvider = ({ const targetUser = Object.entries(raisedHands).find( ([u, r]) => r.reactionEventId === targetEvent, )?.[0]; - console.log(targetEvent, raisedHands); if (!targetUser) { // Reaction target was not for us, ignoring return; @@ -202,7 +199,7 @@ export const ReactionsProvider = ({ room.off(MatrixRoomEvent.Timeline, handleReactionEvent); room.off(MatrixRoomEvent.Redaction, handleReactionEvent); }; - }, [room, addRaisedHand, removeRaisedHand]); + }, [room, addRaisedHand, removeRaisedHand, memberships, raisedHands]); // Reduce the data down for the consumers. const resultRaisedHands = useMemo( @@ -221,7 +218,6 @@ export const ReactionsProvider = ({ removeRaisedHand, supportsReactions, myReactionId, - setMyReactionId, }} > {children}