Files
siftlode/frontend/src/components/auditColumns.dom.test.tsx
T
peter f8f22fc985 test(i18n): a language test unmounts its reader before switching the language back
useTags reset the language in a finally while its hook was still mounted;
the switch scheduled a render, and in a loaded gate run it landed after
the jsdom environment was torn down ('window is not defined', two
unhandled errors). auditColumns and comfortPin had the same shape.
2026-10-02 23:48:37 +02:00

206 lines
7.4 KiB
TypeScript

// @vitest-environment jsdom
//
// E30: an audit row whose server summary is a fixed English line ("rss_poll: ok — new=5, failed=0",
// "log_level = DEBUG", "Background sync paused") reads in the reader's language, rebuilt from the row's
// data through the REAL catalog. A row whose summary is data (an e-mail, a URL) keeps it.
import { afterEach, describe, expect, it } from "vitest";
import { cleanup, fireEvent, render } from "@testing-library/react";
import i18n, { setLanguage } from "../i18n";
import type { AuditRow } from "../lib/api";
import { auditColumns } from "./auditColumns";
// Unmount BEFORE the language goes back: a mounted reader re-renders on the switch, and under a loaded
// run that render landed after the test environment was gone ("window is not defined", E31 S5).
afterEach(() => {
cleanup();
setLanguage("en");
});
const row = (over: Partial<AuditRow>): AuditRow => ({
id: 1,
created_at: null,
actor: null,
action: "job_run",
target_type: null,
target_id: null,
summary: null,
before: null,
after: null,
...over,
});
function details(r: AuditRow): string {
setLanguage("hu");
const t = i18n.getFixedT("hu");
const col = auditColumns(t, "hu", []).find((c) => c.key === "details")!;
const { container } = render(<>{col.render!(r)}</>);
return [...container.querySelectorAll(".wrap-break-word")].map((e) => e.textContent).join(" | ");
}
describe("an audit row's details", () => {
it("name a job run and its result in the reader's language", () => {
const r = row({
target_type: "job",
target_id: "rss_poll",
summary: "rss_poll: ok — new=5, failed=0",
after: { status: "ok", summary: "new=5, failed=0" },
});
expect(details(r)).toBe("RSS-lekérdezés (új feltöltések) kész | új videó: 5 · sikertelen: 0");
});
it("keep a failed run's error text, which is diagnostic", () => {
const r = row({
target_type: "job",
target_id: "enrich",
after: { status: "error", summary: "HTTP 403: quotaExceeded" },
});
expect(details(r)).toBe(
"Nem sikerült lefuttatni: Adatdúsítás + élő frissítés | HTTP 403: quotaExceeded",
);
});
it("name a config change's field and values", () => {
const set = row({
action: "config_set",
target_type: "config",
target_id: "plex_enabled",
summary: "plex_enabled = True",
before: { value: false },
after: { value: true },
});
expect(details(set)).toMatch(/ \| Ki → Be$/);
expect(details(set)).not.toContain("plex_enabled");
const reset = row({
action: "config_reset",
target_type: "config",
target_id: "download_upload_max_bytes",
summary: "download_upload_max_bytes → default",
before: { value: 5242880 },
});
expect(details(reset)).toMatch(/ \| 5\s242\s880 → alapérték$/);
const secret = row({
action: "config_set",
target_type: "config",
target_id: "youtube_api_key",
summary: "youtube_api_key = (secret updated)",
});
expect(details(secret)).toMatch(/ \| titok frissítve/);
});
it("name a nested result and a skipped run from the row's data (E30 review)", () => {
const nested = row({
target_type: "job",
target_id: "people_enrich",
after: {
status: "ok",
summary: "bios={'enriched': 12}, portraits={'cached': 40}",
result: { bios: { enriched: 12 }, portraits: { cached: 40 } },
},
});
expect(details(nested)).not.toContain("{");
expect(details(nested)).toMatch(/^Személyadatok és portrék \(Plex\) kész \| /);
const skipped = row({
target_type: "job",
target_id: "rss_poll",
after: { status: "skipped", summary: "sync paused" },
});
expect(details(skipped)).toBe("RSS-lekérdezés (új feltöltések) kihagyva | a szinkron szünetel");
});
it("tell a secret's reset from its update, and name a choice", () => {
const reset = row({
action: "config_reset",
target_type: "config",
target_id: "tmdb_api_key",
summary: "tmdb_api_key → default",
});
expect(details(reset)).toMatch(/ \| titok visszaállítva/);
const choice = row({
action: "config_set",
target_type: "config",
target_id: "plex_transcode_hw",
before: { value: "auto" },
after: { value: "on" },
});
expect(details(choice)).toMatch(/ \| Automatikus → GPU$/);
});
it("name an interval, a batch and a count in the reader's language", () => {
expect(
details(
row({
action: "scheduler_interval",
target_type: "job",
target_id: "rss_poll",
summary: "rss_poll → 15 min/run",
after: { interval_minutes: 15 },
}),
),
).toBe("RSS-lekérdezés (új feltöltések) | 15 percenként");
expect(
details(row({ action: "maintenance_tune", after: { revalidate_batch: 50000 } })),
).toMatch(/^Futásonként újraellenőrzött videók \| 50\s000$/);
expect(
details(
row({ action: "audit_clear", summary: "Cleared 12 audit entries", after: { cleared: 12 } }),
),
).not.toContain("Cleared");
});
it("add nothing to a pause, whose action already says it", () => {
expect(details(row({ action: "sync_pause", summary: "Background sync paused" }))).toBe("");
});
it("keep a summary that is data", () => {
expect(details(row({ action: "invite_add", summary: "someone@example.com" }))).toBe(
"someone@example.com",
);
});
});
// E31: a folded run (lib/auditGroups) shows its newest run and opens to the earlier ones; a failed run
// is the line to see in a page of routine ones.
describe("a folded run of one job", () => {
const ok = (id: number, enriched: number) =>
row({
id,
target_type: "job",
target_id: "enrich",
after: { status: "ok", result: { enriched } },
});
it("names how many earlier runs it holds, and opens to each", () => {
setLanguage("hu");
const t = i18n.getFixedT("hu");
const col = auditColumns(t, "hu", []).find((c) => c.key === "details")!;
const runs = [ok(3, 50), ok(2, 40), ok(1, 30)];
const { getByRole, container } = render(<>{col.render!({ ...runs[0]!, runs })}</>);
const toggle = getByRole("button", { name: "2 korábbi futás" });
expect(toggle.getAttribute("aria-expanded")).toBe("false");
expect(container.querySelector("li")).toBeNull();
fireEvent.click(toggle);
expect(toggle.getAttribute("aria-expanded")).toBe("true");
expect([...container.querySelectorAll("li")].map((li) => li.textContent)).toEqual([
"—dúsítva: 40",
"—dúsítva: 30",
]);
});
it("has no toggle for a lone run", () => {
const t = i18n.getFixedT("hu");
const col = auditColumns(t, "hu", []).find((c) => c.key === "details")!;
const { container } = render(<>{col.render!(ok(1, 5))}</>);
expect(container.querySelector("button")).toBeNull();
});
it("paints a failed run's line in the danger colour", () => {
const t = i18n.getFixedT("hu");
const col = auditColumns(t, "hu", []).find((c) => c.key === "details")!;
const failed = row({ target_type: "job", target_id: "enrich", after: { status: "error" } });
const { container } = render(<>{col.render!(failed)}</>);
expect(container.querySelector(".wrap-break-word")!.className).toContain("text-danger");
const { container: fine } = render(<>{col.render!(ok(2, 5))}</>);
expect(fine.querySelector(".wrap-break-word")!.className).not.toContain("text-danger");
});
});