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.
206 lines
7.4 KiB
TypeScript
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");
|
|
});
|
|
});
|