import { fireEvent, render, screen, within } from "@testing-library/react"; import { QueryClientProvider } from "@tanstack/react-query"; import { RouterProvider, createMemoryHistory } from "@tanstack/react-router"; import { AuthProvider } from "@/auth/store"; import { createQueryClient } from "@/lib/queryClient"; import { createAppRouter } from "@/routes"; const RESPONSES: Record = { "/api/rules": { rules: [ { id: 1, group_id: 1, group: "Default", pattern: "ads.example.com", kind: "exact", action: "block", created_at: 1700000000, }, { id: 2, group_id: 2, group: "Kids", pattern: "*.cdn.example.com", kind: "wildcard", action: "allow", created_at: 1700000100, }, ], }, "/api/version": { version: "0.0.0-test", git_commit: "0000000", zig_version: "0.16.0", uptime_seconds: 1 }, }; // The API orders groups by name, so the id-1 default is not always first. let groups: { id: number; name: string; safe_search: boolean }[]; beforeEach(() => { groups = [ { id: 1, name: "Default", safe_search: false }, { id: 2, name: "Kids", safe_search: true }, ]; vi.stubGlobal( "fetch", vi.fn(async (input: RequestInfo | URL, init?: RequestInit) => { const url = String(input); if (url === "/api/rules" && init?.method === "POST") { return new Response(JSON.stringify({ error: "rate limited" }), { status: 429, headers: { "content-type": "application/json", "Retry-After": "5" }, }); } const payload = url === "/api/groups" ? { groups } : RESPONSES[url]; if (payload === undefined) return new Response(JSON.stringify({ error: "not stubbed" }), { status: 404 }); return new Response(JSON.stringify(payload), { status: 200, headers: { "content-type": "application/json" }, }); }), ); }); afterEach(() => { vi.unstubAllGlobals(); }); function renderRulesRoute() { const queryClient = createQueryClient(); const router = createAppRouter(createMemoryHistory({ initialEntries: ["/rules"] }), queryClient); render( , ); } test("renders the rule table and the create form with contract enums", async () => { renderRulesRoute(); await screen.findByRole("heading", { name: "Rules" }); const table = within(screen.getByRole("table")); expect(table.getByText("ads.example.com")).toBeTruthy(); expect(table.getByText("*.cdn.example.com")).toBeTruthy(); expect(table.getByText("block")).toBeTruthy(); expect(table.getByText("allow")).toBeTruthy(); expect(table.getByText("Kids")).toBeTruthy(); expect(screen.getAllByRole("button", { name: "Delete" })).toHaveLength(2); const kindSelect = screen.getByLabelText("Kind") as HTMLSelectElement; expect(Array.from(kindSelect.options).map((o) => o.value)).toEqual(["exact", "wildcard"]); const actionSelect = screen.getByLabelText("Action") as HTMLSelectElement; expect(Array.from(actionSelect.options).map((o) => o.value)).toEqual(["allow", "block"]); const groupSelect = screen.getByLabelText("Group") as HTMLSelectElement; expect(Array.from(groupSelect.options).map((o) => o.textContent)).toEqual(["Default", "Kids"]); }); test("rule create shows a countdown when rate limited with Retry-After", async () => { renderRulesRoute(); await screen.findByRole("heading", { name: "Rules" }); fireEvent.change(screen.getByLabelText("Pattern"), { target: { value: "ads.example.net" } }); fireEvent.click(screen.getByRole("button", { name: "Create rule" })); const alert = await screen.findByRole("alert"); expect(alert.textContent).toBe("Rate limited. Try again in 5s."); }); test("the group select preselects the id-1 default, not the alphabetically first group", async () => { groups = [ { id: 5, name: "Attic", safe_search: false }, { id: 1, name: "Default", safe_search: false }, ]; renderRulesRoute(); await screen.findByRole("heading", { name: "Rules" }); const groupSelect = screen.getByLabelText("Group") as HTMLSelectElement; expect(Array.from(groupSelect.options).map((o) => o.textContent)).toEqual(["Attic", "Default"]); expect(groupSelect.value).toBe("1"); }); test("the group select falls back to the first group when the default is absent", async () => { groups = [ { id: 5, name: "Attic", safe_search: false }, { id: 7, name: "Basement", safe_search: false }, ]; renderRulesRoute(); await screen.findByRole("heading", { name: "Rules" }); expect((screen.getByLabelText("Group") as HTMLSelectElement).value).toBe("5"); });