f88a7c7c4f
The 1,156 accepted acks were burying the 5 rejections — the operator had to scroll the whole table to find them. Two changes: 1. Sidebar '999 ACKs' nav item gets a warning-toned badge showing the total rejected segment count from aggregates.rejected_count (mirrors the Reconciliation unmatched badge pattern). One number, visible from any page. 2. Acks page table now sorts rejected rows to the top, then newest-id-first. All 5 rejections fit on page 1 since they're a tiny fraction of the total — no pagination needed. New files: - src/hooks/useAckStats.ts (lightweight aggregates-only fetch) - src/hooks/useAckStats.test.tsx (2 tests) - src/components/Sidebar.test.tsx (3 tests) Pre-existing baseline: 10 frontend failures (api.test.ts exportBatch837, tail-stream.test.ts acks/ta1_acks targeting, Inbox/InboxHeader copy). Unchanged.
51 lines
1.8 KiB
TypeScript
51 lines
1.8 KiB
TypeScript
// @vitest-environment happy-dom
|
|
(globalThis as { IS_REACT_ACT_ENVIRONMENT?: boolean }).IS_REACT_ACT_ENVIRONMENT = true;
|
|
|
|
import { describe, expect, it, vi, beforeEach } from "vitest";
|
|
import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
|
|
import { renderHook, waitFor } from "@testing-library/react";
|
|
import type { ReactNode } from "react";
|
|
|
|
// Mock the API module so the hook doesn't touch the real /api/acks endpoint.
|
|
// The mock mirrors the wire shape that listAcks returns.
|
|
vi.mock("@/lib/api", () => ({
|
|
api: {
|
|
isConfigured: true,
|
|
listAcks: vi.fn().mockResolvedValue({
|
|
items: [],
|
|
total: 1161,
|
|
returned: 0,
|
|
has_more: false,
|
|
aggregates: { accepted: 1156, rejected: 5, received: 1161 },
|
|
}),
|
|
},
|
|
}));
|
|
|
|
import { useAckStats } from "./useAckStats";
|
|
|
|
function withClient(): (props: { children: ReactNode }) => ReactNode {
|
|
const qc = new QueryClient({ defaultOptions: { queries: { retry: false } } });
|
|
return ({ children }) => (
|
|
<QueryClientProvider client={qc}>{children}</QueryClientProvider>
|
|
);
|
|
}
|
|
|
|
describe("useAckStats", () => {
|
|
beforeEach(() => {
|
|
vi.clearAllMocks();
|
|
});
|
|
|
|
it("returns server-computed aggregates for sidebar badge use", async () => {
|
|
const { result } = renderHook(() => useAckStats(), { wrapper: withClient() });
|
|
await waitFor(() => expect(result.current.isSuccess).toBe(true));
|
|
expect(result.current.data?.aggregates.rejected).toBe(5);
|
|
expect(result.current.data?.aggregates.accepted).toBe(1156);
|
|
});
|
|
|
|
it("calls listAcks with limit=1 so the lightweight payload is smallest", async () => {
|
|
const { api } = await import("@/lib/api");
|
|
renderHook(() => useAckStats(), { wrapper: withClient() });
|
|
await waitFor(() => expect(api.listAcks).toHaveBeenCalled());
|
|
expect(api.listAcks).toHaveBeenCalledWith({ limit: 1 });
|
|
});
|
|
}); |