Files
Genarrative/apps/ai-game-creator-shell/tests/appSurface/project-preview/run-status.suite.ts
T
suzmii 53afc85319 修正 AGC 测试按新的 Lexical 聊天输入区驱动与断言
- appSurface/harness 补齐三处 jsdom 缺口:ClipboardEvent、DragEvent、Range.prototype.getBoundingClientRect;否则 Lexical 的粘贴通路直接抛 ReferenceError / TypeError
- appSurface/harness 的 submitChat 改为 async:全选并让编辑器吸收选区、清空、走产品真实 paste 通路写入,再让出一帧让 React 追平 draft,最后点发送
- 新增 composerText / composerValue / composerDisabled / setComposerText 助手,按原生控件与 Lexical contenteditable 两种 DOM 口径读写输入区
- 15 个测试文件中 116 处 toHaveProperty('value', …) 断言等义改写为 await composerText() / await composerValue();1 处 placeholder 断言改查输入区占位文案;2 处 disabled 断言改用 composerDisabled(同时覆盖 data-disabled 与 contenteditable=false);9 处 fireEvent.change 写入改用 setComposerText
- 328 处 submitChat 调用补 await,9 个非 async 用例补 async
- Godot 输入区键盘语义用例按 Lexical 实际行为改写:Shift+Enter 与组合态 Enter 由编辑器消化并插入换行、不发送、草稿保留
- 未改动 src 下任何产品代码,未放宽或删除断言
2026-09-10 11:46:02 +08:00

286 lines
9.8 KiB
TypeScript

import {
composerText,
createGameCreationAppManifest,
createGameCreationAppSeedTasks,
emptyProjectPolicy,
expect,
fireEvent,
GAME_CREATION_AGENT_RUN_SCHEMA_VERSION,
type GameCreationAgentRunTrace,
it,
renderAppAt,
screen,
vi,
waitFor,
within,
} from '../harness';
export function registerProjectRunStatusTests() {
it('shows and refreshes the current project run status in the main window header', async () => {
const manifest = createGameCreationAppManifest(
'local-project-draft',
'未命名游戏原型',
);
let runReadCount = 0;
const makeTrace = (
status: string,
passes: number,
stopReason: string,
lifecycleStatus?: string,
) =>
({
schemaVersion: GAME_CREATION_AGENT_RUN_SCHEMA_VERSION,
runId: 'run-header-status',
commandId: 'game.generate_draft',
status,
lifecycleStatus,
passes,
maxPasses: 3,
toolCallCount: 0,
maxToolCalls: 128,
stopReason,
goal: '做一个厨房弹幕游戏',
coordination: 'Planner -> Generator',
steps: [],
artifacts: Array.from({ length: 9 }, (_, index) => ({
path: `exports/artifact-${index + 1}.json`,
sizeBytes: index + 1,
checksum: `fnv1a64:artifact-${index + 1}`,
})),
taskGraph: {
goal: '做一个厨房弹幕游戏',
readyTaskIds: ['preview-playtest'],
activeTaskIds: ['code-prototype'],
carriedTaskIds: ['design-director'],
repairFocus: [],
repairRoutes: [
{
issue: '缺少输入监听',
taskIds: ['code-prototype', 'quality-review'],
reason: 'code-runtime',
},
],
tasks: createGameCreationAppSeedTasks(),
},
passPlans: [
{
pass: 1,
mode: 'repair',
summary: '继续程序返工',
activeTaskIds: ['code-prototype', 'quality-review'],
carriedTaskIds: ['design-director'],
dependencyWaves: [['code-prototype'], ['quality-review']],
repairFocus: ['缺少输入监听'],
repairRoutes: [
{
issue: '缺少输入监听',
taskIds: ['code-prototype', 'quality-review'],
reason: 'code-runtime',
},
],
},
],
nextStep: 'preview',
error: null,
updatedAt: runReadCount,
}) satisfies GameCreationAgentRunTrace;
const invoke = vi.fn(
async (command: string, args?: Record<string, unknown>) => {
if (command === 'append_local_permission_log') {
return {};
}
if (command === 'read_project_permission_policy') {
return emptyProjectPolicy();
}
if (command === 'init_local_game_project') {
const projectPath = String(args?.projectPath ?? '');
return {
projectPath,
manifestPath: `${projectPath}/.agent/manifest.json`,
manifest,
};
}
if (command === 'read_local_conversation') {
return {
path: '/tmp/authorized-game/.agent/conversations/project.jsonl',
agentId: null,
messages: [],
};
}
if (command === 'read_project_permission_policy') {
return emptyProjectPolicy();
}
if (command === 'read_local_project_file') {
runReadCount += 1;
return {
path: '.agent/run.latest.json',
absolutePath: `${String(args?.projectPath ?? '')}/.agent/run.latest.json`,
content: JSON.stringify(
runReadCount > 1
? makeTrace('passed', 2, 'evaluator-passed')
: makeTrace('running', 1, 'planning', 'pending'),
),
};
}
if (command === 'list_local_project_files') {
return { projectPath: String(args?.projectPath ?? ''), files: [] };
}
throw new Error(`unexpected invoke ${command}`);
},
);
window.__TAURI__ = { core: { invoke } };
renderAppAt('/?dev&projectPath=%2Ftmp%2Fauthorized-game');
expect(
await screen.findByText('run: running / pending · 1/3 轮 · planning'),
).not.toBeNull();
expect(screen.getByLabelText('Agent task graph').textContent).toContain(
'active: 程序组 / Code 生成可运行原型(code-prototype)',
);
expect(screen.getByLabelText('Agent task graph').textContent).toContain(
'carry-over: 设计实现组 / Director 拆解创作方向(design-director)',
);
expect(screen.getByLabelText('Agent task graph').textContent).toContain(
'ready: 程序组 / Playtest 预览并试玩验收(preview-playtest)',
);
expect(screen.getByLabelText('Agent task graph').textContent).toContain(
'route: 程序组 / Code 生成可运行原型(code-prototype), 程序组 / Review 执行质量评审(quality-review) · code-runtime',
);
expect(screen.getByLabelText('Agent pass plans').textContent).toContain(
'pass 1: repair · active 程序组 / Code 生成可运行原型(code-prototype), 程序组 / Review 执行质量评审(quality-review) · carry 设计实现组 / Director 拆解创作方向(design-director) · waves 程序组 / Code 生成可运行原型(code-prototype) / 程序组 / Review 执行质量评审(quality-review) · repair 缺少输入监听 · routes code-runtime: 程序组 / Code 生成可运行原型(code-prototype), 程序组 / Review 执行质量评审(quality-review)',
);
expect(screen.getByLabelText('Agent artifacts').textContent).toContain(
'exports/artifact-8.json',
);
expect(screen.getByLabelText('Agent artifacts').textContent).not.toContain(
'exports/artifact-9.json',
);
expect(screen.getByLabelText('Agent artifacts').textContent).toContain(
'还有 1 个产物',
);
fireEvent.click(
within(screen.getByLabelText('Agent artifacts')).getByRole('button', {
name: '填入读取产物 exports/artifact-1.json',
}),
);
expect(await composerText()).toBe('/read exports/artifact-1.json');
fireEvent.click(screen.getByRole('button', { name: '刷新状态' }));
expect(
await screen.findByText('run: passed · 2/3 轮 · evaluator-passed'),
).not.toBeNull();
expect(invoke).toHaveBeenCalledWith('read_local_project_file', {
projectPath: '/tmp/authorized-game',
relativePath: '.agent/run.latest.json',
commandId: 'agent.trace_read',
});
});
it('requires project policy confirmation before refreshing trace from the panel', async () => {
const manifest = createGameCreationAppManifest(
'local-project-draft',
'未命名游戏原型',
);
const trace: GameCreationAgentRunTrace = {
schemaVersion: GAME_CREATION_AGENT_RUN_SCHEMA_VERSION,
runId: 'run-trace-panel-confirm',
commandId: 'game.generate_draft',
status: 'running',
lifecycleStatus: 'running',
passes: 1,
maxPasses: 3,
toolCallCount: 1,
maxToolCalls: 128,
stopReason: 'planning',
goal: '做一个厨房弹幕游戏',
coordination: 'filesystem',
steps: [],
artifacts: [],
taskGraph: {
goal: '做一个厨房弹幕游戏',
readyTaskIds: [],
activeTaskIds: [],
carriedTaskIds: [],
repairFocus: [],
repairRoutes: [],
tasks: createGameCreationAppSeedTasks(),
},
passPlans: [],
nextStep: 'planner',
error: null,
updatedAt: 1,
};
const invoke = vi.fn(
async (command: string, args?: Record<string, unknown>) => {
if (command === 'append_local_permission_log') {
return {};
}
if (command === 'init_local_game_project') {
const projectPath = String(args?.projectPath ?? '');
return {
projectPath,
manifestPath: `${projectPath}/.agent/manifest.json`,
manifest,
};
}
if (command === 'read_local_conversation') {
return {
path: '/tmp/authorized-game/.agent/conversations/project.jsonl',
agentId: null,
messages: [],
};
}
if (command === 'read_project_permission_policy') {
return {
path: '.agent/policy.json',
policy: {
deniedCommands: [],
confirmCommands: ['agent.trace_read'],
},
};
}
if (command === 'read_local_project_file') {
return {
path: '.agent/run.latest.json',
absolutePath: `${String(args?.projectPath ?? '')}/.agent/run.latest.json`,
content: JSON.stringify(trace),
};
}
if (command === 'list_local_project_files') {
return { projectPath: String(args?.projectPath ?? ''), files: [] };
}
throw new Error(`unexpected invoke ${command}`);
},
);
window.__TAURI__ = { core: { invoke } };
renderAppAt('/?dev&projectPath=%2Ftmp%2Fauthorized-game');
expect(
await screen.findByText('run: running / running · 1/3 轮 · planning'),
).not.toBeNull();
invoke.mockClear();
fireEvent.click(
within(screen.getByLabelText('Agent run trace')).getByRole('button', {
name: '刷新',
}),
);
expect(await screen.findByText('agent.trace_read')).not.toBeNull();
expect(invoke).not.toHaveBeenCalledWith(
'read_local_project_file',
expect.anything(),
);
fireEvent.click(screen.getByRole('button', { name: '确认' }));
await waitFor(() => {
expect(invoke).toHaveBeenCalledWith('read_local_project_file', {
projectPath: '/tmp/authorized-game',
relativePath: '.agent/run.latest.json',
commandId: 'agent.trace_read',
});
});
});
}