Deep-review round 1 of E22. - After a 429 the stream sent one POST per new event while a full batch waited; it now stays quiet for 15 s. Past the queue cap the oldest events go, as the comment always said, a refused batch included. - A batch refused after the level was lowered came back into the queue of a later consent in the same tab; each consent is its own session. - A `me` fetch that left before a level or preference change could put the old value back for up to a minute. `updateMeCache` writes the cache and cancels that fetch; usePref goes through it too. - The consent box's buttons and "revert now" took the focus with them to <body>; it returns to the level picker. Watched until the button is gone — in Chrome the saved level arrives in two renders, and a one-shot check spent itself on the first (jsdom never showed it). - The countdown line is no longer a live region re-read every minute. - "Saját napló" rows were keyed by index on a newest-first list, so every new event remounted every row and dropped the focus off a row button.
184 lines
7.3 KiB
TypeScript
184 lines
7.3 KiB
TypeScript
// @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<typeof import("react-i18next")>();
|
|
return {
|
|
...actual,
|
|
useTranslation: () => ({
|
|
t: (key: string, opts?: { count?: number }) =>
|
|
opts?.count != null ? `${key}:${opts.count}` : 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,
|
|
},
|
|
}),
|
|
);
|
|
vi.mock("../lib/api", async (importOriginal) => {
|
|
const actual = await importOriginal<typeof import("../lib/api")>();
|
|
return {
|
|
...actual,
|
|
api: { ...actual.api, setDiagnosticsLevel: (l: DiagnosticsLevel) => setLevel(l) },
|
|
};
|
|
});
|
|
|
|
import DiagnosticsSettings from "./DiagnosticsSettings";
|
|
|
|
afterEach(() => {
|
|
cleanup();
|
|
vi.clearAllMocks();
|
|
});
|
|
|
|
function mount(me: Partial<Me>) {
|
|
const qc = new QueryClient();
|
|
qc.setQueryData(qk.me(), {
|
|
id: 1,
|
|
is_demo: false,
|
|
preferences: {},
|
|
diagnostics: { level: "INFO", until: null, retention_days: 7 },
|
|
...me,
|
|
});
|
|
render(
|
|
<QueryClientProvider client={qc}>
|
|
<DiagnosticsSettings />
|
|
</QueryClientProvider>,
|
|
);
|
|
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("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 } });
|
|
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 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 } });
|
|
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 },
|
|
});
|
|
await stale;
|
|
expect(qc.getQueryData<Me>(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 } });
|
|
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();
|
|
});
|
|
});
|