@lobehub/lobehub 2.0.0-next.142 → 2.0.0-next.144
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 +50 -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 +18 -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/migrations/0054_better_auth_two_factor.sql +2 -0
- package/packages/database/src/core/migrations.json +1 -1
- package/packages/database/src/models/user.ts +27 -5
- package/packages/types/src/discover/mcp.ts +2 -1
- package/packages/types/src/tool/plugin.ts +2 -1
- package/scripts/migrateServerDB/errorHint.js +26 -0
- package/scripts/migrateServerDB/index.ts +5 -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,360 @@
|
|
|
1
|
+
import { nativeTheme } from 'electron';
|
|
2
|
+
import { beforeEach, describe, expect, it, vi } from 'vitest';
|
|
3
|
+
|
|
4
|
+
import type { App } from '../../App';
|
|
5
|
+
import { Tray } from '../Tray';
|
|
6
|
+
import { TrayManager } from '../TrayManager';
|
|
7
|
+
|
|
8
|
+
// Mock electron modules
|
|
9
|
+
vi.mock('electron', () => ({
|
|
10
|
+
nativeTheme: {
|
|
11
|
+
shouldUseDarkColors: false,
|
|
12
|
+
},
|
|
13
|
+
}));
|
|
14
|
+
|
|
15
|
+
// Mock logger
|
|
16
|
+
vi.mock('@/utils/logger', () => ({
|
|
17
|
+
createLogger: () => ({
|
|
18
|
+
debug: vi.fn(),
|
|
19
|
+
info: vi.fn(),
|
|
20
|
+
warn: vi.fn(),
|
|
21
|
+
error: vi.fn(),
|
|
22
|
+
}),
|
|
23
|
+
}));
|
|
24
|
+
|
|
25
|
+
// Mock environment constants
|
|
26
|
+
vi.mock('@/const/env', () => ({
|
|
27
|
+
isMac: true,
|
|
28
|
+
}));
|
|
29
|
+
|
|
30
|
+
// Mock package.json
|
|
31
|
+
vi.mock('@/../../package.json', () => ({
|
|
32
|
+
name: 'test-app',
|
|
33
|
+
}));
|
|
34
|
+
|
|
35
|
+
// Mock Tray class
|
|
36
|
+
vi.mock('../Tray', () => ({
|
|
37
|
+
Tray: vi.fn(),
|
|
38
|
+
}));
|
|
39
|
+
|
|
40
|
+
describe('TrayManager', () => {
|
|
41
|
+
let trayManager: TrayManager;
|
|
42
|
+
let mockApp: App;
|
|
43
|
+
let mockTray: any;
|
|
44
|
+
|
|
45
|
+
beforeEach(() => {
|
|
46
|
+
vi.clearAllMocks();
|
|
47
|
+
|
|
48
|
+
// Mock Tray instance
|
|
49
|
+
mockTray = {
|
|
50
|
+
identifier: 'main',
|
|
51
|
+
broadcast: vi.fn(),
|
|
52
|
+
destroy: vi.fn(),
|
|
53
|
+
updateIcon: vi.fn(),
|
|
54
|
+
updateTooltip: vi.fn(),
|
|
55
|
+
};
|
|
56
|
+
|
|
57
|
+
// Mock App
|
|
58
|
+
mockApp = {} as unknown as App;
|
|
59
|
+
|
|
60
|
+
// Mock Tray constructor
|
|
61
|
+
vi.mocked(Tray).mockImplementation(() => mockTray);
|
|
62
|
+
|
|
63
|
+
trayManager = new TrayManager(mockApp);
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
describe('constructor', () => {
|
|
67
|
+
it('should initialize TrayManager with app instance', () => {
|
|
68
|
+
expect(trayManager.app).toBe(mockApp);
|
|
69
|
+
expect(trayManager.trays).toBeInstanceOf(Map);
|
|
70
|
+
expect(trayManager.trays.size).toBe(0);
|
|
71
|
+
});
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
describe('initializeTrays', () => {
|
|
75
|
+
it('should initialize main tray', () => {
|
|
76
|
+
trayManager.initializeTrays();
|
|
77
|
+
|
|
78
|
+
expect(Tray).toHaveBeenCalled();
|
|
79
|
+
expect(trayManager.trays.has('main')).toBe(true);
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
it('should call initializeMainTray', () => {
|
|
83
|
+
const spy = vi.spyOn(trayManager, 'initializeMainTray');
|
|
84
|
+
|
|
85
|
+
trayManager.initializeTrays();
|
|
86
|
+
|
|
87
|
+
expect(spy).toHaveBeenCalled();
|
|
88
|
+
});
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
describe('initializeMainTray', () => {
|
|
92
|
+
it('should create main tray with dark icon on macOS when dark mode is enabled', () => {
|
|
93
|
+
Object.defineProperty(nativeTheme, 'shouldUseDarkColors', {
|
|
94
|
+
value: true,
|
|
95
|
+
writable: true,
|
|
96
|
+
configurable: true,
|
|
97
|
+
});
|
|
98
|
+
|
|
99
|
+
const result = trayManager.initializeMainTray();
|
|
100
|
+
|
|
101
|
+
expect(Tray).toHaveBeenCalledWith(
|
|
102
|
+
expect.objectContaining({
|
|
103
|
+
iconPath: 'tray-dark.png',
|
|
104
|
+
identifier: 'main',
|
|
105
|
+
tooltip: 'test-app',
|
|
106
|
+
}),
|
|
107
|
+
mockApp,
|
|
108
|
+
);
|
|
109
|
+
expect(result).toBe(mockTray);
|
|
110
|
+
});
|
|
111
|
+
|
|
112
|
+
it('should create main tray with light icon on macOS when light mode is enabled', () => {
|
|
113
|
+
Object.defineProperty(nativeTheme, 'shouldUseDarkColors', {
|
|
114
|
+
value: false,
|
|
115
|
+
writable: true,
|
|
116
|
+
configurable: true,
|
|
117
|
+
});
|
|
118
|
+
|
|
119
|
+
trayManager.initializeMainTray();
|
|
120
|
+
|
|
121
|
+
expect(Tray).toHaveBeenCalledWith(
|
|
122
|
+
expect.objectContaining({
|
|
123
|
+
iconPath: 'tray-light.png',
|
|
124
|
+
identifier: 'main',
|
|
125
|
+
tooltip: 'test-app',
|
|
126
|
+
}),
|
|
127
|
+
mockApp,
|
|
128
|
+
);
|
|
129
|
+
});
|
|
130
|
+
|
|
131
|
+
it('should add created tray to trays map', () => {
|
|
132
|
+
trayManager.initializeMainTray();
|
|
133
|
+
|
|
134
|
+
expect(trayManager.trays.has('main')).toBe(true);
|
|
135
|
+
expect(trayManager.trays.get('main')).toBe(mockTray);
|
|
136
|
+
});
|
|
137
|
+
|
|
138
|
+
it('should return existing tray if already initialized', () => {
|
|
139
|
+
const firstTray = trayManager.initializeMainTray();
|
|
140
|
+
vi.clearAllMocks();
|
|
141
|
+
|
|
142
|
+
const secondTray = trayManager.initializeMainTray();
|
|
143
|
+
|
|
144
|
+
expect(firstTray).toBe(secondTray);
|
|
145
|
+
expect(Tray).not.toHaveBeenCalled();
|
|
146
|
+
});
|
|
147
|
+
});
|
|
148
|
+
|
|
149
|
+
describe('getMainTray', () => {
|
|
150
|
+
it('should return main tray when it exists', () => {
|
|
151
|
+
trayManager.initializeMainTray();
|
|
152
|
+
|
|
153
|
+
const result = trayManager.getMainTray();
|
|
154
|
+
|
|
155
|
+
expect(result).toBe(mockTray);
|
|
156
|
+
});
|
|
157
|
+
|
|
158
|
+
it('should return undefined when main tray does not exist', () => {
|
|
159
|
+
const result = trayManager.getMainTray();
|
|
160
|
+
|
|
161
|
+
expect(result).toBeUndefined();
|
|
162
|
+
});
|
|
163
|
+
});
|
|
164
|
+
|
|
165
|
+
describe('retrieveByIdentifier', () => {
|
|
166
|
+
it('should return tray by identifier when it exists', () => {
|
|
167
|
+
trayManager.initializeMainTray();
|
|
168
|
+
|
|
169
|
+
const result = trayManager.retrieveByIdentifier('main');
|
|
170
|
+
|
|
171
|
+
expect(result).toBe(mockTray);
|
|
172
|
+
});
|
|
173
|
+
|
|
174
|
+
it('should return undefined when tray with identifier does not exist', () => {
|
|
175
|
+
const result = trayManager.retrieveByIdentifier('main');
|
|
176
|
+
|
|
177
|
+
expect(result).toBeUndefined();
|
|
178
|
+
});
|
|
179
|
+
});
|
|
180
|
+
|
|
181
|
+
describe('broadcastToAllTrays', () => {
|
|
182
|
+
it('should broadcast event to all trays', () => {
|
|
183
|
+
trayManager.initializeMainTray();
|
|
184
|
+
|
|
185
|
+
const event = 'test-event' as any;
|
|
186
|
+
const data = { test: 'data' };
|
|
187
|
+
|
|
188
|
+
trayManager.broadcastToAllTrays(event, data);
|
|
189
|
+
|
|
190
|
+
expect(mockTray.broadcast).toHaveBeenCalledWith(event, data);
|
|
191
|
+
});
|
|
192
|
+
|
|
193
|
+
it('should handle multiple trays', () => {
|
|
194
|
+
// Create main tray
|
|
195
|
+
trayManager.initializeMainTray();
|
|
196
|
+
|
|
197
|
+
// Mock another tray
|
|
198
|
+
const mockTray2 = {
|
|
199
|
+
identifier: 'secondary',
|
|
200
|
+
broadcast: vi.fn(),
|
|
201
|
+
destroy: vi.fn(),
|
|
202
|
+
};
|
|
203
|
+
trayManager.trays.set('secondary' as any, mockTray2 as any);
|
|
204
|
+
|
|
205
|
+
const event = 'test-event' as any;
|
|
206
|
+
const data = { test: 'data' };
|
|
207
|
+
|
|
208
|
+
trayManager.broadcastToAllTrays(event, data);
|
|
209
|
+
|
|
210
|
+
expect(mockTray.broadcast).toHaveBeenCalledWith(event, data);
|
|
211
|
+
expect(mockTray2.broadcast).toHaveBeenCalledWith(event, data);
|
|
212
|
+
});
|
|
213
|
+
|
|
214
|
+
it('should not throw when no trays exist', () => {
|
|
215
|
+
const event = 'test-event' as any;
|
|
216
|
+
const data = { test: 'data' };
|
|
217
|
+
|
|
218
|
+
expect(() => trayManager.broadcastToAllTrays(event, data)).not.toThrow();
|
|
219
|
+
});
|
|
220
|
+
});
|
|
221
|
+
|
|
222
|
+
describe('broadcastToTray', () => {
|
|
223
|
+
it('should broadcast event to specific tray', () => {
|
|
224
|
+
trayManager.initializeMainTray();
|
|
225
|
+
|
|
226
|
+
const event = 'test-event' as any;
|
|
227
|
+
const data = { test: 'data' };
|
|
228
|
+
|
|
229
|
+
trayManager.broadcastToTray('main', event, data);
|
|
230
|
+
|
|
231
|
+
expect(mockTray.broadcast).toHaveBeenCalledWith(event, data);
|
|
232
|
+
});
|
|
233
|
+
|
|
234
|
+
it('should not throw when tray does not exist', () => {
|
|
235
|
+
const event = 'test-event' as any;
|
|
236
|
+
const data = { test: 'data' };
|
|
237
|
+
|
|
238
|
+
expect(() => trayManager.broadcastToTray('main', event, data)).not.toThrow();
|
|
239
|
+
});
|
|
240
|
+
|
|
241
|
+
it('should not call broadcast when tray does not exist', () => {
|
|
242
|
+
const event = 'test-event' as any;
|
|
243
|
+
const data = { test: 'data' };
|
|
244
|
+
|
|
245
|
+
trayManager.broadcastToTray('main', event, data);
|
|
246
|
+
|
|
247
|
+
expect(mockTray.broadcast).not.toHaveBeenCalled();
|
|
248
|
+
});
|
|
249
|
+
});
|
|
250
|
+
|
|
251
|
+
describe('destroyAll', () => {
|
|
252
|
+
it('should destroy all trays', () => {
|
|
253
|
+
trayManager.initializeMainTray();
|
|
254
|
+
|
|
255
|
+
trayManager.destroyAll();
|
|
256
|
+
|
|
257
|
+
expect(mockTray.destroy).toHaveBeenCalled();
|
|
258
|
+
expect(trayManager.trays.size).toBe(0);
|
|
259
|
+
});
|
|
260
|
+
|
|
261
|
+
it('should destroy multiple trays', () => {
|
|
262
|
+
// Create main tray
|
|
263
|
+
trayManager.initializeMainTray();
|
|
264
|
+
|
|
265
|
+
// Mock another tray
|
|
266
|
+
const mockTray2 = {
|
|
267
|
+
identifier: 'secondary',
|
|
268
|
+
broadcast: vi.fn(),
|
|
269
|
+
destroy: vi.fn(),
|
|
270
|
+
};
|
|
271
|
+
trayManager.trays.set('secondary' as any, mockTray2 as any);
|
|
272
|
+
|
|
273
|
+
trayManager.destroyAll();
|
|
274
|
+
|
|
275
|
+
expect(mockTray.destroy).toHaveBeenCalled();
|
|
276
|
+
expect(mockTray2.destroy).toHaveBeenCalled();
|
|
277
|
+
expect(trayManager.trays.size).toBe(0);
|
|
278
|
+
});
|
|
279
|
+
|
|
280
|
+
it('should clear trays map after destroying', () => {
|
|
281
|
+
trayManager.initializeMainTray();
|
|
282
|
+
|
|
283
|
+
trayManager.destroyAll();
|
|
284
|
+
|
|
285
|
+
expect(trayManager.trays.size).toBe(0);
|
|
286
|
+
});
|
|
287
|
+
|
|
288
|
+
it('should not throw when no trays exist', () => {
|
|
289
|
+
expect(() => trayManager.destroyAll()).not.toThrow();
|
|
290
|
+
});
|
|
291
|
+
});
|
|
292
|
+
|
|
293
|
+
describe('retrieveOrInitialize (private method)', () => {
|
|
294
|
+
it('should create new tray when it does not exist', () => {
|
|
295
|
+
const options = {
|
|
296
|
+
iconPath: 'test.png',
|
|
297
|
+
identifier: 'main',
|
|
298
|
+
tooltip: 'Test',
|
|
299
|
+
};
|
|
300
|
+
|
|
301
|
+
const result = trayManager['retrieveOrInitialize'](options);
|
|
302
|
+
|
|
303
|
+
expect(Tray).toHaveBeenCalledWith(options, mockApp);
|
|
304
|
+
expect(result).toBe(mockTray);
|
|
305
|
+
expect(trayManager.trays.has('main')).toBe(true);
|
|
306
|
+
});
|
|
307
|
+
|
|
308
|
+
it('should return existing tray when it already exists', () => {
|
|
309
|
+
const options = {
|
|
310
|
+
iconPath: 'test.png',
|
|
311
|
+
identifier: 'main',
|
|
312
|
+
tooltip: 'Test',
|
|
313
|
+
};
|
|
314
|
+
|
|
315
|
+
const firstResult = trayManager['retrieveOrInitialize'](options);
|
|
316
|
+
vi.clearAllMocks();
|
|
317
|
+
|
|
318
|
+
const secondResult = trayManager['retrieveOrInitialize'](options);
|
|
319
|
+
|
|
320
|
+
expect(firstResult).toBe(secondResult);
|
|
321
|
+
expect(Tray).not.toHaveBeenCalled();
|
|
322
|
+
});
|
|
323
|
+
});
|
|
324
|
+
|
|
325
|
+
describe('integration tests', () => {
|
|
326
|
+
it('should handle complete tray manager lifecycle', () => {
|
|
327
|
+
// Initialize trays
|
|
328
|
+
trayManager.initializeTrays();
|
|
329
|
+
expect(trayManager.trays.size).toBe(1);
|
|
330
|
+
|
|
331
|
+
// Get main tray
|
|
332
|
+
const mainTray = trayManager.getMainTray();
|
|
333
|
+
expect(mainTray).toBeDefined();
|
|
334
|
+
|
|
335
|
+
// Broadcast to all
|
|
336
|
+
trayManager.broadcastToAllTrays('test-event' as any, { data: 'test' });
|
|
337
|
+
expect(mockTray.broadcast).toHaveBeenCalled();
|
|
338
|
+
|
|
339
|
+
// Broadcast to specific tray
|
|
340
|
+
vi.clearAllMocks();
|
|
341
|
+
trayManager.broadcastToTray('main', 'test-event' as any, { data: 'test' });
|
|
342
|
+
expect(mockTray.broadcast).toHaveBeenCalled();
|
|
343
|
+
|
|
344
|
+
// Destroy all
|
|
345
|
+
trayManager.destroyAll();
|
|
346
|
+
expect(mockTray.destroy).toHaveBeenCalled();
|
|
347
|
+
expect(trayManager.trays.size).toBe(0);
|
|
348
|
+
});
|
|
349
|
+
|
|
350
|
+
it('should handle multiple initialization calls safely', () => {
|
|
351
|
+
trayManager.initializeTrays();
|
|
352
|
+
trayManager.initializeTrays();
|
|
353
|
+
trayManager.initializeTrays();
|
|
354
|
+
|
|
355
|
+
// Should only create one tray instance
|
|
356
|
+
expect(Tray).toHaveBeenCalledTimes(1);
|
|
357
|
+
expect(trayManager.trays.size).toBe(1);
|
|
358
|
+
});
|
|
359
|
+
});
|
|
360
|
+
});
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import { beforeEach, describe, expect, it, vi } from 'vitest';
|
|
2
|
+
|
|
3
|
+
import type { App } from '@/core/App';
|
|
4
|
+
|
|
5
|
+
import { BaseMenuPlatform } from './BaseMenuPlatform';
|
|
6
|
+
|
|
7
|
+
// Create a concrete implementation for testing
|
|
8
|
+
class TestMenuPlatform extends BaseMenuPlatform {}
|
|
9
|
+
|
|
10
|
+
// Mock App instance
|
|
11
|
+
const mockApp = {
|
|
12
|
+
i18n: {
|
|
13
|
+
ns: vi.fn(),
|
|
14
|
+
},
|
|
15
|
+
browserManager: {
|
|
16
|
+
getMainWindow: vi.fn(),
|
|
17
|
+
showMainWindow: vi.fn(),
|
|
18
|
+
retrieveByIdentifier: vi.fn(),
|
|
19
|
+
},
|
|
20
|
+
updaterManager: {
|
|
21
|
+
checkForUpdates: vi.fn(),
|
|
22
|
+
},
|
|
23
|
+
menuManager: {
|
|
24
|
+
rebuildAppMenu: vi.fn(),
|
|
25
|
+
},
|
|
26
|
+
storeManager: {
|
|
27
|
+
openInEditor: vi.fn(),
|
|
28
|
+
},
|
|
29
|
+
} as unknown as App;
|
|
30
|
+
|
|
31
|
+
describe('BaseMenuPlatform', () => {
|
|
32
|
+
let menuPlatform: TestMenuPlatform;
|
|
33
|
+
|
|
34
|
+
beforeEach(() => {
|
|
35
|
+
vi.clearAllMocks();
|
|
36
|
+
menuPlatform = new TestMenuPlatform(mockApp);
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
describe('constructor', () => {
|
|
40
|
+
it('should initialize with app instance', () => {
|
|
41
|
+
expect(menuPlatform['app']).toBe(mockApp);
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
it('should store app reference for subclasses', () => {
|
|
45
|
+
const anotherInstance = new TestMenuPlatform(mockApp);
|
|
46
|
+
expect(anotherInstance['app']).toBe(mockApp);
|
|
47
|
+
});
|
|
48
|
+
});
|
|
49
|
+
});
|