// @vitest-environment jsdom
import { afterEach, beforeAll, describe, expect, it } from "vitest";
import { clear, events, setLevel } from "./log";
import { installClientLog } from "./logHooks";
// History is wrapped ONCE per window (installClientLog is idempotent), so this file installs once
// and every test reads only what it caused.
// The app passes i18n/appCopy.ts; here only "Frissítés" (and "Refresh") is app copy, read as "Refresh".
const appCopy = (text: string | null) =>
["Refresh", "Frissítés"].includes(text?.trim() ?? "") ? "Refresh" : null;
beforeAll(() => {
installClientLog(appCopy);
installClientLog(appCopy); // a hot reload's second call must not wrap History twice
expect(events().map((e) => e.event)).toEqual(["app.start", "nav.view"]);
clear();
});
afterEach(() => {
setLevel("INFO");
window.history.replaceState(null, "", "/");
clear();
});
const views = () => events().filter((e) => e.event === "nav.view");
describe("client log hooks", () => {
it("records a page change once, and not a replaceState that only saves a scroll position", () => {
window.history.pushState({ sfPage: "feed" }, "", "/?show=all");
window.history.replaceState({ sfPage: "feed", sfScroll: 120 }, "");
window.history.replaceState({ sfPage: "feed", sfScroll: 480 }, "");
expect(views()).toHaveLength(1);
expect(views()[0]!.attrs).toMatchObject({
via: "push",
page: "feed",
path: "/",
params: ["show"],
overlay: false,
});
});
it("keeps parameter NAMES at the base level, values at DEBUG, typed search only at TRACE", () => {
window.history.pushState({ sfPage: "feed" }, "", "/?tags=music&q=lofi");
expect(views()[0]!.attrs).not.toHaveProperty("values");
expect(JSON.stringify(views())).not.toContain("lofi");
setLevel("DEBUG");
window.history.pushState({ sfPage: "plex" }, "", "/?tags=music&q=lofi");
expect(views()[1]!.attrs!.values).toEqual({ tags: "music" });
setLevel("TRACE");
window.history.pushState({ sfPage: "feed", _yt: "lofi" }, "", "/?tags=music&q=lofi");
expect(views()[2]!.attrs).toMatchObject({
values: { tags: "music", q: "lofi" },
query: "lofi",
});
});
it("records an overlay opening as a view of its own, and Back as a pop", () => {
window.history.pushState({ sfPage: "feed", _ov: 1 }, "");
// What Back does: the browser restores the previous entry's state (the unwrapped prototype
// method, as the browser itself would not go through the app's wrapper), then fires popstate.
History.prototype.replaceState.call(window.history, { sfPage: "feed" }, "");
window.dispatchEvent(new PopStateEvent("popstate"));
expect(views().map((e) => [e.attrs!.via, e.attrs!.overlay])).toEqual([
["push", true],
["pop", false],
]);
});
it("never records a token that rides in the address", () => {
window.history.replaceState(
{ sfPage: "welcome" },
"",
"/watch/Ab3xYz9Tok/unlock?token=zzz#reset=yyy",
);
setLevel("TRACE");
window.history.pushState(
{ sfPage: "welcome2" },
"",
"/watch/Ab3xYz9Tok/unlock?token=zzz#reset=yyy",
);
const text = JSON.stringify(views());
expect(text).not.toContain("Ab3xYz9Tok");
expect(text).not.toContain("zzz");
expect(text).not.toContain("yyy");
});
it("records uncaught errors and unhandled rejections", () => {
window.dispatchEvent(
new ErrorEvent("error", {
error: new TypeError("x is undefined"),
message: "x is undefined",
filename: "http://localhost/assets/index-B1x9.js",
lineno: 3,
colno: 7,
}),
);
const rejection = new Event("unhandledrejection") as Event & { reason: unknown };
rejection.reason = new Error("nope");
window.dispatchEvent(rejection);
window.dispatchEvent(new Event("error")); // a failed load: not a script fault
expect(events().map((e) => [e.event, e.level])).toEqual([
["app.uncaught", "ERROR"],
["app.unhandled_rejection", "ERROR"],
]);
expect(events()[0]!.attrs).toMatchObject({
error: "TypeError",
where: "/assets/index-B1x9.js:3:7",
});
});
});
describe("control capture (a raised diagnostics level only)", () => {
const clicks = () => events().filter((e) => e.event === "ui.click");
afterEach(() => {
document.body.innerHTML = "";
});
it("records nothing at the base level", () => {
document.body.innerHTML = ``;
document.querySelector("button")!.click();
expect(clicks()).toEqual([]);
});
it("names a control by its test id, icon, app-copy label and state", () => {
setLevel("DEBUG");
window.history.replaceState({ sfPage: "feed" }, "");
document.body.innerHTML = `