qwen-code/packages/web-shell/client/utils/externalOpen.test.ts
易良 5c1d77a357
fix(desktop): open external URL artifacts and markdown links (#9069)
* fix(desktop): open external URL artifacts and markdown links

The Web Shell relied on the webview's implicit target="_blank" /
window.open handling for every external link. In the packaged desktop
shell that path silently drops failed new-window requests, so link
artifacts (type: link, storage: external_url) and markdown links in
assistant messages appeared styled but dead, with no error feedback.

Add an explicit open_external_url command to the Tauri shell (validated
against http/https/mailto, opened via the OS default browser) and route
external clicks through it when the Web Shell detects the desktop
bridge. The artifact details panel gains an "Open link" action next to
the Location URL, and failures surface as error toasts instead of
silent no-ops. Plain browsers keep native anchor behavior.

Fixes #9060; follows up on #8593 for the Tauri shell.

* test(web-shell): keep real ToastHost exports in App mock

The App test suite replaces ToastHost with a stub; after adding
TOAST_REQUEST_EVENT/requestToast to the module, the partial mock broke
every App test. Spread importActual so new exports stay available.

* fix(desktop): declare external URL permission

* fix(desktop): authorize remote external URL opens

* fix(desktop): normalize external opener URLs

* test(web-shell): pin desktop link click handling

* fix(desktop): route modified URL clicks
2026-08-13 15:11:44 +00:00

63 lines
2.3 KiB
TypeScript

/**
* @vitest-environment jsdom
*/
import { afterEach, describe, expect, it, vi } from 'vitest';
import {
isDesktopShell,
isExternalOpenUrl,
openExternalUrl,
} from './externalOpen';
type TauriWindow = { __TAURI__?: { core?: { invoke?: unknown } } };
describe('externalOpen', () => {
afterEach(() => {
delete (window as TauriWindow).__TAURI__;
vi.restoreAllMocks();
});
it('detects the desktop shell through the global Tauri bridge', () => {
expect(isDesktopShell()).toBe(false);
(window as TauriWindow).__TAURI__ = { core: { invoke: vi.fn() } };
expect(isDesktopShell()).toBe(true);
});
it('routes opens through the Tauri opener plugin in desktop', async () => {
const invoke = vi.fn().mockResolvedValue(undefined);
(window as TauriWindow).__TAURI__ = { core: { invoke } };
await openExternalUrl('https://github.com/QwenLM/qwen-code/issues/9060');
expect(invoke).toHaveBeenCalledWith('plugin:opener|open_url', {
url: 'https://github.com/QwenLM/qwen-code/issues/9060',
});
});
it('normalizes schemes for the Tauri allowlist', async () => {
const invoke = vi.fn().mockResolvedValue(undefined);
(window as TauriWindow).__TAURI__ = { core: { invoke } };
await openExternalUrl('HTTPS://github.com/QwenLM/qwen-code/issues/9060');
expect(invoke).toHaveBeenCalledWith('plugin:opener|open_url', {
url: 'https://github.com/QwenLM/qwen-code/issues/9060',
});
});
it('recognizes only opener-supported external URLs', () => {
expect(isExternalOpenUrl('https://example.com')).toBe(true);
expect(isExternalOpenUrl('MAILTO:test@example.com')).toBe(true);
expect(isExternalOpenUrl('#section')).toBe(false);
expect(isExternalOpenUrl('/relative')).toBe(false);
});
it('propagates desktop command failures so callers can toast', async () => {
const invoke = vi.fn().mockRejectedValue(new Error('command not allowed'));
(window as TauriWindow).__TAURI__ = { core: { invoke } };
await expect(openExternalUrl('https://example.com')).rejects.toThrow(
'command not allowed',
);
});
it('leaves plain-browser opening to native anchors', async () => {
await expect(openExternalUrl('https://example.com')).rejects.toThrow(
'desktop URL opener is unavailable',
);
});
});