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)
This commit is contained in:
@@ -0,0 +1,92 @@
|
||||
// @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();
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user