// @vitest-environment jsdom // // E22 S4: what the user was SHOWN — a toast, the blocking error dialog, the crash screen — and what // failed behind the scenes (a query, a mutation) all land in the client log. Each producer is its // own module, so each is asserted here through the real thing, not a mock of it. import { afterEach, describe, expect, it, vi } from "vitest"; import { cleanup, render } from "@testing-library/react"; import { clear, events } from "../lib/log"; import { notify } from "../lib/notifications"; import { reportError } from "../lib/errorDialog"; import { createQueryClient } from "../lib/queryClient"; import ErrorBoundary from "./ErrorBoundary"; import { HttpError } from "../lib/api/core"; afterEach(() => { cleanup(); clear(); vi.restoreAllMocks(); }); const logged = (code: string) => events().filter((e) => e.event === code); describe("client log wiring", () => { it("records every notification at its level", () => { notify({ level: "success", title: "Saved", message: "Playlist renamed" }); notify({ level: "error", message: "Could not reach someone@mail.example" }); expect(logged("ui.notify").map((e) => [e.level, e.msg, e.attrs])).toEqual([ ["INFO", "Playlist renamed", { level: "success", title: "Saved" }], ["ERROR", "Could not reach [email]", { level: "error" }], ]); }); it("records the blocking error dialog", () => { reportError("The server refused", "Couldn't save"); expect(logged("ui.error_dialog")).toMatchObject([ { level: "ERROR", msg: "The server refused", attrs: { title: "Couldn't save" } }, ]); }); it("records a failed query by its resource only — the rest of the key is filter state", async () => { const client = createQueryClient(); await client .fetchQuery({ queryKey: ["videos", { tags: ["private-tag"] }], queryFn: () => Promise.reject(new HttpError(503, "down")), }) .catch(() => {}); expect(logged("query.failed")).toMatchObject([ { level: "WARNING", attrs: { key: "videos", status: 503, message: "down" } }, ]); expect(JSON.stringify(events())).not.toContain("private-tag"); }); it("records a failed mutation", async () => { const client = createQueryClient(); const mutation = client.getMutationCache().build(client, { mutationKey: ["playlists", 42], mutationFn: () => Promise.reject(new HttpError(409, "conflict")), meta: { errorShownInline: true }, }); await mutation.execute(undefined).catch(() => {}); expect(logged("mutation.failed")).toMatchObject([ { level: "WARNING", attrs: { key: "playlists", status: 409 } }, ]); }); it("records a render crash with its component stack", () => { vi.spyOn(console, "error").mockImplementation(() => {}); // React reports the crash itself too const Boom = () => { throw new Error("render exploded"); }; render( , ); const [crash] = logged("ui.crash"); expect(crash).toMatchObject({ level: "CRITICAL", attrs: { message: "render exploded" } }); expect(String(crash!.attrs!.component_stack)).toContain("Boom"); }); });