@lobehub/lobehub 2.0.0-next.164 → 2.0.0-next.165

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (101) hide show
  1. package/.cursor/rules/desktop-feature-implementation.mdc +31 -34
  2. package/.cursor/rules/desktop-local-tools-implement.mdc +3 -3
  3. package/.cursor/rules/desktop-window-management.mdc +56 -66
  4. package/CHANGELOG.md +25 -0
  5. package/README.md +6 -6
  6. package/README.zh-CN.md +6 -6
  7. package/apps/desktop/Development.md +42 -46
  8. package/apps/desktop/README.md +37 -1
  9. package/apps/desktop/README.zh-CN.md +26 -1
  10. package/apps/desktop/electron.vite.config.ts +1 -0
  11. package/apps/desktop/src/main/controllers/AuthCtr.ts +4 -3
  12. package/apps/desktop/src/main/controllers/BrowserWindowsCtr.ts +33 -20
  13. package/apps/desktop/src/main/controllers/DevtoolsCtr.ts +4 -2
  14. package/apps/desktop/src/main/controllers/LocalFileCtr.ts +14 -13
  15. package/apps/desktop/src/main/controllers/MenuCtr.ts +5 -4
  16. package/apps/desktop/src/main/controllers/NetworkProxyCtr.ts +18 -19
  17. package/apps/desktop/src/main/controllers/NotificationCtr.ts +4 -3
  18. package/apps/desktop/src/main/controllers/RemoteServerConfigCtr.ts +5 -4
  19. package/apps/desktop/src/main/controllers/RemoteServerSyncCtr.ts +3 -2
  20. package/apps/desktop/src/main/controllers/ShellCommandCtr.ts +5 -4
  21. package/apps/desktop/src/main/controllers/ShortcutCtr.ts +4 -3
  22. package/apps/desktop/src/main/controllers/SystemCtr.ts +7 -37
  23. package/apps/desktop/src/main/controllers/SystemServerCtr.ts +38 -0
  24. package/apps/desktop/src/main/controllers/TrayMenuCtr.ts +5 -4
  25. package/apps/desktop/src/main/controllers/UpdaterCtr.ts +6 -5
  26. package/apps/desktop/src/main/controllers/UploadFileCtr.ts +3 -25
  27. package/apps/desktop/src/main/controllers/UploadFileServerCtr.ts +33 -0
  28. package/apps/desktop/src/main/controllers/__tests__/AuthCtr.test.ts +9 -1
  29. package/apps/desktop/src/main/controllers/__tests__/BrowserWindowsCtr.test.ts +29 -9
  30. package/apps/desktop/src/main/controllers/__tests__/DevtoolsCtr.test.ts +12 -3
  31. package/apps/desktop/src/main/controllers/__tests__/LocalFileCtr.test.ts +7 -0
  32. package/apps/desktop/src/main/controllers/__tests__/MenuCtr.test.ts +10 -0
  33. package/apps/desktop/src/main/controllers/__tests__/NetworkProxyCtr.test.ts +10 -0
  34. package/apps/desktop/src/main/controllers/__tests__/NotificationCtr.test.ts +8 -0
  35. package/apps/desktop/src/main/controllers/__tests__/RemoteServerConfigCtr.test.ts +8 -0
  36. package/apps/desktop/src/main/controllers/__tests__/RemoteServerSyncCtr.test.ts +1 -0
  37. package/apps/desktop/src/main/controllers/__tests__/ShellCommandCtr.test.ts +10 -0
  38. package/apps/desktop/src/main/controllers/__tests__/ShortcutCtr.test.ts +11 -0
  39. package/apps/desktop/src/main/controllers/__tests__/SystemCtr.test.ts +43 -73
  40. package/apps/desktop/src/main/controllers/__tests__/SystemServerCtr.test.ts +75 -0
  41. package/apps/desktop/src/main/controllers/__tests__/TrayMenuCtr.test.ts +24 -13
  42. package/apps/desktop/src/main/controllers/__tests__/UpdaterCtr.test.ts +13 -2
  43. package/apps/desktop/src/main/controllers/__tests__/UploadFileCtr.test.ts +29 -108
  44. package/apps/desktop/src/main/controllers/__tests__/UploadFileServerCtr.test.ts +55 -0
  45. package/apps/desktop/src/main/controllers/_template.ts +2 -2
  46. package/apps/desktop/src/main/controllers/index.ts +5 -29
  47. package/apps/desktop/src/main/controllers/registry.ts +52 -0
  48. package/apps/desktop/src/main/core/App.ts +15 -47
  49. package/apps/desktop/src/main/core/__tests__/App.test.ts +5 -4
  50. package/apps/desktop/src/main/core/infrastructure/IoCContainer.ts +0 -5
  51. package/apps/desktop/src/main/core/infrastructure/__tests__/IoCContainer.test.ts +0 -50
  52. package/apps/desktop/src/main/exports.d.ts +8 -0
  53. package/apps/desktop/src/main/exports.ts +2 -0
  54. package/apps/desktop/src/main/global.d.ts +3 -0
  55. package/apps/desktop/src/main/modules/fileSearch/__tests__/macOS.integration.test.ts +17 -8
  56. package/apps/desktop/src/main/package.json +10 -0
  57. package/apps/desktop/src/main/services/fileSrv.ts +1 -1
  58. package/apps/desktop/src/main/utils/ipc/__tests__/base.test.ts +91 -0
  59. package/apps/desktop/src/main/utils/ipc/base.ts +170 -0
  60. package/apps/desktop/src/main/utils/ipc/index.ts +11 -0
  61. package/apps/desktop/src/main/utils/ipc/utility.ts +20 -0
  62. package/apps/desktop/src/preload/electronApi.ts +4 -1
  63. package/apps/desktop/src/preload/invoke.test.ts +13 -16
  64. package/apps/desktop/src/preload/invoke.ts +2 -5
  65. package/apps/desktop/src/preload/routeInterceptor.test.ts +13 -13
  66. package/apps/desktop/src/preload/routeInterceptor.ts +4 -4
  67. package/apps/desktop/tsconfig.json +15 -5
  68. package/changelog/v1.json +5 -0
  69. package/package.json +4 -3
  70. package/packages/electron-client-ipc/src/index.ts +1 -1
  71. package/packages/electron-client-ipc/src/ipc.test.ts +62 -0
  72. package/packages/electron-client-ipc/src/ipc.ts +63 -0
  73. package/packages/electron-client-ipc/src/streamInvoke.ts +7 -1
  74. package/packages/electron-client-ipc/src/types/dispatch.ts +1 -10
  75. package/packages/electron-client-ipc/vitest.config.mts +10 -0
  76. package/packages/electron-server-ipc/src/ipcClient.ts +1 -2
  77. package/packages/electron-server-ipc/src/ipcServer.ts +1 -2
  78. package/packages/electron-server-ipc/src/types/index.ts +1 -5
  79. package/pnpm-workspace.yaml +1 -1
  80. package/scripts/i18nWorkflow/const.ts +2 -2
  81. package/scripts/i18nWorkflow/i18nConfig.ts +7 -0
  82. package/scripts/i18nWorkflow/utils.ts +1 -1
  83. package/src/app/[variants]/(main)/discover/(detail)/provider/features/Sidebar/ActionButton/ProviderConfig.tsx +2 -2
  84. package/src/locales/default/setting.ts +1 -0
  85. package/src/server/modules/ElectronIPCClient/index.ts +59 -13
  86. package/src/services/electron/__tests__/devtools.test.ts +10 -6
  87. package/src/services/electron/autoUpdate.ts +5 -5
  88. package/src/services/electron/desktopNotification.ts +4 -7
  89. package/src/services/electron/devtools.ts +2 -2
  90. package/src/services/electron/file.ts +3 -2
  91. package/src/services/electron/localFileService.ts +17 -16
  92. package/src/services/electron/remoteServer.ts +7 -6
  93. package/src/services/electron/settings.ts +9 -11
  94. package/src/services/electron/system.ts +8 -6
  95. package/src/store/chat/slices/plugin/actions/pluginTypes.ts +1 -1
  96. package/src/store/global/actions/general.ts +8 -10
  97. package/src/utils/electron/desktopRemoteRPCFetch.ts +3 -2
  98. package/src/utils/electron/ipc.ts +12 -0
  99. package/tsconfig.json +5 -0
  100. package/apps/desktop/src/main/types/ipcClientEvent.ts +0 -3
  101. package/packages/electron-client-ipc/src/dispatch.ts +0 -41
@@ -6,12 +6,13 @@ import {
6
6
 
7
7
  import { createLogger } from '@/utils/logger';
8
8
 
9
- import { ControllerModule, ipcClientEvent } from './index';
9
+ import { ControllerModule, IpcMethod } from './index';
10
10
 
11
11
  // Create logger
12
12
  const logger = createLogger('controllers:TrayMenuCtr');
13
13
 
14
14
  export default class TrayMenuCtr extends ControllerModule {
15
+ static override readonly groupName = 'tray';
15
16
  async toggleMainWindow() {
16
17
  logger.debug('Toggle main window visibility via shortcut');
17
18
  const mainWindow = this.app.browserManager.getMainWindow();
@@ -23,7 +24,7 @@ export default class TrayMenuCtr extends ControllerModule {
23
24
  * @param options Balloon options
24
25
  * @returns Operation result
25
26
  */
26
- @ipcClientEvent('showTrayNotification')
27
+ @IpcMethod()
27
28
  async showNotification(options: ShowTrayNotificationParams) {
28
29
  logger.debug('Show tray balloon notification');
29
30
 
@@ -52,7 +53,7 @@ export default class TrayMenuCtr extends ControllerModule {
52
53
  * @param options Icon options
53
54
  * @returns Operation result
54
55
  */
55
- @ipcClientEvent('updateTrayIcon')
56
+ @IpcMethod()
56
57
  async updateTrayIcon(options: UpdateTrayIconParams) {
57
58
  logger.debug('Update tray icon');
58
59
 
@@ -84,7 +85,7 @@ export default class TrayMenuCtr extends ControllerModule {
84
85
  * @param options Tooltip text options
85
86
  * @returns Operation result
86
87
  */
87
- @ipcClientEvent('updateTrayTooltip')
88
+ @IpcMethod()
88
89
  async updateTrayTooltip(options: UpdateTrayTooltipParams) {
89
90
  logger.debug('Update tray tooltip text');
90
91
 
@@ -1,14 +1,15 @@
1
1
  import { createLogger } from '@/utils/logger';
2
2
 
3
- import { ControllerModule, ipcClientEvent } from './index';
3
+ import { ControllerModule, IpcMethod } from './index';
4
4
 
5
5
  const logger = createLogger('controllers:UpdaterCtr');
6
6
 
7
7
  export default class UpdaterCtr extends ControllerModule {
8
+ static override readonly groupName = 'autoUpdate';
8
9
  /**
9
10
  * Check for updates
10
11
  */
11
- @ipcClientEvent('checkUpdate')
12
+ @IpcMethod()
12
13
  async checkForUpdates() {
13
14
  logger.info('Check for updates requested');
14
15
  await this.app.updaterManager.checkForUpdates();
@@ -17,7 +18,7 @@ export default class UpdaterCtr extends ControllerModule {
17
18
  /**
18
19
  * Download update
19
20
  */
20
- @ipcClientEvent('downloadUpdate')
21
+ @IpcMethod()
21
22
  async downloadUpdate() {
22
23
  logger.info('Download update requested');
23
24
  await this.app.updaterManager.downloadUpdate();
@@ -26,7 +27,7 @@ export default class UpdaterCtr extends ControllerModule {
26
27
  /**
27
28
  * Quit application and install update
28
29
  */
29
- @ipcClientEvent('installNow')
30
+ @IpcMethod()
30
31
  quitAndInstallUpdate() {
31
32
  logger.info('Quit and install update requested');
32
33
  this.app.updaterManager.installNow();
@@ -35,7 +36,7 @@ export default class UpdaterCtr extends ControllerModule {
35
36
  /**
36
37
  * Install update on next startup
37
38
  */
38
- @ipcClientEvent('installLater')
39
+ @IpcMethod()
39
40
  installLater() {
40
41
  logger.info('Install later requested');
41
42
  this.app.updaterManager.installLater();
@@ -1,39 +1,17 @@
1
1
  import { UploadFileParams } from '@lobechat/electron-client-ipc';
2
- import { CreateFileParams } from '@lobechat/electron-server-ipc';
3
2
 
4
3
  import FileService from '@/services/fileSrv';
5
4
 
6
- import { ControllerModule, ipcClientEvent, ipcServerEvent } from './index';
5
+ import { ControllerModule, IpcMethod } from './index';
7
6
 
8
7
  export default class UploadFileCtr extends ControllerModule {
8
+ static override readonly groupName = 'upload';
9
9
  private get fileService() {
10
10
  return this.app.getService(FileService);
11
11
  }
12
12
 
13
- @ipcClientEvent('createFile')
13
+ @IpcMethod()
14
14
  async uploadFile(params: UploadFileParams) {
15
15
  return this.fileService.uploadFile(params);
16
16
  }
17
-
18
- // ======== server event
19
-
20
- @ipcServerEvent('getStaticFilePath')
21
- async getFileUrlById(id: string) {
22
- return this.fileService.getFilePath(id);
23
- }
24
-
25
- @ipcServerEvent('getFileHTTPURL')
26
- async getFileHTTPURL(path: string) {
27
- return this.fileService.getFileHTTPURL(path);
28
- }
29
-
30
- @ipcServerEvent('deleteFiles')
31
- async deleteFiles(paths: string[]) {
32
- return this.fileService.deleteFiles(paths);
33
- }
34
-
35
- @ipcServerEvent('createFile')
36
- async createFile(params: CreateFileParams) {
37
- return this.fileService.uploadFile(params);
38
- }
39
17
  }
@@ -0,0 +1,33 @@
1
+ import { CreateFileParams } from '@lobechat/electron-server-ipc';
2
+
3
+ import FileService from '@/services/fileSrv';
4
+
5
+ import { ControllerModule, IpcServerMethod } from './index';
6
+
7
+ export default class UploadFileServerCtr extends ControllerModule {
8
+ static override readonly groupName = 'upload';
9
+
10
+ private get fileService() {
11
+ return this.app.getService(FileService);
12
+ }
13
+
14
+ @IpcServerMethod()
15
+ async getFileUrlById(id: string) {
16
+ return this.fileService.getFilePath(id);
17
+ }
18
+
19
+ @IpcServerMethod()
20
+ async getFileHTTPURL(path: string) {
21
+ return this.fileService.getFileHTTPURL(path);
22
+ }
23
+
24
+ @IpcServerMethod()
25
+ async deleteFiles(paths: string[]) {
26
+ return this.fileService.deleteFiles(paths);
27
+ }
28
+
29
+ @IpcServerMethod()
30
+ async createFile(params: CreateFileParams) {
31
+ return this.fileService.uploadFile(params);
32
+ }
33
+ }
@@ -18,11 +18,18 @@ vi.mock('@/utils/logger', () => ({
18
18
  }),
19
19
  }));
20
20
 
21
+ const { ipcMainHandleMock } = vi.hoisted(() => ({
22
+ ipcMainHandleMock: vi.fn(),
23
+ }));
24
+
21
25
  // Mock electron
22
26
  vi.mock('electron', () => ({
23
27
  BrowserWindow: {
24
28
  getAllWindows: vi.fn(() => []),
25
29
  },
30
+ ipcMain: {
31
+ handle: ipcMainHandleMock,
32
+ },
26
33
  shell: {
27
34
  openExternal: vi.fn().mockResolvedValue(undefined),
28
35
  },
@@ -99,6 +106,7 @@ describe('AuthCtr', () => {
99
106
 
100
107
  beforeEach(() => {
101
108
  vi.clearAllMocks();
109
+ ipcMainHandleMock.mockClear();
102
110
  randomBytesCounter = 0; // Reset counter for each test
103
111
 
104
112
  // Reset shell.openExternal to default successful behavior
@@ -123,7 +131,7 @@ describe('AuthCtr', () => {
123
131
 
124
132
  afterEach(() => {
125
133
  // Clean up authCtr intervals (using real timers, not fake timers)
126
- authCtr.cleanup();
134
+ authCtr?.cleanup?.();
127
135
  // Clean up any fake timers if used
128
136
  vi.clearAllTimers();
129
137
  });
@@ -3,10 +3,21 @@ import { Mock, beforeEach, describe, expect, it, vi } from 'vitest';
3
3
 
4
4
  import { AppBrowsersIdentifiers, BrowsersIdentifiers } from '@/appBrowsers';
5
5
  import type { App } from '@/core/App';
6
- import type { IpcClientEventSender } from '@/types/ipcClientEvent';
6
+ import type { IpcContext } from '@/utils/ipc';
7
+ import { runWithIpcContext } from '@/utils/ipc';
7
8
 
8
9
  import BrowserWindowsCtr from '../BrowserWindowsCtr';
9
10
 
11
+ const { ipcMainHandleMock } = vi.hoisted(() => ({
12
+ ipcMainHandleMock: vi.fn(),
13
+ }));
14
+
15
+ vi.mock('electron', () => ({
16
+ ipcMain: {
17
+ handle: ipcMainHandleMock,
18
+ },
19
+ }));
20
+
10
21
  // 模拟 App 及其依赖项
11
22
  const mockToggleVisible = vi.fn();
12
23
  const mockLoadUrl = vi.fn();
@@ -16,6 +27,9 @@ const mockCloseWindow = vi.fn();
16
27
  const mockMinimizeWindow = vi.fn();
17
28
  const mockMaximizeWindow = vi.fn();
18
29
  const mockRetrieveByIdentifier = vi.fn();
30
+ const testSenderIdentifierString: string = 'test-window-event-id';
31
+
32
+ const mockGetIdentifierByWebContents = vi.fn(() => testSenderIdentifierString);
19
33
  const mockGetMainWindow = vi.fn(() => ({
20
34
  toggleVisible: mockToggleVisible,
21
35
  loadUrl: mockLoadUrl,
@@ -32,6 +46,7 @@ const { findMatchingRoute } = await import('~common/routes');
32
46
 
33
47
  const mockApp = {
34
48
  browserManager: {
49
+ getIdentifierByWebContents: mockGetIdentifierByWebContents,
35
50
  getMainWindow: mockGetMainWindow,
36
51
  redirectToPage: mockRedirectToPage,
37
52
  closeWindow: mockCloseWindow,
@@ -53,6 +68,7 @@ describe('BrowserWindowsCtr', () => {
53
68
 
54
69
  beforeEach(() => {
55
70
  vi.clearAllMocks();
71
+ ipcMainHandleMock.mockClear();
56
72
  browserWindowsCtr = new BrowserWindowsCtr(mockApp);
57
73
  });
58
74
 
@@ -82,28 +98,32 @@ describe('BrowserWindowsCtr', () => {
82
98
  });
83
99
  });
84
100
 
85
- const testSenderIdentifierString: string = 'test-window-event-id';
86
- const sender: IpcClientEventSender = {
87
- identifier: testSenderIdentifierString,
88
- };
89
-
90
101
  describe('closeWindow', () => {
91
102
  it('should close the window with the given sender identifier', () => {
92
- browserWindowsCtr.closeWindow(undefined, sender);
103
+ const sender = {} as any;
104
+ const context = { sender, event: { sender } as any } as IpcContext;
105
+ runWithIpcContext(context, () => browserWindowsCtr.closeWindow());
106
+ expect(mockGetIdentifierByWebContents).toHaveBeenCalledWith(context.sender);
93
107
  expect(mockCloseWindow).toHaveBeenCalledWith(testSenderIdentifierString);
94
108
  });
95
109
  });
96
110
 
97
111
  describe('minimizeWindow', () => {
98
112
  it('should minimize the window with the given sender identifier', () => {
99
- browserWindowsCtr.minimizeWindow(undefined, sender);
113
+ const sender = {} as any;
114
+ const context = { sender, event: { sender } as any } as IpcContext;
115
+ runWithIpcContext(context, () => browserWindowsCtr.minimizeWindow());
116
+ expect(mockGetIdentifierByWebContents).toHaveBeenCalledWith(context.sender);
100
117
  expect(mockMinimizeWindow).toHaveBeenCalledWith(testSenderIdentifierString);
101
118
  });
102
119
  });
103
120
 
104
121
  describe('maximizeWindow', () => {
105
122
  it('should maximize the window with the given sender identifier', () => {
106
- browserWindowsCtr.maximizeWindow(undefined, sender);
123
+ const sender = {} as any;
124
+ const context = { sender, event: { sender } as any } as IpcContext;
125
+ runWithIpcContext(context, () => browserWindowsCtr.maximizeWindow());
126
+ expect(mockGetIdentifierByWebContents).toHaveBeenCalledWith(context.sender);
107
127
  expect(mockMaximizeWindow).toHaveBeenCalledWith(testSenderIdentifierString);
108
128
  });
109
129
  });
@@ -4,6 +4,16 @@ import type { App } from '@/core/App';
4
4
 
5
5
  import DevtoolsCtr from '../DevtoolsCtr';
6
6
 
7
+ const { ipcMainHandleMock } = vi.hoisted(() => ({
8
+ ipcMainHandleMock: vi.fn(),
9
+ }));
10
+
11
+ vi.mock('electron', () => ({
12
+ ipcMain: {
13
+ handle: ipcMainHandleMock,
14
+ },
15
+ }));
16
+
7
17
  // 模拟 App 及其依赖项
8
18
  const mockShow = vi.fn();
9
19
  const mockRetrieveByIdentifier = vi.fn(() => ({
@@ -24,10 +34,9 @@ describe('DevtoolsCtr', () => {
24
34
 
25
35
  beforeEach(() => {
26
36
  vi.clearAllMocks(); // 只清除 vi.fn() 创建的模拟函数的记录,不影响 IoCContainer 状态
37
+ ipcMainHandleMock.mockClear();
27
38
 
28
- // 实例化 DevtoolsCtr
29
- // 它将继承自真实的 ControllerModule。
30
- // 其 @ipcClientEvent 装饰器会执行并与真实的 IoCContainer 交互。
39
+ // 实例化 DevtoolsCtr。其 @IpcMethod 装饰器会执行并与真实的 IoCContainer 交互。
31
40
  devtoolsCtr = new DevtoolsCtr(mockApp);
32
41
  });
33
42
 
@@ -4,6 +4,10 @@ import type { App } from '@/core/App';
4
4
 
5
5
  import LocalFileCtr from '../LocalFileCtr';
6
6
 
7
+ const { ipcMainHandleMock } = vi.hoisted(() => ({
8
+ ipcMainHandleMock: vi.fn(),
9
+ }));
10
+
7
11
  // Mock logger
8
12
  vi.mock('@/utils/logger', () => ({
9
13
  createLogger: () => ({
@@ -22,6 +26,9 @@ vi.mock('@lobechat/file-loaders', () => ({
22
26
 
23
27
  // Mock electron
24
28
  vi.mock('electron', () => ({
29
+ ipcMain: {
30
+ handle: ipcMainHandleMock,
31
+ },
25
32
  shell: {
26
33
  openPath: vi.fn(),
27
34
  },
@@ -4,6 +4,16 @@ import type { App } from '@/core/App';
4
4
 
5
5
  import MenuController from '../MenuCtr';
6
6
 
7
+ const { ipcMainHandleMock } = vi.hoisted(() => ({
8
+ ipcMainHandleMock: vi.fn(),
9
+ }));
10
+
11
+ vi.mock('electron', () => ({
12
+ ipcMain: {
13
+ handle: ipcMainHandleMock,
14
+ },
15
+ }));
16
+
7
17
  // 模拟 App 及其依赖项
8
18
  const mockRefreshMenus = vi.fn();
9
19
  const mockShowContextMenu = vi.fn();
@@ -5,6 +5,10 @@ import type { App } from '@/core/App';
5
5
 
6
6
  import NetworkProxyCtr from '../NetworkProxyCtr';
7
7
 
8
+ const { ipcMainHandleMock } = vi.hoisted(() => ({
9
+ ipcMainHandleMock: vi.fn(),
10
+ }));
11
+
8
12
  // 模拟 logger
9
13
  vi.mock('@/utils/logger', () => ({
10
14
  createLogger: () => ({
@@ -54,6 +58,7 @@ describe('NetworkProxyCtr', () => {
54
58
 
55
59
  beforeEach(async () => {
56
60
  vi.clearAllMocks();
61
+ ipcMainHandleMock.mockClear();
57
62
 
58
63
  // 动态导入 undici Mock
59
64
  mockUndici = await import('undici');
@@ -418,3 +423,8 @@ describe('NetworkProxyCtr', () => {
418
423
  });
419
424
  });
420
425
  });
426
+ vi.mock('electron', () => ({
427
+ ipcMain: {
428
+ handle: ipcMainHandleMock,
429
+ },
430
+ }));
@@ -5,6 +5,10 @@ import type { App } from '@/core/App';
5
5
 
6
6
  import NotificationCtr from '../NotificationCtr';
7
7
 
8
+ const { ipcMainHandleMock } = vi.hoisted(() => ({
9
+ ipcMainHandleMock: vi.fn(),
10
+ }));
11
+
8
12
  // Mock logger
9
13
  vi.mock('@/utils/logger', () => ({
10
14
  createLogger: () => ({
@@ -25,6 +29,9 @@ vi.mock('electron', () => {
25
29
  MockNotification.isSupported = vi.fn(() => true);
26
30
 
27
31
  return {
32
+ ipcMain: {
33
+ handle: ipcMainHandleMock,
34
+ },
28
35
  Notification: MockNotification,
29
36
  app: {
30
37
  setAppUserModelId: vi.fn(),
@@ -65,6 +72,7 @@ describe('NotificationCtr', () => {
65
72
 
66
73
  beforeEach(() => {
67
74
  vi.clearAllMocks();
75
+ ipcMainHandleMock.mockClear();
68
76
  vi.useFakeTimers();
69
77
  controller = new NotificationCtr(mockApp);
70
78
  });
@@ -5,6 +5,10 @@ import type { App } from '@/core/App';
5
5
 
6
6
  import RemoteServerConfigCtr from '../RemoteServerConfigCtr';
7
7
 
8
+ const { ipcMainHandleMock } = vi.hoisted(() => ({
9
+ ipcMainHandleMock: vi.fn(),
10
+ }));
11
+
8
12
  // Mock logger
9
13
  vi.mock('@/utils/logger', () => ({
10
14
  createLogger: () => ({
@@ -17,6 +21,9 @@ vi.mock('@/utils/logger', () => ({
17
21
 
18
22
  // Mock electron
19
23
  vi.mock('electron', () => ({
24
+ ipcMain: {
25
+ handle: ipcMainHandleMock,
26
+ },
20
27
  safeStorage: {
21
28
  decryptString: vi.fn((buffer: Buffer) => buffer.toString()),
22
29
  encryptString: vi.fn((str: string) => Buffer.from(str)),
@@ -45,6 +52,7 @@ describe('RemoteServerConfigCtr', () => {
45
52
 
46
53
  beforeEach(() => {
47
54
  vi.clearAllMocks();
55
+ ipcMainHandleMock.mockClear();
48
56
  mockStoreManager.get.mockReturnValue({
49
57
  active: false,
50
58
  storageMode: 'local',
@@ -22,6 +22,7 @@ vi.mock('electron', () => ({
22
22
  getPath: vi.fn(() => '/mock/user/data'),
23
23
  },
24
24
  ipcMain: {
25
+ handle: vi.fn(),
25
26
  on: vi.fn(),
26
27
  },
27
28
  }));
@@ -4,6 +4,16 @@ import type { App } from '@/core/App';
4
4
 
5
5
  import ShellCommandCtr from '../ShellCommandCtr';
6
6
 
7
+ const { ipcMainHandleMock } = vi.hoisted(() => ({
8
+ ipcMainHandleMock: vi.fn(),
9
+ }));
10
+
11
+ vi.mock('electron', () => ({
12
+ ipcMain: {
13
+ handle: ipcMainHandleMock,
14
+ },
15
+ }));
16
+
7
17
  // Mock logger
8
18
  vi.mock('@/utils/logger', () => ({
9
19
  createLogger: () => ({
@@ -4,6 +4,16 @@ import type { App } from '@/core/App';
4
4
 
5
5
  import ShortcutController from '../ShortcutCtr';
6
6
 
7
+ const { ipcMainHandleMock } = vi.hoisted(() => ({
8
+ ipcMainHandleMock: vi.fn(),
9
+ }));
10
+
11
+ vi.mock('electron', () => ({
12
+ ipcMain: {
13
+ handle: ipcMainHandleMock,
14
+ },
15
+ }));
16
+
7
17
  // 模拟 App 及其依赖项
8
18
  const mockGetShortcutsConfig = vi.fn().mockReturnValue({
9
19
  toggleMainWindow: 'CommandOrControl+Shift+L',
@@ -26,6 +36,7 @@ describe('ShortcutController', () => {
26
36
 
27
37
  beforeEach(() => {
28
38
  vi.clearAllMocks();
39
+ ipcMainHandleMock.mockClear();
29
40
  shortcutController = new ShortcutController(mockApp);
30
41
  });
31
42