Skip to content

Commit aca867a

Browse files
committed
test(miner-ui): cover chat-scroll stick-to-bottom boundary and clamp (#7793)
apps/loopover-miner-ui/src/lib/chat-scroll.ts encodes the 80px near-bottom threshold and the scroll clamp for the chat auto-scroll feature, but had no direct test: its only consumer (components/chat/message-list.tsx) is covered for DOM structure, and jsdom does not meaningfully simulate scrollTop / scrollHeight there. This logic already needed one dedicated bug-fix pass (#7229/#7298), which is exactly the kind of regression a unit test catches. Adds chat-scroll.test.ts following the one-test-per-pure-module convention demo-data.test.ts already establishes in the same directory: - isChatViewportNearBottom: the <= boundary at exactly CHAT_NEAR_BOTTOM_PX, one pixel past it, sitting exactly at the bottom, content shorter than the viewport (negative distance), scrolled far up, and an explicit thresholdPx overriding the default. - scrollChatViewportToBottom: scrolls to the maximum offset, clamps to 0 when the content is shorter than the viewport, and is idempotent once bottomed. Test-only, no production change.
1 parent 17ae3e6 commit aca867a

1 file changed

Lines changed: 63 additions & 0 deletions

File tree

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
import { describe, expect, it } from "vitest";
2+
import { CHAT_NEAR_BOTTOM_PX, isChatViewportNearBottom, scrollChatViewportToBottom } from "./chat-scroll";
3+
4+
/** A minimal stand-in for the scroll viewport: jsdom can't set scrollHeight/clientHeight on a real element. */
5+
function viewport(scrollHeight: number, clientHeight: number, scrollTop = 0): HTMLElement {
6+
return { scrollTop, scrollHeight, clientHeight } as unknown as HTMLElement;
7+
}
8+
9+
describe("isChatViewportNearBottom (#7229)", () => {
10+
it("treats a distance of exactly CHAT_NEAR_BOTTOM_PX as still pinned (the <= boundary)", () => {
11+
// scrollHeight - scrollTop - clientHeight === 80
12+
expect(isChatViewportNearBottom(viewport(1000, 400, 520))).toBe(true);
13+
});
14+
15+
it("treats one pixel past the threshold as scrolled away", () => {
16+
// ...=== 81
17+
expect(isChatViewportNearBottom(viewport(1000, 400, 519))).toBe(false);
18+
});
19+
20+
it("is true when the viewport sits exactly at the bottom", () => {
21+
expect(isChatViewportNearBottom(viewport(1000, 400, 600))).toBe(true);
22+
});
23+
24+
it("is true for content shorter than the viewport (negative distance, nothing to scroll)", () => {
25+
expect(isChatViewportNearBottom(viewport(200, 400, 0))).toBe(true);
26+
});
27+
28+
it("is false when scrolled far up", () => {
29+
expect(isChatViewportNearBottom(viewport(5000, 400, 0))).toBe(false);
30+
});
31+
32+
it("honors an explicit thresholdPx instead of the default", () => {
33+
// distance === 10: outside a 0px threshold, inside the 80px default.
34+
expect(isChatViewportNearBottom(viewport(1000, 400, 590), 0)).toBe(false);
35+
expect(isChatViewportNearBottom(viewport(1000, 400, 590))).toBe(true);
36+
// distance === 0 still counts at a 0px threshold (<=, not <).
37+
expect(isChatViewportNearBottom(viewport(1000, 400, 600), 0)).toBe(true);
38+
});
39+
40+
it("exposes the documented 80px threshold", () => {
41+
expect(CHAT_NEAR_BOTTOM_PX).toBe(80);
42+
});
43+
});
44+
45+
describe("scrollChatViewportToBottom (#7229)", () => {
46+
it("scrolls to the maximum scrollable offset", () => {
47+
const el = viewport(1000, 400, 0);
48+
scrollChatViewportToBottom(el);
49+
expect(el.scrollTop).toBe(600);
50+
});
51+
52+
it("clamps to 0 when the content is shorter than the viewport", () => {
53+
const el = viewport(200, 400, 25);
54+
scrollChatViewportToBottom(el);
55+
expect(el.scrollTop).toBe(0);
56+
});
57+
58+
it("leaves an already-bottomed viewport at the same offset (idempotent)", () => {
59+
const el = viewport(1000, 400, 600);
60+
scrollChatViewportToBottom(el);
61+
expect(el.scrollTop).toBe(600);
62+
});
63+
});

0 commit comments

Comments
 (0)