From bdc5b240e0f78bfa1e6658cce2668daca7c3e48c Mon Sep 17 00:00:00 2001 From: felladaniel36-hash Date: Fri, 24 Jul 2026 00:11:31 +0100 Subject: [PATCH] #309 useSortableData's clearSort is exposed but never invoked by AnchorTable, SettlementTable, or PoolTable FIXED --- README.md | 4 +++- src/components/AnchorTable.test.tsx | 13 +++++++++++++ src/components/AnchorTable.tsx | 5 ++++- src/components/PoolTable.test.tsx | 15 +++++++++++++++ src/components/PoolTable.tsx | 5 ++++- src/components/SettlementTable.test.tsx | 13 +++++++++++++ src/components/SettlementTable.tsx | 5 ++++- src/components/SortableHeader.test.tsx | 23 +++++++++++++++++++++++ src/components/SortableHeader.tsx | 16 +++++++++++++++- 9 files changed, 94 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index cc6c0c7..1aee606 100644 --- a/README.md +++ b/README.md @@ -92,7 +92,9 @@ accessible: it autofocuses the (non-destructive) cancel button, closes on **Escape**, and traps Tab focus between its two buttons. Tables show an animated skeleton while their first page of data is loading, instead of a bare "Loading…" line. Sortable column headers expose their current direction -via `aria-sort` for assistive technology. +via `aria-sort` for assistive technology. While a table is sorted, a **Reset sort** +control appears in the active column header and restores the original row order in +one action. A mock **wallet connect** lives in the header (a stand-in for a real Stellar wallet integration); the connected account is persisted to `localStorage` so diff --git a/src/components/AnchorTable.test.tsx b/src/components/AnchorTable.test.tsx index 3200fa1..baecebc 100644 --- a/src/components/AnchorTable.test.tsx +++ b/src/components/AnchorTable.test.tsx @@ -45,6 +45,19 @@ describe("AnchorTable", () => { expect(nameCells()).toEqual(["Alphaa", "Bravob", "Charliec"]); }); + it("resets an active sort directly to the original row order", () => { + render(); + const header = screen.getByLabelText("Sort by Anchor").closest("th"); + + expect(screen.queryByRole("button", { name: "Reset sort" })).not.toBeInTheDocument(); + fireEvent.click(screen.getByLabelText("Sort by Anchor")); + expect(nameCells()).toEqual(["Alphaa", "Bravob", "Charliec"]); + + fireEvent.click(screen.getByRole("button", { name: "Reset sort" })); + expect(nameCells()).toEqual(["Charliec", "Alphaa", "Bravob"]); + expect(header).toHaveAttribute("aria-sort", "none"); + }); + it("sorts descending by registered date on a second click", () => { render(); fireEvent.click(screen.getByLabelText("Sort by Registered")); diff --git a/src/components/AnchorTable.tsx b/src/components/AnchorTable.tsx index 6aafd42..b2207b2 100644 --- a/src/components/AnchorTable.tsx +++ b/src/components/AnchorTable.tsx @@ -26,7 +26,7 @@ export function AnchorTable({ /** Ids of anchors with a deactivation currently in flight. */ deregisteringIds?: Set; }) { - const { sorted, sort, requestSort } = useSortableData( + const { sorted, sort, requestSort, clearSort } = useSortableData( anchors, getSortValue, ); @@ -53,18 +53,21 @@ export function AnchorTable({ sortKey="name" sort={sort} onSort={requestSort} + onClearSort={clearSort} /> {onDeregister ? : null} diff --git a/src/components/PoolTable.test.tsx b/src/components/PoolTable.test.tsx index 9e43389..7378211 100644 --- a/src/components/PoolTable.test.tsx +++ b/src/components/PoolTable.test.tsx @@ -47,6 +47,21 @@ describe("PoolTable", () => { expect(assetCells()).toEqual(["USDC", "EURC", "XLM"]); }); + it("resets an active sort directly to the original row order", () => { + render(); + const header = screen + .getByLabelText("Sort by Total liquidity") + .closest("th"); + + expect(screen.queryByRole("button", { name: "Reset sort" })).not.toBeInTheDocument(); + fireEvent.click(screen.getByLabelText("Sort by Total liquidity")); + expect(assetCells()).toEqual(["USDC", "EURC", "XLM"]); + + fireEvent.click(screen.getByRole("button", { name: "Reset sort" })); + expect(assetCells()).toEqual(["XLM", "USDC", "EURC"]); + expect(header).toHaveAttribute("aria-sort", "none"); + }); + it("sorts descending by total liquidity on a second click", () => { render(); fireEvent.click(screen.getByLabelText("Sort by Total liquidity")); diff --git a/src/components/PoolTable.tsx b/src/components/PoolTable.tsx index 5aab3bd..ba3d7a1 100644 --- a/src/components/PoolTable.tsx +++ b/src/components/PoolTable.tsx @@ -15,7 +15,7 @@ function getSortValue(pool: Pool, key: SortKey): string | number { /** Renders aggregated liquidity pools as a sortable table. */ export function PoolTable({ pools }: { pools: Pool[] }) { - const { sorted, sort, requestSort } = useSortableData( + const { sorted, sort, requestSort, clearSort } = useSortableData( pools, getSortValue, ); @@ -44,18 +44,21 @@ export function PoolTable({ pools }: { pools: Pool[] }) { sortKey="asset" sort={sort} onSort={requestSort} + onClearSort={clearSort} /> diff --git a/src/components/SettlementTable.test.tsx b/src/components/SettlementTable.test.tsx index 9b55023..cfef701 100644 --- a/src/components/SettlementTable.test.tsx +++ b/src/components/SettlementTable.test.tsx @@ -88,6 +88,19 @@ describe("SettlementTable sorting", () => { expect(amountCells()).toEqual(["100", "200", "300"]); }); + it("resets an active sort directly to the original row order", () => { + render(); + const header = screen.getByLabelText("Sort by Amount").closest("th"); + + expect(screen.queryByRole("button", { name: "Reset sort" })).not.toBeInTheDocument(); + fireEvent.click(screen.getByLabelText("Sort by Amount")); + expect(amountCells()).toEqual(["100", "200", "300"]); + + fireEvent.click(screen.getByRole("button", { name: "Reset sort" })); + expect(amountCells()).toEqual(["300", "100", "200"]); + expect(header).toHaveAttribute("aria-sort", "none"); + }); + it("sorts descending by amount on a second click", () => { render(); fireEvent.click(screen.getByLabelText("Sort by Amount")); diff --git a/src/components/SettlementTable.tsx b/src/components/SettlementTable.tsx index cc90ed8..58da2d4 100644 --- a/src/components/SettlementTable.tsx +++ b/src/components/SettlementTable.tsx @@ -30,7 +30,7 @@ export function SettlementTable({ onCancel?: (id: number) => void; pendingIds?: Set; }) { - const { sorted, sort, requestSort } = useSortableData( + const { sorted, sort, requestSort, clearSort } = useSortableData( settlements, getSortValue, ); @@ -68,6 +68,7 @@ export function SettlementTable({ sortKey="anchor" sort={sort} onSort={requestSort} + onClearSort={clearSort} /> Asset Fee {actionable ? : null} diff --git a/src/components/SortableHeader.test.tsx b/src/components/SortableHeader.test.tsx index de33d7e..8f64ffa 100644 --- a/src/components/SortableHeader.test.tsx +++ b/src/components/SortableHeader.test.tsx @@ -82,4 +82,27 @@ describe("SortableHeader", () => { expect(onSort).toHaveBeenCalledOnce(); expect(onSort).toHaveBeenCalledWith("asset"); }); + + it("shows a reset control only for the active sort and calls its callback", () => { + const onClearSort = vi.fn(); + render( + + + + + + +
, + ); + + fireEvent.click(screen.getByRole("button", { name: "Reset sort" })); + + expect(onClearSort).toHaveBeenCalledOnce(); + }); }); diff --git a/src/components/SortableHeader.tsx b/src/components/SortableHeader.tsx index 532356d..d9ce181 100644 --- a/src/components/SortableHeader.tsx +++ b/src/components/SortableHeader.tsx @@ -2,17 +2,22 @@ import { SortState } from "@/hooks/useSortableData"; -/** A sortable `` with aria-sort and a click-to-sort button, shared across tables. */ +/** + * A sortable `` with aria-sort, a click-to-sort button, and a reset control + * for the active sort. The reset is only shown when this column is sorted. + */ export function SortableHeader({ label, sortKey, sort, onSort, + onClearSort, }: { label: string; sortKey: K; sort: SortState | null; onSort: (key: K) => void; + onClearSort?: () => void; }) { const active = sort?.key === sortKey; const indicator = active ? (sort?.direction === "asc" ? "▲" : "▼") : ""; @@ -33,6 +38,15 @@ export function SortableHeader({ {label} {indicator} + {active && onClearSort ? ( + + ) : null} ); }