Picking in the year dialog the one year the chips hid took More… away with the dialog, and the focus fell to <body>: it now goes to the picked year's chip. A count badge is compact in the UI language everywhere (a chip read "17200" beside its cell's "17.2K"). While the Plex facets load or fail, the sections they fill say so instead of vanishing as if the library had none.
111 lines
4.1 KiB
TypeScript
111 lines
4.1 KiB
TypeScript
// @vitest-environment jsdom
|
|
//
|
|
// "Saját napló" shows this tab's own log live, newest first, filters it by level group and
|
|
// text, and copies exactly what is filtered (oldest first, one JSON event per line).
|
|
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
|
|
import { act, cleanup, fireEvent, render, screen, within } from "@testing-library/react";
|
|
|
|
import { clear, log, setLevel } from "../../lib/log";
|
|
|
|
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 copied: string[] = [];
|
|
vi.mock("../../lib/clipboard", () => ({
|
|
copyText: (text: string) => {
|
|
copied.push(text);
|
|
return Promise.resolve(true);
|
|
},
|
|
}));
|
|
|
|
import MyLogViewer from "./MyLogViewer";
|
|
|
|
beforeEach(() => {
|
|
setLevel("DEBUG");
|
|
log.info("nav.view", "push feed", { page: "feed" });
|
|
log.warn("http.request", "GET /api/feed 503", { status: 503 });
|
|
log.debug("ui.click", "click feed-refresh", { control: "feed-refresh" });
|
|
});
|
|
afterEach(() => {
|
|
cleanup();
|
|
clear();
|
|
setLevel("INFO");
|
|
copied.length = 0;
|
|
});
|
|
|
|
const rows = () =>
|
|
within(screen.getByTestId("my-log-lines"))
|
|
.queryAllByRole("button")
|
|
.map((b) => b.textContent);
|
|
|
|
describe("MyLogViewer", () => {
|
|
it("lists the tab's events newest first and follows new ones", async () => {
|
|
render(<MyLogViewer />);
|
|
expect(rows().map((r) => r!.includes("ui.click"))).toEqual([true, false, false]);
|
|
await act(async () => {
|
|
log.error("app.uncaught", "TypeError: x is undefined");
|
|
await new Promise((r) => requestAnimationFrame(() => r(null)));
|
|
});
|
|
expect(rows()[0]).toContain("app.uncaught");
|
|
});
|
|
|
|
it("a row keeps its place and the focus as new events arrive above it", async () => {
|
|
render(<MyLogViewer />);
|
|
const row = within(screen.getByTestId("my-log-lines")).getAllByRole("button")[0]!;
|
|
row.focus();
|
|
fireEvent.click(row); // at Részletes this click is itself a new event in real use
|
|
await act(async () => {
|
|
log.debug("ui.click", "a newer event");
|
|
await new Promise((r) => requestAnimationFrame(() => r(null)));
|
|
});
|
|
expect(row.isConnected).toBe(true);
|
|
expect(document.activeElement).toBe(row);
|
|
expect(row.getAttribute("aria-expanded")).toBe("true");
|
|
});
|
|
|
|
it("filters by level group and by text, and copies only what is shown", async () => {
|
|
render(<MyLogViewer />);
|
|
fireEvent.click(screen.getByRole("button", { name: /settings\.myLog\.filters\.warning/ }));
|
|
expect(rows()).toHaveLength(1);
|
|
expect(rows()[0]).toContain("GET /api/feed 503");
|
|
|
|
fireEvent.click(screen.getByRole("button", { name: /settings\.myLog\.filters\.all/ }));
|
|
fireEvent.change(screen.getByRole("searchbox"), { target: { value: "FEED-REFRESH" } });
|
|
expect(rows()).toHaveLength(1);
|
|
|
|
const copy = async () => {
|
|
await act(async () => {
|
|
fireEvent.click(screen.getByRole("button", { name: "settings.myLog.copy" }));
|
|
});
|
|
return copied[copied.length - 1]!.split("\n").map((l) => JSON.parse(l).event);
|
|
};
|
|
expect(await copy()).toEqual(["ui.click"]); // only what the search left
|
|
|
|
fireEvent.change(screen.getByRole("searchbox"), { target: { value: "" } });
|
|
expect(await copy()).toEqual(["nav.view", "http.request", "ui.click"]); // oldest first
|
|
});
|
|
|
|
it("opens a row to its details and clears the log", async () => {
|
|
render(<MyLogViewer />);
|
|
const first = within(screen.getByTestId("my-log-lines")).getAllByRole("button")[0]!;
|
|
fireEvent.click(first);
|
|
expect(first.getAttribute("aria-expanded")).toBe("true");
|
|
expect(screen.getByTestId("my-log-lines").textContent).toContain('"control": "feed-refresh"');
|
|
|
|
await act(async () => {
|
|
fireEvent.click(screen.getByRole("button", { name: "settings.myLog.clear" }));
|
|
await new Promise((r) => requestAnimationFrame(() => r(null)));
|
|
});
|
|
expect(rows()).toEqual([]);
|
|
expect(screen.getByText("settings.myLog.emptyAll")).toBeTruthy();
|
|
});
|
|
});
|