Files
Genarrative/apps/ai-game-creator-shell/tests/runNoticeShellWiring.test.tsx
T
suzmii 6dde321bd1
Project CI / AI game creator shell Rust smoke (pull_request) Successful in 2m5s
Project CI / Backend tests (pull_request) Failing after 11s
Project CI / AI game creator shell Rust crates (pull_request) Successful in 1m13s
Project CI / Frontend tests (pull_request) Successful in 3m15s
Project CI / Repository checks (pull_request) Failing after 15s
Project CI / AI game creator shell Rust lane 1/2 (pull_request) Successful in 7m56s
Project CI / AI game creator shell Rust lane 2/2 (pull_request) Successful in 7m55s
Project CI / AI game creator shell web tests (pull_request) Failing after 3m21s
Project CI / Native shell tests (pull_request) Successful in 8m53s
修 review 发现:运行反馈全面退出对话区,并补齐顶栏与守卫细节
- executeRunLocal 的三条失败路径(缺 Tauri / 缺项目 / 启动预览报错)也改走 onRunNotice 失败色提示,对话区不再出现过程行;随之删除已无调用方的 announceProjectChatMessage 与不再有意义的 announceToChat 参数
- RunNoticeToast 的失败提示延长到 6 秒(成功仍 2.6 秒),避免错误一闪而过
- 版本入口在 UI 编辑器壳里不渲染;版本名改由一层 span 承载省略号,长版本名不再被硬裁
- 视图拿不到 onNotice 时把「在浏览器打开失败」写进 console,不再静默吞掉
- check-native-shells 增加「运行页视图里 openUrl( 只有一个调用点」的判据,堵住自动开浏览器的口子
- 新增外壳级接线用例 runNoticeShellWiring;previewActivation 补「启动失败走失败色提示且不写对话区」;runNoticeToast 补失败时长;样式守卫补省略号
- decision-log 收敛到最终口径(成功与失败都出对话区、失败留 6 秒、编辑器不挂版本入口)
2026-09-22 13:51:30 +08:00

159 lines
5.0 KiB
TypeScript

/** @vitest-environment jsdom */
/**
* 运行提示的外壳级接线:`ProjectChat` 发一条提示,工作台壳真的把它渲染成浮层。
*
* 单测(`runNoticeToast`)只证明组件本身,`previewActivation` 只证明 App 会调这条通道;
* 从「通道被调用」到「用户看到 toast」之间还差一层壳的接线(prop 名、handler、portal 挂点、
* tone 传递)。这里用替身聊天把这一层单独钉住:以后有人把 `<RunNoticeToast/>` 挪进条件
* 分支、或在传 prop 时写错名字,这条会红。
*/
import { fireEvent, render, screen, waitFor } from '@testing-library/react';
import { describe, expect, it, vi } from 'vitest';
import { createGameCreationAppManifest } from '../../../packages/shared/src/contracts/gameCreationApp';
import type { ProjectChatComponentProps } from '../src/features/app-shell/model';
import { WorkspaceLauncherShell } from '../src/features/app-shell/WorkspaceLauncher';
import {
createProjectChatRuntimeHarness,
pickProjectFromLauncher,
testAuthUser,
} from './appSurface/harness';
const PROJECT_PATH = '/tmp/run-notice-shell-project';
function StubRunNoticeChat({ onRunNotice }: ProjectChatComponentProps) {
return (
<div>
<button
type="button"
onClick={() =>
onRunNotice?.({ message: '运行通过,已载入客户端运行视图' })
}
>
触发成功提示
</button>
<button
type="button"
onClick={() =>
onRunNotice?.({
tone: 'error',
message: '运行游戏失败:预览端口被占用',
})
}
>
触发失败提示
</button>
</div>
);
}
/**
* 打开工作台所需的最小命令集照抄 `home.suite` 的活动清单用例:项目目录探测 + 清单 +
* 资源图 / 布局读回,其余命令沿用聊天 harness。
*/
function renderWorkbench() {
const manifest = createGameCreationAppManifest(
'run-notice-shell-project',
'运行提示接线项目',
);
const runtimeHarness = createProjectChatRuntimeHarness({
projectPath: PROJECT_PATH,
});
const invoke = vi.fn(
async (command: string, args?: Record<string, unknown>) => {
if (command === 'get_design_agent_runtime_mode') return null;
if (command === 'inspect_local_project_directory') {
return {
projectPath: PROJECT_PATH,
exists: true,
isDirectory: true,
isGameCreatorProject: true,
projectName: manifest.name,
recentRunStatus: null,
recentRunStopReason: null,
};
}
if (command === 'get_local_game_manifest') {
return manifest;
}
if (command === 'read_local_project_resource_graph') {
return {
resourceIds: [],
referenceEdges: [],
taskFlows: [],
connectionIndex: [],
producerAssignments: [],
dependencyDepths: [],
unresolvedReferenceResourceIds: [],
cyclicResourceIds: [],
cyclicTaskIds: [],
producerMappingTruncated: false,
};
}
if (command === 'read_local_project_resource_canvas_layout') {
return {
schemaVersion: 'game-creator-resource-layout.v1',
projectId: manifest.projectId,
mode: args?.mode,
revision: 0,
positions: [],
updatedAt: 0,
};
}
return runtimeHarness.invoke(command, args);
},
);
window.__TAURI__ = {
core: { invoke: invoke as never },
event: { listen: runtimeHarness.listen as never },
};
render(
<WorkspaceLauncherShell
currentUser={testAuthUser}
initialView="projects"
onLogout={vi.fn()}
ProjectChat={StubRunNoticeChat}
/>,
);
pickProjectFromLauncher(PROJECT_PATH);
}
function toastElement() {
return document.querySelector('[data-project-run-notice-toast="true"]');
}
describe('运行提示的外壳接线', () => {
it('成功提示渲染成浮层,且不落在对话容器里', async () => {
renderWorkbench();
await screen.findByLabelText('项目开发工作台');
fireEvent.click(
await screen.findByRole('button', { name: '触发成功提示' }),
);
await waitFor(() => expect(toastElement()).not.toBeNull());
expect(toastElement()?.textContent ?? '').toContain(
'运行通过,已载入客户端运行视图',
);
// 「对话区里没有这条提示」由 `previewActivation` 用真实聊天容器断言;这里用的替身
// 聊天没有消息列表,重复断言只会自证。
});
it('失败提示按 alert 渲染', async () => {
renderWorkbench();
await screen.findByLabelText('项目开发工作台');
fireEvent.click(
await screen.findByRole('button', { name: '触发失败提示' }),
);
await waitFor(() =>
expect(toastElement()?.querySelector('[role="alert"]')).not.toBeNull(),
);
expect(toastElement()?.textContent ?? '').toContain(
'运行游戏失败:预览端口被占用',
);
});
});