186 lines
6.6 KiB
TypeScript
186 lines
6.6 KiB
TypeScript
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
|
|
|
|
const notificationMock = vi.hoisted(() => ({
|
|
instances: [] as Array<{ options: Record<string, unknown> }>,
|
|
show: vi.fn(),
|
|
isSupported: vi.fn(() => true),
|
|
openExternal: vi.fn(async () => undefined),
|
|
}));
|
|
|
|
vi.mock("electron", () => {
|
|
class MockNotification {
|
|
options: Record<string, unknown>;
|
|
|
|
constructor(options: Record<string, unknown>) {
|
|
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",
|
|
});
|
|
});
|
|
});
|