测试:抽出全仓共享的 Tauri 运行时替身模块
- 新增 tests/tauriRuntimeFake.ts,作为唯一的 window.__TAURI__ 安装点与类型转换点,并提供 createTauriEventBridge 与 resetTauriRuntime - appSurface/harness.ts 改为从该模块导入并转出 installTauriRuntime / createTauriEventBridge,既有 ./harness 导入路径不变 - pickProjectFromLauncher 不再直接写 window.__TAURI__,改走 installTauriRuntime 保留原有 core/event
This commit is contained in:
@@ -15,6 +15,10 @@ import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
|
||||
|
||||
import { resetLlmModelCatalogCacheForTest } from '../../src/services/llmModelCatalog';
|
||||
import { useLauncherHomeDraftStore } from '../../src/view/home/useHomeDraftStore';
|
||||
import {
|
||||
createTauriEventBridge,
|
||||
installTauriRuntime,
|
||||
} from '../tauriRuntimeFake';
|
||||
|
||||
const nativeClipboardMock = vi.hoisted(() => ({
|
||||
text: '',
|
||||
@@ -200,7 +204,7 @@ function pickProjectFromLauncher(
|
||||
if (!tauri || !invoke) {
|
||||
throw new Error('Tauri invoke is not configured');
|
||||
}
|
||||
window.__TAURI__ = {
|
||||
installTauriRuntime({
|
||||
...tauri,
|
||||
core: {
|
||||
...tauri.core,
|
||||
@@ -210,7 +214,7 @@ function pickProjectFromLauncher(
|
||||
? projectPath
|
||||
: invoke(command, args)) as unknown as typeof invoke,
|
||||
},
|
||||
};
|
||||
});
|
||||
fireEvent.click(screen.getByRole('button', { name: action }));
|
||||
}
|
||||
|
||||
@@ -938,53 +942,10 @@ function createProjectChatRuntimeHarness({
|
||||
/**
|
||||
* 安装测试用 Tauri 替身。
|
||||
*
|
||||
* `vi.fn` 与各夹具的 invoke / listen 是固定签名,装不进 `window.__TAURI__` 声明的泛型签名
|
||||
* (两者都是泛型函数),这里把唯一的装入点收在 harness,整体显式转换,行为与直接赋值一致。
|
||||
*
|
||||
* 参数用「可调用」下界而不是 `unknown`:非函数的误装(字符串、undefined)会在编译期被挡下,
|
||||
* 同时不与各夹具更具体的 mock 签名冲突;`core` / `event` 都可缺省,只装 invoke 的用例
|
||||
* 不需要伪造 event。
|
||||
* 定义已收进 `tests/tauriRuntimeFake.ts`(全仓唯一安装点),这里只转出,供 appSurface 各
|
||||
* suite 沿用既有的 `./harness` 导入路径。
|
||||
*/
|
||||
export function installTauriRuntime(runtime: {
|
||||
core?: {
|
||||
invoke?: (
|
||||
command: string,
|
||||
args?: Record<string, unknown>,
|
||||
) => Promise<unknown>;
|
||||
};
|
||||
event?: {
|
||||
listen?: (
|
||||
event: string,
|
||||
handler: (event: { payload: unknown }) => void,
|
||||
) => Promise<() => void>;
|
||||
};
|
||||
}) {
|
||||
window.__TAURI__ = runtime as unknown as Window['__TAURI__'];
|
||||
}
|
||||
|
||||
/**
|
||||
* 测试用 Tauri 事件注册表:只登记 `event.listen` 的回调并按事件名派发。
|
||||
*
|
||||
* 不碰 `window.__TAURI__`、不做类型转换;安装由 `installTauriRuntime` 负责,需要事件桥的
|
||||
* 用例把 `bridge.listen` 交给它即可。
|
||||
*/
|
||||
export function createTauriEventBridge() {
|
||||
const listeners = new Map<string, (event: { payload: unknown }) => void>();
|
||||
return {
|
||||
listen: async (
|
||||
event: string,
|
||||
handler: (event: { payload: unknown }) => void,
|
||||
) => {
|
||||
listeners.set(event, handler);
|
||||
return () => {
|
||||
listeners.delete(event);
|
||||
};
|
||||
},
|
||||
emit: (event: string, payload: unknown) => {
|
||||
listeners.get(event)?.({ payload });
|
||||
},
|
||||
};
|
||||
}
|
||||
export { createTauriEventBridge, installTauriRuntime };
|
||||
|
||||
export function installResizeObserverStub() {
|
||||
let observerCount = 0;
|
||||
|
||||
@@ -0,0 +1,59 @@
|
||||
/**
|
||||
* 测试用 Tauri 全局桥替身:全仓唯一的 `window.__TAURI__` 安装点与类型转换点。
|
||||
*
|
||||
* `Window['__TAURI__']` 声明的是泛型签名(`core.invoke` / `event.listen` 都是泛型函数),
|
||||
* 而各套件的替身是 `vi.fn` 或具体签名的 mock,二者结构上不兼容。若在每个用例里直接赋值,
|
||||
* 就得反复写 `as never` / `as unknown as`,类型检查形同虚设;这里把转换收敛到一处,
|
||||
* 调用方只声明自己真正装了哪些能力。
|
||||
*
|
||||
* 参数用「可调用」下界而不是 `unknown`:非函数的误装(字符串、`undefined`)会在编译期被挡下,
|
||||
* 同时不与各夹具更具体的 mock 签名冲突;`core` / `event` 都可缺省。
|
||||
*/
|
||||
|
||||
export type TauriRuntimeInstall = {
|
||||
core?: {
|
||||
invoke?: (
|
||||
command: string,
|
||||
args?: Record<string, unknown>,
|
||||
) => Promise<unknown>;
|
||||
};
|
||||
event?: {
|
||||
listen?: (
|
||||
event: string,
|
||||
handler: (event: { payload: unknown }) => void,
|
||||
) => Promise<() => void>;
|
||||
};
|
||||
};
|
||||
|
||||
export function installTauriRuntime(runtime: TauriRuntimeInstall) {
|
||||
window.__TAURI__ = runtime as unknown as Window['__TAURI__'];
|
||||
}
|
||||
|
||||
/** 清掉替身,供 `afterEach` / `beforeEach` 复位,等价于历史里的 `window.__TAURI__ = undefined`。 */
|
||||
export function resetTauriRuntime() {
|
||||
window.__TAURI__ = undefined;
|
||||
}
|
||||
|
||||
/**
|
||||
* 测试用 Tauri 事件注册表:只登记 `event.listen` 的回调并按事件名派发。
|
||||
*
|
||||
* 不碰 `window.__TAURI__`、不做类型转换;安装由 `installTauriRuntime` 负责,需要事件桥的
|
||||
* 用例把 `bridge.listen` 交给它即可。
|
||||
*/
|
||||
export function createTauriEventBridge() {
|
||||
const listeners = new Map<string, (event: { payload: unknown }) => void>();
|
||||
return {
|
||||
listen: async (
|
||||
event: string,
|
||||
handler: (event: { payload: unknown }) => void,
|
||||
) => {
|
||||
listeners.set(event, handler);
|
||||
return () => {
|
||||
listeners.delete(event);
|
||||
};
|
||||
},
|
||||
emit: (event: string, payload: unknown) => {
|
||||
listeners.get(event)?.({ payload });
|
||||
},
|
||||
};
|
||||
}
|
||||
Reference in New Issue
Block a user