25c5ecdf6e
Project CI / AI game creator shell Rust crates (pull_request) Successful in 2m58s
Project CI / AI game creator shell Rust lane 1/2 (pull_request) Successful in 6m34s
Project CI / AI game creator shell Rust lane 2/2 (pull_request) Successful in 5m29s
Project CI / Backend tests (pull_request) Failing after 4m38s
Project CI / Frontend tests (pull_request) Successful in 3m33s
Project CI / Repository checks (pull_request) Failing after 4m4s
Project CI / AI game creator shell web tests (pull_request) Successful in 3m13s
Project CI / Native shell tests (pull_request) Successful in 6m35s
- 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<typeof vi.fn> 收窄为具体 invoke 签名 - 迁移后仅 tests/tauriRuntimeFake.ts 直接读写 window.__TAURI__
233 lines
7.4 KiB
TypeScript
233 lines
7.4 KiB
TypeScript
/** @vitest-environment jsdom */
|
|
|
|
/**
|
|
* 浮层提示的外壳级接线:`ProjectChat` 发一条运行提示,工作台壳真的把它渲染成浮层;
|
|
* 原来那一行状态栏(进行中进度、失败结论、取消回显)也走同一条通道,页面上不留那一行。
|
|
*
|
|
* 单测(`launcherNoticeToast`)只证明组件本身,`previewActivation` 只证明 App 会调这条通道;
|
|
* 从「通道被调用」到「用户看到 toast」之间还差一层壳的接线(prop 名、handler、portal 挂点、
|
|
* tone 传递)。这里用替身聊天把这一层单独钉住:以后有人把 `<LauncherNoticeToast/>` 挪进条件
|
|
* 分支、或在传 prop 时写错名字,这条会红。
|
|
*/
|
|
import {
|
|
act,
|
|
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';
|
|
import { installTauriRuntime } from './tauriRuntimeFake';
|
|
|
|
const PROJECT_PATH = '/tmp/run-notice-shell-project';
|
|
|
|
function StubProjectChat() {
|
|
return null;
|
|
}
|
|
|
|
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);
|
|
},
|
|
);
|
|
installTauriRuntime({
|
|
core: { invoke },
|
|
event: { listen: runtimeHarness.listen },
|
|
});
|
|
render(
|
|
<WorkspaceLauncherShell
|
|
currentUser={testAuthUser}
|
|
initialView="projects"
|
|
onLogout={vi.fn()}
|
|
ProjectChat={StubRunNoticeChat}
|
|
/>,
|
|
);
|
|
pickProjectFromLauncher(PROJECT_PATH);
|
|
}
|
|
|
|
function toastElement() {
|
|
return document.querySelector('[data-launcher-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(
|
|
'运行游戏失败:预览端口被占用',
|
|
);
|
|
});
|
|
|
|
it('状态栏退役:进行中状态与取消回显都只走浮层,首页不留那一行', async () => {
|
|
let finishDirectoryPick: ((path: string | null) => void) | null = null;
|
|
const directoryPick = new Promise<string | null>((resolve) => {
|
|
finishDirectoryPick = resolve;
|
|
});
|
|
const runtimeHarness = createProjectChatRuntimeHarness({});
|
|
const invoke = vi.fn(async (command: string) => {
|
|
if (command === 'pick_local_project_directory') {
|
|
return directoryPick;
|
|
}
|
|
return runtimeHarness.invoke(command);
|
|
});
|
|
installTauriRuntime({
|
|
core: { invoke },
|
|
event: { listen: runtimeHarness.listen },
|
|
});
|
|
render(
|
|
<WorkspaceLauncherShell
|
|
currentUser={testAuthUser}
|
|
initialView="home"
|
|
onLogout={vi.fn()}
|
|
ProjectChat={StubProjectChat}
|
|
/>,
|
|
);
|
|
|
|
fireEvent.click(await screen.findByRole('button', { name: '打开项目' }));
|
|
|
|
// 进行中状态:粘性浮层,页面上(以前那一行的位置)不留文字。
|
|
await waitFor(() =>
|
|
expect(toastElement()?.textContent ?? '').toContain('正在选择项目'),
|
|
);
|
|
expect(screen.getByLabelText('首页输入').textContent ?? '').not.toContain(
|
|
'正在选择项目',
|
|
);
|
|
|
|
// 取消:粘性状态被一次性回显替换掉,同样只在浮层里。
|
|
await act(async () => {
|
|
finishDirectoryPick?.(null);
|
|
await directoryPick;
|
|
});
|
|
await waitFor(() =>
|
|
expect(toastElement()?.textContent ?? '').toContain('已取消'),
|
|
);
|
|
expect(screen.getByLabelText('首页输入').textContent ?? '').not.toContain(
|
|
'已取消',
|
|
);
|
|
});
|
|
|
|
it('粘性状态只长在首页 / 项目组,进项目工作台不再飘在界面上', async () => {
|
|
renderWorkbench();
|
|
await screen.findByLabelText('项目开发工作台');
|
|
|
|
// 「打开项目」把状态写成「已打开项目」后立刻切到工作台:那一行原来只长在列表页上,
|
|
// 换成浮层也不能飘到工作台上来。
|
|
expect(toastElement()).toBeNull();
|
|
|
|
fireEvent.click(screen.getByRole('button', { name: '首页' }));
|
|
await waitFor(() =>
|
|
expect(toastElement()?.textContent ?? '').toContain('已打开项目'),
|
|
);
|
|
});
|
|
});
|