From a77d58b90160c7a645669d33675e36326eca2a08 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=8E=8B=E5=BE=B7=E5=AE=87?= Date: Sat, 3 Oct 2026 19:41:21 +0800 Subject: [PATCH] =?UTF-8?q?=E6=B5=8B=E8=AF=95=EF=BC=9A=E6=8A=BD=E5=87=BA?= =?UTF-8?q?=E5=85=A8=E4=BB=93=E5=85=B1=E4=BA=AB=E7=9A=84=20Tauri=20?= =?UTF-8?q?=E8=BF=90=E8=A1=8C=E6=97=B6=E6=9B=BF=E8=BA=AB=E6=A8=A1=E5=9D=97?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 新增 tests/tauriRuntimeFake.ts,作为唯一的 window.__TAURI__ 安装点与类型转换点,并提供 createTauriEventBridge 与 resetTauriRuntime - appSurface/harness.ts 改为从该模块导入并转出 installTauriRuntime / createTauriEventBridge,既有 ./harness 导入路径不变 - pickProjectFromLauncher 不再直接写 window.__TAURI__,改走 installTauriRuntime 保留原有 core/event --- .../tests/appSurface/harness.ts | 57 +++--------------- .../tests/tauriRuntimeFake.ts | 59 +++++++++++++++++++ 2 files changed, 68 insertions(+), 48 deletions(-) create mode 100644 apps/ai-game-creator-shell/tests/tauriRuntimeFake.ts diff --git a/apps/ai-game-creator-shell/tests/appSurface/harness.ts b/apps/ai-game-creator-shell/tests/appSurface/harness.ts index 43d3f3e14..62066c263 100644 --- a/apps/ai-game-creator-shell/tests/appSurface/harness.ts +++ b/apps/ai-game-creator-shell/tests/appSurface/harness.ts @@ -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, - ) => Promise; - }; - 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 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; diff --git a/apps/ai-game-creator-shell/tests/tauriRuntimeFake.ts b/apps/ai-game-creator-shell/tests/tauriRuntimeFake.ts new file mode 100644 index 000000000..5a2e35f44 --- /dev/null +++ b/apps/ai-game-creator-shell/tests/tauriRuntimeFake.ts @@ -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, + ) => Promise; + }; + 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 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 }); + }, + }; +}