@lobehub/lobehub 2.0.0-next.142 → 2.0.0-next.143
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/CHANGELOG.md +25 -0
- package/apps/desktop/package.json +1 -0
- package/apps/desktop/src/main/core/ui/__tests__/MenuManager.test.ts +320 -0
- package/apps/desktop/src/main/core/ui/__tests__/Tray.test.ts +518 -0
- package/apps/desktop/src/main/core/ui/__tests__/TrayManager.test.ts +360 -0
- package/apps/desktop/src/main/menus/impls/BaseMenuPlatform.test.ts +49 -0
- package/apps/desktop/src/main/menus/impls/linux.test.ts +552 -0
- package/apps/desktop/src/main/menus/impls/macOS.test.ts +464 -0
- package/apps/desktop/src/main/menus/impls/windows.test.ts +429 -0
- package/apps/desktop/src/main/modules/fileSearch/__tests__/macOS.integration.test.ts +2 -2
- package/apps/desktop/src/main/services/__tests__/fileSearchSrv.test.ts +402 -0
- package/apps/desktop/src/main/utils/__tests__/file-system.test.ts +91 -0
- package/apps/desktop/src/main/utils/__tests__/logger.test.ts +229 -0
- package/apps/desktop/src/preload/electronApi.test.ts +142 -0
- package/apps/desktop/src/preload/invoke.test.ts +145 -0
- package/apps/desktop/src/preload/routeInterceptor.test.ts +374 -0
- package/apps/desktop/src/preload/streamer.test.ts +365 -0
- package/apps/desktop/vitest.config.mts +1 -0
- package/changelog/v1.json +9 -0
- package/locales/ar/marketAuth.json +13 -0
- package/locales/bg-BG/marketAuth.json +13 -0
- package/locales/de-DE/marketAuth.json +13 -0
- package/locales/en-US/marketAuth.json +13 -0
- package/locales/es-ES/marketAuth.json +13 -0
- package/locales/fa-IR/marketAuth.json +13 -0
- package/locales/fr-FR/marketAuth.json +13 -0
- package/locales/it-IT/marketAuth.json +13 -0
- package/locales/ja-JP/marketAuth.json +13 -0
- package/locales/ko-KR/marketAuth.json +13 -0
- package/locales/nl-NL/marketAuth.json +13 -0
- package/locales/pl-PL/marketAuth.json +13 -0
- package/locales/pt-BR/marketAuth.json +13 -0
- package/locales/ru-RU/marketAuth.json +13 -0
- package/locales/tr-TR/marketAuth.json +13 -0
- package/locales/vi-VN/marketAuth.json +13 -0
- package/locales/zh-CN/marketAuth.json +13 -0
- package/locales/zh-TW/marketAuth.json +13 -0
- package/package.json +1 -1
- package/packages/database/src/models/user.ts +2 -0
- package/packages/types/src/discover/mcp.ts +2 -1
- package/packages/types/src/tool/plugin.ts +2 -1
- package/src/app/[variants]/(main)/chat/settings/features/SmartAgentActionButton/MarketPublishButton.tsx +0 -2
- package/src/app/[variants]/(main)/discover/(detail)/mcp/features/Sidebar/ActionButton/index.tsx +33 -7
- package/src/features/PluginStore/McpList/List/Action.tsx +20 -1
- package/src/layout/AuthProvider/MarketAuth/MarketAuthConfirmModal.tsx +158 -0
- package/src/layout/AuthProvider/MarketAuth/MarketAuthProvider.tsx +130 -14
- package/src/libs/mcp/types.ts +8 -0
- package/src/locales/default/marketAuth.ts +13 -0
- package/src/server/routers/lambda/market/index.ts +85 -2
- package/src/server/services/discover/index.ts +45 -4
- package/src/services/discover.ts +1 -1
- package/src/services/mcp.ts +18 -3
- package/src/store/tool/slices/mcpStore/action.test.ts +141 -0
- package/src/store/tool/slices/mcpStore/action.ts +153 -11
|
@@ -0,0 +1,229 @@
|
|
|
1
|
+
import debug from 'debug';
|
|
2
|
+
import electronLog from 'electron-log';
|
|
3
|
+
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
|
|
4
|
+
|
|
5
|
+
import { createLogger } from '../logger';
|
|
6
|
+
|
|
7
|
+
vi.mock('debug');
|
|
8
|
+
vi.mock('electron-log', () => ({
|
|
9
|
+
default: {
|
|
10
|
+
transports: {
|
|
11
|
+
file: { level: 'info' },
|
|
12
|
+
console: { level: 'warn' },
|
|
13
|
+
},
|
|
14
|
+
error: vi.fn(),
|
|
15
|
+
info: vi.fn(),
|
|
16
|
+
verbose: vi.fn(),
|
|
17
|
+
warn: vi.fn(),
|
|
18
|
+
},
|
|
19
|
+
}));
|
|
20
|
+
|
|
21
|
+
describe('logger', () => {
|
|
22
|
+
const mockDebugLogger = vi.fn();
|
|
23
|
+
|
|
24
|
+
beforeEach(() => {
|
|
25
|
+
vi.mocked(debug).mockReturnValue(mockDebugLogger as any);
|
|
26
|
+
vi.clearAllMocks();
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
afterEach(() => {
|
|
30
|
+
delete process.env.NODE_ENV;
|
|
31
|
+
delete process.env.DEBUG_VERBOSE;
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
describe('createLogger', () => {
|
|
35
|
+
it('should create logger with correct namespace', () => {
|
|
36
|
+
const namespace = 'test:logger';
|
|
37
|
+
createLogger(namespace);
|
|
38
|
+
|
|
39
|
+
expect(debug).toHaveBeenCalledWith(namespace);
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
it('should return logger object with all methods', () => {
|
|
43
|
+
const logger = createLogger('test:logger');
|
|
44
|
+
|
|
45
|
+
expect(logger).toHaveProperty('debug');
|
|
46
|
+
expect(logger).toHaveProperty('error');
|
|
47
|
+
expect(logger).toHaveProperty('info');
|
|
48
|
+
expect(logger).toHaveProperty('verbose');
|
|
49
|
+
expect(logger).toHaveProperty('warn');
|
|
50
|
+
expect(typeof logger.debug).toBe('function');
|
|
51
|
+
expect(typeof logger.error).toBe('function');
|
|
52
|
+
expect(typeof logger.info).toBe('function');
|
|
53
|
+
expect(typeof logger.verbose).toBe('function');
|
|
54
|
+
expect(typeof logger.warn).toBe('function');
|
|
55
|
+
});
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
describe('logger.debug', () => {
|
|
59
|
+
it('should call debug logger with message and args', () => {
|
|
60
|
+
const logger = createLogger('test:debug');
|
|
61
|
+
logger.debug('test message', { data: 'value' });
|
|
62
|
+
|
|
63
|
+
expect(mockDebugLogger).toHaveBeenCalledWith('test message', { data: 'value' });
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
it('should handle multiple arguments', () => {
|
|
67
|
+
const logger = createLogger('test:debug');
|
|
68
|
+
logger.debug('message', 'arg1', 'arg2', 'arg3');
|
|
69
|
+
|
|
70
|
+
expect(mockDebugLogger).toHaveBeenCalledWith('message', 'arg1', 'arg2', 'arg3');
|
|
71
|
+
});
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
describe('logger.error', () => {
|
|
75
|
+
it('should use electronLog.error in production', () => {
|
|
76
|
+
process.env.NODE_ENV = 'production';
|
|
77
|
+
const logger = createLogger('test:error');
|
|
78
|
+
logger.error('error message', { error: 'details' });
|
|
79
|
+
|
|
80
|
+
expect(electronLog.error).toHaveBeenCalledWith('error message', { error: 'details' });
|
|
81
|
+
expect(mockDebugLogger).not.toHaveBeenCalled();
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
it('should use console.error in development', () => {
|
|
85
|
+
process.env.NODE_ENV = 'development';
|
|
86
|
+
const consoleErrorSpy = vi.spyOn(console, 'error').mockImplementation(() => {});
|
|
87
|
+
const logger = createLogger('test:error');
|
|
88
|
+
logger.error('error message', { error: 'details' });
|
|
89
|
+
|
|
90
|
+
expect(consoleErrorSpy).toHaveBeenCalledWith('error message', { error: 'details' });
|
|
91
|
+
expect(electronLog.error).not.toHaveBeenCalled();
|
|
92
|
+
|
|
93
|
+
consoleErrorSpy.mockRestore();
|
|
94
|
+
});
|
|
95
|
+
|
|
96
|
+
it('should default to console.error when NODE_ENV is not set', () => {
|
|
97
|
+
const consoleErrorSpy = vi.spyOn(console, 'error').mockImplementation(() => {});
|
|
98
|
+
const logger = createLogger('test:error');
|
|
99
|
+
logger.error('error message');
|
|
100
|
+
|
|
101
|
+
expect(consoleErrorSpy).toHaveBeenCalledWith('error message');
|
|
102
|
+
expect(electronLog.error).not.toHaveBeenCalled();
|
|
103
|
+
|
|
104
|
+
consoleErrorSpy.mockRestore();
|
|
105
|
+
});
|
|
106
|
+
});
|
|
107
|
+
|
|
108
|
+
describe('logger.info', () => {
|
|
109
|
+
it('should use electronLog.info with namespace in production', () => {
|
|
110
|
+
process.env.NODE_ENV = 'production';
|
|
111
|
+
const logger = createLogger('test:info');
|
|
112
|
+
logger.info('info message', { data: 'value' });
|
|
113
|
+
|
|
114
|
+
expect(electronLog.info).toHaveBeenCalledWith('[test:info]', 'info message', {
|
|
115
|
+
data: 'value',
|
|
116
|
+
});
|
|
117
|
+
expect(mockDebugLogger).toHaveBeenCalledWith('INFO: info message', { data: 'value' });
|
|
118
|
+
});
|
|
119
|
+
|
|
120
|
+
it('should use debug logger in development', () => {
|
|
121
|
+
process.env.NODE_ENV = 'development';
|
|
122
|
+
const logger = createLogger('test:info');
|
|
123
|
+
logger.info('info message', { data: 'value' });
|
|
124
|
+
|
|
125
|
+
expect(electronLog.info).not.toHaveBeenCalled();
|
|
126
|
+
expect(mockDebugLogger).toHaveBeenCalledWith('INFO: info message', { data: 'value' });
|
|
127
|
+
});
|
|
128
|
+
|
|
129
|
+
it('should always call debug logger regardless of environment', () => {
|
|
130
|
+
const logger = createLogger('test:info');
|
|
131
|
+
logger.info('info message');
|
|
132
|
+
|
|
133
|
+
expect(mockDebugLogger).toHaveBeenCalledWith('INFO: info message');
|
|
134
|
+
});
|
|
135
|
+
});
|
|
136
|
+
|
|
137
|
+
describe('logger.verbose', () => {
|
|
138
|
+
it('should always call electronLog.verbose', () => {
|
|
139
|
+
const logger = createLogger('test:verbose');
|
|
140
|
+
logger.verbose('verbose message', { data: 'value' });
|
|
141
|
+
|
|
142
|
+
expect(electronLog.verbose).toHaveBeenCalledWith('verbose message', { data: 'value' });
|
|
143
|
+
});
|
|
144
|
+
|
|
145
|
+
it('should call debug logger when DEBUG_VERBOSE is set', () => {
|
|
146
|
+
process.env.DEBUG_VERBOSE = 'true';
|
|
147
|
+
const logger = createLogger('test:verbose');
|
|
148
|
+
logger.verbose('verbose message', { data: 'value' });
|
|
149
|
+
|
|
150
|
+
expect(electronLog.verbose).toHaveBeenCalledWith('verbose message', { data: 'value' });
|
|
151
|
+
expect(mockDebugLogger).toHaveBeenCalledWith('VERBOSE: verbose message', { data: 'value' });
|
|
152
|
+
});
|
|
153
|
+
|
|
154
|
+
it('should not call debug logger when DEBUG_VERBOSE is not set', () => {
|
|
155
|
+
const logger = createLogger('test:verbose');
|
|
156
|
+
logger.verbose('verbose message', { data: 'value' });
|
|
157
|
+
|
|
158
|
+
expect(electronLog.verbose).toHaveBeenCalledWith('verbose message', { data: 'value' });
|
|
159
|
+
expect(mockDebugLogger).not.toHaveBeenCalled();
|
|
160
|
+
});
|
|
161
|
+
});
|
|
162
|
+
|
|
163
|
+
describe('logger.warn', () => {
|
|
164
|
+
it('should use electronLog.warn in production', () => {
|
|
165
|
+
process.env.NODE_ENV = 'production';
|
|
166
|
+
const logger = createLogger('test:warn');
|
|
167
|
+
logger.warn('warn message', { warning: 'details' });
|
|
168
|
+
|
|
169
|
+
expect(electronLog.warn).toHaveBeenCalledWith('warn message', { warning: 'details' });
|
|
170
|
+
expect(mockDebugLogger).toHaveBeenCalledWith('WARN: warn message', { warning: 'details' });
|
|
171
|
+
});
|
|
172
|
+
|
|
173
|
+
it('should not use electronLog.warn in development', () => {
|
|
174
|
+
process.env.NODE_ENV = 'development';
|
|
175
|
+
const logger = createLogger('test:warn');
|
|
176
|
+
logger.warn('warn message');
|
|
177
|
+
|
|
178
|
+
expect(electronLog.warn).not.toHaveBeenCalled();
|
|
179
|
+
expect(mockDebugLogger).toHaveBeenCalledWith('WARN: warn message');
|
|
180
|
+
});
|
|
181
|
+
|
|
182
|
+
it('should always call debug logger regardless of environment', () => {
|
|
183
|
+
const logger = createLogger('test:warn');
|
|
184
|
+
logger.warn('warn message');
|
|
185
|
+
|
|
186
|
+
expect(mockDebugLogger).toHaveBeenCalledWith('WARN: warn message');
|
|
187
|
+
});
|
|
188
|
+
});
|
|
189
|
+
|
|
190
|
+
describe('logger integration', () => {
|
|
191
|
+
it('should handle empty messages', () => {
|
|
192
|
+
const logger = createLogger('test:integration');
|
|
193
|
+
logger.debug('');
|
|
194
|
+
logger.info('');
|
|
195
|
+
logger.warn('');
|
|
196
|
+
|
|
197
|
+
expect(mockDebugLogger).toHaveBeenCalledWith('');
|
|
198
|
+
expect(mockDebugLogger).toHaveBeenCalledWith('INFO: ');
|
|
199
|
+
expect(mockDebugLogger).toHaveBeenCalledWith('WARN: ');
|
|
200
|
+
});
|
|
201
|
+
|
|
202
|
+
it('should handle no additional arguments', () => {
|
|
203
|
+
const logger = createLogger('test:integration');
|
|
204
|
+
logger.debug('message');
|
|
205
|
+
logger.error('message');
|
|
206
|
+
logger.info('message');
|
|
207
|
+
logger.verbose('message');
|
|
208
|
+
logger.warn('message');
|
|
209
|
+
|
|
210
|
+
expect(mockDebugLogger).toHaveBeenCalledWith('message');
|
|
211
|
+
});
|
|
212
|
+
|
|
213
|
+
it('should format messages consistently across different log levels', () => {
|
|
214
|
+
const logger = createLogger('app:test');
|
|
215
|
+
const message = 'test message';
|
|
216
|
+
const args = { key: 'value' };
|
|
217
|
+
|
|
218
|
+
logger.debug(message, args);
|
|
219
|
+
logger.info(message, args);
|
|
220
|
+
logger.warn(message, args);
|
|
221
|
+
logger.verbose(message, args);
|
|
222
|
+
|
|
223
|
+
expect(mockDebugLogger).toHaveBeenCalledWith(message, args);
|
|
224
|
+
expect(mockDebugLogger).toHaveBeenCalledWith(`INFO: ${message}`, args);
|
|
225
|
+
expect(mockDebugLogger).toHaveBeenCalledWith(`WARN: ${message}`, args);
|
|
226
|
+
expect(electronLog.verbose).toHaveBeenCalledWith(message, args);
|
|
227
|
+
});
|
|
228
|
+
});
|
|
229
|
+
});
|
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
import { beforeEach, describe, expect, it, vi } from 'vitest';
|
|
2
|
+
|
|
3
|
+
// Mock electron modules
|
|
4
|
+
const mockElectronAPI = { someAPI: 'mock-electron-api' };
|
|
5
|
+
const mockContextBridgeExposeInMainWorld = vi.fn();
|
|
6
|
+
|
|
7
|
+
vi.mock('electron', () => ({
|
|
8
|
+
contextBridge: {
|
|
9
|
+
exposeInMainWorld: mockContextBridgeExposeInMainWorld,
|
|
10
|
+
},
|
|
11
|
+
}));
|
|
12
|
+
|
|
13
|
+
vi.mock('@electron-toolkit/preload', () => ({
|
|
14
|
+
electronAPI: mockElectronAPI,
|
|
15
|
+
}));
|
|
16
|
+
|
|
17
|
+
// Mock the invoke and streamer modules
|
|
18
|
+
const mockInvoke = vi.fn();
|
|
19
|
+
const mockOnStreamInvoke = vi.fn();
|
|
20
|
+
|
|
21
|
+
vi.mock('./invoke', () => ({
|
|
22
|
+
invoke: mockInvoke,
|
|
23
|
+
}));
|
|
24
|
+
|
|
25
|
+
vi.mock('./streamer', () => ({
|
|
26
|
+
onStreamInvoke: mockOnStreamInvoke,
|
|
27
|
+
}));
|
|
28
|
+
|
|
29
|
+
const { setupElectronApi } = await import('./electronApi');
|
|
30
|
+
|
|
31
|
+
describe('setupElectronApi', () => {
|
|
32
|
+
let consoleErrorSpy: ReturnType<typeof vi.spyOn>;
|
|
33
|
+
|
|
34
|
+
beforeEach(() => {
|
|
35
|
+
vi.clearAllMocks();
|
|
36
|
+
consoleErrorSpy = vi.spyOn(console, 'error').mockImplementation(() => {});
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
it('should expose electron API to main world', () => {
|
|
40
|
+
setupElectronApi();
|
|
41
|
+
|
|
42
|
+
expect(mockContextBridgeExposeInMainWorld).toHaveBeenCalledWith('electron', mockElectronAPI);
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
it('should expose electronAPI with invoke and onStreamInvoke methods', () => {
|
|
46
|
+
setupElectronApi();
|
|
47
|
+
|
|
48
|
+
expect(mockContextBridgeExposeInMainWorld).toHaveBeenCalledWith('electronAPI', {
|
|
49
|
+
invoke: mockInvoke,
|
|
50
|
+
onStreamInvoke: mockOnStreamInvoke,
|
|
51
|
+
});
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
it('should expose both APIs in correct order', () => {
|
|
55
|
+
setupElectronApi();
|
|
56
|
+
|
|
57
|
+
expect(mockContextBridgeExposeInMainWorld).toHaveBeenCalledTimes(2);
|
|
58
|
+
|
|
59
|
+
// First call should be for 'electron'
|
|
60
|
+
expect(mockContextBridgeExposeInMainWorld.mock.calls[0][0]).toBe('electron');
|
|
61
|
+
expect(mockContextBridgeExposeInMainWorld.mock.calls[0][1]).toBe(mockElectronAPI);
|
|
62
|
+
|
|
63
|
+
// Second call should be for 'electronAPI'
|
|
64
|
+
expect(mockContextBridgeExposeInMainWorld.mock.calls[1][0]).toBe('electronAPI');
|
|
65
|
+
expect(mockContextBridgeExposeInMainWorld.mock.calls[1][1]).toEqual({
|
|
66
|
+
invoke: mockInvoke,
|
|
67
|
+
onStreamInvoke: mockOnStreamInvoke,
|
|
68
|
+
});
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
it('should handle errors when exposing electron API fails', () => {
|
|
72
|
+
const error = new Error('Failed to expose electron API');
|
|
73
|
+
mockContextBridgeExposeInMainWorld.mockImplementationOnce(() => {
|
|
74
|
+
throw error;
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
setupElectronApi();
|
|
78
|
+
|
|
79
|
+
expect(consoleErrorSpy).toHaveBeenCalledWith(error);
|
|
80
|
+
// Should still try to expose electronAPI even if first one fails
|
|
81
|
+
expect(mockContextBridgeExposeInMainWorld).toHaveBeenCalledTimes(2);
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
it('should continue execution if exposing electronAPI fails', () => {
|
|
85
|
+
mockContextBridgeExposeInMainWorld
|
|
86
|
+
.mockImplementationOnce(() => {}) // First call succeeds
|
|
87
|
+
.mockImplementationOnce(() => {
|
|
88
|
+
throw new Error('Failed to expose electronAPI');
|
|
89
|
+
}); // Second call fails
|
|
90
|
+
|
|
91
|
+
// The second call throws and is not caught, so it will throw
|
|
92
|
+
// The error handling only wraps the first contextBridge.exposeInMainWorld call
|
|
93
|
+
expect(() => setupElectronApi()).toThrow('Failed to expose electronAPI');
|
|
94
|
+
|
|
95
|
+
expect(mockContextBridgeExposeInMainWorld).toHaveBeenCalledTimes(2);
|
|
96
|
+
});
|
|
97
|
+
|
|
98
|
+
it('should only catch errors for electron API exposure', () => {
|
|
99
|
+
const error = new Error('Context bridge error');
|
|
100
|
+
mockContextBridgeExposeInMainWorld.mockImplementationOnce(() => {
|
|
101
|
+
throw error;
|
|
102
|
+
});
|
|
103
|
+
|
|
104
|
+
setupElectronApi();
|
|
105
|
+
|
|
106
|
+
// Error should be logged, not thrown
|
|
107
|
+
expect(consoleErrorSpy).toHaveBeenCalledWith(error);
|
|
108
|
+
expect(consoleErrorSpy).toHaveBeenCalledTimes(1);
|
|
109
|
+
});
|
|
110
|
+
|
|
111
|
+
it('should expose correct invoke function reference', () => {
|
|
112
|
+
setupElectronApi();
|
|
113
|
+
|
|
114
|
+
const exposedAPI = mockContextBridgeExposeInMainWorld.mock.calls[1][1];
|
|
115
|
+
expect(exposedAPI.invoke).toBe(mockInvoke);
|
|
116
|
+
});
|
|
117
|
+
|
|
118
|
+
it('should expose correct onStreamInvoke function reference', () => {
|
|
119
|
+
setupElectronApi();
|
|
120
|
+
|
|
121
|
+
const exposedAPI = mockContextBridgeExposeInMainWorld.mock.calls[1][1];
|
|
122
|
+
expect(exposedAPI.onStreamInvoke).toBe(mockOnStreamInvoke);
|
|
123
|
+
});
|
|
124
|
+
|
|
125
|
+
it('should not modify the original functions', () => {
|
|
126
|
+
const originalInvoke = mockInvoke;
|
|
127
|
+
const originalOnStreamInvoke = mockOnStreamInvoke;
|
|
128
|
+
|
|
129
|
+
setupElectronApi();
|
|
130
|
+
|
|
131
|
+
expect(mockInvoke).toBe(originalInvoke);
|
|
132
|
+
expect(mockOnStreamInvoke).toBe(originalOnStreamInvoke);
|
|
133
|
+
});
|
|
134
|
+
|
|
135
|
+
it('should be callable multiple times without side effects', () => {
|
|
136
|
+
setupElectronApi();
|
|
137
|
+
setupElectronApi();
|
|
138
|
+
|
|
139
|
+
// Should be called 4 times total (2 per setup call)
|
|
140
|
+
expect(mockContextBridgeExposeInMainWorld).toHaveBeenCalledTimes(4);
|
|
141
|
+
});
|
|
142
|
+
});
|
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
import { ClientDispatchEventKey } from '@lobechat/electron-client-ipc';
|
|
2
|
+
import { beforeEach, describe, expect, it, vi } from 'vitest';
|
|
3
|
+
|
|
4
|
+
// Mock electron module
|
|
5
|
+
const mockIpcRendererInvoke = vi.fn();
|
|
6
|
+
|
|
7
|
+
vi.mock('electron', () => ({
|
|
8
|
+
ipcRenderer: {
|
|
9
|
+
invoke: mockIpcRendererInvoke,
|
|
10
|
+
},
|
|
11
|
+
}));
|
|
12
|
+
|
|
13
|
+
const { invoke } = await import('./invoke');
|
|
14
|
+
|
|
15
|
+
describe('invoke', () => {
|
|
16
|
+
beforeEach(() => {
|
|
17
|
+
vi.clearAllMocks();
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
it('should invoke ipcRenderer with correct event name and no data', async () => {
|
|
21
|
+
const expectedResult = { success: true };
|
|
22
|
+
mockIpcRendererInvoke.mockResolvedValue(expectedResult);
|
|
23
|
+
|
|
24
|
+
const result = await invoke('getAppVersion' as ClientDispatchEventKey);
|
|
25
|
+
|
|
26
|
+
expect(mockIpcRendererInvoke).toHaveBeenCalledWith('getAppVersion');
|
|
27
|
+
expect(mockIpcRendererInvoke).toHaveBeenCalledTimes(1);
|
|
28
|
+
expect(result).toEqual(expectedResult);
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
it('should invoke ipcRenderer with event name and single data parameter', async () => {
|
|
32
|
+
const eventData = { path: '/settings' };
|
|
33
|
+
const expectedResult = { navigated: true };
|
|
34
|
+
mockIpcRendererInvoke.mockResolvedValue(expectedResult);
|
|
35
|
+
|
|
36
|
+
const result = await invoke('interceptRoute' as ClientDispatchEventKey, eventData);
|
|
37
|
+
|
|
38
|
+
expect(mockIpcRendererInvoke).toHaveBeenCalledWith('interceptRoute', eventData);
|
|
39
|
+
expect(mockIpcRendererInvoke).toHaveBeenCalledTimes(1);
|
|
40
|
+
expect(result).toEqual(expectedResult);
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
it('should invoke ipcRenderer with event name and multiple data parameters', async () => {
|
|
44
|
+
const param1 = 'test-param-1';
|
|
45
|
+
const param2 = { value: 42 };
|
|
46
|
+
const param3 = [1, 2, 3];
|
|
47
|
+
const expectedResult = { processed: true };
|
|
48
|
+
mockIpcRendererInvoke.mockResolvedValue(expectedResult);
|
|
49
|
+
|
|
50
|
+
const result = await invoke('someEvent' as ClientDispatchEventKey, param1, param2, param3);
|
|
51
|
+
|
|
52
|
+
expect(mockIpcRendererInvoke).toHaveBeenCalledWith('someEvent', param1, param2, param3);
|
|
53
|
+
expect(mockIpcRendererInvoke).toHaveBeenCalledTimes(1);
|
|
54
|
+
expect(result).toEqual(expectedResult);
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
it('should handle ipcRenderer invoke rejection', async () => {
|
|
58
|
+
const error = new Error('IPC communication failed');
|
|
59
|
+
mockIpcRendererInvoke.mockRejectedValue(error);
|
|
60
|
+
|
|
61
|
+
await expect(invoke('getAppVersion' as ClientDispatchEventKey)).rejects.toThrow(
|
|
62
|
+
'IPC communication failed',
|
|
63
|
+
);
|
|
64
|
+
expect(mockIpcRendererInvoke).toHaveBeenCalledWith('getAppVersion');
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
it('should handle ipcRenderer returning undefined', async () => {
|
|
68
|
+
mockIpcRendererInvoke.mockResolvedValue(undefined);
|
|
69
|
+
|
|
70
|
+
const result = await invoke('someEvent' as ClientDispatchEventKey);
|
|
71
|
+
|
|
72
|
+
expect(mockIpcRendererInvoke).toHaveBeenCalledWith('someEvent');
|
|
73
|
+
expect(result).toBeUndefined();
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
it('should handle ipcRenderer returning null', async () => {
|
|
77
|
+
mockIpcRendererInvoke.mockResolvedValue(null);
|
|
78
|
+
|
|
79
|
+
const result = await invoke('someEvent' as ClientDispatchEventKey);
|
|
80
|
+
|
|
81
|
+
expect(mockIpcRendererInvoke).toHaveBeenCalledWith('someEvent');
|
|
82
|
+
expect(result).toBeNull();
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
it('should preserve complex data structures', async () => {
|
|
86
|
+
const complexData = {
|
|
87
|
+
array: [1, 2, 3],
|
|
88
|
+
nested: {
|
|
89
|
+
bool: true,
|
|
90
|
+
null: null,
|
|
91
|
+
string: 'test',
|
|
92
|
+
undefined: undefined,
|
|
93
|
+
},
|
|
94
|
+
number: 42,
|
|
95
|
+
};
|
|
96
|
+
mockIpcRendererInvoke.mockResolvedValue(complexData);
|
|
97
|
+
|
|
98
|
+
const result = await invoke('getData' as ClientDispatchEventKey);
|
|
99
|
+
|
|
100
|
+
expect(result).toEqual(complexData);
|
|
101
|
+
});
|
|
102
|
+
|
|
103
|
+
it('should maintain type safety with generic return type', async () => {
|
|
104
|
+
interface TestResponse {
|
|
105
|
+
message: string;
|
|
106
|
+
status: number;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
const expectedResponse: TestResponse = { message: 'success', status: 200 };
|
|
110
|
+
mockIpcRendererInvoke.mockResolvedValue(expectedResponse);
|
|
111
|
+
|
|
112
|
+
const result = await invoke('testEvent' as ClientDispatchEventKey);
|
|
113
|
+
|
|
114
|
+
expect(result).toEqual(expectedResponse);
|
|
115
|
+
expect(typeof result.message).toBe('string');
|
|
116
|
+
expect(typeof result.status).toBe('number');
|
|
117
|
+
});
|
|
118
|
+
|
|
119
|
+
it('should handle concurrent invocations correctly', async () => {
|
|
120
|
+
mockIpcRendererInvoke
|
|
121
|
+
.mockResolvedValueOnce({ id: 1 })
|
|
122
|
+
.mockResolvedValueOnce({ id: 2 })
|
|
123
|
+
.mockResolvedValueOnce({ id: 3 });
|
|
124
|
+
|
|
125
|
+
const [result1, result2, result3] = await Promise.all([
|
|
126
|
+
invoke('event1' as ClientDispatchEventKey),
|
|
127
|
+
invoke('event2' as ClientDispatchEventKey),
|
|
128
|
+
invoke('event3' as ClientDispatchEventKey),
|
|
129
|
+
]);
|
|
130
|
+
|
|
131
|
+
expect(result1).toEqual({ id: 1 });
|
|
132
|
+
expect(result2).toEqual({ id: 2 });
|
|
133
|
+
expect(result3).toEqual({ id: 3 });
|
|
134
|
+
expect(mockIpcRendererInvoke).toHaveBeenCalledTimes(3);
|
|
135
|
+
});
|
|
136
|
+
|
|
137
|
+
it('should handle empty string as data parameter', async () => {
|
|
138
|
+
mockIpcRendererInvoke.mockResolvedValue({ received: '' });
|
|
139
|
+
|
|
140
|
+
const result = await invoke('sendData' as ClientDispatchEventKey, '');
|
|
141
|
+
|
|
142
|
+
expect(mockIpcRendererInvoke).toHaveBeenCalledWith('sendData', '');
|
|
143
|
+
expect(result).toEqual({ received: '' });
|
|
144
|
+
});
|
|
145
|
+
});
|