e3b4127440
Project CI / AI game creator shell Rust crates (push) Successful in 1m52s
Project CI / AI game creator shell Rust smoke (push) Successful in 2m47s
Project CI / Backend tests (push) Failing after 4m0s
Project CI / Frontend tests (push) Successful in 1m59s
Project CI / Native shell tests (push) Successful in 6m19s
Project CI / Repository checks (push) Successful in 2m43s
Project CI / AI game creator shell web tests (push) Successful in 1m47s
Project CI / AI game creator shell Rust lane 1/2 (push) Successful in 11m5s
Project CI / AI game creator shell Rust lane 2/2 (push) Successful in 11m19s
新增 homeCreationFailure 统一创建失败出口:宿主机器码 / IPC 失联 / 专用未分类兜底三种文案,宿主原始文本改为 appendWebviewLog 落 application.log 而不上屏 首页创建失败不再无条件覆盖成「创建未完成,请重试」,也不再写死「首条需求投递失败」指认某一步;工作区已建好时保留该事实并提供「已创建的工作区」恢复说法 宿主预检按真实原因分流浏览器验证错误码(browser-missing / launch-timeout / launch-failed / cancelled / browser-cleanup-failed / browser-environment-failed / evidence-failed / input-invalid / page-check-failed),未识别落回专用 web-preflight-unclassified 依赖准备收据保留宿主返回的真实失败码,不再一律写成 web-scaffold-preparation-failed 直连回合失败分类器 else 改为专用 runtime-unclassified,并补 sidecar-turn-timeout / sidecar-launch-failed / app-server-launch-failed / claude-code-failed 四个有证据的具体码 Codex CLI 与 Claude sidecar 的进程启动失败保留真实 OS 错误,不再报成「不支持 app-server 子命令」或「Node runtime 不可用」 更新检查新增结果型入口 checkForAppUpdateResult,「关于」页检查失败显示真实原因而不是「当前已是最新版本」 首页无码预检兜底文案不再写「请检查 Node/npm 或浏览器」这类未证实的候选原因 同步 pitfalls 六条与团队约定:兜底码/兜底文案规则、五项修复的现象根因处理验证、测试夹具编译修复记录 验证:vitest homeCreationFailure/homeWebPreflight/appUpdate/appSurface 共 215 passed;cargo test environment_check:: 19 passed、agent::runtime_error:: 5 passed、direct_failure_diagnostic 4 passed、tests::configuration:: 45 passed;cargo fmt --check、tsc --noEmit、npm run typecheck、check:encoding、git diff --check 通过
143 lines
5.0 KiB
TypeScript
143 lines
5.0 KiB
TypeScript
// @vitest-environment jsdom
|
|
import { act, cleanup, renderHook } from '@testing-library/react';
|
|
import { afterEach, expect, it, vi } from 'vitest';
|
|
|
|
import { createGameCreationAppManifest } from '../../../packages/shared/src/contracts/gameCreationApp';
|
|
import {
|
|
HOME_WEB_PREFLIGHT_FAILURE,
|
|
HOME_WEB_PREFLIGHT_IPC_FAILURE,
|
|
} from '../src/features/app-shell/homeWebPreflight';
|
|
import { useHomeProjectCreation } from '../src/features/app-shell/useHomeProjectCreation';
|
|
|
|
afterEach(() => {
|
|
cleanup();
|
|
delete window.__TAURI__;
|
|
});
|
|
|
|
function mount(preflight: () => Promise<unknown>) {
|
|
const calls: string[] = [];
|
|
const invoke = vi.fn(async (command: string) => {
|
|
if (command !== 'capture_analytics_context') calls.push(command);
|
|
if (command === 'preflight_web_game_creation') return preflight();
|
|
if (command === 'suggest_automatic_project_name') return '预检项目';
|
|
if (command === 'create_automatic_local_game_project')
|
|
return {
|
|
projectPath: 'C:/test/preflight-game',
|
|
manifestPath: 'C:/test/preflight-game/.agent/manifest.json',
|
|
manifest: createGameCreationAppManifest('preflight-game', '预检项目'),
|
|
};
|
|
if (command === 'get_local_game_project_revision') return { revision: 1 };
|
|
if (command === 'set_design_agent_runtime_mode')
|
|
return { activeRuntime: 'design' };
|
|
return null;
|
|
});
|
|
window.__TAURI__ = { core: { invoke } } as unknown as typeof window.__TAURI__;
|
|
const hook = renderHook(() =>
|
|
useHomeProjectCreation({
|
|
setStatus: vi.fn(),
|
|
showTransientNotice: vi.fn(),
|
|
setLauncherView: vi.fn(),
|
|
setAgentChatProjectPath: vi.fn(),
|
|
rememberRecentWorkspace: vi.fn(),
|
|
}),
|
|
);
|
|
return { ...hook, calls, invoke };
|
|
}
|
|
|
|
it('awaits the real home preflight before naming or creating a game', async () => {
|
|
let ready!: (value: unknown) => void;
|
|
const pending = new Promise((resolve) => {
|
|
ready = resolve;
|
|
});
|
|
const { result, calls } = mount(() => pending);
|
|
let creation!: Promise<string>;
|
|
await act(async () => {
|
|
creation = result.current.createHomeDraftAutomatically(
|
|
{ creationType: 'game', prompt: '做一个小游戏', attachments: [] },
|
|
'direct-build',
|
|
);
|
|
});
|
|
expect(calls).toEqual(['preflight_web_game_creation']);
|
|
await act(async () => {
|
|
ready({ status: 'ready' });
|
|
await creation;
|
|
});
|
|
expect(calls.slice(0, 3)).toEqual([
|
|
'preflight_web_game_creation',
|
|
'suggest_automatic_project_name',
|
|
'create_automatic_local_game_project',
|
|
]);
|
|
expect(result.current.currentProjectContext?.creationType).toBe('game');
|
|
});
|
|
|
|
it('blocks naming, creation, and first generation with the host error code', async () => {
|
|
const { result, calls } = mount(async () => {
|
|
throw new Error(
|
|
'Web 游戏环境预检未通过:web-preflight-browser-missing;尚未启动生成',
|
|
);
|
|
});
|
|
let status = '';
|
|
await act(async () => {
|
|
status = await result.current.createHomeDraftAutomatically(
|
|
{ creationType: 'game', prompt: '做一个小游戏', attachments: [] },
|
|
'direct-build',
|
|
);
|
|
});
|
|
expect(status).toBe(
|
|
'Web 游戏环境预检未通过(web-preflight-browser-missing);尚未开始生成。',
|
|
);
|
|
expect(calls).toEqual(['preflight_web_game_creation']);
|
|
expect(result.current.currentProjectContext).toBeNull();
|
|
});
|
|
|
|
it('distinguishes an IPC failure from a host preflight failure', async () => {
|
|
const { result, calls } = mount(async () => {
|
|
throw new Error('Failed to fetch');
|
|
});
|
|
let status = '';
|
|
await act(async () => {
|
|
status = await result.current.createHomeDraftAutomatically(
|
|
{ creationType: 'game', prompt: '做一个小游戏', attachments: [] },
|
|
'direct-build',
|
|
);
|
|
});
|
|
expect(status).toBe(HOME_WEB_PREFLIGHT_IPC_FAILURE);
|
|
expect(calls).toEqual([
|
|
'preflight_web_game_creation',
|
|
'preflight_web_game_creation',
|
|
]);
|
|
expect(result.current.currentProjectContext).toBeNull();
|
|
});
|
|
|
|
it('keeps the legacy fallback for a blocked report without a code', async () => {
|
|
const { result, calls } = mount(async () => ({ status: 'blocked' }));
|
|
let status = '';
|
|
await act(async () => {
|
|
status = await result.current.createHomeDraftAutomatically(
|
|
{ creationType: 'game', prompt: '做一个小游戏', attachments: [] },
|
|
'direct-build',
|
|
);
|
|
});
|
|
expect(status).toBe(HOME_WEB_PREFLIGHT_FAILURE);
|
|
expect(calls).toEqual(['preflight_web_game_creation']);
|
|
expect(result.current.currentProjectContext).toBeNull();
|
|
});
|
|
|
|
it.each(['game', 'doc'] as const)(
|
|
'does not require the Web preflight for %s planning',
|
|
async (creationType) => {
|
|
const { result, calls } = mount(async () => {
|
|
throw new Error('must not run');
|
|
});
|
|
await act(async () => {
|
|
await result.current.createHomeDraftAutomatically(
|
|
{ creationType, prompt: '策划需求', attachments: [] },
|
|
'planning',
|
|
);
|
|
});
|
|
expect(calls).not.toContain('preflight_web_game_creation');
|
|
expect(calls).not.toContain('suggest_automatic_project_name');
|
|
expect(calls).toContain('create_automatic_local_game_project');
|
|
},
|
|
);
|