import { describe, it, expect, beforeEach, beforeAll, vi } from "vitest"; import { getRecentErrorLogs, getLogs, clearLogs, initLogCapture, } from "./logService"; beforeAll(() => { // Patch console.* so addEntry runs. Idempotent. initLogCapture(); }); describe("getRecentErrorLogs", () => { beforeEach(() => { // Reset the in-memory buffer. clearLogs also clears sessionStorage // which jsdom provides in vitest. clearLogs(); }); it("returns an empty string when the log buffer is empty", () => { expect(getRecentErrorLogs(5)).toBe(""); }); it("returns an empty string when n <= 0", () => { console.error("boom"); expect(getRecentErrorLogs(0)).toBe(""); expect(getRecentErrorLogs(-3)).toBe(""); }); it("filters out info-level entries", () => { // Freeze time so the ISO prefix is predictable vi.setSystemTime(new Date("2026-04-17T15:00:00.000Z")); console.log("just chatter"); console.warn("low fuel"); console.error("engine out"); const out = getRecentErrorLogs(10); expect(out).not.toContain("chatter"); expect(out).toContain("WARN: low fuel"); expect(out).toContain("ERROR: engine out"); vi.useRealTimers(); }); it("keeps only the last N non-info entries in order", () => { for (let i = 0; i < 5; i++) console.warn(`w${i}`); const out = getRecentErrorLogs(2); const lines = out.split("\n"); expect(lines).toHaveLength(2); expect(lines[0]).toContain("w3"); expect(lines[1]).toContain("w4"); }); it("formats each line as `[ISO] LEVEL: message`", () => { vi.setSystemTime(new Date("2026-04-17T15:23:45.000Z")); console.error("export failed"); const out = getRecentErrorLogs(1); expect(out).toMatch(/^\[2026-04-17T15:23:45\.000Z\] ERROR: export failed$/); vi.useRealTimers(); }); }); // Regression: the log console (Settings → Systems → Journaux) stayed empty // because getLogs returned the same mutated array reference on every call, so // useSyncExternalStore (which compares snapshots by identity) never re-rendered // when a new log arrived. getLogs must return a STABLE reference between // mutations (no per-call re-slice, or React loops) and a NEW one after each. describe("getLogs — live-update snapshot", () => { beforeEach(() => { clearLogs(); }); it("returns a stable reference between mutations", () => { expect(getLogs()).toBe(getLogs()); console.warn("x"); const snap = getLogs(); expect(snap).toBe(getLogs()); }); it("returns a NEW reference after a log so the store re-renders", () => { const before = getLogs(); console.warn("new entry"); const after = getLogs(); expect(after).not.toBe(before); expect(after).toHaveLength(before.length + 1); expect(after[after.length - 1].message).toBe("new entry"); }); it("returns a NEW reference after clearLogs", () => { console.warn("y"); const before = getLogs(); clearLogs(); const after = getLogs(); expect(after).not.toBe(before); expect(after).toHaveLength(0); }); });