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
This commit is contained in:
Florian Duros
2026-05-20 09:40:09 +00:00
committed by GitHub
parent d41ff70057
commit 4b6e0b7ebf
4 changed files with 34 additions and 26 deletions
@@ -21,7 +21,6 @@ import { useRoomState } from "../../../hooks/useRoomState";
import RoomContext, { TimelineRenderingType } from "../../../contexts/RoomContext"; import RoomContext, { TimelineRenderingType } from "../../../contexts/RoomContext";
import { ReadPinsEventId } from "./types"; import { ReadPinsEventId } from "./types";
import { type RoomPermalinkCreator } from "../../../utils/permalinks/Permalinks"; import { type RoomPermalinkCreator } from "../../../utils/permalinks/Permalinks";
import { filterBoolean } from "../../../utils/arrays";
import Modal from "../../../Modal"; import Modal from "../../../Modal";
import { UnpinAllDialog } from "../dialogs/UnpinAllDialog"; import { UnpinAllDialog } from "../dialogs/UnpinAllDialog";
import EmptyState from "./EmptyState"; import EmptyState from "./EmptyState";
@@ -77,9 +76,7 @@ export function PinnedMessagesCard({ room, onClose, permalinkCreator }: PinnedMe
/> />
); );
} else if (pinnedEvents?.length) { } else if (pinnedEvents?.length) {
content = ( content = <PinnedMessages events={pinnedEvents} room={room} permalinkCreator={permalinkCreator} />;
<PinnedMessages events={filterBoolean(pinnedEvents)} room={room} permalinkCreator={permalinkCreator} />
);
} else { } else {
content = <Spinner />; content = <Spinner />;
} }
+7 -13
View File
@@ -25,6 +25,7 @@ import { useMatrixClientContext } from "../contexts/MatrixClientContext";
import { useAsyncMemo } from "./useAsyncMemo"; import { useAsyncMemo } from "./useAsyncMemo";
import PinningUtils from "../utils/PinningUtils"; import PinningUtils from "../utils/PinningUtils";
import { batch } from "../utils/promise.ts"; import { batch } from "../utils/promise.ts";
import { filterBoolean } from "../utils/arrays.ts";
/** /**
* Get the pinned event IDs from a room. * Get the pinned event IDs from a room.
@@ -176,18 +177,19 @@ async function fetchPinnedEvent(room: Room, pinnedEventId: string, cli: MatrixCl
* @param room * @param room
* @param pinnedEventIds * @param pinnedEventIds
*/ */
export function useFetchedPinnedEvents(room: Room, pinnedEventIds: string[]): Array<MatrixEvent | null> | null { export function useFetchedPinnedEvents(room: Room, pinnedEventIds: string[]): Array<MatrixEvent> {
const cli = useMatrixClientContext(); const cli = useMatrixClientContext();
return useAsyncMemo( const events = useAsyncMemo(
() => { () => {
const fetchPromises = pinnedEventIds.map((eventId) => () => fetchPinnedEvent(room, eventId, cli)); const fetchPromises = pinnedEventIds.map((eventId) => () => fetchPinnedEvent(room, eventId, cli));
// Fetch the pinned events in batches of 10 // Fetch the pinned events in batches of 10
return batch(fetchPromises, 10); return batch(fetchPromises, 10);
}, },
[cli, room, pinnedEventIds], [cli, room, pinnedEventIds],
null, [],
); );
return filterBoolean(events);
} }
/** /**
@@ -196,15 +198,7 @@ export function useFetchedPinnedEvents(room: Room, pinnedEventIds: string[]): Ar
* @param room * @param room
* @param pinnedEventIds * @param pinnedEventIds
*/ */
export function useSortedFetchedPinnedEvents(room: Room, pinnedEventIds: string[]): Array<MatrixEvent | null> { export function useSortedFetchedPinnedEvents(room: Room, pinnedEventIds: string[]): Array<MatrixEvent> {
const pinnedEvents = useFetchedPinnedEvents(room, pinnedEventIds); const pinnedEvents = useFetchedPinnedEvents(room, pinnedEventIds);
return useMemo(() => { return useMemo(() => pinnedEvents.sort((a, b) => a.getTs() - b.getTs()), [pinnedEvents]);
if (!pinnedEvents) return [];
return pinnedEvents.sort((a, b) => {
if (!a) return -1;
if (!b) return 1;
return a.getTs() - b.getTs();
});
}, [pinnedEvents]);
} }
@@ -294,6 +294,23 @@ describe("<PinnedMessagesCard />", () => {
await initPinnedMessagesCard([], [pin]); await initPinnedMessagesCard([], [pin]);
expect(screen.queryAllByRole("listitem")).toHaveLength(0); 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", () => { describe("unpin all", () => {
@@ -329,7 +329,7 @@ exports[`<PinnedMessagesCard /> unpin all should not allow to unpinall 1`] = `
</p> </p>
</div> </div>
<button <button
aria-labelledby="_r_10f_" aria-labelledby="_r_10u_"
class="_icon-button_1215g_8" class="_icon-button_1215g_8"
data-kind="secondary" data-kind="secondary"
data-testid="base-card-close-button" data-testid="base-card-close-button"
@@ -387,13 +387,13 @@ exports[`<PinnedMessagesCard /> unpin all should not allow to unpinall 1`] = `
class="mx_PinnedEventTile_top" class="mx_PinnedEventTile_top"
> >
<span <span
aria-labelledby="_r_10m_" aria-labelledby="_r_115_"
class="mx_PinnedEventTile_sender mx_Username_color3" class="mx_PinnedEventTile_sender mx_Username_color3"
> >
@alice:example.org @alice:example.org
</span> </span>
<button <button
aria-describedby="_r_10l_" aria-describedby="_r_114_"
aria-disabled="false" aria-disabled="false"
aria-expanded="false" aria-expanded="false"
aria-haspopup="menu" aria-haspopup="menu"
@@ -401,7 +401,7 @@ exports[`<PinnedMessagesCard /> unpin all should not allow to unpinall 1`] = `
class="_icon-button_1215g_8" class="_icon-button_1215g_8"
data-kind="primary" data-kind="primary"
data-state="closed" data-state="closed"
id="radix-_r_10r_" id="radix-_r_11a_"
role="button" role="button"
style="--cpd-icon-button-size: 24px;" style="--cpd-icon-button-size: 24px;"
tabindex="0" tabindex="0"
@@ -427,7 +427,7 @@ exports[`<PinnedMessagesCard /> unpin all should not allow to unpinall 1`] = `
</div> </div>
<div <div
class="mx_MTextBody mx_EventTile_content _root_1hgc7_8 _text_1hgc7_13" class="mx_MTextBody mx_EventTile_content _root_1hgc7_8 _text_1hgc7_13"
id="_r_10l_" id="_r_114_"
> >
<div <div
class="mx_EventTile_body translate" class="mx_EventTile_body translate"
@@ -468,13 +468,13 @@ exports[`<PinnedMessagesCard /> unpin all should not allow to unpinall 1`] = `
class="mx_PinnedEventTile_top" class="mx_PinnedEventTile_top"
> >
<span <span
aria-labelledby="_r_10u_" aria-labelledby="_r_11d_"
class="mx_PinnedEventTile_sender mx_Username_color3" class="mx_PinnedEventTile_sender mx_Username_color3"
> >
@alice:example.org @alice:example.org
</span> </span>
<button <button
aria-describedby="_r_10t_" aria-describedby="_r_11c_"
aria-disabled="false" aria-disabled="false"
aria-expanded="false" aria-expanded="false"
aria-haspopup="menu" aria-haspopup="menu"
@@ -482,7 +482,7 @@ exports[`<PinnedMessagesCard /> unpin all should not allow to unpinall 1`] = `
class="_icon-button_1215g_8" class="_icon-button_1215g_8"
data-kind="primary" data-kind="primary"
data-state="closed" data-state="closed"
id="radix-_r_113_" id="radix-_r_11i_"
role="button" role="button"
style="--cpd-icon-button-size: 24px;" style="--cpd-icon-button-size: 24px;"
tabindex="0" tabindex="0"
@@ -508,7 +508,7 @@ exports[`<PinnedMessagesCard /> unpin all should not allow to unpinall 1`] = `
</div> </div>
<div <div
class="mx_MTextBody mx_EventTile_content _root_1hgc7_8 _text_1hgc7_13" class="mx_MTextBody mx_EventTile_content _root_1hgc7_8 _text_1hgc7_13"
id="_r_10t_" id="_r_11c_"
> >
<div <div
class="mx_EventTile_body translate" class="mx_EventTile_body translate"