diff --git a/apps/ai-game-creator-shell/tests/chatDialogFrameLayout.test.ts b/apps/ai-game-creator-shell/tests/chatDialogFrameLayout.test.ts
index 4e2f41816..379024e85 100644
--- a/apps/ai-game-creator-shell/tests/chatDialogFrameLayout.test.ts
+++ b/apps/ai-game-creator-shell/tests/chatDialogFrameLayout.test.ts
@@ -11,12 +11,6 @@ import {
} from './styleCascade';
const STYLES_PATH = repoPath('apps/ai-game-creator-shell/src/styles.css');
-const CHAT_VIEW_PATH = repoPath(
- 'apps/ai-game-creator-shell/src/view/project-development/chat/DirectProjectChatView.tsx',
-);
-const CHAT_CONVERSATION_PATH = repoPath(
- 'apps/ai-game-creator-shell/src/view/project-development/chat/components/DirectProjectConversation/DirectProjectConversation.tsx',
-);
const CHAT_COMPOSER_PATH = repoPath(
'apps/ai-game-creator-shell/src/view/project-development/chat/components/DirectProjectComposer/DirectProjectComposer.tsx',
);
@@ -591,62 +585,6 @@ describe('陶泥儿对话区:Codex 三段式(顶栏 / 唯一滚动区 / 文
expect(pixelValue(topbar, 'min-height')).toBe(44);
});
- it('输入盒仍落在 form.project-chat-composer 子树里,交互控件原位', () => {
- const viewSource = readFileSync(CHAT_VIEW_PATH, 'utf8');
- const conversationSource = readFileSync(CHAT_CONVERSATION_PATH, 'utf8');
- const composerSource = readFileSync(CHAT_COMPOSER_PATH, 'utf8');
-
- // 面板三段式的顺序:顶栏 → 会话区 → 输入盒,组合顺序由视图持有。
- const conversationOpen = viewSource.indexOf(
- '
',
- );
- expect(conversationOpen).toBeGreaterThanOrEqual(0);
- const headerIndex = viewSource.indexOf(
- '
');
- expect(formEnd, '输入盒(form)没有闭合').toBeGreaterThan(formOpen);
- const formSource = composerSource.slice(
- formOpen,
- formEnd + ''.length,
- );
-
- // 编辑器、`+` / `@` 引用、模型选择、提交按钮全在这棵子树里,位置只由样式挪。
- expect(formSource).toContain('ResourceReferenceInput');
- // 输入盒里不再有「项目名 · 本地」信息标签行(按需求删除)。
- expect(formSource).not.toContain('project-chat-composer-context');
- expect(formSource).toContain('project-chat-composer-controls');
- expect(formSource).toContain('project-chat-reference-trigger');
- expect(formSource).toContain('ConversationModelSelect');
- expect(formSource).toContain('onSubmit={');
- // 提交按钮本体还是那只按钮,只是以变量形式引用进这棵子树。
- expect(composerSource).toContain('className="project-chat-submit-button"');
- });
-
/**
* 面板纵向必须撑满 `.game-workbench-chat` 的高度。
*
diff --git a/apps/ai-game-creator-shell/tests/gamePublishFeedback.test.tsx b/apps/ai-game-creator-shell/tests/gamePublishFeedback.test.tsx
deleted file mode 100644
index ed29f627d..000000000
--- a/apps/ai-game-creator-shell/tests/gamePublishFeedback.test.tsx
+++ /dev/null
@@ -1,359 +0,0 @@
-// @vitest-environment jsdom
-/**
- * 客户端「发布」入口的可见反馈。
- *
- * 陶泥儿 tab 左半是内联的发布资料表单,内容末尾的橙色「打包并发布」一次点击先构建打包、
- * 再提交发布。打包用独立的全屏进度弹窗(失败留在弹窗内),成功后不再弹出发布面板。
- */
-import { readFileSync } from 'node:fs';
-
-import { beforeEach, describe, it, vi } from 'vitest';
-
-import type { GameCreationAppManifest } from '../../../packages/shared/src/contracts/gameCreationApp';
-import type { ProjectPermissionPolicyView } from '../src/app/types';
-import {
- App,
- createGameCreationAppManifest,
- createProjectChatRuntimeHarness,
- emptyProjectPolicy,
- expect,
- fireEvent,
- render,
- screen,
- waitFor,
- within,
-} from './appSurface/harness';
-import { repoPath } from './repoPath';
-import {
- declaration,
- parseStyleSheet,
- resolveDeclarations,
-} from './styleCascade';
-import { installTauriRuntime } from './tauriRuntimeFake';
-
-vi.mock('../src/services/gameDistributionPublish', async (importOriginal) => {
- const actual =
- await importOriginal<
- typeof import('../src/services/gameDistributionPublish')
- >();
- return {
- ...actual,
- // 面板打开就会回读发布绑定;这里按「首次发布」放行,避免落到真实原生命令。
- readGameDistributionPublication: vi.fn(async () => ({
- state: 'first-publish',
- binding: null,
- refreshed: true,
- draft: null,
- visibility: null,
- })),
- };
-});
-
-// 表单只负责选图并调用上传;这里替换掉真实直传,让内联表单能在测试里拿到封面 ID。
-vi.mock('../src/services/assetDirectUpload', () => ({
- uploadPlatformMediaAsset: vi.fn(async () => ({
- assetObjectId: 'asset_cover',
- objectKey: 'game-distribution/cover/cover.png',
- })),
-}));
-
-const PROJECT_PATH = '/tmp/game-publish-feedback-project';
-const PROJECT_ID = 'game-publish-feedback-project';
-
-function withPrototypeStatus(
- manifest: GameCreationAppManifest,
- status: 'pending' | 'completed',
-): GameCreationAppManifest {
- return {
- ...manifest,
- tasks: manifest.tasks.map((task) =>
- task.id === 'code-prototype' ? { ...task, status } : task,
- ),
- };
-}
-
-function createFixtureManifest(): GameCreationAppManifest {
- return withPrototypeStatus(
- createGameCreationAppManifest(PROJECT_ID, '发布反馈项目'),
- 'completed',
- );
-}
-
-function createPendingPrototypeManifest(): GameCreationAppManifest {
- return withPrototypeStatus(
- createGameCreationAppManifest(PROJECT_ID, '原型未完成项目'),
- 'pending',
- );
-}
-
-function installTauri(
- options: {
- exportPackage?: () => unknown;
- manifest?: GameCreationAppManifest;
- policy?: ProjectPermissionPolicyView;
- readPolicy?: () => unknown;
- } = {},
-) {
- const manifest = options.manifest ?? createFixtureManifest();
- const chatHarness = createProjectChatRuntimeHarness({
- projectPath: PROJECT_PATH,
- });
- const invoke = vi.fn(
- async (command: string, args?: Record) => {
- if (command === 'get_local_game_manifest') return manifest;
- if (command === 'get_local_game_project_revision') return { revision: 1 };
- if (command === 'read_project_permission_policy') {
- if (options.readPolicy) return options.readPolicy();
- return options.policy ?? emptyProjectPolicy();
- }
- if (command === 'export_local_project_package') {
- if (options.exportPackage) return options.exportPackage();
- return {
- projectPath: PROJECT_PATH,
- packagePath: `${PROJECT_PATH}/exports/game.zip`,
- packageRelativePath: 'exports/game.zip',
- fileCount: 1,
- totalBytes: 1,
- };
- }
- return chatHarness.invoke(command, args);
- },
- );
- installTauriRuntime({
- core: { invoke },
- event: { listen: chatHarness.listen },
- });
- return { invoke };
-}
-
-function renderPublishProject(
- manifest: GameCreationAppManifest = createFixtureManifest(),
-) {
- return render(
- ,
- );
-}
-
-/**
- * 发布入口在导出产物面板的陶泥儿 tab:先开导出面板,再进陶泥儿,等灰度读到后才可点。
- */
-async function openTaonierPublishEntry() {
- fireEvent.click(await screen.findByRole('button', { name: '导出产物' }));
- fireEvent.click(await screen.findByRole('button', { name: '陶泥儿' }));
- const publish = await screen.findByRole('button', { name: '打包并发布' });
- await waitFor(() => expect(publish).toHaveProperty('disabled', false));
- return publish;
-}
-
-/** 内联表单只有拿到封面素材 ID 才算校验通过;用假上传把封面补齐后再点主行动。 */
-async function attachCover() {
- fireEvent.change(await screen.findByLabelText('游戏封面图片'), {
- target: {
- files: [new File(['cover-bytes'], 'cover.png', { type: 'image/png' })],
- },
- });
- await screen.findByText(/已选择「cover\.png」/u);
-}
-
-beforeEach(() => {
- window.history.pushState({}, '', '/');
-});
-
-function createExportPackageResult() {
- return {
- projectPath: PROJECT_PATH,
- packagePath: `${PROJECT_PATH}/exports/game.zip`,
- packageRelativePath: 'exports/game.zip',
- fileCount: 1,
- totalBytes: 1,
- };
-}
-
-function createConfirmPolicy() {
- return {
- path: '.agent/policy.json',
- policy: {
- deniedCommands: [],
- confirmCommands: ['project.export_package'],
- },
- };
-}
-
-function createDeferred() {
- let resolve!: (value: T) => void;
- let reject!: (reason?: unknown) => void;
- const promise = new Promise((resolvePromise, rejectPromise) => {
- resolve = resolvePromise;
- reject = rejectPromise;
- });
- return { promise, resolve, reject };
-}
-
-describe('客户端发布入口的可见反馈', () => {
- it('首个可运行原型未完成时显示独立阻断弹窗,不触发用户项目构建', async () => {
- const exportPackage = vi.fn(createExportPackageResult);
- const manifest = createPendingPrototypeManifest();
- installTauri({ exportPackage, manifest });
-
- renderPublishProject(manifest);
-
- const publish = await openTaonierPublishEntry();
- // 表单先过本地门禁,原型阻断才会接管;先补齐封面让校验通过。
- await attachCover();
- fireEvent.click(publish);
- const surface = await screen.findByLabelText('游戏发布对话栏');
- const noticeDialog = await screen.findByRole('dialog', {
- name: '发布提示',
- });
- expect(noticeDialog.textContent ?? '').toContain(
- '首个可运行原型尚未完成,暂不能发布;请先完成可运行原型并通过运行验证。',
- );
- expect(surface.textContent ?? '').not.toContain(
- '首个可运行原型尚未完成,暂不能发布',
- );
- expect(surface.textContent ?? '').not.toContain('正在检查发布权限…');
- expect(exportPackage).not.toHaveBeenCalled();
-
- fireEvent.click(within(noticeDialog).getByRole('button', { name: '关闭' }));
- await waitFor(() => {
- expect(screen.queryByRole('dialog', { name: '发布提示' })).toBeNull();
- });
- });
-
- it('打包时显示全屏进度遮罩,成功后直接进入发布提交(不再弹发布面板)', async () => {
- const deferred =
- createDeferred>();
- const exportPackage = vi.fn(() => deferred.promise);
- installTauri({ exportPackage });
- renderPublishProject();
-
- const publish = await openTaonierPublishEntry();
- await attachCover();
- fireEvent.click(publish);
-
- const progressDialog = await screen.findByRole('dialog', {
- name: '发布进度',
- });
- expect(progressDialog.textContent ?? '').toContain(
- '正在构建并打包试玩包,请稍候…',
- );
- expect(progressDialog.textContent ?? '').toContain(
- '发布完成前请保持客户端开启,页面暂时不可操作。',
- );
- expect(
- progressDialog.closest('.game-publish-progress-overlay'),
- ).not.toBeNull();
- expect(exportPackage).toHaveBeenCalledTimes(1);
- expect(screen.queryByText('导出试玩包需要确认,确认后继续。')).toBeNull();
-
- deferred.resolve(createExportPackageResult());
- await waitFor(() => {
- expect(screen.queryByRole('dialog', { name: '发布进度' })).toBeNull();
- });
- // 校验通过才会走到打包;打包成功后直接提交发布,不再弹旧的发布面板。
- expect(screen.queryByRole('dialog', { name: '发布到游戏广场' })).toBeNull();
- });
-
- it('打包失败时在进度弹窗里显示错误并允许关闭', async () => {
- installTauri({
- exportPackage: () => {
- throw new Error('构建可玩版本失败:缺少入口');
- },
- });
- renderPublishProject();
-
- const publish = await openTaonierPublishEntry();
- await attachCover();
- fireEvent.click(publish);
-
- const failureDialog = await screen.findByRole('dialog', {
- name: '发布失败',
- });
- expect(failureDialog.textContent ?? '').toContain(
- '构建可玩版本失败:缺少入口',
- );
- fireEvent.click(
- within(failureDialog).getByRole('button', { name: '关闭' }),
- );
- await waitFor(() => {
- expect(screen.queryByRole('dialog', { name: '发布失败' })).toBeNull();
- });
- });
-
- it('发布进度遮罩固定覆盖整个工作区并压暗背景', () => {
- const rules = parseStyleSheet(
- readFileSync(
- repoPath('apps/ai-game-creator-shell/src/styles.css'),
- 'utf8',
- ),
- );
- const overlay = resolveDeclarations(
- rules,
- ['.game-publish-progress-overlay'],
- 1440,
- );
- expect(declaration(overlay, 'position')).toBe('fixed');
- // 遮罩从自绘标题栏下方开始:发布进行中仍然要能最小化 / 关闭窗口。
- expect(declaration(overlay, 'top')).toBe('var(--window-chrome-height)');
- expect(declaration(overlay, 'right')).toBe('0');
- expect(declaration(overlay, 'bottom')).toBe('0');
- expect(declaration(overlay, 'left')).toBe('0');
- expect(declaration(overlay, 'z-index')).toBe('500');
- expect(declaration(overlay, 'pointer-events')).toBe('auto');
- expect(declaration(overlay, 'background')).toBe('rgb(35 24 19 / 62%)');
- });
-
- it('策略要求确认时不再回到聊天确认卡片,直接进入进度弹窗', async () => {
- const exportPackage = vi.fn(createExportPackageResult);
- installTauri({ exportPackage, policy: createConfirmPolicy() });
- renderPublishProject();
-
- const publish = await openTaonierPublishEntry();
- await attachCover();
- fireEvent.click(publish);
-
- await waitFor(() => expect(exportPackage).toHaveBeenCalledTimes(1));
- expect(screen.queryByText('project.export_package')).toBeNull();
- expect(screen.queryByText('导出试玩包需要确认,确认后继续。')).toBeNull();
- });
-
- it('项目还没起名时「打包并发布」不变灰,点击后给出可读的必填提示', async () => {
- const exportPackage = vi.fn(createExportPackageResult);
- const manifest: GameCreationAppManifest = {
- ...createFixtureManifest(),
- name: '',
- goal: '',
- };
- installTauri({ exportPackage, manifest });
- renderPublishProject(manifest);
-
- const publish = await openTaonierPublishEntry();
- expect(publish).toHaveProperty('disabled', false);
-
- fireEvent.click(publish);
-
- // 按钮始终可点,校验在前端当场给出、由「发布提示」弹窗承载;不通过时不为一次注定失败的发布白跑构建。
- const noticeDialog = await screen.findByRole('dialog', {
- name: '发布提示',
- });
- expect(noticeDialog.textContent ?? '').toContain('请先填写游戏名称');
- expect(exportPackage).not.toHaveBeenCalled();
- });
-
- it('没选封面点「打包并发布」:直接弹发布提示,不打包也不出进度弹窗', async () => {
- const exportPackage = vi.fn(createExportPackageResult);
- installTauri({ exportPackage });
- renderPublishProject();
-
- fireEvent.click(await openTaonierPublishEntry());
-
- const noticeDialog = await screen.findByRole('dialog', {
- name: '发布提示',
- });
- expect(noticeDialog.textContent ?? '').toContain(
- '请先选择游戏封面(JPG/PNG/WebP)',
- );
- expect(exportPackage).not.toHaveBeenCalled();
- expect(screen.queryByRole('dialog', { name: '发布进度' })).toBeNull();
- });
-});