import { describe, it, expect, beforeEach, afterEach } from "vitest"; import { get } from "svelte/store"; import { emitMockEvent } from "../../../vitest.setup"; import { debugConsoleStore, filteredLogs } from "./debugConsole"; describe("debugConsoleStore", () => { beforeEach(() => { debugConsoleStore.clear(); debugConsoleStore.close(); debugConsoleStore.setFilterLevel("all"); debugConsoleStore.setAutoScroll(true); }); afterEach(() => { debugConsoleStore.restoreConsole(); debugConsoleStore.clear(); }); it("initializes with correct default state", () => { const state = get(debugConsoleStore); expect(state.logs).toEqual([]); expect(state.isOpen).toBe(false); expect(state.maxLogs).toBe(1000); expect(state.filterLevel).toBe("all"); expect(state.autoScroll).toBe(true); }); describe("toggle", () => { it("opens when currently closed", () => { debugConsoleStore.toggle(); expect(get(debugConsoleStore).isOpen).toBe(true); }); it("closes when currently open", () => { debugConsoleStore.open(); debugConsoleStore.toggle(); expect(get(debugConsoleStore).isOpen).toBe(false); }); }); describe("open", () => { it("sets isOpen to true", () => { debugConsoleStore.open(); expect(get(debugConsoleStore).isOpen).toBe(true); }); }); describe("close", () => { it("sets isOpen to false", () => { debugConsoleStore.open(); debugConsoleStore.close(); expect(get(debugConsoleStore).isOpen).toBe(false); }); }); describe("clear", () => { it("removes all log entries", async () => { await debugConsoleStore.setupBackendLogsListener(); emitMockEvent("debug:log", { level: "info", message: "test entry" }); expect(get(debugConsoleStore).logs.length).toBe(1); debugConsoleStore.clear(); expect(get(debugConsoleStore).logs).toEqual([]); }); }); describe("setFilterLevel", () => { it("updates filterLevel to the specified level", () => { debugConsoleStore.setFilterLevel("error"); expect(get(debugConsoleStore).filterLevel).toBe("error"); }); it("can reset filterLevel back to all", () => { debugConsoleStore.setFilterLevel("warn"); debugConsoleStore.setFilterLevel("all"); expect(get(debugConsoleStore).filterLevel).toBe("all"); }); }); describe("setAutoScroll", () => { it("disables autoScroll", () => { debugConsoleStore.setAutoScroll(false); expect(get(debugConsoleStore).autoScroll).toBe(false); }); it("re-enables autoScroll", () => { debugConsoleStore.setAutoScroll(false); debugConsoleStore.setAutoScroll(true); expect(get(debugConsoleStore).autoScroll).toBe(true); }); }); describe("setupConsoleCapture", () => { afterEach(() => { debugConsoleStore.restoreConsole(); }); it("captures console.log calls as info-level frontend logs", () => { debugConsoleStore.setupConsoleCapture(); console.log("hello world"); const logs = get(debugConsoleStore).logs; const captured = logs.find((l) => l.message === "hello world"); expect(captured).toBeDefined(); expect(captured?.level).toBe("info"); expect(captured?.source).toBe("frontend"); }); it("captures console.info calls as info-level logs", () => { debugConsoleStore.setupConsoleCapture(); console.info("info message"); const logs = get(debugConsoleStore).logs; const captured = logs.find((l) => l.message === "info message"); expect(captured?.level).toBe("info"); }); it("captures console.warn calls as warn-level logs", () => { debugConsoleStore.setupConsoleCapture(); console.warn("warning message"); const logs = get(debugConsoleStore).logs; const captured = logs.find((l) => l.message === "warning message"); expect(captured?.level).toBe("warn"); }); it("captures console.error calls as error-level logs", () => { debugConsoleStore.setupConsoleCapture(); console.error("error message"); const logs = get(debugConsoleStore).logs; const captured = logs.find((l) => l.message === "error message"); expect(captured?.level).toBe("error"); }); it("captures console.debug calls as debug-level logs", () => { debugConsoleStore.setupConsoleCapture(); console.debug("debug message"); const logs = get(debugConsoleStore).logs; const captured = logs.find((l) => l.message === "debug message"); expect(captured?.level).toBe("debug"); }); it("joins multiple console arguments with spaces", () => { debugConsoleStore.setupConsoleCapture(); console.log("hello", "world", 42); const logs = get(debugConsoleStore).logs; const captured = logs.find((l) => l.message === "hello world 42"); expect(captured).toBeDefined(); }); it("captures unhandled window error events", () => { debugConsoleStore.setupConsoleCapture(); const errorEvent = new ErrorEvent("error", { message: "Test unhandled error", filename: "test.js", lineno: 10, colno: 5, }); window.dispatchEvent(errorEvent); const logs = get(debugConsoleStore).logs; const captured = logs.find( (l) => l.level === "error" && l.message.includes("[Unhandled Error]") ); expect(captured).toBeDefined(); expect(captured?.message).toContain("Test unhandled error"); }); it("captures unhandled promise rejection events", () => { debugConsoleStore.setupConsoleCapture(); const rejectionEvent = Object.assign(new Event("unhandledrejection"), { reason: "test rejection reason", }); window.dispatchEvent(rejectionEvent); const logs = get(debugConsoleStore).logs; const captured = logs.find( (l) => l.level === "error" && l.message.includes("[Unhandled Promise Rejection]") ); expect(captured).toBeDefined(); expect(captured?.message).toContain("test rejection reason"); }); }); describe("restoreConsole", () => { it("stops capturing console output after restore", () => { debugConsoleStore.setupConsoleCapture(); debugConsoleStore.restoreConsole(); const countBefore = get(debugConsoleStore).logs.length; console.log("this should not be captured"); expect(get(debugConsoleStore).logs.length).toBe(countBefore); }); }); describe("setupBackendLogsListener", () => { it("captures backend logs emitted via debug:log event", async () => { await debugConsoleStore.setupBackendLogsListener(); emitMockEvent("debug:log", { level: "info", message: "backend message" }); const logs = get(debugConsoleStore).logs; expect(logs.length).toBe(1); expect(logs[0].level).toBe("info"); expect(logs[0].message).toBe("backend message"); expect(logs[0].source).toBe("backend"); }); it("handles different log levels from backend", async () => { await debugConsoleStore.setupBackendLogsListener(); emitMockEvent("debug:log", { level: "error", message: "backend error" }); const logs = get(debugConsoleStore).logs; expect(logs[0].level).toBe("error"); }); }); describe("circular buffer", () => { it("drops oldest log when exceeding 1000-entry limit", async () => { await debugConsoleStore.setupBackendLogsListener(); for (let i = 0; i < 1001; i++) { emitMockEvent("debug:log", { level: "info", message: `log ${i}` }); } const logs = get(debugConsoleStore).logs; expect(logs.length).toBe(1000); expect(logs[0].message).toBe("log 1"); expect(logs[999].message).toBe("log 1000"); }); }); }); describe("filteredLogs derived store", () => { beforeEach(async () => { debugConsoleStore.clear(); debugConsoleStore.setFilterLevel("all"); await debugConsoleStore.setupBackendLogsListener(); }); afterEach(() => { debugConsoleStore.clear(); }); it("returns all logs when filterLevel is all", () => { emitMockEvent("debug:log", { level: "debug", message: "d" }); emitMockEvent("debug:log", { level: "info", message: "i" }); emitMockEvent("debug:log", { level: "warn", message: "w" }); emitMockEvent("debug:log", { level: "error", message: "e" }); expect(get(filteredLogs).length).toBe(4); }); it("returns only error logs when filterLevel is error", () => { emitMockEvent("debug:log", { level: "debug", message: "d" }); emitMockEvent("debug:log", { level: "info", message: "i" }); emitMockEvent("debug:log", { level: "warn", message: "w" }); emitMockEvent("debug:log", { level: "error", message: "e" }); debugConsoleStore.setFilterLevel("error"); const logs = get(filteredLogs); expect(logs.length).toBe(1); expect(logs[0].level).toBe("error"); }); it("returns warn and error logs when filterLevel is warn", () => { emitMockEvent("debug:log", { level: "debug", message: "d" }); emitMockEvent("debug:log", { level: "info", message: "i" }); emitMockEvent("debug:log", { level: "warn", message: "w" }); emitMockEvent("debug:log", { level: "error", message: "e" }); debugConsoleStore.setFilterLevel("warn"); const logs = get(filteredLogs); expect(logs.length).toBe(2); expect(logs.every((l) => l.level === "warn" || l.level === "error")).toBe(true); }); it("excludes debug logs when filterLevel is info", () => { emitMockEvent("debug:log", { level: "debug", message: "d" }); emitMockEvent("debug:log", { level: "info", message: "i" }); emitMockEvent("debug:log", { level: "warn", message: "w" }); emitMockEvent("debug:log", { level: "error", message: "e" }); debugConsoleStore.setFilterLevel("info"); const logs = get(filteredLogs); expect(logs.length).toBe(3); expect(logs.some((l) => l.level === "debug")).toBe(false); }); it("returns all log levels when filterLevel is debug", () => { emitMockEvent("debug:log", { level: "debug", message: "d" }); emitMockEvent("debug:log", { level: "info", message: "i" }); emitMockEvent("debug:log", { level: "warn", message: "w" }); emitMockEvent("debug:log", { level: "error", message: "e" }); debugConsoleStore.setFilterLevel("debug"); expect(get(filteredLogs).length).toBe(4); }); });