117 lines
2.9 KiB
TypeScript
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,
|
|
};
|
|
}
|