Fix/typecheck test #601

Merged
k88936 merged 11 commits from fix/typecheck-test into master 2026-10-03 21:05:43 +08:00
114 changed files with 1839 additions and 850 deletions
+2 -1
View File
@@ -19,7 +19,8 @@
"bundled-resources:test": "node --test scripts/prepare-bundled-resources.test.mjs",
"llm-status": "node scripts/run-cli-with-config.mjs --llm-status",
"config": "node scripts/game-creator-config-wizard.mjs",
"typecheck": "tsc -p tsconfig.json --noEmit && npm run skill-pack:check && npm run bundled-resources:check && node scripts/check-config.mjs"
"check:tests:types": "tsc -p tsconfig.tests.json --noEmit",
"typecheck": "npm run check:tests:types && tsc -p tsconfig.json --noEmit && npm run skill-pack:check && npm run bundled-resources:check && node scripts/check-config.mjs"
},
"dependencies": {
"@anthropic-ai/claude-agent-sdk": "0.3.285",
@@ -11,8 +11,11 @@ import {
import React from 'react';
import { describe, expect, it, vi } from 'vitest';
import type { ProjectResourceCanvasPosition } from '../../../packages/shared/src/contracts/gameCreationApp';
import type { ProjectResourceCanvasCategory } from '../../../packages/shared/src/contracts/gameCreationApp';
import {
isProjectResourceCanvasCategory,
type ProjectResourceCanvasCategory,
type ProjectResourceCanvasPosition,
} from '../../../packages/shared/src/contracts/gameCreationApp';
import {
RESOURCE_CANVAS_CARD_WIDTH,
RESOURCE_CANVAS_DEPENDENCY_COLUMN_GAP,
@@ -130,7 +133,11 @@ function OverlayHarness({
overlayRef?: React.Ref<ResourceDependencyOverlayHandle>;
}) {
const sections = Array.from(
new Set(positions.map((position) => position.section)),
new Set(
positions
.map((position) => position.section)
.filter(isProjectResourceCanvasCategory),
),
);
return React.createElement(
'div',
@@ -809,7 +816,7 @@ describe('ResourceDependencyOverlay', () => {
it('keeps drag previews in plane-local coordinates', async () => {
const getBoundingClientRect = vi
.spyOn(HTMLElement.prototype, 'getBoundingClientRect')
.mockImplementation(function () {
.mockImplementation(function (this: HTMLElement) {
const isPlane = this.hasAttribute('data-resource-section-plane');
return {
x: isPlane ? 160 : 20,
@@ -1046,7 +1053,9 @@ describe('ResourceDependencyOverlay', () => {
.map((value) =>
value <= 0.04045 ? value / 12.92 : ((value + 0.055) / 1.055) ** 2.4,
);
return channels[0] * 0.2126 + channels[1] * 0.7152 + channels[2] * 0.0722;
return (
channels[0]! * 0.2126 + channels[1]! * 0.7152 + channels[2]! * 0.0722
);
};
const lineLuminance = luminance('c45f20');
const canvasLuminance = luminance('fffdfa');
@@ -2,13 +2,16 @@
import React from 'react';
import { describe, expect, it, vi } from 'vitest';
import { ResourceEditSurface } from '../src/view/project-development/ResourceEditSurface';
import {
type ResourceEditSubmitInput,
ResourceEditSurface,
} from '../src/view/project-development/ResourceEditSurface';
import { fireEvent, render, screen, waitFor } from './appSurface/harness';
describe('统一资源编辑面板', () => {
it('失败后锁定原请求字段并使用相同输入重试', async () => {
const submit = vi
.fn<(input: { prompt: string; assetName: string }) => Promise<void>>()
.fn<[input: ResourceEditSubmitInput], Promise<void>>()
.mockRejectedValueOnce(new Error('网络暂不可用'))
.mockResolvedValueOnce(undefined);
render(
@@ -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();
@@ -10,76 +10,51 @@ import { installUnhandledRejectionBridge } from '../unhandledRejectionBridge';
import {
act,
AuthenticatedClient,
createTauriEventBridge,
expect,
fireEvent,
installTauriRuntime,
it,
React,
render,
resetTauriRuntime,
screen,
testAuthUser,
vi,
waitFor,
} from './harness';
type AuthEventBridge = {
listeners: Map<string, (event: { payload: unknown }) => void>;
emit: (event: string, payload: unknown) => void;
};
/**
* 注入 Tauri 事件替身。
*
* jsdom 没有 `__TAURI_INTERNALS__`,`subscribeTauriEvent` 会退回
* `window.__TAURI__.event.listen`,所以这里按同一入口登记回调。
* `window.__TAURI__.event.listen`,所以按同一入口登记回调:安装走
* harness.installTauriRuntime,事件注册 / 派发由 harness.createTauriEventBridge 负责。
*/
function installTauriBridge(
invoke: (command: string, args?: Record<string, unknown>) => Promise<unknown>,
options: { withEvents?: boolean } = {},
): AuthEventBridge {
const listeners = new Map<string, (event: { payload: unknown }) => void>();
window.__TAURI__ = {
core: { invoke },
...(options.withEvents
? {
event: {
listen: async (
event: string,
handler: (payload: { payload: unknown }) => void,
) => {
listeners.set(event, handler);
return () => listeners.delete(event);
},
},
}
: {}),
} as never;
return {
listeners,
emit: (event, payload) => {
const handler = listeners.get(event);
if (handler) handler({ payload });
},
};
}
function renderAuthSurface(
invoke: (command: string, args?: Record<string, unknown>) => Promise<unknown>,
options: { withEvents?: boolean } = {},
) {
const bridge = installTauriBridge(invoke, options);
const bridge = createTauriEventBridge();
installTauriRuntime({
core: { invoke },
...(options.withEvents ? { event: { listen: bridge.listen } } : {}),
});
render(
React.createElement(AuthenticatedClient, null, ({ user, logout }) =>
React.createElement(
'main',
{ 'aria-label': '已登录' },
React.createElement('span', null, user.displayName),
// React 19 的 createElement 的 children 形参只接受 ReactNode,渲染函数子节点改传进 props。
React.createElement(AuthenticatedClient, {
children: ({ user, logout }) =>
React.createElement(
'button',
{ type: 'button', onClick: logout },
'退出',
'main',
{ 'aria-label': '已登录' },
React.createElement('span', null, user.displayName),
React.createElement(
'button',
{ type: 'button', onClick: logout },
'退出',
),
),
),
),
}),
);
return bridge;
}
@@ -118,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 () => {
@@ -21,6 +21,7 @@ import {
createProjectChatRuntimeHarness,
expect,
fireEvent,
installTauriRuntime,
it,
pickProjectFromLauncher,
React,
@@ -108,10 +109,10 @@ async function openDirectCodexSurface(
return chatHarness.invoke(command, args);
},
);
window.__TAURI__ = {
installTauriRuntime({
core: { invoke },
event: { listen: chatHarness.listen },
};
});
renderLauncherProjectsAt('/?launcher');
pickProjectFromLauncher(DYNAMIC_GAME_PROJECT_PATH);
const surface = await screen.findByLabelText('陶泥儿项目对话');
@@ -325,7 +326,7 @@ export function registerChatComposerControlTests() {
const manifestAssets = [
{
id: 'asset:hero',
kind: 'character',
kind: 'character' as const,
mediaType: 'image/png',
localPath: 'assets/hero.png',
source: { kind: 'uploaded' as const },
@@ -1,9 +1,12 @@
import type { Mock } from 'vitest';
import {
act,
App,
createProjectChatRuntimeHarness,
expect,
fireEvent,
installTauriRuntime,
it,
React,
render,
@@ -12,16 +15,43 @@ import {
waitFor,
} from './harness';
type ChatRuntimeHarness = ReturnType<typeof createProjectChatRuntimeHarness>;
/**
* 把夹具的 invoke / listen 装进 `window.__TAURI__`:转换点只有 harness.installTauriRuntime 一处。
*/
function installChatRuntime(harness: ChatRuntimeHarness) {
installTauriRuntime({
core: { invoke: harness.invoke },
event: { listen: harness.listen },
});
}
/**
* harness.invoke 的返回类型被 harness 内全部命令分支收敛成字面量联合,用例覆盖命令时要返回
* 联合外的自定义值(例如导入文件路径),这里按固定签名 mock 取用,只放宽返回值类型。
*/
type LooseInvokeMock = Mock<
[command: string, args?: Record<string, unknown>],
Promise<unknown>
>;
function looseInvoke(harness: ChatRuntimeHarness): LooseInvokeMock {
return harness.invoke as unknown as LooseInvokeMock;
}
// App 的 props 类型未导出,React 19 的 createElement 重载推断不出参数类型,这里显式转成组件类型。
const AppComponent = App as React.ComponentType<
NonNullable<React.ComponentProps<typeof App>>
>;
function renderDesignAgent(
harness: ReturnType<typeof createProjectChatRuntimeHarness>,
) {
window.__TAURI__ = {
core: { invoke: harness.invoke },
event: { listen: harness.listen },
};
installChatRuntime(harness);
window.history.pushState({}, '', '/');
return render(
React.createElement(App, {
React.createElement(AppComponent, {
initialProjectPath: harness.projectPath,
planningStartMode: true,
}),
@@ -127,13 +157,14 @@ export function registerDesignAgentSurfaceTests() {
designAgentView: designConversationView(),
designAgentContinueView: designConversationView(),
});
const originalInvoke = harness.invoke.getMockImplementation()!;
const invoke = looseInvoke(harness);
const originalInvoke = invoke.getMockImplementation()!;
let failImport = true;
let finishImport!: () => void;
const importGate = new Promise<void>((resolve) => {
finishImport = resolve;
});
harness.invoke.mockImplementation(async (command, args) => {
invoke.mockImplementation(async (command, args) => {
if (command === 'import_design_workspace_file') {
await importGate;
if (args?.fileName === '补充.md' && failImport)
@@ -224,8 +255,9 @@ export function registerDesignAgentSurfaceTests() {
const harness = createProjectChatRuntimeHarness({
designAgentView: designApprovalView(),
});
const originalInvoke = harness.invoke.getMockImplementation()!;
harness.invoke.mockImplementation(async (command, args) =>
const invoke = looseInvoke(harness);
const originalInvoke = invoke.getMockImplementation()!;
invoke.mockImplementation(async (command, args) =>
command === 'import_design_workspace_file'
? 'references/需求.md'
: originalInvoke(command, args),
@@ -367,13 +399,10 @@ export function registerDesignAgentSurfaceTests() {
],
},
});
window.__TAURI__ = {
core: { invoke: harness.invoke },
event: { listen: harness.listen },
};
installChatRuntime(harness);
window.history.pushState({}, '', '/');
render(
React.createElement(App, {
React.createElement(AppComponent, {
initialProjectPath: harness.projectPath,
planningStartMode: true,
}),
@@ -411,9 +440,10 @@ export function registerDesignAgentSurfaceTests() {
const harness = createProjectChatRuntimeHarness({
designAgentView: designConversationView(),
});
const originalInvoke = harness.invoke.getMockImplementation()!;
const invoke = looseInvoke(harness);
const originalInvoke = invoke.getMockImplementation()!;
let finish!: (view: unknown) => void;
harness.invoke.mockImplementation((command, args) =>
invoke.mockImplementation((command, args) =>
command === 'continue_design_agent_session'
? new Promise((resolve) => {
finish = resolve;
@@ -467,12 +497,13 @@ export function registerDesignAgentSurfaceTests() {
const harness = createProjectChatRuntimeHarness({
designAgentView: { ...designConversationView(), messages: [history] },
});
const originalInvoke = harness.invoke.getMockImplementation()!;
const invoke = looseInvoke(harness);
const originalInvoke = invoke.getMockImplementation()!;
const requests: {
clientTurnId: string;
finish: (view: unknown) => void;
}[] = [];
harness.invoke.mockImplementation((command, args) =>
invoke.mockImplementation((command, args) =>
command === 'continue_design_agent_session'
? new Promise((resolve) => {
requests.push({
@@ -489,7 +520,7 @@ export function registerDesignAgentSurfaceTests() {
await setComposerText(input, '继续');
fireEvent.submit(input.closest('form')!);
await waitFor(() => expect(requests).toHaveLength(1));
const request = requests[0];
const request = requests[0]!;
const messageId = `${request.clientTurnId}:response:0`;
const reply = '整块返回也逐步显示';
const terminal = {
@@ -543,7 +574,7 @@ export function registerDesignAgentSurfaceTests() {
screen.getByRole('button', { name: '思考中' }).hasAttribute('disabled'),
).toBe(true);
expect(screen.queryByText('迟到旧回复')).toBeNull();
await act(async () => requests[1].finish(terminal));
await act(async () => requests[1]!.finish(terminal));
});
}
@@ -732,13 +763,10 @@ export function registerDesignAgentSurfaceTests() {
const harness = createProjectChatRuntimeHarness({
designAgentView: designApprovalView(),
});
window.__TAURI__ = {
core: { invoke: harness.invoke },
event: { listen: harness.listen },
};
installChatRuntime(harness);
window.history.pushState({}, '', '/');
render(
React.createElement(App, {
React.createElement(AppComponent, {
initialProjectPath: harness.projectPath,
planningStartMode: true,
}),
@@ -778,13 +806,10 @@ export function registerDesignAgentSurfaceTests() {
},
},
});
window.__TAURI__ = {
core: { invoke: harness.invoke },
event: { listen: harness.listen },
};
installChatRuntime(harness);
window.history.pushState({}, '', '/');
render(
React.createElement(App, {
React.createElement(AppComponent, {
initialProjectPath: harness.projectPath,
planningStartMode: true,
}),
@@ -816,13 +841,10 @@ export function registerDesignAgentSurfaceTests() {
designAgentView: designConversationView(),
designAgentContinueView: designConversationView(),
});
window.__TAURI__ = {
core: { invoke: harness.invoke },
event: { listen: harness.listen },
};
installChatRuntime(harness);
window.history.pushState({}, '', '/');
render(
React.createElement(App, {
React.createElement(AppComponent, {
initialProjectPath: harness.projectPath,
planningStartMode: true,
}),
@@ -886,13 +908,10 @@ export function registerDesignAgentSurfaceTests() {
const harness = createProjectChatRuntimeHarness({
designAgentView: designHistoryView(),
});
window.__TAURI__ = {
core: { invoke: harness.invoke },
event: { listen: harness.listen },
};
installChatRuntime(harness);
window.history.pushState({}, '', '/');
render(
React.createElement(App, {
React.createElement(AppComponent, {
initialProjectPath: harness.projectPath,
planningStartMode: true,
}),
@@ -911,9 +930,9 @@ export function registerDesignAgentSurfaceTests() {
(element) => element.getAttribute('data-agent-content') === 'process',
),
).toBe(true);
fireEvent.click(summaries[0]);
expect(details[0].open).toBe(true);
expect(details[1].open).toBe(false);
await waitFor(() => expect(summaries[0].textContent).toBe('思考过程'));
fireEvent.click(summaries[0]!);
expect(details[0]!.open).toBe(true);
expect(details[1]!.open).toBe(false);
await waitFor(() => expect(summaries[0]!.textContent).toBe('思考过程'));
});
}
@@ -15,6 +15,11 @@ 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,
resetTauriRuntime,
} from '../tauriRuntimeFake';
const nativeClipboardMock = vi.hoisted(() => ({
text: '',
@@ -195,20 +200,22 @@ function pickProjectFromLauncher(
projectPath: string,
action: '打开项目' | '新建项目' = '打开项目',
) {
const invoke = window.__TAURI__?.core?.invoke;
if (!invoke) {
const tauri = window.__TAURI__;
const invoke = tauri?.core?.invoke;
if (!tauri || !invoke) {
throw new Error('Tauri invoke is not configured');
}
window.__TAURI__ = {
...window.__TAURI__,
installTauriRuntime({
...tauri,
core: {
...window.__TAURI__.core,
invoke: async (command, args) =>
...tauri.core,
// 泛型 invoke 无法由非泛型包装函数精确表达:命中目录选择命令时固定返回路径,其余转发。
invoke: (async (command: string, args?: Record<string, unknown>) =>
command === 'pick_local_project_directory'
? projectPath
: invoke(command, args),
: invoke(command, args)) as unknown as typeof invoke,
},
};
});
fireEvent.click(screen.getByRole('button', { name: action }));
}
@@ -787,13 +794,13 @@ function createProjectChatRuntimeHarness({
manifestInvalidatedHandlers.delete(
handler as unknown as ManifestInvalidatedHandler,
);
if (progressHandler === handler) {
if ((progressHandler as unknown) === handler) {
progressHandler = null;
}
if (designAgentUpdateHandler === handler) {
if ((designAgentUpdateHandler as unknown) === handler) {
designAgentUpdateHandler = null;
}
if (directThreadNotifyHandler === handler) {
if ((directThreadNotifyHandler as unknown) === handler) {
directThreadNotifyHandler = null;
}
};
@@ -933,6 +940,14 @@ function createProjectChatRuntimeHarness({
};
}
/**
* 安装测试用 Tauri 替身。
*
* 定义已收进 `tests/tauriRuntimeFake.ts`(全仓唯一安装点),这里只转出,供 appSurface 各
* suite 沿用既有的 `./harness` 导入路径。
*/
export { createTauriEventBridge, installTauriRuntime, resetTauriRuntime };
export function installResizeObserverStub() {
let observerCount = 0;
let observerDisconnected = false;
@@ -1000,7 +1015,7 @@ afterEach(() => {
window.history.pushState({}, '', '/');
window.localStorage.clear();
window.sessionStorage.clear();
delete window.__TAURI__;
resetTauriRuntime();
vi.restoreAllMocks();
if (originalCreateObjectUrl) {
@@ -11,6 +11,7 @@ import {
findResourceSelectButton,
fireEvent,
getResourceSelectButton,
installTauriRuntime,
it,
nativeClipboardMock,
pickProjectFromLauncher,
@@ -28,6 +29,12 @@ import {
within,
} from './harness';
// App 的 props 参数带默认值,`React.ComponentProps<typeof App>` 会带上 undefined,
// createElement 因此推断不出 props;这里收窄成非空组件类型,运行时仍是同一个组件。
const TestApp = App as React.ComponentType<
NonNullable<React.ComponentProps<typeof App>>
>;
/**
* 外壳级浮层提示的挂点。
*
@@ -126,7 +133,7 @@ export function registerClientHomeTests() {
}
throw new Error(`unexpected invoke ${command}`);
});
window.__TAURI__ = { core: { invoke } };
installTauriRuntime({ core: { invoke } });
renderLauncherAt('/?launcher');
// 挂载即加载目录,触发按钮直接落在默认模型上,不出现「选择模型」空态。
@@ -174,7 +181,7 @@ export function registerClientHomeTests() {
if (command === 'read_game_creator_app_config') return { config: {} };
throw new Error(`unexpected invoke ${command}`);
});
window.__TAURI__ = { core: { invoke } };
installTauriRuntime({ core: { invoke } });
renderLauncherAt('/?launcher');
await waitFor(() =>
expect(invoke).toHaveBeenCalledWith('get_game_template_library_access'),
@@ -256,7 +263,7 @@ export function registerClientHomeTests() {
}
throw new Error(`unexpected invoke ${command}`);
});
window.__TAURI__ = { core: { invoke } };
installTauriRuntime({ core: { invoke } });
renderLauncherAt('/?launcher');
// 首页推荐位只展示封面、标题、运行时与已下载徽标,本机灵感图库已随模板库上线删除。
@@ -308,7 +315,7 @@ export function registerClientHomeTests() {
assets: [
{
id: 'live-hero',
kind: 'icon-spritesheet',
kind: 'icon-spritesheet' as const,
mediaType: 'image/png',
localPath: 'assets/live-hero.png',
source: { kind: 'generated' as const, taskId: 'art-asset-plan' },
@@ -398,7 +405,7 @@ export function registerClientHomeTests() {
throw new Error(`unexpected invoke ${command}`);
},
);
window.__TAURI__ = { core: { invoke } };
installTauriRuntime({ core: { invoke } });
render(
React.createElement(WorkspaceLauncherShell, {
currentUser: testAuthUser,
@@ -540,10 +547,10 @@ export function registerClientHomeTests() {
return runtimeHarness.invoke(command, args);
},
);
window.__TAURI__ = {
installTauriRuntime({
core: { invoke },
event: { listen: runtimeHarness.listen },
};
});
render(
React.createElement(WorkspaceLauncherShell, {
currentUser: testAuthUser,
@@ -713,10 +720,10 @@ export function registerClientHomeTests() {
return runtimeHarness.invoke(command, args);
},
);
window.__TAURI__ = {
installTauriRuntime({
core: { invoke },
event: { listen: runtimeHarness.listen },
};
});
render(
React.createElement(WorkspaceLauncherShell, {
currentUser: testAuthUser,
@@ -820,7 +827,7 @@ export function registerClientHomeTests() {
throw new Error(`unexpected invoke ${command}`);
},
);
window.__TAURI__ = { core: { invoke } };
installTauriRuntime({ core: { invoke } });
renderLauncherProjectsAt('/?launcher');
expect(screen.getByLabelText('GameAgent 客户端')).not.toBeNull();
@@ -1017,7 +1024,7 @@ export function registerClientHomeTests() {
return undefined;
},
);
window.__TAURI__ = { core: { invoke } };
installTauriRuntime({ core: { invoke } });
renderLauncherAt('/?launcher');
fireEvent.click(screen.getByRole('button', { name: '充值' }));
@@ -1117,7 +1124,7 @@ export function registerClientHomeTests() {
}
return undefined;
});
window.__TAURI__ = { core: { invoke } };
installTauriRuntime({ core: { invoke } });
renderLauncherAt('/?launcher');
fireEvent.click(screen.getByRole('button', { name: '我的' }));
@@ -1166,7 +1173,9 @@ export function registerClientHomeTests() {
export function registerHomeProjectCreationTests() {
it('scopes the 策划补全 option to the game entry and clears it on creation-type switch', () => {
window.__TAURI__ = { core: { invoke: vi.fn(async () => null) } };
installTauriRuntime({
core: { invoke: vi.fn(async () => null) },
});
renderLauncherAt('/?launcher', 'home', true);
const creationTypes = screen.getByRole('group', { name: '创作类型' });
@@ -1223,10 +1232,10 @@ export function registerHomeProjectCreationTests() {
return chatHarness.invoke(command, args);
},
);
window.__TAURI__ = {
installTauriRuntime({
core: { invoke },
event: { listen: chatHarness.listen },
};
});
renderLauncherAt('/?launcher', 'home', true);
fireEvent.click(screen.getByRole('checkbox', { name: '策划补全' }));
@@ -1279,7 +1288,7 @@ export function registerHomeProjectCreationTests() {
'genarrative-ai-game-creator.recent-workspaces.v1',
JSON.stringify(['/tmp/authorized-game']),
);
window.__TAURI__ = { core: { invoke } };
installTauriRuntime({ core: { invoke } });
renderLauncherProjectsAt('/?launcher');
expect(await screen.findByText('authorized-game')).not.toBeNull();
@@ -1329,7 +1338,7 @@ export function registerHomeProjectCreationTests() {
throw new Error(`unexpected invoke ${command}`);
},
);
window.__TAURI__ = { core: { invoke } };
installTauriRuntime({ core: { invoke } });
renderLauncherProjectsAt('/?launcher');
pickProjectFromLauncher('/tmp/missing-game');
@@ -1371,7 +1380,7 @@ export function registerHomeProjectCreationTests() {
}
throw new Error(`unexpected invoke ${command}`);
});
window.__TAURI__ = { core: { invoke } };
installTauriRuntime({ core: { invoke } });
renderLauncherProjectsAt('/?launcher');
@@ -1424,7 +1433,7 @@ export function registerHomeProjectCreationTests() {
throw new Error(`unexpected invoke ${command}`);
},
);
window.__TAURI__ = { core: { invoke } };
installTauriRuntime({ core: { invoke } });
renderLauncherProjectsAt('/?launcher');
fireEvent.click(screen.getByRole('button', { name: '打开项目' }));
@@ -1457,7 +1466,7 @@ export function registerHomeProjectCreationTests() {
}
throw new Error(`unexpected invoke ${command}`);
});
window.__TAURI__ = { core: { invoke } };
installTauriRuntime({ core: { invoke } });
renderLauncherProjectsAt('/?launcher');
fireEvent.click(screen.getByRole('button', { name: '打开项目' }));
@@ -1477,7 +1486,7 @@ export function registerHomeProjectCreationTests() {
}
throw new Error(`unexpected invoke ${command}`);
});
window.__TAURI__ = { core: { invoke } };
installTauriRuntime({ core: { invoke } });
renderLauncherProjectsAt('/?launcher');
const openButton = screen.getByRole('button', { name: '打开项目' });
@@ -1536,10 +1545,10 @@ export function registerHomeProjectCreationTests() {
return chatHarness.invoke(command, args);
},
);
window.__TAURI__ = {
installTauriRuntime({
core: { invoke },
event: { listen: chatHarness.listen },
};
});
renderLauncherAt('/?launcher', 'home', true);
const creationTypes = screen.getByRole('group', { name: '创作类型' });
@@ -1683,10 +1692,10 @@ export function registerHomeProjectCreationTests() {
return chatHarness.invoke(command, args);
},
);
window.__TAURI__ = {
installTauriRuntime({
core: { invoke },
event: { listen: chatHarness.listen },
};
});
renderLauncherAt('/?launcher', 'home', true);
const fileInput =
@@ -1796,7 +1805,7 @@ export function registerHomeProjectCreationTests() {
}
throw new Error(`unexpected invoke ${command}`);
});
window.__TAURI__ = { core: { invoke } };
installTauriRuntime({ core: { invoke } });
renderLauncherAt('/?launcher', 'home', true);
const promptInput = screen.getByLabelText('创作想法');
@@ -1868,7 +1877,7 @@ export function registerHomeProjectCreationTests() {
}
throw new Error(`unexpected invoke ${command}`);
});
window.__TAURI__ = { core: { invoke } };
installTauriRuntime({ core: { invoke } });
renderLauncherAt('/?launcher', 'home', true);
await act(async () => {
await vi.advanceTimersByTimeAsync(0);
@@ -1962,10 +1971,10 @@ export function registerHomeProjectCreationTests() {
return chatHarness.invoke(command, args);
},
);
window.__TAURI__ = {
installTauriRuntime({
core: { invoke },
event: { listen: chatHarness.listen },
};
});
renderLauncherAt('/?launcher', 'home', true);
// 做方案走立项链路:建项之后还要写策划运行时,这一步失败时项目目录已经存在。
@@ -2029,10 +2038,10 @@ export function registerHomeProjectCreationTests() {
return chatHarness.invoke(command, args);
},
);
window.__TAURI__ = {
installTauriRuntime({
core: { invoke },
event: { listen: chatHarness.listen },
};
});
renderLauncherAt('/?launcher', 'home', true);
if (modeLabel === '做方案') {
@@ -2126,10 +2135,10 @@ export function registerHomeProjectCreationTests() {
return chatHarness.invoke(command, args);
},
);
window.__TAURI__ = {
installTauriRuntime({
core: { invoke },
event: { listen: chatHarness.listen },
};
});
renderLauncherAt('/?launcher', 'home', true);
fireEvent.click(screen.getByRole('button', { name: '做方案' }));
@@ -2264,10 +2273,10 @@ export function registerHomeProjectCreationTests() {
return chatHarness.invoke(command, args);
},
);
window.__TAURI__ = {
installTauriRuntime({
core: { invoke },
event: { listen: chatHarness.listen },
};
});
renderLauncherAt('/?launcher', 'home', true);
fireEvent.click(screen.getByRole('button', { name: '做方案' }));
@@ -2330,10 +2339,10 @@ export function registerHomeProjectCreationTests() {
return runtimeHarness.invoke(command, args);
},
);
window.__TAURI__ = {
installTauriRuntime({
core: { invoke },
event: { listen: runtimeHarness.listen },
};
});
renderLauncherAt('/?launcher', 'home', true);
const promptInput = screen.getByLabelText('创作想法');
@@ -2360,7 +2369,7 @@ export function registerHomeProjectCreationTests() {
const committedAssets = [
{
id: 'direct-art-spec',
kind: 'icon-spec',
kind: 'icon-spec' as const,
mediaType: 'image/png',
localPath: 'assets/art-spec.png',
source: {
@@ -2370,7 +2379,7 @@ export function registerHomeProjectCreationTests() {
},
{
id: 'direct-game-background',
kind: 'scene',
kind: 'scene' as const,
mediaType: 'image/png',
localPath: 'assets/direct-game-background.png',
source: {
@@ -2380,7 +2389,7 @@ export function registerHomeProjectCreationTests() {
},
{
id: 'direct-art-spritesheet',
kind: 'icon-spritesheet',
kind: 'icon-spritesheet' as const,
mediaType: 'image/png',
localPath: 'assets/art-spritesheet.png',
source: {
@@ -2506,10 +2515,10 @@ export function registerHomeProjectCreationTests() {
throw new Error(`unexpected invoke ${command}`);
},
);
window.__TAURI__ = { core: { invoke } };
installTauriRuntime({ core: { invoke } });
render(
React.createElement(App, {
React.createElement(TestApp, {
initialProjectPath: projectPath,
initialProjectManifest: manifest,
initialPlanningPrompt: '继续修改已有项目',
@@ -2590,10 +2599,10 @@ export function registerHomeProjectCreationTests() {
throw new Error(`unexpected invoke ${command}`);
},
);
window.__TAURI__ = { core: { invoke } };
installTauriRuntime({ core: { invoke } });
render(
React.createElement(App, {
React.createElement(TestApp, {
initialProjectPath: projectPath,
initialProjectManifest: manifest,
initialPlanningPrompt: '生成一个游戏',
@@ -2624,7 +2633,7 @@ export function registerHomeProjectCreationTests() {
// 重进项目:被拒消息不得从历史里被重放,拒绝文案也不该被当成历史条目。
render(
React.createElement(App, {
React.createElement(TestApp, {
initialProjectPath: projectPath,
initialProjectManifest: manifest,
}),
@@ -2701,7 +2710,7 @@ export function registerRecentProjectsTests() {
throw new Error(`unexpected invoke ${command}`);
},
);
window.__TAURI__ = { core: { invoke } };
installTauriRuntime({ core: { invoke } });
window.localStorage.setItem(
'genarrative-ai-game-creator.recent-workspaces.v1',
JSON.stringify(paths),
@@ -2772,7 +2781,7 @@ export function registerRecentProjectsTests() {
return undefined;
},
);
window.__TAURI__ = { core: { invoke } };
installTauriRuntime({ core: { invoke } });
renderLauncherAt('/?launcher');
expect(screen.getByLabelText('最近项目')).not.toBeNull();
@@ -2831,7 +2840,11 @@ export function registerRecentProjectsTests() {
'星轨夜航',
);
const invoke = vi.fn(
async (command: string, args?: Record<string, unknown>) => {
// 回调体内要读 invoke.mock.calls,显式标注返回类型以打断类型推断的自引用环。
async (
command: string,
args?: Record<string, unknown>,
): Promise<unknown> => {
if (command === 'get_design_agent_runtime_mode') return null;
if (command === 'inspect_local_project_directory') {
const renamed =
@@ -2858,7 +2871,7 @@ export function registerRecentProjectsTests() {
throw new Error(`unexpected invoke ${command}`);
},
);
window.__TAURI__ = { core: { invoke } };
installTauriRuntime({ core: { invoke } });
window.localStorage.setItem(
'genarrative-ai-game-creator.recent-workspaces.v1',
JSON.stringify([projectPath]),
@@ -2906,7 +2919,7 @@ export function registerRecentProjectsTests() {
throw new Error(`unexpected invoke ${command}`);
},
);
window.__TAURI__ = { core: { invoke } };
installTauriRuntime({ core: { invoke } });
window.localStorage.setItem(
'genarrative-ai-game-creator.recent-workspaces.v1',
JSON.stringify([projectPath]),
@@ -2955,7 +2968,7 @@ export function registerRecentProjectsTests() {
throw new Error(`unexpected invoke ${command}`);
},
);
window.__TAURI__ = { core: { invoke } };
installTauriRuntime({ core: { invoke } });
window.localStorage.setItem(
'genarrative-ai-game-creator.recent-workspaces.v1',
JSON.stringify(['/tmp/recent-one']),
@@ -2994,7 +3007,7 @@ export function registerRecentProjectsTests() {
}
throw new Error(`unexpected invoke ${command}`);
});
window.__TAURI__ = { core: { invoke } };
installTauriRuntime({ core: { invoke } });
renderLauncherProjectsAt('/?launcher');
pickProjectFromLauncher('/tmp/open-failed-game');
@@ -3044,7 +3057,7 @@ export function registerRecentProjectsTests() {
throw new Error(`unexpected invoke ${command}`);
},
);
window.__TAURI__ = { core: { invoke } };
installTauriRuntime({ core: { invoke } });
window.localStorage.setItem(
'genarrative-ai-game-creator.recent-workspaces.v1',
JSON.stringify([
@@ -3191,7 +3204,7 @@ export function registerRecentProjectsTests() {
throw new Error(`unexpected invoke ${command}`);
},
);
window.__TAURI__ = { core: { invoke } };
installTauriRuntime({ core: { invoke } });
window.localStorage.setItem(
'genarrative-ai-game-creator.recent-workspaces.v1',
JSON.stringify(['/tmp/refreshable-game']),
@@ -3256,7 +3269,7 @@ export function registerRecentProjectsTests() {
throw new Error(`unexpected invoke ${command}`);
},
);
window.__TAURI__ = { core: { invoke } };
installTauriRuntime({ core: { invoke } });
renderLauncherAt('/?launcher');
expect(screen.getByLabelText('最近项目')).not.toBeNull();
@@ -3304,7 +3317,7 @@ export function registerRecentProjectsTests() {
}
throw new Error(`unexpected invoke ${command}`);
});
window.__TAURI__ = { core: { invoke } };
installTauriRuntime({ core: { invoke } });
renderLauncherProjectsAt('/?launcher');
fireEvent.click(screen.getByRole('button', { name: '打开项目' }));
@@ -3334,7 +3347,7 @@ export function registerRecentProjectsTests() {
throw new Error(`unexpected invoke ${command}`);
},
);
window.__TAURI__ = { core: { invoke } };
installTauriRuntime({ core: { invoke } });
renderLauncherProjectsAt('/?launcher');
fireEvent.click(screen.getByRole('button', { name: '新建项目' }));
@@ -3375,7 +3388,7 @@ export function registerRecentProjectsTests() {
},
);
const confirm = vi.spyOn(window, 'confirm').mockReturnValue(false);
window.__TAURI__ = { core: { invoke } };
installTauriRuntime({ core: { invoke } });
renderLauncherProjectsAt('/?launcher');
fireEvent.click(screen.getByRole('button', { name: '新建项目' }));
@@ -3421,7 +3434,7 @@ export function registerRecentProjectsTests() {
},
);
const confirm = vi.spyOn(window, 'confirm').mockReturnValue(true);
window.__TAURI__ = { core: { invoke } };
installTauriRuntime({ core: { invoke } });
renderLauncherProjectsAt('/?launcher');
fireEvent.click(screen.getByRole('button', { name: '新建项目' }));
@@ -3474,7 +3487,7 @@ export function registerRecentProjectsTests() {
throw new Error(`unexpected invoke ${command}`);
},
);
window.__TAURI__ = { core: { invoke } };
installTauriRuntime({ core: { invoke } });
renderLauncherProjectsAt('/?launcher');
fireEvent.click(screen.getByRole('button', { name: '新建项目' }));
@@ -3501,7 +3514,7 @@ export function registerRecentProjectsTests() {
}
throw new Error(`unexpected invoke ${command}`);
});
window.__TAURI__ = { core: { invoke } };
installTauriRuntime({ core: { invoke } });
renderLauncherProjectsAt('/?launcher');
fireEvent.click(screen.getByRole('button', { name: '新建项目' }));
@@ -3568,10 +3581,10 @@ export function registerRecentProjectsTests() {
return supervisorHarness.invoke(command, args);
},
);
window.__TAURI__ = {
installTauriRuntime({
core: { invoke },
event: { listen: supervisorHarness.listen },
};
});
renderLauncherAt('/?launcher', 'home', true);
// 设置 → 工作区:默认位置就是不选目录,仍然落在 AGC 管理的应用数据目录。
@@ -10,9 +10,11 @@ import {
emptyProjectPolicy,
expect,
fireEvent,
installTauriRuntime,
it,
mockRoleAgentReply,
renderAppAt,
resetTauriRuntime,
roleAgentMockReply,
screen,
vi,
@@ -192,7 +194,7 @@ export function registerProjectConversationTests() {
throw new Error(`unexpected invoke ${command}`);
},
);
window.__TAURI__ = { core: { invoke } };
installTauriRuntime({ core: { invoke } });
renderAppAt('/?main&projectPath=%2Ftmp%2Fauthorized-game');
invoke.mockClear();
fireEvent.click(screen.getByRole('button', { name: /拆解创作方向/ }));
@@ -266,7 +268,7 @@ export function registerProjectConversationTests() {
throw new Error(`unexpected invoke ${command}`);
},
);
window.__TAURI__ = { core: { invoke } };
installTauriRuntime({ core: { invoke } });
renderAppAt('/?main&projectPath=%2Ftmp%2Fauthorized-game');
invoke.mockClear();
fireEvent.click(screen.getByRole('button', { name: /拆解创作方向/ }));
@@ -352,7 +354,7 @@ export function registerProjectConversationTests() {
throw new Error(`unexpected invoke ${command}`);
},
);
window.__TAURI__ = { core: { invoke } };
installTauriRuntime({ core: { invoke } });
renderAppAt('/?main&projectPath=%2Ftmp%2Fauthorized-game');
invoke.mockClear();
fireEvent.click(screen.getByRole('button', { name: /拆解创作方向/ }));
@@ -428,7 +430,7 @@ export function registerProjectConversationTests() {
throw new Error(`unexpected invoke ${command}`);
},
);
window.__TAURI__ = { core: { invoke } };
installTauriRuntime({ core: { invoke } });
renderAppAt('/?main&projectPath=%2Ftmp%2Fauthorized-game');
fireEvent.click(screen.getByRole('button', { name: /拆解创作方向/ }));
const input = await screen.findByLabelText('Agent 对话内容');
@@ -520,7 +522,7 @@ export function registerProjectConversationTests() {
throw new Error(`unexpected invoke ${command}`);
},
);
window.__TAURI__ = { core: { invoke } };
installTauriRuntime({ core: { invoke } });
renderAppAt('/?main&projectPath=%2Ftmp%2Fauthorized-game');
fireEvent.click(screen.getByRole('button', { name: /拆解创作方向/ }));
const input = await screen.findByLabelText('Agent 对话内容');
@@ -616,7 +618,7 @@ export function registerProjectConversationTests() {
throw new Error(`unexpected invoke ${command}`);
},
);
window.__TAURI__ = { core: { invoke } };
installTauriRuntime({ core: { invoke } });
renderAppAt('/?main&projectPath=%2Ftmp%2Fauthorized-game');
fireEvent.click(screen.getByRole('button', { name: /拆解创作方向/ }));
@@ -685,12 +687,12 @@ export function registerProjectConversationTests() {
throw new Error(`unexpected invoke ${command}`);
},
);
window.__TAURI__ = { core: { invoke } };
installTauriRuntime({ core: { invoke } });
renderAppAt('/?main&projectPath=%2Ftmp%2Fauthorized-game');
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();
@@ -787,7 +789,7 @@ export function registerProjectConversationTests() {
}
throw new Error(`unexpected listen ${eventName}`);
});
window.__TAURI__ = { core: { invoke }, event: { listen } };
installTauriRuntime({ core: { invoke }, event: { listen } });
renderAppAt('/?main&projectPath=%2Ftmp%2Fauthorized-game');
fireEvent.click(screen.getByRole('button', { name: /拆解创作方向/ }));
const input = await screen.findByLabelText('Agent 对话内容');
@@ -876,7 +878,7 @@ export function registerProjectConversationTests() {
throw new Error(`unexpected invoke ${command}`);
},
);
window.__TAURI__ = { core: { invoke } };
installTauriRuntime({ core: { invoke } });
renderAppAt('/?main&projectPath=%2Ftmp%2Fauthorized-game');
fireEvent.click(screen.getByRole('button', { name: /拆解创作方向/ }));
expect(await screen.findByText('旧 Agent 历史消息')).not.toBeNull();
File diff suppressed because it is too large Load Diff
@@ -11,6 +11,7 @@ import {
fireEvent,
GAME_CREATION_AGENT_RUN_SCHEMA_VERSION,
type GameCreationAgentRunTrace,
installTauriRuntime,
it,
renderLauncherAt,
screen,
@@ -111,6 +112,9 @@ export function registerAgentStatusDerivationTests() {
pass: 1,
agent: 'Evaluator',
phase: 'evaluation',
taskId: null,
group: null,
role: null,
status: 'passed',
inputPaths: ['.agent/passes/pass-1/draft.json', '.agent/findings.md'],
outputPaths: ['.agent/findings.md'],
@@ -119,11 +123,14 @@ export function registerAgentStatusDerivationTests() {
{
toolId: 'agent.evaluate',
status: 'ok',
inputPaths: [],
outputPaths: [],
summary: 'Evaluator 质量评审',
},
],
},
],
artifacts: [],
taskGraph: {
goal: '做一个厨房弹幕游戏',
readyTaskIds: ['audio-director'],
@@ -220,7 +227,7 @@ export function registerRuntimeSettingsTests() {
}
throw new Error(`unexpected invoke ${command}`);
});
window.__TAURI__ = { core: { invoke } };
installTauriRuntime({ core: { invoke } });
renderLauncherAt('/?launcher');
fireEvent.click(screen.getByRole('button', { name: '配置' }));
@@ -269,7 +276,7 @@ export function registerRuntimeSettingsTests() {
}
throw new Error(`unexpected invoke ${command}`);
});
window.__TAURI__ = { core: { invoke } };
installTauriRuntime({ core: { invoke } });
renderLauncherAt('/?launcher');
fireEvent.click(screen.getByRole('button', { name: '配置' }));
@@ -338,7 +345,7 @@ export function registerRuntimeSettingsTests() {
throw new Error(`unexpected invoke ${command}`);
},
);
window.__TAURI__ = { core: { invoke } };
installTauriRuntime({ core: { invoke } });
renderLauncherAt('/?launcher');
fireEvent.click(screen.getByRole('button', { name: '配置' }));
@@ -412,7 +419,7 @@ export function registerRuntimeSettingsTests() {
throw new Error(`unexpected invoke ${command}`);
},
);
window.__TAURI__ = { core: { invoke } };
installTauriRuntime({ core: { invoke } });
renderLauncherAt('/?launcher');
fireEvent.click(screen.getByRole('button', { name: '配置' }));
@@ -484,7 +491,7 @@ export function registerRuntimeSettingsTests() {
throw new Error(`unexpected invoke ${command}`);
},
);
window.__TAURI__ = { core: { invoke } };
installTauriRuntime({ core: { invoke } });
renderLauncherAt('/?launcher');
fireEvent.click(screen.getByRole('button', { name: '配置' }));
@@ -536,7 +543,7 @@ export function registerRuntimeSettingsTests() {
throw new Error(`unexpected invoke ${command}`);
},
);
window.__TAURI__ = { core: { invoke } };
installTauriRuntime({ core: { invoke } });
renderLauncherAt('/?launcher');
fireEvent.click(screen.getByRole('button', { name: '配置' }));
@@ -617,7 +624,7 @@ export function registerRuntimeSettingsTests() {
}
throw new Error(`unexpected invoke ${command}`);
});
window.__TAURI__ = { core: { invoke } };
installTauriRuntime({ core: { invoke } });
document.documentElement.style.overflow = 'auto';
document.body.style.overflow = 'scroll';
renderLauncherAt('/?launcher');
@@ -659,7 +666,7 @@ export function registerRuntimeSettingsTests() {
}
throw new Error(`unexpected invoke ${command}`);
});
window.__TAURI__ = { core: { invoke } };
installTauriRuntime({ core: { invoke } });
renderLauncherAt('/?launcher');
fireEvent.click(screen.getByRole('button', { name: '配置' }));
@@ -715,7 +722,7 @@ export function registerRuntimeSettingsTests() {
}
throw new Error(`unexpected invoke ${command}`);
});
window.__TAURI__ = { core: { invoke } };
installTauriRuntime({ core: { invoke } });
renderLauncherAt('/?launcher');
// 首页模型选择器挂载后会在目录请求之后读取一次配置,等它完成再打开对话框。
@@ -58,8 +58,8 @@ export function registerToolCallGroupTests() {
'running',
'completed',
]);
expect(within(rows[0]).getByText('失败')).not.toBeNull();
expect(within(rows[2]).queryByText('失败')).toBeNull();
expect(within(rows[0]!).getByText('失败')).not.toBeNull();
expect(within(rows[2]!).queryByText('失败')).toBeNull();
});
it('summarizes the group as its own operation count', () => {
@@ -458,9 +458,9 @@ export function registerToolCallGroupTests() {
view.rerender(
React.createElement(ToolCallGroup, {
calls: [
{ ...calls[0], status: 'completed' as const, updatedAt: lastEnd },
{ ...calls[0]!, status: 'completed' as const, updatedAt: lastEnd },
{
...calls[1],
...calls[1]!,
status: 'completed' as const,
updatedAt: lastEnd - 400,
},
@@ -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,11 +43,12 @@ function reportCalls() {
}
function renderLogin(invokeAuth: AuthInvoke) {
window.__TAURI__ = { core: { invoke: invokeAuth } } as never;
installTauriRuntime({ core: { invoke: invokeAuth } });
render(
createElement(AuthenticatedClient, null, () =>
createElement('main', { 'aria-label': '已登录' }),
),
// React 19 的 createElement 的 children 形参只接受 ReactNode,渲染函数子节点改编进 props。
createElement(AuthenticatedClient, {
children: () => createElement('main', { 'aria-label': '已登录' }),
}),
);
}
@@ -67,7 +69,7 @@ describe('认证失败的上报判据', () => {
afterEach(() => {
cleanup();
resetPlatformSessionForTests();
delete window.__TAURI__;
resetTauriRuntime();
vi.mocked(invoke).mockClear();
});
@@ -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({
@@ -65,7 +66,7 @@ describe('聊天输入盒的附件上限', () => {
});
expect(imported[0]).toHaveLength(5);
expect(imported[1]).toHaveLength(3);
expect(imported[0].length + imported[1].length).toBe(
expect(imported[0]!.length + imported[1]!.length).toBe(
MAX_CHAT_COMPOSER_ATTACHMENTS,
);
});
@@ -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<T>() {
}
afterEach(() => {
cleanup();
delete window.__TAURI__;
resetTauriRuntime();
vi.restoreAllMocks();
});
@@ -54,11 +55,12 @@ function mount(
return null;
},
);
window.__TAURI__ = { core: { invoke } } as unknown as typeof window.__TAURI__;
installTauriRuntime({ core: { invoke } });
const hook = renderHook(
() =>
useHomeProjectCreation({
setStatus: vi.fn(),
showTransientNotice: vi.fn(),
setLauncherView: vi.fn(),
rememberRecentWorkspace: vi.fn(),
}),
@@ -88,7 +90,7 @@ it.each(['recent', 'picker', 'create'] as const)(
);
});
expect(records()).toHaveLength(1);
expect(records()[0][1]).toMatchObject({ context, openSource: source });
expect(records()[0]![1]).toMatchObject({ context, openSource: source });
},
);
@@ -105,7 +107,7 @@ it('身份抓取挂起不阻塞打开,成功时间在抓取完成前冻结', a
captured.resolve(context);
});
expect(records()).toHaveLength(1);
expect(Date.parse(String(records()[0][1]?.eventTime))).toBeLessThanOrEqual(
expect(Date.parse(String(records()[0]![1]?.eventTime))).toBeLessThanOrEqual(
latestSuccessTime,
);
});
@@ -179,7 +181,7 @@ it('同项目再次显式打开是新操作,重渲染不会产生额外打开'
await result.current.openProject('C:/project', 'open');
});
expect(records()).toHaveLength(2);
expect(records()[0][1]?.operationId).not.toBe(records()[1][1]?.operationId);
expect(records()[0]![1]?.operationId).not.toBe(records()[1]![1]?.operationId);
});
it('身份抓取和记录写入失败静默,不改变成功打开', async () => {
@@ -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<unknown>) {
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(
@@ -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 内登录',
);
@@ -89,7 +89,7 @@ test('预设映射到渠道地址,自定义走归一化', async () => {
});
test('开发态切换服务器会落盘并在内存中生效', async () => {
vi.stubEnv('DEV', true);
vi.stubEnv('DEV', 'true');
const mod = await loadClientHttp();
expect(mod.isClientServerSelectionEnabled()).toBe(true);
@@ -108,7 +108,7 @@ test('开发态切换服务器会落盘并在内存中生效', async () => {
});
test('开发态从 Rust 偏好恢复服务器选择', async () => {
vi.stubEnv('DEV', true);
vi.stubEnv('DEV', 'true');
const mod = await loadClientHttp();
invoke.mockResolvedValue({
clientServerSelection: {
@@ -1,12 +1,13 @@
/** @vitest-environment jsdom */
import { fireEvent, render, screen } from '@testing-library/react';
import { createElement } from 'react';
import { createElement, type ReactNode } from 'react';
import { afterEach, describe, expect, it, vi } from 'vitest';
import { ClientRuntimeErrorBoundary } from '../src/app/AuthenticatedClient';
function BrokenAuthenticatedContent() {
// 该组件永远抛错,声明 ReactNode 返回类型才能作为 children 传给错误边界。
function BrokenAuthenticatedContent(): ReactNode {
throw new Error('首页渲染失败');
}
@@ -20,11 +21,11 @@ describe('AGC authenticated client error boundary', () => {
const onLogout = vi.fn();
render(
createElement(
ClientRuntimeErrorBoundary,
{ onLogout },
createElement(BrokenAuthenticatedContent),
),
createElement(ClientRuntimeErrorBoundary, {
onLogout,
// `children` 是必填 prop(createElement 第三参不再满足必填 props 约束)。
children: createElement(BrokenAuthenticatedContent),
}),
);
expect(
@@ -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 () => {
@@ -38,13 +39,15 @@ test('打开 Cocos 目录走导入入口,不创建 Web 脚手架或发送初
}
throw new Error(`意外调用:${command}`);
});
window.__TAURI__ = { core: { invoke } };
// TauriInvoke 是泛型函数签名,vi.fn 的具体返回类型无法直接赋给它,这里只在装配处桥接。
installTauriRuntime({ core: { invoke } });
const setLauncherView = vi.fn();
const { result } = renderHook(() =>
useHomeProjectCreation({
setStatus: vi.fn(),
// 现行 UseHomeProjectCreationOptions 必填 showTransientNotice,旧的 setAgentChatProjectPath 已移除。
showTransientNotice: vi.fn(),
setLauncherView,
setAgentChatProjectPath: vi.fn(),
rememberRecentWorkspace: vi.fn(),
}),
);
@@ -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 () => {
@@ -4,23 +4,25 @@ 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<typeof vi.fn>) {
window.__TAURI__ = { core: { invoke } } as unknown as typeof window.__TAURI__;
function setup(
invoke: (command: string, args?: Record<string, unknown>) => Promise<unknown>,
) {
installTauriRuntime({ core: { invoke } });
return renderHook(() =>
useHomeProjectCreation({
setStatus: vi.fn(),
showTransientNotice: vi.fn(),
setLauncherView: vi.fn(),
setAgentChatProjectPath: vi.fn(),
rememberRecentWorkspace: vi.fn(),
}),
);
@@ -5,7 +5,9 @@ import { afterEach, beforeEach, expect, it, vi } from 'vitest';
import type { DesignView } from '../src/app/types';
import { useDesignReplyAnimation } from '../src/view/project-development/planning/useDesignReplyAnimation';
beforeEach(() => vi.useFakeTimers());
beforeEach(() => {
vi.useFakeTimers();
});
afterEach(() => {
cleanup();
vi.useRealTimers();
@@ -39,7 +41,7 @@ it('keeps each reply progress across tool snapshots and repeated completion', ()
result.current.receiveText(first.id, first.text);
vi.advanceTimersByTime(100);
});
expect(result.current.replies[0].visible).toBe('第一');
expect(result.current.replies[0]!.visible).toBe('第一');
act(() =>
result.current.receiveView(
view(
@@ -49,7 +51,7 @@ it('keeps each reply progress across tool snapshots and repeated completion', ()
),
);
expect(result.current.replies).toHaveLength(1);
expect(result.current.replies[0].visible).toBe('第一');
expect(result.current.replies[0]!.visible).toBe('第一');
act(() => result.current.receiveText(second.id, second.text));
const terminal = view([history, first, second]);
act(() => {
@@ -74,9 +76,9 @@ it('resets only an unpersisted retry attempt and discards failed partial output'
result.current.receiveText('t:response:0', '尝试失败');
vi.advanceTimersByTime(100);
});
expect(result.current.replies[0].visible).toBe('尝试');
expect(result.current.replies[0]!.visible).toBe('尝试');
act(() => result.current.receiveText('t:response:0', ''));
expect(result.current.replies[0].visible).toBe('');
expect(result.current.replies[0]!.visible).toBe('');
const saved = { id: 't:response:0', role: 'assistant', text: '成功' };
act(() => {
result.current.receiveText(saved.id, saved.text);
@@ -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(<DesignWorkspacePanel projectPath="imported-project" />);
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(<DesignWorkspacePanel projectPath="test-project" />);
await waitFor(() =>
expect(

Some files were not shown because too many files have changed in this diff Show More