murashit_codex-panel/tests/view-scroll.test.ts
2026-05-13 00:05:56 +09:00

74 lines
2.7 KiB
TypeScript

// @vitest-environment jsdom
import { describe, expect, it } from "vitest";
import { bottomScrollTop, captureScrollAnchor, isNearScrollBottom, restoreScrollAnchor } from "../src/view/scroll";
describe("message scroll helpers", () => {
it("detects whether the transcript is pinned near the bottom", () => {
expect(isNearScrollBottom({ scrollHeight: 1000, scrollTop: 620, clientHeight: 320 })).toBe(true);
expect(isNearScrollBottom({ scrollHeight: 1000, scrollTop: 500, clientHeight: 320 })).toBe(false);
});
it("uses the full scroll height for bottom pinning", () => {
expect(bottomScrollTop({ scrollHeight: 1200 })).toBe(1200);
});
it("captures and restores the first visible message block", () => {
const container = messageContainer({ scrollTop: 150, scrollHeight: 1000, clientHeight: 300 });
const first = messageBlock("first", 0, 120);
const second = messageBlock("second", 120, 160);
container.append(first, second);
const anchor = captureScrollAnchor(container);
expect(anchor).toEqual({ key: "second", offset: -30, fallbackTop: 150 });
setLayout(second, 360, 160);
restoreScrollAnchor(container, anchor);
expect(container.scrollTop).toBe(390);
});
it("falls back to the previous scroll top when the anchor block disappears", () => {
const container = messageContainer({ scrollTop: 150, scrollHeight: 1000, clientHeight: 300 });
container.append(messageBlock("first", 0, 120), messageBlock("second", 120, 160));
const anchor = captureScrollAnchor(container);
container.replaceChildren(messageBlock("other", 0, 120));
restoreScrollAnchor(container, anchor);
expect(container.scrollTop).toBe(150);
});
});
function messageContainer(metrics: { scrollTop: number; scrollHeight: number; clientHeight: number }): HTMLElement {
const container = document.createElement("div");
let scrollTop = metrics.scrollTop;
Object.defineProperties(container, {
scrollTop: {
get: () => scrollTop,
set: (value: number) => {
scrollTop = value;
},
configurable: true,
},
scrollHeight: { value: metrics.scrollHeight, configurable: true },
clientHeight: { value: metrics.clientHeight, configurable: true },
});
return container;
}
function messageBlock(key: string, offsetTop: number, offsetHeight: number): HTMLElement {
const element = document.createElement("div");
element.setAttribute("data-codex-panel-block-key", key);
setLayout(element, offsetTop, offsetHeight);
return element;
}
function setLayout(element: HTMLElement, offsetTop: number, offsetHeight: number): void {
Object.defineProperties(element, {
offsetTop: { value: offsetTop, configurable: true },
offsetHeight: { value: offsetHeight, configurable: true },
});
}