|
| 1 | +import { describe, expect, it } from "vitest"; |
| 2 | +import { CHAT_NEAR_BOTTOM_PX, isChatViewportNearBottom, scrollChatViewportToBottom } from "./chat-scroll"; |
| 3 | + |
| 4 | +// Pure-logic coverage for the stick-to-bottom auto-scroll math (#7793), following the one-test-per-pure-module |
| 5 | +// convention demo-data.test.ts establishes in this directory. jsdom doesn't meaningfully simulate a real |
| 6 | +// viewport's scrollTop/scrollHeight, so these drive the exported functions directly with plain metric objects. |
| 7 | + |
| 8 | +/** A minimal viewport stand-in carrying just the three metrics isChatViewportNearBottom reads. */ |
| 9 | +function viewport(scrollTop: number, scrollHeight: number, clientHeight: number) { |
| 10 | + return { scrollTop, scrollHeight, clientHeight }; |
| 11 | +} |
| 12 | + |
| 13 | +describe("isChatViewportNearBottom (#7793)", () => { |
| 14 | + it("is true when the viewport is scrolled to the very bottom (distance 0)", () => { |
| 15 | + // scrollHeight - scrollTop - clientHeight === 0 <= 80 |
| 16 | + expect(isChatViewportNearBottom(viewport(900, 1000, 100))).toBe(true); |
| 17 | + }); |
| 18 | + |
| 19 | + it("is true exactly at the CHAT_NEAR_BOTTOM_PX boundary (distance === threshold)", () => { |
| 20 | + // distance = 1000 - 820 - 100 = 80, which is <= 80 -> still "pinned". |
| 21 | + expect(isChatViewportNearBottom(viewport(820, 1000, 100))).toBe(true); |
| 22 | + }); |
| 23 | + |
| 24 | + it("is false one pixel past the boundary (distance === threshold + 1)", () => { |
| 25 | + // distance = 1000 - 819 - 100 = 81, which is > 80 -> no longer pinned. |
| 26 | + expect(isChatViewportNearBottom(viewport(819, 1000, 100))).toBe(false); |
| 27 | + }); |
| 28 | + |
| 29 | + it("is false when scrolled well up from the bottom", () => { |
| 30 | + // distance = 1000 - 0 - 100 = 900 > 80 |
| 31 | + expect(isChatViewportNearBottom(viewport(0, 1000, 100))).toBe(false); |
| 32 | + }); |
| 33 | + |
| 34 | + it("treats short content (viewport taller than content) as near the bottom", () => { |
| 35 | + // distance = 100 - 0 - 500 = -400 <= 80 -> a non-scrollable short chat is always "pinned". |
| 36 | + expect(isChatViewportNearBottom(viewport(0, 100, 500))).toBe(true); |
| 37 | + }); |
| 38 | + |
| 39 | + it("honors a custom threshold over the default CHAT_NEAR_BOTTOM_PX", () => { |
| 40 | + // distance = 1000 - 950 - 100 = -50; with a tiny threshold of 0 that's still <= 0 -> true... |
| 41 | + expect(isChatViewportNearBottom(viewport(950, 1000, 100), 0)).toBe(true); |
| 42 | + // ...and a distance of 10 exceeds a threshold of 5 -> false, proving the arg is actually used. |
| 43 | + expect(isChatViewportNearBottom(viewport(890, 1000, 100), 5)).toBe(false); |
| 44 | + }); |
| 45 | + |
| 46 | + it("exports the documented 80px default threshold", () => { |
| 47 | + expect(CHAT_NEAR_BOTTOM_PX).toBe(80); |
| 48 | + }); |
| 49 | +}); |
| 50 | + |
| 51 | +describe("scrollChatViewportToBottom (#7793)", () => { |
| 52 | + it("sets scrollTop to scrollHeight - clientHeight for scrollable content", () => { |
| 53 | + const el = { scrollTop: 0, scrollHeight: 1000, clientHeight: 100 } as HTMLElement; |
| 54 | + scrollChatViewportToBottom(el); |
| 55 | + expect(el.scrollTop).toBe(900); |
| 56 | + }); |
| 57 | + |
| 58 | + it("clamps to 0 when the content is shorter than the viewport (never negative)", () => { |
| 59 | + // scrollHeight - clientHeight = 100 - 500 = -400 -> Math.max(0, ...) pins it at 0. |
| 60 | + const el = { scrollTop: 42, scrollHeight: 100, clientHeight: 500 } as HTMLElement; |
| 61 | + scrollChatViewportToBottom(el); |
| 62 | + expect(el.scrollTop).toBe(0); |
| 63 | + }); |
| 64 | +}); |
0 commit comments