Files
nxdns/admin/src/features/configuration/routing.test.tsx
T
mokhtar 7e0df5fd94
Gates / frontend (push) Successful in 1m22s
Gates / test (push) Successful in 1m54s
Gates / test-aarch64 (push) Successful in 7m57s
Gates / package (push) Successful in 5m29s
Gates / container (push) Successful in 10s
CI / gates (push) Successful in 32m51s
milestone 32: task-shaped configuration, file mode as a rendering, config status api
2026-08-22 22:42:50 +02:00

99 lines
4.3 KiB
TypeScript

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<Response>(() => {}) } });
await renderPage("/configuration/protection", "Protection");
expect(screen.getByRole("tab", { name: "Sources" })).toBeTruthy();
expect(await screen.findByText("Loading…")).toBeTruthy();
});