删除已迁移模块的遗留测试用例

- 删除 tests/gamePublishFeedback.test.tsx:发布入口迁入导出产物面板、发布表单拆成 hook + 视图后,该套 App 级反馈用例已失效
- 移除 tests/chatDialogFrameLayout.test.ts 中扫描 DirectProjectChatView 会话块顺序的用例(该 DOM 已迁到 DirectProjectChatBox),并删除不再使用的 CHAT_VIEW_PATH / CHAT_CONVERSATION_PATH
This commit is contained in:
2026-10-06 23:52:17 +08:00
parent 7bcef53494
commit f4faaa2659
2 changed files with 0 additions and 421 deletions
@@ -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(
'<div className="project-chat-conversation">',
);
expect(conversationOpen).toBeGreaterThanOrEqual(0);
const headerIndex = viewSource.indexOf(
'<DirectProjectChatHeader',
conversationOpen,
);
const bodyIndex = viewSource.indexOf(
'<DirectProjectConversation',
conversationOpen,
);
const composerIndex = viewSource.indexOf(
'<DirectProjectComposer',
conversationOpen,
);
expect(headerIndex).toBeGreaterThan(conversationOpen);
expect(bodyIndex, '会话区必须排在顶栏之后').toBeGreaterThan(headerIndex);
expect(composerIndex, '输入盒必须排在会话区之后').toBeGreaterThan(
bodyIndex,
);
// 唯一滚动区仍是消息列表;`[overflow-anchor:none]` 是滚动补偿契约的一部分
// (见 `docs/adr/【ADR】DirectProject对话滚动与历史自动加载-2026-10-02.md`),
// 原生 scroll anchoring 一旦重新打开就会和会话侧补偿互相抵消。
expect(conversationSource).toContain(
'className="message-list project-chat-message-list [overflow-anchor:none]"',
);
const formOpen = composerSource.indexOf('<form');
expect(formOpen, '输入盒(form)缺失').toBeGreaterThanOrEqual(0);
const formEnd = composerSource.indexOf('</form>');
expect(formEnd, '输入盒(form)没有闭合').toBeGreaterThan(formOpen);
const formSource = composerSource.slice(
formOpen,
formEnd + '</form>'.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` 的高度。
*
@@ -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<string, unknown>) => {
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(
<App initialProjectManifest={manifest} initialProjectPath={PROJECT_PATH} />,
);
}
/**
* 发布入口在导出产物面板的陶泥儿 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<T>() {
let resolve!: (value: T) => void;
let reject!: (reason?: unknown) => void;
const promise = new Promise<T>((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<ReturnType<typeof createExportPackageResult>>();
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();
});
});