Files
siftlode/frontend/src/components/ConfigPanel.sections.dom.test.tsx
T
peter be156fc52b feat(ui): E31 S4b — the admin pages behind one rail row, Configuration as a sectioned list, Statistics as tiles and bars
- NavRailGroup: one "Admin" rail row opening a panel of the admin pages (links, so a
  middle-click still opens a tab); the rail fits 133% zoom without a hidden scroll
- sysconfig.SECTIONS orders the config groups under four headings (every group in exactly
  one, test-held); the page shows them as Settings' side list (Tabs `group` headings);
  the lone registration group reads "Registration" under "Access"
- StatTiles / StatTile / StatBars: the Statistics overview's figures as tiles, the
  per-operation quota use as horizontal bars (a zero draws none)
2026-10-02 00:07:00 +02:00

93 lines
2.9 KiB
TypeScript

// @vitest-environment jsdom
//
// E31 (the owner's pick): Configuration's sixteen group tabs wrapped to three rows at 133% zoom, so they
// are a side list under four headings, as Settings has. The registry owns the order: the groups follow
// its sections, not the order the groups object happens to arrive in.
import { afterEach, describe, expect, it, vi } from "vitest";
import { cleanup, fireEvent, render, screen } from "@testing-library/react";
import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
import type { ConfigItem } from "../lib/api";
vi.mock("react-i18next", async (importOriginal) => {
const actual = await importOriginal<typeof import("react-i18next")>();
return {
...actual,
useTranslation: () => ({ t: (key: string) => key, i18n: { language: "en" } }),
};
});
const text = (key: string, group: string): ConfigItem => ({
key,
type: "str",
group,
secret: false,
min: null,
max: null,
is_set: false,
allow_empty: true,
choices: [],
value: "",
default: "",
});
vi.mock("../lib/api", async (importOriginal) => {
const actual = await importOriginal<typeof import("../lib/api")>();
return {
...actual,
api: {
...actual.api,
adminConfig: () =>
Promise.resolve({
groups: { b: [text("kb", "b")], c: [text("kc", "c")], a: [text("ka", "a")] },
sections: [
{ id: "one", groups: ["a", "b"] },
{ id: "two", groups: ["c"] },
],
secrets_manageable: true,
}),
},
};
});
vi.mock("../lib/useMe", () => ({ useMe: () => ({ email_configured: true }) }));
import ConfigPanel from "./ConfigPanel";
afterEach(() => {
cleanup();
localStorage.clear();
});
const renderPanel = () =>
render(
<QueryClientProvider client={new QueryClient()}>
<ConfigPanel />
</QueryClientProvider>,
);
describe("Configuration sections (E31)", () => {
it("lists the groups in the registry's sections, each heading once above its run", async () => {
renderPanel();
const list = await screen.findByRole("tablist");
expect(list.getAttribute("aria-orientation")).toBe("vertical");
// The list's own lines, top to bottom: headings and tabs.
const lines = Array.from(list.children).map((el) =>
el.getAttribute("role") === "tab" ? `tab:${el.textContent}` : `head:${el.textContent}`,
);
expect(lines).toEqual([
"head:config.sections.one",
"tab:config.groups.a",
"tab:config.groups.b",
"head:config.sections.two",
"tab:config.groups.c",
]);
});
it("opens on the first group and switches to the one picked", async () => {
renderPanel();
expect(await screen.findByText("config.fields.ka.label")).toBeTruthy();
fireEvent.click(screen.getByRole("tab", { name: "config.groups.c" }));
expect(screen.getByText("config.fields.kc.label")).toBeTruthy();
expect(screen.queryByText("config.fields.ka.label")).toBeNull();
});
});