diff --git a/src/features/messages/use-message-selection.test.tsx b/src/features/messages/use-message-selection.test.tsx new file mode 100644 index 0000000000..b9dc681666 --- /dev/null +++ b/src/features/messages/use-message-selection.test.tsx @@ -0,0 +1,54 @@ +// @vitest-environment jsdom +import { cleanup, render } from "@testing-library/react"; +import { useRef } from "react"; +import { afterEach, expect, it } from "vitest"; +import type { ChannelMessage } from "../relay/contracts"; +import type { RelaySession } from "../relay/session"; +import { selectNewestMessage } from "./message-selection"; +import { useMessageSelection } from "./use-message-selection"; + +afterEach(cleanup); + +const message = (id: string, createdAt: number, createdAtMs?: number) => + ({ id, createdAt, createdAtMs }) as ChannelMessage; + +function List({ rows }: { rows: readonly ChannelMessage[] }) { + const list = useRef(null); + useMessageSelection(list, rows, {} as RelaySession); + return ( +
+ {rows.map((row) => ( +
+
{row.id}
+
+ ))} +
+ ); +} + +function newestSelected(rows: readonly ChannelMessage[]) { + const { getByRole } = render(, { reactStrictMode: true }); + expect(selectNewestMessage(getByRole("region", { name: "Messages" }))).toBe( + true, + ); + return document.activeElement?.closest("[data-message-id]") + ?.dataset.messageId; +} + +it("selects the latest message of a same-second burst by send ms, then id", () => { + expect( + newestSelected([message("z", 100, 100_100), message("a", 100, 100_200)]), + ).toBe("a"); + cleanup(); + expect(newestSelected([message("a", 100), message("b", 100)])).toBe("b"); +}); + +it("selects the latest message across seconds and skips membership notices", () => { + const notice = { + ...message("notice", 300), + membership: {}, + } as ChannelMessage; + expect( + newestSelected([message("old", 100), message("new", 200), notice]), + ).toBe("new"); +}); diff --git a/src/features/messages/use-message-selection.ts b/src/features/messages/use-message-selection.ts index f39a4ed060..b0bb1a1476 100644 --- a/src/features/messages/use-message-selection.ts +++ b/src/features/messages/use-message-selection.ts @@ -1,5 +1,6 @@ import { useLayoutEffect, useRef, type RefObject } from "react"; import type { ChannelMessage } from "../relay/contracts"; +import { compareMessages } from "../relay/message-order"; import type { RelaySession } from "../relay/session"; import { useMessageEditScope } from "./MessageEditScope"; import { useMessageKeyActions } from "./MessageManagement"; @@ -26,7 +27,7 @@ export function useMessageSelection( newest: () => latest.current.reduce( (newest, row) => - !row.membership && (!newest || row.createdAt > newest.createdAt) + !row.membership && (!newest || compareMessages(row, newest) > 0) ? row : newest, undefined,