import { afterEach, beforeEach, describe, expect, it, vi } from "vitest"; const notificationMock = vi.hoisted(() => ({ instances: [] as Array<{ options: Record }>, show: vi.fn(), isSupported: vi.fn(() => true), openExternal: vi.fn(async () => undefined), })); vi.mock("electron", () => { class MockNotification { options: Record; constructor(options: Record) { this.options = options; notificationMock.instances.push(this); } show(): void { notificationMock.show(); } static isSupported(): boolean { return notificationMock.isSupported(); } } return { Notification: MockNotification, shell: { openExternal: notificationMock.openExternal } }; }); const originalPlatform = process.platform; function setPlatform(platform: NodeJS.Platform): void { Object.defineProperty(process, "platform", { value: platform, configurable: true }); } describe("digital employee desktop notifications", () => { beforeEach(() => { vi.resetModules(); setPlatform(originalPlatform); notificationMock.instances = []; notificationMock.show.mockReset(); notificationMock.isSupported.mockReset(); notificationMock.isSupported.mockReturnValue(true); notificationMock.openExternal.mockReset(); notificationMock.openExternal.mockResolvedValue(undefined); }); afterEach(() => { setPlatform(originalPlatform); }); it("shows unread action notifications through Electron Notification", async () => { const { showDigitalEmployeeDesktopNotification } = await import("./desktopNotificationService"); const result = showDigitalEmployeeDesktopNotification({ notification_id: "approval:1", title: "待审批动作", body: "客户导出需要主管确认", level: "action", kind: "need_approval", status: "unread", }); expect(result).toEqual({ ok: true, shown: true }); expect(notificationMock.instances).toHaveLength(1); expect(notificationMock.instances[0]?.options).toMatchObject({ title: "待审批动作", body: "客户导出需要主管确认", }); expect(notificationMock.show).toHaveBeenCalledTimes(1); }); it.each([ ["read", "action"], ["dismissed", "action"], ["resolved", "action"], ["unread", "info"], ])("skips %s/%s notifications", async (status, level) => { const { showDigitalEmployeeDesktopNotification } = await import("./desktopNotificationService"); const result = showDigitalEmployeeDesktopNotification({ notification_id: `notification:${status}:${level}`, title: "普通消息", body: "无需系统提醒", status, level, }); expect(result).toMatchObject({ ok: true, shown: false }); expect(notificationMock.show).not.toHaveBeenCalled(); }); it("uses safe fallback text and truncates long notification content", async () => { const { showDigitalEmployeeDesktopNotification } = await import("./desktopNotificationService"); const result = showDigitalEmployeeDesktopNotification({ notification_id: "warn:1", title: "x".repeat(160), body: "y".repeat(420), level: "warn", status: "unread", }); expect(result).toMatchObject({ ok: true, shown: true }); const options = notificationMock.instances[0]?.options ?? {}; expect(String(options.title).length).toBeLessThanOrEqual(80); expect(String(options.body).length).toBeLessThanOrEqual(240); notificationMock.instances = []; showDigitalEmployeeDesktopNotification({ notification_id: "warn:2", level: "warn", status: "unread" }); expect(notificationMock.instances[0]?.options).toMatchObject({ title: "数字员工通知", body: "有新的数字员工通知需要处理", }); }); it("returns failure when Electron Notification is unavailable or show fails", async () => { const { showDigitalEmployeeDesktopNotification } = await import("./desktopNotificationService"); notificationMock.isSupported.mockReturnValue(false); expect(showDigitalEmployeeDesktopNotification({ notification_id: "warn:1", level: "warn", status: "unread" })).toMatchObject({ ok: false, shown: false, reason: "notification_unavailable", }); notificationMock.isSupported.mockReturnValue(true); notificationMock.show.mockImplementationOnce(() => { throw new Error("system denied"); }); expect(showDigitalEmployeeDesktopNotification({ notification_id: "warn:2", level: "warn", status: "unread" })).toMatchObject({ ok: false, shown: false, error: "system denied", }); }); it("reports desktop notification status from Electron support", async () => { const { getDigitalEmployeeDesktopNotificationStatus } = await import("./desktopNotificationService"); expect(getDigitalEmployeeDesktopNotificationStatus()).toMatchObject({ supported: true, platform: originalPlatform, can_open_settings: ["darwin", "win32"].includes(originalPlatform), reason: null, }); notificationMock.isSupported.mockReturnValue(false); expect(getDigitalEmployeeDesktopNotificationStatus()).toMatchObject({ supported: false, reason: "notification_unavailable", }); }); it("opens macOS and Windows notification settings", async () => { const { openDigitalEmployeeDesktopNotificationSettings } = await import("./desktopNotificationService"); setPlatform("darwin"); await expect(openDigitalEmployeeDesktopNotificationSettings()).resolves.toMatchObject({ success: true, platform: "darwin" }); expect(notificationMock.openExternal).toHaveBeenLastCalledWith(expect.stringContaining("x-apple.systempreferences")); setPlatform("win32"); await expect(openDigitalEmployeeDesktopNotificationSettings()).resolves.toMatchObject({ success: true, platform: "win32" }); expect(notificationMock.openExternal).toHaveBeenLastCalledWith("ms-settings:notifications"); }); it("returns a safe failure when notification settings cannot be opened", async () => { const { openDigitalEmployeeDesktopNotificationSettings } = await import("./desktopNotificationService"); setPlatform("linux"); await expect(openDigitalEmployeeDesktopNotificationSettings()).resolves.toMatchObject({ success: false, platform: "linux", reason: "notification_settings_unavailable", }); expect(notificationMock.openExternal).not.toHaveBeenCalled(); setPlatform("darwin"); notificationMock.openExternal.mockRejectedValueOnce(new Error("settings denied")); await expect(openDigitalEmployeeDesktopNotificationSettings()).resolves.toMatchObject({ success: false, error: "settings denied", }); }); });