/** @vitest-environment jsdom */ /** * 运行提示的外壳级接线:`ProjectChat` 发一条提示,工作台壳真的把它渲染成浮层。 * * 单测(`runNoticeToast`)只证明组件本身,`previewActivation` 只证明 App 会调这条通道; * 从「通道被调用」到「用户看到 toast」之间还差一层壳的接线(prop 名、handler、portal 挂点、 * tone 传递)。这里用替身聊天把这一层单独钉住:以后有人把 `` 挪进条件 * 分支、或在传 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 (
); } /** * 打开工作台所需的最小命令集照抄 `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) => { 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( , ); 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( '运行游戏失败:预览端口被占用', ); }); });