Skip to content

Commit 07d5374

Browse files
authored
🐛 fix: fix desktop upload image on macOS (lobehub#7741)
* fix image * fix * fix lint * fix * add tests * update testss * remove tests * try to fix * try to fix desktop image issue * Revert "try to fix desktop image issue" This reverts commit 8dad8f3.
1 parent 0cda082 commit 07d5374

File tree

13 files changed

+1291
-49
lines changed

13 files changed

+1291
-49
lines changed
Lines changed: 195 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,195 @@
1+
import { beforeEach, describe, expect, it, vi, Mock } from 'vitest';
2+
import { InterceptRouteParams } from '@lobechat/electron-client-ipc';
3+
4+
import type { App } from '@/core/App';
5+
import type { IpcClientEventSender } from '@/types/ipcClientEvent';
6+
import { BrowsersIdentifiers, AppBrowsersIdentifiers } from '@/appBrowsers';
7+
8+
import BrowserWindowsCtr from '../BrowserWindowsCtr';
9+
10+
// 模拟 App 及其依赖项
11+
const mockToggleVisible = vi.fn();
12+
const mockShowSettingsWindowWithTab = vi.fn();
13+
const mockCloseWindow = vi.fn();
14+
const mockMinimizeWindow = vi.fn();
15+
const mockMaximizeWindow = vi.fn();
16+
const mockRetrieveByIdentifier = vi.fn();
17+
const mockGetMainWindow = vi.fn(() => ({
18+
toggleVisible: mockToggleVisible,
19+
}));
20+
const mockShow = vi.fn();
21+
22+
// mock findMatchingRoute and extractSubPath
23+
vi.mock('~common/routes', async () => ({
24+
findMatchingRoute: vi.fn(),
25+
extractSubPath: vi.fn(),
26+
}));
27+
const { findMatchingRoute, extractSubPath } = await import('~common/routes');
28+
29+
const mockApp = {
30+
browserManager: {
31+
getMainWindow: mockGetMainWindow,
32+
showSettingsWindowWithTab: mockShowSettingsWindowWithTab,
33+
closeWindow: mockCloseWindow,
34+
minimizeWindow: mockMinimizeWindow,
35+
maximizeWindow: mockMaximizeWindow,
36+
retrieveByIdentifier: mockRetrieveByIdentifier.mockImplementation((identifier: AppBrowsersIdentifiers | string) => {
37+
if (identifier === BrowsersIdentifiers.settings || identifier === 'some-other-window') {
38+
return { show: mockShow };
39+
}
40+
return { show: mockShow }; // Default mock for other identifiers
41+
}),
42+
},
43+
} as unknown as App;
44+
45+
describe('BrowserWindowsCtr', () => {
46+
let browserWindowsCtr: BrowserWindowsCtr;
47+
48+
beforeEach(() => {
49+
vi.clearAllMocks();
50+
browserWindowsCtr = new BrowserWindowsCtr(mockApp);
51+
});
52+
53+
describe('toggleMainWindow', () => {
54+
it('should get the main window and toggle its visibility', async () => {
55+
await browserWindowsCtr.toggleMainWindow();
56+
expect(mockGetMainWindow).toHaveBeenCalled();
57+
expect(mockToggleVisible).toHaveBeenCalled();
58+
});
59+
});
60+
61+
describe('openSettingsWindow', () => {
62+
it('should show the settings window with the specified tab', async () => {
63+
const tab = 'appearance';
64+
const result = await browserWindowsCtr.openSettingsWindow(tab);
65+
expect(mockShowSettingsWindowWithTab).toHaveBeenCalledWith(tab);
66+
expect(result).toEqual({ success: true });
67+
});
68+
69+
it('should return error if showing settings window fails', async () => {
70+
const errorMessage = 'Failed to show';
71+
mockShowSettingsWindowWithTab.mockRejectedValueOnce(new Error(errorMessage));
72+
const result = await browserWindowsCtr.openSettingsWindow('display');
73+
expect(result).toEqual({ error: errorMessage, success: false });
74+
});
75+
});
76+
77+
const testSenderIdentifierString: string = 'test-window-event-id';
78+
const sender: IpcClientEventSender = {
79+
identifier: testSenderIdentifierString,
80+
};
81+
82+
describe('closeWindow', () => {
83+
it('should close the window with the given sender identifier', () => {
84+
browserWindowsCtr.closeWindow(undefined, sender);
85+
expect(mockCloseWindow).toHaveBeenCalledWith(testSenderIdentifierString);
86+
});
87+
});
88+
89+
describe('minimizeWindow', () => {
90+
it('should minimize the window with the given sender identifier', () => {
91+
browserWindowsCtr.minimizeWindow(undefined, sender);
92+
expect(mockMinimizeWindow).toHaveBeenCalledWith(testSenderIdentifierString);
93+
});
94+
});
95+
96+
describe('maximizeWindow', () => {
97+
it('should maximize the window with the given sender identifier', () => {
98+
browserWindowsCtr.maximizeWindow(undefined, sender);
99+
expect(mockMaximizeWindow).toHaveBeenCalledWith(testSenderIdentifierString);
100+
});
101+
});
102+
103+
describe('interceptRoute', () => {
104+
const baseParams = { source: 'link-click' as const };
105+
106+
it('should not intercept if no matching route is found', async () => {
107+
const params: InterceptRouteParams = { ...baseParams, path: '/unknown/route', url: 'app://host/unknown/route' };
108+
(findMatchingRoute as Mock).mockReturnValue(undefined);
109+
const result = await browserWindowsCtr.interceptRoute(params);
110+
expect(findMatchingRoute).toHaveBeenCalledWith(params.path);
111+
expect(result).toEqual({ intercepted: false, path: params.path, source: params.source });
112+
});
113+
114+
it('should show settings window if matched route target is settings', async () => {
115+
const params: InterceptRouteParams = { ...baseParams, path: '/settings/common', url: 'app://host/settings/common' };
116+
const matchedRoute = { targetWindow: BrowsersIdentifiers.settings, pathPrefix: '/settings' };
117+
const subPath = 'common';
118+
(findMatchingRoute as Mock).mockReturnValue(matchedRoute);
119+
(extractSubPath as Mock).mockReturnValue(subPath);
120+
121+
const result = await browserWindowsCtr.interceptRoute(params);
122+
123+
expect(findMatchingRoute).toHaveBeenCalledWith(params.path);
124+
expect(extractSubPath).toHaveBeenCalledWith(params.path, matchedRoute.pathPrefix);
125+
expect(mockShowSettingsWindowWithTab).toHaveBeenCalledWith(subPath);
126+
expect(result).toEqual({
127+
intercepted: true,
128+
path: params.path,
129+
source: params.source,
130+
subPath,
131+
targetWindow: matchedRoute.targetWindow,
132+
});
133+
expect(mockShow).not.toHaveBeenCalled();
134+
});
135+
136+
it('should open target window if matched route target is not settings', async () => {
137+
const params: InterceptRouteParams = { ...baseParams, path: '/other/page', url: 'app://host/other/page' };
138+
const targetWindowIdentifier = 'some-other-window' as AppBrowsersIdentifiers;
139+
const matchedRoute = { targetWindow: targetWindowIdentifier, pathPrefix: '/other' };
140+
(findMatchingRoute as Mock).mockReturnValue(matchedRoute);
141+
142+
const result = await browserWindowsCtr.interceptRoute(params);
143+
144+
expect(findMatchingRoute).toHaveBeenCalledWith(params.path);
145+
expect(mockRetrieveByIdentifier).toHaveBeenCalledWith(targetWindowIdentifier);
146+
expect(mockShow).toHaveBeenCalled();
147+
expect(result).toEqual({
148+
intercepted: true,
149+
path: params.path,
150+
source: params.source,
151+
targetWindow: matchedRoute.targetWindow,
152+
});
153+
expect(mockShowSettingsWindowWithTab).not.toHaveBeenCalled();
154+
});
155+
156+
it('should return error if processing route interception fails for settings', async () => {
157+
const params: InterceptRouteParams = { ...baseParams, path: '/settings/general', url: 'app://host/settings/general' };
158+
const matchedRoute = { targetWindow: BrowsersIdentifiers.settings, pathPrefix: '/settings' };
159+
const subPath = 'general';
160+
const errorMessage = 'Processing error for settings';
161+
(findMatchingRoute as Mock).mockReturnValue(matchedRoute);
162+
(extractSubPath as Mock).mockReturnValue(subPath);
163+
mockShowSettingsWindowWithTab.mockRejectedValueOnce(new Error(errorMessage));
164+
165+
const result = await browserWindowsCtr.interceptRoute(params);
166+
167+
expect(result).toEqual({
168+
error: errorMessage,
169+
intercepted: false,
170+
path: params.path,
171+
source: params.source,
172+
});
173+
});
174+
175+
it('should return error if processing route interception fails for other window', async () => {
176+
const params: InterceptRouteParams = { ...baseParams, path: '/another/custom', url: 'app://host/another/custom' };
177+
const targetWindowIdentifier = 'another-custom-window' as AppBrowsersIdentifiers;
178+
const matchedRoute = { targetWindow: targetWindowIdentifier, pathPrefix: '/another' };
179+
const errorMessage = 'Processing error for other window';
180+
(findMatchingRoute as Mock).mockReturnValue(matchedRoute);
181+
mockRetrieveByIdentifier.mockImplementationOnce(() => {
182+
throw new Error(errorMessage);
183+
});
184+
185+
const result = await browserWindowsCtr.interceptRoute(params);
186+
187+
expect(result).toEqual({
188+
error: errorMessage,
189+
intercepted: false,
190+
path: params.path,
191+
source: params.source,
192+
});
193+
});
194+
});
195+
});
Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
import { beforeEach, describe, expect, it, vi } from 'vitest';
2+
3+
import type { App } from '@/core/App';
4+
5+
import DevtoolsCtr from '../DevtoolsCtr';
6+
7+
// 模拟 App 及其依赖项
8+
const mockShow = vi.fn();
9+
const mockRetrieveByIdentifier = vi.fn(() => ({
10+
show: mockShow,
11+
}));
12+
13+
// 创建一个足够模拟 App 行为的对象,以满足 DevtoolsCtr 的需求
14+
const mockApp = {
15+
browserManager: {
16+
retrieveByIdentifier: mockRetrieveByIdentifier,
17+
},
18+
// 如果 DevtoolsCtr 或其基类在构造或方法调用中使用了 app 的其他属性/方法,
19+
// 也需要在这里添加相应的模拟
20+
} as unknown as App; // 使用类型断言,因为我们只模拟了部分 App 结构
21+
22+
describe('DevtoolsCtr', () => {
23+
let devtoolsCtr: DevtoolsCtr;
24+
25+
beforeEach(() => {
26+
vi.clearAllMocks(); // 只清除 vi.fn() 创建的模拟函数的记录,不影响 IoCContainer 状态
27+
28+
// 实例化 DevtoolsCtr。
29+
// 它将继承自真实的 ControllerModule。
30+
// 其 @ipcClientEvent 装饰器会执行并与真实的 IoCContainer 交互。
31+
devtoolsCtr = new DevtoolsCtr(mockApp);
32+
});
33+
34+
describe('openDevtools', () => {
35+
it('should retrieve the devtools browser window using app.browserManager and show it', async () => {
36+
await devtoolsCtr.openDevtools();
37+
38+
// 验证 browserManager.retrieveByIdentifier 是否以 'devtools' 参数被调用
39+
expect(mockRetrieveByIdentifier).toHaveBeenCalledWith('devtools');
40+
// 验证返回对象的 show 方法是否被调用
41+
expect(mockShow).toHaveBeenCalled();
42+
});
43+
});
44+
});
Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
import { beforeEach, describe, expect, it, vi } from 'vitest';
2+
3+
import type { App } from '@/core/App';
4+
5+
import MenuController from '../MenuCtr';
6+
7+
// 模拟 App 及其依赖项
8+
const mockRefreshMenus = vi.fn();
9+
const mockShowContextMenu = vi.fn();
10+
const mockRebuildAppMenu = vi.fn();
11+
12+
const mockApp = {
13+
menuManager: {
14+
refreshMenus: mockRefreshMenus,
15+
showContextMenu: mockShowContextMenu,
16+
rebuildAppMenu: mockRebuildAppMenu,
17+
},
18+
} as unknown as App;
19+
20+
describe('MenuController', () => {
21+
let menuController: MenuController;
22+
23+
beforeEach(() => {
24+
vi.clearAllMocks();
25+
menuController = new MenuController(mockApp);
26+
});
27+
28+
describe('refreshAppMenu', () => {
29+
it('should call menuManager.refreshMenus', () => {
30+
// 模拟返回值
31+
mockRefreshMenus.mockReturnValueOnce(true);
32+
33+
const result = menuController.refreshAppMenu();
34+
35+
expect(mockRefreshMenus).toHaveBeenCalled();
36+
expect(result).toBe(true);
37+
});
38+
});
39+
40+
describe('showContextMenu', () => {
41+
it('should call menuManager.showContextMenu with type only', () => {
42+
const menuType = 'chat';
43+
mockShowContextMenu.mockReturnValueOnce({ shown: true });
44+
45+
const result = menuController.showContextMenu(menuType);
46+
47+
expect(mockShowContextMenu).toHaveBeenCalledWith(menuType, undefined);
48+
expect(result).toEqual({ shown: true });
49+
});
50+
51+
it('should call menuManager.showContextMenu with type and data', () => {
52+
const menuType = 'file';
53+
const menuData = { fileId: '123', filePath: '/path/to/file.txt' };
54+
mockShowContextMenu.mockReturnValueOnce({ shown: true });
55+
56+
const result = menuController.showContextMenu(menuType, menuData);
57+
58+
expect(mockShowContextMenu).toHaveBeenCalledWith(menuType, menuData);
59+
expect(result).toEqual({ shown: true });
60+
});
61+
});
62+
63+
describe('setDevMenuVisibility', () => {
64+
it('should call menuManager.rebuildAppMenu with showDevItems true', () => {
65+
mockRebuildAppMenu.mockReturnValueOnce(true);
66+
67+
const result = menuController.setDevMenuVisibility(true);
68+
69+
expect(mockRebuildAppMenu).toHaveBeenCalledWith({ showDevItems: true });
70+
expect(result).toBe(true);
71+
});
72+
73+
it('should call menuManager.rebuildAppMenu with showDevItems false', () => {
74+
mockRebuildAppMenu.mockReturnValueOnce(true);
75+
76+
const result = menuController.setDevMenuVisibility(false);
77+
78+
expect(mockRebuildAppMenu).toHaveBeenCalledWith({ showDevItems: false });
79+
expect(result).toBe(true);
80+
});
81+
});
82+
});
Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
import { beforeEach, describe, expect, it, vi } from 'vitest';
2+
3+
import type { App } from '@/core/App';
4+
5+
import ShortcutController from '../ShortcutCtr';
6+
7+
// 模拟 App 及其依赖项
8+
const mockGetShortcutsConfig = vi.fn().mockReturnValue({
9+
toggleMainWindow: 'CommandOrControl+Shift+L',
10+
openSettings: 'CommandOrControl+,'
11+
});
12+
const mockUpdateShortcutConfig = vi.fn().mockImplementation((id, accelerator) => {
13+
// 简单模拟更新成功
14+
return true;
15+
});
16+
17+
const mockApp = {
18+
shortcutManager: {
19+
getShortcutsConfig: mockGetShortcutsConfig,
20+
updateShortcutConfig: mockUpdateShortcutConfig,
21+
},
22+
} as unknown as App;
23+
24+
describe('ShortcutController', () => {
25+
let shortcutController: ShortcutController;
26+
27+
beforeEach(() => {
28+
vi.clearAllMocks();
29+
shortcutController = new ShortcutController(mockApp);
30+
});
31+
32+
describe('getShortcutsConfig', () => {
33+
it('should return shortcuts config from shortcutManager', () => {
34+
const result = shortcutController.getShortcutsConfig();
35+
36+
expect(mockGetShortcutsConfig).toHaveBeenCalled();
37+
expect(result).toEqual({
38+
toggleMainWindow: 'CommandOrControl+Shift+L',
39+
openSettings: 'CommandOrControl+,'
40+
});
41+
});
42+
});
43+
44+
describe('updateShortcutConfig', () => {
45+
it('should call shortcutManager.updateShortcutConfig with correct parameters', () => {
46+
const id = 'toggleMainWindow';
47+
const accelerator = 'CommandOrControl+Alt+L';
48+
49+
const result = shortcutController.updateShortcutConfig(id, accelerator);
50+
51+
expect(mockUpdateShortcutConfig).toHaveBeenCalledWith(id, accelerator);
52+
expect(result).toBe(true);
53+
});
54+
55+
it('should return the result from shortcutManager.updateShortcutConfig', () => {
56+
// 模拟更新失败的情况
57+
mockUpdateShortcutConfig.mockReturnValueOnce(false);
58+
59+
const result = shortcutController.updateShortcutConfig('invalidKey', 'invalid+combo');
60+
61+
expect(result).toBe(false);
62+
});
63+
});
64+
});

0 commit comments

Comments
 (0)