import { fireEvent, render, screen, waitFor, 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 }[]; let deleted: string[]; let posted: { pattern: string; kind: string }[]; function deleteCalls(): string[] { return deleted; } beforeEach(() => { groups = [ { id: 1, name: "Default", safe_search: false }, { id: 2, name: "Kids", safe_search: true }, ]; deleted = []; posted = []; vi.stubGlobal( "fetch", vi.fn(async (input: RequestInfo | URL, init?: RequestInit) => { const url = String(input); if (init?.method === "DELETE") { deleted.push(url); return new Response(null, { status: 204 }); } if (url === "/api/rules" && init?.method === "POST") { posted.push(JSON.parse(String(init.body)) as { pattern: string; kind: string }); 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( , ); } /** * A RAC Select names its trigger with the current value and then the label, so * the label alone is a suffix match. Opening it is the only way to read the * options: there is no `