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!", }); }); }); describe("Tauri plugin notification method (Method 4)", () => { it("sends notification when permission is already granted", async () => { const { isPermissionGranted, sendNotification } = await import("@tauri-apps/plugin-notification"); setMockInvokeResult("send_windows_notification", new Error("failed")); setMockInvokeResult("send_windows_toast", new Error("failed")); setMockInvokeResult("send_wsl_notification", new Error("failed")); vi.mocked(isPermissionGranted).mockResolvedValueOnce(true); await notificationManager.notify(NotificationType.SUCCESS); expect(sendNotification).toHaveBeenCalledWith( expect.objectContaining({ title: NOTIFICATION_SOUNDS[NotificationType.SUCCESS].phrase, }) ); }); it("requests permission and sends notification when not yet granted", async () => { const { isPermissionGranted, requestPermission, sendNotification } = await import("@tauri-apps/plugin-notification"); setMockInvokeResult("send_windows_notification", new Error("failed")); setMockInvokeResult("send_windows_toast", new Error("failed")); setMockInvokeResult("send_wsl_notification", new Error("failed")); vi.mocked(isPermissionGranted).mockResolvedValueOnce(false); vi.mocked(requestPermission).mockResolvedValueOnce("granted"); await notificationManager.notify(NotificationType.SUCCESS); expect(requestPermission).toHaveBeenCalledWith(); expect(sendNotification).toHaveBeenCalledWith( expect.objectContaining({ body: "Task completed successfully!" }) ); }); it("falls through to next method when permission is denied", async () => { const { isPermissionGranted, requestPermission } = await import("@tauri-apps/plugin-notification"); setMockInvokeResult("send_windows_notification", new Error("failed")); setMockInvokeResult("send_windows_toast", new Error("failed")); setMockInvokeResult("send_wsl_notification", new Error("failed")); vi.mocked(isPermissionGranted).mockResolvedValueOnce(false); vi.mocked(requestPermission).mockResolvedValueOnce("denied"); setMockInvokeResult("send_notify_send", new Error("failed")); await notificationManager.notify(NotificationType.SUCCESS); expect(sendTerminalNotification).toHaveBeenCalledWith( NotificationType.SUCCESS, "Task completed successfully!" ); }); }); });