milestone 32: task-shaped configuration, file mode as a rendering, config status api
This commit is contained in:
@@ -0,0 +1,77 @@
|
||||
import { render, screen, waitFor } from "@testing-library/react";
|
||||
import { QueryClientProvider } from "@tanstack/react-query";
|
||||
import { createQueryClient } from "@/lib/queryClient";
|
||||
import DefinitionList from "@/ui/DefinitionList";
|
||||
import ConfigLockIndicator from "./ConfigLockIndicator";
|
||||
import { CONFIG_PATH, DATABASE, MANAGED_FILE, stubApi } from "./testFixtures";
|
||||
import type { ConfigStatus } from "@/lib/types";
|
||||
|
||||
/**
|
||||
* The two shared pieces the redesign adds: the file-mode rendering of a scalar
|
||||
* and the compact lock a configuration control outside a configuration page
|
||||
* carries.
|
||||
*/
|
||||
|
||||
afterEach(() => {
|
||||
vi.unstubAllGlobals();
|
||||
});
|
||||
|
||||
function renderIndicator(status: ConfigStatus | "failed") {
|
||||
if (status === "failed") {
|
||||
stubApi(DATABASE, {
|
||||
responses: {
|
||||
"GET /api/config/status": new Response(JSON.stringify({ error: "unavailable" }), { status: 503 }),
|
||||
},
|
||||
});
|
||||
} else {
|
||||
stubApi(status);
|
||||
}
|
||||
render(
|
||||
<QueryClientProvider client={createQueryClient()}>
|
||||
<ConfigLockIndicator />
|
||||
</QueryClientProvider>,
|
||||
);
|
||||
}
|
||||
|
||||
test("a definition names the value in words and the file key beside it", () => {
|
||||
render(
|
||||
<DefinitionList
|
||||
items={[
|
||||
{ label: "Port", zonKey: "dns.port", value: "53" },
|
||||
{ label: "Authentication", value: "required" },
|
||||
]}
|
||||
/>,
|
||||
);
|
||||
|
||||
const port = screen.getByText("Port");
|
||||
expect(port.tagName).toBe("DT");
|
||||
expect(port.textContent).toBe("Portdns.port");
|
||||
// A derived value has no key: inventing one would send the reader looking
|
||||
// for a line that is not in the file.
|
||||
expect(screen.getByText("Authentication").textContent).toBe("Authentication");
|
||||
expect(screen.getByText("required").tagName).toBe("DD");
|
||||
});
|
||||
|
||||
test("the lock is silent when the database owns the configuration", async () => {
|
||||
renderIndicator(DATABASE);
|
||||
|
||||
await waitFor(() => expect(screen.queryByText("Locked")).toBeNull());
|
||||
});
|
||||
|
||||
test("under file authority the lock names the file, in words a reader hears", async () => {
|
||||
renderIndicator(MANAGED_FILE);
|
||||
|
||||
const lock = await screen.findByText("Locked");
|
||||
await waitFor(() =>
|
||||
expect(lock.getAttribute("aria-label")).toBe(
|
||||
`Locked. Managed by ${CONFIG_PATH}; edit the file and restart nxdns.`,
|
||||
),
|
||||
);
|
||||
});
|
||||
|
||||
test("an unanswered status still locks, and says that is why", async () => {
|
||||
renderIndicator("failed");
|
||||
|
||||
const lock = await screen.findByText("Locked");
|
||||
await waitFor(() => expect(lock.getAttribute("aria-label")).toContain("Configuration status unavailable"));
|
||||
});
|
||||
Reference in New Issue
Block a user