// @vitest-environment jsdom // // E22 S5: Settings → Diagnosztika. Raising the level is a consent, so choosing Részletes or Teljes // must change NOTHING until the consent box's own button is pressed; going back to Alap needs no // consent. i18n is a key-passthrough (with the count, so the retention days are visible); the level // endpoint is stubbed, and `me` lives in a real query cache so a saved level re-renders like in the app. import { afterEach, describe, expect, it, vi } from "vitest"; import { cleanup, fireEvent, render, screen, waitFor, within } from "@testing-library/react"; import { QueryClient, QueryClientProvider } from "@tanstack/react-query"; import type { DiagnosticsLevel, DiagnosticsState, Me } from "../lib/api"; import { qk } from "../lib/queryKeys"; vi.mock("react-i18next", async (importOriginal) => { const actual = await importOriginal(); return { ...actual, useTranslation: () => ({ t: (key: string, opts?: { count?: number; h?: number; m?: number; time?: string }) => opts?.count != null ? `${key}:${opts.count}` : opts?.m != null ? `${key}:${opts.h ?? 0}h${opts.m}m` : opts?.time != null ? `${key}:${opts.time}` : key, i18n: { language: "en" }, }), }; }); const setLevel = vi.fn( (level: DiagnosticsLevel): Promise<{ ok: true; diagnostics: DiagnosticsState }> => Promise.resolve({ ok: true, diagnostics: { level, until: level === "INFO" ? null : new Date(Date.now() + 86_400_000).toISOString(), retention_days: 7, session_minutes: 60, }, }), ); vi.mock("../lib/api", async (importOriginal) => { const actual = await importOriginal(); return { ...actual, api: { ...actual.api, setDiagnosticsLevel: (l: DiagnosticsLevel) => setLevel(l) }, }; }); import DiagnosticsSettings from "./DiagnosticsSettings"; afterEach(() => { cleanup(); vi.clearAllMocks(); }); function mount(me: Partial) { const qc = new QueryClient(); qc.setQueryData(qk.me(), { id: 1, is_demo: false, preferences: {}, diagnostics: { level: "INFO", until: null, retention_days: 7, session_minutes: 60 }, ...me, }); render( , ); return qc; } const levelButton = (name: string) => within(screen.getByRole("group", { name: "settings.diagnostics.level" })).getByRole("button", { name, }); describe("DiagnosticsSettings", () => { it("choosing a detailed level asks first and changes nothing until confirmed", async () => { mount({}); fireEvent.click(levelButton("settings.diagnostics.levels.DEBUG")); const consent = screen.getByTestId("diagnostics-consent"); expect(consent.textContent).toContain("settings.diagnostics.consent.what.DEBUG"); expect(consent.textContent).toContain("settings.diagnostics.consent.server:7"); expect(setLevel).not.toHaveBeenCalled(); fireEvent.click( within(consent).getByRole("button", { name: "settings.diagnostics.consent.cancel" }), ); expect(screen.queryByTestId("diagnostics-consent")).toBeNull(); expect(levelButton("settings.diagnostics.levels.INFO").getAttribute("aria-pressed")).toBe( "true", ); expect(setLevel).not.toHaveBeenCalled(); }); it("the consent button raises the level, and the status line offers the way back", async () => { mount({}); fireEvent.click(levelButton("settings.diagnostics.levels.TRACE")); fireEvent.click(screen.getByTestId("diagnostics-consent-confirm")); await waitFor(() => expect(screen.getByTestId("diagnostics-status")).toBeTruthy()); expect(setLevel).toHaveBeenCalledWith("TRACE"); expect(screen.queryByTestId("diagnostics-consent")).toBeNull(); expect(levelButton("settings.diagnostics.levels.TRACE").getAttribute("aria-pressed")).toBe( "true", ); fireEvent.click(screen.getByRole("button", { name: "settings.diagnostics.revert" })); await waitFor(() => expect(screen.queryByTestId("diagnostics-status")).toBeNull()); expect(setLevel).toHaveBeenLastCalledWith("INFO"); }); it("counts the 24 hours from the moment the level was raised, not from when the page opened", async () => { vi.useFakeTimers({ toFake: ["Date"] }); try { vi.setSystemTime(new Date("2026-09-19T02:54:00Z")); mount({}); vi.setSystemTime(new Date("2026-09-19T02:56:30Z")); // the page sat open for 2.5 minutes fireEvent.click(levelButton("settings.diagnostics.levels.DEBUG")); fireEvent.click(screen.getByTestId("diagnostics-consent-confirm")); await waitFor(() => expect(screen.getByTestId("diagnostics-status")).toBeTruthy()); expect(screen.getByTestId("diagnostics-status").textContent).toContain("24 hr 0 min"); } finally { vi.useRealTimers(); } }); it("going back to the base level needs no consent", async () => { const until = new Date(Date.now() + 3_600_000).toISOString(); mount({ diagnostics: { level: "DEBUG", until, retention_days: 7, session_minutes: 60 } }); fireEvent.click(levelButton("settings.diagnostics.levels.INFO")); expect(screen.queryByTestId("diagnostics-consent")).toBeNull(); await waitFor(() => expect(setLevel).toHaveBeenCalledWith("INFO")); }); it("the focus goes back to the level picker when the box it was in goes away", async () => { mount({}); fireEvent.click(levelButton("settings.diagnostics.levels.DEBUG")); const cancel = screen.getByRole("button", { name: "settings.diagnostics.consent.cancel" }); cancel.focus(); fireEvent.click(cancel); expect(document.activeElement).toBe(levelButton("settings.diagnostics.levels.INFO")); fireEvent.click(levelButton("settings.diagnostics.levels.TRACE")); const confirm = screen.getByTestId("diagnostics-consent-confirm"); confirm.focus(); fireEvent.click(confirm); await waitFor(() => expect(screen.getByTestId("diagnostics-status")).toBeTruthy()); expect(document.activeElement).toBe(levelButton("settings.diagnostics.levels.TRACE")); const revert = screen.getByRole("button", { name: "settings.diagnostics.revert" }); revert.focus(); fireEvent.click(revert); await waitFor(() => expect(screen.queryByTestId("diagnostics-status")).toBeNull()); expect(document.activeElement).toBe(levelButton("settings.diagnostics.levels.INFO")); }); it("the watch outlives a render in which the element it is watching is still there", async () => { // The distinguishing property of the restore, and the one the browser found: it keeps looking // until the element is GONE, instead of spending itself on the first render after the change. // Chrome commits a saved level as two renders and jsdom as one, so this drives the general // case directly — the user moved the focus somewhere else while the request was out, and that // somewhere else disappears later, several renders on. vi.useFakeTimers(); try { // Raised, so the status line's countdown is mounted and its 30 s tick keeps re-rendering. const until = new Date(Date.now() + 3_600_000).toISOString(); mount({ diagnostics: { level: "DEBUG", until, retention_days: 7, session_minutes: 60 } }); fireEvent.click(levelButton("settings.diagnostics.levels.TRACE")); const elsewhere = document.createElement("button"); document.body.append(elsewhere); elsewhere.focus(); // Cancel closes the box; the watch is armed on `elsewhere`, which is still in the page. fireEvent.click(screen.getByRole("button", { name: "settings.diagnostics.consent.cancel" })); expect(document.activeElement).toBe(elsewhere); // their place is kept, nothing is stolen // Renders keep coming (the countdown ticks) while it is still there, and must not spend it. await vi.advanceTimersByTimeAsync(60_000); expect(document.activeElement).toBe(elsewhere); elsewhere.remove(); // now the focus really has fallen to expect(document.activeElement).toBe(document.body); await vi.advanceTimersByTimeAsync(30_000); expect(document.activeElement).toBe(levelButton("settings.diagnostics.levels.DEBUG")); } finally { vi.useRealTimers(); } }); it("the ticking status line is not a live region", async () => { const until = new Date(Date.now() + 3_600_000).toISOString(); mount({ diagnostics: { level: "DEBUG", until, retention_days: 7, session_minutes: 60 } }); const status = screen.getByTestId("diagnostics-status"); expect(status.closest('[role="status"], [aria-live]')).toBeNull(); }); it("a `me` fetch that left before the change cannot put the old level back", async () => { const qc = mount({}); let answerStale: (me: unknown) => void = () => {}; const stale = qc .fetchQuery({ queryKey: qk.me(), queryFn: () => new Promise((resolve) => (answerStale = resolve)), }) .catch(() => null); fireEvent.click(levelButton("settings.diagnostics.levels.TRACE")); fireEvent.click(screen.getByTestId("diagnostics-consent-confirm")); await waitFor(() => expect(screen.getByTestId("diagnostics-status")).toBeTruthy()); answerStale({ id: 1, is_demo: false, preferences: {}, diagnostics: { level: "INFO", until: null, retention_days: 7, session_minutes: 60 }, }); await stale; expect(qc.getQueryData(qk.me())?.diagnostics.level).toBe("TRACE"); expect(screen.getByTestId("diagnostics-status")).toBeTruthy(); }); it("a lapsed level shows as the base level", () => { const until = new Date(Date.now() - 60_000).toISOString(); mount({ diagnostics: { level: "TRACE", until, retention_days: 7, session_minutes: 60 } }); expect(levelButton("settings.diagnostics.levels.INFO").getAttribute("aria-pressed")).toBe( "true", ); expect(screen.queryByTestId("diagnostics-status")).toBeNull(); }); it("the shared demo account gets an explanation instead of the picker", () => { mount({ is_demo: true }); expect(screen.queryByRole("group", { name: "settings.diagnostics.level" })).toBeNull(); expect(screen.getByText("settings.diagnostics.demo")).toBeTruthy(); }); });