Compare commits

...

4 Commits

Author SHA1 Message Date
Half-Shot
1d685bc44e Fix tests 2024-11-12 17:10:26 +00:00
Half-Shot
7a05ad9f79 update comment 2024-11-12 17:04:28 +00:00
Half-Shot
3b272181ae Use fetchRelations to backfill. 2024-11-12 17:02:04 +00:00
Half-Shot
a62e884e8c Backfill hand raised events once client has synced. 2024-11-12 16:36:41 +00:00
3 changed files with 44 additions and 23 deletions

View File

@@ -5,7 +5,7 @@ SPDX-License-Identifier: AGPL-3.0-only
Please see LICENSE in the repository root for full details.
*/
import { render } from "@testing-library/react";
import { findByRole, getByRole, render } from "@testing-library/react";
import { act, FC } from "react";
import { describe, expect, test } from "vitest";
import { RoomEvent } from "matrix-js-sdk/src/matrix";
@@ -43,7 +43,7 @@ const TestComponent: FC = () => {
<div>
<ul>
{Object.entries(raisedHands).map(([userId, date]) => (
<li key={userId}>
<li role="listitem" key={userId}>
<span>{userId}</span>
<time>{date.getTime()}</time>
</li>
@@ -106,12 +106,12 @@ describe("useReactions", () => {
createHandRaisedReaction(memberEventAlice, membership),
]);
const rtcSession = new MockRTCSession(room, membership);
const { queryByRole } = render(
const { findByRole } = render(
<TestReactionsWrapper rtcSession={rtcSession}>
<TestComponent />
</TestReactionsWrapper>,
);
expect(queryByRole("list")?.children).to.have.lengthOf(1);
expect(findByRole("listitem")).toBeTruthy();
});
// If the membership event changes for a user, we want to remove
// the raised hand event.
@@ -120,12 +120,12 @@ describe("useReactions", () => {
createHandRaisedReaction(memberEventAlice, membership),
]);
const rtcSession = new MockRTCSession(room, membership);
const { queryByRole } = render(
const { findByRole, queryByRole } = render(
<TestReactionsWrapper rtcSession={rtcSession}>
<TestComponent />
</TestReactionsWrapper>,
);
expect(queryByRole("list")?.children).to.have.lengthOf(1);
expect(findByRole("listitem")).toBeTruthy();
act(() => rtcSession.testRemoveMember(memberUserIdAlice));
expect(queryByRole("list")?.children).to.have.lengthOf(0);
});
@@ -134,12 +134,12 @@ describe("useReactions", () => {
createHandRaisedReaction(memberEventAlice, membership),
]);
const rtcSession = new MockRTCSession(room, membership);
const { queryByRole } = render(
const { queryByRole, findByRole } = render(
<TestReactionsWrapper rtcSession={rtcSession}>
<TestComponent />
</TestReactionsWrapper>,
);
expect(queryByRole("list")?.children).to.have.lengthOf(1);
expect(findByRole("listitem")).toBeTruthy();
// Simulate leaving and rejoining
act(() => {
rtcSession.testRemoveMember(memberUserIdAlice);

View File

@@ -106,6 +106,7 @@ export const ReactionsProvider = ({
);
const addRaisedHand = useCallback((userId: string, info: RaisedHandInfo) => {
logger.info(`Adding raised hand for ${userId}`);
setRaisedHands((prevRaisedHands) => ({
...prevRaisedHands,
[userId]: info,
@@ -113,6 +114,7 @@ export const ReactionsProvider = ({
}, []);
const removeRaisedHand = useCallback((userId: string) => {
logger.info(`Removing raised hand for ${userId}`);
setRaisedHands(
({ [userId]: _removed, ...remainingRaisedHands }) => remainingRaisedHands,
);
@@ -121,16 +123,26 @@ export const ReactionsProvider = ({
// This effect will check the state whenever the membership of the session changes.
useEffect(() => {
// Fetches the first reaction for a given event.
const getLastReactionEvent = (
const getLastReactionEvent = async (
eventId: string,
expectedSender: string,
): MatrixEvent | undefined => {
): Promise<MatrixEvent | undefined> => {
const relations = room.relations.getChildEventsForEvent(
eventId,
RelationType.Annotation,
EventType.Reaction,
);
const allEvents = relations?.getRelations() ?? [];
let allEvents = relations?.getRelations() ?? [];
// If we found no relations for this event, fetch via fetchRelations.
if (allEvents.length === 0) {
const res = await room.client.fetchRelations(
room.roomId,
eventId,
RelationType.Annotation,
EventType.Reaction,
);
allEvents = res.chunk.map((e) => new MatrixEvent(e));
}
return allEvents.find(
(reaction) =>
reaction.event.sender === expectedSender &&
@@ -160,18 +172,26 @@ export const ReactionsProvider = ({
// was raised, reset.
removeRaisedHand(m.sender);
}
const reaction = getLastReactionEvent(m.eventId, m.sender);
if (reaction) {
const eventId = reaction?.getId();
if (!eventId) {
continue;
}
addRaisedHand(m.sender, {
membershipEventId: m.eventId,
reactionEventId: eventId,
time: new Date(reaction.localTimestamp),
getLastReactionEvent(m.eventId, m.sender)
.then((reaction) => {
if (reaction) {
const eventId = reaction.getId();
if (!eventId) {
return;
}
addRaisedHand(m.sender!, {
membershipEventId: m.eventId!,
reactionEventId: eventId,
time: new Date(reaction.localTimestamp),
});
}
})
.catch((ex) => {
logger.warn(
`Failed to fetch reaction for member ${m.sender} (${m.eventId})`,
ex,
);
});
}
}
// Ignoring raisedHands here because we don't want to trigger each time the raised
// hands set is updated.
@@ -307,8 +327,8 @@ export const ReactionsProvider = ({
return (): void => {
room.off(MatrixRoomEvent.Timeline, handleReactionEvent);
room.off(MatrixRoomEvent.Redaction, handleReactionEvent);
room.client.off(MatrixEventEvent.Decrypted, handleReactionEvent);
room.off(MatrixRoomEvent.LocalEchoUpdated, handleReactionEvent);
room.client.off(MatrixEventEvent.Decrypted, handleReactionEvent);
reactionTimeouts.forEach((t) => clearTimeout(t));
// If we're clearing timeouts, we also clear all reactions.
setReactions({});

View File

@@ -139,6 +139,7 @@ export class MockRoom extends EventEmitter {
return Promise.resolve({ event_id: randomUUID() });
},
decryptEventIfNeeded: async () => {},
fetchRelations: async () => Promise.resolve({ chunk: [] }),
on() {
return this;
},