From 4be035d37e860cb4e5474c5b0d72b03cc4317568 Mon Sep 17 00:00:00 2001 From: DIYgod Date: Tue, 12 May 2026 16:43:44 +0800 Subject: [PATCH] fix(desktop): keep tray instance when refreshing menu --- apps/desktop/layer/main/src/lib/tray.test.ts | 131 +++++++++++++++++++ apps/desktop/layer/main/src/lib/tray.ts | 16 ++- 2 files changed, 143 insertions(+), 4 deletions(-) create mode 100644 apps/desktop/layer/main/src/lib/tray.test.ts diff --git a/apps/desktop/layer/main/src/lib/tray.test.ts b/apps/desktop/layer/main/src/lib/tray.test.ts new file mode 100644 index 000000000..8ff447150 --- /dev/null +++ b/apps/desktop/layer/main/src/lib/tray.test.ts @@ -0,0 +1,131 @@ +import { beforeEach, describe, expect, it, vi } from "vitest" + +const mocks = vi.hoisted(() => { + const env = { + isMacOS: false, + isMAS: false, + isWindows: false, + } + + return { + app: { + getName: vi.fn(() => "Folo"), + quit: vi.fn(), + }, + buildFromTemplate: vi.fn((template) => ({ template })), + env, + getBadgeCount: vi.fn(() => 0), + getTrayIconPath: vi.fn(() => "/icon.png"), + logger: { + info: vi.fn(), + }, + nativeImage: { + createFromPath: vi.fn(() => { + const image = { + resize: vi.fn(() => image), + setTemplateImage: vi.fn(), + } + return image + }), + }, + store: { + get: vi.fn(() => true), + set: vi.fn(), + }, + trayInstances: [] as Array<{ + destroy: ReturnType + on: ReturnType + setContextMenu: ReturnType + setToolTip: ReturnType + }>, + Tray: class { + constructor() { + const tray = { + destroy: vi.fn(), + on: vi.fn(), + setContextMenu: vi.fn(), + setToolTip: vi.fn(), + } + mocks.trayInstances.push(tray) + return tray + } + }, + } +}) + +vi.mock("electron", () => ({ + app: { + getName: mocks.app.getName, + getBadgeCount: mocks.getBadgeCount, + quit: mocks.app.quit, + setBadgeCount: vi.fn(), + }, + Menu: { + buildFromTemplate: mocks.buildFromTemplate, + }, + nativeImage: mocks.nativeImage, + Tray: mocks.Tray, +})) + +vi.mock("~/env", () => mocks.env) + +vi.mock("~/helper", () => ({ + getTrayIconPath: mocks.getTrayIconPath, +})) + +vi.mock("~/logger", () => ({ + logger: mocks.logger, + revealLogFile: vi.fn(), +})) + +vi.mock("~/manager/window", () => ({ + WindowManager: { + getMainWindowOrCreate: vi.fn(() => ({ + isMinimized: vi.fn(() => false), + show: vi.fn(), + webContents: { + reload: vi.fn(), + toggleDevTools: vi.fn(), + }, + })), + }, +})) + +vi.mock("~/updater", () => ({ + checkForAppUpdates: vi.fn(), +})) + +vi.mock("./i18n", () => ({ + t: vi.fn((key: string, options?: { name?: string }) => + options?.name ? `${key} ${options.name}` : key, + ), +})) + +vi.mock("./store", () => ({ + store: mocks.store, +})) + +describe("tray", () => { + beforeEach(() => { + vi.resetModules() + vi.clearAllMocks() + + mocks.env.isMacOS = false + mocks.env.isMAS = false + mocks.env.isWindows = false + mocks.getBadgeCount.mockReturnValue(0) + mocks.store.get.mockReturnValue(true) + mocks.trayInstances.length = 0 + }) + + it("refreshes the existing tray menu instead of recreating the native tray", async () => { + const { registerAppTray } = await import("./tray") + + registerAppTray() + registerAppTray() + + expect(mocks.trayInstances).toHaveLength(1) + expect(mocks.trayInstances[0]!.destroy).not.toHaveBeenCalled() + expect(mocks.trayInstances[0]!.setContextMenu).toHaveBeenCalledTimes(2) + }) +}) diff --git a/apps/desktop/layer/main/src/lib/tray.ts b/apps/desktop/layer/main/src/lib/tray.ts index 78e0bf653..b731e4f7e 100644 --- a/apps/desktop/layer/main/src/lib/tray.ts +++ b/apps/desktop/layer/main/src/lib/tray.ts @@ -75,10 +75,19 @@ const getTrayContextMenu = () => { }, ]) } + +const refreshTrayContextMenu = () => { + if (!tray) return + + tray.setContextMenu(getTrayContextMenu()) + tray.setToolTip(app.getName()) +} + export const registerAppTray = () => { if (!getTrayConfig()) return if (tray) { - destroyAppTray() + refreshTrayContextMenu() + return } const icon = nativeImage.createFromPath(getTrayIconPath()) @@ -87,10 +96,9 @@ export const registerAppTray = () => { trayIcon.setTemplateImage(true) tray = new Tray(trayIcon) - tray.setContextMenu(getTrayContextMenu()) - tray.setToolTip(app.getName()) + refreshTrayContextMenu() tray.on("mouse-enter", () => { - tray?.setContextMenu(getTrayContextMenu()) + refreshTrayContextMenu() }) if (isWindows) { tray.on("click", showWindow)