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
99 lines
4.3 KiB
TypeScript
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();
|
|
});
|