Three round-1 fixes whose tests passed against the broken version too. The focus restore keeps watching until the element that closed the box is GONE, rather than spending itself on the first render after the change — the distinction Chrome exposed and jsdom did not, because jsdom commits the save in one render where Chrome takes two. Driven now through the general case instead: the user moved the focus elsewhere while the request was out, renders keep coming while that element is still there, and the restore happens only once it disappears. The choice test's i18n mock returned an option's bare value, which is exactly what dropping the `config.choices.*` lookup renders — a Hungarian admin would have seen `on`/`off` instead of `GPU`/`Szoftver` with the gate green. Marked labels now, still short enough not to move the segmented-bar budget the same file measures. The admin config row's stacking had no test at all, only its sibling fix did — and stacking is the only fix for a text or number key, whose fixed-width input is the wide control. Class contract, as SearchBar.dom.test.tsx already does for the narrow-header rules, since jsdom does no layout. Also: the time-zone cleanup deleted TZ, and Node re-reads it on assignment only, so the process kept the last zone the file set. Harmless until someone appends a test below it.
77 lines
2.8 KiB
TypeScript
77 lines
2.8 KiB
TypeScript
// @vitest-environment jsdom
|
|
//
|
|
// E22 round-2 review: locks the stacking that makes an admin config row usable on a phone. The
|
|
// control column cannot shrink, so beside a wide control it left the label and its hint a sliver of
|
|
// the ~250 px a phone leaves next to the navigation rail. The sibling fix — a long-labelled choice
|
|
// becoming a dropdown — is covered in ConfigPanel.choice.dom.test.tsx, but that one does nothing for
|
|
// a text, number or secret key, whose `w-52` input is the wide control: this is their only fix.
|
|
//
|
|
// jsdom does no layout, so what is asserted is the class contract those rules rest on, the same
|
|
// trade SearchBar.dom.test.tsx already makes for the narrow-header rules.
|
|
import { afterEach, describe, expect, it, vi } from "vitest";
|
|
import { cleanup, 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): ConfigItem => ({
|
|
key,
|
|
type: "str",
|
|
group: "g",
|
|
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: { g: [text("site_name")] }, secrets_manageable: true }),
|
|
},
|
|
};
|
|
});
|
|
vi.mock("../lib/useMe", () => ({ useMe: () => ({ email_configured: true }) }));
|
|
|
|
import ConfigPanel from "./ConfigPanel";
|
|
|
|
afterEach(() => {
|
|
cleanup();
|
|
vi.clearAllMocks();
|
|
});
|
|
|
|
describe("ConfigPanel field row (E22 round 2)", () => {
|
|
it("stacks the label above the control below `sm`, and lets the control fit the screen", async () => {
|
|
render(
|
|
<QueryClientProvider client={new QueryClient()}>
|
|
<ConfigPanel />
|
|
</QueryClientProvider>,
|
|
);
|
|
const label = await screen.findByText("config.fields.site_name.label");
|
|
const row = label.parentElement!.parentElement!; // label → the label/hint column → the row
|
|
// One column on a phone, the two-column row only from `sm` up:
|
|
expect(row.className).toContain("flex-col");
|
|
expect(row.className).toContain("sm:flex-row");
|
|
// The control column may not out-width the screen, and only stops shrinking from `sm` up:
|
|
const control = row.lastElementChild as HTMLElement;
|
|
expect(control.className).toContain("max-w-full");
|
|
expect(control.className).toContain("sm:shrink-0");
|
|
expect(control.className).not.toMatch(/(^|\s)shrink-0(\s|$)/);
|
|
});
|
|
});
|