import { act, fireEvent, screen, waitFor } from "@testing-library/react"; import { createAppRouter } from "@/routes"; import { validateGroupId, validateProtectionSearch, validateResolutionSearch } from "@/features/configuration/search"; import { DATABASE, renderPage, stubApi } from "./testFixtures"; /** * The configuration URL is the applied state: which page, which tab, which * group. A view of a page is a link, and the back button walks what the reader * chose rather than what the page corrected on their behalf. */ afterEach(() => { vi.unstubAllGlobals(); }); test("the six resource routes are gone, with no alias left behind", () => { const paths = new Set(Object.keys(createAppRouter().routesByPath)); for (const gone of ["/groups", "/blocklists", "/rules", "/local-dns", "/upstreams", "/settings"]) { expect(paths.has(gone)).toBe(false); } for (const kept of ["/configuration/protection", "/configuration/resolution", "/configuration/system"]) { expect(paths.has(kept)).toBe(true); } // No landing route either: `/configuration` is not a page. expect(paths.has("/configuration")).toBe(false); }); test("an unknown tab falls back to the page default rather than an empty panel", () => { expect(validateProtectionSearch({ tab: "upstreams" }).tab).toBe("groups"); expect(validateProtectionSearch({}).tab).toBe("groups"); expect(validateResolutionSearch({ tab: "sources" }).tab).toBe("upstreams"); expect(validateResolutionSearch({ tab: "zones" }).tab).toBe("zones"); }); test("a group id is a positive integer or nothing", () => { expect(validateGroupId(3)).toBe(3); expect(validateGroupId(0)).toBeUndefined(); expect(validateGroupId(-1)).toBeUndefined(); expect(validateGroupId(1.5)).toBeUndefined(); expect(validateGroupId("2")).toBeUndefined(); }); test("an unknown group falls back to the first one and rewrites the URL in place", async () => { stubApi(DATABASE); const router = await renderPage("/configuration/protection?group=99", "Protection"); await waitFor(() => expect(router.state.location.search.group).toBe(1)); await screen.findByRole("heading", { name: "default", level: 2 }); // `replace`: the corrected address did not become a place to go back to. expect(router.history.length).toBe(1); }); test("selecting a group pushes a history entry the back button walks", async () => { stubApi(DATABASE); const router = await renderPage("/configuration/protection", "Protection"); await waitFor(() => expect(router.state.location.search.group).toBe(1)); fireEvent.click(screen.getByRole("link", { name: "kids" })); await waitFor(() => expect(router.state.location.search.group).toBe(2)); await screen.findByRole("heading", { name: "kids", level: 2 }); act(() => router.history.back()); await waitFor(() => expect(router.state.location.search.group).toBe(1)); }); test("the selected group survives a tab change, and the change is a history entry", async () => { stubApi(DATABASE); const router = await renderPage("/configuration/protection?group=2", "Protection"); await screen.findByRole("heading", { name: "kids", level: 2 }); fireEvent.click(screen.getByRole("tab", { name: "Sources" })); await screen.findByRole("button", { name: "Update now" }); expect(router.state.location.search).toEqual({ tab: "sources", group: 2 }); act(() => router.history.back()); await waitFor(() => expect(router.state.location.search.tab).toBe("groups")); expect(router.state.location.search.group).toBe(2); await screen.findByRole("heading", { name: "kids", level: 2 }); }); test("the Resolution tab is URL state too", async () => { stubApi(DATABASE); const router = await renderPage("/configuration/resolution", "Resolution"); fireEvent.click(screen.getByRole("tab", { name: "Forward zones" })); await waitFor(() => expect(router.state.location.search.tab).toBe("zones")); await screen.findByRole("button", { name: "Add zone" }); }); test("the loaders are started, not awaited: the page renders while a collection is in flight", async () => { // Groups never answers. If the loader were awaited the navigation would hang // and neither the heading nor the tabs would ever paint. stubApi(DATABASE, { responses: { "GET /api/groups": new Promise(() => {}) } }); await renderPage("/configuration/protection", "Protection"); expect(screen.getByRole("tab", { name: "Sources" })).toBeTruthy(); expect(await screen.findByText("Loading…")).toBeTruthy(); });