1import { describe, expect, it } from "vite-plus/test";
2import { renderToStaticMarkup } from "react-dom/server";
3
4import { resolveThreadLineageWindow, ThreadLineageRowList } from "./ThreadRelationshipsControl";
5
6const rows = Array.from({ length: 20 }, (_, index) => `row-${index}`);
7
8function renderRowList(visibleCount: number) {
9 const { visibleRows, hiddenCount } = resolveThreadLineageWindow(rows, visibleCount);
10 return renderToStaticMarkup(
11 <ThreadLineageRowList hiddenCount={hiddenCount} onShowMore={() => {}}>
12 {visibleRows.map((row) => (
13 <li key={row}>{row}</li>
14 ))}
15 </ThreadLineageRowList>,
16 );
17}
18
19describe("thread lineage row list", () => {
20 it("shows six rows before the first expansion", () => {
21 const { visibleRows, hiddenCount } = resolveThreadLineageWindow(rows, 6);
22
23 expect(visibleRows).toEqual(rows.slice(0, 6));
24 expect(hiddenCount).toBe(14);
25 });
26
27 it("offers one page at a time", () => {
28 expect(renderRowList(6)).toContain("Show 12 more");
29 expect(renderRowList(6 + 12)).toContain("Show 2 more");
30 });
31
32 it("omits the expansion affordance when everything fits", () => {
33 const markup = renderRowList(rows.length);
34
35 expect(markup).not.toContain("more");
36 expect(resolveThreadLineageWindow(rows.slice(0, 6), 6).hiddenCount).toBe(0);
37 });
38
39 it("keeps the rows in a bounded, labelled scroll region and the button outside it", () => {
40 const markup = renderRowList(6);
41 const list = /<ul([^>]*)>/.exec(markup)?.[1] ?? "";
42
43 expect(list).toContain('aria-label="Related threads"');
44 expect(list).toContain("max-h-[13.5rem]");
45 expect(list).toContain("overflow-y-auto");
46 expect(list).toContain("overscroll-contain");
47 expect(markup.indexOf("</ul>")).toBeLessThan(markup.indexOf("<button"));
48 });
49});