From ea53bf0d4d4a5127b6adaffc8c37eac0418fe38b Mon Sep 17 00:00:00 2001 From: Hikari Date: Tue, 3 Mar 2026 15:48:55 -0800 Subject: [PATCH] test: add coverage for debugConsole, notificationManager, and achievements --- .../notifications/notificationManager.test.ts | 188 ++++++++++++ src/lib/stores/achievements.test.ts | 185 ++++++++++++ src/lib/stores/debugConsole.test.ts | 282 ++++++++++++++++++ 3 files changed, 655 insertions(+) create mode 100644 src/lib/notifications/notificationManager.test.ts create mode 100644 src/lib/stores/achievements.test.ts create mode 100644 src/lib/stores/debugConsole.test.ts diff --git a/src/lib/notifications/notificationManager.test.ts b/src/lib/notifications/notificationManager.test.ts new file mode 100644 index 0000000..187516e --- /dev/null +++ b/src/lib/notifications/notificationManager.test.ts @@ -0,0 +1,188 @@ +import { describe, it, expect, vi, beforeEach, afterEach } from "vitest"; +import { invoke } from "@tauri-apps/api/core"; +import { isPermissionGranted } from "@tauri-apps/plugin-notification"; +import { setMockInvokeResult } from "../../../vitest.setup"; +import { notificationManager } from "./notificationManager"; +import { sendTerminalNotification } from "./terminalNotifier"; +import { NotificationType, NOTIFICATION_SOUNDS } from "./types"; + +vi.mock("./soundPlayer", () => ({ + soundPlayer: { play: vi.fn().mockResolvedValue(undefined) }, +})); + +vi.mock("./terminalNotifier", () => ({ + sendTerminalNotification: vi.fn(), +})); + +describe("NotificationManager", () => { + let consoleSpy: ReturnType; + let consoleWarnSpy: ReturnType; + let consoleErrorSpy: ReturnType; + + beforeEach(() => { + consoleSpy = vi.spyOn(console, "log").mockImplementation(() => {}); + consoleWarnSpy = vi.spyOn(console, "warn").mockImplementation(() => {}); + consoleErrorSpy = vi.spyOn(console, "error").mockImplementation(() => {}); + }); + + afterEach(() => { + consoleSpy.mockRestore(); + consoleWarnSpy.mockRestore(); + consoleErrorSpy.mockRestore(); + }); + + describe("notify()", () => { + it("calls the first notification method by default", async () => { + await notificationManager.notify(NotificationType.SUCCESS); + expect(invoke).toHaveBeenCalledWith( + "send_windows_notification", + expect.objectContaining({ + title: NOTIFICATION_SOUNDS[NotificationType.SUCCESS].phrase, + }) + ); + }); + + it("passes a custom message to the notification", async () => { + await notificationManager.notify(NotificationType.ERROR, "Custom error message"); + expect(invoke).toHaveBeenCalledWith("send_windows_notification", { + title: NOTIFICATION_SOUNDS[NotificationType.ERROR].phrase, + body: "Custom error message", + }); + }); + + it("falls back to the next method when the first fails", async () => { + setMockInvokeResult("send_windows_notification", new Error("Method 1 failed")); + await notificationManager.notify(NotificationType.SUCCESS); + expect(invoke).toHaveBeenCalledWith("send_windows_toast", expect.any(Object)); + }); + + it("falls back to terminal notification when all methods fail", async () => { + setMockInvokeResult("send_windows_notification", new Error("failed")); + setMockInvokeResult("send_windows_toast", new Error("failed")); + setMockInvokeResult("send_wsl_notification", new Error("failed")); + setMockInvokeResult("send_notify_send", new Error("failed")); + vi.mocked(isPermissionGranted).mockRejectedValueOnce(new Error("Permission check failed")); + + await notificationManager.notify(NotificationType.SUCCESS); + + expect(sendTerminalNotification).toHaveBeenCalledWith( + NotificationType.SUCCESS, + "Task completed successfully!" + ); + }); + + it("uses the default SUCCESS message when none is provided", async () => { + await notificationManager.notify(NotificationType.SUCCESS); + expect(invoke).toHaveBeenCalledWith("send_windows_notification", { + title: expect.any(String), + body: "Task completed successfully!", + }); + }); + + it("uses the default ERROR message", async () => { + await notificationManager.notify(NotificationType.ERROR); + expect(invoke).toHaveBeenCalledWith("send_windows_notification", { + title: expect.any(String), + body: "Something went wrong...", + }); + }); + + it("uses the default PERMISSION message", async () => { + await notificationManager.notify(NotificationType.PERMISSION); + expect(invoke).toHaveBeenCalledWith("send_windows_notification", { + title: expect.any(String), + body: "Permission needed to continue", + }); + }); + + it("uses the default CONNECTION message", async () => { + await notificationManager.notify(NotificationType.CONNECTION); + expect(invoke).toHaveBeenCalledWith("send_windows_notification", { + title: expect.any(String), + body: "Successfully connected to Claude Code", + }); + }); + + it("uses the default TASK_START message", async () => { + await notificationManager.notify(NotificationType.TASK_START); + expect(invoke).toHaveBeenCalledWith("send_windows_notification", { + title: expect.any(String), + body: "Starting task...", + }); + }); + + it("uses the default COST_ALERT message", async () => { + await notificationManager.notify(NotificationType.COST_ALERT); + expect(invoke).toHaveBeenCalledWith("send_windows_notification", { + title: expect.any(String), + body: "You've exceeded your cost threshold!", + }); + }); + + it("uses the fallback default message for unhandled types", async () => { + await notificationManager.notify(NotificationType.ACHIEVEMENT); + expect(invoke).toHaveBeenCalledWith("send_windows_notification", { + title: expect.any(String), + body: "Notification", + }); + }); + }); + + describe("helper methods", () => { + it("notifySuccess calls notify with SUCCESS type and default message", async () => { + await notificationManager.notifySuccess(); + expect(invoke).toHaveBeenCalledWith("send_windows_notification", { + title: NOTIFICATION_SOUNDS[NotificationType.SUCCESS].phrase, + body: "Task completed successfully!", + }); + }); + + it("notifySuccess passes a custom message", async () => { + await notificationManager.notifySuccess("All done!"); + expect(invoke).toHaveBeenCalledWith("send_windows_notification", { + title: expect.any(String), + body: "All done!", + }); + }); + + it("notifyError calls notify with ERROR type", async () => { + await notificationManager.notifyError(); + expect(invoke).toHaveBeenCalledWith("send_windows_notification", { + title: NOTIFICATION_SOUNDS[NotificationType.ERROR].phrase, + body: "Something went wrong...", + }); + }); + + it("notifyPermission calls notify with PERMISSION type", async () => { + await notificationManager.notifyPermission(); + expect(invoke).toHaveBeenCalledWith("send_windows_notification", { + title: NOTIFICATION_SOUNDS[NotificationType.PERMISSION].phrase, + body: "Permission needed to continue", + }); + }); + + it("notifyConnection calls notify with CONNECTION type", async () => { + await notificationManager.notifyConnection(); + expect(invoke).toHaveBeenCalledWith("send_windows_notification", { + title: NOTIFICATION_SOUNDS[NotificationType.CONNECTION].phrase, + body: "Successfully connected to Claude Code", + }); + }); + + it("notifyTaskStart calls notify with TASK_START type", async () => { + await notificationManager.notifyTaskStart(); + expect(invoke).toHaveBeenCalledWith("send_windows_notification", { + title: NOTIFICATION_SOUNDS[NotificationType.TASK_START].phrase, + body: "Starting task...", + }); + }); + + it("notifyCostAlert calls notify with COST_ALERT type", async () => { + await notificationManager.notifyCostAlert(); + expect(invoke).toHaveBeenCalledWith("send_windows_notification", { + title: NOTIFICATION_SOUNDS[NotificationType.COST_ALERT].phrase, + body: "You've exceeded your cost threshold!", + }); + }); + }); +}); diff --git a/src/lib/stores/achievements.test.ts b/src/lib/stores/achievements.test.ts new file mode 100644 index 0000000..68ebf58 --- /dev/null +++ b/src/lib/stores/achievements.test.ts @@ -0,0 +1,185 @@ +import { describe, it, expect, vi } from "vitest"; +import { get } from "svelte/store"; +import { setMockInvokeResult, emitMockEvent } from "../../../vitest.setup"; +import { + achievementsStore, + unlockedAchievements, + lockedAchievements, + achievementsByRarity, + achievementProgress, + initAchievementsListener, +} from "./achievements"; +import type { AchievementUnlockedEvent } from "$lib/types/achievements"; +import { playAchievementSound } from "$lib/sounds/achievement"; + +vi.mock("$lib/sounds/achievement", () => ({ + playAchievementSound: vi.fn(), +})); + +// Helper to build a minimal unlock event +function makeEvent(id: AchievementUnlockedEvent["achievement"]["id"]): AchievementUnlockedEvent { + return { + achievement: { + id, + name: "Test", + description: "Test achievement", + icon: "🏆", + unlocked_at: null, + }, + }; +} + +describe("achievementsStore initial state", () => { + it("all achievements start as locked", () => { + const state = get(achievementsStore); + expect(state.achievements["FirstSteps"].unlocked).toBe(false); + expect(state.achievements["GrowingStrong"].unlocked).toBe(false); + }); + + it("totalUnlocked starts at 0", () => { + expect(get(achievementsStore).totalUnlocked).toBe(0); + }); + + it("lastUnlocked starts as null", () => { + expect(get(achievementsStore).lastUnlocked).toBeNull(); + }); +}); + +describe("derived stores initial state", () => { + it("unlockedAchievements is initially empty", () => { + expect(get(unlockedAchievements)).toEqual([]); + }); + + it("lockedAchievements contains all achievements initially", () => { + const locked = get(lockedAchievements); + const total = Object.keys(get(achievementsStore).achievements).length; + expect(locked.length).toBe(total); + }); + + it("achievementsByRarity groups achievements into rarity buckets", () => { + const byRarity = get(achievementsByRarity); + expect(byRarity.common).toBeInstanceOf(Array); + expect(byRarity.rare).toBeInstanceOf(Array); + expect(byRarity.epic).toBeInstanceOf(Array); + expect(byRarity.legendary).toBeInstanceOf(Array); + expect(byRarity.common.length).toBeGreaterThan(0); + }); + + it("achievementProgress shows zero unlocked initially", () => { + const progress = get(achievementProgress); + expect(progress.unlocked).toBe(0); + expect(progress.total).toBeGreaterThan(0); + expect(progress.percentage).toBe(0); + }); +}); + +describe("achievementsStore.unlockAchievement", () => { + it("marks the achievement as unlocked and updates totalUnlocked", () => { + achievementsStore.unlockAchievement(makeEvent("GrowingStrong")); + const state = get(achievementsStore); + expect(state.achievements["GrowingStrong"].unlocked).toBe(true); + expect(state.totalUnlocked).toBe(1); + expect(state.lastUnlocked?.id).toBe("GrowingStrong"); + }); + + it("sets unlockedAt from the event's unlocked_at timestamp", () => { + achievementsStore.unlockAchievement({ + achievement: { + id: "BlossomingCoder", + name: "Blossoming Coder", + description: "100k tokens", + icon: "🌸", + unlocked_at: "2026-01-15T12:00:00.000Z", + }, + }); + const state = get(achievementsStore); + expect(state.achievements["BlossomingCoder"].unlockedAt).toBeInstanceOf(Date); + }); + + it("does nothing when the achievement is already unlocked", () => { + achievementsStore.unlockAchievement(makeEvent("TokenMaster")); + const firstTotal = get(achievementsStore).totalUnlocked; + achievementsStore.unlockAchievement(makeEvent("TokenMaster")); + expect(get(achievementsStore).totalUnlocked).toBe(firstTotal); + }); + + it("calls playAchievementSound when playSound is true (default)", () => { + achievementsStore.unlockAchievement(makeEvent("TokenBillionaire")); + expect(playAchievementSound).toHaveBeenCalled(); + }); + + it("does not call playAchievementSound when playSound is false", () => { + achievementsStore.unlockAchievement(makeEvent("TokenTreasure"), false); + expect(playAchievementSound).not.toHaveBeenCalled(); + }); + + it("logs an error when playAchievementSound throws", () => { + vi.mocked(playAchievementSound).mockImplementationOnce(() => { + throw new Error("Sound failed"); + }); + const consoleSpy = vi.spyOn(console, "error").mockImplementation(() => {}); + achievementsStore.unlockAchievement(makeEvent("HelloWorld")); + expect(consoleSpy).toHaveBeenCalledWith("Failed to play achievement sound:", expect.any(Error)); + consoleSpy.mockRestore(); + }); +}); + +describe("derived stores after unlocks", () => { + it("unlockedAchievements includes previously unlocked achievements", () => { + const unlocked = get(unlockedAchievements); + expect(unlocked.some((a) => a.id === "GrowingStrong")).toBe(true); + }); + + it("lockedAchievements excludes previously unlocked achievements", () => { + const locked = get(lockedAchievements); + expect(locked.some((a) => a.id === "GrowingStrong")).toBe(false); + }); + + it("achievementProgress reflects the current unlocked count", () => { + const progress = get(achievementProgress); + expect(progress.unlocked).toBeGreaterThan(0); + expect(progress.percentage).toBeGreaterThan(0); + }); +}); + +describe("achievementsStore.updateProgress", () => { + it("updates the progress value for an achievement", () => { + achievementsStore.updateProgress("FirstMessage", 50); + expect(get(achievementsStore).achievements["FirstMessage"].progress).toBe(50); + }); +}); + +describe("achievementsStore.reset", () => { + it("resets totalUnlocked to 0 and lastUnlocked to null", () => { + achievementsStore.reset(); + const state = get(achievementsStore); + expect(state.totalUnlocked).toBe(0); + expect(state.lastUnlocked).toBeNull(); + }); +}); + +describe("initAchievementsListener", () => { + it("unlocks an achievement when the achievement:unlocked event fires", async () => { + await initAchievementsListener(); + emitMockEvent("achievement:unlocked", makeEvent("FirstSteps")); + expect(get(achievementsStore).achievements["FirstSteps"].unlocked).toBe(true); + }); + + it("loads saved achievements from the backend without playing sounds", async () => { + setMockInvokeResult("load_saved_achievements", [makeEvent("ConversationStarter")]); + await initAchievementsListener(); + expect(get(achievementsStore).achievements["ConversationStarter"].unlocked).toBe(true); + expect(playAchievementSound).not.toHaveBeenCalled(); + }); + + it("logs an error when loading saved achievements fails", async () => { + setMockInvokeResult("load_saved_achievements", new Error("Storage unavailable")); + const consoleSpy = vi.spyOn(console, "error").mockImplementation(() => {}); + await initAchievementsListener(); + expect(consoleSpy).toHaveBeenCalledWith( + "Failed to load saved achievements:", + expect.any(Error) + ); + consoleSpy.mockRestore(); + }); +}); diff --git a/src/lib/stores/debugConsole.test.ts b/src/lib/stores/debugConsole.test.ts new file mode 100644 index 0000000..dfd80d5 --- /dev/null +++ b/src/lib/stores/debugConsole.test.ts @@ -0,0 +1,282 @@ +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); + }); +});