diff --git a/src/reactions/RaisedHandIndicator.tsx b/src/reactions/RaisedHandIndicator.tsx index 7375dd06..245a7eb3 100644 --- a/src/reactions/RaisedHandIndicator.tsx +++ b/src/reactions/RaisedHandIndicator.tsx @@ -21,6 +21,7 @@ export function RaisedHandIndicator({ }): ReactNode { const [raisedHandDuration, setRaisedHandDuration] = useState(""); + // This effect creates a simple timer effect. useEffect(() => { if (!raisedHandTime || !showTimer) { return; diff --git a/src/useReactions.test.tsx b/src/useReactions.test.tsx index 0a8d2b8e..7f5517ca 100644 --- a/src/useReactions.test.tsx +++ b/src/useReactions.test.tsx @@ -26,6 +26,13 @@ import { randomUUID } from "crypto"; import { ReactionsProvider, useReactions } from "./useReactions"; +/** + * Test explanation. + * This test suite checks that the useReactions hook appropriately reacts + * to new reactions, redactions and membership changesin the room. There is + * a large amount of test structure used to construct a mock environment. + */ + const memberUserIdAlice = "@alice:example.org"; const memberEventAlice = "$membership-alice:example.org"; const memberUserIdBob = "@bob:example.org"; @@ -198,7 +205,7 @@ describe("useReactions", () => { rerender(); expect(queryByRole("list")?.children).to.have.lengthOf(0); }); - test("handles loading events from cold", () => { + test("handles loading prior raised hand events", () => { const room = new MockRoom([createReaction(memberEventAlice)]); const rtcSession = new MockRTCSession(room); const { queryByRole } = render( @@ -206,6 +213,8 @@ describe("useReactions", () => { ); expect(queryByRole("list")?.children).to.have.lengthOf(1); }); + // If the membership event changes for a user, we want to remove + // the raised hand event. test("will remove reaction when a member leaves the call", () => { const room = new MockRoom([createReaction(memberEventAlice)]); const rtcSession = new MockRTCSession(room); @@ -224,6 +233,7 @@ describe("useReactions", () => { , ); expect(queryByRole("list")?.children).to.have.lengthOf(1); + // Simulate leaving and rejoining rtcSession.testRemoveMember(memberUserIdAlice); rtcSession.testAddMember(memberUserIdAlice); rerender(); diff --git a/src/useReactions.tsx b/src/useReactions.tsx index a23f13a1..d2e46b57 100644 --- a/src/useReactions.tsx +++ b/src/useReactions.tsx @@ -72,6 +72,7 @@ export const ReactionsProvider = ({ clientState?.state === "valid" && clientState.supportedFeatures.reactions; const room = rtcSession.room; + // Calculate our own reaction event. const myReactionId = useMemo((): string | null => { const myUserId = room.client.getUserId(); if (myUserId) { @@ -80,6 +81,15 @@ export const ReactionsProvider = ({ return null; }, [raisedHands, room]); + // Reduce the data down for the consumers. + const resultRaisedHands = useMemo( + () => + Object.fromEntries( + Object.entries(raisedHands).map(([uid, data]) => [uid, data.time]), + ), + [raisedHands], + ); + const addRaisedHand = useCallback( (userId: string, info: RaisedHandInfo) => { setRaisedHands({ @@ -98,7 +108,7 @@ export const ReactionsProvider = ({ [raisedHands], ); - // Load any existing reactions. + // This effect will check the state whenever the membership of the session changes. useEffect(() => { const getLastReactionEvent = (eventId: string): MatrixEvent | undefined => { const relations = room.relations.getChildEventsForEvent( @@ -148,6 +158,7 @@ export const ReactionsProvider = ({ // eslint-disable-next-line react-hooks/exhaustive-deps }, [room, memberships]); + // This effect handles any *live* reaction/redactions in the room. useEffect(() => { const handleReactionEvent = (event: MatrixEvent): void => { const sender = event.getSender(); @@ -201,15 +212,6 @@ export const ReactionsProvider = ({ }; }, [room, addRaisedHand, removeRaisedHand, memberships, raisedHands]); - // Reduce the data down for the consumers. - const resultRaisedHands = useMemo( - () => - Object.fromEntries( - Object.entries(raisedHands).map(([uid, data]) => [uid, data.time]), - ), - [raisedHands], - ); - return (