From 25c5ecdf6e3f2e70dbd5c42d8e45b73f161fa18d 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 20:19:45 +0800 Subject: [PATCH] =?UTF-8?q?=E6=B5=8B=E8=AF=95=EF=BC=9A=E5=85=A8=E4=BB=93?= =?UTF-8?q?=E6=B5=8B=E8=AF=95=E7=BB=9F=E4=B8=80=20Tauri=20=E8=BF=90?= =?UTF-8?q?=E8=A1=8C=E6=97=B6=E6=9B=BF=E8=BA=AB=E5=AE=89=E8=A3=85=E7=82=B9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 78 处内联 window.__TAURI__ 赋值改为 installTauriRuntime,覆盖 appSurface 之外的 37 个测试文件 - 31 处 delete window.__TAURI__ / window.__TAURI__ = undefined 复位改为 resetTauriRuntime - 删除 recentProjectsHook、projectCreationDirectory、projectResourceLiveIntegration、useProjectResourceCardPreviews、resourceVersionReplacementModel、unityProjectOpen 里的本地 asTauriInvoke / asTauriListen 适配器与相关类型别名 - designProjectRestore 的 setup 参数由 ReturnType 收窄为具体 invoke 签名 - 迁移后仅 tests/tauriRuntimeFake.ts 直接读写 window.__TAURI__ --- .../tests/accountHost.test.ts | 5 +- .../tests/appSurface/auth.suite.ts | 3 +- .../tests/appSurface/harness.ts | 5 +- .../appSurface/project-conversation.suite.ts | 3 +- .../tests/authFailureReporting.test.tsx | 5 +- .../tests/chatComposerAttachmentCap.test.tsx | 5 +- .../tests/clientAnalytics.test.tsx | 5 +- .../tests/clientAuthErrorCarrier.test.ts | 5 +- .../tests/clientAuthHost.test.ts | 7 +- .../tests/cocosProjectOpen.test.tsx | 5 +- .../tests/customLlmSettings.test.tsx | 5 +- .../tests/designProjectRestore.test.tsx | 9 ++- .../tests/designWorkspaceDebug.test.tsx | 9 ++- .../directThreadChatSubscription.test.tsx | 3 +- .../tests/errorReporting.test.ts | 3 +- .../tests/gamePublishFeedback.test.tsx | 9 ++- .../tests/homeWebPreflight.test.tsx | 5 +- .../tests/launcherNoticeShellWiring.test.tsx | 17 +++-- .../tests/previewActivation.test.tsx | 9 ++- .../tests/projectCreationDirectory.test.ts | 48 +++--------- .../projectResourceLiveIntegration.test.tsx | 20 ++--- .../tests/projectSnapshotWorkspace.test.tsx | 5 +- .../tests/recentProjectsHook.test.tsx | 58 ++++----------- .../tests/referenceSourceProviders.test.ts | 15 ++-- .../resourceBatchTagsIntegration.test.tsx | 9 ++- .../resourceCanvasChatReferenceDrop.test.tsx | 5 +- ...urceCanvasGenerationHostLifecycle.test.tsx | 3 +- .../resourceCanvasGenerationLanding.test.tsx | 5 +- ...nvasGenerationTasksSidebarDismiss.test.tsx | 3 +- .../tests/resourceCanvasManualLayout.test.tsx | 9 ++- ...esourceCardPreviewAlphaBackground.test.tsx | 3 +- .../tests/resourceJsonCanvas.test.tsx | 5 +- .../tests/resourceReferenceInput.test.tsx | 9 ++- .../tests/resourceTagStatsRefresh.test.tsx | 9 ++- .../tests/resourceVersionReplacement.test.tsx | 3 +- .../resourceVersionReplacementModel.test.ts | 14 +--- .../tests/runPreviewBrowserOpen.test.tsx | 5 +- .../tests/runPreviewFullscreen.test.tsx | 5 +- .../tests/tauriRuntimeFake.ts | 4 +- .../tests/unityProjectOpen.test.tsx | 16 +--- .../useProjectResourceCanvasLayout.test.ts | 5 +- .../useProjectResourceCardPreviews.test.ts | 73 ++++++++----------- .../workspaceLauncherManifestMerge.test.tsx | 5 +- .../tests/workspaceWindowSync.test.tsx | 3 +- 44 files changed, 201 insertions(+), 255 deletions(-) diff --git a/apps/ai-game-creator-shell/tests/accountHost.test.ts b/apps/ai-game-creator-shell/tests/accountHost.test.ts index 7912089d3..7b9a93d73 100644 --- a/apps/ai-game-creator-shell/tests/accountHost.test.ts +++ b/apps/ai-game-creator-shell/tests/accountHost.test.ts @@ -14,9 +14,9 @@ const platformSession = vi.hoisted(() => ({ })); // 与仓库其它 App 内调用方同口径:通过 `window.__TAURI__.core.invoke` 解析原生桥。 -window.__TAURI__ = { +installTauriRuntime({ core: { invoke: (...args: unknown[]) => invoke(...args) }, -} as never; +}); vi.mock('../src/services/platformSession', () => ({ currentPlatformSessionGeneration: () => platformSession.generation, requestPlatformSessionRefresh: (...args: unknown[]) => @@ -31,6 +31,7 @@ import { getClientProfileWalletLedger, redeemClientProfileRewardCode, } from '../src/services/accountHost'; +import { installTauriRuntime } from './tauriRuntimeFake'; beforeEach(() => { invoke.mockReset(); diff --git a/apps/ai-game-creator-shell/tests/appSurface/auth.suite.ts b/apps/ai-game-creator-shell/tests/appSurface/auth.suite.ts index 7ed4d0845..c1690feb9 100644 --- a/apps/ai-game-creator-shell/tests/appSurface/auth.suite.ts +++ b/apps/ai-game-creator-shell/tests/appSurface/auth.suite.ts @@ -17,6 +17,7 @@ import { it, React, render, + resetTauriRuntime, screen, testAuthUser, vi, @@ -92,7 +93,7 @@ function captureWindowRejections() { export function registerAuthTests() { afterEach(() => { resetPlatformSessionForTests(); - delete window.__TAURI__; + resetTauriRuntime(); }); it('throws a startup timeout through to unhandledrejection and leaves the loading state', async () => { diff --git a/apps/ai-game-creator-shell/tests/appSurface/harness.ts b/apps/ai-game-creator-shell/tests/appSurface/harness.ts index 62066c263..63ba99d30 100644 --- a/apps/ai-game-creator-shell/tests/appSurface/harness.ts +++ b/apps/ai-game-creator-shell/tests/appSurface/harness.ts @@ -18,6 +18,7 @@ import { useLauncherHomeDraftStore } from '../../src/view/home/useHomeDraftStore import { createTauriEventBridge, installTauriRuntime, + resetTauriRuntime, } from '../tauriRuntimeFake'; const nativeClipboardMock = vi.hoisted(() => ({ @@ -945,7 +946,7 @@ function createProjectChatRuntimeHarness({ * 定义已收进 `tests/tauriRuntimeFake.ts`(全仓唯一安装点),这里只转出,供 appSurface 各 * suite 沿用既有的 `./harness` 导入路径。 */ -export { createTauriEventBridge, installTauriRuntime }; +export { createTauriEventBridge, installTauriRuntime, resetTauriRuntime }; export function installResizeObserverStub() { let observerCount = 0; @@ -1014,7 +1015,7 @@ afterEach(() => { window.history.pushState({}, '', '/'); window.localStorage.clear(); window.sessionStorage.clear(); - delete window.__TAURI__; + resetTauriRuntime(); vi.restoreAllMocks(); if (originalCreateObjectUrl) { diff --git a/apps/ai-game-creator-shell/tests/appSurface/project-conversation.suite.ts b/apps/ai-game-creator-shell/tests/appSurface/project-conversation.suite.ts index 1b13530fd..e0d3ae2d2 100644 --- a/apps/ai-game-creator-shell/tests/appSurface/project-conversation.suite.ts +++ b/apps/ai-game-creator-shell/tests/appSurface/project-conversation.suite.ts @@ -14,6 +14,7 @@ import { it, mockRoleAgentReply, renderAppAt, + resetTauriRuntime, roleAgentMockReply, screen, vi, @@ -691,7 +692,7 @@ export function registerProjectConversationTests() { fireEvent.click(screen.getByRole('button', { name: /拆解创作方向/ })); const input = await screen.findByLabelText('Agent 对话内容'); fireEvent.change(input, { target: { value: '确认运行环境提示' } }); - delete window.__TAURI__; + resetTauriRuntime(); fireEvent.submit(input.closest('form') as HTMLFormElement); expect(await screen.findByText('需要在 Tauri App 内运行')).not.toBeNull(); diff --git a/apps/ai-game-creator-shell/tests/authFailureReporting.test.tsx b/apps/ai-game-creator-shell/tests/authFailureReporting.test.tsx index 5e82a2d42..7d4b6d79e 100644 --- a/apps/ai-game-creator-shell/tests/authFailureReporting.test.tsx +++ b/apps/ai-game-creator-shell/tests/authFailureReporting.test.tsx @@ -28,6 +28,7 @@ import { invoke } from '@tauri-apps/api/core'; import { AuthenticatedClient } from '../src/app/AuthenticatedClient'; import { resetPlatformSessionForTests } from '../src/services/platformSession'; +import { installTauriRuntime, resetTauriRuntime } from './tauriRuntimeFake'; import { installUnhandledRejectionBridge } from './unhandledRejectionBridge'; type AuthInvoke = ( @@ -42,7 +43,7 @@ function reportCalls() { } function renderLogin(invokeAuth: AuthInvoke) { - window.__TAURI__ = { core: { invoke: invokeAuth } } as never; + installTauriRuntime({ core: { invoke: invokeAuth } }); render( // React 19 的 createElement 的 children 形参只接受 ReactNode,渲染函数子节点改编进 props。 createElement(AuthenticatedClient, { @@ -68,7 +69,7 @@ describe('认证失败的上报判据', () => { afterEach(() => { cleanup(); resetPlatformSessionForTests(); - delete window.__TAURI__; + resetTauriRuntime(); vi.mocked(invoke).mockClear(); }); diff --git a/apps/ai-game-creator-shell/tests/chatComposerAttachmentCap.test.tsx b/apps/ai-game-creator-shell/tests/chatComposerAttachmentCap.test.tsx index 13c633906..c67af4f15 100644 --- a/apps/ai-game-creator-shell/tests/chatComposerAttachmentCap.test.tsx +++ b/apps/ai-game-creator-shell/tests/chatComposerAttachmentCap.test.tsx @@ -7,6 +7,7 @@ import { useDirectProjectChatController, } from '../src/view/project-development/chat/controller/useDirectProjectChatController'; import type { DirectCodexTurnAttachment } from '../src/view/project-development/chat/conversation/directCodexTurnAttachments'; +import { installTauriRuntime, resetTauriRuntime } from './tauriRuntimeFake'; // jsdom 的 Blob/File 没实现 `arrayBuffer`,而附件导入链路要按字节读文件。 if (typeof Blob.prototype.arrayBuffer !== 'function') { @@ -25,7 +26,7 @@ function files(count: number, prefix: string) { afterEach(() => { cleanup(); vi.restoreAllMocks(); - window.__TAURI__ = undefined; + resetTauriRuntime(); }); describe('聊天输入盒的附件上限', () => { @@ -38,7 +39,7 @@ describe('聊天输入盒的附件上限', () => { const fileName = (args as { fileName?: string }).fileName ?? 'file'; return { localPath: `assets/upload-${(uploadSeq += 1)}-${fileName}` }; }); - window.__TAURI__ = { core: { invoke: invoke as never } }; + installTauriRuntime({ core: { invoke } }); const { result } = renderHook(() => useDirectProjectChatController({ diff --git a/apps/ai-game-creator-shell/tests/clientAnalytics.test.tsx b/apps/ai-game-creator-shell/tests/clientAnalytics.test.tsx index 3d362c71a..9568be6b7 100644 --- a/apps/ai-game-creator-shell/tests/clientAnalytics.test.tsx +++ b/apps/ai-game-creator-shell/tests/clientAnalytics.test.tsx @@ -10,6 +10,7 @@ import { beginProjectOpenAnalytics, beginUiSaveAnalytics, } from '../src/services/clientAnalytics'; +import { installTauriRuntime, resetTauriRuntime } from './tauriRuntimeFake'; const context = { route: { user_id: 'user-a', destination_origin: 'https://a.example' }, @@ -27,7 +28,7 @@ function deferred() { } afterEach(() => { cleanup(); - delete window.__TAURI__; + resetTauriRuntime(); vi.restoreAllMocks(); }); @@ -54,7 +55,7 @@ function mount( return null; }, ); - window.__TAURI__ = { core: { invoke } } as unknown as typeof window.__TAURI__; + installTauriRuntime({ core: { invoke } }); const hook = renderHook( () => useHomeProjectCreation({ diff --git a/apps/ai-game-creator-shell/tests/clientAuthErrorCarrier.test.ts b/apps/ai-game-creator-shell/tests/clientAuthErrorCarrier.test.ts index d8b0b27a2..d3a43aa58 100644 --- a/apps/ai-game-creator-shell/tests/clientAuthErrorCarrier.test.ts +++ b/apps/ai-game-creator-shell/tests/clientAuthErrorCarrier.test.ts @@ -24,6 +24,7 @@ import type { RuntimeSessionInstallFailed } from '../src/services/generated/Runt import type { SendCodeRejected } from '../src/services/generated/SendCodeRejected'; import type { ServerAddressRejected } from '../src/services/generated/ServerAddressRejected'; import type { UnexpectedRejection } from '../src/services/generated/UnexpectedRejection'; +import { installTauriRuntime, resetTauriRuntime } from './tauriRuntimeFake'; function expectNever(value: never): void { void value; @@ -127,7 +128,7 @@ function variantMessage(error: ClientAuthError): string { } function installInvoke(handler: () => Promise) { - window.__TAURI__ = { core: { invoke: vi.fn(handler) } } as never; + installTauriRuntime({ core: { invoke: vi.fn(handler) } }); } describe('认证命令失败的 JS 侧载体', () => { @@ -150,7 +151,7 @@ describe('认证命令失败的 JS 侧载体', () => { }); it('认证桥未安装时保持原样抛出,不包装成命令失败', async () => { - delete window.__TAURI__; + resetTauriRuntime(); await expect( loginClientWithPassword( diff --git a/apps/ai-game-creator-shell/tests/clientAuthHost.test.ts b/apps/ai-game-creator-shell/tests/clientAuthHost.test.ts index 6c6393676..4bea6f174 100644 --- a/apps/ai-game-creator-shell/tests/clientAuthHost.test.ts +++ b/apps/ai-game-creator-shell/tests/clientAuthHost.test.ts @@ -18,6 +18,7 @@ import { refreshClientAuthSession, sendClientPhoneLoginCode, } from '../src/services/clientAuth'; +import { installTauriRuntime, resetTauriRuntime } from './tauriRuntimeFake'; const testUser = { id: 'user-1', @@ -35,9 +36,9 @@ const testUser = { beforeEach(() => { invoke.mockReset(); - window.__TAURI__ = { + installTauriRuntime({ core: { invoke: (...args: unknown[]) => invoke(...args) }, - } as never; + }); }); test('手机号归一化只保留可提交的纯号码', () => { @@ -144,7 +145,7 @@ test('续期结果区分成功、失效与身份变化', async () => { }); test('没有原生宿主时登录能力明确失败关闭', async () => { - delete window.__TAURI__; + resetTauriRuntime(); await expect(readClientAuthState()).rejects.toThrow( '需要在 Tauri App 内登录', ); diff --git a/apps/ai-game-creator-shell/tests/cocosProjectOpen.test.tsx b/apps/ai-game-creator-shell/tests/cocosProjectOpen.test.tsx index 24a13e0ac..d1f52a7ac 100644 --- a/apps/ai-game-creator-shell/tests/cocosProjectOpen.test.tsx +++ b/apps/ai-game-creator-shell/tests/cocosProjectOpen.test.tsx @@ -4,9 +4,10 @@ import { afterEach, expect, test, vi } from 'vitest'; import { createGameCreationAppManifest } from '../../../packages/shared/src/contracts/gameCreationApp'; import { useHomeProjectCreation } from '../src/features/app-shell/useHomeProjectCreation'; +import { installTauriRuntime, resetTauriRuntime } from './tauriRuntimeFake'; afterEach(() => { - delete window.__TAURI__; + resetTauriRuntime(); }); test('打开 Cocos 目录走导入入口,不创建 Web 脚手架或发送初始消息', async () => { @@ -39,7 +40,7 @@ test('打开 Cocos 目录走导入入口,不创建 Web 脚手架或发送初 throw new Error(`意外调用:${command}`); }); // TauriInvoke 是泛型函数签名,vi.fn 的具体返回类型无法直接赋给它,这里只在装配处桥接。 - window.__TAURI__ = { core: { invoke } } as unknown as typeof window.__TAURI__; + installTauriRuntime({ core: { invoke } }); const setLauncherView = vi.fn(); const { result } = renderHook(() => useHomeProjectCreation({ diff --git a/apps/ai-game-creator-shell/tests/customLlmSettings.test.tsx b/apps/ai-game-creator-shell/tests/customLlmSettings.test.tsx index 3a14cdc6a..d4c2d3b09 100644 --- a/apps/ai-game-creator-shell/tests/customLlmSettings.test.tsx +++ b/apps/ai-game-creator-shell/tests/customLlmSettings.test.tsx @@ -18,6 +18,7 @@ import type { } from '../src/app/types'; import { CustomLlmSettings } from '../src/features/runtime-config/CustomLlmSettings'; import { RuntimeConfigDialog } from '../src/features/runtime-config/RuntimeConfigDialog'; +import { installTauriRuntime, resetTauriRuntime } from './tauriRuntimeFake'; const invoke = vi.fn(); const llm = { @@ -34,11 +35,11 @@ function Harness() { beforeEach(() => { invoke.mockReset(); - window.__TAURI__ = { core: { invoke } }; + installTauriRuntime({ core: { invoke } }); }); afterEach(() => { cleanup(); - delete window.__TAURI__; + resetTauriRuntime(); }); test('reads endpoint models, filters candidates, and previews only checked models', async () => { diff --git a/apps/ai-game-creator-shell/tests/designProjectRestore.test.tsx b/apps/ai-game-creator-shell/tests/designProjectRestore.test.tsx index 369301f23..c5c5f6878 100644 --- a/apps/ai-game-creator-shell/tests/designProjectRestore.test.tsx +++ b/apps/ai-game-creator-shell/tests/designProjectRestore.test.tsx @@ -4,17 +4,20 @@ import { afterEach, expect, it, vi } from 'vitest'; import { createGameCreationAppManifest } from '../../../packages/shared/src/contracts/gameCreationApp'; import { useHomeProjectCreation } from '../src/features/app-shell/useHomeProjectCreation'; +import { installTauriRuntime, resetTauriRuntime } from './tauriRuntimeFake'; afterEach(() => { cleanup(); - delete window.__TAURI__; + resetTauriRuntime(); }); const projectPath = 'C:/test/design-project'; const manifest = createGameCreationAppManifest('test-project', '策划项目'); -function setup(invoke: ReturnType) { - window.__TAURI__ = { core: { invoke } } as unknown as typeof window.__TAURI__; +function setup( + invoke: (command: string, args?: Record) => Promise, +) { + installTauriRuntime({ core: { invoke } }); return renderHook(() => useHomeProjectCreation({ setStatus: vi.fn(), diff --git a/apps/ai-game-creator-shell/tests/designWorkspaceDebug.test.tsx b/apps/ai-game-creator-shell/tests/designWorkspaceDebug.test.tsx index 55f9b4082..0e9949803 100644 --- a/apps/ai-game-creator-shell/tests/designWorkspaceDebug.test.tsx +++ b/apps/ai-game-creator-shell/tests/designWorkspaceDebug.test.tsx @@ -12,6 +12,7 @@ import React from 'react'; import { afterEach, expect, it, vi } from 'vitest'; import { DesignWorkspacePanel } from '../src/features/project-workspace/DesignWorkspacePanel'; +import { installTauriRuntime, resetTauriRuntime } from './tauriRuntimeFake'; vi.mock('../src/components/ChatMarkdownMessage', () => ({ ChatMarkdownMessage: () => null, @@ -20,7 +21,7 @@ vi.mock('../src/components/ChatMarkdownMessage', () => ({ afterEach(() => { cleanup(); vi.unstubAllEnvs(); - delete window.__TAURI__; + resetTauriRuntime(); }); it('shows imported references after a workspace event without reloading the design session', async () => { @@ -41,7 +42,7 @@ it('shows imported references after a workspace event without reloading the desi if (command === 'read_design_workspace_file') return '# 导入的需求'; throw new Error(`Unexpected command: ${command}`); }); - window.__TAURI__ = { + installTauriRuntime({ core: { invoke }, event: { listen: vi.fn(async (_name, callback) => { @@ -49,7 +50,7 @@ it('shows imported references after a workspace event without reloading the desi return () => undefined; }), }, - } as unknown as typeof window.__TAURI__; + }); render(); await waitFor(() => expect(onUpdate).toBeDefined()); await waitFor(() => @@ -95,7 +96,7 @@ it('prepares debug fixtures from the header and refreshes the phase and files wi } throw new Error(`Unexpected command: ${command}`); }); - window.__TAURI__ = { core: { invoke } } as unknown as typeof window.__TAURI__; + installTauriRuntime({ core: { invoke } }); render(); await waitFor(() => expect( diff --git a/apps/ai-game-creator-shell/tests/directThreadChatSubscription.test.tsx b/apps/ai-game-creator-shell/tests/directThreadChatSubscription.test.tsx index f7a8ba9ac..da10d1fc4 100644 --- a/apps/ai-game-creator-shell/tests/directThreadChatSubscription.test.tsx +++ b/apps/ai-game-creator-shell/tests/directThreadChatSubscription.test.tsx @@ -4,6 +4,7 @@ import { afterEach, describe, expect, it, vi } from 'vitest'; import { useDirectThreadChatSubscription } from '../src/view/project-development/chat/controller/useDirectThreadChatSubscription'; import { createTauriEventFake, type TauriEventFake } from './tauriEventFake'; +import { resetTauriRuntime } from './tauriRuntimeFake'; const BOOTSTRAP = { subscriptionId: 'direct-subscription-1', @@ -17,7 +18,7 @@ afterEach(() => { cleanup(); eventFake?.restore(); eventFake = null; - delete window.__TAURI__; + resetTauriRuntime(); delete (window as Window & { __TAURI_INTERNALS__?: unknown }) .__TAURI_INTERNALS__; }); diff --git a/apps/ai-game-creator-shell/tests/errorReporting.test.ts b/apps/ai-game-creator-shell/tests/errorReporting.test.ts index 832ca4ac3..104ea9fc0 100644 --- a/apps/ai-game-creator-shell/tests/errorReporting.test.ts +++ b/apps/ai-game-creator-shell/tests/errorReporting.test.ts @@ -85,6 +85,7 @@ import { subscribeClientErrorEvents, } from '../src/services/errorReporting'; import type { ClientAuthError } from '../src/services/generated/ClientAuthError'; +import { resetTauriRuntime } from './tauriRuntimeFake'; describe('客户端错误报告池', () => { afterEach(() => { @@ -272,7 +273,7 @@ describe('客户端错误报告池', () => { }); it('浏览器预览中订阅错误事件不会产生未处理拒绝', async () => { - delete window.__TAURI__; + resetTauriRuntime(); const unsubscribe = subscribeClientErrorEvents(vi.fn()); unsubscribe(); diff --git a/apps/ai-game-creator-shell/tests/gamePublishFeedback.test.tsx b/apps/ai-game-creator-shell/tests/gamePublishFeedback.test.tsx index 8077e32a8..8bae3c725 100644 --- a/apps/ai-game-creator-shell/tests/gamePublishFeedback.test.tsx +++ b/apps/ai-game-creator-shell/tests/gamePublishFeedback.test.tsx @@ -29,6 +29,7 @@ import { parseStyleSheet, resolveDeclarations, } from './styleCascade'; +import { installTauriRuntime } from './tauriRuntimeFake'; const readGamePublishAvailabilityMock = vi.hoisted(() => vi.fn(async () => true), @@ -107,10 +108,10 @@ function installTauri( return chatHarness.invoke(command, args); }, ); - window.__TAURI__ = { - core: { invoke: invoke as never }, - event: { listen: chatHarness.listen as never }, - }; + installTauriRuntime({ + core: { invoke }, + event: { listen: chatHarness.listen }, + }); return { invoke }; } diff --git a/apps/ai-game-creator-shell/tests/homeWebPreflight.test.tsx b/apps/ai-game-creator-shell/tests/homeWebPreflight.test.tsx index bc2f95718..99325fff0 100644 --- a/apps/ai-game-creator-shell/tests/homeWebPreflight.test.tsx +++ b/apps/ai-game-creator-shell/tests/homeWebPreflight.test.tsx @@ -8,10 +8,11 @@ import { HOME_WEB_PREFLIGHT_IPC_FAILURE, } from '../src/features/app-shell/homeWebPreflight'; import { useHomeProjectCreation } from '../src/features/app-shell/useHomeProjectCreation'; +import { installTauriRuntime, resetTauriRuntime } from './tauriRuntimeFake'; afterEach(() => { cleanup(); - delete window.__TAURI__; + resetTauriRuntime(); }); function mount(preflight: () => Promise) { @@ -31,7 +32,7 @@ function mount(preflight: () => Promise) { return { activeRuntime: 'design' }; return null; }); - window.__TAURI__ = { core: { invoke } } as unknown as typeof window.__TAURI__; + installTauriRuntime({ core: { invoke } }); const hook = renderHook(() => useHomeProjectCreation({ setStatus: vi.fn(), diff --git a/apps/ai-game-creator-shell/tests/launcherNoticeShellWiring.test.tsx b/apps/ai-game-creator-shell/tests/launcherNoticeShellWiring.test.tsx index 1b75163a8..6741e4ea3 100644 --- a/apps/ai-game-creator-shell/tests/launcherNoticeShellWiring.test.tsx +++ b/apps/ai-game-creator-shell/tests/launcherNoticeShellWiring.test.tsx @@ -26,6 +26,7 @@ import { pickProjectFromLauncher, testAuthUser, } from './appSurface/harness'; +import { installTauriRuntime } from './tauriRuntimeFake'; const PROJECT_PATH = '/tmp/run-notice-shell-project'; @@ -115,10 +116,10 @@ function renderWorkbench() { return runtimeHarness.invoke(command, args); }, ); - window.__TAURI__ = { - core: { invoke: invoke as never }, - event: { listen: runtimeHarness.listen as never }, - }; + installTauriRuntime({ + core: { invoke }, + event: { listen: runtimeHarness.listen }, + }); render( { } return runtimeHarness.invoke(command); }); - window.__TAURI__ = { - core: { invoke: invoke as never }, - event: { listen: runtimeHarness.listen as never }, - }; + installTauriRuntime({ + core: { invoke }, + event: { listen: runtimeHarness.listen }, + }); render( ['core']>['invoke'] ->; - -type TauriListen = NonNullable< - NonNullable['event']>['listen'] ->; - -function asTauriInvoke( - invoke: (command: string, args?: Record) => Promise, -): TauriInvoke { - // 泛型 invoke 与具体的非泛型 mock 结构上不可兼容,只能用 as unknown as 收敛。 - return invoke as unknown as TauriInvoke; -} - -function asTauriListen( - listen: ( - event: string, - handler: (event: { payload: unknown }) => void, - ) => Promise, -): TauriListen { - // 泛型 listen 与携带具体负载的 mock 结构上不可兼容,只能用 as unknown as 收敛。 - return listen as unknown as TauriListen; -} +import { installTauriRuntime, resetTauriRuntime } from './tauriRuntimeFake'; describe('项目创建目录偏好', () => { beforeEach(() => { resetWorkspacePreferencesForTests(); - delete window.__TAURI__; + resetTauriRuntime(); }); it('去掉首尾空白与多余分隔符,并保留盘根', () => { @@ -106,19 +82,17 @@ describe('项目创建目录偏好', () => { } throw new Error('unexpected invoke ' + command); }); - window.__TAURI__ = { - core: { invoke: asTauriInvoke(invoke) }, + installTauriRuntime({ + core: { invoke }, event: { - listen: asTauriListen( - vi.fn(async (_event, handler) => { - eventHandler = handler; - return () => { - eventHandler = null; - }; - }), - ), + listen: vi.fn(async (_event, handler) => { + eventHandler = handler; + return () => { + eventHandler = null; + }; + }), }, - }; + }); const { result } = renderHook(() => useProjectCreationDirectory()); await waitFor(() => { diff --git a/apps/ai-game-creator-shell/tests/projectResourceLiveIntegration.test.tsx b/apps/ai-game-creator-shell/tests/projectResourceLiveIntegration.test.tsx index 39ee08608..0cb0a8e0f 100644 --- a/apps/ai-game-creator-shell/tests/projectResourceLiveIntegration.test.tsx +++ b/apps/ai-game-creator-shell/tests/projectResourceLiveIntegration.test.tsx @@ -7,7 +7,6 @@ import type { GameCreationAppAssetManifestEntry, GameCreationAppManifest, } from '../../../packages/shared/src/contracts/gameCreationApp'; -import type { TauriInvoke } from '../src/app/types'; const canvasFixture = vi.hoisted(() => ({ manifest: null as GameCreationAppManifest | null, @@ -34,6 +33,7 @@ import { waitFor, within, } from './appSurface/harness'; +import { installTauriRuntime, resetTauriRuntime } from './tauriRuntimeFake'; const projectPath = '/tmp/live-canvas-integration'; @@ -47,16 +47,6 @@ function legacyAssetKind(kind: string): GameCreationAppAssetKind { return kind as unknown as GameCreationAppAssetKind; } -/** - * 泛型 `invoke` 与具体的非泛型 mock 结构上不可兼容,只能用显式转换收敛类型; - * 运行时原样返回同一个 mock,调用方仍持有原引用(`mock.calls` 等断言不受影响)。 - */ -function asTauriInvoke( - invoke: (command: string, args?: Record) => Promise, -): TauriInvoke { - return invoke as unknown as TauriInvoke; -} - /** * 切栏目走「资源总览」的栏目缩略卡片(左侧大纲导航已删除)。 * 与两个 appSurface suite 里的同名助手同口径:切完必须落到目标栏目的子画布。 @@ -543,7 +533,7 @@ function SwitchableDerivedWorkbench({ describe('project resource live canvas integration', () => { afterEach(() => { - delete window.__TAURI__; + resetTauriRuntime(); canvasFixture.manifest = null; canvasFixture.revision = 0; }); @@ -837,10 +827,10 @@ describe('project resource live canvas integration', () => { throw new Error(`unexpected invoke ${command}`); }, ); - window.__TAURI__ = { - core: { invoke: asTauriInvoke(invoke) }, + installTauriRuntime({ + core: { invoke }, event: { listen: async () => () => undefined }, - }; + }); return { invoke, deriveCalls, diff --git a/apps/ai-game-creator-shell/tests/projectSnapshotWorkspace.test.tsx b/apps/ai-game-creator-shell/tests/projectSnapshotWorkspace.test.tsx index 88d12ef53..63f651063 100644 --- a/apps/ai-game-creator-shell/tests/projectSnapshotWorkspace.test.tsx +++ b/apps/ai-game-creator-shell/tests/projectSnapshotWorkspace.test.tsx @@ -5,16 +5,17 @@ import { afterEach, describe, expect, it, vi } from 'vitest'; import { useProjectSnapshotWorkspace } from '../src/features/app-shell/useProjectSnapshotWorkspace'; import { createProjectSnapshotWorkspaceRegistration } from '../src/services/projectSnapshotWorkspace'; +import { installTauriRuntime, resetTauriRuntime } from './tauriRuntimeFake'; afterEach(() => { cleanup(); - delete window.__TAURI__; + resetTauriRuntime(); }); describe('项目快照窗口登记', () => { it('在无 projectPath URL 的单窗口登记、切换、离开,StrictMode 不误关项目', async () => { const invoke = vi.fn(async () => undefined); - window.__TAURI__ = { core: { invoke: invoke as never } }; + installTauriRuntime({ core: { invoke } }); function Workspace({ path }: { path: string | null }) { useProjectSnapshotWorkspace(path); return null; diff --git a/apps/ai-game-creator-shell/tests/recentProjectsHook.test.tsx b/apps/ai-game-creator-shell/tests/recentProjectsHook.test.tsx index c7177bab2..f94eebda4 100644 --- a/apps/ai-game-creator-shell/tests/recentProjectsHook.test.tsx +++ b/apps/ai-game-creator-shell/tests/recentProjectsHook.test.tsx @@ -3,31 +3,7 @@ import { act, renderHook, waitFor } from '@testing-library/react'; import { afterEach, expect, test, vi } from 'vitest'; import { useRecentProjects } from '../src/features/app-shell/useRecentProjects'; - -type TauriInvoke = NonNullable< - NonNullable['core']>['invoke'] ->; - -type TauriListen = NonNullable< - NonNullable['event']>['listen'] ->; - -function asTauriInvoke( - invoke: (command: string, args?: Record) => Promise, -): TauriInvoke { - // 泛型 invoke 与具体的非泛型 mock 结构上不可兼容,只能用 as unknown as 收敛。 - return invoke as unknown as TauriInvoke; -} - -function asTauriListen( - listen: ( - event: string, - handler: (event: { payload: unknown }) => void, - ) => Promise, -): TauriListen { - // 泛型 listen 与携带具体负载的 mock 结构上不可兼容,只能用 as unknown as 收敛。 - return listen as unknown as TauriListen; -} +import { installTauriRuntime, resetTauriRuntime } from './tauriRuntimeFake'; const READY_PROJECT = { projectPath: '/tmp/ready-project', @@ -46,7 +22,7 @@ const READY_PROJECT = { }; afterEach(() => { - delete window.__TAURI__; + resetTauriRuntime(); window.localStorage.clear(); }); @@ -72,7 +48,7 @@ test('刷新坏项目时保留其它项目已确认的正常状态', async () => return READY_PROJECT; }, ); - window.__TAURI__ = { core: { invoke: asTauriInvoke(invoke) } }; + installTauriRuntime({ core: { invoke } }); window.localStorage.setItem( 'genarrative-ai-game-creator.recent-workspaces.v1', JSON.stringify(['/tmp/ready-project']), @@ -131,7 +107,7 @@ test('单次目录检查失败会就地重试,不会把整行钉成「检查 return READY_PROJECT; }, ); - window.__TAURI__ = { core: { invoke: asTauriInvoke(invoke) } }; + installTauriRuntime({ core: { invoke } }); window.localStorage.setItem( 'genarrative-ai-game-creator.recent-workspaces.v1', JSON.stringify(['/tmp/ready-project']), @@ -173,7 +149,7 @@ test('失败结果不跨轮保留:刷新时该项回到「检查中」并重 return { ...READY_PROJECT, projectPath }; }, ); - window.__TAURI__ = { core: { invoke: asTauriInvoke(invoke) } }; + installTauriRuntime({ core: { invoke } }); window.localStorage.setItem( 'genarrative-ai-game-creator.recent-workspaces.v1', JSON.stringify(['/tmp/broken-project']), @@ -218,7 +194,7 @@ test('提权类失败不重试:不放大 UAC 弹窗', async () => { ); }, ); - window.__TAURI__ = { core: { invoke: asTauriInvoke(invoke) } }; + installTauriRuntime({ core: { invoke } }); window.localStorage.setItem( 'genarrative-ai-game-creator.recent-workspaces.v1', JSON.stringify(['/tmp/elevation-project']), @@ -263,19 +239,17 @@ test('Rust 偏好事件与 command 返回同时到达时只检查新增项目一 throw new Error('unexpected invoke ' + command); }, ); - window.__TAURI__ = { - core: { invoke: asTauriInvoke(invoke) }, + installTauriRuntime({ + core: { invoke }, event: { - listen: asTauriListen( - vi.fn(async (_event, handler) => { - emitPreferenceEvent = handler; - return () => { - emitPreferenceEvent = null; - }; - }), - ), + listen: vi.fn(async (_event, handler) => { + emitPreferenceEvent = handler; + return () => { + emitPreferenceEvent = null; + }; + }), }, - }; + }); const { result } = renderHook(() => useRecentProjects(vi.fn())); await waitFor(() => { @@ -309,7 +283,7 @@ test('Rust 偏好事件与 command 返回同时到达时只检查新增项目一 ); }, ); - window.__TAURI__ = { core: { invoke: asTauriInvoke(invoke) } }; + installTauriRuntime({ core: { invoke } }); window.localStorage.setItem( 'genarrative-ai-game-creator.recent-workspaces.v1', JSON.stringify(['/tmp/denied-elevation-project']), diff --git a/apps/ai-game-creator-shell/tests/referenceSourceProviders.test.ts b/apps/ai-game-creator-shell/tests/referenceSourceProviders.test.ts index 68d0b8fc8..98ce0cd79 100644 --- a/apps/ai-game-creator-shell/tests/referenceSourceProviders.test.ts +++ b/apps/ai-game-creator-shell/tests/referenceSourceProviders.test.ts @@ -19,6 +19,7 @@ import { type RuntimeRegionReference, } from '../src/features/project-workspace/resourceReferences'; import type { UserContentPart } from '../src/view/project-development/chat/generated/UserContentPart'; +import { installTauriRuntime, resetTauriRuntime } from './tauriRuntimeFake'; function asset( id: string, @@ -67,7 +68,7 @@ const attachmentPart: UserContentPart = { afterEach(() => { cleanup(); vi.restoreAllMocks(); - window.__TAURI__ = undefined; + resetTauriRuntime(); }); describe('资源引用 provider', () => { @@ -368,7 +369,7 @@ describe('Skill provider', () => { if (command === 'list_client_extensions') return []; throw new Error(`unexpected invoke ${command}`); }); - window.__TAURI__ = { core: { invoke: invoke as never } }; + installTauriRuntime({ core: { invoke } }); const { result } = renderHook(() => useSkillReferenceProvider()); expect(result.current.trigger).toBe('$'); @@ -420,7 +421,7 @@ describe('Skill provider', () => { } throw new Error(`unexpected invoke ${command}`); }); - window.__TAURI__ = { core: { invoke: invoke as never } }; + installTauriRuntime({ core: { invoke } }); const { result } = renderHook(() => useSkillReferenceProvider()); act(() => { @@ -450,7 +451,7 @@ describe('Skill provider', () => { if (command === 'list_client_extensions') return []; throw new Error(`unexpected invoke ${command}`); }); - window.__TAURI__ = { core: { invoke: invoke as never } }; + installTauriRuntime({ core: { invoke } }); const { result } = renderHook(() => useSkillReferenceProvider()); @@ -511,7 +512,7 @@ describe('Skill provider', () => { } throw new Error(`unexpected invoke ${command}`); }); - window.__TAURI__ = { core: { invoke: invoke as never } }; + installTauriRuntime({ core: { invoke } }); const { result } = renderHook(() => useSkillReferenceProvider()); act(() => { @@ -542,7 +543,7 @@ describe('Skill provider', () => { throw new Error(`unexpected invoke ${command}`); }) .mockRejectedValueOnce(new Error('transient')); - window.__TAURI__ = { core: { invoke: invoke as never } }; + installTauriRuntime({ core: { invoke } }); const { result } = renderHook(() => useSkillReferenceProvider()); act(() => { @@ -568,7 +569,7 @@ describe('Skill provider', () => { it('`toReference` / `refresh` / `mentionToken` 只认 Skill', async () => { const invoke = vi.fn(async () => []); - window.__TAURI__ = { core: { invoke: invoke as never } }; + installTauriRuntime({ core: { invoke } }); const { result } = renderHook(() => useSkillReferenceProvider()); expect( diff --git a/apps/ai-game-creator-shell/tests/resourceBatchTagsIntegration.test.tsx b/apps/ai-game-creator-shell/tests/resourceBatchTagsIntegration.test.tsx index 29f75a8a3..3598255aa 100644 --- a/apps/ai-game-creator-shell/tests/resourceBatchTagsIntegration.test.tsx +++ b/apps/ai-game-creator-shell/tests/resourceBatchTagsIntegration.test.tsx @@ -17,6 +17,7 @@ import { waitFor, within, } from './appSurface/harness'; +import { installTauriRuntime } from './tauriRuntimeFake'; const PROJECT_PATH = '/tmp/batch-tags-project'; const PROJECT_ID = 'batch-tags-project'; @@ -294,10 +295,10 @@ function installHostTauri( throw new Error(`unexpected invoke ${command}`); }, ); - window.__TAURI__ = { - core: { invoke: invoke as never }, - event: { listen: (async () => () => undefined) as never }, - }; + installTauriRuntime({ + core: { invoke }, + event: { listen: async () => () => undefined }, + }); return { invoke, batchWrites, diff --git a/apps/ai-game-creator-shell/tests/resourceCanvasChatReferenceDrop.test.tsx b/apps/ai-game-creator-shell/tests/resourceCanvasChatReferenceDrop.test.tsx index f0540b325..629217d3e 100644 --- a/apps/ai-game-creator-shell/tests/resourceCanvasChatReferenceDrop.test.tsx +++ b/apps/ai-game-creator-shell/tests/resourceCanvasChatReferenceDrop.test.tsx @@ -22,6 +22,7 @@ import { render, screen, } from './appSurface/harness'; +import { installTauriRuntime, resetTauriRuntime } from './tauriRuntimeFake'; /** * 「拖动素材到对话 = 批量 @ 引用」的行为级验收。 @@ -144,7 +145,7 @@ function installTauri(): { layoutWrites: LayoutWrite[] } { return undefined; }, ); - window.__TAURI__ = { core: { invoke } } as unknown as typeof window.__TAURI__; + installTauriRuntime({ core: { invoke } }); return { layoutWrites }; } @@ -236,7 +237,7 @@ function cardIn(manager: HTMLElement, resourceId: string) { } afterEach(() => { - delete window.__TAURI__; + resetTauriRuntime(); vi.restoreAllMocks(); }); diff --git a/apps/ai-game-creator-shell/tests/resourceCanvasGenerationHostLifecycle.test.tsx b/apps/ai-game-creator-shell/tests/resourceCanvasGenerationHostLifecycle.test.tsx index 028c6fc3e..55b77f58f 100644 --- a/apps/ai-game-creator-shell/tests/resourceCanvasGenerationHostLifecycle.test.tsx +++ b/apps/ai-game-creator-shell/tests/resourceCanvasGenerationHostLifecycle.test.tsx @@ -21,6 +21,7 @@ import { within, } from './appSurface/harness'; import { typeGenerationPrompt } from './resourceGenerationPromptTestUtils'; +import { installTauriRuntime } from './tauriRuntimeFake'; /** * 画布生成入口的**宿主生命周期**验收(真实 `ProjectDevelopmentView`,不做 props mock)。 @@ -264,7 +265,7 @@ function installHostTauri(options: { }, ); - window.__TAURI__ = { core: { invoke } } as unknown as typeof window.__TAURI__; + installTauriRuntime({ core: { invoke } }); return { invoke, layoutWrites, diff --git a/apps/ai-game-creator-shell/tests/resourceCanvasGenerationLanding.test.tsx b/apps/ai-game-creator-shell/tests/resourceCanvasGenerationLanding.test.tsx index 12091df8e..2ce2aea2a 100644 --- a/apps/ai-game-creator-shell/tests/resourceCanvasGenerationLanding.test.tsx +++ b/apps/ai-game-creator-shell/tests/resourceCanvasGenerationLanding.test.tsx @@ -29,10 +29,11 @@ import { within, } from './appSurface/harness'; import { typeGenerationPrompt } from './resourceGenerationPromptTestUtils'; +import { installTauriRuntime, resetTauriRuntime } from './tauriRuntimeFake'; afterEach(() => { cleanup(); - delete window.__TAURI__; + resetTauriRuntime(); vi.restoreAllMocks(); }); @@ -230,7 +231,7 @@ function installTauri({ return undefined; }, ); - window.__TAURI__ = { core: { invoke } } as unknown as typeof window.__TAURI__; + installTauriRuntime({ core: { invoke } }); return { invoke, layoutWrites, generationStarts, deriveInputs, unexpected }; } diff --git a/apps/ai-game-creator-shell/tests/resourceCanvasGenerationTasksSidebarDismiss.test.tsx b/apps/ai-game-creator-shell/tests/resourceCanvasGenerationTasksSidebarDismiss.test.tsx index 54154fcfc..c7f2f107e 100644 --- a/apps/ai-game-creator-shell/tests/resourceCanvasGenerationTasksSidebarDismiss.test.tsx +++ b/apps/ai-game-creator-shell/tests/resourceCanvasGenerationTasksSidebarDismiss.test.tsx @@ -15,6 +15,7 @@ import { within, } from './appSurface/harness'; import { repoPath } from './repoPath'; +import { installTauriRuntime } from './tauriRuntimeFake'; /** * 「生成任务」侧栏与右上角入口的三条口径: @@ -62,7 +63,7 @@ function installInvoke() { }, ); // 该 mock 按命令返回固定结构,无法满足 TauriInvoke 的泛型逐命令建模,故断言为 window.__TAURI__。 - window.__TAURI__ = { core: { invoke } } as unknown as typeof window.__TAURI__; + installTauriRuntime({ core: { invoke } }); } async function renderWorkbench(projectId: string) { diff --git a/apps/ai-game-creator-shell/tests/resourceCanvasManualLayout.test.tsx b/apps/ai-game-creator-shell/tests/resourceCanvasManualLayout.test.tsx index 70f6ab687..d90f37c1b 100644 --- a/apps/ai-game-creator-shell/tests/resourceCanvasManualLayout.test.tsx +++ b/apps/ai-game-creator-shell/tests/resourceCanvasManualLayout.test.tsx @@ -26,6 +26,7 @@ import { screen, within, } from './appSurface/harness'; +import { installTauriRuntime, resetTauriRuntime } from './tauriRuntimeFake'; /** * 「AGC 资源画布:新素材不自动重排 + 生成后自动聚焦 + 显式整理画布」的行为级验收。 @@ -227,7 +228,7 @@ function installLayoutTauri( }, ); - window.__TAURI__ = { core: { invoke } } as unknown as typeof window.__TAURI__; + installTauriRuntime({ core: { invoke } }); return { invoke, layoutWrites, layoutReads, unexpectedCommands }; } @@ -346,7 +347,7 @@ function LayoutWorkbench({ } afterEach(() => { - delete window.__TAURI__; + resetTauriRuntime(); vi.restoreAllMocks(); }); @@ -729,9 +730,9 @@ describe('资源画布手动重排口径', () => { throw new Error(`unexpected invoke ${command}`); }, ); - window.__TAURI__ = { + installTauriRuntime({ core: { invoke }, - } as unknown as typeof window.__TAURI__; + }); const resources: ResourceCanvasItem[] = ['resource-a', 'resource-b'].map( (id) => ({ diff --git a/apps/ai-game-creator-shell/tests/resourceCardPreviewAlphaBackground.test.tsx b/apps/ai-game-creator-shell/tests/resourceCardPreviewAlphaBackground.test.tsx index 5c33b7045..4a7ef6eb6 100644 --- a/apps/ai-game-creator-shell/tests/resourceCardPreviewAlphaBackground.test.tsx +++ b/apps/ai-game-creator-shell/tests/resourceCardPreviewAlphaBackground.test.tsx @@ -42,6 +42,7 @@ import { parseStyleSheet, resolveDeclarations, } from './styleCascade'; +import { installTauriRuntime } from './tauriRuntimeFake'; const PROJECT_PATH = '/tmp/workbench-preview-alpha'; const PROJECT_ID = 'workbench-preview-alpha'; @@ -248,7 +249,7 @@ function installPreviewTauri(manifest: GameCreationAppManifest) { throw new Error(`unexpected invoke ${command}`); }, ); - window.__TAURI__ = { core: { invoke: invoke as never } }; + installTauriRuntime({ core: { invoke } }); return invoke; } diff --git a/apps/ai-game-creator-shell/tests/resourceJsonCanvas.test.tsx b/apps/ai-game-creator-shell/tests/resourceJsonCanvas.test.tsx index 644cef8e9..fc6f70325 100644 --- a/apps/ai-game-creator-shell/tests/resourceJsonCanvas.test.tsx +++ b/apps/ai-game-creator-shell/tests/resourceJsonCanvas.test.tsx @@ -16,6 +16,7 @@ import { waitFor, within, } from './appSurface/harness'; +import { installTauriRuntime, resetTauriRuntime } from './tauriRuntimeFake'; vi.mock('@tauri-apps/api/core', async () => ({ ...(await vi.importActual( @@ -124,7 +125,7 @@ async function mountJsonCanvas( throw new Error(`unexpected command ${command}`); }, ); - window.__TAURI__ = { core: { invoke } } as unknown as typeof window.__TAURI__; + installTauriRuntime({ core: { invoke } }); const onManifestChange = vi.fn(); render( { - delete window.__TAURI__; + resetTauriRuntime(); }); describe('JSON 画布卡片与入口', () => { diff --git a/apps/ai-game-creator-shell/tests/resourceReferenceInput.test.tsx b/apps/ai-game-creator-shell/tests/resourceReferenceInput.test.tsx index 321e33e9c..96115110d 100644 --- a/apps/ai-game-creator-shell/tests/resourceReferenceInput.test.tsx +++ b/apps/ai-game-creator-shell/tests/resourceReferenceInput.test.tsx @@ -68,6 +68,7 @@ import { resourceReferenceTagLibrary, type RuntimeRegionReference, } from '../src/features/project-workspace/resourceReferences'; +import { installTauriRuntime, resetTauriRuntime } from './tauriRuntimeFake'; /** * jsdom 没有 `DragEvent` / `ClipboardEvent` 构造器,而 Lexical 的默认粘贴路径按构造器名字判断 @@ -416,7 +417,7 @@ describe('ResourceReferenceInput', () => { } throw new Error(`unexpected invoke ${command}`); }); - window.__TAURI__ = { core: { invoke: invoke as never } }; + installTauriRuntime({ core: { invoke } }); try { render( @@ -439,7 +440,7 @@ describe('ResourceReferenceInput', () => { expect(invoke).toHaveBeenCalledWith('list_agc_skill_catalog'); }); } finally { - delete window.__TAURI__; + resetTauriRuntime(); } }); @@ -607,7 +608,7 @@ describe('ResourceReferenceInput', () => { } throw new Error(`unexpected invoke ${command}`); }); - window.__TAURI__ = { core: { invoke: invoke as never } }; + installTauriRuntime({ core: { invoke } }); try { const ref = createRef(); @@ -653,7 +654,7 @@ describe('ResourceReferenceInput', () => { { type: 'input_text', text: ' 出图' }, ]); } finally { - delete window.__TAURI__; + resetTauriRuntime(); } }); diff --git a/apps/ai-game-creator-shell/tests/resourceTagStatsRefresh.test.tsx b/apps/ai-game-creator-shell/tests/resourceTagStatsRefresh.test.tsx index dcb318aaf..efe85908d 100644 --- a/apps/ai-game-creator-shell/tests/resourceTagStatsRefresh.test.tsx +++ b/apps/ai-game-creator-shell/tests/resourceTagStatsRefresh.test.tsx @@ -19,6 +19,7 @@ import { waitFor, within, } from './appSurface/harness'; +import { installTauriRuntime } from './tauriRuntimeFake'; const PROJECT_PATH = '/tmp/tag-stats-refresh-project'; const PROJECT_ID = 'tag-stats-refresh-project'; @@ -212,10 +213,10 @@ function installHostTauri() { return chatHarness.invoke(command, args); }, ); - window.__TAURI__ = { - core: { invoke: invoke as never }, - event: { listen: chatHarness.listen as never }, - }; + installTauriRuntime({ + core: { invoke }, + event: { listen: chatHarness.listen }, + }); return { invoke, classificationWrites }; } diff --git a/apps/ai-game-creator-shell/tests/resourceVersionReplacement.test.tsx b/apps/ai-game-creator-shell/tests/resourceVersionReplacement.test.tsx index 38603346b..08458f07a 100644 --- a/apps/ai-game-creator-shell/tests/resourceVersionReplacement.test.tsx +++ b/apps/ai-game-creator-shell/tests/resourceVersionReplacement.test.tsx @@ -18,6 +18,7 @@ import { waitFor, within, } from './appSurface/harness'; +import { installTauriRuntime } from './tauriRuntimeFake'; /** * 版本级资源替换的真链路用例(PRD §3.2 / §5.3)。 @@ -356,7 +357,7 @@ function renderReplacementWorkbench(options: RenderOptions = {}) { throw new Error(`unexpected invoke ${command}`); }, ); - window.__TAURI__ = { core: { invoke: invoke as never } }; + installTauriRuntime({ core: { invoke } }); const onActiveVersionChange = vi.fn(); const onPlay = vi.fn(); diff --git a/apps/ai-game-creator-shell/tests/resourceVersionReplacementModel.test.ts b/apps/ai-game-creator-shell/tests/resourceVersionReplacementModel.test.ts index 043e3e116..fca11932e 100644 --- a/apps/ai-game-creator-shell/tests/resourceVersionReplacementModel.test.ts +++ b/apps/ai-game-creator-shell/tests/resourceVersionReplacementModel.test.ts @@ -24,17 +24,7 @@ import { replaceVersionResource, } from '../src/features/resource-canvas/resourceVersionReplacementTransport'; import { projectResourcesFromReadModels } from '../src/view/project-development/resourceProjectionModel'; - -type TauriInvoke = NonNullable< - NonNullable['core']>['invoke'] ->; - -function asTauriInvoke( - invoke: (command: string, args?: Record) => Promise, -): TauriInvoke { - // 泛型 invoke 与具体的非泛型 mock 结构上不可兼容,只能用 as unknown as 收敛。 - return invoke as unknown as TauriInvoke; -} +import { installTauriRuntime } from './tauriRuntimeFake'; function candidate( resourceId: string, @@ -371,7 +361,7 @@ describe('候选择序与资源映射', () => { describe('资源替换的 IPC 边界', () => { it('两个命令都只传一个 input 对象,字段名与 Rust 结构体逐字一致', async () => { const invoke = vi.fn(async () => ({ ok: true })); - window.__TAURI__ = { core: { invoke: asTauriInvoke(invoke) } }; + installTauriRuntime({ core: { invoke } }); await readVersionResourceReplacementCandidates({ projectPath: '/tmp/project', diff --git a/apps/ai-game-creator-shell/tests/runPreviewBrowserOpen.test.tsx b/apps/ai-game-creator-shell/tests/runPreviewBrowserOpen.test.tsx index 3f7044dea..15d53e54e 100644 --- a/apps/ai-game-creator-shell/tests/runPreviewBrowserOpen.test.tsx +++ b/apps/ai-game-creator-shell/tests/runPreviewBrowserOpen.test.tsx @@ -4,6 +4,7 @@ import { afterEach, describe, expect, it, vi } from 'vitest'; import { createGameCreationAppManifest } from '../../../packages/shared/src/contracts/gameCreationApp'; import ProjectDevelopmentView from '../src/view/project-development'; +import { installTauriRuntime } from './tauriRuntimeFake'; const openUrl = vi.fn(async (_url: string) => undefined); vi.mock('@tauri-apps/plugin-opener', () => ({ @@ -13,7 +14,7 @@ vi.mock('@tauri-apps/plugin-opener', () => ({ const PREVIEW_URL = 'http://127.0.0.1:4173/'; function installInvoke() { - window.__TAURI__ = { + installTauriRuntime({ core: { invoke: vi.fn(async (command: string, args?: Record) => { if (command === 'read_local_project_resource_graph') { @@ -38,7 +39,7 @@ function installInvoke() { throw new Error(`unexpected invoke ${command}`); }), }, - } as unknown as typeof window.__TAURI__; + }); } function renderRunView(options: { withPreview?: boolean } = {}) { diff --git a/apps/ai-game-creator-shell/tests/runPreviewFullscreen.test.tsx b/apps/ai-game-creator-shell/tests/runPreviewFullscreen.test.tsx index 6a2dc5c60..199efc3e6 100644 --- a/apps/ai-game-creator-shell/tests/runPreviewFullscreen.test.tsx +++ b/apps/ai-game-creator-shell/tests/runPreviewFullscreen.test.tsx @@ -4,11 +4,12 @@ import { afterEach, describe, expect, it, vi } from 'vitest'; import { createGameCreationAppManifest } from '../../../packages/shared/src/contracts/gameCreationApp'; import ProjectDevelopmentView from '../src/view/project-development'; +import { installTauriRuntime } from './tauriRuntimeFake'; const PREVIEW_URL = 'http://127.0.0.1:4173/'; function installInvoke() { - window.__TAURI__ = { + installTauriRuntime({ core: { invoke: vi.fn(async (command: string, args?: Record) => { if (command === 'read_local_project_resource_graph') { @@ -33,7 +34,7 @@ function installInvoke() { throw new Error(`unexpected invoke ${command}`); }), }, - } as unknown as typeof window.__TAURI__; + }); } function renderRunView() { diff --git a/apps/ai-game-creator-shell/tests/tauriRuntimeFake.ts b/apps/ai-game-creator-shell/tests/tauriRuntimeFake.ts index 5a2e35f44..74c3e98f7 100644 --- a/apps/ai-game-creator-shell/tests/tauriRuntimeFake.ts +++ b/apps/ai-game-creator-shell/tests/tauriRuntimeFake.ts @@ -29,9 +29,9 @@ export function installTauriRuntime(runtime: TauriRuntimeInstall) { window.__TAURI__ = runtime as unknown as Window['__TAURI__']; } -/** 清掉替身,供 `afterEach` / `beforeEach` 复位,等价于历史里的 `window.__TAURI__ = undefined`。 */ +/** 清掉替身,供 `afterEach` / `beforeEach` 复位(等价于仓库既有的 `delete window.__TAURI__`)。 */ export function resetTauriRuntime() { - window.__TAURI__ = undefined; + delete window.__TAURI__; } /** diff --git a/apps/ai-game-creator-shell/tests/unityProjectOpen.test.tsx b/apps/ai-game-creator-shell/tests/unityProjectOpen.test.tsx index 3e4abf7c1..713ccee5a 100644 --- a/apps/ai-game-creator-shell/tests/unityProjectOpen.test.tsx +++ b/apps/ai-game-creator-shell/tests/unityProjectOpen.test.tsx @@ -5,22 +5,12 @@ import { afterEach, expect, test, vi } from 'vitest'; import { createGameCreationAppManifest } from '../../../packages/shared/src/contracts/gameCreationApp'; import { buildRecentProjectRows } from '../src/features/app-shell/model'; import { useHomeProjectCreation } from '../src/features/app-shell/useHomeProjectCreation'; +import { installTauriRuntime, resetTauriRuntime } from './tauriRuntimeFake'; afterEach(() => { - delete window.__TAURI__; + resetTauriRuntime(); }); -type TauriInvoke = NonNullable< - NonNullable['core']>['invoke'] ->; - -function asTauriInvoke( - invoke: (command: string, args?: Record) => Promise, -): TauriInvoke { - // 泛型 invoke 与具体的非泛型 mock 结构上不可兼容,只能用 as unknown as 收敛。 - return invoke as unknown as TauriInvoke; -} - test.each([false, true])( 'Unity 项目通过现有工作台打开,已有元数据=%s', async (initialized) => { @@ -53,7 +43,7 @@ test.each([false, true])( return { activeRuntime: 'game' }; throw new Error(`意外调用:${command}`); }); - window.__TAURI__ = { core: { invoke: asTauriInvoke(invoke) } }; + installTauriRuntime({ core: { invoke } }); const setLauncherView = vi.fn(); const { result } = renderHook(() => useHomeProjectCreation({ diff --git a/apps/ai-game-creator-shell/tests/useProjectResourceCanvasLayout.test.ts b/apps/ai-game-creator-shell/tests/useProjectResourceCanvasLayout.test.ts index c1b579d58..bbd90923d 100644 --- a/apps/ai-game-creator-shell/tests/useProjectResourceCanvasLayout.test.ts +++ b/apps/ai-game-creator-shell/tests/useProjectResourceCanvasLayout.test.ts @@ -21,6 +21,7 @@ import { inspectProjectResourceCanvasLayoutRead, useProjectResourceCanvasLayout, } from '../src/view/project-development/useProjectResourceCanvasLayout'; +import { installTauriRuntime, resetTauriRuntime } from './tauriRuntimeFake'; const projectId = 'layout-hook-project'; const projectPath = '/tmp/layout-hook-project'; @@ -88,12 +89,12 @@ function installTauriInvoke( invoke: (command: string, args?: Record) => Promise, ): void { // 泛型签名与具体 Mock 结构上不可兼容,这里显式转换以完成测试装配。 - window.__TAURI__ = { core: { invoke } } as unknown as typeof window.__TAURI__; + installTauriRuntime({ core: { invoke } }); } afterEach(() => { cleanup(); - window.__TAURI__ = undefined; + resetTauriRuntime(); vi.restoreAllMocks(); }); diff --git a/apps/ai-game-creator-shell/tests/useProjectResourceCardPreviews.test.ts b/apps/ai-game-creator-shell/tests/useProjectResourceCardPreviews.test.ts index e9d192e1e..cc4472fd5 100644 --- a/apps/ai-game-creator-shell/tests/useProjectResourceCardPreviews.test.ts +++ b/apps/ai-game-creator-shell/tests/useProjectResourceCardPreviews.test.ts @@ -18,7 +18,6 @@ import { vi, } from 'vitest'; -import type { TauriInvoke } from '../src/app/types'; import { isProjectResourcePreviewCancellation } from '../src/services/projectResourcePreviewTransport'; import { createResourceCanvasPrefetchScopeKey } from '../src/view/project-development/resourceCanvasController'; import { @@ -32,22 +31,12 @@ import { nextPreviewJob, useProjectResourceCardPreviews as useProjectResourceCardPreviewsUnderTest, } from '../src/view/project-development/useProjectResourceCardPreviews'; +import { installTauriRuntime, resetTauriRuntime } from './tauriRuntimeFake'; type PreviewHookInput = Parameters< typeof useProjectResourceCardPreviewsUnderTest >[0]; -/** - * `vi.fn` 的返回类型是固定签名,装不进 `window.__TAURI__.core.invoke` 的泛型签名; - * 这里集中做一次显式转换:`as unknown as` 只走类型层,运行时原样返回同一个 Mock, - * 用例仍能继续用 `.mock` 断言。 - */ -function asTauriInvoke( - invoke: Mock, -): TauriInvoke { - return invoke as unknown as TauriInvoke; -} - /** * 生产里 `prefetchScopeKey` 是**必填**的视图标识,由 `index.tsx` 用 * `createResourceCanvasPrefetchScopeKey` 按「视图 + 栏目 + 排序模式」算出。 @@ -146,7 +135,7 @@ beforeEach(() => { afterEach(() => { cleanup(); - window.__TAURI__ = undefined; + resetTauriRuntime(); vi.restoreAllMocks(); if (originalCreateObjectUrl) { Object.defineProperty(URL, 'createObjectURL', originalCreateObjectUrl); @@ -178,7 +167,7 @@ describe('useProjectResourceCardPreviews', () => { : {}), }), ); - window.__TAURI__ = { core: { invoke: asTauriInvoke(invoke) } }; + installTauriRuntime({ core: { invoke } }); const resources = [json]; const canvasRef = { current: document.createElement('div') }; const { result, rerender } = renderHook( @@ -229,14 +218,14 @@ describe('useProjectResourceCardPreviews', () => { content: 'const answer = 42;', }), ); - window.__TAURI__ = { + installTauriRuntime({ core: { invoke: async ( command: string, args?: Record, ) => (await invoke(command, args)) as Result, }, - }; + }); const { result } = renderHook(() => useProjectResourceCardPreviews({ projectPath: '/tmp/document-preview', @@ -280,7 +269,7 @@ describe('useProjectResourceCardPreviews', () => { async (_command: string, args?: Record) => preview(String(args?.relativePath ?? art.path)), ); - window.__TAURI__ = { core: { invoke: asTauriInvoke(invoke) } }; + installTauriRuntime({ core: { invoke } }); const canvasRef = { current: document.createElement('div') }; const { result } = renderHook(() => @@ -314,7 +303,7 @@ describe('useProjectResourceCardPreviews', () => { mediaType: 'image/gif', }); const invoke = vi.fn(async () => preview(art.path, 'image/gif')); - window.__TAURI__ = { core: { invoke: asTauriInvoke(invoke) } }; + installTauriRuntime({ core: { invoke } }); const canvasRef = { current: document.createElement('div') }; const { result } = renderHook(() => @@ -360,7 +349,7 @@ describe('useProjectResourceCardPreviews', () => { async (_command: string, _args?: Record) => preview(art.path, 'image/gif'), ); - window.__TAURI__ = { core: { invoke: asTauriInvoke(invoke) } }; + installTauriRuntime({ core: { invoke } }); const canvasRef = { current: document.createElement('div') }; const { result } = renderHook(() => @@ -395,7 +384,7 @@ describe('useProjectResourceCardPreviews', () => { category: 'audio', }); const invoke = vi.fn(async () => preview(audio.path, 'audio/mpeg')); - window.__TAURI__ = { core: { invoke: asTauriInvoke(invoke) } }; + installTauriRuntime({ core: { invoke } }); const canvasRef = { current: document.createElement('div') }; const { result } = renderHook(() => @@ -503,7 +492,7 @@ describe('useProjectResourceCardPreviews', () => { new Error('读取项目媒体资源失败:temporarily busy'), ) .mockResolvedValueOnce(preview(art.path)); - window.__TAURI__ = { core: { invoke: asTauriInvoke(invoke) } }; + installTauriRuntime({ core: { invoke } }); const canvasRef = { current: document.createElement('div') }; const { result } = renderHook(() => useProjectResourceCardPreviews({ @@ -544,7 +533,7 @@ describe('useProjectResourceCardPreviews', () => { .mockRejectedValue( new Error('image.inspect 图片结构无效:assets/broken.png'), ); - window.__TAURI__ = { core: { invoke: asTauriInvoke(invoke) } }; + installTauriRuntime({ core: { invoke } }); const canvasRef = { current: document.createElement('div') }; const { result } = renderHook(() => useProjectResourceCardPreviews({ @@ -599,7 +588,7 @@ describe('useProjectResourceCardPreviews', () => { new Promise>(() => undefined) ); }); - window.__TAURI__ = { core: { invoke: asTauriInvoke(invoke) } }; + installTauriRuntime({ core: { invoke } }); const canvasRef = { current: document.createElement('div') }; const { result } = renderHook(() => useProjectResourceCardPreviews({ @@ -667,7 +656,7 @@ describe('useProjectResourceCardPreviews', () => { const invoke = vi.fn(async (_: string, args?: Record) => preview(String(args?.relativePath ?? ''), 'video/mp4'), ); - window.__TAURI__ = { core: { invoke: asTauriInvoke(invoke) } }; + installTauriRuntime({ core: { invoke } }); const canvasRef = { current: document.createElement('div') }; const { result, unmount } = renderHook(() => useProjectResourceCardPreviews({ @@ -707,7 +696,7 @@ describe('useProjectResourceCardPreviews', () => { const invoke = vi.fn(async (_: string, args?: Record) => preview(String(args?.relativePath ?? '')), ); - window.__TAURI__ = { core: { invoke: asTauriInvoke(invoke) } }; + installTauriRuntime({ core: { invoke } }); const canvasRef = { current: document.createElement('div') }; const { result, rerender, unmount } = renderHook( (resources: ProjectResource[]) => @@ -797,7 +786,7 @@ describe('useProjectResourceCardPreviews', () => { pending.push({ path: String(args?.relativePath ?? ''), read }); return read.promise; }); - window.__TAURI__ = { core: { invoke: asTauriInvoke(invoke) } }; + installTauriRuntime({ core: { invoke } }); const canvasRef = { current: document.createElement('div') }; const { result, rerender } = renderHook( (props: { @@ -897,7 +886,7 @@ describe('useProjectResourceCardPreviews', () => { return preview(art.path); }, ); - window.__TAURI__ = { core: { invoke: asTauriInvoke(invoke) } }; + installTauriRuntime({ core: { invoke } }); const canvasRef = { current: document.createElement('div') }; const { result, rerender, unmount } = renderHook( (mode: 'dependency' | 'type') => @@ -954,7 +943,7 @@ describe('useProjectResourceCardPreviews', () => { async (_command: string, args?: Record) => preview(String(args?.relativePath ?? art.path)), ); - window.__TAURI__ = { core: { invoke: asTauriInvoke(invoke) } }; + installTauriRuntime({ core: { invoke } }); const canvasRef = { current: document.createElement('div') }; const { result, rerender } = renderHook( (previewVersion: string) => @@ -1009,7 +998,7 @@ describe('useProjectResourceCardPreviews', () => { pending.push(read); return read.promise; }); - window.__TAURI__ = { core: { invoke: asTauriInvoke(invoke) } }; + installTauriRuntime({ core: { invoke } }); const canvasRef = { current: document.createElement('div') }; const { result, rerender } = renderHook( (props: { projectPath: string; projectId: string }) => @@ -1084,7 +1073,7 @@ describe('useProjectResourceCardPreviews', () => { async (_command: string, args?: Record) => preview(String(args?.relativePath ?? art.path)), ); - window.__TAURI__ = { core: { invoke: asTauriInvoke(invoke) } }; + installTauriRuntime({ core: { invoke } }); const canvasRef = { current: document.createElement('div') }; const intersectionRoot = document.createElement('div'); const callbacks: IntersectionObserverCallback[] = []; @@ -1279,7 +1268,7 @@ describe('useProjectResourceCardPreviews', () => { async (_command: string, args?: Record) => preview(String(args?.relativePath ?? art.path)), ); - window.__TAURI__ = { core: { invoke: asTauriInvoke(invoke) } }; + installTauriRuntime({ core: { invoke } }); // root 在首次渲染时还没有挂上(等价于画本容器尚未就绪)。 const canvasRef: { current: HTMLDivElement | null } = { current: null }; const harness = installIntersectionObserver(); @@ -1344,7 +1333,7 @@ describe('useProjectResourceCardPreviews', () => { async (_command: string, args?: Record) => preview(String(args?.relativePath ?? '')), ); - window.__TAURI__ = { core: { invoke: asTauriInvoke(invoke) } }; + installTauriRuntime({ core: { invoke } }); const canvasRef: { current: HTMLDivElement | null } = { current: null }; const harness = installIntersectionObserver(); @@ -1407,7 +1396,7 @@ describe('useProjectResourceCardPreviews', () => { async (_command: string, args?: Record) => preview(String(args?.relativePath ?? '')), ); - window.__TAURI__ = { core: { invoke: asTauriInvoke(invoke) } }; + installTauriRuntime({ core: { invoke } }); const canvasRef = { current: document.createElement('div') }; const callbacks: IntersectionObserverCallback[] = []; @@ -1551,7 +1540,7 @@ describe('useProjectResourceCardPreviews', () => { async (_command: string, args?: Record) => preview(String(args?.relativePath ?? art.path)), ); - window.__TAURI__ = { core: { invoke: asTauriInvoke(invoke) } }; + installTauriRuntime({ core: { invoke } }); const canvasRef: { current: HTMLDivElement | null } = { current: null }; const harness = installIntersectionObserver(); @@ -1594,7 +1583,7 @@ describe('useProjectResourceCardPreviews', () => { async (_command: string, args?: Record) => preview(String(args?.relativePath ?? art.path)), ); - window.__TAURI__ = { core: { invoke: asTauriInvoke(invoke) } }; + installTauriRuntime({ core: { invoke } }); const canvasRef = { current: document.createElement('div') }; const harness = installIntersectionObserver(); @@ -1702,7 +1691,7 @@ describe('useProjectResourceCardPreviews', () => { const held = deferred>(); const invoke = vi.fn(async () => held.promise); - window.__TAURI__ = { core: { invoke: asTauriInvoke(invoke) } }; + installTauriRuntime({ core: { invoke } }); const canvasRef = { current: document.createElement('div') }; const { result, rerender } = renderHook( @@ -1782,7 +1771,7 @@ describe('useProjectResourceCardPreviews', () => { }); const held = deferred>(); const invoke = vi.fn(async () => held.promise); - window.__TAURI__ = { core: { invoke: asTauriInvoke(invoke) } }; + installTauriRuntime({ core: { invoke } }); const canvasRef = { current: document.createElement('div') }; const { result, rerender } = renderHook( @@ -1860,7 +1849,7 @@ describe('预览队列上限吞掉按需请求时的可观测性', () => { ); // 所有读取都挂住:队列只进不出,才能在用例里稳定压满。 const invoke = vi.fn(() => new Promise(() => undefined)); - window.__TAURI__ = { core: { invoke: asTauriInvoke(invoke) } }; + installTauriRuntime({ core: { invoke } }); const canvasRef = { current: document.createElement('div') }; const warn = vi.spyOn(console, 'warn').mockImplementation(() => undefined); const { result } = renderHook(() => @@ -2128,7 +2117,7 @@ describe('冷启动首屏的放行范围与放行顺序', () => { async (_command: string, args?: Record) => preview(String(args?.relativePath ?? '')), ); - window.__TAURI__ = { core: { invoke: asTauriInvoke(invoke) } }; + installTauriRuntime({ core: { invoke } }); render( React.createElement(PreviewBandHarness, { @@ -2168,7 +2157,7 @@ describe('冷启动首屏的放行范围与放行顺序', () => { // 登记顺序故意先余量圈、后视口内:单趟入队会把这个顺序原样写进队列。 const resources = [...marginOnly, ...inViewport]; const invoke = vi.fn(() => new Promise(() => undefined)); - window.__TAURI__ = { core: { invoke: asTauriInvoke(invoke) } }; + installTauriRuntime({ core: { invoke } }); const harness = installRecordingIntersectionObserver(); const controllerRef: { current: PreviewController | null } = { current: null, @@ -2241,7 +2230,7 @@ describe('预览缓存驱逐必须避开可见卡', () => { async (_command: string, args?: Record) => preview(String(args?.relativePath ?? '')), ); - window.__TAURI__ = { core: { invoke: asTauriInvoke(invoke) } }; + installTauriRuntime({ core: { invoke } }); const canvasRef = { current: document.createElement('div') }; const { result } = renderHook(() => @@ -2321,7 +2310,7 @@ describe('预览缓存驱逐必须避开可见卡', () => { const invoke = vi.fn(async (_: string, args?: Record) => preview(String(args?.relativePath ?? '')), ); - window.__TAURI__ = { core: { invoke: asTauriInvoke(invoke) } }; + installTauriRuntime({ core: { invoke } }); const canvasRef = { current: document.createElement('div') }; const { result } = renderHook(() => useProjectResourceCardPreviews({ diff --git a/apps/ai-game-creator-shell/tests/workspaceLauncherManifestMerge.test.tsx b/apps/ai-game-creator-shell/tests/workspaceLauncherManifestMerge.test.tsx index f2b693b74..c703729f1 100644 --- a/apps/ai-game-creator-shell/tests/workspaceLauncherManifestMerge.test.tsx +++ b/apps/ai-game-creator-shell/tests/workspaceLauncherManifestMerge.test.tsx @@ -18,6 +18,7 @@ import { } from '../../../packages/shared/src/contracts/gameCreationApp'; import type { ProjectChatComponentProps } from '../src/features/app-shell/model'; import { WorkspaceLauncherShell } from '../src/features/app-shell/WorkspaceLauncher'; +import { installTauriRuntime, resetTauriRuntime } from './tauriRuntimeFake'; const PROJECT_PATH = '/tmp/manifest-merge-notice-project'; const PROJECT_ID = 'local-project-draft'; @@ -126,7 +127,7 @@ function installInvokeMock() { } }, ); - window.__TAURI__ = { core: { invoke: invoke as never } }; + installTauriRuntime({ core: { invoke } }); return invoke; } @@ -234,7 +235,7 @@ describe('清单快照被拒收时的用户可见性与恢复', () => { afterEach(() => { cleanup(); - window.__TAURI__ = undefined; + resetTauriRuntime(); vi.restoreAllMocks(); }); diff --git a/apps/ai-game-creator-shell/tests/workspaceWindowSync.test.tsx b/apps/ai-game-creator-shell/tests/workspaceWindowSync.test.tsx index 49e76f169..208ceeecc 100644 --- a/apps/ai-game-creator-shell/tests/workspaceWindowSync.test.tsx +++ b/apps/ai-game-creator-shell/tests/workspaceWindowSync.test.tsx @@ -18,6 +18,7 @@ import { render, testAuthUser, } from './appSurface/harness'; +import { resetTauriRuntime } from './tauriRuntimeFake'; const homeProjectOverride = vi.hoisted(() => ({ openProject: null as @@ -78,7 +79,7 @@ it('真实窗口与工作台状态同步收敛,回调读取最新处理器且 ); - delete window.__TAURI__; + resetTauriRuntime(); const rendered = render(view()); try { await act(async () => {