fix(chat): restore image message scroll anchor
This commit is contained in:
@@ -0,0 +1,116 @@
|
||||
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,
|
||||
};
|
||||
}
|
||||
Reference in New Issue
Block a user