/** * Notification Rules Tests * * Tests the connection status change handler, which fires a connection * notification sound exactly once per reconnect cycle. * * What this module does: * - Tracks the previous connection status in module-level state * - Fires a notification only when transitioning from a non-connected * state (disconnected/connecting) to "connected" * - Ignores the initial connection (null → connected) to avoid noisy * notifications on app start * - Provides no-op handlers for tool execution and user messages * (reserved for future notification rules) * - cleanupNotificationRules() resets tracking state on teardown */ import { describe, it, expect, vi, beforeEach } from "vitest"; const { mockNotifyConnection } = vi.hoisted(() => ({ mockNotifyConnection: vi.fn(), })); vi.mock("./notificationManager", () => ({ notificationManager: { notifyConnection: mockNotifyConnection, }, })); import { handleConnectionStatusChange, handleToolExecution, handleNewUserMessage, initializeNotificationRules, cleanupNotificationRules, } from "./rules"; // --- describe("handleConnectionStatusChange", () => { beforeEach(() => { mockNotifyConnection.mockReset(); cleanupNotificationRules(); // Reset module-level previousConnectionStatus to null }); describe("initial connection (null → status)", () => { it("does not notify on first connection (null → connected)", () => { // previousConnectionStatus is null (falsy), so condition is not met handleConnectionStatusChange("connected"); expect(mockNotifyConnection).not.toHaveBeenCalled(); }); it("does not notify when disconnecting from initial state (null → disconnected)", () => { handleConnectionStatusChange("disconnected"); expect(mockNotifyConnection).not.toHaveBeenCalled(); }); it("does not notify when entering connecting from initial state (null → connecting)", () => { handleConnectionStatusChange("connecting"); expect(mockNotifyConnection).not.toHaveBeenCalled(); }); }); describe("reconnection (disconnected → connected)", () => { it("notifies when reconnecting after a disconnection", () => { handleConnectionStatusChange("disconnected"); handleConnectionStatusChange("connected"); expect(mockNotifyConnection).toHaveBeenCalledWith(); }); it("notifies exactly once per reconnect", () => { handleConnectionStatusChange("disconnected"); handleConnectionStatusChange("connected"); expect(mockNotifyConnection).toHaveBeenCalledTimes(1); }); }); describe("reconnection (connecting → connected)", () => { it("notifies when transitioning from connecting to connected", () => { handleConnectionStatusChange("connecting"); handleConnectionStatusChange("connected"); expect(mockNotifyConnection).toHaveBeenCalledWith(); }); }); describe("already connected (connected → connected)", () => { it("does not notify when already connected", () => { handleConnectionStatusChange("disconnected"); handleConnectionStatusChange("connected"); // First connection — notifies mockNotifyConnection.mockReset(); handleConnectionStatusChange("connected"); // Second — same status, no notify expect(mockNotifyConnection).not.toHaveBeenCalled(); }); }); describe("disconnecting (connected → disconnected)", () => { it("does not notify when disconnecting", () => { handleConnectionStatusChange("disconnected"); handleConnectionStatusChange("connected"); mockNotifyConnection.mockReset(); handleConnectionStatusChange("disconnected"); expect(mockNotifyConnection).not.toHaveBeenCalled(); }); }); describe("multiple reconnect cycles", () => { it("notifies once per reconnect cycle", () => { // First cycle handleConnectionStatusChange("disconnected"); handleConnectionStatusChange("connected"); expect(mockNotifyConnection).toHaveBeenCalledTimes(1); mockNotifyConnection.mockReset(); // Second cycle handleConnectionStatusChange("disconnected"); handleConnectionStatusChange("connected"); expect(mockNotifyConnection).toHaveBeenCalledTimes(1); }); }); }); describe("cleanupNotificationRules", () => { it("resets state so the next connection is treated as the first", () => { // Establish a known previous status handleConnectionStatusChange("disconnected"); // Now cleanup cleanupNotificationRules(); // After cleanup, previousConnectionStatus is null again // So the next "connected" should NOT notify (treated as initial connection) handleConnectionStatusChange("connected"); expect(mockNotifyConnection).not.toHaveBeenCalled(); }); }); describe("no-op handlers", () => { it("handleToolExecution does not throw", () => { expect(() => handleToolExecution("Bash")).not.toThrow(); }); it("handleNewUserMessage does not throw", () => { expect(() => handleNewUserMessage()).not.toThrow(); }); it("initializeNotificationRules does not throw", () => { expect(() => initializeNotificationRules()).not.toThrow(); }); });