From 4b6e0b7ebfd05ed53ca7f05e7cbc97ec5d7c014a Mon Sep 17 00:00:00 2001 From: Florian Duros Date: Wed, 20 May 2026 11:40:09 +0200 Subject: [PATCH] Fix pinned message banner disappearing when a pinned message event is unkown (#33534) * fix: remove unknown event when fetching pinned message events * test: update tsts --- .../views/right_panel/PinnedMessagesCard.tsx | 5 +---- apps/web/src/hooks/usePinnedEvents.ts | 20 +++++++------------ .../right_panel/PinnedMessagesCard-test.tsx | 17 ++++++++++++++++ .../PinnedMessagesCard-test.tsx.snap | 18 ++++++++--------- 4 files changed, 34 insertions(+), 26 deletions(-) diff --git a/apps/web/src/components/views/right_panel/PinnedMessagesCard.tsx b/apps/web/src/components/views/right_panel/PinnedMessagesCard.tsx index 611b485b78..b2b4aa72cd 100644 --- a/apps/web/src/components/views/right_panel/PinnedMessagesCard.tsx +++ b/apps/web/src/components/views/right_panel/PinnedMessagesCard.tsx @@ -21,7 +21,6 @@ import { useRoomState } from "../../../hooks/useRoomState"; import RoomContext, { TimelineRenderingType } from "../../../contexts/RoomContext"; import { ReadPinsEventId } from "./types"; import { type RoomPermalinkCreator } from "../../../utils/permalinks/Permalinks"; -import { filterBoolean } from "../../../utils/arrays"; import Modal from "../../../Modal"; import { UnpinAllDialog } from "../dialogs/UnpinAllDialog"; import EmptyState from "./EmptyState"; @@ -77,9 +76,7 @@ export function PinnedMessagesCard({ room, onClose, permalinkCreator }: PinnedMe /> ); } else if (pinnedEvents?.length) { - content = ( - - ); + content = ; } else { content = ; } diff --git a/apps/web/src/hooks/usePinnedEvents.ts b/apps/web/src/hooks/usePinnedEvents.ts index 98f544097b..ea0524c710 100644 --- a/apps/web/src/hooks/usePinnedEvents.ts +++ b/apps/web/src/hooks/usePinnedEvents.ts @@ -25,6 +25,7 @@ import { useMatrixClientContext } from "../contexts/MatrixClientContext"; import { useAsyncMemo } from "./useAsyncMemo"; import PinningUtils from "../utils/PinningUtils"; import { batch } from "../utils/promise.ts"; +import { filterBoolean } from "../utils/arrays.ts"; /** * Get the pinned event IDs from a room. @@ -176,18 +177,19 @@ async function fetchPinnedEvent(room: Room, pinnedEventId: string, cli: MatrixCl * @param room * @param pinnedEventIds */ -export function useFetchedPinnedEvents(room: Room, pinnedEventIds: string[]): Array | null { +export function useFetchedPinnedEvents(room: Room, pinnedEventIds: string[]): Array { const cli = useMatrixClientContext(); - return useAsyncMemo( + const events = useAsyncMemo( () => { const fetchPromises = pinnedEventIds.map((eventId) => () => fetchPinnedEvent(room, eventId, cli)); // Fetch the pinned events in batches of 10 return batch(fetchPromises, 10); }, [cli, room, pinnedEventIds], - null, + [], ); + return filterBoolean(events); } /** @@ -196,15 +198,7 @@ export function useFetchedPinnedEvents(room: Room, pinnedEventIds: string[]): Ar * @param room * @param pinnedEventIds */ -export function useSortedFetchedPinnedEvents(room: Room, pinnedEventIds: string[]): Array { +export function useSortedFetchedPinnedEvents(room: Room, pinnedEventIds: string[]): Array { const pinnedEvents = useFetchedPinnedEvents(room, pinnedEventIds); - return useMemo(() => { - if (!pinnedEvents) return []; - - return pinnedEvents.sort((a, b) => { - if (!a) return -1; - if (!b) return 1; - return a.getTs() - b.getTs(); - }); - }, [pinnedEvents]); + return useMemo(() => pinnedEvents.sort((a, b) => a.getTs() - b.getTs()), [pinnedEvents]); } diff --git a/apps/web/test/unit-tests/components/views/right_panel/PinnedMessagesCard-test.tsx b/apps/web/test/unit-tests/components/views/right_panel/PinnedMessagesCard-test.tsx index 7196667d9c..e66930aabe 100644 --- a/apps/web/test/unit-tests/components/views/right_panel/PinnedMessagesCard-test.tsx +++ b/apps/web/test/unit-tests/components/views/right_panel/PinnedMessagesCard-test.tsx @@ -294,6 +294,23 @@ describe("", () => { await initPinnedMessagesCard([], [pin]); expect(screen.queryAllByRole("listitem")).toHaveLength(0); }); + + it("should filter out null events returned by useFetchedPinnedEvents and only display valid events", async () => { + // RoomCreate is not a pinnable event type — useFetchedPinnedEvents returns null for it + const unpinnableEvent = mkEvent({ + event: true, + type: EventType.RoomCreate, + content: {}, + room: "!room:example.org", + user: "@alice:example.org", + }); + // pin1 is a valid pinnable message, unpinnableEvent causes useFetchedPinnedEvents to return null + // useSortedFetchedPinnedEvents must filter out the null, leaving only pin1 + await initPinnedMessagesCard([pin1], [unpinnableEvent]); + + await waitFor(() => expect(screen.queryAllByRole("listitem")).toHaveLength(1)); + expect(screen.getByText("First pinned message")).toBeInTheDocument(); + }); }); describe("unpin all", () => { diff --git a/apps/web/test/unit-tests/components/views/right_panel/__snapshots__/PinnedMessagesCard-test.tsx.snap b/apps/web/test/unit-tests/components/views/right_panel/__snapshots__/PinnedMessagesCard-test.tsx.snap index 035744fd70..2a491c1fa8 100644 --- a/apps/web/test/unit-tests/components/views/right_panel/__snapshots__/PinnedMessagesCard-test.tsx.snap +++ b/apps/web/test/unit-tests/components/views/right_panel/__snapshots__/PinnedMessagesCard-test.tsx.snap @@ -329,7 +329,7 @@ exports[` unpin all should not allow to unpinall 1`] = `