Files
cozsweet-frontend-nextjs/src/app/chat/__tests__/chat-scroll-session.test.ts
T

117 lines
2.9 KiB
TypeScript

import { afterEach, describe, expect, it } from "vitest";
import { ChatScrollSessionStorage } from "@/lib/chat/chat_scroll_session_storage";
import {
consumeChatScrollSnapshot,
getChatScrollRestoreTop,
saveChatScrollSnapshot,
} from "../chat-scroll-session";
afterEach(async () => {
await consumeChatScrollSnapshot();
await ChatScrollSessionStorage.clearSnapshot();
document.body.innerHTML = "";
});
describe("chat scroll session", () => {
it("saves image viewer scroll sessions by message anchor", async () => {
const { anchorNode, scrollNode } = createScrollFixture({
anchorTop: 220,
scrollTop: 120,
});
saveChatScrollSnapshot(scrollNode, { anchorMessageId: "msg-image-1" });
const snapshot = await consumeChatScrollSnapshot();
expect(snapshot).toEqual({
version: 2,
reason: "image-viewer",
anchorMessageId: "msg-image-1",
anchorOffsetTop: 120,
fallbackScrollTop: 120,
savedAt: expect.any(Number),
});
expect(anchorNode.dataset.chatMessageId).toBe("msg-image-1");
});
it("restores scroll top by keeping the anchor at the saved visual offset", () => {
const { scrollNode } = createScrollFixture({
anchorTop: 250,
scrollTop: 200,
});
expect(
getChatScrollRestoreTop(scrollNode, {
version: 2,
reason: "image-viewer",
anchorMessageId: "msg-image-1",
anchorOffsetTop: 120,
fallbackScrollTop: 120,
savedAt: Date.now(),
}),
).toBe(230);
});
it("falls back to the saved scroll top when the anchor is missing", () => {
const { scrollNode } = createScrollFixture({
anchorTop: 250,
scrollTop: 200,
});
expect(
getChatScrollRestoreTop(scrollNode, {
version: 2,
reason: "image-viewer",
anchorMessageId: "missing-message",
anchorOffsetTop: 120,
fallbackScrollTop: 180,
savedAt: Date.now(),
}),
).toBe(180);
});
});
function createScrollFixture(input: {
anchorTop: number;
scrollTop: number;
}): {
anchorNode: HTMLElement;
scrollNode: HTMLElement;
} {
const scrollNode = document.createElement("div");
Object.defineProperties(scrollNode, {
clientHeight: { configurable: true, value: 400 },
scrollHeight: { configurable: true, value: 1000 },
scrollTop: {
configurable: true,
value: input.scrollTop,
writable: true,
},
});
scrollNode.getBoundingClientRect = () => makeRect(100);
const anchorNode = document.createElement("div");
anchorNode.dataset.chatMessageId = "msg-image-1";
anchorNode.getBoundingClientRect = () => makeRect(input.anchorTop);
scrollNode.appendChild(anchorNode);
document.body.appendChild(scrollNode);
return { anchorNode, scrollNode };
}
function makeRect(top: number): DOMRect {
return {
bottom: top + 40,
height: 40,
left: 0,
right: 100,
toJSON: () => ({}),
top,
width: 100,
x: 0,
y: top,
};
}