feat: implement browser open functionality and related tests

This commit is contained in:
duanshuwen
2026-04-23 19:27:21 +08:00
parent c9617a3777
commit 979fb0a0f6
7 changed files with 567 additions and 18 deletions

View File

@@ -0,0 +1,144 @@
// @vitest-environment node
import { beforeEach, describe, expect, it, vi } from 'vitest';
const mocks = vi.hoisted(() => ({
appendMessage: vi.fn(),
setActiveRun: vi.fn(),
clearActiveRun: vi.fn(),
extractBrowserOpenIntent: vi.fn(),
openUrlInBrowser: vi.fn(),
appendTranscriptLine: vi.fn(),
logger: {
error: vi.fn(),
},
}));
vi.mock('../electron/gateway/session-store', () => ({
sessionStore: {
appendMessage: mocks.appendMessage,
setActiveRun: mocks.setActiveRun,
clearActiveRun: mocks.clearActiveRun,
},
}));
vi.mock('@electron/service/browser-open-service', () => ({
extractBrowserOpenIntent: mocks.extractBrowserOpenIntent,
openUrlInBrowser: mocks.openUrlInBrowser,
}));
vi.mock('@electron/utils/token-usage-writer', () => ({
appendTranscriptLine: mocks.appendTranscriptLine,
}));
vi.mock('@electron/service/logger', () => ({
default: mocks.logger,
}));
function flushAsyncTasks(): Promise<void> {
return new Promise((resolve) => {
setTimeout(resolve, 0);
});
}
describe('gateway browser shortcut', () => {
beforeEach(() => {
vi.resetModules();
vi.clearAllMocks();
mocks.extractBrowserOpenIntent.mockImplementation((text: string) => (
text === '打开 http://www.baidu.com'
? { url: 'http://www.baidu.com/' }
: null
));
});
it('starts a browser-open run and emits a final assistant message on success', async () => {
mocks.openUrlInBrowser.mockResolvedValue({
url: 'http://www.baidu.com/',
pageUrl: 'http://www.baidu.com/',
title: '百度一下,你就知道',
});
const { maybeHandleBrowserOpenMessage } = await import('../electron/gateway/browser-shortcut');
const broadcast = vi.fn();
const handled = maybeHandleBrowserOpenMessage(
'agent:test:main',
'run-1',
{ role: 'user', content: '打开 http://www.baidu.com' },
broadcast,
);
expect(handled).toBe(true);
expect(mocks.setActiveRun).toHaveBeenCalledWith(
'agent:test:main',
'run-1',
expect.any(AbortController),
);
await flushAsyncTasks();
expect(mocks.openUrlInBrowser).toHaveBeenCalledWith(
'http://www.baidu.com/',
expect.objectContaining({ signal: expect.any(AbortSignal) }),
);
expect(mocks.appendMessage).toHaveBeenCalledWith(
'agent:test:main',
expect.objectContaining({
role: 'assistant',
content: '已为你打开 http://www.baidu.com/(百度一下,你就知道)',
}),
);
expect(broadcast).toHaveBeenCalledWith(expect.objectContaining({
type: 'chat:final',
sessionKey: 'agent:test:main',
runId: 'run-1',
}));
});
it('emits a failure assistant message when browser open fails', async () => {
mocks.openUrlInBrowser.mockRejectedValue(new Error('No browser context available'));
const { maybeHandleBrowserOpenMessage } = await import('../electron/gateway/browser-shortcut');
const broadcast = vi.fn();
const handled = maybeHandleBrowserOpenMessage(
'agent:test:main',
'run-2',
{ role: 'user', content: '打开 http://www.baidu.com' },
broadcast,
);
expect(handled).toBe(true);
await flushAsyncTasks();
expect(mocks.appendMessage).toHaveBeenCalledWith(
'agent:test:main',
expect.objectContaining({
role: 'assistant',
content: '打开失败No browser context available',
}),
);
expect(broadcast).toHaveBeenCalledWith(expect.objectContaining({
type: 'chat:final',
runId: 'run-2',
}));
});
it('returns false when the user message is not an explicit browser-open command', async () => {
mocks.extractBrowserOpenIntent.mockReturnValue(null);
const { maybeHandleBrowserOpenMessage } = await import('../electron/gateway/browser-shortcut');
expect(
maybeHandleBrowserOpenMessage(
'agent:test:main',
'run-3',
{ role: 'user', content: '帮我总结一下百度首页' },
vi.fn(),
),
).toBe(false);
expect(mocks.openUrlInBrowser).not.toHaveBeenCalled();
});
});