Tidy up and finish test rewrites

This commit is contained in:
Half-Shot
2024-10-29 16:49:37 +00:00
parent 5a5c1bedfe
commit ff7da135ca
5 changed files with 79 additions and 79 deletions

View File

@@ -61,13 +61,8 @@ export function RaiseHandToggleButton({
client, client,
rtcSession, rtcSession,
}: RaisedHandToggleButton): ReactNode { }: RaisedHandToggleButton): ReactNode {
const { const { raisedHands, removeRaisedHand, addRaisedHand, myReactionId } =
raisedHands, useReactions();
removeRaisedHand,
addRaisedHand,
myReactionId,
setMyReactionId,
} = useReactions();
const [busy, setBusy] = useState(false); const [busy, setBusy] = useState(false);
const userId = client.getUserId()!; const userId = client.getUserId()!;
const isHandRaised = !!raisedHands[userId]; const isHandRaised = !!raisedHands[userId];
@@ -81,7 +76,6 @@ export function RaiseHandToggleButton({
.redactEvent(rtcSession.room.roomId, myReactionId) .redactEvent(rtcSession.room.roomId, myReactionId)
.then(() => { .then(() => {
logger.debug("Redacted raise hand event"); logger.debug("Redacted raise hand event");
setMyReactionId(null);
removeRaisedHand(userId); removeRaisedHand(userId);
}) })
.catch((e) => { .catch((e) => {
@@ -109,7 +103,6 @@ export function RaiseHandToggleButton({
}) })
.then((reaction) => { .then((reaction) => {
logger.debug("Sent raise hand event", reaction.event_id); logger.debug("Sent raise hand event", reaction.event_id);
setMyReactionId(reaction.event_id);
addRaisedHand(userId, { addRaisedHand(userId, {
membershipEventId: parentEventId, membershipEventId: parentEventId,
reactionEventId: reaction.event_id, reactionEventId: reaction.event_id,
@@ -131,7 +124,6 @@ export function RaiseHandToggleButton({
rtcSession.room.roomId, rtcSession.room.roomId,
addRaisedHand, addRaisedHand,
removeRaisedHand, removeRaisedHand,
setMyReactionId,
userId, userId,
]); ]);

View File

@@ -15,11 +15,7 @@ import {
useSetting, useSetting,
} from "./settings"; } from "./settings";
interface Props { export const PreferencesSettingsTab: FC = () => {
roomId?: string;
}
export const PreferencesSettingsTab: FC<Props> = ({}) => {
const { t } = useTranslation(); const { t } = useTranslation();
const [showHandRaisedTimer, setShowHandRaisedTimer] = useSetting( const [showHandRaisedTimer, setShowHandRaisedTimer] = useSetting(
showHandRaisedTimerSetting, showHandRaisedTimerSetting,

View File

@@ -53,7 +53,6 @@ interface SpotlightItemBaseProps {
unencryptedWarning: boolean; unencryptedWarning: boolean;
displayName: string; displayName: string;
"aria-hidden"?: boolean; "aria-hidden"?: boolean;
raisedHand: boolean;
} }
interface SpotlightUserMediaItemBaseProps extends SpotlightItemBaseProps { interface SpotlightUserMediaItemBaseProps extends SpotlightItemBaseProps {
@@ -158,7 +157,6 @@ const SpotlightItem = forwardRef<HTMLDivElement, SpotlightItemProps>(
unencryptedWarning, unencryptedWarning,
displayName, displayName,
"aria-hidden": ariaHidden, "aria-hidden": ariaHidden,
raisedHand: false,
}; };
return vm instanceof ScreenShareViewModel ? ( return vm instanceof ScreenShareViewModel ? (

View File

@@ -26,23 +26,31 @@ import { randomUUID } from "crypto";
import { ReactionsProvider, useReactions } from "./useReactions"; import { ReactionsProvider, useReactions } from "./useReactions";
const membership = [ const memberUserIdAlice = "@alice:example.org";
"@alice:example.org", const memberEventAlice = "$membership-alice:example.org";
"@bob:example.org", const memberUserIdBob = "@bob:example.org";
"@charlie:example.org", const memberEventBob = "$membership-bob:example.org";
];
const membership: Record<string, string> = {
[memberEventAlice]: memberUserIdAlice,
[memberEventBob]: memberUserIdBob,
"$membership-charlie:example.org": "@charlie:example.org",
};
const TestComponent: FC = () => { const TestComponent: FC = () => {
const { raisedHands } = useReactions(); const { raisedHands, myReactionId } = useReactions();
return ( return (
<ul> <div>
{Object.entries(raisedHands).map(([userId, date]) => ( <ul>
<li key={userId}> {Object.entries(raisedHands).map(([userId, date]) => (
<span>{userId}</span> <li key={userId}>
<time>{date.getTime()}</time> <span>{userId}</span>
</li> <time>{date.getTime()}</time>
))} </li>
</ul> ))}
</ul>
<p>{myReactionId ? "Local reaction" : "No local reaction"}</p>
</div>
); );
}; };
@@ -59,9 +67,9 @@ const TestComponentWrapper = ({
}; };
export class MockRTCSession extends EventEmitter { export class MockRTCSession extends EventEmitter {
public memberships = membership.map((sender) => ({ public memberships = Object.entries(membership).map(([eventId, sender]) => ({
sender, sender,
eventId: `!fake-${randomUUID()}:event`, eventId,
createdTs: (): Date => new Date(), createdTs: (): Date => new Date(),
})); }));
@@ -69,12 +77,12 @@ export class MockRTCSession extends EventEmitter {
super(); super();
} }
public testRemoveMember(userId: string) { public testRemoveMember(userId: string): void {
this.memberships = this.memberships.filter((u) => u.sender !== userId); this.memberships = this.memberships.filter((u) => u.sender !== userId);
this.emit(MatrixRTCSessionEvent.MembershipsChanged); this.emit(MatrixRTCSessionEvent.MembershipsChanged);
} }
public testAddMember(sender: string) { public testAddMember(sender: string): void {
this.memberships.push({ this.memberships.push({
sender, sender,
eventId: `!fake-${randomUUID()}:event`, 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({ return new MatrixEvent({
sender, sender: membership[parentMemberEvent],
type: EventType.Reaction, type: EventType.Reaction,
origin_server_ts: new Date().getTime(), origin_server_ts: new Date().getTime(),
content: { content: {
"m.relates_to": { "m.relates_to": {
key: "🖐️", key: "🖐️",
event_id: parentMemberEvent,
}, },
}, },
event_id: randomUUID(), event_id: randomUUID(),
}); });
} }
function createRedaction(sender: string): MatrixEvent { function createRedaction(sender: string, reactionEventId: string): MatrixEvent {
return new MatrixEvent({ return new MatrixEvent({
sender, sender,
type: EventType.RoomRedaction, type: EventType.RoomRedaction,
origin_server_ts: new Date().getTime(), origin_server_ts: new Date().getTime(),
redacts: reactionEventId,
content: {}, content: {},
event_id: randomUUID(), event_id: randomUUID(),
}); });
@@ -115,29 +125,27 @@ export class MockRoom extends EventEmitter {
public get client(): MatrixClient { public get client(): MatrixClient {
return { return {
getUserId: (): string => "@alice:example.org", getUserId: (): string => memberUserIdAlice,
} as unknown as MatrixClient; } as unknown as MatrixClient;
} }
public get relations(): Room["relations"] { public get relations(): Room["relations"] {
return { return {
getChildEventsForEvent: () => ({ getChildEventsForEvent: (membershipEventId: string) => ({
getRelations: () => this.existingRelations, getRelations: (): MatrixEvent[] => {
const sender = membership[membershipEventId];
return this.existingRelations.filter((r) => r.getSender() === sender);
},
}), }),
} as unknown as Room["relations"]; } as unknown as Room["relations"];
} }
public testSendReaction(sender: string): void { public testSendReaction(parentMemberEvent: string): string {
this.emit( const evt = createReaction(parentMemberEvent);
RoomEvent.Timeline, this.emit(RoomEvent.Timeline, evt, this, undefined, false, {
createReaction(sender), timeline: new EventTimeline(new EventTimelineSet(undefined)),
this, });
undefined, return evt.getId()!;
false,
{
timeline: new EventTimeline(new EventTimelineSet(undefined)),
},
);
} }
} }
@@ -149,16 +157,26 @@ describe("useReactions", () => {
); );
expect(queryByRole("list")?.children).to.have.lengthOf(0); 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(
<TestComponentWrapper rtcSession={rtcSession} />,
);
room.testSendReaction(memberEventAlice);
rerender(<TestComponentWrapper rtcSession={rtcSession} />);
expect(queryByText("Local reaction")).toBeTruthy();
});
test("handles incoming raised hand", () => { test("handles incoming raised hand", () => {
const room = new MockRoom(); const room = new MockRoom();
const rtcSession = new MockRTCSession(room); const rtcSession = new MockRTCSession(room);
const { queryByRole, rerender } = render( const { queryByRole, rerender } = render(
<TestComponentWrapper rtcSession={rtcSession} />, <TestComponentWrapper rtcSession={rtcSession} />,
); );
room.testSendReaction("@foo:bar"); room.testSendReaction(memberEventAlice);
rerender(<TestComponentWrapper rtcSession={rtcSession} />); rerender(<TestComponentWrapper rtcSession={rtcSession} />);
expect(queryByRole("list")?.children).to.have.lengthOf(1); expect(queryByRole("list")?.children).to.have.lengthOf(1);
room.testSendReaction("@baz:bar"); room.testSendReaction(memberEventBob);
rerender(<TestComponentWrapper rtcSession={rtcSession} />); rerender(<TestComponentWrapper rtcSession={rtcSession} />);
expect(queryByRole("list")?.children).to.have.lengthOf(2); expect(queryByRole("list")?.children).to.have.lengthOf(2);
}); });
@@ -168,12 +186,12 @@ describe("useReactions", () => {
const { queryByRole, rerender } = render( const { queryByRole, rerender } = render(
<TestComponentWrapper rtcSession={rtcSession} />, <TestComponentWrapper rtcSession={rtcSession} />,
); );
room.testSendReaction("@foo:bar"); const reactionEventId = room.testSendReaction(memberEventAlice);
rerender(<TestComponentWrapper rtcSession={rtcSession} />); rerender(<TestComponentWrapper rtcSession={rtcSession} />);
expect(queryByRole("list")?.children).to.have.lengthOf(1); expect(queryByRole("list")?.children).to.have.lengthOf(1);
room.emit( room.emit(
RoomEvent.Redaction, RoomEvent.Redaction,
createRedaction("@foo:bar"), createRedaction(memberUserIdAlice, reactionEventId),
room, room,
undefined, undefined,
); );
@@ -181,33 +199,33 @@ describe("useReactions", () => {
expect(queryByRole("list")?.children).to.have.lengthOf(0); expect(queryByRole("list")?.children).to.have.lengthOf(0);
}); });
test("handles loading events from cold", () => { 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 rtcSession = new MockRTCSession(room);
const { queryByRole } = render( const { queryByRole } = render(
<TestComponentWrapper rtcSession={rtcSession} />, <TestComponentWrapper rtcSession={rtcSession} />,
); );
expect(queryByRole("list")?.children).to.have.lengthOf(1); expect(queryByRole("list")?.children).to.have.lengthOf(1);
}); });
test.only("will remove reaction when a member leaves the call", () => { test("will remove reaction when a member leaves the call", () => {
const room = new MockRoom([createReaction(membership[0])]); const room = new MockRoom([createReaction(memberEventAlice)]);
const rtcSession = new MockRTCSession(room); const rtcSession = new MockRTCSession(room);
const { queryByRole, rerender } = render( const { queryByRole, rerender } = render(
<TestComponentWrapper rtcSession={rtcSession} />, <TestComponentWrapper rtcSession={rtcSession} />,
); );
expect(queryByRole("list")?.children).to.have.lengthOf(1); expect(queryByRole("list")?.children).to.have.lengthOf(1);
rtcSession.testRemoveMember(membership[0]); rtcSession.testRemoveMember(memberUserIdAlice);
rerender(<TestComponentWrapper rtcSession={rtcSession} />); rerender(<TestComponentWrapper rtcSession={rtcSession} />);
expect(queryByRole("list")?.children).to.have.lengthOf(0); expect(queryByRole("list")?.children).to.have.lengthOf(0);
}); });
test("will remove reaction when a member joins via a new event", () => { 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 rtcSession = new MockRTCSession(room);
const { queryByRole, rerender } = render( const { queryByRole, rerender } = render(
<TestComponentWrapper rtcSession={rtcSession} />, <TestComponentWrapper rtcSession={rtcSession} />,
); );
expect(queryByRole("list")?.children).to.have.lengthOf(1); expect(queryByRole("list")?.children).to.have.lengthOf(1);
rtcSession.testRemoveMember(membership[0]); rtcSession.testRemoveMember(memberUserIdAlice);
rtcSession.testAddMember(membership[0]); rtcSession.testAddMember(memberUserIdAlice);
rerender(<TestComponentWrapper rtcSession={rtcSession} />); rerender(<TestComponentWrapper rtcSession={rtcSession} />);
expect(queryByRole("list")?.children).to.have.lengthOf(0); expect(queryByRole("list")?.children).to.have.lengthOf(0);
}); });

View File

@@ -22,10 +22,10 @@ import {
useMemo, useMemo,
} from "react"; } from "react";
import { MatrixRTCSession } from "matrix-js-sdk/src/matrixrtc/MatrixRTCSession"; import { MatrixRTCSession } from "matrix-js-sdk/src/matrixrtc/MatrixRTCSession";
import { logger } from "matrix-js-sdk/src/logger";
import { useMatrixRTCSessionMemberships } from "./useMatrixRTCSessionMemberships"; import { useMatrixRTCSessionMemberships } from "./useMatrixRTCSessionMemberships";
import { useClientState } from "./ClientContext"; import { useClientState } from "./ClientContext";
import { logger } from "matrix-js-sdk/src/logger";
interface ReactionsContextType { interface ReactionsContextType {
raisedHands: Record<string, Date>; raisedHands: Record<string, Date>;
@@ -33,7 +33,6 @@ interface ReactionsContextType {
removeRaisedHand: (userId: string) => void; removeRaisedHand: (userId: string) => void;
supportsReactions: boolean; supportsReactions: boolean;
myReactionId: string | null; myReactionId: string | null;
setMyReactionId: (id: string | null) => void;
} }
const ReactionsContext = createContext<ReactionsContextType | undefined>( const ReactionsContext = createContext<ReactionsContextType | undefined>(
@@ -54,6 +53,9 @@ export const useReactions = (): ReactionsContextType => {
return context; return context;
}; };
/**
* Provider that handles raised hand reactions for a given `rtcSession`.
*/
export const ReactionsProvider = ({ export const ReactionsProvider = ({
children, children,
rtcSession, rtcSession,
@@ -64,14 +66,19 @@ export const ReactionsProvider = ({
const [raisedHands, setRaisedHands] = useState< const [raisedHands, setRaisedHands] = useState<
Record<string, RaisedHandInfo> Record<string, RaisedHandInfo>
>({}); >({});
const [myReactionId, setMyReactionId] = useState<string | null>(null);
const memberships = useMatrixRTCSessionMemberships(rtcSession); const memberships = useMatrixRTCSessionMemberships(rtcSession);
const clientState = useClientState(); const clientState = useClientState();
const supportsReactions = const supportsReactions =
clientState?.state === "valid" && clientState.supportedFeatures.reactions; clientState?.state === "valid" && clientState.supportedFeatures.reactions;
const room = rtcSession.room; 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( const addRaisedHand = useCallback(
(userId: string, info: RaisedHandInfo) => { (userId: string, info: RaisedHandInfo) => {
@@ -86,9 +93,6 @@ export const ReactionsProvider = ({
const removeRaisedHand = useCallback( const removeRaisedHand = useCallback(
(userId: string) => { (userId: string) => {
delete raisedHands[userId]; delete raisedHands[userId];
if (userId === myUserId) {
setMyReactionId(null);
}
setRaisedHands({ ...raisedHands }); setRaisedHands({ ...raisedHands });
}, },
[raisedHands], [raisedHands],
@@ -106,7 +110,6 @@ export const ReactionsProvider = ({
return allEvents.length > 0 ? allEvents[0] : undefined; return allEvents.length > 0 ? allEvents[0] : undefined;
}; };
console.log(memberships, raisedHands);
// Remove any raised hands for users no longer joined to the call. // Remove any raised hands for users no longer joined to the call.
for (const userId of Object.keys(raisedHands).filter( for (const userId of Object.keys(raisedHands).filter(
(rhId) => !memberships.find((u) => u.sender == rhId), (rhId) => !memberships.find((u) => u.sender == rhId),
@@ -133,19 +136,14 @@ export const ReactionsProvider = ({
if (reaction && reaction.getType() === EventType.Reaction) { if (reaction && reaction.getType() === EventType.Reaction) {
const content = reaction.getContent() as ReactionEventContent; const content = reaction.getContent() as ReactionEventContent;
if (content?.["m.relates_to"]?.key === "🖐️") { if (content?.["m.relates_to"]?.key === "🖐️") {
console.log("found key, raising hand", m.sender);
addRaisedHand(m.sender, { addRaisedHand(m.sender, {
membershipEventId: m.eventId, membershipEventId: m.eventId,
reactionEventId: eventId, reactionEventId: eventId,
time: new Date(reaction.localTimestamp), 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. // Deliberately ignoring addRaisedHand, raisedHands which was causing looping.
// eslint-disable-next-line react-hooks/exhaustive-deps // eslint-disable-next-line react-hooks/exhaustive-deps
}, [room, memberships]); }, [room, memberships]);
@@ -186,7 +184,6 @@ export const ReactionsProvider = ({
const targetUser = Object.entries(raisedHands).find( const targetUser = Object.entries(raisedHands).find(
([u, r]) => r.reactionEventId === targetEvent, ([u, r]) => r.reactionEventId === targetEvent,
)?.[0]; )?.[0];
console.log(targetEvent, raisedHands);
if (!targetUser) { if (!targetUser) {
// Reaction target was not for us, ignoring // Reaction target was not for us, ignoring
return; return;
@@ -202,7 +199,7 @@ export const ReactionsProvider = ({
room.off(MatrixRoomEvent.Timeline, handleReactionEvent); room.off(MatrixRoomEvent.Timeline, handleReactionEvent);
room.off(MatrixRoomEvent.Redaction, handleReactionEvent); room.off(MatrixRoomEvent.Redaction, handleReactionEvent);
}; };
}, [room, addRaisedHand, removeRaisedHand]); }, [room, addRaisedHand, removeRaisedHand, memberships, raisedHands]);
// Reduce the data down for the consumers. // Reduce the data down for the consumers.
const resultRaisedHands = useMemo( const resultRaisedHands = useMemo(
@@ -221,7 +218,6 @@ export const ReactionsProvider = ({
removeRaisedHand, removeRaisedHand,
supportsReactions, supportsReactions,
myReactionId, myReactionId,
setMyReactionId,
}} }}
> >
{children} {children}