@lobehub/lobehub 2.0.0-next.164 → 2.0.0-next.166
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.
- package/.cursor/rules/desktop-feature-implementation.mdc +31 -34
- package/.cursor/rules/desktop-local-tools-implement.mdc +3 -3
- package/.cursor/rules/desktop-window-management.mdc +56 -66
- package/CHANGELOG.md +50 -0
- package/Dockerfile +44 -52
- package/README.md +6 -6
- package/README.zh-CN.md +6 -6
- package/apps/desktop/Development.md +42 -46
- package/apps/desktop/README.md +37 -1
- package/apps/desktop/README.zh-CN.md +26 -1
- package/apps/desktop/electron.vite.config.ts +1 -0
- package/apps/desktop/src/main/controllers/AuthCtr.ts +4 -3
- package/apps/desktop/src/main/controllers/BrowserWindowsCtr.ts +33 -20
- package/apps/desktop/src/main/controllers/DevtoolsCtr.ts +4 -2
- package/apps/desktop/src/main/controllers/LocalFileCtr.ts +14 -13
- package/apps/desktop/src/main/controllers/MenuCtr.ts +5 -4
- package/apps/desktop/src/main/controllers/NetworkProxyCtr.ts +18 -19
- package/apps/desktop/src/main/controllers/NotificationCtr.ts +4 -3
- package/apps/desktop/src/main/controllers/RemoteServerConfigCtr.ts +5 -4
- package/apps/desktop/src/main/controllers/RemoteServerSyncCtr.ts +3 -2
- package/apps/desktop/src/main/controllers/ShellCommandCtr.ts +5 -4
- package/apps/desktop/src/main/controllers/ShortcutCtr.ts +4 -3
- package/apps/desktop/src/main/controllers/SystemCtr.ts +7 -37
- package/apps/desktop/src/main/controllers/SystemServerCtr.ts +38 -0
- package/apps/desktop/src/main/controllers/TrayMenuCtr.ts +5 -4
- package/apps/desktop/src/main/controllers/UpdaterCtr.ts +6 -5
- package/apps/desktop/src/main/controllers/UploadFileCtr.ts +3 -25
- package/apps/desktop/src/main/controllers/UploadFileServerCtr.ts +33 -0
- package/apps/desktop/src/main/controllers/__tests__/AuthCtr.test.ts +9 -1
- package/apps/desktop/src/main/controllers/__tests__/BrowserWindowsCtr.test.ts +29 -9
- package/apps/desktop/src/main/controllers/__tests__/DevtoolsCtr.test.ts +12 -3
- package/apps/desktop/src/main/controllers/__tests__/LocalFileCtr.test.ts +7 -0
- package/apps/desktop/src/main/controllers/__tests__/MenuCtr.test.ts +10 -0
- package/apps/desktop/src/main/controllers/__tests__/NetworkProxyCtr.test.ts +10 -0
- package/apps/desktop/src/main/controllers/__tests__/NotificationCtr.test.ts +8 -0
- package/apps/desktop/src/main/controllers/__tests__/RemoteServerConfigCtr.test.ts +8 -0
- package/apps/desktop/src/main/controllers/__tests__/RemoteServerSyncCtr.test.ts +1 -0
- package/apps/desktop/src/main/controllers/__tests__/ShellCommandCtr.test.ts +10 -0
- package/apps/desktop/src/main/controllers/__tests__/ShortcutCtr.test.ts +11 -0
- package/apps/desktop/src/main/controllers/__tests__/SystemCtr.test.ts +43 -73
- package/apps/desktop/src/main/controllers/__tests__/SystemServerCtr.test.ts +75 -0
- package/apps/desktop/src/main/controllers/__tests__/TrayMenuCtr.test.ts +24 -13
- package/apps/desktop/src/main/controllers/__tests__/UpdaterCtr.test.ts +13 -2
- package/apps/desktop/src/main/controllers/__tests__/UploadFileCtr.test.ts +29 -108
- package/apps/desktop/src/main/controllers/__tests__/UploadFileServerCtr.test.ts +55 -0
- package/apps/desktop/src/main/controllers/_template.ts +2 -2
- package/apps/desktop/src/main/controllers/index.ts +5 -29
- package/apps/desktop/src/main/controllers/registry.ts +52 -0
- package/apps/desktop/src/main/core/App.ts +15 -47
- package/apps/desktop/src/main/core/__tests__/App.test.ts +5 -4
- package/apps/desktop/src/main/core/infrastructure/IoCContainer.ts +0 -5
- package/apps/desktop/src/main/core/infrastructure/__tests__/IoCContainer.test.ts +0 -50
- package/apps/desktop/src/main/exports.d.ts +8 -0
- package/apps/desktop/src/main/exports.ts +2 -0
- package/apps/desktop/src/main/global.d.ts +3 -0
- package/apps/desktop/src/main/modules/fileSearch/__tests__/macOS.integration.test.ts +17 -8
- package/apps/desktop/src/main/package.json +10 -0
- package/apps/desktop/src/main/services/fileSrv.ts +1 -1
- package/apps/desktop/src/main/utils/ipc/__tests__/base.test.ts +91 -0
- package/apps/desktop/src/main/utils/ipc/base.ts +170 -0
- package/apps/desktop/src/main/utils/ipc/index.ts +11 -0
- package/apps/desktop/src/main/utils/ipc/utility.ts +20 -0
- package/apps/desktop/src/preload/electronApi.ts +4 -1
- package/apps/desktop/src/preload/invoke.test.ts +13 -16
- package/apps/desktop/src/preload/invoke.ts +2 -5
- package/apps/desktop/src/preload/routeInterceptor.test.ts +13 -13
- package/apps/desktop/src/preload/routeInterceptor.ts +4 -4
- package/apps/desktop/tsconfig.json +15 -5
- package/changelog/v1.json +10 -0
- package/package.json +4 -3
- package/packages/electron-client-ipc/src/index.ts +1 -1
- package/packages/electron-client-ipc/src/ipc.test.ts +62 -0
- package/packages/electron-client-ipc/src/ipc.ts +63 -0
- package/packages/electron-client-ipc/src/streamInvoke.ts +7 -1
- package/packages/electron-client-ipc/src/types/dispatch.ts +1 -10
- package/packages/electron-client-ipc/vitest.config.mts +10 -0
- package/packages/electron-server-ipc/src/ipcClient.ts +1 -2
- package/packages/electron-server-ipc/src/ipcServer.ts +1 -2
- package/packages/electron-server-ipc/src/types/index.ts +1 -5
- package/pnpm-workspace.yaml +1 -1
- package/scripts/i18nWorkflow/const.ts +2 -2
- package/scripts/i18nWorkflow/i18nConfig.ts +7 -0
- package/scripts/i18nWorkflow/utils.ts +1 -1
- package/src/app/[variants]/(main)/discover/(detail)/provider/features/Sidebar/ActionButton/ProviderConfig.tsx +2 -2
- package/src/locales/default/setting.ts +1 -0
- package/src/server/modules/ElectronIPCClient/index.ts +59 -13
- package/src/services/electron/__tests__/devtools.test.ts +10 -6
- package/src/services/electron/autoUpdate.ts +5 -5
- package/src/services/electron/desktopNotification.ts +4 -7
- package/src/services/electron/devtools.ts +2 -2
- package/src/services/electron/file.ts +3 -2
- package/src/services/electron/localFileService.ts +17 -16
- package/src/services/electron/remoteServer.ts +7 -6
- package/src/services/electron/settings.ts +9 -11
- package/src/services/electron/system.ts +8 -6
- package/src/store/chat/slices/plugin/actions/pluginTypes.ts +1 -1
- package/src/store/global/actions/general.ts +8 -10
- package/src/utils/electron/desktopRemoteRPCFetch.ts +3 -2
- package/src/utils/electron/ipc.ts +12 -0
- package/tsconfig.json +5 -0
- package/apps/desktop/src/main/types/ipcClientEvent.ts +0 -3
- package/packages/electron-client-ipc/src/dispatch.ts +0 -41
|
@@ -1,18 +1,16 @@
|
|
|
1
1
|
import { ElectronAppState, ThemeMode } from '@lobechat/electron-client-ipc';
|
|
2
2
|
import { app, nativeTheme, shell, systemPreferences } from 'electron';
|
|
3
3
|
import { macOS } from 'electron-is';
|
|
4
|
-
import { readFileSync, writeFileSync } from 'node:fs';
|
|
5
|
-
import { join } from 'node:path';
|
|
6
4
|
import process from 'node:process';
|
|
7
5
|
|
|
8
|
-
import { DB_SCHEMA_HASH_FILENAME, LOCAL_DATABASE_DIR, userDataDir } from '@/const/dir';
|
|
9
6
|
import { createLogger } from '@/utils/logger';
|
|
10
7
|
|
|
11
|
-
import { ControllerModule,
|
|
8
|
+
import { ControllerModule, IpcMethod } from './index';
|
|
12
9
|
|
|
13
10
|
const logger = createLogger('controllers:SystemCtr');
|
|
14
11
|
|
|
15
12
|
export default class SystemController extends ControllerModule {
|
|
13
|
+
static override readonly groupName = 'system';
|
|
16
14
|
private systemThemeListenerInitialized = false;
|
|
17
15
|
|
|
18
16
|
/**
|
|
@@ -26,7 +24,7 @@ export default class SystemController extends ControllerModule {
|
|
|
26
24
|
* Handles the 'getDesktopAppState' IPC request.
|
|
27
25
|
* Gathers essential application and system information.
|
|
28
26
|
*/
|
|
29
|
-
@
|
|
27
|
+
@IpcMethod()
|
|
30
28
|
async getAppState(): Promise<ElectronAppState> {
|
|
31
29
|
const platform = process.platform;
|
|
32
30
|
const arch = process.arch;
|
|
@@ -56,13 +54,13 @@ export default class SystemController extends ControllerModule {
|
|
|
56
54
|
/**
|
|
57
55
|
* 检查可用性
|
|
58
56
|
*/
|
|
59
|
-
@
|
|
57
|
+
@IpcMethod()
|
|
60
58
|
checkAccessibilityForMacOS() {
|
|
61
59
|
if (!macOS()) return;
|
|
62
60
|
return systemPreferences.isTrustedAccessibilityClient(true);
|
|
63
61
|
}
|
|
64
62
|
|
|
65
|
-
@
|
|
63
|
+
@IpcMethod()
|
|
66
64
|
openExternalLink(url: string) {
|
|
67
65
|
return shell.openExternal(url);
|
|
68
66
|
}
|
|
@@ -70,7 +68,7 @@ export default class SystemController extends ControllerModule {
|
|
|
70
68
|
/**
|
|
71
69
|
* 更新应用语言设置
|
|
72
70
|
*/
|
|
73
|
-
@
|
|
71
|
+
@IpcMethod()
|
|
74
72
|
async updateLocale(locale: string) {
|
|
75
73
|
// 保存语言设置
|
|
76
74
|
this.app.storeManager.set('locale', locale);
|
|
@@ -82,7 +80,7 @@ export default class SystemController extends ControllerModule {
|
|
|
82
80
|
return { success: true };
|
|
83
81
|
}
|
|
84
82
|
|
|
85
|
-
@
|
|
83
|
+
@IpcMethod()
|
|
86
84
|
async updateThemeModeHandler(themeMode: ThemeMode) {
|
|
87
85
|
this.app.storeManager.set('themeMode', themeMode);
|
|
88
86
|
this.app.browserManager.broadcastToAllWindows('themeChanged', { themeMode });
|
|
@@ -91,34 +89,6 @@ export default class SystemController extends ControllerModule {
|
|
|
91
89
|
this.app.browserManager.handleAppThemeChange();
|
|
92
90
|
}
|
|
93
91
|
|
|
94
|
-
@ipcServerEvent('getDatabasePath')
|
|
95
|
-
async getDatabasePath() {
|
|
96
|
-
return join(this.app.appStoragePath, LOCAL_DATABASE_DIR);
|
|
97
|
-
}
|
|
98
|
-
|
|
99
|
-
@ipcServerEvent('getDatabaseSchemaHash')
|
|
100
|
-
async getDatabaseSchemaHash() {
|
|
101
|
-
try {
|
|
102
|
-
return readFileSync(this.DB_SCHEMA_HASH_PATH, 'utf8');
|
|
103
|
-
} catch {
|
|
104
|
-
return undefined;
|
|
105
|
-
}
|
|
106
|
-
}
|
|
107
|
-
|
|
108
|
-
@ipcServerEvent('getUserDataPath')
|
|
109
|
-
async getUserDataPath() {
|
|
110
|
-
return userDataDir;
|
|
111
|
-
}
|
|
112
|
-
|
|
113
|
-
@ipcServerEvent('setDatabaseSchemaHash')
|
|
114
|
-
async setDatabaseSchemaHash(hash: string) {
|
|
115
|
-
writeFileSync(this.DB_SCHEMA_HASH_PATH, hash, 'utf8');
|
|
116
|
-
}
|
|
117
|
-
|
|
118
|
-
private get DB_SCHEMA_HASH_PATH() {
|
|
119
|
-
return join(this.app.appStoragePath, DB_SCHEMA_HASH_FILENAME);
|
|
120
|
-
}
|
|
121
|
-
|
|
122
92
|
/**
|
|
123
93
|
* Initialize system theme listener to monitor OS theme changes
|
|
124
94
|
*/
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import { readFileSync, writeFileSync } from 'node:fs';
|
|
2
|
+
import { join } from 'node:path';
|
|
3
|
+
|
|
4
|
+
import { DB_SCHEMA_HASH_FILENAME, LOCAL_DATABASE_DIR, userDataDir } from '@/const/dir';
|
|
5
|
+
|
|
6
|
+
import { ControllerModule, IpcServerMethod } from './index';
|
|
7
|
+
|
|
8
|
+
export default class SystemServerCtr extends ControllerModule {
|
|
9
|
+
static override readonly groupName = 'system';
|
|
10
|
+
|
|
11
|
+
@IpcServerMethod()
|
|
12
|
+
async getDatabasePath() {
|
|
13
|
+
return join(this.app.appStoragePath, LOCAL_DATABASE_DIR);
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
@IpcServerMethod()
|
|
17
|
+
async getDatabaseSchemaHash() {
|
|
18
|
+
try {
|
|
19
|
+
return readFileSync(this.DB_SCHEMA_HASH_PATH, 'utf8');
|
|
20
|
+
} catch {
|
|
21
|
+
return undefined;
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
@IpcServerMethod()
|
|
26
|
+
async getUserDataPath() {
|
|
27
|
+
return userDataDir;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
@IpcServerMethod()
|
|
31
|
+
async setDatabaseSchemaHash(hash: string) {
|
|
32
|
+
writeFileSync(this.DB_SCHEMA_HASH_PATH, hash, 'utf8');
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
private get DB_SCHEMA_HASH_PATH() {
|
|
36
|
+
return join(this.app.appStoragePath, DB_SCHEMA_HASH_FILENAME);
|
|
37
|
+
}
|
|
38
|
+
}
|
|
@@ -6,12 +6,13 @@ import {
|
|
|
6
6
|
|
|
7
7
|
import { createLogger } from '@/utils/logger';
|
|
8
8
|
|
|
9
|
-
import { ControllerModule,
|
|
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
|
-
@
|
|
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
|
-
@
|
|
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
|
-
@
|
|
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,
|
|
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
|
-
@
|
|
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
|
-
@
|
|
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
|
-
@
|
|
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
|
-
@
|
|
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,
|
|
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
|
-
@
|
|
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
|
|
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 {
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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',
|
|
@@ -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
|
|