修正 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 下任何产品代码,未放宽或删除断言
This commit is contained in:
2026-09-10 11:39:35 +08:00
parent 691d0a8ce6
commit 53afc85319
15 changed files with 711 additions and 605 deletions
@@ -1,4 +1,6 @@
import { import {
composerText,
composerValue,
createGameCreationAppManifest, createGameCreationAppManifest,
createGameCreationAppSeedTasks, createGameCreationAppSeedTasks,
emptyProjectPolicy, emptyProjectPolicy,
@@ -9,6 +11,7 @@ import {
it, it,
renderAppAt, renderAppAt,
screen, screen,
setComposerText,
submitChat, submitChat,
vi, vi,
waitFor, waitFor,
@@ -167,14 +170,14 @@ export function registerAgentRuntimeCommandTests() {
window.__TAURI__ = { core: { invoke } }; window.__TAURI__ = { core: { invoke } };
renderAppAt('/'); renderAppAt('/');
submitChat('/project /tmp/authorized-game'); await submitChat('/project /tmp/authorized-game');
fireEvent.click(screen.getByRole('button', { name: '确认' })); fireEvent.click(screen.getByRole('button', { name: '确认' }));
expect( expect(
await screen.findByText('已设置本地项目:/tmp/authorized-game'), await screen.findByText('已设置本地项目:/tmp/authorized-game'),
).not.toBeNull(); ).not.toBeNull();
invoke.mockClear(); invoke.mockClear();
submitChat('/trace'); await submitChat('/trace');
expect(await screen.findByText(/Runrun-chat-trace/)).not.toBeNull(); expect(await screen.findByText(/Runrun-chat-trace/)).not.toBeNull();
expect( expect(
@@ -226,13 +229,13 @@ export function registerAgentRuntimeCommandTests() {
window.__TAURI__ = { core: { invoke } }; window.__TAURI__ = { core: { invoke } };
renderAppAt('/'); renderAppAt('/');
submitChat('/project /tmp/authorized-game'); await submitChat('/project /tmp/authorized-game');
fireEvent.click(screen.getByRole('button', { name: '确认' })); fireEvent.click(screen.getByRole('button', { name: '确认' }));
expect( expect(
await screen.findByText('已设置本地项目:/tmp/authorized-game'), await screen.findByText('已设置本地项目:/tmp/authorized-game'),
).not.toBeNull(); ).not.toBeNull();
submitChat('/trace'); await submitChat('/trace');
expect( expect(
await screen.findByText( await screen.findByText(
@@ -312,14 +315,14 @@ export function registerAgentRuntimeCommandTests() {
window.__TAURI__ = { core: { invoke } }; window.__TAURI__ = { core: { invoke } };
renderAppAt('/'); renderAppAt('/');
submitChat('/project /tmp/authorized-game'); await submitChat('/project /tmp/authorized-game');
fireEvent.click(screen.getByRole('button', { name: '确认' })); fireEvent.click(screen.getByRole('button', { name: '确认' }));
expect( expect(
await screen.findByText('已设置本地项目:/tmp/authorized-game'), await screen.findByText('已设置本地项目:/tmp/authorized-game'),
).not.toBeNull(); ).not.toBeNull();
invoke.mockClear(); invoke.mockClear();
submitChat('/trace'); await submitChat('/trace');
expect( expect(
await screen.findByText('准备读取 Agent run trace。'), await screen.findByText('准备读取 Agent run trace。'),
@@ -460,33 +463,24 @@ export function registerAgentRuntimeCommandTests() {
window.__TAURI__ = { core: { invoke } }; window.__TAURI__ = { core: { invoke } };
renderAppAt('/'); renderAppAt('/');
submitChat('/project /tmp/authorized-game'); await submitChat('/project /tmp/authorized-game');
fireEvent.click(screen.getByRole('button', { name: '确认' })); fireEvent.click(screen.getByRole('button', { name: '确认' }));
expect( expect(
await screen.findByText('已设置本地项目:/tmp/authorized-game'), await screen.findByText('已设置本地项目:/tmp/authorized-game'),
).not.toBeNull(); ).not.toBeNull();
fireEvent.click(screen.getByRole('button', { name: '输出' })); fireEvent.click(screen.getByRole('button', { name: '输出' }));
expect(screen.getByLabelText('创作想法')).toHaveProperty( expect(await composerText()).toBe('/read .agent/output.jsonl');
'value',
'/read .agent/output.jsonl',
);
fireEvent.click(screen.getByRole('button', { name: '活动' })); fireEvent.click(screen.getByRole('button', { name: '活动' }));
expect(screen.getByLabelText('创作想法')).toHaveProperty( expect(await composerText()).toBe('/read .agent/activity.jsonl');
'value',
'/read .agent/activity.jsonl',
);
fireEvent.click(screen.getByRole('button', { name: '上下文包' })); fireEvent.click(screen.getByRole('button', { name: '上下文包' }));
expect(screen.getByLabelText('创作想法')).toHaveProperty( expect(await composerText()).toBe('/read .agent/context.bundle.json');
'value',
'/read .agent/context.bundle.json',
);
expect(invoke).not.toHaveBeenCalledWith( expect(invoke).not.toHaveBeenCalledWith(
'read_local_project_file', 'read_local_project_file',
expect.objectContaining({ relativePath: '.agent/context.bundle.json' }), expect.objectContaining({ relativePath: '.agent/context.bundle.json' }),
); );
submitChat('/agent-status'); await submitChat('/agent-status');
expect( expect(
await screen.findByText( await screen.findByText(
/run run-control-chat 当前状态:pending \/ pending/, /run run-control-chat 当前状态:pending \/ pending/,
@@ -494,17 +488,12 @@ export function registerAgentRuntimeCommandTests() {
).not.toBeNull(); ).not.toBeNull();
expect(screen.getByText(/runrun-control-chat/)).not.toBeNull(); expect(screen.getByText(/runrun-control-chat/)).not.toBeNull();
fireEvent.click(screen.getByRole('button', { name: '读取 Run 输出' })); fireEvent.click(screen.getByRole('button', { name: '读取 Run 输出' }));
expect(screen.getByLabelText('创作想法')).toHaveProperty( expect(await composerText()).toBe('/read .agent/output.jsonl');
'value',
'/read .agent/output.jsonl',
);
expect(invoke).not.toHaveBeenCalledWith( expect(invoke).not.toHaveBeenCalledWith(
'read_local_project_file', 'read_local_project_file',
expect.objectContaining({ relativePath: '.agent/output.jsonl' }), expect.objectContaining({ relativePath: '.agent/output.jsonl' }),
); );
fireEvent.change(screen.getByLabelText('创作想法'), { await setComposerText(screen.getByLabelText('创作想法'), '');
target: { value: '' },
});
expect(invoke).toHaveBeenCalledWith('control_agent_run', { expect(invoke).toHaveBeenCalledWith('control_agent_run', {
projectPath: '/tmp/authorized-game', projectPath: '/tmp/authorized-game',
action: 'status', action: 'status',
@@ -516,7 +505,7 @@ export function registerAgentRuntimeCommandTests() {
commandId: 'agent.run_status', commandId: 'agent.run_status',
}); });
submitChat('/agent-kill'); await submitChat('/agent-kill');
expect(screen.getByText('agent.kill')).not.toBeNull(); expect(screen.getByText('agent.kill')).not.toBeNull();
expect( expect(
screen.getByText( screen.getByText(
@@ -533,7 +522,7 @@ export function registerAgentRuntimeCommandTests() {
detail: undefined, detail: undefined,
}); });
submitChat('/agent-retry'); await submitChat('/agent-retry');
expect(screen.getByText('agent.retry')).not.toBeNull(); expect(screen.getByText('agent.retry')).not.toBeNull();
expect( expect(
screen.getByText( screen.getByText(
@@ -552,7 +541,7 @@ export function registerAgentRuntimeCommandTests() {
detail: undefined, detail: undefined,
}); });
submitChat('/agent-resume 继续修复输入监听'); await submitChat('/agent-resume 继续修复输入监听');
expect(screen.getByText('agent.resume')).not.toBeNull(); expect(screen.getByText('agent.resume')).not.toBeNull();
expect( expect(
screen.getByText( screen.getByText(
@@ -604,13 +593,13 @@ export function registerAgentRuntimeCommandTests() {
window.__TAURI__ = { core: { invoke } }; window.__TAURI__ = { core: { invoke } };
renderAppAt('/'); renderAppAt('/');
submitChat('/project /tmp/authorized-game'); await submitChat('/project /tmp/authorized-game');
fireEvent.click(screen.getByRole('button', { name: '确认' })); fireEvent.click(screen.getByRole('button', { name: '确认' }));
expect( expect(
await screen.findByText('已设置本地项目:/tmp/authorized-game'), await screen.findByText('已设置本地项目:/tmp/authorized-game'),
).not.toBeNull(); ).not.toBeNull();
submitChat('/agent-status'); await submitChat('/agent-status');
expect( expect(
await screen.findByText( await screen.findByText(
@@ -654,13 +643,13 @@ export function registerAgentRuntimeCommandTests() {
window.__TAURI__ = { core: { invoke } }; window.__TAURI__ = { core: { invoke } };
renderAppAt('/'); renderAppAt('/');
submitChat('/project /tmp/authorized-game'); await submitChat('/project /tmp/authorized-game');
fireEvent.click(screen.getByRole('button', { name: '确认' })); fireEvent.click(screen.getByRole('button', { name: '确认' }));
expect( expect(
await screen.findByText('已设置本地项目:/tmp/authorized-game'), await screen.findByText('已设置本地项目:/tmp/authorized-game'),
).not.toBeNull(); ).not.toBeNull();
submitChat('/agent-kill'); await submitChat('/agent-kill');
fireEvent.click(screen.getByRole('button', { name: '确认' })); fireEvent.click(screen.getByRole('button', { name: '确认' }));
expect( expect(
@@ -709,13 +698,13 @@ export function registerAgentRuntimeCommandTests() {
window.__TAURI__ = { core: { invoke } }; window.__TAURI__ = { core: { invoke } };
renderAppAt('/'); renderAppAt('/');
submitChat('/project /tmp/authorized-game'); await submitChat('/project /tmp/authorized-game');
fireEvent.click(screen.getByRole('button', { name: '确认' })); fireEvent.click(screen.getByRole('button', { name: '确认' }));
expect( expect(
await screen.findByText('已设置本地项目:/tmp/authorized-game'), await screen.findByText('已设置本地项目:/tmp/authorized-game'),
).not.toBeNull(); ).not.toBeNull();
submitChat('/agent-kill'); await submitChat('/agent-kill');
expect(screen.getByText('agent.kill')).not.toBeNull(); expect(screen.getByText('agent.kill')).not.toBeNull();
fireEvent.click(screen.getByRole('button', { name: '确认' })); fireEvent.click(screen.getByRole('button', { name: '确认' }));
@@ -817,14 +806,14 @@ export function registerAgentRuntimeCommandTests() {
window.__TAURI__ = { core: { invoke } }; window.__TAURI__ = { core: { invoke } };
renderAppAt('/'); renderAppAt('/');
submitChat('/project /tmp/authorized-game'); await submitChat('/project /tmp/authorized-game');
fireEvent.click(screen.getByRole('button', { name: '确认' })); fireEvent.click(screen.getByRole('button', { name: '确认' }));
expect( expect(
await screen.findByText('已设置本地项目:/tmp/authorized-game'), await screen.findByText('已设置本地项目:/tmp/authorized-game'),
).not.toBeNull(); ).not.toBeNull();
invoke.mockClear(); invoke.mockClear();
submitChat('/agent-kill'); await submitChat('/agent-kill');
fireEvent.click(screen.getByRole('button', { name: '确认' })); fireEvent.click(screen.getByRole('button', { name: '确认' }));
expect(await screen.findByText('准备执行 Agent run 操作。')).not.toBeNull(); expect(await screen.findByText('准备执行 Agent run 操作。')).not.toBeNull();
@@ -927,14 +916,14 @@ export function registerAgentRuntimeCommandTests() {
window.__TAURI__ = { core: { invoke } }; window.__TAURI__ = { core: { invoke } };
renderAppAt('/'); renderAppAt('/');
submitChat('/project /tmp/authorized-game'); await submitChat('/project /tmp/authorized-game');
fireEvent.click(screen.getByRole('button', { name: '确认' })); fireEvent.click(screen.getByRole('button', { name: '确认' }));
expect( expect(
await screen.findByText('已设置本地项目:/tmp/authorized-game'), await screen.findByText('已设置本地项目:/tmp/authorized-game'),
).not.toBeNull(); ).not.toBeNull();
invoke.mockClear(); invoke.mockClear();
submitChat('/agent-status'); await submitChat('/agent-status');
expect(await screen.findByText('准备查看 Agent run 状态。')).not.toBeNull(); expect(await screen.findByText('准备查看 Agent run 状态。')).not.toBeNull();
expect(screen.getByText('agent.run_status')).not.toBeNull(); expect(screen.getByText('agent.run_status')).not.toBeNull();
@@ -993,13 +982,13 @@ export function registerAgentRuntimeCommandTests() {
window.__TAURI__ = { core: { invoke } }; window.__TAURI__ = { core: { invoke } };
renderAppAt('/'); renderAppAt('/');
submitChat('/project /tmp/authorized-game'); await submitChat('/project /tmp/authorized-game');
fireEvent.click(screen.getByRole('button', { name: '确认' })); fireEvent.click(screen.getByRole('button', { name: '确认' }));
expect( expect(
await screen.findByText('已设置本地项目:/tmp/authorized-game'), await screen.findByText('已设置本地项目:/tmp/authorized-game'),
).not.toBeNull(); ).not.toBeNull();
submitChat('/agent-status'); await submitChat('/agent-status');
const agentStatusCommand = await screen.findByText('agent.run_status'); const agentStatusCommand = await screen.findByText('agent.run_status');
fireEvent.click( fireEvent.click(
@@ -1044,13 +1033,13 @@ export function registerAgentRuntimeCommandTests() {
window.__TAURI__ = { core: { invoke } }; window.__TAURI__ = { core: { invoke } };
renderAppAt('/'); renderAppAt('/');
submitChat('/project /tmp/authorized-game'); await submitChat('/project /tmp/authorized-game');
fireEvent.click(screen.getByRole('button', { name: '确认' })); fireEvent.click(screen.getByRole('button', { name: '确认' }));
expect( expect(
await screen.findByText('已设置本地项目:/tmp/authorized-game'), await screen.findByText('已设置本地项目:/tmp/authorized-game'),
).not.toBeNull(); ).not.toBeNull();
submitChat('/agent-kill'); await submitChat('/agent-kill');
const agentCommand = screen.getByText('agent.kill'); const agentCommand = screen.getByText('agent.kill');
fireEvent.click( fireEvent.click(
within(agentCommand.closest('.pending-command') as HTMLElement).getByRole( within(agentCommand.closest('.pending-command') as HTMLElement).getByRole(
@@ -1150,14 +1139,14 @@ export function registerAgentRuntimeCommandTests() {
window.__TAURI__ = { core: { invoke } }; window.__TAURI__ = { core: { invoke } };
renderAppAt('/'); renderAppAt('/');
submitChat('/project /tmp/authorized-game'); await submitChat('/project /tmp/authorized-game');
fireEvent.click(screen.getByRole('button', { name: '确认' })); fireEvent.click(screen.getByRole('button', { name: '确认' }));
expect( expect(
await screen.findByText('已设置本地项目:/tmp/authorized-game'), await screen.findByText('已设置本地项目:/tmp/authorized-game'),
).not.toBeNull(); ).not.toBeNull();
invoke.mockClear(); invoke.mockClear();
submitChat('/agent-status'); await submitChat('/agent-status');
expect( expect(
await screen.findByText( await screen.findByText(
@@ -1264,7 +1253,7 @@ export function registerAgentRuntimeCommandTests() {
window.__TAURI__ = { core: { invoke } }; window.__TAURI__ = { core: { invoke } };
renderAppAt('/'); renderAppAt('/');
submitChat('/project /tmp/authorized-game'); await submitChat('/project /tmp/authorized-game');
fireEvent.click(screen.getByRole('button', { name: '确认' })); fireEvent.click(screen.getByRole('button', { name: '确认' }));
expect( expect(
await screen.findByText('已设置本地项目:/tmp/authorized-game'), await screen.findByText('已设置本地项目:/tmp/authorized-game'),
@@ -1314,7 +1303,7 @@ export function registerAgentRuntimeCommandTests() {
}); });
const composerInput = screen.getByLabelText('创作想法'); const composerInput = screen.getByLabelText('创作想法');
fireEvent.click(screen.getByRole('button', { name: '继续说明' })); fireEvent.click(screen.getByRole('button', { name: '继续说明' }));
expect(composerInput).toHaveProperty('value', '/agent-resume '); expect(await composerValue(composerInput)).toBe('/agent-resume ');
}); });
it('requires project policy confirmation before reading agent run status from the panel', async () => { it('requires project policy confirmation before reading agent run status from the panel', async () => {
@@ -1399,7 +1388,7 @@ export function registerAgentRuntimeCommandTests() {
window.__TAURI__ = { core: { invoke } }; window.__TAURI__ = { core: { invoke } };
renderAppAt('/'); renderAppAt('/');
submitChat('/project /tmp/authorized-game'); await submitChat('/project /tmp/authorized-game');
fireEvent.click(screen.getByRole('button', { name: '确认' })); fireEvent.click(screen.getByRole('button', { name: '确认' }));
expect( expect(
await screen.findByText('已设置本地项目:/tmp/authorized-game'), await screen.findByText('已设置本地项目:/tmp/authorized-game'),
@@ -1507,7 +1496,7 @@ export function registerAgentRuntimeCommandTests() {
window.__TAURI__ = { core: { invoke } }; window.__TAURI__ = { core: { invoke } };
renderAppAt('/'); renderAppAt('/');
submitChat('/project /tmp/authorized-game'); await submitChat('/project /tmp/authorized-game');
fireEvent.click(screen.getByRole('button', { name: '确认' })); fireEvent.click(screen.getByRole('button', { name: '确认' }));
expect( expect(
await screen.findByText('已设置本地项目:/tmp/authorized-game'), await screen.findByText('已设置本地项目:/tmp/authorized-game'),
@@ -1595,16 +1584,16 @@ export function registerAgentRuntimeCommandTests() {
window.__TAURI__ = { core: { invoke } }; window.__TAURI__ = { core: { invoke } };
renderAppAt('/'); renderAppAt('/');
submitChat('/project /tmp/authorized-game'); await submitChat('/project /tmp/authorized-game');
fireEvent.click(screen.getByRole('button', { name: '确认' })); fireEvent.click(screen.getByRole('button', { name: '确认' }));
expect( expect(
await screen.findByText('已设置本地项目:/tmp/authorized-game'), await screen.findByText('已设置本地项目:/tmp/authorized-game'),
).not.toBeNull(); ).not.toBeNull();
submitChat('/memory'); await submitChat('/memory');
expect(await screen.findByText(/长期记忆:/)).not.toBeNull(); expect(await screen.findByText(/长期记忆:/)).not.toBeNull();
submitChat('/remember long 保留厨房主题'); await submitChat('/remember long 保留厨房主题');
fireEvent.click(screen.getByRole('button', { name: '确认' })); fireEvent.click(screen.getByRole('button', { name: '确认' }));
expect(await screen.findByText('已追加长期记忆。')).not.toBeNull(); expect(await screen.findByText('已追加长期记忆。')).not.toBeNull();
expect(invoke).toHaveBeenCalledWith('read_local_game_memory', { expect(invoke).toHaveBeenCalledWith('read_local_game_memory', {
@@ -1617,7 +1606,7 @@ export function registerAgentRuntimeCommandTests() {
content: '# 项目长期记忆\n- 保留厨房主题\n', content: '# 项目长期记忆\n- 保留厨房主题\n',
}); });
submitChat('/memory-set long 覆盖后的长期记忆'); await submitChat('/memory-set long 覆盖后的长期记忆');
fireEvent.click(screen.getByRole('button', { name: '确认' })); fireEvent.click(screen.getByRole('button', { name: '确认' }));
expect(await screen.findByText('已保存长期记忆。')).not.toBeNull(); expect(await screen.findByText('已保存长期记忆。')).not.toBeNull();
expect(invoke).toHaveBeenCalledWith('write_local_game_memory', { expect(invoke).toHaveBeenCalledWith('write_local_game_memory', {
@@ -1626,7 +1615,7 @@ export function registerAgentRuntimeCommandTests() {
content: '覆盖后的长期记忆', content: '覆盖后的长期记忆',
}); });
submitChat('/forget-memory'); await submitChat('/forget-memory');
fireEvent.click(screen.getByRole('button', { name: '确认' })); fireEvent.click(screen.getByRole('button', { name: '确认' }));
expect(await screen.findByText('已删除长期记忆。')).not.toBeNull(); expect(await screen.findByText('已删除长期记忆。')).not.toBeNull();
expect(invoke).toHaveBeenCalledWith('delete_local_game_memory', { expect(invoke).toHaveBeenCalledWith('delete_local_game_memory', {
@@ -1676,14 +1665,14 @@ export function registerAgentRuntimeCommandTests() {
window.__TAURI__ = { core: { invoke } }; window.__TAURI__ = { core: { invoke } };
renderAppAt('/'); renderAppAt('/');
submitChat('/project /tmp/authorized-game'); await submitChat('/project /tmp/authorized-game');
fireEvent.click(screen.getByRole('button', { name: '确认' })); fireEvent.click(screen.getByRole('button', { name: '确认' }));
expect( expect(
await screen.findByText('已设置本地项目:/tmp/authorized-game'), await screen.findByText('已设置本地项目:/tmp/authorized-game'),
).not.toBeNull(); ).not.toBeNull();
invoke.mockClear(); invoke.mockClear();
submitChat('/memory'); await submitChat('/memory');
expect(await screen.findByText('准备读取项目记忆。')).not.toBeNull(); expect(await screen.findByText('准备读取项目记忆。')).not.toBeNull();
expect(screen.getByText('memory.read')).not.toBeNull(); expect(screen.getByText('memory.read')).not.toBeNull();
@@ -1737,14 +1726,14 @@ export function registerAgentRuntimeCommandTests() {
window.__TAURI__ = { core: { invoke } }; window.__TAURI__ = { core: { invoke } };
renderAppAt('/'); renderAppAt('/');
submitChat('/project /tmp/authorized-game'); await submitChat('/project /tmp/authorized-game');
fireEvent.click(screen.getByRole('button', { name: '确认' })); fireEvent.click(screen.getByRole('button', { name: '确认' }));
expect( expect(
await screen.findByText('已设置本地项目:/tmp/authorized-game'), await screen.findByText('已设置本地项目:/tmp/authorized-game'),
).not.toBeNull(); ).not.toBeNull();
invoke.mockClear(); invoke.mockClear();
submitChat('/memory'); await submitChat('/memory');
expect(await screen.findByText('准备读取项目记忆。')).not.toBeNull(); expect(await screen.findByText('准备读取项目记忆。')).not.toBeNull();
const memoryReadCommand = screen.getByText('memory.read'); const memoryReadCommand = screen.getByText('memory.read');
@@ -1822,16 +1811,16 @@ export function registerAgentRuntimeCommandTests() {
window.__TAURI__ = { core: { invoke } }; window.__TAURI__ = { core: { invoke } };
renderAppAt('/'); renderAppAt('/');
submitChat('/project /tmp/authorized-game'); await submitChat('/project /tmp/authorized-game');
fireEvent.click(screen.getByRole('button', { name: '确认' })); fireEvent.click(screen.getByRole('button', { name: '确认' }));
expect(await screen.findByText('已打开:authorized-game')).not.toBeNull(); expect(await screen.findByText('已打开:authorized-game')).not.toBeNull();
submitChat('/memory typo'); await submitChat('/memory typo');
expect( expect(
await screen.findByText('格式:/memory [short|long|blackboard]'), await screen.findByText('格式:/memory [short|long|blackboard]'),
).not.toBeNull(); ).not.toBeNull();
submitChat('/forget-memory typo'); await submitChat('/forget-memory typo');
expect( expect(
await screen.findByText('格式:/forget-memory [short|long|blackboard]'), await screen.findByText('格式:/forget-memory [short|long|blackboard]'),
).not.toBeNull(); ).not.toBeNull();
@@ -1901,16 +1890,16 @@ export function registerAgentRuntimeCommandTests() {
window.__TAURI__ = { core: { invoke } }; window.__TAURI__ = { core: { invoke } };
renderAppAt('/'); renderAppAt('/');
submitChat('/project /tmp/authorized-game'); await submitChat('/project /tmp/authorized-game');
fireEvent.click(screen.getByRole('button', { name: '确认' })); fireEvent.click(screen.getByRole('button', { name: '确认' }));
expect( expect(
await screen.findByText('已设置本地项目:/tmp/authorized-game'), await screen.findByText('已设置本地项目:/tmp/authorized-game'),
).not.toBeNull(); ).not.toBeNull();
submitChat('/memory blackboard'); await submitChat('/memory blackboard');
expect(await screen.findByText(/黑板记忆:/)).not.toBeNull(); expect(await screen.findByText(/黑板记忆:/)).not.toBeNull();
submitChat('/remember blackboard 共享美术约束'); await submitChat('/remember blackboard 共享美术约束');
fireEvent.click(screen.getByRole('button', { name: '确认' })); fireEvent.click(screen.getByRole('button', { name: '确认' }));
expect(await screen.findByText('已追加黑板记忆。')).not.toBeNull(); expect(await screen.findByText('已追加黑板记忆。')).not.toBeNull();
expect(invoke).toHaveBeenCalledWith('write_local_game_memory', { expect(invoke).toHaveBeenCalledWith('write_local_game_memory', {
@@ -1919,7 +1908,7 @@ export function registerAgentRuntimeCommandTests() {
content: '# 项目黑板\n- 共享美术约束\n', content: '# 项目黑板\n- 共享美术约束\n',
}); });
submitChat('/memory-set 黑板 统一使用俯视角'); await submitChat('/memory-set 黑板 统一使用俯视角');
fireEvent.click(screen.getByRole('button', { name: '确认' })); fireEvent.click(screen.getByRole('button', { name: '确认' }));
expect(await screen.findByText('已保存黑板记忆。')).not.toBeNull(); expect(await screen.findByText('已保存黑板记忆。')).not.toBeNull();
expect(invoke).toHaveBeenCalledWith('write_local_game_memory', { expect(invoke).toHaveBeenCalledWith('write_local_game_memory', {
@@ -1928,7 +1917,7 @@ export function registerAgentRuntimeCommandTests() {
content: '统一使用俯视角', content: '统一使用俯视角',
}); });
submitChat('/forget-memory blackboard'); await submitChat('/forget-memory blackboard');
fireEvent.click(screen.getByRole('button', { name: '确认' })); fireEvent.click(screen.getByRole('button', { name: '确认' }));
expect(await screen.findByText('已删除黑板记忆。')).not.toBeNull(); expect(await screen.findByText('已删除黑板记忆。')).not.toBeNull();
expect(invoke).toHaveBeenCalledWith('delete_local_game_memory', { expect(invoke).toHaveBeenCalledWith('delete_local_game_memory', {
@@ -4931,10 +4931,10 @@ export function registerDeveloperToolsTests() {
expect(invoke).not.toHaveBeenCalled(); expect(invoke).not.toHaveBeenCalled();
}); });
it('lists standard agent capabilities from chat without opening dev panels', () => { it('lists standard agent capabilities from chat without opening dev panels', async () => {
renderAppAt('/'); renderAppAt('/');
submitChat('/capabilities'); await submitChat('/capabilities');
const capabilities = screen.getByText(/Agent 能力清单:/); const capabilities = screen.getByText(/Agent 能力清单:/);
expect(capabilities.textContent).toMatch(/任务拆分/); expect(capabilities.textContent).toMatch(/任务拆分/);
@@ -4966,7 +4966,7 @@ export function registerDeveloperToolsTests() {
window.__TAURI__ = { core: { invoke } }; window.__TAURI__ = { core: { invoke } };
renderAppAt('/'); renderAppAt('/');
submitChat('/capabilities'); await submitChat('/capabilities');
expect(await screen.findByText(/Native Runtime 能力/)).not.toBeNull(); expect(await screen.findByText(/Native Runtime 能力/)).not.toBeNull();
expect(screen.queryByText(/任务拆分/)).toBeNull(); expect(screen.queryByText(/任务拆分/)).toBeNull();
@@ -4983,17 +4983,17 @@ export function registerDeveloperToolsTests() {
window.__TAURI__ = { core: { invoke } }; window.__TAURI__ = { core: { invoke } };
renderAppAt('/'); renderAppAt('/');
submitChat('/capabilities'); await submitChat('/capabilities');
expect(await screen.findByText(/任务拆分/)).not.toBeNull(); expect(await screen.findByText(/任务拆分/)).not.toBeNull();
expect(screen.getByText(/本地 HTTP 预览/)).not.toBeNull(); expect(screen.getByText(/本地 HTTP 预览/)).not.toBeNull();
expect(invoke).toHaveBeenCalledWith('get_game_creation_agent_capabilities'); expect(invoke).toHaveBeenCalledWith('get_game_creation_agent_capabilities');
}); });
it('exposes the audit command from chat help', () => { it('exposes the audit command from chat help', async () => {
renderAppAt('/'); renderAppAt('/');
submitChat('/help'); await submitChat('/help');
expect( expect(
screen.getByText(/\/audit:审计当前项目的 Agent 能力证据/), screen.getByText(/\/audit:审计当前项目的 Agent 能力证据/),
@@ -5472,11 +5472,11 @@ export function registerDeveloperToolsTests() {
window.__TAURI__ = { core: { invoke } }; window.__TAURI__ = { core: { invoke } };
renderAppAt('/'); renderAppAt('/');
submitChat('/project /tmp/authorized-game'); await submitChat('/project /tmp/authorized-game');
fireEvent.click(screen.getByRole('button', { name: '确认' })); fireEvent.click(screen.getByRole('button', { name: '确认' }));
expect(await screen.findByText('已打开:authorized-game')).not.toBeNull(); expect(await screen.findByText('已打开:authorized-game')).not.toBeNull();
submitChat('/audit'); await submitChat('/audit');
expect(await screen.findByText(/Agent v1 审计:/)).not.toBeNull(); expect(await screen.findByText(/Agent v1 审计:/)).not.toBeNull();
expect(screen.getByText(/能力\/命令契约:通过/)).not.toBeNull(); expect(screen.getByText(/能力\/命令契约:通过/)).not.toBeNull();
@@ -5598,12 +5598,12 @@ export function registerDeveloperToolsTests() {
window.__TAURI__ = { core: { invoke } }; window.__TAURI__ = { core: { invoke } };
renderAppAt('/'); renderAppAt('/');
submitChat('/project /tmp/authorized-game'); await submitChat('/project /tmp/authorized-game');
fireEvent.click(screen.getByRole('button', { name: '确认' })); fireEvent.click(screen.getByRole('button', { name: '确认' }));
expect(await screen.findByText('已打开:authorized-game')).not.toBeNull(); expect(await screen.findByText('已打开:authorized-game')).not.toBeNull();
invoke.mockClear(); invoke.mockClear();
submitChat('/audit'); await submitChat('/audit');
expect( expect(
await screen.findByText('准备确认审计读取:agent.trace_read'), await screen.findByText('准备确认审计读取:agent.trace_read'),
@@ -5679,12 +5679,12 @@ export function registerDeveloperToolsTests() {
window.__TAURI__ = { core: { invoke } }; window.__TAURI__ = { core: { invoke } };
renderAppAt('/'); renderAppAt('/');
submitChat('/project /tmp/authorized-game'); await submitChat('/project /tmp/authorized-game');
fireEvent.click(screen.getByRole('button', { name: '确认' })); fireEvent.click(screen.getByRole('button', { name: '确认' }));
expect(await screen.findByText('已打开:authorized-game')).not.toBeNull(); expect(await screen.findByText('已打开:authorized-game')).not.toBeNull();
invoke.mockClear(); invoke.mockClear();
submitChat('/audit'); await submitChat('/audit');
expect( expect(
await screen.findByText('准备确认审计读取:file.read'), await screen.findByText('准备确认审计读取:file.read'),
@@ -5791,12 +5791,12 @@ export function registerDeveloperToolsTests() {
window.__TAURI__ = { core: { invoke } }; window.__TAURI__ = { core: { invoke } };
renderAppAt('/'); renderAppAt('/');
submitChat('/project /tmp/authorized-game'); await submitChat('/project /tmp/authorized-game');
fireEvent.click(screen.getByRole('button', { name: '确认' })); fireEvent.click(screen.getByRole('button', { name: '确认' }));
expect(await screen.findByText('已打开:authorized-game')).not.toBeNull(); expect(await screen.findByText('已打开:authorized-game')).not.toBeNull();
invoke.mockClear(); invoke.mockClear();
submitChat('/audit'); await submitChat('/audit');
expect( expect(
await screen.findByText('准备确认审计读取:file.read'), await screen.findByText('准备确认审计读取:file.read'),
@@ -5887,11 +5887,11 @@ export function registerDeveloperToolsTests() {
window.__TAURI__ = { core: { invoke } }; window.__TAURI__ = { core: { invoke } };
renderAppAt('/'); renderAppAt('/');
submitChat('/project /tmp/authorized-game'); await submitChat('/project /tmp/authorized-game');
fireEvent.click(screen.getByRole('button', { name: '确认' })); fireEvent.click(screen.getByRole('button', { name: '确认' }));
expect(await screen.findByText('已打开:authorized-game')).not.toBeNull(); expect(await screen.findByText('已打开:authorized-game')).not.toBeNull();
submitChat('/audit'); await submitChat('/audit');
expect(await screen.findByText(/权限 Gate\/命令日志:待补/)).not.toBeNull(); expect(await screen.findByText(/权限 Gate\/命令日志:待补/)).not.toBeNull();
expect( expect(
@@ -5947,11 +5947,11 @@ export function registerDeveloperToolsTests() {
window.__TAURI__ = { core: { invoke } }; window.__TAURI__ = { core: { invoke } };
renderAppAt('/'); renderAppAt('/');
submitChat('/project /tmp/authorized-game'); await submitChat('/project /tmp/authorized-game');
fireEvent.click(screen.getByRole('button', { name: '确认' })); fireEvent.click(screen.getByRole('button', { name: '确认' }));
expect(await screen.findByText('已打开:authorized-game')).not.toBeNull(); expect(await screen.findByText('已打开:authorized-game')).not.toBeNull();
submitChat('/audit'); await submitChat('/audit');
expect(await screen.findByText(/权限 Gate\/命令日志:通过/)).not.toBeNull(); expect(await screen.findByText(/权限 Gate\/命令日志:通过/)).not.toBeNull();
expect(screen.getByText(/含 auto 权限记录/)).not.toBeNull(); expect(screen.getByText(/含 auto 权限记录/)).not.toBeNull();
@@ -5996,11 +5996,11 @@ export function registerDeveloperToolsTests() {
window.__TAURI__ = { core: { invoke } }; window.__TAURI__ = { core: { invoke } };
renderAppAt('/'); renderAppAt('/');
submitChat('/project /tmp/authorized-game'); await submitChat('/project /tmp/authorized-game');
fireEvent.click(screen.getByRole('button', { name: '确认' })); fireEvent.click(screen.getByRole('button', { name: '确认' }));
expect(await screen.findByText('已打开:authorized-game')).not.toBeNull(); expect(await screen.findByText('已打开:authorized-game')).not.toBeNull();
submitChat('/audit'); await submitChat('/audit');
expect(await screen.findByText(/6 组任务配置:通过/)).not.toBeNull(); expect(await screen.findByText(/6 组任务配置:通过/)).not.toBeNull();
expect(screen.getByText(/6 组协作证据:待生成/)).not.toBeNull(); expect(screen.getByText(/6 组协作证据:待生成/)).not.toBeNull();
@@ -35,6 +35,15 @@ const originalPointerEvent = Object.getOwnPropertyDescriptor(
window, window,
'PointerEvent', 'PointerEvent',
); );
const originalClipboardEvent = Object.getOwnPropertyDescriptor(
window,
'ClipboardEvent',
);
const originalDragEvent = Object.getOwnPropertyDescriptor(window, 'DragEvent');
const originalRangeGetBoundingClientRect = Object.getOwnPropertyDescriptor(
Range.prototype,
'getBoundingClientRect',
);
const originalIntersectionObserver = Object.getOwnPropertyDescriptor( const originalIntersectionObserver = Object.getOwnPropertyDescriptor(
window, window,
'IntersectionObserver', 'IntersectionObserver',
@@ -56,6 +65,29 @@ class TestPointerEvent extends MouseEvent {
} }
} }
// jsdom 没有实现 DragEvent / ClipboardEvent,而 Lexical 在粘贴与拖放处理里用
// `instanceof DragEvent` / `instanceof ClipboardEvent` 判定事件来源,缺失会直接抛
// ReferenceError,粘贴通路根本进不去编辑器。这里只补最小的构造器,让事件能带上
// `clipboardData` 并被 Lexical 认出来,不模拟真实浏览器语义。
class TestClipboardEvent extends Event {}
class TestDragEvent extends MouseEvent {}
// jsdom 的 Range 也没有 getBoundingClientRectLexical 计算选区矩形时会抛 TypeError。
function emptyRangeRect(): DOMRect {
return {
x: 0,
y: 0,
top: 0,
left: 0,
right: 0,
bottom: 0,
width: 0,
height: 0,
toJSON: () => ({}),
} as DOMRect;
}
function createMemoryStorage(): Storage { function createMemoryStorage(): Storage {
const values = new Map<string, string>(); const values = new Map<string, string>();
return { return {
@@ -192,10 +224,111 @@ function pickProjectFromLauncher(
fireEvent.click(screen.getByRole('button', { name: action })); fireEvent.click(screen.getByRole('button', { name: action }));
} }
function submitChat(value: string) { function isNativeTextControl(
fireEvent.change(screen.getByLabelText('创作想法'), { element: HTMLElement,
target: { value }, ): element is HTMLInputElement | HTMLTextAreaElement {
return element.tagName === 'INPUT' || element.tagName === 'TEXTAREA';
}
// Lexical 的编辑器状态提交排在微任务里(外部 value 变化、粘贴、删除都走这条路径),
// 读取或提交前先让 React 追平编辑器内容,否则会读到上一帧的文本/禁用状态。
// 这里让出一个宏任务而不是用 act(async),因为 act 在编辑器持续自更新的用例里会长时间不收敛。
async function settleComposer() {
await new Promise((resolve) => {
setTimeout(resolve, 0);
}); });
}
// 读取输入区文本:原生 input/textarea 读 valueLexical contenteditable 读 textContent。
async function composerValue(element: HTMLElement) {
await settleComposer();
return isNativeTextControl(element)
? element.value
: (element.textContent ?? '');
}
type ComposerScope = { getByLabelText: (label: string) => HTMLElement };
async function composerText(label = '创作想法', scope: ComposerScope = screen) {
return composerValue(scope.getByLabelText(label));
}
async function composerDisabled(
label = '创作想法',
scope: ComposerScope = screen,
) {
await settleComposer();
const element = scope.getByLabelText(label);
if (isNativeTextControl(element)) {
return element.disabled;
}
const wrapper = element.closest('.resource-reference-input');
return (
wrapper?.getAttribute('data-disabled') === 'true' ||
element.getAttribute('contenteditable') !== 'true'
);
}
// 把整段内容选进 DOM 选区,并让 Lexical 吸收它:jsdom 不会为程序化选区派发
// selectionchange,编辑器内部 selection 不更新的话,粘贴会插在旧内容旁边而不是覆盖。
function selectComposerContents(element: HTMLElement) {
element.focus();
const range = document.createRange();
range.selectNodeContents(element);
const selection = window.getSelection();
selection?.removeAllRanges();
selection?.addRange(range);
document.dispatchEvent(new Event('selectionchange'));
}
function clearComposerContents(element: HTMLElement) {
fireEvent.keyDown(element, { key: 'Delete', code: 'Delete' });
}
// 写完内容后把光标收回到末尾,模拟真实输入后的选区;否则残留的“全选”会改变
// Shift+Enter 等按键的后续语义。
function collapseComposerSelectionToEnd(element: HTMLElement) {
const range = document.createRange();
range.selectNodeContents(element);
range.collapse(false);
const selection = window.getSelection();
selection?.removeAllRanges();
selection?.addRange(range);
document.dispatchEvent(new Event('selectionchange'));
}
function pasteComposerText(element: HTMLElement, value: string) {
fireEvent.paste(element, {
clipboardData: {
getData: (type: string) => (type === 'text/plain' ? value : ''),
files: [],
items: [],
types: ['text/plain'],
},
});
}
// 向输入区写入文本,保证写入结果等于 value。原生控件用 change 事件;Lexical
// contenteditable 先全选、清空,再走产品真实的 paste 通路写入。
async function setComposerText(element: HTMLElement, value: string) {
if (isNativeTextControl(element)) {
fireEvent.change(element, { target: { value } });
return;
}
selectComposerContents(element);
await settleComposer();
clearComposerContents(element);
await settleComposer();
if (value.length > 0) {
pasteComposerText(element, value);
await settleComposer();
}
collapseComposerSelectionToEnd(element);
await settleComposer();
}
async function submitChat(value: string) {
await setComposerText(screen.getByLabelText('创作想法'), value);
fireEvent.click(screen.getByRole('button', { name: '发送' })); fireEvent.click(screen.getByRole('button', { name: '发送' }));
} }
@@ -1036,7 +1169,7 @@ function createProjectSupervisorRuntimeHarness({
} }
async function openMainProject(projectPath: string) { async function openMainProject(projectPath: string) {
submitChat(`/project ${projectPath}`); await submitChat(`/project ${projectPath}`);
fireEvent.click(screen.getByRole('button', { name: '确认' })); fireEvent.click(screen.getByRole('button', { name: '确认' }));
expect( expect(
await screen.findByText(`已打开:${projectNameFromPath(projectPath)}`), await screen.findByText(`已打开:${projectNameFromPath(projectPath)}`),
@@ -1057,6 +1190,18 @@ beforeEach(() => {
configurable: true, configurable: true,
value: TestPointerEvent, value: TestPointerEvent,
}); });
Object.defineProperty(window, 'ClipboardEvent', {
configurable: true,
value: TestClipboardEvent,
});
Object.defineProperty(window, 'DragEvent', {
configurable: true,
value: TestDragEvent,
});
Object.defineProperty(Range.prototype, 'getBoundingClientRect', {
configurable: true,
value: emptyRangeRect,
});
Object.defineProperties(URL, { Object.defineProperties(URL, {
createObjectURL: { createObjectURL: {
configurable: true, configurable: true,
@@ -1097,6 +1242,25 @@ afterEach(() => {
} else { } else {
Reflect.deleteProperty(window, 'PointerEvent'); Reflect.deleteProperty(window, 'PointerEvent');
} }
if (originalClipboardEvent) {
Object.defineProperty(window, 'ClipboardEvent', originalClipboardEvent);
} else {
Reflect.deleteProperty(window, 'ClipboardEvent');
}
if (originalDragEvent) {
Object.defineProperty(window, 'DragEvent', originalDragEvent);
} else {
Reflect.deleteProperty(window, 'DragEvent');
}
if (originalRangeGetBoundingClientRect) {
Object.defineProperty(
Range.prototype,
'getBoundingClientRect',
originalRangeGetBoundingClientRect,
);
} else {
Reflect.deleteProperty(Range.prototype, 'getBoundingClientRect');
}
if (originalIntersectionObserver) { if (originalIntersectionObserver) {
Object.defineProperty( Object.defineProperty(
window, window,
@@ -1118,6 +1282,9 @@ export {
App, App,
AuthenticatedClient, AuthenticatedClient,
cleanup, cleanup,
composerDisabled,
composerText,
composerValue,
createGameCreationAppManifest, createGameCreationAppManifest,
createGameCreationAppSeedTasks, createGameCreationAppSeedTasks,
createPlanGddStateView, createPlanGddStateView,
@@ -1149,6 +1316,7 @@ export {
roleAgentMockReply, roleAgentMockReply,
screen, screen,
selectDeveloperAgentChatMode, selectDeveloperAgentChatMode,
setComposerText,
submitChat, submitChat,
TEST_LOCAL_PROJECT_PATH, TEST_LOCAL_PROJECT_PATH,
testAuthUser, testAuthUser,
@@ -24,6 +24,7 @@ import {
renderLauncherProjectsAt, renderLauncherProjectsAt,
screen, screen,
selectDeveloperAgentChatMode, selectDeveloperAgentChatMode,
setComposerText,
testAuthUser, testAuthUser,
vi, vi,
waitFor, waitFor,
@@ -1657,7 +1658,7 @@ export function registerHomeProjectCreationTests() {
expect(await screen.findByText('附件已经进入当前项目。')).not.toBeNull(); expect(await screen.findByText('附件已经进入当前项目。')).not.toBeNull();
const followUpInput = screen.getByLabelText('陶泥儿对话内容'); const followUpInput = screen.getByLabelText('陶泥儿对话内容');
fireEvent.change(followUpInput, { target: { value: '再补一句玩法' } }); await setComposerText(followUpInput, '再补一句玩法');
fireEvent.submit(followUpInput.closest('form') as HTMLFormElement); fireEvent.submit(followUpInput.closest('form') as HTMLFormElement);
await waitFor(() => { await waitFor(() => {
expect( expect(
@@ -1,4 +1,5 @@
import { import {
composerText,
createGameCreationAppManifest, createGameCreationAppManifest,
emptyProjectPolicy, emptyProjectPolicy,
expect, expect,
@@ -64,7 +65,7 @@ export function registerProjectAssetTests() {
window.__TAURI__ = { core: { invoke } }; window.__TAURI__ = { core: { invoke } };
renderAppAt('/'); renderAppAt('/');
submitChat('/project /tmp/authorized-game'); await submitChat('/project /tmp/authorized-game');
fireEvent.click(screen.getByRole('button', { name: '确认' })); fireEvent.click(screen.getByRole('button', { name: '确认' }));
expect( expect(
await screen.findByText('已设置本地项目:/tmp/authorized-game'), await screen.findByText('已设置本地项目:/tmp/authorized-game'),
@@ -92,15 +93,12 @@ export function registerProjectAssetTests() {
bytes: [104, 101, 114, 111], bytes: [104, 101, 114, 111],
}); });
submitChat('/assets'); await submitChat('/assets');
expect( expect(
await screen.findByText(/upload · assets\/uploads\/hero\.png · uploaded/), await screen.findByText(/upload · assets\/uploads\/hero\.png · uploaded/),
).not.toBeNull(); ).not.toBeNull();
fireEvent.click(screen.getByRole('button', { name: '读取首个资产' })); fireEvent.click(screen.getByRole('button', { name: '读取首个资产' }));
expect(screen.getByLabelText('创作想法')).toHaveProperty( expect(await composerText()).toBe('/read assets/uploads/hero.png');
'value',
'/read assets/uploads/hero.png',
);
expect(invoke).toHaveBeenCalledWith('get_local_game_manifest', { expect(invoke).toHaveBeenCalledWith('get_local_game_manifest', {
projectPath: '/tmp/authorized-game', projectPath: '/tmp/authorized-game',
commandId: 'asset.list', commandId: 'asset.list',
@@ -178,14 +176,14 @@ export function registerProjectAssetTests() {
window.__TAURI__ = { core: { invoke } }; window.__TAURI__ = { core: { invoke } };
renderAppAt('/'); renderAppAt('/');
submitChat('/project /tmp/authorized-game'); await submitChat('/project /tmp/authorized-game');
fireEvent.click(screen.getByRole('button', { name: '确认' })); fireEvent.click(screen.getByRole('button', { name: '确认' }));
expect( expect(
await screen.findByText('已设置本地项目:/tmp/authorized-game'), await screen.findByText('已设置本地项目:/tmp/authorized-game'),
).not.toBeNull(); ).not.toBeNull();
invoke.mockClear(); invoke.mockClear();
submitChat('/audio'); await submitChat('/audio');
expect(await screen.findByText(/音频素材:3 个/)).not.toBeNull(); expect(await screen.findByText(/音频素材:3 个/)).not.toBeNull();
expect(screen.getByText(/来源:上传 1、生成 1、画板 1/)).not.toBeNull(); expect(screen.getByText(/来源:上传 1、生成 1、画板 1/)).not.toBeNull();
@@ -208,10 +206,7 @@ export function registerProjectAssetTests() {
fireEvent.click( fireEvent.click(
audioManifestDraftButtons[audioManifestDraftButtons.length - 1], audioManifestDraftButtons[audioManifestDraftButtons.length - 1],
); );
expect(screen.getByLabelText('创作想法')).toHaveProperty( expect(await composerText()).toBe('/read assets/manifest.audio.json');
'value',
'/read assets/manifest.audio.json',
);
expect(invoke).not.toHaveBeenCalledWith( expect(invoke).not.toHaveBeenCalledWith(
'read_local_project_file', 'read_local_project_file',
expect.anything(), expect.anything(),
@@ -298,14 +293,14 @@ export function registerProjectAssetTests() {
window.__TAURI__ = { core: { invoke } }; window.__TAURI__ = { core: { invoke } };
renderAppAt('/'); renderAppAt('/');
submitChat('/project /tmp/authorized-game'); await submitChat('/project /tmp/authorized-game');
fireEvent.click(screen.getByRole('button', { name: '确认' })); fireEvent.click(screen.getByRole('button', { name: '确认' }));
expect( expect(
await screen.findByText('已设置本地项目:/tmp/authorized-game'), await screen.findByText('已设置本地项目:/tmp/authorized-game'),
).not.toBeNull(); ).not.toBeNull();
invoke.mockClear(); invoke.mockClear();
submitChat('/art'); await submitChat('/art');
expect(await screen.findByText(/美术素材:2 个/)).not.toBeNull(); expect(await screen.findByText(/美术素材:2 个/)).not.toBeNull();
expect(screen.getByText(/来源:生成 1、画板 1/)).not.toBeNull(); expect(screen.getByText(/来源:生成 1、画板 1/)).not.toBeNull();
@@ -324,10 +319,7 @@ export function registerProjectAssetTests() {
fireEvent.click( fireEvent.click(
artManifestDraftButtons[artManifestDraftButtons.length - 1], artManifestDraftButtons[artManifestDraftButtons.length - 1],
); );
expect(screen.getByLabelText('创作想法')).toHaveProperty( expect(await composerText()).toBe('/read assets/manifest.art.json');
'value',
'/read assets/manifest.art.json',
);
expect(invoke).not.toHaveBeenCalledWith( expect(invoke).not.toHaveBeenCalledWith(
'read_local_project_file', 'read_local_project_file',
expect.anything(), expect.anything(),
@@ -397,14 +389,14 @@ export function registerProjectAssetTests() {
window.__TAURI__ = { core: { invoke } }; window.__TAURI__ = { core: { invoke } };
renderAppAt('/'); renderAppAt('/');
submitChat('/project /tmp/authorized-game'); await submitChat('/project /tmp/authorized-game');
fireEvent.click(screen.getByRole('button', { name: '确认' })); fireEvent.click(screen.getByRole('button', { name: '确认' }));
expect( expect(
await screen.findByText('已设置本地项目:/tmp/authorized-game'), await screen.findByText('已设置本地项目:/tmp/authorized-game'),
).not.toBeNull(); ).not.toBeNull();
invoke.mockClear(); invoke.mockClear();
submitChat('/asset-register assets/hero.png sprite image/png'); await submitChat('/asset-register assets/hero.png sprite image/png');
expect( expect(
await screen.findByText('asset.register · assets/hero.png'), await screen.findByText('asset.register · assets/hero.png'),
@@ -474,13 +466,13 @@ export function registerProjectAssetTests() {
window.__TAURI__ = { core: { invoke } }; window.__TAURI__ = { core: { invoke } };
renderAppAt('/'); renderAppAt('/');
submitChat('/project /tmp/authorized-game'); await submitChat('/project /tmp/authorized-game');
fireEvent.click(screen.getByRole('button', { name: '确认' })); fireEvent.click(screen.getByRole('button', { name: '确认' }));
expect( expect(
await screen.findByText('已设置本地项目:/tmp/authorized-game'), await screen.findByText('已设置本地项目:/tmp/authorized-game'),
).not.toBeNull(); ).not.toBeNull();
submitChat('/asset-register assets/hero.png'); await submitChat('/asset-register assets/hero.png');
const command = await screen.findByText('asset.register · assets/hero.png'); const command = await screen.findByText('asset.register · assets/hero.png');
fireEvent.click( fireEvent.click(
within(command.closest('.pending-command') as HTMLElement).getByRole( within(command.closest('.pending-command') as HTMLElement).getByRole(
@@ -548,14 +540,14 @@ export function registerProjectAssetTests() {
window.__TAURI__ = { core: { invoke } }; window.__TAURI__ = { core: { invoke } };
renderAppAt('/'); renderAppAt('/');
submitChat('/project /tmp/authorized-game'); await submitChat('/project /tmp/authorized-game');
fireEvent.click(screen.getByRole('button', { name: '确认' })); fireEvent.click(screen.getByRole('button', { name: '确认' }));
expect( expect(
await screen.findByText('已设置本地项目:/tmp/authorized-game'), await screen.findByText('已设置本地项目:/tmp/authorized-game'),
).not.toBeNull(); ).not.toBeNull();
invoke.mockClear(); invoke.mockClear();
submitChat('/asset-register assets/hero.png sprite image/png'); await submitChat('/asset-register assets/hero.png sprite image/png');
expect( expect(
await screen.findByText('准备登记项目资产:assets/hero.png'), await screen.findByText('准备登记项目资产:assets/hero.png'),
@@ -618,13 +610,13 @@ export function registerProjectAssetTests() {
window.__TAURI__ = { core: { invoke } }; window.__TAURI__ = { core: { invoke } };
renderAppAt('/'); renderAppAt('/');
submitChat('/project /tmp/authorized-game'); await submitChat('/project /tmp/authorized-game');
fireEvent.click(screen.getByRole('button', { name: '确认' })); fireEvent.click(screen.getByRole('button', { name: '确认' }));
expect( expect(
await screen.findByText('已设置本地项目:/tmp/authorized-game'), await screen.findByText('已设置本地项目:/tmp/authorized-game'),
).not.toBeNull(); ).not.toBeNull();
submitChat('/asset-register ../hero.png'); await submitChat('/asset-register ../hero.png');
expect( expect(
await screen.findByText('资产路径必须是项目内相对路径。'), await screen.findByText('资产路径必须是项目内相对路径。'),
@@ -805,13 +797,13 @@ export function registerProjectAssetTests() {
window.__TAURI__ = { core: { invoke } }; window.__TAURI__ = { core: { invoke } };
renderAppAt('/'); renderAppAt('/');
submitChat('/project /tmp/authorized-game'); await submitChat('/project /tmp/authorized-game');
fireEvent.click(screen.getByRole('button', { name: '确认' })); fireEvent.click(screen.getByRole('button', { name: '确认' }));
expect( expect(
await screen.findByText('已设置本地项目:/tmp/authorized-game'), await screen.findByText('已设置本地项目:/tmp/authorized-game'),
).not.toBeNull(); ).not.toBeNull();
submitChat('/assets'); await submitChat('/assets');
expect(await screen.findByText(/本地项目资产:/)).not.toBeNull(); expect(await screen.findByText(/本地项目资产:/)).not.toBeNull();
expect(screen.getByText(/asset-20\.png/)).not.toBeNull(); expect(screen.getByText(/asset-20\.png/)).not.toBeNull();
@@ -855,13 +847,13 @@ export function registerProjectAssetTests() {
window.__TAURI__ = { core: { invoke } }; window.__TAURI__ = { core: { invoke } };
renderAppAt('/'); renderAppAt('/');
submitChat('/project /tmp/authorized-game'); await submitChat('/project /tmp/authorized-game');
fireEvent.click(screen.getByRole('button', { name: '确认' })); fireEvent.click(screen.getByRole('button', { name: '确认' }));
expect( expect(
await screen.findByText('已设置本地项目:/tmp/authorized-game'), await screen.findByText('已设置本地项目:/tmp/authorized-game'),
).not.toBeNull(); ).not.toBeNull();
submitChat('/assets'); await submitChat('/assets');
const assetCommand = await screen.findByText('asset.list'); const assetCommand = await screen.findByText('asset.list');
fireEvent.click( fireEvent.click(
@@ -914,13 +906,13 @@ export function registerProjectAssetTests() {
window.__TAURI__ = { core: { invoke } }; window.__TAURI__ = { core: { invoke } };
renderAppAt('/'); renderAppAt('/');
submitChat('/project /tmp/authorized-game'); await submitChat('/project /tmp/authorized-game');
fireEvent.click(screen.getByRole('button', { name: '确认' })); fireEvent.click(screen.getByRole('button', { name: '确认' }));
expect( expect(
await screen.findByText('已设置本地项目:/tmp/authorized-game'), await screen.findByText('已设置本地项目:/tmp/authorized-game'),
).not.toBeNull(); ).not.toBeNull();
submitChat('/read game/index.html'); await submitChat('/read game/index.html');
expect(await screen.findByText(/文件:game\/index\.html/)).not.toBeNull(); expect(await screen.findByText(/文件:game\/index\.html/)).not.toBeNull();
const fileContent = screen.getByText('<canvas id="game"></canvas>'); const fileContent = screen.getByText('<canvas id="game"></canvas>');
@@ -975,14 +967,14 @@ export function registerProjectAssetTests() {
window.__TAURI__ = { core: { invoke } }; window.__TAURI__ = { core: { invoke } };
renderAppAt('/'); renderAppAt('/');
submitChat('/project /tmp/authorized-game'); await submitChat('/project /tmp/authorized-game');
fireEvent.click(screen.getByRole('button', { name: '确认' })); fireEvent.click(screen.getByRole('button', { name: '确认' }));
expect( expect(
await screen.findByText('已设置本地项目:/tmp/authorized-game'), await screen.findByText('已设置本地项目:/tmp/authorized-game'),
).not.toBeNull(); ).not.toBeNull();
invoke.mockClear(); invoke.mockClear();
submitChat('/read game/index.html'); await submitChat('/read game/index.html');
expect(await screen.findByText('准备读取项目文件。')).not.toBeNull(); expect(await screen.findByText('准备读取项目文件。')).not.toBeNull();
expect(screen.getByText('file.read')).not.toBeNull(); expect(screen.getByText('file.read')).not.toBeNull();
@@ -1041,16 +1033,16 @@ export function registerProjectAssetTests() {
window.__TAURI__ = { core: { invoke } }; window.__TAURI__ = { core: { invoke } };
renderAppAt('/'); renderAppAt('/');
submitChat('/project /tmp/authorized-game'); await submitChat('/project /tmp/authorized-game');
fireEvent.click(screen.getByRole('button', { name: '确认' })); fireEvent.click(screen.getByRole('button', { name: '确认' }));
expect( expect(
await screen.findByText('已设置本地项目:/tmp/authorized-game'), await screen.findByText('已设置本地项目:/tmp/authorized-game'),
).not.toBeNull(); ).not.toBeNull();
submitChat('/read ../secret.txt'); await submitChat('/read ../secret.txt');
expect(screen.getByText('文件路径必须是项目内相对路径。')).not.toBeNull(); expect(screen.getByText('文件路径必须是项目内相对路径。')).not.toBeNull();
submitChat('/read /tmp/secret.txt'); await submitChat('/read /tmp/secret.txt');
expect(screen.getAllByText('文件路径必须是项目内相对路径。').length).toBe( expect(screen.getAllByText('文件路径必须是项目内相对路径。').length).toBe(
2, 2,
); );
@@ -1104,13 +1096,13 @@ export function registerProjectAssetTests() {
window.__TAURI__ = { core: { invoke } }; window.__TAURI__ = { core: { invoke } };
renderAppAt('/'); renderAppAt('/');
submitChat('/project /tmp/authorized-game'); await submitChat('/project /tmp/authorized-game');
fireEvent.click(screen.getByRole('button', { name: '确认' })); fireEvent.click(screen.getByRole('button', { name: '确认' }));
expect( expect(
await screen.findByText('已设置本地项目:/tmp/authorized-game'), await screen.findByText('已设置本地项目:/tmp/authorized-game'),
).not.toBeNull(); ).not.toBeNull();
submitChat('/tasks'); await submitChat('/tasks');
expect(await screen.findByText(/任务拆分:/)).not.toBeNull(); expect(await screen.findByText(/任务拆分:/)).not.toBeNull();
expect( expect(
@@ -1193,11 +1185,11 @@ export function registerCanvasAssetTests() {
window.__TAURI__ = { core: { invoke } }; window.__TAURI__ = { core: { invoke } };
renderAppAt('/'); renderAppAt('/');
submitChat('/project /tmp/authorized-game'); await submitChat('/project /tmp/authorized-game');
fireEvent.click(screen.getByRole('button', { name: '确认' })); fireEvent.click(screen.getByRole('button', { name: '确认' }));
expect(await screen.findByText('已打开:authorized-game')).not.toBeNull(); expect(await screen.findByText('已打开:authorized-game')).not.toBeNull();
submitChat( await submitChat(
'/import-canvas-asset assets/hero.png canvas-project-1 object:asset-object-1 character image/png', '/import-canvas-asset assets/hero.png canvas-project-1 object:asset-object-1 character image/png',
); );
expect( expect(
@@ -1647,8 +1639,7 @@ export function registerCanvasAssetTests() {
expect( expect(
await screen.findByText('已选择画板导出包:/tmp/canvas-export.zip'), await screen.findByText('已选择画板导出包:/tmp/canvas-export.zip'),
).not.toBeNull(); ).not.toBeNull();
expect(screen.getByLabelText('创作想法')).toHaveProperty( expect(await composerText()).toBe(
'value',
'/import-canvas-export /tmp/canvas-export.zip ', '/import-canvas-export /tmp/canvas-export.zip ',
); );
expect(invoke).toHaveBeenCalledWith('pick_local_file'); expect(invoke).toHaveBeenCalledWith('pick_local_file');
@@ -1716,24 +1707,26 @@ export function registerCanvasAssetTests() {
window.__TAURI__ = { core: { invoke } }; window.__TAURI__ = { core: { invoke } };
renderAppAt('/'); renderAppAt('/');
submitChat('/project /tmp/authorized-game'); await submitChat('/project /tmp/authorized-game');
fireEvent.click(screen.getByRole('button', { name: '确认' })); fireEvent.click(screen.getByRole('button', { name: '确认' }));
expect(await screen.findByText('已打开:authorized-game')).not.toBeNull(); expect(await screen.findByText('已打开:authorized-game')).not.toBeNull();
expect(await screen.findByText(/还没有最近一次 Agent run/)).not.toBeNull(); expect(await screen.findByText(/还没有最近一次 Agent run/)).not.toBeNull();
submitChat( await submitChat(
'/import-canvas-asset /tmp/hero.png canvas-project-1 resource-1', '/import-canvas-asset /tmp/hero.png canvas-project-1 resource-1',
); );
expect( expect(
screen.getByText('画板资产路径必须是项目内相对路径。'), screen.getByText('画板资产路径必须是项目内相对路径。'),
).not.toBeNull(); ).not.toBeNull();
submitChat('/import-canvas-asset ../hero.png canvas-project-1 resource-1'); await submitChat(
'/import-canvas-asset ../hero.png canvas-project-1 resource-1',
);
expect( expect(
screen.getAllByText('画板资产路径必须是项目内相对路径。').length, screen.getAllByText('画板资产路径必须是项目内相对路径。').length,
).toBe(2); ).toBe(2);
submitChat('/import-canvas-export relative.zip canvas-project-1'); await submitChat('/import-canvas-export relative.zip canvas-project-1');
expect( expect(
screen.getByText('画板导出 ZIP 路径必须是绝对路径。'), screen.getByText('画板导出 ZIP 路径必须是绝对路径。'),
).not.toBeNull(); ).not.toBeNull();
@@ -1818,16 +1811,18 @@ export function registerCanvasAssetTests() {
window.__TAURI__ = { core: { invoke } }; window.__TAURI__ = { core: { invoke } };
renderAppAt('/'); renderAppAt('/');
submitChat('/project /tmp/authorized-game'); await submitChat('/project /tmp/authorized-game');
fireEvent.click(screen.getByRole('button', { name: '确认' })); fireEvent.click(screen.getByRole('button', { name: '确认' }));
expect(await screen.findByText('已打开:authorized-game')).not.toBeNull(); expect(await screen.findByText('已打开:authorized-game')).not.toBeNull();
submitChat('/canvas bad\u0007canvas'); await submitChat('/canvas bad\u0007canvas');
submitChat('/sync-canvas-project bad\u0007canvas'); await submitChat('/sync-canvas-project bad\u0007canvas');
submitChat( await submitChat(
'/import-canvas-asset assets/hero.png bad\u0007canvas resource-1', '/import-canvas-asset assets/hero.png bad\u0007canvas resource-1',
); );
submitChat('/import-canvas-export /tmp/canvas-export.zip bad\u0007canvas'); await submitChat(
'/import-canvas-export /tmp/canvas-export.zip bad\u0007canvas',
);
expect(screen.getAllByText('画板项目 ID 不能包含控制字符。').length).toBe( expect(screen.getAllByText('画板项目 ID 不能包含控制字符。').length).toBe(
4, 4,
@@ -1886,7 +1881,7 @@ export function registerCanvasAssetTests() {
window.__TAURI__ = { core: { invoke } }; window.__TAURI__ = { core: { invoke } };
renderAppAt('/'); renderAppAt('/');
submitChat('/canvas canvas-project-1'); await submitChat('/canvas canvas-project-1');
expect( expect(
await screen.findByText('准备打开画板项目:canvas-project-1'), await screen.findByText('准备打开画板项目:canvas-project-1'),
@@ -1918,10 +1913,7 @@ export function registerCanvasAssetTests() {
), ),
).not.toBeNull(); ).not.toBeNull();
fireEvent.click(screen.getByRole('button', { name: '同步此画板' })); fireEvent.click(screen.getByRole('button', { name: '同步此画板' }));
expect(screen.getByLabelText('创作想法')).toHaveProperty( expect(await composerText()).toBe('/sync-canvas-project canvas-project-1');
'value',
'/sync-canvas-project canvas-project-1',
);
}); });
it('syncs canvas project assets from chat after project confirmation', async () => { it('syncs canvas project assets from chat after project confirmation', async () => {
@@ -1998,11 +1990,11 @@ export function registerCanvasAssetTests() {
window.__TAURI__ = { core: { invoke } }; window.__TAURI__ = { core: { invoke } };
renderAppAt('/'); renderAppAt('/');
submitChat('/project /tmp/authorized-game'); await submitChat('/project /tmp/authorized-game');
fireEvent.click(screen.getByRole('button', { name: '确认' })); fireEvent.click(screen.getByRole('button', { name: '确认' }));
expect(await screen.findByText('已打开:authorized-game')).not.toBeNull(); expect(await screen.findByText('已打开:authorized-game')).not.toBeNull();
submitChat('/sync-canvas-project canvas-project-1'); await submitChat('/sync-canvas-project canvas-project-1');
fireEvent.click(screen.getByRole('button', { name: '确认' })); fireEvent.click(screen.getByRole('button', { name: '确认' }));
expect( expect(
@@ -2083,11 +2075,11 @@ export function registerCanvasAssetTests() {
window.__TAURI__ = { core: { invoke } }; window.__TAURI__ = { core: { invoke } };
renderAppAt('/'); renderAppAt('/');
submitChat('/project /tmp/authorized-game'); await submitChat('/project /tmp/authorized-game');
fireEvent.click(screen.getByRole('button', { name: '确认' })); fireEvent.click(screen.getByRole('button', { name: '确认' }));
expect(await screen.findByText('已打开:authorized-game')).not.toBeNull(); expect(await screen.findByText('已打开:authorized-game')).not.toBeNull();
submitChat('/generate-art 月光厨房弹幕主角'); await submitChat('/generate-art 月光厨房弹幕主角');
expect(await screen.findByText('准备生成首版美术素材。')).not.toBeNull(); expect(await screen.findByText('准备生成首版美术素材。')).not.toBeNull();
expect(screen.getByText('canvas.asset_generate')).not.toBeNull(); expect(screen.getByText('canvas.asset_generate')).not.toBeNull();
expect( expect(
@@ -2151,14 +2143,14 @@ export function registerCanvasAssetTests() {
window.__TAURI__ = { core: { invoke } }; window.__TAURI__ = { core: { invoke } };
renderAppAt('/'); renderAppAt('/');
submitChat('/project /tmp/authorized-game'); await submitChat('/project /tmp/authorized-game');
fireEvent.click(screen.getByRole('button', { name: '确认' })); fireEvent.click(screen.getByRole('button', { name: '确认' }));
expect( expect(
await screen.findByText('已设置本地项目:/tmp/authorized-game'), await screen.findByText('已设置本地项目:/tmp/authorized-game'),
).not.toBeNull(); ).not.toBeNull();
invoke.mockClear(); invoke.mockClear();
submitChat('/generate-art 月光厨房弹幕主角'); await submitChat('/generate-art 月光厨房弹幕主角');
fireEvent.click(screen.getByRole('button', { name: '确认' })); fireEvent.click(screen.getByRole('button', { name: '确认' }));
expect(await screen.findByText('准备生成首版美术素材。')).not.toBeNull(); expect(await screen.findByText('准备生成首版美术素材。')).not.toBeNull();
@@ -2219,13 +2211,15 @@ export function registerCanvasAssetTests() {
window.__TAURI__ = { core: { invoke } }; window.__TAURI__ = { core: { invoke } };
renderAppAt('/'); renderAppAt('/');
submitChat('/project /tmp/authorized-game'); await submitChat('/project /tmp/authorized-game');
fireEvent.click(screen.getByRole('button', { name: '确认' })); fireEvent.click(screen.getByRole('button', { name: '确认' }));
expect( expect(
await screen.findByText('已设置本地项目:/tmp/authorized-game'), await screen.findByText('已设置本地项目:/tmp/authorized-game'),
).not.toBeNull(); ).not.toBeNull();
submitChat('/import-canvas-export /tmp/canvas-export.zip canvas-project-1'); await submitChat(
'/import-canvas-export /tmp/canvas-export.zip canvas-project-1',
);
expect( expect(
await screen.findByText( await screen.findByText(
/导入 \/tmp\/canvas-export\.zip 到 \/tmp\/authorized-game\/assets\/canvas-imports\/ · 画板 canvas-project-1/, /导入 \/tmp\/canvas-export\.zip 到 \/tmp\/authorized-game\/assets\/canvas-imports\/ · 画板 canvas-project-1/,
File diff suppressed because it is too large Load Diff
@@ -2,6 +2,7 @@ import { isDirectCodexTurnAlreadyRunningError } from '../../src/App';
import { import {
act, act,
agentRuntimeUserInputRequest, agentRuntimeUserInputRequest,
composerText,
createGameCreationAppManifest, createGameCreationAppManifest,
createGameCreationAppSeedTasks, createGameCreationAppSeedTasks,
createProjectSupervisorRuntimeHarness, createProjectSupervisorRuntimeHarness,
@@ -290,7 +291,7 @@ export function registerProjectConversationTests() {
window.__TAURI__ = { core: { invoke } }; window.__TAURI__ = { core: { invoke } };
renderAppAt('/'); renderAppAt('/');
submitChat('/project /tmp/authorized-game'); await submitChat('/project /tmp/authorized-game');
fireEvent.click(screen.getByRole('button', { name: '确认' })); fireEvent.click(screen.getByRole('button', { name: '确认' }));
expect(await screen.findByText('历史需求:保留弹幕厨房')).not.toBeNull(); expect(await screen.findByText('历史需求:保留弹幕厨房')).not.toBeNull();
@@ -368,19 +369,19 @@ export function registerProjectConversationTests() {
window.__TAURI__ = { core: { invoke } }; window.__TAURI__ = { core: { invoke } };
renderAppAt('/'); renderAppAt('/');
submitChat('/project /tmp/authorized-game'); await submitChat('/project /tmp/authorized-game');
fireEvent.click(screen.getByRole('button', { name: '确认' })); fireEvent.click(screen.getByRole('button', { name: '确认' }));
expect(await screen.findByText('重载历史需求')).not.toBeNull(); expect(await screen.findByText('重载历史需求')).not.toBeNull();
submitChat('临时未保存的输入'); await submitChat('临时未保存的输入');
expect(await screen.findByText('临时未保存的输入')).not.toBeNull(); expect(await screen.findByText('临时未保存的输入')).not.toBeNull();
submitChat('/history'); await submitChat('/history');
expect(await screen.findByText('重载历史回复')).not.toBeNull(); expect(await screen.findByText('重载历史回复')).not.toBeNull();
expect(screen.queryByText('临时未保存的输入')).toBeNull(); expect(screen.queryByText('临时未保存的输入')).toBeNull();
expect(screen.getByText('已读取项目对话历史:2 条')).not.toBeNull(); expect(screen.getByText('已读取项目对话历史:2 条')).not.toBeNull();
submitChat('另一条临时未保存的输入'); await submitChat('另一条临时未保存的输入');
expect(await screen.findByText('另一条临时未保存的输入')).not.toBeNull(); expect(await screen.findByText('另一条临时未保存的输入')).not.toBeNull();
fireEvent.click(screen.getByRole('button', { name: '历史' })); fireEvent.click(screen.getByRole('button', { name: '历史' }));
@@ -433,7 +434,7 @@ export function registerProjectConversationTests() {
window.__TAURI__ = { core: { invoke } }; window.__TAURI__ = { core: { invoke } };
renderAppAt('/'); renderAppAt('/');
submitChat('/project /tmp/authorized-game'); await submitChat('/project /tmp/authorized-game');
fireEvent.click(screen.getByRole('button', { name: '确认' })); fireEvent.click(screen.getByRole('button', { name: '确认' }));
expect(await screen.findByText('已打开:authorized-game')).not.toBeNull(); expect(await screen.findByText('已打开:authorized-game')).not.toBeNull();
@@ -777,7 +778,7 @@ export function registerProjectConversationTests() {
renderAppAt('/?main&projectPath=%2Ftmp%2Fauthorized-game'); renderAppAt('/?main&projectPath=%2Ftmp%2Fauthorized-game');
await screen.findByText('想做什么游戏?'); await screen.findByText('想做什么游戏?');
submitChat('/第一条本地命令'); await submitChat('/第一条本地命令');
await waitFor(() => { await waitFor(() => {
expect(appendAttempts).toBe(1); expect(appendAttempts).toBe(1);
}); });
@@ -787,7 +788,7 @@ export function registerProjectConversationTests() {
), ),
).not.toBeNull(); ).not.toBeNull();
submitChat('/第二条本地命令'); await submitChat('/第二条本地命令');
await waitFor(() => { await waitFor(() => {
expect(releaseRetryAppend).not.toBeNull(); expect(releaseRetryAppend).not.toBeNull();
}); });
@@ -873,7 +874,7 @@ export function registerProjectConversationTests() {
await screen.findByText('想做什么游戏?'); await screen.findByText('想做什么游戏?');
invoke.mockClear(); invoke.mockClear();
submitChat('/需要确认保存'); await submitChat('/需要确认保存');
const conversationWriteCommand = const conversationWriteCommand =
await screen.findByText('conversation.write'); await screen.findByText('conversation.write');
@@ -961,7 +962,7 @@ export function registerProjectConversationTests() {
await screen.findByText('想做什么游戏?'); await screen.findByText('想做什么游戏?');
invoke.mockClear(); invoke.mockClear();
submitChat('/先不保存'); await submitChat('/先不保存');
const firstPrompt = await screen.findByText('conversation.write'); const firstPrompt = await screen.findByText('conversation.write');
fireEvent.click( fireEvent.click(
@@ -980,7 +981,7 @@ export function registerProjectConversationTests() {
expect.anything(), expect.anything(),
); );
submitChat('/继续补充'); await submitChat('/继续补充');
const secondPrompt = await screen.findByText('conversation.write'); const secondPrompt = await screen.findByText('conversation.write');
fireEvent.click( fireEvent.click(
@@ -1064,11 +1065,11 @@ export function registerProjectConversationTests() {
renderAppAt('/?main&projectPath=%2Ftmp%2Fauthorized-game'); renderAppAt('/?main&projectPath=%2Ftmp%2Fauthorized-game');
await screen.findByText('想做什么游戏?'); await screen.findByText('想做什么游戏?');
submitChat('/第一条并发命令'); await submitChat('/第一条并发命令');
await waitFor(() => { await waitFor(() => {
expect(releaseFirstAppend).not.toBeNull(); expect(releaseFirstAppend).not.toBeNull();
}); });
submitChat('/第二条并发命令'); await submitChat('/第二条并发命令');
releaseFirstAppend?.(); releaseFirstAppend?.();
@@ -1249,7 +1250,7 @@ export function registerProjectConversationTests() {
window.__TAURI__ = { core: { invoke } }; window.__TAURI__ = { core: { invoke } };
renderAppAt('/'); renderAppAt('/');
submitChat('/project /tmp/authorized-game'); await submitChat('/project /tmp/authorized-game');
fireEvent.click(screen.getByRole('button', { name: '确认' })); fireEvent.click(screen.getByRole('button', { name: '确认' }));
expect(await screen.findByText('已打开:authorized-game')).not.toBeNull(); expect(await screen.findByText('已打开:authorized-game')).not.toBeNull();
@@ -1368,10 +1369,7 @@ export function registerProjectConversationTests() {
expect(agentConversationReadCount).toBeGreaterThanOrEqual(2); expect(agentConversationReadCount).toBeGreaterThanOrEqual(2);
fireEvent.click(screen.getByRole('button', { name: '填入同步命令' })); fireEvent.click(screen.getByRole('button', { name: '填入同步命令' }));
expect(screen.queryByLabelText('Agent 对话')).toBeNull(); expect(screen.queryByLabelText('Agent 对话')).toBeNull();
expect(screen.getByLabelText('创作想法')).toHaveProperty( expect(await composerText()).toBe('/sync-canvas-project ');
'value',
'/sync-canvas-project ',
);
fireEvent.click(screen.getByRole('button', { name: /拆解创作方向/ })); fireEvent.click(screen.getByRole('button', { name: /拆解创作方向/ }));
const reopenedAgentDialog = await screen.findByLabelText('Agent 对话'); const reopenedAgentDialog = await screen.findByLabelText('Agent 对话');
fireEvent.click( fireEvent.click(
@@ -1380,10 +1378,7 @@ export function registerProjectConversationTests() {
}), }),
); );
expect(screen.queryByLabelText('Agent 对话')).toBeNull(); expect(screen.queryByLabelText('Agent 对话')).toBeNull();
expect(screen.getByLabelText('创作想法')).toHaveProperty( expect(await composerText()).toBe('/read .agent/spec.md');
'value',
'/read .agent/spec.md',
);
expect(invoke).not.toHaveBeenCalledWith( expect(invoke).not.toHaveBeenCalledWith(
'sync_canvas_project_assets', 'sync_canvas_project_assets',
expect.anything(), expect.anything(),
@@ -17,6 +17,9 @@ import {
agentRuntimeUserInputRequest, agentRuntimeUserInputRequest,
App, App,
cleanup, cleanup,
composerDisabled,
composerText,
composerValue,
createGameCreationAppManifest, createGameCreationAppManifest,
createGameCreationAppSeedTasks, createGameCreationAppSeedTasks,
createProjectSupervisorRuntimeHarness, createProjectSupervisorRuntimeHarness,
@@ -40,6 +43,7 @@ import {
renderLauncherProjectsAt, renderLauncherProjectsAt,
resolve, resolve,
screen, screen,
setComposerText,
submitChat, submitChat,
vi, vi,
waitFor, waitFor,
@@ -4854,10 +4858,9 @@ export function registerUserSurfaceBoundaryTests() {
).disabled, ).disabled,
).toBe(true); ).toBe(true);
fireEvent.click(screen.getByRole('button', { name: '打开画板' })); fireEvent.click(screen.getByRole('button', { name: '打开画板' }));
expect(composerInput).toHaveProperty('value', '/canvas '); expect(await composerValue(composerInput)).toBe('/canvas ');
fireEvent.click(screen.getByRole('button', { name: '灵感草稿' })); fireEvent.click(screen.getByRole('button', { name: '灵感草稿' }));
expect(composerInput).toHaveProperty( expect(await composerValue(composerInput)).toBe(
'value',
'像素风厨房弹幕小游戏:玩家用方向键躲避飞来的食材,收集调料加分,60 秒内尽量高分,失败后可一键重开。', '像素风厨房弹幕小游戏:玩家用方向键躲避飞来的食材,收集调料加分,60 秒内尽量高分,失败后可一键重开。',
); );
expect(screen.queryByText('game.generate_draft')).toBeNull(); expect(screen.queryByText('game.generate_draft')).toBeNull();
@@ -5080,8 +5083,8 @@ export function registerProjectSupervisorSurfaceTests() {
within(supervisorSurface).queryByLabelText('项目总控 Agent 状态'), within(supervisorSurface).queryByLabelText('项目总控 Agent 状态'),
).toBeNull(); ).toBeNull();
expect( expect(
within(supervisorSurface).getByPlaceholderText( within(supervisorSurface).getByText(
'告诉陶泥儿接下来要做什么', '告诉陶泥儿接下来要做什么,或输入 @ 选择资源',
), ),
).not.toBeNull(); ).not.toBeNull();
expect( expect(
@@ -5332,9 +5335,7 @@ export function registerProjectSupervisorSurfaceTests() {
expect.anything(), expect.anything(),
); );
const input = within(supervisorSurface).getByLabelText('陶泥儿对话内容'); const input = within(supervisorSurface).getByLabelText('陶泥儿对话内容');
fireEvent.change(input, { await setComposerText(input, '从旧任务状态继续生成,但使用 direct Codex');
target: { value: '从旧任务状态继续生成,但使用 direct Codex' },
});
fireEvent.submit(input.closest('form') as HTMLFormElement); fireEvent.submit(input.closest('form') as HTMLFormElement);
expect( expect(
@@ -5408,9 +5409,10 @@ export function registerProjectSupervisorSurfaceTests() {
expect(screen.queryByLabelText('项目总控 Agent 状态')).toBeNull(); expect(screen.queryByLabelText('项目总控 Agent 状态')).toBeNull();
expect(screen.queryByLabelText('专业 Agent 协作状态')).toBeNull(); expect(screen.queryByLabelText('专业 Agent 协作状态')).toBeNull();
fireEvent.change(within(surface).getByLabelText('项目总控对话内容'), { await setComposerText(
target: { value: '继续完成可玩原型' }, within(surface).getByLabelText('项目总控对话内容'),
}); '继续完成可玩原型',
);
fireEvent.click(within(surface).getByRole('button', { name: '发送' })); fireEvent.click(within(surface).getByRole('button', { name: '发送' }));
await waitFor(() => { await waitFor(() => {
@@ -5455,15 +5457,16 @@ export function registerProjectSupervisorSurfaceTests() {
renderSupervisorChat(); renderSupervisorChat();
const firstSurface = await screen.findByLabelText('项目总控 Agent 纯聊天'); const firstSurface = await screen.findByLabelText('项目总控 Agent 纯聊天');
await within(firstSurface).findByLabelText('项目总控消息'); await within(firstSurface).findByLabelText('项目总控消息');
fireEvent.change(within(firstSurface).getByLabelText('项目总控对话内容'), { await setComposerText(
target: { value: '这条草稿还没有发送' }, within(firstSurface).getByLabelText('项目总控对话内容'),
}); '这条草稿还没有发送',
);
cleanup(); cleanup();
renderSupervisorChat(); renderSupervisorChat();
const restoredInput = await screen.findByLabelText('项目总控对话内容'); const restoredInput = await screen.findByLabelText('项目总控对话内容');
expect(restoredInput).toHaveProperty('value', '这条草稿还没有发送'); expect(await composerValue(restoredInput)).toBe('这条草稿还没有发送');
}); });
it('shows and handles runtime recovery confirmation in the standalone Project Supervisor chat', async () => { it('shows and handles runtime recovery confirmation in the standalone Project Supervisor chat', async () => {
@@ -5658,13 +5661,9 @@ export function registerProjectSupervisorSurfaceTests() {
within(card).getByText('首版角色规范图采用哪种美术方向?'), within(card).getByText('首版角色规范图采用哪种美术方向?'),
).not.toBeNull(); ).not.toBeNull();
expect(within(card).getByText('优先验证轮廓与动作可读性。')).not.toBeNull(); expect(within(card).getByText('优先验证轮廓与动作可读性。')).not.toBeNull();
expect( expect(await composerDisabled('项目总控对话内容', within(surface))).toBe(
( true,
within(surface).getByLabelText( );
'项目总控对话内容',
) as HTMLTextAreaElement
).disabled,
).toBe(true);
fireEvent.click(within(card).getByRole('button', { name: /像素风/ })); fireEvent.click(within(card).getByRole('button', { name: /像素风/ }));
fireEvent.click(within(card).getByRole('button', { name: '提交回答' })); fireEvent.click(within(card).getByRole('button', { name: '提交回答' }));
@@ -5794,9 +5793,10 @@ export function registerProjectSupervisorSurfaceTests() {
}, },
); );
fireEvent.change(screen.getByLabelText('陶泥儿对话内容'), { await setComposerText(
target: { value: '先完成正式客户端玩法拆解' }, screen.getByLabelText('陶泥儿对话内容'),
}); '先完成正式客户端玩法拆解',
);
fireEvent.click( fireEvent.click(
within(supervisorSurface).getByRole('button', { name: '发送' }), within(supervisorSurface).getByRole('button', { name: '发送' }),
); );
@@ -6068,9 +6068,10 @@ export function registerProjectSupervisorSurfaceTests() {
).toBe(false); ).toBe(false);
}); });
fireEvent.change(screen.getByLabelText('陶泥儿对话内容'), { await setComposerText(
target: { value: '补充:优先复用现有素材' }, screen.getByLabelText('陶泥儿对话内容'),
}); '补充:优先复用现有素材',
);
fireEvent.click( fireEvent.click(
within(supervisorSurface).getByRole('button', { name: '发送' }), within(supervisorSurface).getByRole('button', { name: '发送' }),
); );
@@ -6416,19 +6417,28 @@ export function registerProjectSupervisorSurfaceTests() {
).toBe(false); ).toBe(false);
const composer = within(surface).getByLabelText('陶泥儿对话内容'); const composer = within(surface).getByLabelText('陶泥儿对话内容');
fireEvent.change(composer, { target: { value: '修改玩家移动脚本' } }); await setComposerText(composer, '修改玩家移动脚本');
// 输入区已从原生 textarea 换成 Lexical 富文本:Shift+Enter 由编辑器自己插入换行
// (按键被编辑器消化,因此 fireEvent 返回 false),语义仍是「换行而不是发送」。
expect(fireEvent.keyDown(composer, { key: 'Enter', shiftKey: true })).toBe( expect(fireEvent.keyDown(composer, { key: 'Enter', shiftKey: true })).toBe(
true, false,
); );
// composerValue 会先等编辑器提交这一帧,再校验草稿保留且确实插入了换行。
expect(await composerValue(composer)).toBe('修改玩家移动脚本');
expect(
composer.querySelector('br:not([data-lexical-managed-linebreak])'),
).not.toBeNull();
expect( expect(
invoke.mock.calls.some( invoke.mock.calls.some(
([command]) => command === 'chat_with_game_creator_direct_codex', ([command]) => command === 'chat_with_game_creator_direct_codex',
), ),
).toBe(false); ).toBe(false);
// 组合态 Enter 同样不发送:按键被编辑器消化,草稿原样保留。
expect( expect(
fireEvent.keyDown(composer, { key: 'Enter', isComposing: true }), fireEvent.keyDown(composer, { key: 'Enter', isComposing: true }),
).toBe(true); ).toBe(false);
expect(await composerValue(composer)).toBe('修改玩家移动脚本');
expect( expect(
invoke.mock.calls.some( invoke.mock.calls.some(
([command]) => command === 'chat_with_game_creator_direct_codex', ([command]) => command === 'chat_with_game_creator_direct_codex',
@@ -6466,7 +6476,7 @@ export function registerProjectWorkbenchNavigationTests() {
'open_game_creator_launcher_window', 'open_game_creator_launcher_window',
); );
submitChat('/switch-project'); await submitChat('/switch-project');
expect( expect(
screen.getAllByText('请回到首页的项目组切换项目。').length, screen.getAllByText('请回到首页的项目组切换项目。').length,
@@ -6541,7 +6551,7 @@ export function registerProjectWorkbenchNavigationTests() {
}); });
expect(await screen.findByText('已打开项目目录。')).not.toBeNull(); expect(await screen.findByText('已打开项目目录。')).not.toBeNull();
submitChat('/open-project'); await submitChat('/open-project');
await waitFor(() => { await waitFor(() => {
expect( expect(
@@ -6625,8 +6635,7 @@ export function registerProjectWorkbenchNavigationTests() {
expect(document.activeElement).toBe(screen.getByLabelText('创作想法')), expect(document.activeElement).toBe(screen.getByLabelText('创作想法')),
); );
expect(screen.getByLabelText('创作想法')).toHaveProperty( expect(await composerText()).toBe(
'value',
'/asset-register assets/uploads/hero.png image image/png', '/asset-register assets/uploads/hero.png image image/png',
); );
expect( expect(
@@ -1,4 +1,11 @@
import { expect, fireEvent, screen, submitChat, within } from '../../harness'; import {
composerText,
expect,
fireEvent,
screen,
submitChat,
within,
} from '../../harness';
import type { PreviewShortcutInvoke } from './invoke-mock'; import type { PreviewShortcutInvoke } from './invoke-mock';
import { messageBubble } from './message-bubble'; import { messageBubble } from './message-bubble';
@@ -14,7 +21,7 @@ export async function assertPlanningAndStatusShortcutFlow(
const runLocalCountBeforeRisks = invoke.mock.calls.filter( const runLocalCountBeforeRisks = invoke.mock.calls.filter(
([command]) => command === 'run_limited_local_command', ([command]) => command === 'run_limited_local_command',
).length; ).length;
submitChat('/risks'); await submitChat('/risks');
expect(await screen.findByText(/项目风险:/)).not.toBeNull(); expect(await screen.findByText(/项目风险:/)).not.toBeNull();
expect( expect(
screen.getByText(/最近 run 已通过,但当前本地预览未运行。 建议:\/run/), screen.getByText(/最近 run 已通过,但当前本地预览未运行。 建议:\/run/),
@@ -23,7 +30,7 @@ export async function assertPlanningAndStatusShortcutFlow(
screen.getByText(/还有 3 个 ready 任务等待处理。 建议:\/tasks/), screen.getByText(/还有 3 个 ready 任务等待处理。 建议:\/tasks/),
).not.toBeNull(); ).not.toBeNull();
fireEvent.click(screen.getByRole('button', { name: '处理首个风险' })); fireEvent.click(screen.getByRole('button', { name: '处理首个风险' }));
expect(screen.getByLabelText('创作想法')).toHaveProperty('value', '/run'); expect(await composerText()).toBe('/run');
expect( expect(
invoke.mock.calls.filter( invoke.mock.calls.filter(
([command]) => command === 'get_local_game_manifest', ([command]) => command === 'get_local_game_manifest',
@@ -69,7 +76,7 @@ export async function assertPlanningAndStatusShortcutFlow(
([command]) => command === 'control_agent_run', ([command]) => command === 'control_agent_run',
).length, ).length,
}; };
submitChat('/blockers'); await submitChat('/blockers');
const blockerMessages = await screen.findAllByText(/当前阻塞项:/); const blockerMessages = await screen.findAllByText(/当前阻塞项:/);
const blockerMessage = messageBubble( const blockerMessage = messageBubble(
blockerMessages[blockerMessages.length - 1], blockerMessages[blockerMessages.length - 1],
@@ -97,7 +104,7 @@ export async function assertPlanningAndStatusShortcutFlow(
blockerMessageList as HTMLElement, blockerMessageList as HTMLElement,
).getAllByRole('button', { name: '处理首个阻塞' }); ).getAllByRole('button', { name: '处理首个阻塞' });
fireEvent.click(blockerDraftButtons[blockerDraftButtons.length - 1]); fireEvent.click(blockerDraftButtons[blockerDraftButtons.length - 1]);
expect(screen.getByLabelText('创作想法')).toHaveProperty('value', '/run'); expect(await composerText()).toBe('/run');
expect( expect(
invoke.mock.calls.filter( invoke.mock.calls.filter(
([command]) => command === 'get_local_game_manifest', ([command]) => command === 'get_local_game_manifest',
@@ -171,7 +178,7 @@ export async function assertPlanningAndStatusShortcutFlow(
([command]) => command === 'control_agent_run', ([command]) => command === 'control_agent_run',
).length, ).length,
}; };
submitChat('/ready'); await submitChat('/ready');
const readyMessages = await screen.findAllByText(/试玩就绪度:/); const readyMessages = await screen.findAllByText(/试玩就绪度:/);
const readyMessage = messageBubble(readyMessages[readyMessages.length - 1]); const readyMessage = messageBubble(readyMessages[readyMessages.length - 1]);
expect(readyMessage.textContent).toContain('项目:未命名游戏原型'); expect(readyMessage.textContent).toContain('项目:未命名游戏原型');
@@ -197,7 +204,7 @@ export async function assertPlanningAndStatusShortcutFlow(
readyMessageList as HTMLElement, readyMessageList as HTMLElement,
).getAllByRole('button', { name: '启动试玩' }); ).getAllByRole('button', { name: '启动试玩' });
fireEvent.click(readyDraftButtons[readyDraftButtons.length - 1]); fireEvent.click(readyDraftButtons[readyDraftButtons.length - 1]);
expect(screen.getByLabelText('创作想法')).toHaveProperty('value', '/run'); expect(await composerText()).toBe('/run');
expect( expect(
invoke.mock.calls.filter( invoke.mock.calls.filter(
([command]) => command === 'get_local_game_manifest', ([command]) => command === 'get_local_game_manifest',
@@ -271,7 +278,7 @@ export async function assertPlanningAndStatusShortcutFlow(
([command]) => command === 'control_agent_run', ([command]) => command === 'control_agent_run',
).length, ).length,
}; };
submitChat('/evidence'); await submitChat('/evidence');
const evidenceMessages = await screen.findAllByText(/验证证据台账:/); const evidenceMessages = await screen.findAllByText(/验证证据台账:/);
const evidenceMessage = messageBubble( const evidenceMessage = messageBubble(
evidenceMessages[evidenceMessages.length - 1], evidenceMessages[evidenceMessages.length - 1],
@@ -306,7 +313,7 @@ export async function assertPlanningAndStatusShortcutFlow(
evidenceMessageList as HTMLElement, evidenceMessageList as HTMLElement,
).getAllByRole('button', { name: '补齐首个证据缺口' }); ).getAllByRole('button', { name: '补齐首个证据缺口' });
fireEvent.click(evidenceDraftButtons[evidenceDraftButtons.length - 1]); fireEvent.click(evidenceDraftButtons[evidenceDraftButtons.length - 1]);
expect(screen.getByLabelText('创作想法')).toHaveProperty('value', '/run'); expect(await composerText()).toBe('/run');
expect( expect(
invoke.mock.calls.filter( invoke.mock.calls.filter(
([command]) => command === 'get_local_game_manifest', ([command]) => command === 'get_local_game_manifest',
@@ -380,7 +387,7 @@ export async function assertPlanningAndStatusShortcutFlow(
([command]) => command === 'control_agent_run', ([command]) => command === 'control_agent_run',
).length, ).length,
}; };
submitChat('/deps'); await submitChat('/deps');
const dependencyMessages = await screen.findAllByText(/任务依赖链:/); const dependencyMessages = await screen.findAllByText(/任务依赖链:/);
const dependencyMessage = messageBubble( const dependencyMessage = messageBubble(
dependencyMessages[dependencyMessages.length - 1], dependencyMessages[dependencyMessages.length - 1],
@@ -407,10 +414,7 @@ export async function assertPlanningAndStatusShortcutFlow(
dependencyMessageList as HTMLElement, dependencyMessageList as HTMLElement,
).getAllByRole('button', { name: '查看验收标准' }); ).getAllByRole('button', { name: '查看验收标准' });
fireEvent.click(dependencyDraftButtons[dependencyDraftButtons.length - 1]); fireEvent.click(dependencyDraftButtons[dependencyDraftButtons.length - 1]);
expect(screen.getByLabelText('创作想法')).toHaveProperty( expect(await composerText()).toBe('/criteria');
'value',
'/criteria',
);
expect( expect(
invoke.mock.calls.filter( invoke.mock.calls.filter(
([command]) => command === 'get_local_game_manifest', ([command]) => command === 'get_local_game_manifest',
@@ -484,7 +488,7 @@ export async function assertPlanningAndStatusShortcutFlow(
([command]) => command === 'control_agent_run', ([command]) => command === 'control_agent_run',
).length, ).length,
}; };
submitChat('/revise'); await submitChat('/revise');
const revisionMessages = await screen.findAllByText(/改版草稿:/); const revisionMessages = await screen.findAllByText(/改版草稿:/);
const revisionMessage = messageBubble( const revisionMessage = messageBubble(
revisionMessages[revisionMessages.length - 1], revisionMessages[revisionMessages.length - 1],
@@ -534,8 +538,7 @@ export async function assertPlanningAndStatusShortcutFlow(
revisionMessageList as HTMLElement, revisionMessageList as HTMLElement,
).getAllByRole('button', { name: '填入改版说明' }); ).getAllByRole('button', { name: '填入改版说明' });
fireEvent.click(revisionDraftButtons[revisionDraftButtons.length - 1]); fireEvent.click(revisionDraftButtons[revisionDraftButtons.length - 1]);
expect(screen.getByLabelText('创作想法')).toHaveProperty( expect(await composerText()).toEqual(
'value',
expect.stringContaining('/agent-resume 改版说明:'), expect.stringContaining('/agent-resume 改版说明:'),
); );
expect( expect(
@@ -611,7 +614,7 @@ export async function assertPlanningAndStatusShortcutFlow(
([command]) => command === 'control_agent_run', ([command]) => command === 'control_agent_run',
).length, ).length,
}; };
submitChat('/privacy'); await submitChat('/privacy');
const privacyMessages = await screen.findAllByText(/隐私与导出边界:/); const privacyMessages = await screen.findAllByText(/隐私与导出边界:/);
const privacyMessage = messageBubble( const privacyMessage = messageBubble(
privacyMessages[privacyMessages.length - 1], privacyMessages[privacyMessages.length - 1],
@@ -651,7 +654,7 @@ export async function assertPlanningAndStatusShortcutFlow(
privacyMessageList as HTMLElement, privacyMessageList as HTMLElement,
).getAllByRole('button', { name: '查看素材来源' }); ).getAllByRole('button', { name: '查看素材来源' });
fireEvent.click(privacyDraftButtons[privacyDraftButtons.length - 1]); fireEvent.click(privacyDraftButtons[privacyDraftButtons.length - 1]);
expect(screen.getByLabelText('创作想法')).toHaveProperty('value', '/credits'); expect(await composerText()).toBe('/credits');
expect( expect(
invoke.mock.calls.filter( invoke.mock.calls.filter(
([command]) => command === 'get_local_game_manifest', ([command]) => command === 'get_local_game_manifest',
@@ -705,7 +708,7 @@ export async function assertPlanningAndStatusShortcutFlow(
const runLocalCountBeforeCriteria = invoke.mock.calls.filter( const runLocalCountBeforeCriteria = invoke.mock.calls.filter(
([command]) => command === 'run_limited_local_command', ([command]) => command === 'run_limited_local_command',
).length; ).length;
submitChat('/criteria'); await submitChat('/criteria');
expect(await screen.findByText(/当前验收标准:/)).not.toBeNull(); expect(await screen.findByText(/当前验收标准:/)).not.toBeNull();
const criteriaMessages = screen.getAllByText(/当前验收标准:/); const criteriaMessages = screen.getAllByText(/当前验收标准:/);
const criteriaMessage = messageBubble( const criteriaMessage = messageBubble(
@@ -724,7 +727,7 @@ export async function assertPlanningAndStatusShortcutFlow(
name: '查看任务', name: '查看任务',
}); });
fireEvent.click(criteriaDraftButtons[criteriaDraftButtons.length - 1]); fireEvent.click(criteriaDraftButtons[criteriaDraftButtons.length - 1]);
expect(screen.getByLabelText('创作想法')).toHaveProperty('value', '/tasks'); expect(await composerText()).toBe('/tasks');
expect( expect(
invoke.mock.calls.filter( invoke.mock.calls.filter(
([command]) => command === 'read_local_project_file', ([command]) => command === 'read_local_project_file',
@@ -750,7 +753,7 @@ export async function assertPlanningAndStatusShortcutFlow(
const runLocalCountBeforeGroups = invoke.mock.calls.filter( const runLocalCountBeforeGroups = invoke.mock.calls.filter(
([command]) => command === 'run_limited_local_command', ([command]) => command === 'run_limited_local_command',
).length; ).length;
submitChat('/groups'); await submitChat('/groups');
expect(await screen.findByText(/专业组进度:/)).not.toBeNull(); expect(await screen.findByText(/专业组进度:/)).not.toBeNull();
const groupMessages = screen.getAllByText(/专业组进度:/); const groupMessages = screen.getAllByText(/专业组进度:/);
const groupMessage = messageBubble(groupMessages[groupMessages.length - 1]); const groupMessage = messageBubble(groupMessages[groupMessages.length - 1]);
@@ -763,7 +766,7 @@ export async function assertPlanningAndStatusShortcutFlow(
name: '查看任务', name: '查看任务',
}); });
fireEvent.click(groupDraftButtons[groupDraftButtons.length - 1]); fireEvent.click(groupDraftButtons[groupDraftButtons.length - 1]);
expect(screen.getByLabelText('创作想法')).toHaveProperty('value', '/tasks'); expect(await composerText()).toBe('/tasks');
expect( expect(
invoke.mock.calls.filter( invoke.mock.calls.filter(
([command]) => command === 'read_local_project_file', ([command]) => command === 'read_local_project_file',
@@ -803,7 +806,7 @@ export async function assertPlanningAndStatusShortcutFlow(
([command]) => command === 'export_local_project_package', ([command]) => command === 'export_local_project_package',
).length, ).length,
}; };
submitChat('/balance'); await submitChat('/balance');
expect(await screen.findByText(/数值状态:/)).not.toBeNull(); expect(await screen.findByText(/数值状态:/)).not.toBeNull();
const balanceMessages = screen.getAllByText(/数值状态:/); const balanceMessages = screen.getAllByText(/数值状态:/);
const balanceMessage = messageBubble( const balanceMessage = messageBubble(
@@ -837,8 +840,7 @@ export async function assertPlanningAndStatusShortcutFlow(
balanceMessageList as HTMLElement, balanceMessageList as HTMLElement,
).getAllByRole('button', { name: '填写数值反馈' }); ).getAllByRole('button', { name: '填写数值反馈' });
fireEvent.click(balanceDraftButtons[balanceDraftButtons.length - 1]); fireEvent.click(balanceDraftButtons[balanceDraftButtons.length - 1]);
expect(screen.getByLabelText('创作想法')).toHaveProperty( expect(await composerText()).toBe(
'value',
'/agent-resume 数值调整:前 30 秒更易上手;得分反馈更明显;失败后重开节奏更快', '/agent-resume 数值调整:前 30 秒更易上手;得分反馈更明显;失败后重开节奏更快',
); );
expect( expect(
@@ -886,7 +888,7 @@ export async function assertPlanningAndStatusShortcutFlow(
const runLocalCountBeforeBudget = invoke.mock.calls.filter( const runLocalCountBeforeBudget = invoke.mock.calls.filter(
([command]) => command === 'run_limited_local_command', ([command]) => command === 'run_limited_local_command',
).length; ).length;
submitChat('/budget'); await submitChat('/budget');
expect(await screen.findByText(/运行预算:/)).not.toBeNull(); expect(await screen.findByText(/运行预算:/)).not.toBeNull();
const budgetMessages = screen.getAllByText(/运行预算:/); const budgetMessages = screen.getAllByText(/运行预算:/);
const budgetMessage = messageBubble( const budgetMessage = messageBubble(
@@ -905,7 +907,7 @@ export async function assertPlanningAndStatusShortcutFlow(
name: '查看发布准备', name: '查看发布准备',
}); });
fireEvent.click(budgetDraftButtons[budgetDraftButtons.length - 1]); fireEvent.click(budgetDraftButtons[budgetDraftButtons.length - 1]);
expect(screen.getByLabelText('创作想法')).toHaveProperty('value', '/publish'); expect(await composerText()).toBe('/publish');
expect( expect(
invoke.mock.calls.filter( invoke.mock.calls.filter(
([command]) => command === 'read_local_project_file', ([command]) => command === 'read_local_project_file',
@@ -937,7 +939,7 @@ export async function assertPlanningAndStatusShortcutFlow(
const openPreviewCountBeforeQa = invoke.mock.calls.filter( const openPreviewCountBeforeQa = invoke.mock.calls.filter(
([command]) => command === 'activate_local_game_preview', ([command]) => command === 'activate_local_game_preview',
).length; ).length;
submitChat('/qa'); await submitChat('/qa');
expect(await screen.findByText(/质量检查:/)).not.toBeNull(); expect(await screen.findByText(/质量检查:/)).not.toBeNull();
const qaMessages = screen.getAllByText(/质量检查:/); const qaMessages = screen.getAllByText(/质量检查:/);
const qaMessage = messageBubble(qaMessages[qaMessages.length - 1]); const qaMessage = messageBubble(qaMessages[qaMessages.length - 1]);
@@ -954,10 +956,7 @@ export async function assertPlanningAndStatusShortcutFlow(
name: '查看试玩状态', name: '查看试玩状态',
}); });
fireEvent.click(qaDraftButtons[qaDraftButtons.length - 1]); fireEvent.click(qaDraftButtons[qaDraftButtons.length - 1]);
expect(screen.getByLabelText('创作想法')).toHaveProperty( expect(await composerText()).toBe('/playtest');
'value',
'/playtest',
);
expect( expect(
invoke.mock.calls.filter( invoke.mock.calls.filter(
([command]) => command === 'read_local_project_file', ([command]) => command === 'read_local_project_file',
@@ -1002,7 +1001,7 @@ export async function assertPlanningAndStatusShortcutFlow(
const openPreviewCountBeforeChanges = invoke.mock.calls.filter( const openPreviewCountBeforeChanges = invoke.mock.calls.filter(
([command]) => command === 'activate_local_game_preview', ([command]) => command === 'activate_local_game_preview',
).length; ).length;
submitChat('/changes'); await submitChat('/changes');
expect(await screen.findByText(/最近变更:/)).not.toBeNull(); expect(await screen.findByText(/最近变更:/)).not.toBeNull();
const changeMessages = screen.getAllByText(/最近变更:/); const changeMessages = screen.getAllByText(/最近变更:/);
const changeMessage = messageBubble( const changeMessage = messageBubble(
@@ -1030,10 +1029,7 @@ export async function assertPlanningAndStatusShortcutFlow(
name: '读取首个产物', name: '读取首个产物',
}); });
fireEvent.click(changeDraftButtons[changeDraftButtons.length - 1]); fireEvent.click(changeDraftButtons[changeDraftButtons.length - 1]);
expect(screen.getByLabelText('创作想法')).toHaveProperty( expect(await composerText()).toBe('/read exports/README.md');
'value',
'/read exports/README.md',
);
expect( expect(
invoke.mock.calls.filter( invoke.mock.calls.filter(
([command]) => command === 'read_local_project_file', ([command]) => command === 'read_local_project_file',
@@ -1080,7 +1076,7 @@ export async function assertPlanningAndStatusShortcutFlow(
const openPreviewCountBeforeReview = invoke.mock.calls.filter( const openPreviewCountBeforeReview = invoke.mock.calls.filter(
([command]) => command === 'activate_local_game_preview', ([command]) => command === 'activate_local_game_preview',
).length; ).length;
submitChat('/review'); await submitChat('/review');
expect(await screen.findByText(/评审状态:/)).not.toBeNull(); expect(await screen.findByText(/评审状态:/)).not.toBeNull();
const reviewMessages = screen.getAllByText(/评审状态:/); const reviewMessages = screen.getAllByText(/评审状态:/);
const reviewMessage = messageBubble( const reviewMessage = messageBubble(
@@ -1095,10 +1091,7 @@ export async function assertPlanningAndStatusShortcutFlow(
'Evaluator #1 · passed · 质量评审通过', 'Evaluator #1 · passed · 质量评审通过',
); );
fireEvent.click(screen.getByRole('button', { name: '读取评审记录' })); fireEvent.click(screen.getByRole('button', { name: '读取评审记录' }));
expect(screen.getByLabelText('创作想法')).toHaveProperty( expect(await composerText()).toBe('/read .agent/findings.md');
'value',
'/read .agent/findings.md',
);
expect( expect(
invoke.mock.calls.filter( invoke.mock.calls.filter(
([command]) => command === 'read_local_project_file', ([command]) => command === 'read_local_project_file',
@@ -1128,7 +1121,7 @@ export async function assertPlanningAndStatusShortcutFlow(
const fileReadCountBeforeContext = invoke.mock.calls.filter( const fileReadCountBeforeContext = invoke.mock.calls.filter(
([command]) => command === 'read_local_project_file', ([command]) => command === 'read_local_project_file',
).length; ).length;
submitChat('/context'); await submitChat('/context');
expect(await screen.findByText(/上下文来源:/)).not.toBeNull(); expect(await screen.findByText(/上下文来源:/)).not.toBeNull();
expect(screen.getByText(/项目对话:\/history/)).not.toBeNull(); expect(screen.getByText(/项目对话:\/history/)).not.toBeNull();
expect(screen.getByText(/项目黑板:\/memory blackboard/)).not.toBeNull(); expect(screen.getByText(/项目黑板:\/memory blackboard/)).not.toBeNull();
@@ -1143,10 +1136,7 @@ export async function assertPlanningAndStatusShortcutFlow(
name: '读取首个上下文', name: '读取首个上下文',
}); });
fireEvent.click(contextDraftButtons[contextDraftButtons.length - 1]); fireEvent.click(contextDraftButtons[contextDraftButtons.length - 1]);
expect(screen.getByLabelText('创作想法')).toHaveProperty( expect(await composerText()).toBe('/read .agent/spec.md');
'value',
'/read .agent/spec.md',
);
expect( expect(
invoke.mock.calls.filter( invoke.mock.calls.filter(
([command]) => command === 'read_local_project_file', ([command]) => command === 'read_local_project_file',
@@ -1168,7 +1158,7 @@ export async function assertPlanningAndStatusShortcutFlow(
const openPreviewCountBeforeTimeline = invoke.mock.calls.filter( const openPreviewCountBeforeTimeline = invoke.mock.calls.filter(
([command]) => command === 'activate_local_game_preview', ([command]) => command === 'activate_local_game_preview',
).length; ).length;
submitChat('/timeline'); await submitChat('/timeline');
expect(await screen.findByText(/项目时间线:/)).not.toBeNull(); expect(await screen.findByText(/项目时间线:/)).not.toBeNull();
const timelineMessages = screen.getAllByText(/项目时间线:/); const timelineMessages = screen.getAllByText(/项目时间线:/);
const timelineMessage = messageBubble( const timelineMessage = messageBubble(
@@ -1190,10 +1180,7 @@ export async function assertPlanningAndStatusShortcutFlow(
name: '读取最近日志', name: '读取最近日志',
}); });
fireEvent.click(timelineDraftButtons[timelineDraftButtons.length - 1]); fireEvent.click(timelineDraftButtons[timelineDraftButtons.length - 1]);
expect(screen.getByLabelText('创作想法')).toHaveProperty( expect(await composerText()).toBe('/read .agent/logs/command.log');
'value',
'/read .agent/logs/command.log',
);
expect( expect(
invoke.mock.calls.filter( invoke.mock.calls.filter(
([command]) => command === 'read_local_project_file', ([command]) => command === 'read_local_project_file',
@@ -1229,7 +1216,7 @@ export async function assertPlanningAndStatusShortcutFlow(
const runLocalCountBeforeHandoff = invoke.mock.calls.filter( const runLocalCountBeforeHandoff = invoke.mock.calls.filter(
([command]) => command === 'run_limited_local_command', ([command]) => command === 'run_limited_local_command',
).length; ).length;
submitChat('/handoff'); await submitChat('/handoff');
expect(await screen.findByText(/项目交接:/)).not.toBeNull(); expect(await screen.findByText(/项目交接:/)).not.toBeNull();
const handoffMessages = screen.getAllByText(/项目交接:/); const handoffMessages = screen.getAllByText(/项目交接:/);
const handoffMessage = messageBubble( const handoffMessage = messageBubble(
@@ -1246,7 +1233,7 @@ export async function assertPlanningAndStatusShortcutFlow(
name: '查看下一步', name: '查看下一步',
}); });
fireEvent.click(nextActionButtons[nextActionButtons.length - 1]); fireEvent.click(nextActionButtons[nextActionButtons.length - 1]);
expect(screen.getByLabelText('创作想法')).toHaveProperty('value', '/next'); expect(await composerText()).toBe('/next');
expect( expect(
invoke.mock.calls.filter( invoke.mock.calls.filter(
([command]) => command === 'get_local_game_manifest', ([command]) => command === 'get_local_game_manifest',
@@ -1266,13 +1253,13 @@ export async function assertPlanningAndStatusShortcutFlow(
const fileReadCountBeforeRuns = invoke.mock.calls.filter( const fileReadCountBeforeRuns = invoke.mock.calls.filter(
([command]) => command === 'read_local_project_file', ([command]) => command === 'read_local_project_file',
).length; ).length;
submitChat('/runs'); await submitChat('/runs');
expect(await screen.findByText(/Run 历史读取命令:/)).not.toBeNull(); expect(await screen.findByText(/Run 历史读取命令:/)).not.toBeNull();
expect( expect(
screen.getByText(/当前指针 run-main-shortcut-trace .*\/trace/), screen.getByText(/当前指针 run-main-shortcut-trace .*\/trace/),
).not.toBeNull(); ).not.toBeNull();
fireEvent.click(screen.getByRole('button', { name: '查看最近 Run' })); fireEvent.click(screen.getByRole('button', { name: '查看最近 Run' }));
expect(screen.getByLabelText('创作想法')).toHaveProperty('value', '/trace'); expect(await composerText()).toBe('/trace');
expect( expect(
invoke.mock.calls.filter( invoke.mock.calls.filter(
([command]) => command === 'read_local_project_file', ([command]) => command === 'read_local_project_file',
@@ -1282,7 +1269,7 @@ export async function assertPlanningAndStatusShortcutFlow(
const fileReadCountBeforeRunFiles = invoke.mock.calls.filter( const fileReadCountBeforeRunFiles = invoke.mock.calls.filter(
([command]) => command === 'read_local_project_file', ([command]) => command === 'read_local_project_file',
).length; ).length;
submitChat('/run-files'); await submitChat('/run-files');
expect( expect(
await screen.findByText(/Agent 运行辅助文件读取命令:/), await screen.findByText(/Agent 运行辅助文件读取命令:/),
).not.toBeNull(); ).not.toBeNull();
@@ -1297,10 +1284,7 @@ export async function assertPlanningAndStatusShortcutFlow(
name: '读输出流', name: '读输出流',
}), }),
); );
expect(screen.getByLabelText('创作想法')).toHaveProperty( expect(await composerText()).toBe('/read .agent/output.jsonl');
'value',
'/read .agent/output.jsonl',
);
expect( expect(
invoke.mock.calls.filter( invoke.mock.calls.filter(
([command]) => command === 'read_local_project_file', ([command]) => command === 'read_local_project_file',
@@ -1310,7 +1294,7 @@ export async function assertPlanningAndStatusShortcutFlow(
const fileReadCountBeforeInternals = invoke.mock.calls.filter( const fileReadCountBeforeInternals = invoke.mock.calls.filter(
([command]) => command === 'read_local_project_file', ([command]) => command === 'read_local_project_file',
).length; ).length;
submitChat('/internals'); await submitChat('/internals');
expect(await screen.findByText(/项目内部真相源读取命令:/)).not.toBeNull(); expect(await screen.findByText(/项目内部真相源读取命令:/)).not.toBeNull();
expect( expect(
screen.getByText(/读 manifest · \.agent\/manifest\.json/), screen.getByText(/读 manifest · \.agent\/manifest\.json/),
@@ -1322,10 +1306,7 @@ export async function assertPlanningAndStatusShortcutFlow(
name: '读 manifest', name: '读 manifest',
}); });
fireEvent.click(internalDraftButtons[internalDraftButtons.length - 1]); fireEvent.click(internalDraftButtons[internalDraftButtons.length - 1]);
expect(screen.getByLabelText('创作想法')).toHaveProperty( expect(await composerText()).toBe('/read .agent/manifest.json');
'value',
'/read .agent/manifest.json',
);
expect( expect(
invoke.mock.calls.filter( invoke.mock.calls.filter(
([command]) => command === 'read_local_project_file', ([command]) => command === 'read_local_project_file',
@@ -1358,7 +1339,7 @@ export async function assertPlanningAndStatusShortcutFlow(
([command]) => command === 'list_local_project_export_packages', ([command]) => command === 'list_local_project_export_packages',
).length, ).length,
}; };
submitChat('/todo'); await submitChat('/todo');
const todoMessages = await screen.findAllByText(/下一轮小步:/); const todoMessages = await screen.findAllByText(/下一轮小步:/);
const todoMessage = messageBubble(todoMessages[todoMessages.length - 1]); const todoMessage = messageBubble(todoMessages[todoMessages.length - 1]);
expect(todoMessage.textContent).toContain('项目:未命名游戏原型'); expect(todoMessage.textContent).toContain('项目:未命名游戏原型');
@@ -1380,7 +1361,7 @@ export async function assertPlanningAndStatusShortcutFlow(
{ name: '查看任务' }, { name: '查看任务' },
); );
fireEvent.click(todoDraftButtons[todoDraftButtons.length - 1]); fireEvent.click(todoDraftButtons[todoDraftButtons.length - 1]);
expect(screen.getByLabelText('创作想法')).toHaveProperty('value', '/tasks'); expect(await composerText()).toBe('/tasks');
expect( expect(
invoke.mock.calls.filter( invoke.mock.calls.filter(
([command]) => command === 'get_local_game_manifest', ([command]) => command === 'get_local_game_manifest',
@@ -1451,7 +1432,7 @@ export async function assertPlanningAndStatusShortcutFlow(
([command]) => command === 'control_agent_run', ([command]) => command === 'control_agent_run',
).length, ).length,
}; };
submitChat('/plan'); await submitChat('/plan');
const planMessages = await screen.findAllByText(/下一轮分工计划:/); const planMessages = await screen.findAllByText(/下一轮分工计划:/);
const planMessage = messageBubble(planMessages[planMessages.length - 1]); const planMessage = messageBubble(planMessages[planMessages.length - 1]);
expect(planMessage.textContent).toContain('项目:未命名游戏原型'); expect(planMessage.textContent).toContain('项目:未命名游戏原型');
@@ -1479,8 +1460,7 @@ export async function assertPlanningAndStatusShortcutFlow(
{ name: '继续执行计划' }, { name: '继续执行计划' },
); );
fireEvent.click(planDraftButtons[planDraftButtons.length - 1]); fireEvent.click(planDraftButtons[planDraftButtons.length - 1]);
expect(screen.getByLabelText('创作想法')).toHaveProperty( expect(await composerText()).toBe(
'value',
'/agent-resume 下一轮计划:美术组 / Asset 生成首版美术素材', '/agent-resume 下一轮计划:美术组 / Asset 生成首版美术素材',
); );
expect( expect(
@@ -1556,7 +1536,7 @@ export async function assertPlanningAndStatusShortcutFlow(
([command]) => command === 'control_agent_run', ([command]) => command === 'control_agent_run',
).length, ).length,
}; };
submitChat('/guide'); await submitChat('/guide');
const guideMessages = await screen.findAllByText(/使用导引:/); const guideMessages = await screen.findAllByText(/使用导引:/);
const guideMessage = messageBubble(guideMessages[guideMessages.length - 1]); const guideMessage = messageBubble(guideMessages[guideMessages.length - 1]);
expect(guideMessage.textContent).toContain('项目:未命名游戏原型'); expect(guideMessage.textContent).toContain('项目:未命名游戏原型');
@@ -1576,7 +1556,7 @@ export async function assertPlanningAndStatusShortcutFlow(
guideMessageList as HTMLElement, guideMessageList as HTMLElement,
).getAllByRole('button', { name: '执行导引建议' }); ).getAllByRole('button', { name: '执行导引建议' });
fireEvent.click(guideDraftButtons[guideDraftButtons.length - 1]); fireEvent.click(guideDraftButtons[guideDraftButtons.length - 1]);
expect(screen.getByLabelText('创作想法')).toHaveProperty('value', '/run'); expect(await composerText()).toBe('/run');
expect( expect(
invoke.mock.calls.filter( invoke.mock.calls.filter(
([command]) => command === 'get_local_game_manifest', ([command]) => command === 'get_local_game_manifest',
@@ -1650,7 +1630,7 @@ export async function assertPlanningAndStatusShortcutFlow(
([command]) => command === 'control_agent_run', ([command]) => command === 'control_agent_run',
).length, ).length,
}; };
submitChat('/progress'); await submitChat('/progress');
const progressMessages = await screen.findAllByText(/项目进度:/); const progressMessages = await screen.findAllByText(/项目进度:/);
const progressMessage = messageBubble( const progressMessage = messageBubble(
progressMessages[progressMessages.length - 1], progressMessages[progressMessages.length - 1],
@@ -1680,7 +1660,7 @@ export async function assertPlanningAndStatusShortcutFlow(
progressMessageList as HTMLElement, progressMessageList as HTMLElement,
).getAllByRole('button', { name: '启动预览' }); ).getAllByRole('button', { name: '启动预览' });
fireEvent.click(progressDraftButtons[progressDraftButtons.length - 1]); fireEvent.click(progressDraftButtons[progressDraftButtons.length - 1]);
expect(screen.getByLabelText('创作想法')).toHaveProperty('value', '/run'); expect(await composerText()).toBe('/run');
expect( expect(
invoke.mock.calls.filter( invoke.mock.calls.filter(
([command]) => command === 'get_local_game_manifest', ([command]) => command === 'get_local_game_manifest',
@@ -1,4 +1,11 @@
import { expect, fireEvent, screen, submitChat, within } from '../../harness'; import {
composerText,
expect,
fireEvent,
screen,
submitChat,
within,
} from '../../harness';
import type { PreviewShortcutInvoke } from './invoke-mock'; import type { PreviewShortcutInvoke } from './invoke-mock';
import { messageBubble } from './message-bubble'; import { messageBubble } from './message-bubble';
@@ -8,7 +15,7 @@ export async function assertPlaytestAndReleaseShortcutFlow(
const runLocalCountBeforeNext = invoke.mock.calls.filter( const runLocalCountBeforeNext = invoke.mock.calls.filter(
([command]) => command === 'run_limited_local_command', ([command]) => command === 'run_limited_local_command',
).length; ).length;
submitChat('/next'); await submitChat('/next');
expect(await screen.findByText(/下一步建议:/)).not.toBeNull(); expect(await screen.findByText(/下一步建议:/)).not.toBeNull();
expect(screen.getByText(/运行自检并启动本地预览:\/run/)).not.toBeNull(); expect(screen.getByText(/运行自检并启动本地预览:\/run/)).not.toBeNull();
expect(screen.getByText(/查看创作目标:\/goal/)).not.toBeNull(); expect(screen.getByText(/查看创作目标:\/goal/)).not.toBeNull();
@@ -86,7 +93,7 @@ export async function assertPlaytestAndReleaseShortcutFlow(
name: '运行自检并启动本地预览', name: '运行自检并启动本地预览',
}), }),
); );
expect(screen.getByLabelText('创作想法')).toHaveProperty('value', '/run'); expect(await composerText()).toBe('/run');
expect( expect(
invoke.mock.calls.filter( invoke.mock.calls.filter(
([command]) => command === 'run_limited_local_command', ([command]) => command === 'run_limited_local_command',
@@ -105,7 +112,7 @@ export async function assertPlaytestAndReleaseShortcutFlow(
const startPreviewCountBeforePublish = invoke.mock.calls.filter( const startPreviewCountBeforePublish = invoke.mock.calls.filter(
([command]) => command === 'start_local_game_preview', ([command]) => command === 'start_local_game_preview',
).length; ).length;
submitChat('/publish'); await submitChat('/publish');
expect(await screen.findByText(/发布准备:/)).not.toBeNull(); expect(await screen.findByText(/发布准备:/)).not.toBeNull();
expect( expect(
screen.getByText(/原型:最近 run 已通过 run-main-shortcut-trace/), screen.getByText(/原型:最近 run 已通过 run-main-shortcut-trace/),
@@ -121,7 +128,7 @@ export async function assertPlaytestAndReleaseShortcutFlow(
name: '启动预览', name: '启动预览',
}); });
fireEvent.click(publishDraftButtons[publishDraftButtons.length - 1]); fireEvent.click(publishDraftButtons[publishDraftButtons.length - 1]);
expect(screen.getByLabelText('创作想法')).toHaveProperty('value', '/run'); expect(await composerText()).toBe('/run');
expect( expect(
invoke.mock.calls.filter( invoke.mock.calls.filter(
([command]) => command === 'get_local_game_manifest', ([command]) => command === 'get_local_game_manifest',
@@ -169,7 +176,7 @@ export async function assertPlaytestAndReleaseShortcutFlow(
([command]) => command === 'list_local_project_export_packages', ([command]) => command === 'list_local_project_export_packages',
).length, ).length,
}; };
submitChat('/listing'); await submitChat('/listing');
const listingMessages = await screen.findAllByText(/作品页草稿:/); const listingMessages = await screen.findAllByText(/作品页草稿:/);
const listingMessage = messageBubble( const listingMessage = messageBubble(
listingMessages[listingMessages.length - 1], listingMessages[listingMessages.length - 1],
@@ -203,10 +210,7 @@ export async function assertPlaytestAndReleaseShortcutFlow(
listingMessageList as HTMLElement, listingMessageList as HTMLElement,
).getAllByRole('button', { name: '读发布说明' }); ).getAllByRole('button', { name: '读发布说明' });
fireEvent.click(listingDraftButtons[listingDraftButtons.length - 1]); fireEvent.click(listingDraftButtons[listingDraftButtons.length - 1]);
expect(screen.getByLabelText('创作想法')).toHaveProperty( expect(await composerText()).toBe('/read exports/README.md');
'value',
'/read exports/README.md',
);
expect( expect(
invoke.mock.calls.filter( invoke.mock.calls.filter(
([command]) => command === 'get_local_game_manifest', ([command]) => command === 'get_local_game_manifest',
@@ -263,7 +267,7 @@ export async function assertPlaytestAndReleaseShortcutFlow(
const openPreviewCountBeforePlaytest = invoke.mock.calls.filter( const openPreviewCountBeforePlaytest = invoke.mock.calls.filter(
([command]) => command === 'activate_local_game_preview', ([command]) => command === 'activate_local_game_preview',
).length; ).length;
submitChat('/playtest'); await submitChat('/playtest');
const playtestMessages = await screen.findAllByText(/试玩状态:/); const playtestMessages = await screen.findAllByText(/试玩状态:/);
const playtestMessage = messageBubble( const playtestMessage = messageBubble(
playtestMessages[playtestMessages.length - 1], playtestMessages[playtestMessages.length - 1],
@@ -282,7 +286,7 @@ export async function assertPlaytestAndReleaseShortcutFlow(
name: '启动试玩', name: '启动试玩',
}); });
fireEvent.click(playtestDraftButtons[playtestDraftButtons.length - 1]); fireEvent.click(playtestDraftButtons[playtestDraftButtons.length - 1]);
expect(screen.getByLabelText('创作想法')).toHaveProperty('value', '/run'); expect(await composerText()).toBe('/run');
expect( expect(
invoke.mock.calls.filter( invoke.mock.calls.filter(
([command]) => command === 'read_local_project_file', ([command]) => command === 'read_local_project_file',
@@ -332,7 +336,7 @@ export async function assertPlaytestAndReleaseShortcutFlow(
([command]) => command === 'export_local_project_package', ([command]) => command === 'export_local_project_package',
).length, ).length,
}; };
submitChat('/test-plan'); await submitChat('/test-plan');
const testPlanMessages = await screen.findAllByText(/手动测试计划:/); const testPlanMessages = await screen.findAllByText(/手动测试计划:/);
const testPlanMessage = messageBubble( const testPlanMessage = messageBubble(
testPlanMessages[testPlanMessages.length - 1], testPlanMessages[testPlanMessages.length - 1],
@@ -369,7 +373,7 @@ export async function assertPlaytestAndReleaseShortcutFlow(
testPlanMessageList as HTMLElement, testPlanMessageList as HTMLElement,
).getAllByRole('button', { name: '启动试玩' }); ).getAllByRole('button', { name: '启动试玩' });
fireEvent.click(testPlanDraftButtons[testPlanDraftButtons.length - 1]); fireEvent.click(testPlanDraftButtons[testPlanDraftButtons.length - 1]);
expect(screen.getByLabelText('创作想法')).toHaveProperty('value', '/run'); expect(await composerText()).toBe('/run');
expect( expect(
invoke.mock.calls.filter( invoke.mock.calls.filter(
([command]) => command === 'get_local_game_manifest', ([command]) => command === 'get_local_game_manifest',
@@ -435,7 +439,7 @@ export async function assertPlaytestAndReleaseShortcutFlow(
([command]) => command === 'control_agent_run', ([command]) => command === 'control_agent_run',
).length, ).length,
}; };
submitChat('/audience'); await submitChat('/audience');
const audienceMessages = await screen.findAllByText(/首批试玩对象:/); const audienceMessages = await screen.findAllByText(/首批试玩对象:/);
const audienceMessage = messageBubble( const audienceMessage = messageBubble(
audienceMessages[audienceMessages.length - 1], audienceMessages[audienceMessages.length - 1],
@@ -476,7 +480,7 @@ export async function assertPlaytestAndReleaseShortcutFlow(
audienceMessageList as HTMLElement, audienceMessageList as HTMLElement,
).getAllByRole('button', { name: '启动试玩' }); ).getAllByRole('button', { name: '启动试玩' });
fireEvent.click(audienceDraftButtons[audienceDraftButtons.length - 1]); fireEvent.click(audienceDraftButtons[audienceDraftButtons.length - 1]);
expect(screen.getByLabelText('创作想法')).toHaveProperty('value', '/run'); expect(await composerText()).toBe('/run');
expect( expect(
invoke.mock.calls.filter( invoke.mock.calls.filter(
([command]) => command === 'get_local_game_manifest', ([command]) => command === 'get_local_game_manifest',
@@ -550,7 +554,7 @@ export async function assertPlaytestAndReleaseShortcutFlow(
([command]) => command === 'control_agent_run', ([command]) => command === 'control_agent_run',
).length, ).length,
}; };
submitChat('/invite'); await submitChat('/invite');
const inviteMessages = await screen.findAllByText(/试玩邀请:/); const inviteMessages = await screen.findAllByText(/试玩邀请:/);
const inviteMessage = messageBubble( const inviteMessage = messageBubble(
inviteMessages[inviteMessages.length - 1], inviteMessages[inviteMessages.length - 1],
@@ -583,7 +587,7 @@ export async function assertPlaytestAndReleaseShortcutFlow(
inviteMessageList as HTMLElement, inviteMessageList as HTMLElement,
).getAllByRole('button', { name: '启动试玩' }); ).getAllByRole('button', { name: '启动试玩' });
fireEvent.click(inviteDraftButtons[inviteDraftButtons.length - 1]); fireEvent.click(inviteDraftButtons[inviteDraftButtons.length - 1]);
expect(screen.getByLabelText('创作想法')).toHaveProperty('value', '/run'); expect(await composerText()).toBe('/run');
expect( expect(
invoke.mock.calls.filter( invoke.mock.calls.filter(
([command]) => command === 'get_local_game_manifest', ([command]) => command === 'get_local_game_manifest',
@@ -730,7 +734,7 @@ export async function assertPlaytestAndReleaseShortcutFlow(
([command]) => command === 'control_agent_run', ([command]) => command === 'control_agent_run',
).length, ).length,
}; };
submitChat('/survey'); await submitChat('/survey');
const surveyMessages = await screen.findAllByText(/试玩问卷:/); const surveyMessages = await screen.findAllByText(/试玩问卷:/);
const surveyMessage = messageBubble( const surveyMessage = messageBubble(
surveyMessages[surveyMessages.length - 1], surveyMessages[surveyMessages.length - 1],
@@ -765,7 +769,7 @@ export async function assertPlaytestAndReleaseShortcutFlow(
surveyMessageList as HTMLElement, surveyMessageList as HTMLElement,
).getAllByRole('button', { name: '启动试玩' }); ).getAllByRole('button', { name: '启动试玩' });
fireEvent.click(surveyDraftButtons[surveyDraftButtons.length - 1]); fireEvent.click(surveyDraftButtons[surveyDraftButtons.length - 1]);
expect(screen.getByLabelText('创作想法')).toHaveProperty('value', '/run'); expect(await composerText()).toBe('/run');
expect( expect(
invoke.mock.calls.filter( invoke.mock.calls.filter(
([command]) => command === 'get_local_game_manifest', ([command]) => command === 'get_local_game_manifest',
@@ -839,7 +843,7 @@ export async function assertPlaytestAndReleaseShortcutFlow(
([command]) => command === 'control_agent_run', ([command]) => command === 'control_agent_run',
).length, ).length,
}; };
submitChat('/cover'); await submitChat('/cover');
const coverMessages = await screen.findAllByText(/封面与缩略图:/); const coverMessages = await screen.findAllByText(/封面与缩略图:/);
const coverMessage = messageBubble(coverMessages[coverMessages.length - 1]); const coverMessage = messageBubble(coverMessages[coverMessages.length - 1]);
expect(coverMessage.textContent).toContain('项目:未命名游戏原型'); expect(coverMessage.textContent).toContain('项目:未命名游戏原型');
@@ -874,7 +878,7 @@ export async function assertPlaytestAndReleaseShortcutFlow(
coverMessageList as HTMLElement, coverMessageList as HTMLElement,
).getAllByRole('button', { name: '启动试玩' }); ).getAllByRole('button', { name: '启动试玩' });
fireEvent.click(coverDraftButtons[coverDraftButtons.length - 1]); fireEvent.click(coverDraftButtons[coverDraftButtons.length - 1]);
expect(screen.getByLabelText('创作想法')).toHaveProperty('value', '/run'); expect(await composerText()).toBe('/run');
expect( expect(
invoke.mock.calls.filter( invoke.mock.calls.filter(
([command]) => command === 'get_local_game_manifest', ([command]) => command === 'get_local_game_manifest',
@@ -948,7 +952,7 @@ export async function assertPlaytestAndReleaseShortcutFlow(
([command]) => command === 'control_agent_run', ([command]) => command === 'control_agent_run',
).length, ).length,
}; };
submitChat('/screenshots'); await submitChat('/screenshots');
const screenshotMessages = await screen.findAllByText(/宣传截图:/); const screenshotMessages = await screen.findAllByText(/宣传截图:/);
const screenshotMessage = messageBubble( const screenshotMessage = messageBubble(
screenshotMessages[screenshotMessages.length - 1], screenshotMessages[screenshotMessages.length - 1],
@@ -985,7 +989,7 @@ export async function assertPlaytestAndReleaseShortcutFlow(
screenshotMessageList as HTMLElement, screenshotMessageList as HTMLElement,
).getAllByRole('button', { name: '启动试玩' }); ).getAllByRole('button', { name: '启动试玩' });
fireEvent.click(screenshotDraftButtons[screenshotDraftButtons.length - 1]); fireEvent.click(screenshotDraftButtons[screenshotDraftButtons.length - 1]);
expect(screen.getByLabelText('创作想法')).toHaveProperty('value', '/run'); expect(await composerText()).toBe('/run');
expect( expect(
invoke.mock.calls.filter( invoke.mock.calls.filter(
([command]) => command === 'get_local_game_manifest', ([command]) => command === 'get_local_game_manifest',
@@ -1059,7 +1063,7 @@ export async function assertPlaytestAndReleaseShortcutFlow(
([command]) => command === 'control_agent_run', ([command]) => command === 'control_agent_run',
).length, ).length,
}; };
submitChat('/trailer'); await submitChat('/trailer');
const trailerMessages = await screen.findAllByText(/试玩短视频:/); const trailerMessages = await screen.findAllByText(/试玩短视频:/);
const trailerMessage = messageBubble( const trailerMessage = messageBubble(
trailerMessages[trailerMessages.length - 1], trailerMessages[trailerMessages.length - 1],
@@ -1097,7 +1101,7 @@ export async function assertPlaytestAndReleaseShortcutFlow(
trailerMessageList as HTMLElement, trailerMessageList as HTMLElement,
).getAllByRole('button', { name: '启动试玩' }); ).getAllByRole('button', { name: '启动试玩' });
fireEvent.click(trailerDraftButtons[trailerDraftButtons.length - 1]); fireEvent.click(trailerDraftButtons[trailerDraftButtons.length - 1]);
expect(screen.getByLabelText('创作想法')).toHaveProperty('value', '/run'); expect(await composerText()).toBe('/run');
expect( expect(
invoke.mock.calls.filter( invoke.mock.calls.filter(
([command]) => command === 'get_local_game_manifest', ([command]) => command === 'get_local_game_manifest',
@@ -1171,7 +1175,7 @@ export async function assertPlaytestAndReleaseShortcutFlow(
([command]) => command === 'control_agent_run', ([command]) => command === 'control_agent_run',
).length, ).length,
}; };
submitChat('/faq'); await submitChat('/faq');
const faqMessages = await screen.findAllByText(/试玩 FAQ/); const faqMessages = await screen.findAllByText(/试玩 FAQ/);
const faqMessage = messageBubble(faqMessages[faqMessages.length - 1]); const faqMessage = messageBubble(faqMessages[faqMessages.length - 1]);
expect(faqMessage.textContent).toContain('项目:未命名游戏原型'); expect(faqMessage.textContent).toContain('项目:未命名游戏原型');
@@ -1202,7 +1206,7 @@ export async function assertPlaytestAndReleaseShortcutFlow(
{ name: '启动试玩' }, { name: '启动试玩' },
); );
fireEvent.click(faqDraftButtons[faqDraftButtons.length - 1]); fireEvent.click(faqDraftButtons[faqDraftButtons.length - 1]);
expect(screen.getByLabelText('创作想法')).toHaveProperty('value', '/run'); expect(await composerText()).toBe('/run');
expect( expect(
invoke.mock.calls.filter( invoke.mock.calls.filter(
([command]) => command === 'get_local_game_manifest', ([command]) => command === 'get_local_game_manifest',
@@ -1276,7 +1280,7 @@ export async function assertPlaytestAndReleaseShortcutFlow(
([command]) => command === 'control_agent_run', ([command]) => command === 'control_agent_run',
).length, ).length,
}; };
submitChat('/post'); await submitChat('/post');
const postMessages = await screen.findAllByText(/社区发布文案:/); const postMessages = await screen.findAllByText(/社区发布文案:/);
const postMessage = messageBubble(postMessages[postMessages.length - 1]); const postMessage = messageBubble(postMessages[postMessages.length - 1]);
expect(postMessage.textContent).toContain('项目:未命名游戏原型'); expect(postMessage.textContent).toContain('项目:未命名游戏原型');
@@ -1313,7 +1317,7 @@ export async function assertPlaytestAndReleaseShortcutFlow(
{ name: '启动试玩' }, { name: '启动试玩' },
); );
fireEvent.click(postDraftButtons[postDraftButtons.length - 1]); fireEvent.click(postDraftButtons[postDraftButtons.length - 1]);
expect(screen.getByLabelText('创作想法')).toHaveProperty('value', '/run'); expect(await composerText()).toBe('/run');
expect( expect(
invoke.mock.calls.filter( invoke.mock.calls.filter(
([command]) => command === 'get_local_game_manifest', ([command]) => command === 'get_local_game_manifest',
@@ -1387,7 +1391,7 @@ export async function assertPlaytestAndReleaseShortcutFlow(
([command]) => command === 'control_agent_run', ([command]) => command === 'control_agent_run',
).length, ).length,
}; };
submitChat('/store'); await submitChat('/store');
const storeMessages = await screen.findAllByText(/上架资料:/); const storeMessages = await screen.findAllByText(/上架资料:/);
const storeMessage = messageBubble(storeMessages[storeMessages.length - 1]); const storeMessage = messageBubble(storeMessages[storeMessages.length - 1]);
expect(storeMessage.textContent).toContain('项目:未命名游戏原型'); expect(storeMessage.textContent).toContain('项目:未命名游戏原型');
@@ -1423,7 +1427,7 @@ export async function assertPlaytestAndReleaseShortcutFlow(
storeMessageList as HTMLElement, storeMessageList as HTMLElement,
).getAllByRole('button', { name: '启动试玩' }); ).getAllByRole('button', { name: '启动试玩' });
fireEvent.click(storeDraftButtons[storeDraftButtons.length - 1]); fireEvent.click(storeDraftButtons[storeDraftButtons.length - 1]);
expect(screen.getByLabelText('创作想法')).toHaveProperty('value', '/run'); expect(await composerText()).toBe('/run');
expect( expect(
invoke.mock.calls.filter( invoke.mock.calls.filter(
([command]) => command === 'get_local_game_manifest', ([command]) => command === 'get_local_game_manifest',
@@ -1497,7 +1501,7 @@ export async function assertPlaytestAndReleaseShortcutFlow(
([command]) => command === 'control_agent_run', ([command]) => command === 'control_agent_run',
).length, ).length,
}; };
submitChat('/media-kit'); await submitChat('/media-kit');
const mediaKitMessages = await screen.findAllByText(/媒体资料包:/); const mediaKitMessages = await screen.findAllByText(/媒体资料包:/);
const mediaKitMessage = messageBubble( const mediaKitMessage = messageBubble(
mediaKitMessages[mediaKitMessages.length - 1], mediaKitMessages[mediaKitMessages.length - 1],
@@ -1532,7 +1536,7 @@ export async function assertPlaytestAndReleaseShortcutFlow(
mediaKitMessageList as HTMLElement, mediaKitMessageList as HTMLElement,
).getAllByRole('button', { name: '启动试玩' }); ).getAllByRole('button', { name: '启动试玩' });
fireEvent.click(mediaKitDraftButtons[mediaKitDraftButtons.length - 1]); fireEvent.click(mediaKitDraftButtons[mediaKitDraftButtons.length - 1]);
expect(screen.getByLabelText('创作想法')).toHaveProperty('value', '/run'); expect(await composerText()).toBe('/run');
expect( expect(
invoke.mock.calls.filter( invoke.mock.calls.filter(
([command]) => command === 'get_local_game_manifest', ([command]) => command === 'get_local_game_manifest',
@@ -1606,7 +1610,7 @@ export async function assertPlaytestAndReleaseShortcutFlow(
([command]) => command === 'control_agent_run', ([command]) => command === 'control_agent_run',
).length, ).length,
}; };
submitChat('/release-notes'); await submitChat('/release-notes');
const releaseNotesMessages = await screen.findAllByText(/试玩更新说明:/); const releaseNotesMessages = await screen.findAllByText(/试玩更新说明:/);
const releaseNotesMessage = messageBubble( const releaseNotesMessage = messageBubble(
releaseNotesMessages[releaseNotesMessages.length - 1], releaseNotesMessages[releaseNotesMessages.length - 1],
@@ -1651,7 +1655,7 @@ export async function assertPlaytestAndReleaseShortcutFlow(
fireEvent.click( fireEvent.click(
releaseNotesDraftButtons[releaseNotesDraftButtons.length - 1], releaseNotesDraftButtons[releaseNotesDraftButtons.length - 1],
); );
expect(screen.getByLabelText('创作想法')).toHaveProperty('value', '/run'); expect(await composerText()).toBe('/run');
expect( expect(
invoke.mock.calls.filter( invoke.mock.calls.filter(
([command]) => command === 'get_local_game_manifest', ([command]) => command === 'get_local_game_manifest',
@@ -1725,7 +1729,7 @@ export async function assertPlaytestAndReleaseShortcutFlow(
([command]) => command === 'control_agent_run', ([command]) => command === 'control_agent_run',
).length, ).length,
}; };
submitChat('/known-issues'); await submitChat('/known-issues');
const knownIssueMessages = await screen.findAllByText(/已知问题清单:/); const knownIssueMessages = await screen.findAllByText(/已知问题清单:/);
const knownIssueMessage = messageBubble( const knownIssueMessage = messageBubble(
knownIssueMessages[knownIssueMessages.length - 1], knownIssueMessages[knownIssueMessages.length - 1],
@@ -1756,7 +1760,7 @@ export async function assertPlaytestAndReleaseShortcutFlow(
knownIssueMessageList as HTMLElement, knownIssueMessageList as HTMLElement,
).getAllByRole('button', { name: '启动试玩' }); ).getAllByRole('button', { name: '启动试玩' });
fireEvent.click(knownIssueDraftButtons[knownIssueDraftButtons.length - 1]); fireEvent.click(knownIssueDraftButtons[knownIssueDraftButtons.length - 1]);
expect(screen.getByLabelText('创作想法')).toHaveProperty('value', '/run'); expect(await composerText()).toBe('/run');
expect( expect(
invoke.mock.calls.filter( invoke.mock.calls.filter(
([command]) => command === 'get_local_game_manifest', ([command]) => command === 'get_local_game_manifest',
@@ -1,4 +1,11 @@
import { expect, fireEvent, screen, submitChat, within } from '../../harness'; import {
composerText,
expect,
fireEvent,
screen,
submitChat,
within,
} from '../../harness';
import type { PreviewShortcutInvoke } from './invoke-mock'; import type { PreviewShortcutInvoke } from './invoke-mock';
import { messageBubble } from './message-bubble'; import { messageBubble } from './message-bubble';
@@ -36,14 +43,14 @@ export async function assertProjectAndDesignShortcutFlow(
expect(screen.queryByText(/planner-secret/)).toBeNull(); expect(screen.queryByText(/planner-secret/)).toBeNull();
expect(screen.queryByText(/art-secret/)).toBeNull(); expect(screen.queryByText(/art-secret/)).toBeNull();
submitChat('/agents'); await submitChat('/agents');
await screen.findByText(/Agent 状态:/); await screen.findByText(/Agent 状态:/);
expect( expect(
/Agent 状态:[\s\S]*美术组 \/ Asset · 生成首版美术素材 · 待处理[\s\S]*智能服务:已连接/.test( /Agent 状态:[\s\S]*美术组 \/ Asset · 生成首版美术素材 · 待处理[\s\S]*智能服务:已连接/.test(
screen.getByLabelText('聊天').textContent ?? '', screen.getByLabelText('聊天').textContent ?? '',
), ),
).toBe(true); ).toBe(true);
submitChat('/agent-conversations'); await submitChat('/agent-conversations');
const agentConversationMessage = const agentConversationMessage =
await screen.findByText('Agent 对话读取命令:'); await screen.findByText('Agent 对话读取命令:');
const agentConversationBubble = messageBubble(agentConversationMessage); const agentConversationBubble = messageBubble(agentConversationMessage);
@@ -51,8 +58,7 @@ export async function assertProjectAndDesignShortcutFlow(
'美术组 / Asset · 生成首版美术素材:/read .agent/conversations/agents/art-asset-plan.jsonl', '美术组 / Asset · 生成首版美术素材:/read .agent/conversations/agents/art-asset-plan.jsonl',
); );
fireEvent.click(screen.getByRole('button', { name: '读取拆解创作方向对话' })); fireEvent.click(screen.getByRole('button', { name: '读取拆解创作方向对话' }));
expect(screen.getByLabelText('创作想法')).toHaveProperty( expect(await composerText()).toBe(
'value',
'/read .agent/conversations/agents/design-director.jsonl', '/read .agent/conversations/agents/design-director.jsonl',
); );
expect(invoke).not.toHaveBeenCalledWith( expect(invoke).not.toHaveBeenCalledWith(
@@ -61,7 +67,7 @@ export async function assertProjectAndDesignShortcutFlow(
relativePath: '.agent/conversations/agents/design-director.jsonl', relativePath: '.agent/conversations/agents/design-director.jsonl',
}), }),
); );
submitChat('/agent-memories'); await submitChat('/agent-memories');
const agentMemoryMessage = const agentMemoryMessage =
await screen.findByText('Agent 私有记忆读取命令:'); await screen.findByText('Agent 私有记忆读取命令:');
const agentMemoryBubble = messageBubble(agentMemoryMessage); const agentMemoryBubble = messageBubble(agentMemoryMessage);
@@ -69,10 +75,7 @@ export async function assertProjectAndDesignShortcutFlow(
'美术组 / Asset · 生成首版美术素材:/read memory/agents/art/asset.md', '美术组 / Asset · 生成首版美术素材:/read memory/agents/art/asset.md',
); );
fireEvent.click(screen.getByRole('button', { name: '读取拆解创作方向记忆' })); fireEvent.click(screen.getByRole('button', { name: '读取拆解创作方向记忆' }));
expect(screen.getByLabelText('创作想法')).toHaveProperty( expect(await composerText()).toBe('/read memory/agents/design/director.md');
'value',
'/read memory/agents/design/director.md',
);
expect(invoke).not.toHaveBeenCalledWith( expect(invoke).not.toHaveBeenCalledWith(
'read_local_project_file', 'read_local_project_file',
expect.objectContaining({ expect.objectContaining({
@@ -105,12 +108,12 @@ export async function assertProjectAndDesignShortcutFlow(
const manifestReadCountBeforeBrief = invoke.mock.calls.filter( const manifestReadCountBeforeBrief = invoke.mock.calls.filter(
([command]) => command === 'get_local_game_manifest', ([command]) => command === 'get_local_game_manifest',
).length; ).length;
submitChat('/brief'); await submitChat('/brief');
expect(await screen.findByText(/项目简报:/)).not.toBeNull(); expect(await screen.findByText(/项目简报:/)).not.toBeNull();
expect(screen.getByText(/任务:完成 0\/16 · ready 3/)).not.toBeNull(); expect(screen.getByText(/任务:完成 0\/16 · ready 3/)).not.toBeNull();
expect(screen.getByText(/最近 Runrun-main-shortcut-trace/)).not.toBeNull(); expect(screen.getByText(/最近 Runrun-main-shortcut-trace/)).not.toBeNull();
fireEvent.click(screen.getByRole('button', { name: '查看下一步' })); fireEvent.click(screen.getByRole('button', { name: '查看下一步' }));
expect(screen.getByLabelText('创作想法')).toHaveProperty('value', '/next'); expect(await composerText()).toBe('/next');
expect( expect(
invoke.mock.calls.filter( invoke.mock.calls.filter(
([command]) => command === 'get_local_game_manifest', ([command]) => command === 'get_local_game_manifest',
@@ -126,7 +129,7 @@ export async function assertProjectAndDesignShortcutFlow(
const runLocalCountBeforeGoal = invoke.mock.calls.filter( const runLocalCountBeforeGoal = invoke.mock.calls.filter(
([command]) => command === 'run_limited_local_command', ([command]) => command === 'run_limited_local_command',
).length; ).length;
submitChat('/goal'); await submitChat('/goal');
expect(await screen.findByText(/创作目标:/)).not.toBeNull(); expect(await screen.findByText(/创作目标:/)).not.toBeNull();
const goalMessages = screen.getAllByText(/创作目标:/); const goalMessages = screen.getAllByText(/创作目标:/);
const goalMessage = messageBubble(goalMessages[goalMessages.length - 1]); const goalMessage = messageBubble(goalMessages[goalMessages.length - 1]);
@@ -139,10 +142,7 @@ export async function assertProjectAndDesignShortcutFlow(
expect(goalMessage.textContent).toContain('上下文:/context'); expect(goalMessage.textContent).toContain('上下文:/context');
expect(goalMessage.textContent).toContain('建议:/agent-resume 细化目标:'); expect(goalMessage.textContent).toContain('建议:/agent-resume 细化目标:');
fireEvent.click(screen.getByRole('button', { name: '补充目标' })); fireEvent.click(screen.getByRole('button', { name: '补充目标' }));
expect(screen.getByLabelText('创作想法')).toHaveProperty( expect(await composerText()).toBe('/agent-resume 细化目标:');
'value',
'/agent-resume 细化目标:',
);
expect( expect(
invoke.mock.calls.filter( invoke.mock.calls.filter(
([command]) => command === 'get_local_game_manifest', ([command]) => command === 'get_local_game_manifest',
@@ -185,7 +185,7 @@ export async function assertProjectAndDesignShortcutFlow(
([command]) => command === 'list_local_project_export_packages', ([command]) => command === 'list_local_project_export_packages',
).length, ).length,
}; };
submitChat('/spec'); await submitChat('/spec');
const specMessages = await screen.findAllByText(/创作规格包:/); const specMessages = await screen.findAllByText(/创作规格包:/);
const specMessage = messageBubble(specMessages[specMessages.length - 1]); const specMessage = messageBubble(specMessages[specMessages.length - 1]);
expect(specMessage.textContent).toContain('项目:未命名游戏原型'); expect(specMessage.textContent).toContain('项目:未命名游戏原型');
@@ -213,10 +213,7 @@ export async function assertProjectAndDesignShortcutFlow(
{ name: '读取规格' }, { name: '读取规格' },
); );
fireEvent.click(specDraftButtons[specDraftButtons.length - 1]); fireEvent.click(specDraftButtons[specDraftButtons.length - 1]);
expect(screen.getByLabelText('创作想法')).toHaveProperty( expect(await composerText()).toBe('/read .agent/spec.md');
'value',
'/read .agent/spec.md',
);
expect( expect(
invoke.mock.calls.filter( invoke.mock.calls.filter(
([command]) => command === 'get_local_game_manifest', ([command]) => command === 'get_local_game_manifest',
@@ -284,7 +281,7 @@ export async function assertProjectAndDesignShortcutFlow(
([command]) => command === 'list_local_project_export_packages', ([command]) => command === 'list_local_project_export_packages',
).length, ).length,
}; };
submitChat('/mvp'); await submitChat('/mvp');
expect(await screen.findByText(/MVP 范围:/)).not.toBeNull(); expect(await screen.findByText(/MVP 范围:/)).not.toBeNull();
const mvpMessages = screen.getAllByText(/MVP 范围:/); const mvpMessages = screen.getAllByText(/MVP 范围:/);
const mvpMessage = messageBubble(mvpMessages[mvpMessages.length - 1]); const mvpMessage = messageBubble(mvpMessages[mvpMessages.length - 1]);
@@ -313,7 +310,7 @@ export async function assertProjectAndDesignShortcutFlow(
{ name: '启动预览' }, { name: '启动预览' },
); );
fireEvent.click(mvpDraftButtons[mvpDraftButtons.length - 1]); fireEvent.click(mvpDraftButtons[mvpDraftButtons.length - 1]);
expect(screen.getByLabelText('创作想法')).toHaveProperty('value', '/run'); expect(await composerText()).toBe('/run');
expect( expect(
invoke.mock.calls.filter( invoke.mock.calls.filter(
([command]) => command === 'get_local_game_manifest', ([command]) => command === 'get_local_game_manifest',
@@ -381,7 +378,7 @@ export async function assertProjectAndDesignShortcutFlow(
([command]) => command === 'list_local_project_export_packages', ([command]) => command === 'list_local_project_export_packages',
).length, ).length,
}; };
submitChat('/pitch'); await submitChat('/pitch');
const pitchMessages = await screen.findAllByText(/试玩定位:/); const pitchMessages = await screen.findAllByText(/试玩定位:/);
const pitchMessage = messageBubble(pitchMessages[pitchMessages.length - 1]); const pitchMessage = messageBubble(pitchMessages[pitchMessages.length - 1]);
expect(pitchMessage.textContent).toContain('项目:未命名游戏原型'); expect(pitchMessage.textContent).toContain('项目:未命名游戏原型');
@@ -408,7 +405,7 @@ export async function assertProjectAndDesignShortcutFlow(
pitchMessageList as HTMLElement, pitchMessageList as HTMLElement,
).getAllByRole('button', { name: '启动预览' }); ).getAllByRole('button', { name: '启动预览' });
fireEvent.click(pitchDraftButtons[pitchDraftButtons.length - 1]); fireEvent.click(pitchDraftButtons[pitchDraftButtons.length - 1]);
expect(screen.getByLabelText('创作想法')).toHaveProperty('value', '/run'); expect(await composerText()).toBe('/run');
expect( expect(
invoke.mock.calls.filter( invoke.mock.calls.filter(
([command]) => command === 'get_local_game_manifest', ([command]) => command === 'get_local_game_manifest',
@@ -476,7 +473,7 @@ export async function assertProjectAndDesignShortcutFlow(
([command]) => command === 'list_local_project_export_packages', ([command]) => command === 'list_local_project_export_packages',
).length, ).length,
}; };
submitChat('/demo'); await submitChat('/demo');
const demoMessages = await screen.findAllByText(/试玩讲解稿:/); const demoMessages = await screen.findAllByText(/试玩讲解稿:/);
const demoMessage = messageBubble(demoMessages[demoMessages.length - 1]); const demoMessage = messageBubble(demoMessages[demoMessages.length - 1]);
expect(demoMessage.textContent).toContain('项目:未命名游戏原型'); expect(demoMessage.textContent).toContain('项目:未命名游戏原型');
@@ -510,7 +507,7 @@ export async function assertProjectAndDesignShortcutFlow(
{ name: '启动预览' }, { name: '启动预览' },
); );
fireEvent.click(demoDraftButtons[demoDraftButtons.length - 1]); fireEvent.click(demoDraftButtons[demoDraftButtons.length - 1]);
expect(screen.getByLabelText('创作想法')).toHaveProperty('value', '/run'); expect(await composerText()).toBe('/run');
expect( expect(
invoke.mock.calls.filter( invoke.mock.calls.filter(
([command]) => command === 'get_local_game_manifest', ([command]) => command === 'get_local_game_manifest',
@@ -575,7 +572,7 @@ export async function assertProjectAndDesignShortcutFlow(
([command]) => command === 'export_local_project_package', ([command]) => command === 'export_local_project_package',
).length, ).length,
}; };
submitChat('/rules'); await submitChat('/rules');
expect(await screen.findByText(/玩法操作:/)).not.toBeNull(); expect(await screen.findByText(/玩法操作:/)).not.toBeNull();
const controlMessages = screen.getAllByText(/玩法操作:/); const controlMessages = screen.getAllByText(/玩法操作:/);
const controlMessage = messageBubble( const controlMessage = messageBubble(
@@ -616,8 +613,7 @@ export async function assertProjectAndDesignShortcutFlow(
controlMessageList as HTMLElement, controlMessageList as HTMLElement,
).getAllByRole('button', { name: '补充操作说明' }); ).getAllByRole('button', { name: '补充操作说明' });
fireEvent.click(controlDraftButtons[controlDraftButtons.length - 1]); fireEvent.click(controlDraftButtons[controlDraftButtons.length - 1]);
expect(screen.getByLabelText('创作想法')).toHaveProperty( expect(await composerText()).toBe(
'value',
'/agent-resume 操作说明:在首屏明确移动/点击操作、胜负目标、失败后重开方式', '/agent-resume 操作说明:在首屏明确移动/点击操作、胜负目标、失败后重开方式',
); );
expect( expect(
@@ -682,7 +678,7 @@ export async function assertProjectAndDesignShortcutFlow(
([command]) => command === 'list_local_project_export_packages', ([command]) => command === 'list_local_project_export_packages',
).length, ).length,
}; };
submitChat('/tutorial'); await submitChat('/tutorial');
const tutorialMessages = await screen.findAllByText(/新手引导:/); const tutorialMessages = await screen.findAllByText(/新手引导:/);
const tutorialMessage = messageBubble( const tutorialMessage = messageBubble(
tutorialMessages[tutorialMessages.length - 1], tutorialMessages[tutorialMessages.length - 1],
@@ -716,8 +712,7 @@ export async function assertProjectAndDesignShortcutFlow(
tutorialMessageList as HTMLElement, tutorialMessageList as HTMLElement,
).getAllByRole('button', { name: '补充新手引导' }); ).getAllByRole('button', { name: '补充新手引导' });
fireEvent.click(tutorialDraftButtons[tutorialDraftButtons.length - 1]); fireEvent.click(tutorialDraftButtons[tutorialDraftButtons.length - 1]);
expect(screen.getByLabelText('创作想法')).toHaveProperty( expect(await composerText()).toBe(
'value',
'/agent-resume 新手引导:在首屏加入目标、操作、反馈、失败重开提示', '/agent-resume 新手引导:在首屏加入目标、操作、反馈、失败重开提示',
); );
expect( expect(
@@ -787,7 +782,7 @@ export async function assertProjectAndDesignShortcutFlow(
([command]) => command === 'list_local_project_export_packages', ([command]) => command === 'list_local_project_export_packages',
).length, ).length,
}; };
submitChat('/mobile'); await submitChat('/mobile');
const mobileMessages = await screen.findAllByText(/移动试玩:/); const mobileMessages = await screen.findAllByText(/移动试玩:/);
const mobileMessage = messageBubble( const mobileMessage = messageBubble(
mobileMessages[mobileMessages.length - 1], mobileMessages[mobileMessages.length - 1],
@@ -824,8 +819,7 @@ export async function assertProjectAndDesignShortcutFlow(
mobileMessageList as HTMLElement, mobileMessageList as HTMLElement,
).getAllByRole('button', { name: '补充移动试玩' }); ).getAllByRole('button', { name: '补充移动试玩' });
fireEvent.click(mobileDraftButtons[mobileDraftButtons.length - 1]); fireEvent.click(mobileDraftButtons[mobileDraftButtons.length - 1]);
expect(screen.getByLabelText('创作想法')).toHaveProperty( expect(await composerText()).toBe(
'value',
'/agent-resume 移动试玩:补充触屏操作、响应式画布、横竖屏提示、重开按钮', '/agent-resume 移动试玩:补充触屏操作、响应式画布、横竖屏提示、重开按钮',
); );
expect( expect(
@@ -898,7 +892,7 @@ export async function assertProjectAndDesignShortcutFlow(
([command]) => command === 'control_agent_run', ([command]) => command === 'control_agent_run',
).length, ).length,
}; };
submitChat('/compatibility'); await submitChat('/compatibility');
const compatibilityMessages = await screen.findAllByText(/兼容性说明:/); const compatibilityMessages = await screen.findAllByText(/兼容性说明:/);
const compatibilityMessage = messageBubble( const compatibilityMessage = messageBubble(
compatibilityMessages[compatibilityMessages.length - 1], compatibilityMessages[compatibilityMessages.length - 1],
@@ -940,7 +934,7 @@ export async function assertProjectAndDesignShortcutFlow(
fireEvent.click( fireEvent.click(
compatibilityDraftButtons[compatibilityDraftButtons.length - 1], compatibilityDraftButtons[compatibilityDraftButtons.length - 1],
); );
expect(screen.getByLabelText('创作想法')).toHaveProperty('value', '/run'); expect(await composerText()).toBe('/run');
expect( expect(
invoke.mock.calls.filter( invoke.mock.calls.filter(
([command]) => command === 'get_local_game_manifest', ([command]) => command === 'get_local_game_manifest',
@@ -1014,7 +1008,7 @@ export async function assertProjectAndDesignShortcutFlow(
([command]) => command === 'control_agent_run', ([command]) => command === 'control_agent_run',
).length, ).length,
}; };
submitChat('/accessibility'); await submitChat('/accessibility');
const accessibilityMessages = await screen.findAllByText(/可读性与无障碍:/); const accessibilityMessages = await screen.findAllByText(/可读性与无障碍:/);
const accessibilityMessage = messageBubble( const accessibilityMessage = messageBubble(
accessibilityMessages[accessibilityMessages.length - 1], accessibilityMessages[accessibilityMessages.length - 1],
@@ -1061,8 +1055,7 @@ export async function assertProjectAndDesignShortcutFlow(
fireEvent.click( fireEvent.click(
accessibilityDraftButtons[accessibilityDraftButtons.length - 1], accessibilityDraftButtons[accessibilityDraftButtons.length - 1],
); );
expect(screen.getByLabelText('创作想法')).toHaveProperty( expect(await composerText()).toBe(
'value',
'/agent-resume 可读性与无障碍:补充文字对比、清晰按钮标签、键盘等价操作、非颜色唯一反馈、静音可玩', '/agent-resume 可读性与无障碍:补充文字对比、清晰按钮标签、键盘等价操作、非颜色唯一反馈、静音可玩',
); );
expect( expect(
@@ -1138,7 +1131,7 @@ export async function assertProjectAndDesignShortcutFlow(
([command]) => command === 'control_agent_run', ([command]) => command === 'control_agent_run',
).length, ).length,
}; };
submitChat('/localization'); await submitChat('/localization');
const localizationMessages = await screen.findAllByText(/本地化与文案:/); const localizationMessages = await screen.findAllByText(/本地化与文案:/);
const localizationMessage = messageBubble( const localizationMessage = messageBubble(
localizationMessages[localizationMessages.length - 1], localizationMessages[localizationMessages.length - 1],
@@ -1192,10 +1185,7 @@ export async function assertProjectAndDesignShortcutFlow(
fireEvent.click( fireEvent.click(
localizationDraftButtons[localizationDraftButtons.length - 1], localizationDraftButtons[localizationDraftButtons.length - 1],
); );
expect(screen.getByLabelText('创作想法')).toHaveProperty( expect(await composerText()).toBe('/read exports/README.md');
'value',
'/read exports/README.md',
);
expect( expect(
invoke.mock.calls.filter( invoke.mock.calls.filter(
([command]) => command === 'get_local_game_manifest', ([command]) => command === 'get_local_game_manifest',
@@ -1269,7 +1259,7 @@ export async function assertProjectAndDesignShortcutFlow(
([command]) => command === 'control_agent_run', ([command]) => command === 'control_agent_run',
).length, ).length,
}; };
submitChat('/performance'); await submitChat('/performance');
const performanceMessages = await screen.findAllByText(/性能与加载:/); const performanceMessages = await screen.findAllByText(/性能与加载:/);
const performanceMessage = messageBubble( const performanceMessage = messageBubble(
performanceMessages[performanceMessages.length - 1], performanceMessages[performanceMessages.length - 1],
@@ -1310,10 +1300,7 @@ export async function assertProjectAndDesignShortcutFlow(
performanceMessageList as HTMLElement, performanceMessageList as HTMLElement,
).getAllByRole('button', { name: '列出 Run 产物' }); ).getAllByRole('button', { name: '列出 Run 产物' });
fireEvent.click(performanceDraftButtons[performanceDraftButtons.length - 1]); fireEvent.click(performanceDraftButtons[performanceDraftButtons.length - 1]);
expect(screen.getByLabelText('创作想法')).toHaveProperty( expect(await composerText()).toBe('/run-artifacts');
'value',
'/run-artifacts',
);
expect( expect(
invoke.mock.calls.filter( invoke.mock.calls.filter(
([command]) => command === 'get_local_game_manifest', ([command]) => command === 'get_local_game_manifest',
@@ -1387,7 +1374,7 @@ export async function assertProjectAndDesignShortcutFlow(
([command]) => command === 'control_agent_run', ([command]) => command === 'control_agent_run',
).length, ).length,
}; };
submitChat('/polish'); await submitChat('/polish');
const polishMessages = await screen.findAllByText(/试玩前打磨:/); const polishMessages = await screen.findAllByText(/试玩前打磨:/);
const polishMessage = messageBubble( const polishMessage = messageBubble(
polishMessages[polishMessages.length - 1], polishMessages[polishMessages.length - 1],
@@ -1427,8 +1414,7 @@ export async function assertProjectAndDesignShortcutFlow(
polishMessageList as HTMLElement, polishMessageList as HTMLElement,
).getAllByRole('button', { name: '补充打磨' }); ).getAllByRole('button', { name: '补充打磨' });
fireEvent.click(polishDraftButtons[polishDraftButtons.length - 1]); fireEvent.click(polishDraftButtons[polishDraftButtons.length - 1]);
expect(screen.getByLabelText('创作想法')).toHaveProperty( expect(await composerText()).toBe(
'value',
'/agent-resume 打磨:补齐新手引导、触屏操作、可读性、性能、素材署名和试玩反馈', '/agent-resume 打磨:补齐新手引导、触屏操作、可读性、性能、素材署名和试玩反馈',
); );
expect( expect(
@@ -1501,7 +1487,7 @@ export async function assertProjectAndDesignShortcutFlow(
([command]) => command === 'list_local_project_export_packages', ([command]) => command === 'list_local_project_export_packages',
).length, ).length,
}; };
submitChat('/credits'); await submitChat('/credits');
const creditMessages = await screen.findAllByText(/素材署名:/); const creditMessages = await screen.findAllByText(/素材署名:/);
const creditMessage = messageBubble( const creditMessage = messageBubble(
creditMessages[creditMessages.length - 1], creditMessages[creditMessages.length - 1],
@@ -1527,7 +1513,7 @@ export async function assertProjectAndDesignShortcutFlow(
creditMessageList as HTMLElement, creditMessageList as HTMLElement,
).getAllByRole('button', { name: '查看资产' }); ).getAllByRole('button', { name: '查看资产' });
fireEvent.click(creditDraftButtons[creditDraftButtons.length - 1]); fireEvent.click(creditDraftButtons[creditDraftButtons.length - 1]);
expect(screen.getByLabelText('创作想法')).toHaveProperty('value', '/assets'); expect(await composerText()).toBe('/assets');
expect( expect(
invoke.mock.calls.filter( invoke.mock.calls.filter(
([command]) => command === 'get_local_game_manifest', ([command]) => command === 'get_local_game_manifest',
@@ -1,4 +1,6 @@
import { import {
composerText,
composerValue,
expect, expect,
fireEvent, fireEvent,
screen, screen,
@@ -35,7 +37,7 @@ export async function assertProjectToolsAndPreviewFlow(
([command]) => command === 'export_local_project_package', ([command]) => command === 'export_local_project_package',
).length, ).length,
}; };
submitChat('/feedback'); await submitChat('/feedback');
const feedbackMessages = await screen.findAllByText(/试玩反馈:/); const feedbackMessages = await screen.findAllByText(/试玩反馈:/);
const feedbackMessage = messageBubble( const feedbackMessage = messageBubble(
feedbackMessages[feedbackMessages.length - 1], feedbackMessages[feedbackMessages.length - 1],
@@ -60,7 +62,7 @@ export async function assertProjectToolsAndPreviewFlow(
feedbackMessageList as HTMLElement, feedbackMessageList as HTMLElement,
).getAllByRole('button', { name: '启动预览' }); ).getAllByRole('button', { name: '启动预览' });
fireEvent.click(feedbackDraftButtons[feedbackDraftButtons.length - 1]); fireEvent.click(feedbackDraftButtons[feedbackDraftButtons.length - 1]);
expect(screen.getByLabelText('创作想法')).toHaveProperty('value', '/run'); expect(await composerText()).toBe('/run');
expect( expect(
invoke.mock.calls.filter( invoke.mock.calls.filter(
([command]) => command === 'get_local_game_manifest', ([command]) => command === 'get_local_game_manifest',
@@ -126,7 +128,7 @@ export async function assertProjectToolsAndPreviewFlow(
([command]) => command === 'control_agent_run', ([command]) => command === 'control_agent_run',
).length, ).length,
}; };
submitChat('/retention'); await submitChat('/retention');
const retentionMessages = await screen.findAllByText(/复玩观察:/); const retentionMessages = await screen.findAllByText(/复玩观察:/);
const retentionMessage = messageBubble( const retentionMessage = messageBubble(
retentionMessages[retentionMessages.length - 1], retentionMessages[retentionMessages.length - 1],
@@ -165,7 +167,7 @@ export async function assertProjectToolsAndPreviewFlow(
retentionMessageList as HTMLElement, retentionMessageList as HTMLElement,
).getAllByRole('button', { name: '启动试玩' }); ).getAllByRole('button', { name: '启动试玩' });
fireEvent.click(retentionDraftButtons[retentionDraftButtons.length - 1]); fireEvent.click(retentionDraftButtons[retentionDraftButtons.length - 1]);
expect(screen.getByLabelText('创作想法')).toHaveProperty('value', '/run'); expect(await composerText()).toBe('/run');
expect( expect(
invoke.mock.calls.filter( invoke.mock.calls.filter(
([command]) => command === 'get_local_game_manifest', ([command]) => command === 'get_local_game_manifest',
@@ -236,7 +238,7 @@ export async function assertProjectToolsAndPreviewFlow(
([command]) => command === 'list_local_project_export_packages', ([command]) => command === 'list_local_project_export_packages',
).length, ).length,
}; };
submitChat('/share'); await submitChat('/share');
const shareMessages = await screen.findAllByText(/试玩交付:/); const shareMessages = await screen.findAllByText(/试玩交付:/);
const shareMessage = messageBubble(shareMessages[shareMessages.length - 1]); const shareMessage = messageBubble(shareMessages[shareMessages.length - 1]);
expect(shareMessage.textContent).toContain('项目:未命名游戏原型'); expect(shareMessage.textContent).toContain('项目:未命名游戏原型');
@@ -260,7 +262,7 @@ export async function assertProjectToolsAndPreviewFlow(
shareMessageList as HTMLElement, shareMessageList as HTMLElement,
).getAllByRole('button', { name: '导出试玩包' }); ).getAllByRole('button', { name: '导出试玩包' });
fireEvent.click(shareDraftButtons[shareDraftButtons.length - 1]); fireEvent.click(shareDraftButtons[shareDraftButtons.length - 1]);
expect(screen.getByLabelText('创作想法')).toHaveProperty('value', '/export'); expect(await composerText()).toBe('/export');
expect( expect(
invoke.mock.calls.filter( invoke.mock.calls.filter(
([command]) => command === 'get_local_game_manifest', ([command]) => command === 'get_local_game_manifest',
@@ -306,39 +308,39 @@ export async function assertProjectToolsAndPreviewFlow(
expect(await screen.findByText(/策略:\.agent\/policy\.json/)).not.toBeNull(); expect(await screen.findByText(/策略:\.agent\/policy\.json/)).not.toBeNull();
const composerInput = screen.getByLabelText('创作想法'); const composerInput = screen.getByLabelText('创作想法');
fireEvent.click(screen.getByRole('button', { name: '索引确认' })); fireEvent.click(screen.getByRole('button', { name: '索引确认' }));
expect(composerInput).toHaveProperty( expect(await composerValue(composerInput)).toBe(
'value',
'/policy-confirm project.index', '/policy-confirm project.index',
); );
fireEvent.click(screen.getByRole('button', { name: '资产登记确认' })); fireEvent.click(screen.getByRole('button', { name: '资产登记确认' }));
expect(composerInput).toHaveProperty( expect(await composerValue(composerInput)).toBe(
'value',
'/policy-confirm asset.register', '/policy-confirm asset.register',
); );
fireEvent.click(screen.getByRole('button', { name: '记忆写入确认' })); fireEvent.click(screen.getByRole('button', { name: '记忆写入确认' }));
expect(composerInput).toHaveProperty('value', '/policy-confirm memory.write'); expect(await composerValue(composerInput)).toBe(
'/policy-confirm memory.write',
);
fireEvent.click(screen.getByRole('button', { name: '预览确认' })); fireEvent.click(screen.getByRole('button', { name: '预览确认' }));
expect(composerInput).toHaveProperty( expect(await composerValue(composerInput)).toBe(
'value',
'/policy-confirm preview.start', '/policy-confirm preview.start',
); );
fireEvent.click(screen.getByRole('button', { name: '打开预览确认' })); fireEvent.click(screen.getByRole('button', { name: '打开预览确认' }));
expect(composerInput).toHaveProperty('value', '/policy-confirm preview.open'); expect(await composerValue(composerInput)).toBe(
'/policy-confirm preview.open',
);
fireEvent.click(screen.getByRole('button', { name: '停止预览确认' })); fireEvent.click(screen.getByRole('button', { name: '停止预览确认' }));
expect(composerInput).toHaveProperty('value', '/policy-confirm preview.stop'); expect(await composerValue(composerInput)).toBe(
'/policy-confirm preview.stop',
);
fireEvent.click(screen.getByRole('button', { name: 'Agent确认' })); fireEvent.click(screen.getByRole('button', { name: 'Agent确认' }));
expect(composerInput).toHaveProperty( expect(await composerValue(composerInput)).toBe(
'value',
'/policy-confirm agent.run_status', '/policy-confirm agent.run_status',
); );
fireEvent.click(screen.getByRole('button', { name: '读对话确认' })); fireEvent.click(screen.getByRole('button', { name: '读对话确认' }));
expect(composerInput).toHaveProperty( expect(await composerValue(composerInput)).toBe(
'value',
'/policy-confirm conversation.read', '/policy-confirm conversation.read',
); );
fireEvent.click(screen.getByRole('button', { name: '存对话确认' })); fireEvent.click(screen.getByRole('button', { name: '存对话确认' }));
expect(composerInput).toHaveProperty( expect(await composerValue(composerInput)).toBe(
'value',
'/policy-confirm conversation.write', '/policy-confirm conversation.write',
); );
expect(screen.queryByText('project.policy_write')).toBeNull(); expect(screen.queryByText('project.policy_write')).toBeNull();
@@ -366,7 +368,7 @@ export async function assertProjectToolsAndPreviewFlow(
'relativePath' in args && 'relativePath' in args &&
args.relativePath === 'assets/manifest.art.json', args.relativePath === 'assets/manifest.art.json',
).length; ).length;
submitChat('/art'); await submitChat('/art');
expect(await screen.findByText(/美术素材:1 个/)).not.toBeNull(); expect(await screen.findByText(/美术素材:1 个/)).not.toBeNull();
expect(screen.getByText(/来源:画板 1/)).not.toBeNull(); expect(screen.getByText(/来源:画板 1/)).not.toBeNull();
expect( expect(
@@ -376,8 +378,7 @@ export async function assertProjectToolsAndPreviewFlow(
name: '读美术清单', name: '读美术清单',
}); });
fireEvent.click(visualDraftButtons[visualDraftButtons.length - 1]); fireEvent.click(visualDraftButtons[visualDraftButtons.length - 1]);
expect(composerInput).toHaveProperty( expect(await composerValue(composerInput)).toBe(
'value',
'/read assets/manifest.art.json', '/read assets/manifest.art.json',
); );
expect( expect(
@@ -396,7 +397,7 @@ export async function assertProjectToolsAndPreviewFlow(
'relativePath' in args && 'relativePath' in args &&
args.relativePath === 'assets/manifest.audio.json', args.relativePath === 'assets/manifest.audio.json',
).length; ).length;
submitChat('/audio'); await submitChat('/audio');
expect(await screen.findByText(/音频素材:暂无登记音频/)).not.toBeNull(); expect(await screen.findByText(/音频素材:暂无登记音频/)).not.toBeNull();
expect( expect(
screen.getByText(/可先登记项目内音效,或把已有画板音频作为素材导入/), screen.getByText(/可先登记项目内音效,或把已有画板音频作为素材导入/),
@@ -405,8 +406,7 @@ export async function assertProjectToolsAndPreviewFlow(
name: '登记音效', name: '登记音效',
}); });
fireEvent.click(audioDraftButtons[audioDraftButtons.length - 1]); fireEvent.click(audioDraftButtons[audioDraftButtons.length - 1]);
expect(composerInput).toHaveProperty( expect(await composerValue(composerInput)).toBe(
'value',
'/asset-register assets/audio/sfx.wav audio audio/wav', '/asset-register assets/audio/sfx.wav audio audio/wav',
); );
expect( expect(
@@ -431,25 +431,25 @@ export async function assertProjectToolsAndPreviewFlow(
name: buttonName, name: buttonName,
}), }),
); );
expect(composerInput).toHaveProperty('value', draftValue); expect(await composerValue(composerInput)).toBe(draftValue);
await waitFor(() => expect(document.activeElement).toBe(composerInput)); await waitFor(() => expect(document.activeElement).toBe(composerInput));
} }
submitChat('/artifacts'); await submitChat('/artifacts');
expect(await screen.findByText(/常用生成产物:/)).not.toBeNull(); expect(await screen.findByText(/常用生成产物:/)).not.toBeNull();
expect(screen.getByText(/读入口 · game\/index\.html/)).not.toBeNull(); expect(screen.getByText(/读入口 · game\/index\.html/)).not.toBeNull();
expect(screen.getByText(/读发布说明 · exports\/README\.md/)).not.toBeNull(); expect(screen.getByText(/读发布说明 · exports\/README\.md/)).not.toBeNull();
const readEntryButtons = screen.getAllByRole('button', { name: '读入口' }); const readEntryButtons = screen.getAllByRole('button', { name: '读入口' });
fireEvent.click(readEntryButtons[readEntryButtons.length - 1]); fireEvent.click(readEntryButtons[readEntryButtons.length - 1]);
expect(composerInput).toHaveProperty('value', '/read game/index.html'); expect(await composerValue(composerInput)).toBe('/read game/index.html');
await waitFor(() => expect(document.activeElement).toBe(composerInput)); await waitFor(() => expect(document.activeElement).toBe(composerInput));
submitChat('/run-artifacts'); await submitChat('/run-artifacts');
const runArtifactsHeading = const runArtifactsHeading =
await screen.findByText('最近 Run 产物读取命令:'); await screen.findByText('最近 Run 产物读取命令:');
expect(messageBubble(runArtifactsHeading).textContent).toContain( expect(messageBubble(runArtifactsHeading).textContent).toContain(
'exports/README.md · 128B · fnv1a64:exports/read exports/README.md', 'exports/README.md · 128B · fnv1a64:exports/read exports/README.md',
); );
fireEvent.click(screen.getByRole('button', { name: '读取首个 Run 产物' })); fireEvent.click(screen.getByRole('button', { name: '读取首个 Run 产物' }));
expect(composerInput).toHaveProperty('value', '/read exports/README.md'); expect(await composerValue(composerInput)).toBe('/read exports/README.md');
expect(invoke).not.toHaveBeenCalledWith( expect(invoke).not.toHaveBeenCalledWith(
'read_local_project_file', 'read_local_project_file',
expect.objectContaining({ expect.objectContaining({
@@ -459,7 +459,7 @@ export async function assertProjectToolsAndPreviewFlow(
const fileReadCountBeforePasses = invoke.mock.calls.filter( const fileReadCountBeforePasses = invoke.mock.calls.filter(
([command]) => command === 'read_local_project_file', ([command]) => command === 'read_local_project_file',
).length; ).length;
submitChat('/passes'); await submitChat('/passes');
const passArtifactMessages = const passArtifactMessages =
await screen.findAllByText(/Agent 轮次产物读取命令:/); await screen.findAllByText(/Agent 轮次产物读取命令:/);
const passArtifactMessage = messageBubble( const passArtifactMessage = messageBubble(
@@ -475,8 +475,7 @@ export async function assertProjectToolsAndPreviewFlow(
name: '读取首个轮次产物', name: '读取首个轮次产物',
}); });
fireEvent.click(passArtifactButtons[passArtifactButtons.length - 1]); fireEvent.click(passArtifactButtons[passArtifactButtons.length - 1]);
expect(composerInput).toHaveProperty( expect(await composerValue(composerInput)).toBe(
'value',
'/read .agent/passes/pass-1/agenda.md', '/read .agent/passes/pass-1/agenda.md',
); );
expect( expect(
@@ -484,7 +483,7 @@ export async function assertProjectToolsAndPreviewFlow(
([command]) => command === 'read_local_project_file', ([command]) => command === 'read_local_project_file',
), ),
).toHaveLength(fileReadCountBeforePasses); ).toHaveLength(fileReadCountBeforePasses);
submitChat('/logs'); await submitChat('/logs');
expect(await screen.findByText(/常用日志读取命令:/)).not.toBeNull(); expect(await screen.findByText(/常用日志读取命令:/)).not.toBeNull();
expect( expect(
screen.getByText(/读命令日志 · \.agent\/logs\/command\.log/), screen.getByText(/读命令日志 · \.agent\/logs\/command\.log/),
@@ -506,8 +505,7 @@ export async function assertProjectToolsAndPreviewFlow(
name: '读命令日志', name: '读命令日志',
}), }),
); );
expect(composerInput).toHaveProperty( expect(await composerValue(composerInput)).toBe(
'value',
'/read .agent/logs/command.log', '/read .agent/logs/command.log',
); );
expect( expect(
@@ -520,30 +518,28 @@ export async function assertProjectToolsAndPreviewFlow(
), ),
).toHaveLength(commandLogReadCountBefore); ).toHaveLength(commandLogReadCountBefore);
fireEvent.click(screen.getByRole('button', { name: '打开画板' })); fireEvent.click(screen.getByRole('button', { name: '打开画板' }));
expect(composerInput).toHaveProperty('value', '/canvas '); expect(await composerValue(composerInput)).toBe('/canvas ');
await waitFor(() => expect(document.activeElement).toBe(composerInput)); await waitFor(() => expect(document.activeElement).toBe(composerInput));
fireEvent.click(screen.getByRole('button', { name: '同步画板' })); fireEvent.click(screen.getByRole('button', { name: '同步画板' }));
expect(composerInput).toHaveProperty('value', '/sync-canvas-project '); expect(await composerValue(composerInput)).toBe('/sync-canvas-project ');
await waitFor(() => expect(document.activeElement).toBe(composerInput)); await waitFor(() => expect(document.activeElement).toBe(composerInput));
fireEvent.click(screen.getByRole('button', { name: '生成美术' })); fireEvent.click(screen.getByRole('button', { name: '生成美术' }));
expect(composerInput).toHaveProperty('value', '/generate-art '); expect(await composerValue(composerInput)).toBe('/generate-art ');
await waitFor(() => expect(document.activeElement).toBe(composerInput)); await waitFor(() => expect(document.activeElement).toBe(composerInput));
fireEvent.click( fireEvent.click(
within(screen.getByLabelText('预览快捷操作')).getByRole('button', { within(screen.getByLabelText('预览快捷操作')).getByRole('button', {
name: '登记音效', name: '登记音效',
}), }),
); );
expect(composerInput).toHaveProperty( expect(await composerValue(composerInput)).toBe(
'value',
'/asset-register assets/audio/sfx.wav audio audio/wav', '/asset-register assets/audio/sfx.wav audio audio/wav',
); );
await waitFor(() => expect(document.activeElement).toBe(composerInput)); await waitFor(() => expect(document.activeElement).toBe(composerInput));
fireEvent.click(screen.getByRole('button', { name: '导入画板资产' })); fireEvent.click(screen.getByRole('button', { name: '导入画板资产' }));
expect(composerInput).toHaveProperty('value', '/import-canvas-asset '); expect(await composerValue(composerInput)).toBe('/import-canvas-asset ');
await waitFor(() => expect(document.activeElement).toBe(composerInput)); await waitFor(() => expect(document.activeElement).toBe(composerInput));
fireEvent.click(screen.getByRole('button', { name: '导入画板音频' })); fireEvent.click(screen.getByRole('button', { name: '导入画板音频' }));
expect(composerInput).toHaveProperty( expect(await composerValue(composerInput)).toBe(
'value',
'/import-canvas-asset assets/audio/sfx.wav ', '/import-canvas-asset assets/audio/sfx.wav ',
); );
await waitFor(() => expect(document.activeElement).toBe(composerInput)); await waitFor(() => expect(document.activeElement).toBe(composerInput));
@@ -560,8 +556,7 @@ export async function assertProjectToolsAndPreviewFlow(
name: '填入读取 assets/uploads/hero.txt', name: '填入读取 assets/uploads/hero.txt',
}), }),
); );
expect(composerInput).toHaveProperty( expect(await composerValue(composerInput)).toBe(
'value',
'/read assets/uploads/hero.txt', '/read assets/uploads/hero.txt',
); );
@@ -581,7 +576,7 @@ export async function assertProjectToolsAndPreviewFlow(
screen.getByText(/exports\/README\.md · fnv1a64:exports/), screen.getByText(/exports\/README\.md · fnv1a64:exports/),
).not.toBeNull(); ).not.toBeNull();
fireEvent.click(screen.getByRole('button', { name: '读取首个产物' })); fireEvent.click(screen.getByRole('button', { name: '读取首个产物' }));
expect(composerInput).toHaveProperty('value', '/read exports/README.md'); expect(await composerValue(composerInput)).toBe('/read exports/README.md');
expect(invoke).not.toHaveBeenCalledWith('read_local_project_file', { expect(invoke).not.toHaveBeenCalledWith('read_local_project_file', {
projectPath: '/tmp/authorized-game', projectPath: '/tmp/authorized-game',
relativePath: 'exports/README.md', relativePath: 'exports/README.md',
@@ -601,14 +596,13 @@ export async function assertProjectToolsAndPreviewFlow(
name: '填入读取 game/index.html', name: '填入读取 game/index.html',
}), }),
); );
expect(composerInput).toHaveProperty('value', '/read game/index.html'); expect(await composerValue(composerInput)).toBe('/read game/index.html');
fireEvent.click( fireEvent.click(
within(screen.getByLabelText('最近项目文件')).getByRole('button', { within(screen.getByLabelText('最近项目文件')).getByRole('button', {
name: '登记资产 game/index.html', name: '登记资产 game/index.html',
}), }),
); );
expect(composerInput).toHaveProperty( expect(await composerValue(composerInput)).toBe(
'value',
'/asset-register game/index.html document text/html', '/asset-register game/index.html document text/html',
); );
expect(invoke).not.toHaveBeenCalledWith( expect(invoke).not.toHaveBeenCalledWith(
@@ -631,11 +625,11 @@ export async function assertProjectToolsAndPreviewFlow(
expect(await screen.findByText(/黑板记忆:/)).not.toBeNull(); expect(await screen.findByText(/黑板记忆:/)).not.toBeNull();
expect(screen.getByText(/跨 agent 共享约束/)).not.toBeNull(); expect(screen.getByText(/跨 agent 共享约束/)).not.toBeNull();
fireEvent.click(screen.getByRole('button', { name: '记到黑板' })); fireEvent.click(screen.getByRole('button', { name: '记到黑板' }));
expect(composerInput).toHaveProperty('value', '/remember blackboard '); expect(await composerValue(composerInput)).toBe('/remember blackboard ');
fireEvent.click(screen.getByRole('button', { name: '覆盖黑板' })); fireEvent.click(screen.getByRole('button', { name: '覆盖黑板' }));
expect(composerInput).toHaveProperty('value', '/memory-set blackboard '); expect(await composerValue(composerInput)).toBe('/memory-set blackboard ');
fireEvent.click(screen.getByRole('button', { name: '清空黑板' })); fireEvent.click(screen.getByRole('button', { name: '清空黑板' }));
expect(composerInput).toHaveProperty('value', '/forget-memory blackboard'); expect(await composerValue(composerInput)).toBe('/forget-memory blackboard');
fireEvent.click(screen.getByRole('button', { name: '快照' })); fireEvent.click(screen.getByRole('button', { name: '快照' }));
expect(await screen.findByText('project.checkpoint')).not.toBeNull(); expect(await screen.findByText('project.checkpoint')).not.toBeNull();
@@ -661,13 +655,13 @@ export async function assertProjectToolsAndPreviewFlow(
name: '填入对比 checkpoint-main', name: '填入对比 checkpoint-main',
}), }),
); );
expect(composerInput).toHaveProperty('value', '/diff checkpoint-main'); expect(await composerValue(composerInput)).toBe('/diff checkpoint-main');
fireEvent.click( fireEvent.click(
within(checkpointList).getByRole('button', { within(checkpointList).getByRole('button', {
name: '填入回滚 checkpoint-main', name: '填入回滚 checkpoint-main',
}), }),
); );
expect(composerInput).toHaveProperty('value', '/restore checkpoint-main'); expect(await composerValue(composerInput)).toBe('/restore checkpoint-main');
fireEvent.click( fireEvent.click(
within(checkpointList).getByRole('button', { within(checkpointList).getByRole('button', {
name: '回滚 checkpoint-main', name: '回滚 checkpoint-main',
@@ -717,12 +711,9 @@ export async function assertProjectToolsAndPreviewFlow(
name: '显示目录', name: '显示目录',
}); });
fireEvent.click(exportRevealButtons[exportRevealButtons.length - 1]); fireEvent.click(exportRevealButtons[exportRevealButtons.length - 1]);
expect(screen.getByLabelText('创作想法')).toHaveProperty( expect(await composerText()).toBe('/open-project');
'value',
'/open-project',
);
submitChat('/export'); await submitChat('/export');
await waitFor(() => await waitFor(() =>
expect(screen.getAllByText('准备导出本地试玩包。').length).toBeGreaterThan( expect(screen.getAllByText('准备导出本地试玩包。').length).toBeGreaterThan(
1, 1,
@@ -733,7 +724,7 @@ export async function assertProjectToolsAndPreviewFlow(
); );
fireEvent.click(screen.getByRole('button', { name: '取消' })); fireEvent.click(screen.getByRole('button', { name: '取消' }));
submitChat('/exports'); await submitChat('/exports');
expect(await screen.findByText(/本地试玩包:/)).not.toBeNull(); expect(await screen.findByText(/本地试玩包:/)).not.toBeNull();
expect( expect(
screen.getByText(/exports\/playtest-package-002\.zip · 2048B/), screen.getByText(/exports\/playtest-package-002\.zip · 2048B/),
@@ -748,10 +739,7 @@ export async function assertProjectToolsAndPreviewFlow(
name: '显示目录', name: '显示目录',
}); });
fireEvent.click(exportListRevealButtons[exportListRevealButtons.length - 1]); fireEvent.click(exportListRevealButtons[exportListRevealButtons.length - 1]);
expect(screen.getByLabelText('创作想法')).toHaveProperty( expect(await composerText()).toBe('/open-project');
'value',
'/open-project',
);
fireEvent.click(screen.getByRole('button', { name: '启动预览' })); fireEvent.click(screen.getByRole('button', { name: '启动预览' }));
expect(await screen.findByText('preview.start')).not.toBeNull(); expect(await screen.findByText('preview.start')).not.toBeNull();
@@ -1,4 +1,5 @@
import { import {
composerText,
createGameCreationAppManifest, createGameCreationAppManifest,
createGameCreationAppSeedTasks, createGameCreationAppSeedTasks,
emptyProjectPolicy, emptyProjectPolicy,
@@ -455,7 +456,7 @@ export function registerProjectRunHistoryTests() {
const runReadCountBeforeRunsCommand = invoke.mock.calls.filter( const runReadCountBeforeRunsCommand = invoke.mock.calls.filter(
([command]) => command === 'read_local_project_file', ([command]) => command === 'read_local_project_file',
).length; ).length;
submitChat('/runs'); await submitChat('/runs');
expect(await screen.findByText(/Run 历史读取命令:/)).not.toBeNull(); expect(await screen.findByText(/Run 历史读取命令:/)).not.toBeNull();
expect( expect(
screen.getByText( screen.getByText(
@@ -463,8 +464,7 @@ export function registerProjectRunHistoryTests() {
), ),
).not.toBeNull(); ).not.toBeNull();
fireEvent.click(screen.getByRole('button', { name: '读取首个历史 Run' })); fireEvent.click(screen.getByRole('button', { name: '读取首个历史 Run' }));
expect(screen.getByLabelText('创作想法')).toHaveProperty( expect(await composerText()).toBe(
'value',
'/read .agent/runs/run-hidden-from-chat.json', '/read .agent/runs/run-hidden-from-chat.json',
); );
expect( expect(
@@ -1,4 +1,5 @@
import { import {
composerText,
createGameCreationAppManifest, createGameCreationAppManifest,
createGameCreationAppSeedTasks, createGameCreationAppSeedTasks,
emptyProjectPolicy, emptyProjectPolicy,
@@ -162,10 +163,7 @@ export function registerProjectRunStatusTests() {
name: '填入读取产物 exports/artifact-1.json', name: '填入读取产物 exports/artifact-1.json',
}), }),
); );
expect(screen.getByLabelText('创作想法')).toHaveProperty( expect(await composerText()).toBe('/read exports/artifact-1.json');
'value',
'/read exports/artifact-1.json',
);
fireEvent.click(screen.getByRole('button', { name: '刷新状态' })); fireEvent.click(screen.getByRole('button', { name: '刷新状态' }));
expect( expect(
@@ -2,6 +2,7 @@ import {
act, act,
agentRuntimeUserInputRequest, agentRuntimeUserInputRequest,
cleanup, cleanup,
composerDisabled,
createGameCreationAppManifest, createGameCreationAppManifest,
createGameCreationAppSeedTasks, createGameCreationAppSeedTasks,
createProjectSupervisorRuntimeHarness, createProjectSupervisorRuntimeHarness,
@@ -40,7 +41,7 @@ export function registerSupervisorRuntimeTests() {
); );
}); });
submitChat('做一个反弹弹幕厨房游戏'); await submitChat('做一个反弹弹幕厨房游戏');
await waitFor(() => { await waitFor(() => {
expect(harness.invoke).toHaveBeenCalledWith( expect(harness.invoke).toHaveBeenCalledWith(
@@ -284,7 +285,7 @@ export function registerSupervisorRuntimeTests() {
await openMainProject(harness.projectPath); await openMainProject(harness.projectPath);
expect(await screen.findByText('项目总控 Agent · 已完成')).not.toBeNull(); expect(await screen.findByText('项目总控 Agent · 已完成')).not.toBeNull();
submitChat('开始真正的新一轮实现'); await submitChat('开始真正的新一轮实现');
await waitFor(() => { await waitFor(() => {
expect( expect(
harness.invoke.mock.calls.filter( harness.invoke.mock.calls.filter(
@@ -429,7 +430,7 @@ export function registerSupervisorRuntimeTests() {
), ),
); );
submitChat('切换为自主构建并完成可玩原型'); await submitChat('切换为自主构建并完成可玩原型');
await waitFor(() => { await waitFor(() => {
expect( expect(
harness.invoke.mock.calls.filter( harness.invoke.mock.calls.filter(
@@ -479,7 +480,7 @@ export function registerSupervisorRuntimeTests() {
), ),
); );
submitChat('补充:先检查已有素材'); await submitChat('补充:先检查已有素材');
await waitFor(() => { await waitFor(() => {
expect(harness.invoke).toHaveBeenCalledWith( expect(harness.invoke).toHaveBeenCalledWith(
@@ -689,9 +690,7 @@ export function registerSupervisorRuntimeTests() {
const card = await screen.findByLabelText('Needs input'); const card = await screen.findByLabelText('Needs input');
expect(screen.getByText('项目总控 Agent · 需要回答')).not.toBeNull(); expect(screen.getByText('项目总控 Agent · 需要回答')).not.toBeNull();
expect( expect(await composerDisabled()).toBe(true);
(screen.getByLabelText('创作想法') as HTMLInputElement).disabled,
).toBe(true);
expect(screen.getByRole('button', { name: '等待回答' })).not.toBeNull(); expect(screen.getByRole('button', { name: '等待回答' })).not.toBeNull();
fireEvent.click(within(card).getByRole('button', { name: /像素风/ })); fireEvent.click(within(card).getByRole('button', { name: /像素风/ }));
fireEvent.click(within(card).getByRole('button', { name: '提交回答' })); fireEvent.click(within(card).getByRole('button', { name: '提交回答' }));
@@ -1110,7 +1109,7 @@ export function registerSupervisorRuntimeTests() {
expect.any(Function), expect.any(Function),
); );
}); });
submitChat('完成本轮总控任务'); await submitChat('完成本轮总控任务');
await waitFor(() => { await waitFor(() => {
expect( expect(
harness.invoke.mock.calls.filter( harness.invoke.mock.calls.filter(
@@ -1533,7 +1532,7 @@ export function registerSupervisorRuntimeTests() {
window.__TAURI__ = { core: { invoke }, event: { listen } }; window.__TAURI__ = { core: { invoke }, event: { listen } };
renderAppAt('/'); renderAppAt('/');
submitChat('/project /tmp/authorized-game'); await submitChat('/project /tmp/authorized-game');
fireEvent.click(screen.getByRole('button', { name: '确认' })); fireEvent.click(screen.getByRole('button', { name: '确认' }));
expect( expect(
await screen.findByText('已设置本地项目:/tmp/authorized-game'), await screen.findByText('已设置本地项目:/tmp/authorized-game'),
@@ -1840,13 +1839,13 @@ export function registerSupervisorRuntimeTests() {
window.__TAURI__ = { core: { invoke } }; window.__TAURI__ = { core: { invoke } };
renderAppAt('/'); renderAppAt('/');
submitChat('/project /tmp/authorized-game'); await submitChat('/project /tmp/authorized-game');
fireEvent.click(screen.getByRole('button', { name: '确认' })); fireEvent.click(screen.getByRole('button', { name: '确认' }));
expect( expect(
await screen.findByText('已设置本地项目:/tmp/authorized-game'), await screen.findByText('已设置本地项目:/tmp/authorized-game'),
).not.toBeNull(); ).not.toBeNull();
submitChat('/generate 做一个反弹弹幕厨房游戏'); await submitChat('/generate 做一个反弹弹幕厨房游戏');
fireEvent.click(screen.getByRole('button', { name: '确认' })); fireEvent.click(screen.getByRole('button', { name: '确认' }));
expect( expect(
@@ -1950,14 +1949,14 @@ export function registerSupervisorRuntimeTests() {
window.__TAURI__ = { core: { invoke } }; window.__TAURI__ = { core: { invoke } };
renderAppAt('/'); renderAppAt('/');
submitChat('/project /tmp/authorized-game'); await submitChat('/project /tmp/authorized-game');
fireEvent.click(screen.getByRole('button', { name: '确认' })); fireEvent.click(screen.getByRole('button', { name: '确认' }));
expect( expect(
await screen.findByText('已设置本地项目:/tmp/authorized-game'), await screen.findByText('已设置本地项目:/tmp/authorized-game'),
).not.toBeNull(); ).not.toBeNull();
invoke.mockClear(); invoke.mockClear();
submitChat('/generate 做一个厨房弹幕游戏'); await submitChat('/generate 做一个厨房弹幕游戏');
fireEvent.click(screen.getByRole('button', { name: '确认' })); fireEvent.click(screen.getByRole('button', { name: '确认' }));
expect(await screen.findByText('生成结果项目路径无效')).not.toBeNull(); expect(await screen.findByText('生成结果项目路径无效')).not.toBeNull();
@@ -2033,13 +2032,13 @@ export function registerSupervisorRuntimeTests() {
window.__TAURI__ = { core: { invoke } }; window.__TAURI__ = { core: { invoke } };
renderAppAt('/'); renderAppAt('/');
submitChat('/project /tmp/authorized-game'); await submitChat('/project /tmp/authorized-game');
fireEvent.click(screen.getByRole('button', { name: '确认' })); fireEvent.click(screen.getByRole('button', { name: '确认' }));
expect( expect(
await screen.findByText('已设置本地项目:/tmp/authorized-game'), await screen.findByText('已设置本地项目:/tmp/authorized-game'),
).not.toBeNull(); ).not.toBeNull();
submitChat('/generate 做一个厨房弹幕游戏'); await submitChat('/generate 做一个厨房弹幕游戏');
fireEvent.click(screen.getByRole('button', { name: '确认' })); fireEvent.click(screen.getByRole('button', { name: '确认' }));
expect( expect(