// One definition of a PageUp/PageDown page for the palette and every popover list (owner // request). The DOM-bound halves are covered where they are wired (CommandPalette, Combobox, // ActionMenu); this pins the arithmetic both lean on. import { describe, expect, it } from "vitest"; import { PAGE_FALLBACK, pageStep, pageTarget } from "./listPaging"; const box = (top: number, height: number) => ({ getBoundingClientRect: () => ({ top, bottom: top + height, height }) }) as unknown as Element; describe("pageStep", () => { it("is the rows wholly in view minus one", () => { // A 100px list with 30px rows: 0–30, 30–60, 60–90 fit; 90–120 is cut off. const rows = [0, 30, 60, 90, 120].map((top) => box(top, 30)); expect(pageStep(box(0, 100), rows)).toBe(2); }); it("never stands still, and falls back without layout", () => { expect(pageStep(box(0, 20), [box(0, 30)])).toBe(1); expect(pageStep(box(0, 0), [box(0, 30)])).toBe(PAGE_FALLBACK); expect(pageStep(null, [])).toBe(PAGE_FALLBACK); }); }); describe("pageTarget", () => { it("moves a page and stops at both ends instead of wrapping", () => { expect(pageTarget(0, 10, 25)).toBe(10); expect(pageTarget(20, 10, 25)).toBe(24); expect(pageTarget(24, 10, 25)).toBe(24); expect(pageTarget(3, -10, 25)).toBe(0); expect(pageTarget(0, 10, 0)).toBeNull(); }); it("passes over a row it may not land on, then looks back — never past the start", () => { const off = new Set([10, 11]); const usable = (i: number) => !off.has(i); expect(pageTarget(0, 10, 25, usable)).toBe(12); // on past the landing row first off.add(24); off.add(23); expect(pageTarget(15, 10, 25, usable)).toBe(22); // at the end: back toward the start expect(pageTarget(22, 10, 25, usable)).toBeNull(); // nothing between the start and the end expect(pageTarget(15, -10, 25, (i) => i > 12)).toBe(13); }); });