Merge branch 'fix/agc-test-selector-cleanup' into feat/agc-canvas-resource-workbench-v3
Project CI / Repository checks (pull_request) Failing after 18m28s
Project CI / Native shell tests (pull_request) Successful in 25m45s
Project CI / Frontend tests (pull_request) Successful in 53m14s
Project CI / Backend tests (pull_request) Successful in 57m10s

This commit is contained in:
2026-09-10 11:52:23 +08:00
16 changed files with 798 additions and 678 deletions
@@ -1,4 +1,6 @@
import {
composerText,
composerValue,
createGameCreationAppManifest,
createGameCreationAppSeedTasks,
emptyProjectPolicy,
@@ -9,6 +11,7 @@ import {
it,
renderAppAt,
screen,
setComposerText,
submitChat,
vi,
waitFor,
@@ -167,14 +170,14 @@ export function registerAgentRuntimeCommandTests() {
window.__TAURI__ = { core: { invoke } };
renderAppAt('/');
submitChat('/project /tmp/authorized-game');
await submitChat('/project /tmp/authorized-game');
fireEvent.click(screen.getByRole('button', { name: '确认' }));
expect(
await screen.findByText('已设置本地项目:/tmp/authorized-game'),
).not.toBeNull();
invoke.mockClear();
submitChat('/trace');
await submitChat('/trace');
expect(await screen.findByText(/Runrun-chat-trace/)).not.toBeNull();
expect(
@@ -226,13 +229,13 @@ export function registerAgentRuntimeCommandTests() {
window.__TAURI__ = { core: { invoke } };
renderAppAt('/');
submitChat('/project /tmp/authorized-game');
await submitChat('/project /tmp/authorized-game');
fireEvent.click(screen.getByRole('button', { name: '确认' }));
expect(
await screen.findByText('已设置本地项目:/tmp/authorized-game'),
).not.toBeNull();
submitChat('/trace');
await submitChat('/trace');
expect(
await screen.findByText(
@@ -312,14 +315,14 @@ export function registerAgentRuntimeCommandTests() {
window.__TAURI__ = { core: { invoke } };
renderAppAt('/');
submitChat('/project /tmp/authorized-game');
await submitChat('/project /tmp/authorized-game');
fireEvent.click(screen.getByRole('button', { name: '确认' }));
expect(
await screen.findByText('已设置本地项目:/tmp/authorized-game'),
).not.toBeNull();
invoke.mockClear();
submitChat('/trace');
await submitChat('/trace');
expect(
await screen.findByText('准备读取 Agent run trace。'),
@@ -460,33 +463,24 @@ export function registerAgentRuntimeCommandTests() {
window.__TAURI__ = { core: { invoke } };
renderAppAt('/');
submitChat('/project /tmp/authorized-game');
await submitChat('/project /tmp/authorized-game');
fireEvent.click(screen.getByRole('button', { name: '确认' }));
expect(
await screen.findByText('已设置本地项目:/tmp/authorized-game'),
).not.toBeNull();
fireEvent.click(screen.getByRole('button', { name: '输出' }));
expect(screen.getByLabelText('创作想法')).toHaveProperty(
'value',
'/read .agent/output.jsonl',
);
expect(await composerText()).toBe('/read .agent/output.jsonl');
fireEvent.click(screen.getByRole('button', { name: '活动' }));
expect(screen.getByLabelText('创作想法')).toHaveProperty(
'value',
'/read .agent/activity.jsonl',
);
expect(await composerText()).toBe('/read .agent/activity.jsonl');
fireEvent.click(screen.getByRole('button', { name: '上下文包' }));
expect(screen.getByLabelText('创作想法')).toHaveProperty(
'value',
'/read .agent/context.bundle.json',
);
expect(await composerText()).toBe('/read .agent/context.bundle.json');
expect(invoke).not.toHaveBeenCalledWith(
'read_local_project_file',
expect.objectContaining({ relativePath: '.agent/context.bundle.json' }),
);
submitChat('/agent-status');
await submitChat('/agent-status');
expect(
await screen.findByText(
/run run-control-chat 当前状态:pending \/ pending/,
@@ -494,17 +488,12 @@ export function registerAgentRuntimeCommandTests() {
).not.toBeNull();
expect(screen.getByText(/runrun-control-chat/)).not.toBeNull();
fireEvent.click(screen.getByRole('button', { name: '读取 Run 输出' }));
expect(screen.getByLabelText('创作想法')).toHaveProperty(
'value',
'/read .agent/output.jsonl',
);
expect(await composerText()).toBe('/read .agent/output.jsonl');
expect(invoke).not.toHaveBeenCalledWith(
'read_local_project_file',
expect.objectContaining({ relativePath: '.agent/output.jsonl' }),
);
fireEvent.change(screen.getByLabelText('创作想法'), {
target: { value: '' },
});
await setComposerText(screen.getByLabelText('创作想法'), '');
expect(invoke).toHaveBeenCalledWith('control_agent_run', {
projectPath: '/tmp/authorized-game',
action: 'status',
@@ -516,7 +505,7 @@ export function registerAgentRuntimeCommandTests() {
commandId: 'agent.run_status',
});
submitChat('/agent-kill');
await submitChat('/agent-kill');
expect(screen.getByText('agent.kill')).not.toBeNull();
expect(
screen.getByText(
@@ -533,7 +522,7 @@ export function registerAgentRuntimeCommandTests() {
detail: undefined,
});
submitChat('/agent-retry');
await submitChat('/agent-retry');
expect(screen.getByText('agent.retry')).not.toBeNull();
expect(
screen.getByText(
@@ -552,7 +541,7 @@ export function registerAgentRuntimeCommandTests() {
detail: undefined,
});
submitChat('/agent-resume 继续修复输入监听');
await submitChat('/agent-resume 继续修复输入监听');
expect(screen.getByText('agent.resume')).not.toBeNull();
expect(
screen.getByText(
@@ -604,13 +593,13 @@ export function registerAgentRuntimeCommandTests() {
window.__TAURI__ = { core: { invoke } };
renderAppAt('/');
submitChat('/project /tmp/authorized-game');
await submitChat('/project /tmp/authorized-game');
fireEvent.click(screen.getByRole('button', { name: '确认' }));
expect(
await screen.findByText('已设置本地项目:/tmp/authorized-game'),
).not.toBeNull();
submitChat('/agent-status');
await submitChat('/agent-status');
expect(
await screen.findByText(
@@ -654,13 +643,13 @@ export function registerAgentRuntimeCommandTests() {
window.__TAURI__ = { core: { invoke } };
renderAppAt('/');
submitChat('/project /tmp/authorized-game');
await submitChat('/project /tmp/authorized-game');
fireEvent.click(screen.getByRole('button', { name: '确认' }));
expect(
await screen.findByText('已设置本地项目:/tmp/authorized-game'),
).not.toBeNull();
submitChat('/agent-kill');
await submitChat('/agent-kill');
fireEvent.click(screen.getByRole('button', { name: '确认' }));
expect(
@@ -709,13 +698,13 @@ export function registerAgentRuntimeCommandTests() {
window.__TAURI__ = { core: { invoke } };
renderAppAt('/');
submitChat('/project /tmp/authorized-game');
await submitChat('/project /tmp/authorized-game');
fireEvent.click(screen.getByRole('button', { name: '确认' }));
expect(
await screen.findByText('已设置本地项目:/tmp/authorized-game'),
).not.toBeNull();
submitChat('/agent-kill');
await submitChat('/agent-kill');
expect(screen.getByText('agent.kill')).not.toBeNull();
fireEvent.click(screen.getByRole('button', { name: '确认' }));
@@ -817,14 +806,14 @@ export function registerAgentRuntimeCommandTests() {
window.__TAURI__ = { core: { invoke } };
renderAppAt('/');
submitChat('/project /tmp/authorized-game');
await submitChat('/project /tmp/authorized-game');
fireEvent.click(screen.getByRole('button', { name: '确认' }));
expect(
await screen.findByText('已设置本地项目:/tmp/authorized-game'),
).not.toBeNull();
invoke.mockClear();
submitChat('/agent-kill');
await submitChat('/agent-kill');
fireEvent.click(screen.getByRole('button', { name: '确认' }));
expect(await screen.findByText('准备执行 Agent run 操作。')).not.toBeNull();
@@ -927,14 +916,14 @@ export function registerAgentRuntimeCommandTests() {
window.__TAURI__ = { core: { invoke } };
renderAppAt('/');
submitChat('/project /tmp/authorized-game');
await submitChat('/project /tmp/authorized-game');
fireEvent.click(screen.getByRole('button', { name: '确认' }));
expect(
await screen.findByText('已设置本地项目:/tmp/authorized-game'),
).not.toBeNull();
invoke.mockClear();
submitChat('/agent-status');
await submitChat('/agent-status');
expect(await screen.findByText('准备查看 Agent run 状态。')).not.toBeNull();
expect(screen.getByText('agent.run_status')).not.toBeNull();
@@ -993,13 +982,13 @@ export function registerAgentRuntimeCommandTests() {
window.__TAURI__ = { core: { invoke } };
renderAppAt('/');
submitChat('/project /tmp/authorized-game');
await submitChat('/project /tmp/authorized-game');
fireEvent.click(screen.getByRole('button', { name: '确认' }));
expect(
await screen.findByText('已设置本地项目:/tmp/authorized-game'),
).not.toBeNull();
submitChat('/agent-status');
await submitChat('/agent-status');
const agentStatusCommand = await screen.findByText('agent.run_status');
fireEvent.click(
@@ -1044,13 +1033,13 @@ export function registerAgentRuntimeCommandTests() {
window.__TAURI__ = { core: { invoke } };
renderAppAt('/');
submitChat('/project /tmp/authorized-game');
await submitChat('/project /tmp/authorized-game');
fireEvent.click(screen.getByRole('button', { name: '确认' }));
expect(
await screen.findByText('已设置本地项目:/tmp/authorized-game'),
).not.toBeNull();
submitChat('/agent-kill');
await submitChat('/agent-kill');
const agentCommand = screen.getByText('agent.kill');
fireEvent.click(
within(agentCommand.closest('.pending-command') as HTMLElement).getByRole(
@@ -1150,14 +1139,14 @@ export function registerAgentRuntimeCommandTests() {
window.__TAURI__ = { core: { invoke } };
renderAppAt('/');
submitChat('/project /tmp/authorized-game');
await submitChat('/project /tmp/authorized-game');
fireEvent.click(screen.getByRole('button', { name: '确认' }));
expect(
await screen.findByText('已设置本地项目:/tmp/authorized-game'),
).not.toBeNull();
invoke.mockClear();
submitChat('/agent-status');
await submitChat('/agent-status');
expect(
await screen.findByText(
@@ -1264,7 +1253,7 @@ export function registerAgentRuntimeCommandTests() {
window.__TAURI__ = { core: { invoke } };
renderAppAt('/');
submitChat('/project /tmp/authorized-game');
await submitChat('/project /tmp/authorized-game');
fireEvent.click(screen.getByRole('button', { name: '确认' }));
expect(
await screen.findByText('已设置本地项目:/tmp/authorized-game'),
@@ -1314,7 +1303,7 @@ export function registerAgentRuntimeCommandTests() {
});
const composerInput = screen.getByLabelText('创作想法');
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 () => {
@@ -1399,7 +1388,7 @@ export function registerAgentRuntimeCommandTests() {
window.__TAURI__ = { core: { invoke } };
renderAppAt('/');
submitChat('/project /tmp/authorized-game');
await submitChat('/project /tmp/authorized-game');
fireEvent.click(screen.getByRole('button', { name: '确认' }));
expect(
await screen.findByText('已设置本地项目:/tmp/authorized-game'),
@@ -1507,7 +1496,7 @@ export function registerAgentRuntimeCommandTests() {
window.__TAURI__ = { core: { invoke } };
renderAppAt('/');
submitChat('/project /tmp/authorized-game');
await submitChat('/project /tmp/authorized-game');
fireEvent.click(screen.getByRole('button', { name: '确认' }));
expect(
await screen.findByText('已设置本地项目:/tmp/authorized-game'),
@@ -1595,16 +1584,16 @@ export function registerAgentRuntimeCommandTests() {
window.__TAURI__ = { core: { invoke } };
renderAppAt('/');
submitChat('/project /tmp/authorized-game');
await submitChat('/project /tmp/authorized-game');
fireEvent.click(screen.getByRole('button', { name: '确认' }));
expect(
await screen.findByText('已设置本地项目:/tmp/authorized-game'),
).not.toBeNull();
submitChat('/memory');
await submitChat('/memory');
expect(await screen.findByText(/长期记忆:/)).not.toBeNull();
submitChat('/remember long 保留厨房主题');
await submitChat('/remember long 保留厨房主题');
fireEvent.click(screen.getByRole('button', { name: '确认' }));
expect(await screen.findByText('已追加长期记忆。')).not.toBeNull();
expect(invoke).toHaveBeenCalledWith('read_local_game_memory', {
@@ -1617,7 +1606,7 @@ export function registerAgentRuntimeCommandTests() {
content: '# 项目长期记忆\n- 保留厨房主题\n',
});
submitChat('/memory-set long 覆盖后的长期记忆');
await submitChat('/memory-set long 覆盖后的长期记忆');
fireEvent.click(screen.getByRole('button', { name: '确认' }));
expect(await screen.findByText('已保存长期记忆。')).not.toBeNull();
expect(invoke).toHaveBeenCalledWith('write_local_game_memory', {
@@ -1626,7 +1615,7 @@ export function registerAgentRuntimeCommandTests() {
content: '覆盖后的长期记忆',
});
submitChat('/forget-memory');
await submitChat('/forget-memory');
fireEvent.click(screen.getByRole('button', { name: '确认' }));
expect(await screen.findByText('已删除长期记忆。')).not.toBeNull();
expect(invoke).toHaveBeenCalledWith('delete_local_game_memory', {
@@ -1676,14 +1665,14 @@ export function registerAgentRuntimeCommandTests() {
window.__TAURI__ = { core: { invoke } };
renderAppAt('/');
submitChat('/project /tmp/authorized-game');
await submitChat('/project /tmp/authorized-game');
fireEvent.click(screen.getByRole('button', { name: '确认' }));
expect(
await screen.findByText('已设置本地项目:/tmp/authorized-game'),
).not.toBeNull();
invoke.mockClear();
submitChat('/memory');
await submitChat('/memory');
expect(await screen.findByText('准备读取项目记忆。')).not.toBeNull();
expect(screen.getByText('memory.read')).not.toBeNull();
@@ -1737,14 +1726,14 @@ export function registerAgentRuntimeCommandTests() {
window.__TAURI__ = { core: { invoke } };
renderAppAt('/');
submitChat('/project /tmp/authorized-game');
await submitChat('/project /tmp/authorized-game');
fireEvent.click(screen.getByRole('button', { name: '确认' }));
expect(
await screen.findByText('已设置本地项目:/tmp/authorized-game'),
).not.toBeNull();
invoke.mockClear();
submitChat('/memory');
await submitChat('/memory');
expect(await screen.findByText('准备读取项目记忆。')).not.toBeNull();
const memoryReadCommand = screen.getByText('memory.read');
@@ -1822,16 +1811,16 @@ export function registerAgentRuntimeCommandTests() {
window.__TAURI__ = { core: { invoke } };
renderAppAt('/');
submitChat('/project /tmp/authorized-game');
await submitChat('/project /tmp/authorized-game');
fireEvent.click(screen.getByRole('button', { name: '确认' }));
expect(await screen.findByText('已打开:authorized-game')).not.toBeNull();
submitChat('/memory typo');
await submitChat('/memory typo');
expect(
await screen.findByText('格式:/memory [short|long|blackboard]'),
).not.toBeNull();
submitChat('/forget-memory typo');
await submitChat('/forget-memory typo');
expect(
await screen.findByText('格式:/forget-memory [short|long|blackboard]'),
).not.toBeNull();
@@ -1901,16 +1890,16 @@ export function registerAgentRuntimeCommandTests() {
window.__TAURI__ = { core: { invoke } };
renderAppAt('/');
submitChat('/project /tmp/authorized-game');
await submitChat('/project /tmp/authorized-game');
fireEvent.click(screen.getByRole('button', { name: '确认' }));
expect(
await screen.findByText('已设置本地项目:/tmp/authorized-game'),
).not.toBeNull();
submitChat('/memory blackboard');
await submitChat('/memory blackboard');
expect(await screen.findByText(/黑板记忆:/)).not.toBeNull();
submitChat('/remember blackboard 共享美术约束');
await submitChat('/remember blackboard 共享美术约束');
fireEvent.click(screen.getByRole('button', { name: '确认' }));
expect(await screen.findByText('已追加黑板记忆。')).not.toBeNull();
expect(invoke).toHaveBeenCalledWith('write_local_game_memory', {
@@ -1919,7 +1908,7 @@ export function registerAgentRuntimeCommandTests() {
content: '# 项目黑板\n- 共享美术约束\n',
});
submitChat('/memory-set 黑板 统一使用俯视角');
await submitChat('/memory-set 黑板 统一使用俯视角');
fireEvent.click(screen.getByRole('button', { name: '确认' }));
expect(await screen.findByText('已保存黑板记忆。')).not.toBeNull();
expect(invoke).toHaveBeenCalledWith('write_local_game_memory', {
@@ -1928,7 +1917,7 @@ export function registerAgentRuntimeCommandTests() {
content: '统一使用俯视角',
});
submitChat('/forget-memory blackboard');
await submitChat('/forget-memory blackboard');
fireEvent.click(screen.getByRole('button', { name: '确认' }));
expect(await screen.findByText('已删除黑板记忆。')).not.toBeNull();
expect(invoke).toHaveBeenCalledWith('delete_local_game_memory', {
@@ -4931,10 +4931,10 @@ export function registerDeveloperToolsTests() {
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('/');
submitChat('/capabilities');
await submitChat('/capabilities');
const capabilities = screen.getByText(/Agent 能力清单:/);
expect(capabilities.textContent).toMatch(/任务拆分/);
@@ -4966,7 +4966,7 @@ export function registerDeveloperToolsTests() {
window.__TAURI__ = { core: { invoke } };
renderAppAt('/');
submitChat('/capabilities');
await submitChat('/capabilities');
expect(await screen.findByText(/Native Runtime 能力/)).not.toBeNull();
expect(screen.queryByText(/任务拆分/)).toBeNull();
@@ -4983,17 +4983,17 @@ export function registerDeveloperToolsTests() {
window.__TAURI__ = { core: { invoke } };
renderAppAt('/');
submitChat('/capabilities');
await submitChat('/capabilities');
expect(await screen.findByText(/任务拆分/)).not.toBeNull();
expect(screen.getByText(/本地 HTTP 预览/)).not.toBeNull();
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('/');
submitChat('/help');
await submitChat('/help');
expect(
screen.getByText(/\/audit:审计当前项目的 Agent 能力证据/),
@@ -5472,11 +5472,11 @@ export function registerDeveloperToolsTests() {
window.__TAURI__ = { core: { invoke } };
renderAppAt('/');
submitChat('/project /tmp/authorized-game');
await submitChat('/project /tmp/authorized-game');
fireEvent.click(screen.getByRole('button', { name: '确认' }));
expect(await screen.findByText('已打开:authorized-game')).not.toBeNull();
submitChat('/audit');
await submitChat('/audit');
expect(await screen.findByText(/Agent v1 审计:/)).not.toBeNull();
expect(screen.getByText(/能力\/命令契约:通过/)).not.toBeNull();
@@ -5598,12 +5598,12 @@ export function registerDeveloperToolsTests() {
window.__TAURI__ = { core: { invoke } };
renderAppAt('/');
submitChat('/project /tmp/authorized-game');
await submitChat('/project /tmp/authorized-game');
fireEvent.click(screen.getByRole('button', { name: '确认' }));
expect(await screen.findByText('已打开:authorized-game')).not.toBeNull();
invoke.mockClear();
submitChat('/audit');
await submitChat('/audit');
expect(
await screen.findByText('准备确认审计读取:agent.trace_read'),
@@ -5679,12 +5679,12 @@ export function registerDeveloperToolsTests() {
window.__TAURI__ = { core: { invoke } };
renderAppAt('/');
submitChat('/project /tmp/authorized-game');
await submitChat('/project /tmp/authorized-game');
fireEvent.click(screen.getByRole('button', { name: '确认' }));
expect(await screen.findByText('已打开:authorized-game')).not.toBeNull();
invoke.mockClear();
submitChat('/audit');
await submitChat('/audit');
expect(
await screen.findByText('准备确认审计读取:file.read'),
@@ -5791,12 +5791,12 @@ export function registerDeveloperToolsTests() {
window.__TAURI__ = { core: { invoke } };
renderAppAt('/');
submitChat('/project /tmp/authorized-game');
await submitChat('/project /tmp/authorized-game');
fireEvent.click(screen.getByRole('button', { name: '确认' }));
expect(await screen.findByText('已打开:authorized-game')).not.toBeNull();
invoke.mockClear();
submitChat('/audit');
await submitChat('/audit');
expect(
await screen.findByText('准备确认审计读取:file.read'),
@@ -5887,11 +5887,11 @@ export function registerDeveloperToolsTests() {
window.__TAURI__ = { core: { invoke } };
renderAppAt('/');
submitChat('/project /tmp/authorized-game');
await submitChat('/project /tmp/authorized-game');
fireEvent.click(screen.getByRole('button', { name: '确认' }));
expect(await screen.findByText('已打开:authorized-game')).not.toBeNull();
submitChat('/audit');
await submitChat('/audit');
expect(await screen.findByText(/权限 Gate\/命令日志:待补/)).not.toBeNull();
expect(
@@ -5947,11 +5947,11 @@ export function registerDeveloperToolsTests() {
window.__TAURI__ = { core: { invoke } };
renderAppAt('/');
submitChat('/project /tmp/authorized-game');
await submitChat('/project /tmp/authorized-game');
fireEvent.click(screen.getByRole('button', { name: '确认' }));
expect(await screen.findByText('已打开:authorized-game')).not.toBeNull();
submitChat('/audit');
await submitChat('/audit');
expect(await screen.findByText(/权限 Gate\/命令日志:通过/)).not.toBeNull();
expect(screen.getByText(/含 auto 权限记录/)).not.toBeNull();
@@ -5996,11 +5996,11 @@ export function registerDeveloperToolsTests() {
window.__TAURI__ = { core: { invoke } };
renderAppAt('/');
submitChat('/project /tmp/authorized-game');
await submitChat('/project /tmp/authorized-game');
fireEvent.click(screen.getByRole('button', { name: '确认' }));
expect(await screen.findByText('已打开:authorized-game')).not.toBeNull();
submitChat('/audit');
await submitChat('/audit');
expect(await screen.findByText(/6 组任务配置:通过/)).not.toBeNull();
expect(screen.getByText(/6 组协作证据:待生成/)).not.toBeNull();
@@ -35,6 +35,15 @@ const originalPointerEvent = Object.getOwnPropertyDescriptor(
window,
'PointerEvent',
);
const originalClipboardEvent = Object.getOwnPropertyDescriptor(
window,
'ClipboardEvent',
);
const originalDragEvent = Object.getOwnPropertyDescriptor(window, 'DragEvent');
const originalRangeGetBoundingClientRect = Object.getOwnPropertyDescriptor(
Range.prototype,
'getBoundingClientRect',
);
const originalIntersectionObserver = Object.getOwnPropertyDescriptor(
window,
'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 {
const values = new Map<string, string>();
return {
@@ -192,13 +224,146 @@ function pickProjectFromLauncher(
fireEvent.click(screen.getByRole('button', { name: action }));
}
function submitChat(value: string) {
fireEvent.change(screen.getByLabelText('创作想法'), {
target: { value },
function isNativeTextControl(
element: HTMLElement,
): 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: '发送' }));
}
// 同一张资源卡上有两个按钮:打开详情与 V3 新增的 @ 引用按钮,二者的可访问名都包含
// 资源文件名,因此按文件名正则查询会同时命中两个元素。资源详情按钮的可访问名模板固定为
// `打开资源详情:<分类标签> <资源文件名>`(分类标签内不含空格),这里按模板做精确匹配。
function escapeRegExpLiteral(value: string) {
return value.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
}
function resourceDetailButtonName(label: string) {
return new RegExp(`^打开资源详情:\\S+ ${escapeRegExpLiteral(label)}$`);
}
function getResourceDetailButton(label: string) {
return screen.getByRole('button', { name: resourceDetailButtonName(label) });
}
function findResourceDetailButton(
label: string,
options?: { timeout?: number; interval?: number },
) {
return screen.findByRole(
'button',
{ name: resourceDetailButtonName(label) },
options,
);
}
function queryResourceDetailButton(label: string) {
return screen.queryByRole('button', {
name: resourceDetailButtonName(label),
});
}
function emptyProjectPolicy() {
return {
path: '.agent/policy.json',
@@ -1004,7 +1169,7 @@ function createProjectSupervisorRuntimeHarness({
}
async function openMainProject(projectPath: string) {
submitChat(`/project ${projectPath}`);
await submitChat(`/project ${projectPath}`);
fireEvent.click(screen.getByRole('button', { name: '确认' }));
expect(
await screen.findByText(`已打开:${projectNameFromPath(projectPath)}`),
@@ -1025,6 +1190,18 @@ beforeEach(() => {
configurable: true,
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, {
createObjectURL: {
configurable: true,
@@ -1065,6 +1242,25 @@ afterEach(() => {
} else {
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) {
Object.defineProperty(
window,
@@ -1086,6 +1282,9 @@ export {
App,
AuthenticatedClient,
cleanup,
composerDisabled,
composerText,
composerValue,
createGameCreationAppManifest,
createGameCreationAppSeedTasks,
createPlanGddStateView,
@@ -1094,8 +1293,10 @@ export {
describe,
emptyProjectPolicy,
expect,
findResourceDetailButton,
fireEvent,
GAME_CREATION_AGENT_RUN_SCHEMA_VERSION,
getResourceDetailButton,
it,
mockRoleAgentReply,
nativeClipboardMock,
@@ -1103,6 +1304,7 @@ export {
pickProjectFromLauncher,
ProjectDevelopmentView,
projectSupervisorResponseStream,
queryResourceDetailButton,
React,
readFileSync,
render,
@@ -1114,6 +1316,7 @@ export {
roleAgentMockReply,
screen,
selectDeveloperAgentChatMode,
setComposerText,
submitChat,
TEST_LOCAL_PROJECT_PATH,
testAuthUser,
@@ -9,10 +9,13 @@ import {
createGameCreationAppManifest,
createProjectSupervisorRuntimeHarness,
expect,
findResourceDetailButton,
fireEvent,
getResourceDetailButton,
it,
nativeClipboardMock,
pickProjectFromLauncher,
queryResourceDetailButton,
React,
render,
renderAppAt,
@@ -21,6 +24,7 @@ import {
renderLauncherProjectsAt,
screen,
selectDeveloperAgentChatMode,
setComposerText,
testAuthUser,
vi,
waitFor,
@@ -312,14 +316,12 @@ export function registerClientHomeTests() {
pickProjectFromLauncher(projectPath);
const runButton = await screen.findByRole('tab', { name: '运行' });
expect(runButton.getAttribute('data-unavailable')).toBe('true');
expect(screen.queryByRole('button', { name: /live-hero\.png/ })).toBeNull();
expect(queryResourceDetailButton('live-hero.png')).toBeNull();
fireEvent.click(screen.getByRole('button', { name: '同步最新 manifest' }));
await openResourceBookCategory('美术资源');
expect(
await screen.findByRole('button', { name: /live-hero\.png/ }),
).not.toBeNull();
expect(await findResourceDetailButton('live-hero.png')).not.toBeNull();
await openResourceBookCategory('项目版本');
expect(
await screen.findByRole('button', { name: /版本 1/ }),
@@ -460,9 +462,7 @@ export function registerClientHomeTests() {
pickProjectFromLauncher(projectPath);
const runButton = await screen.findByRole('tab', { name: '运行' });
expect(runButton.getAttribute('data-unavailable')).toBe('true');
expect(
screen.queryByRole('button', { name: /runtime-live-hero\.png/ }),
).toBeNull();
expect(queryResourceDetailButton('runtime-live-hero.png')).toBeNull();
await waitFor(() => {
expect(runtimeHarness.listen).toHaveBeenCalledWith(
'game-creator-manifest-invalidated',
@@ -488,11 +488,9 @@ export function registerClientHomeTests() {
await openResourceBookCategory('美术资源');
expect(
await screen.findByRole(
'button',
{ name: /runtime-live-hero\.png/ },
{ timeout: 5_000 },
),
await findResourceDetailButton('runtime-live-hero.png', {
timeout: 5_000,
}),
).not.toBeNull();
await openResourceBookCategory('项目版本');
expect(
@@ -665,18 +663,14 @@ export function registerClientHomeTests() {
fireEvent.click(screen.getByRole('button', { name: '项目组' }));
pickProjectFromLauncher(secondProjectPath);
await openResourceBookCategory('美术资源');
expect(
await screen.findByRole('button', { name: /second\.png/ }),
).not.toBeNull();
expect(await findResourceDetailButton('second.png')).not.toBeNull();
await act(async () => {
resolveStaleRefresh(staleFirstManifest);
await staleRefresh;
});
expect(
screen.queryByRole('button', { name: /stale-first\.png/ }),
).toBeNull();
expect(screen.getByRole('button', { name: /second\.png/ })).not.toBeNull();
expect(queryResourceDetailButton('stale-first.png')).toBeNull();
expect(getResourceDetailButton('second.png')).not.toBeNull();
});
it('starts from the client home and opens a project in the same window', async () => {
@@ -1664,7 +1658,7 @@ export function registerHomeProjectCreationTests() {
expect(await screen.findByText('附件已经进入当前项目。')).not.toBeNull();
const followUpInput = screen.getByLabelText('陶泥儿对话内容');
fireEvent.change(followUpInput, { target: { value: '再补一句玩法' } });
await setComposerText(followUpInput, '再补一句玩法');
fireEvent.submit(followUpInput.closest('form') as HTMLFormElement);
await waitFor(() => {
expect(
@@ -1,4 +1,5 @@
import {
composerText,
createGameCreationAppManifest,
emptyProjectPolicy,
expect,
@@ -64,7 +65,7 @@ export function registerProjectAssetTests() {
window.__TAURI__ = { core: { invoke } };
renderAppAt('/');
submitChat('/project /tmp/authorized-game');
await submitChat('/project /tmp/authorized-game');
fireEvent.click(screen.getByRole('button', { name: '确认' }));
expect(
await screen.findByText('已设置本地项目:/tmp/authorized-game'),
@@ -92,15 +93,12 @@ export function registerProjectAssetTests() {
bytes: [104, 101, 114, 111],
});
submitChat('/assets');
await submitChat('/assets');
expect(
await screen.findByText(/upload · assets\/uploads\/hero\.png · uploaded/),
).not.toBeNull();
fireEvent.click(screen.getByRole('button', { name: '读取首个资产' }));
expect(screen.getByLabelText('创作想法')).toHaveProperty(
'value',
'/read assets/uploads/hero.png',
);
expect(await composerText()).toBe('/read assets/uploads/hero.png');
expect(invoke).toHaveBeenCalledWith('get_local_game_manifest', {
projectPath: '/tmp/authorized-game',
commandId: 'asset.list',
@@ -178,14 +176,14 @@ export function registerProjectAssetTests() {
window.__TAURI__ = { core: { invoke } };
renderAppAt('/');
submitChat('/project /tmp/authorized-game');
await submitChat('/project /tmp/authorized-game');
fireEvent.click(screen.getByRole('button', { name: '确认' }));
expect(
await screen.findByText('已设置本地项目:/tmp/authorized-game'),
).not.toBeNull();
invoke.mockClear();
submitChat('/audio');
await submitChat('/audio');
expect(await screen.findByText(/音频素材:3 个/)).not.toBeNull();
expect(screen.getByText(/来源:上传 1、生成 1、画板 1/)).not.toBeNull();
@@ -208,10 +206,7 @@ export function registerProjectAssetTests() {
fireEvent.click(
audioManifestDraftButtons[audioManifestDraftButtons.length - 1],
);
expect(screen.getByLabelText('创作想法')).toHaveProperty(
'value',
'/read assets/manifest.audio.json',
);
expect(await composerText()).toBe('/read assets/manifest.audio.json');
expect(invoke).not.toHaveBeenCalledWith(
'read_local_project_file',
expect.anything(),
@@ -298,14 +293,14 @@ export function registerProjectAssetTests() {
window.__TAURI__ = { core: { invoke } };
renderAppAt('/');
submitChat('/project /tmp/authorized-game');
await submitChat('/project /tmp/authorized-game');
fireEvent.click(screen.getByRole('button', { name: '确认' }));
expect(
await screen.findByText('已设置本地项目:/tmp/authorized-game'),
).not.toBeNull();
invoke.mockClear();
submitChat('/art');
await submitChat('/art');
expect(await screen.findByText(/美术素材:2 个/)).not.toBeNull();
expect(screen.getByText(/来源:生成 1、画板 1/)).not.toBeNull();
@@ -324,10 +319,7 @@ export function registerProjectAssetTests() {
fireEvent.click(
artManifestDraftButtons[artManifestDraftButtons.length - 1],
);
expect(screen.getByLabelText('创作想法')).toHaveProperty(
'value',
'/read assets/manifest.art.json',
);
expect(await composerText()).toBe('/read assets/manifest.art.json');
expect(invoke).not.toHaveBeenCalledWith(
'read_local_project_file',
expect.anything(),
@@ -397,14 +389,14 @@ export function registerProjectAssetTests() {
window.__TAURI__ = { core: { invoke } };
renderAppAt('/');
submitChat('/project /tmp/authorized-game');
await submitChat('/project /tmp/authorized-game');
fireEvent.click(screen.getByRole('button', { name: '确认' }));
expect(
await screen.findByText('已设置本地项目:/tmp/authorized-game'),
).not.toBeNull();
invoke.mockClear();
submitChat('/asset-register assets/hero.png sprite image/png');
await submitChat('/asset-register assets/hero.png sprite image/png');
expect(
await screen.findByText('asset.register · assets/hero.png'),
@@ -474,13 +466,13 @@ export function registerProjectAssetTests() {
window.__TAURI__ = { core: { invoke } };
renderAppAt('/');
submitChat('/project /tmp/authorized-game');
await submitChat('/project /tmp/authorized-game');
fireEvent.click(screen.getByRole('button', { name: '确认' }));
expect(
await screen.findByText('已设置本地项目:/tmp/authorized-game'),
).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');
fireEvent.click(
within(command.closest('.pending-command') as HTMLElement).getByRole(
@@ -548,14 +540,14 @@ export function registerProjectAssetTests() {
window.__TAURI__ = { core: { invoke } };
renderAppAt('/');
submitChat('/project /tmp/authorized-game');
await submitChat('/project /tmp/authorized-game');
fireEvent.click(screen.getByRole('button', { name: '确认' }));
expect(
await screen.findByText('已设置本地项目:/tmp/authorized-game'),
).not.toBeNull();
invoke.mockClear();
submitChat('/asset-register assets/hero.png sprite image/png');
await submitChat('/asset-register assets/hero.png sprite image/png');
expect(
await screen.findByText('准备登记项目资产:assets/hero.png'),
@@ -618,13 +610,13 @@ export function registerProjectAssetTests() {
window.__TAURI__ = { core: { invoke } };
renderAppAt('/');
submitChat('/project /tmp/authorized-game');
await submitChat('/project /tmp/authorized-game');
fireEvent.click(screen.getByRole('button', { name: '确认' }));
expect(
await screen.findByText('已设置本地项目:/tmp/authorized-game'),
).not.toBeNull();
submitChat('/asset-register ../hero.png');
await submitChat('/asset-register ../hero.png');
expect(
await screen.findByText('资产路径必须是项目内相对路径。'),
@@ -805,13 +797,13 @@ export function registerProjectAssetTests() {
window.__TAURI__ = { core: { invoke } };
renderAppAt('/');
submitChat('/project /tmp/authorized-game');
await submitChat('/project /tmp/authorized-game');
fireEvent.click(screen.getByRole('button', { name: '确认' }));
expect(
await screen.findByText('已设置本地项目:/tmp/authorized-game'),
).not.toBeNull();
submitChat('/assets');
await submitChat('/assets');
expect(await screen.findByText(/本地项目资产:/)).not.toBeNull();
expect(screen.getByText(/asset-20\.png/)).not.toBeNull();
@@ -855,13 +847,13 @@ export function registerProjectAssetTests() {
window.__TAURI__ = { core: { invoke } };
renderAppAt('/');
submitChat('/project /tmp/authorized-game');
await submitChat('/project /tmp/authorized-game');
fireEvent.click(screen.getByRole('button', { name: '确认' }));
expect(
await screen.findByText('已设置本地项目:/tmp/authorized-game'),
).not.toBeNull();
submitChat('/assets');
await submitChat('/assets');
const assetCommand = await screen.findByText('asset.list');
fireEvent.click(
@@ -914,13 +906,13 @@ export function registerProjectAssetTests() {
window.__TAURI__ = { core: { invoke } };
renderAppAt('/');
submitChat('/project /tmp/authorized-game');
await submitChat('/project /tmp/authorized-game');
fireEvent.click(screen.getByRole('button', { name: '确认' }));
expect(
await screen.findByText('已设置本地项目:/tmp/authorized-game'),
).not.toBeNull();
submitChat('/read game/index.html');
await submitChat('/read game/index.html');
expect(await screen.findByText(/文件:game\/index\.html/)).not.toBeNull();
const fileContent = screen.getByText('<canvas id="game"></canvas>');
@@ -975,14 +967,14 @@ export function registerProjectAssetTests() {
window.__TAURI__ = { core: { invoke } };
renderAppAt('/');
submitChat('/project /tmp/authorized-game');
await submitChat('/project /tmp/authorized-game');
fireEvent.click(screen.getByRole('button', { name: '确认' }));
expect(
await screen.findByText('已设置本地项目:/tmp/authorized-game'),
).not.toBeNull();
invoke.mockClear();
submitChat('/read game/index.html');
await submitChat('/read game/index.html');
expect(await screen.findByText('准备读取项目文件。')).not.toBeNull();
expect(screen.getByText('file.read')).not.toBeNull();
@@ -1041,16 +1033,16 @@ export function registerProjectAssetTests() {
window.__TAURI__ = { core: { invoke } };
renderAppAt('/');
submitChat('/project /tmp/authorized-game');
await submitChat('/project /tmp/authorized-game');
fireEvent.click(screen.getByRole('button', { name: '确认' }));
expect(
await screen.findByText('已设置本地项目:/tmp/authorized-game'),
).not.toBeNull();
submitChat('/read ../secret.txt');
await submitChat('/read ../secret.txt');
expect(screen.getByText('文件路径必须是项目内相对路径。')).not.toBeNull();
submitChat('/read /tmp/secret.txt');
await submitChat('/read /tmp/secret.txt');
expect(screen.getAllByText('文件路径必须是项目内相对路径。').length).toBe(
2,
);
@@ -1104,13 +1096,13 @@ export function registerProjectAssetTests() {
window.__TAURI__ = { core: { invoke } };
renderAppAt('/');
submitChat('/project /tmp/authorized-game');
await submitChat('/project /tmp/authorized-game');
fireEvent.click(screen.getByRole('button', { name: '确认' }));
expect(
await screen.findByText('已设置本地项目:/tmp/authorized-game'),
).not.toBeNull();
submitChat('/tasks');
await submitChat('/tasks');
expect(await screen.findByText(/任务拆分:/)).not.toBeNull();
expect(
@@ -1193,11 +1185,11 @@ export function registerCanvasAssetTests() {
window.__TAURI__ = { core: { invoke } };
renderAppAt('/');
submitChat('/project /tmp/authorized-game');
await submitChat('/project /tmp/authorized-game');
fireEvent.click(screen.getByRole('button', { name: '确认' }));
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',
);
expect(
@@ -1647,8 +1639,7 @@ export function registerCanvasAssetTests() {
expect(
await screen.findByText('已选择画板导出包:/tmp/canvas-export.zip'),
).not.toBeNull();
expect(screen.getByLabelText('创作想法')).toHaveProperty(
'value',
expect(await composerText()).toBe(
'/import-canvas-export /tmp/canvas-export.zip ',
);
expect(invoke).toHaveBeenCalledWith('pick_local_file');
@@ -1716,24 +1707,26 @@ export function registerCanvasAssetTests() {
window.__TAURI__ = { core: { invoke } };
renderAppAt('/');
submitChat('/project /tmp/authorized-game');
await submitChat('/project /tmp/authorized-game');
fireEvent.click(screen.getByRole('button', { name: '确认' }));
expect(await screen.findByText('已打开:authorized-game')).not.toBeNull();
expect(await screen.findByText(/还没有最近一次 Agent run/)).not.toBeNull();
submitChat(
await submitChat(
'/import-canvas-asset /tmp/hero.png canvas-project-1 resource-1',
);
expect(
screen.getByText('画板资产路径必须是项目内相对路径。'),
).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(
screen.getAllByText('画板资产路径必须是项目内相对路径。').length,
).toBe(2);
submitChat('/import-canvas-export relative.zip canvas-project-1');
await submitChat('/import-canvas-export relative.zip canvas-project-1');
expect(
screen.getByText('画板导出 ZIP 路径必须是绝对路径。'),
).not.toBeNull();
@@ -1818,16 +1811,18 @@ export function registerCanvasAssetTests() {
window.__TAURI__ = { core: { invoke } };
renderAppAt('/');
submitChat('/project /tmp/authorized-game');
await submitChat('/project /tmp/authorized-game');
fireEvent.click(screen.getByRole('button', { name: '确认' }));
expect(await screen.findByText('已打开:authorized-game')).not.toBeNull();
submitChat('/canvas bad\u0007canvas');
submitChat('/sync-canvas-project bad\u0007canvas');
submitChat(
await submitChat('/canvas bad\u0007canvas');
await submitChat('/sync-canvas-project bad\u0007canvas');
await submitChat(
'/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(
4,
@@ -1886,7 +1881,7 @@ export function registerCanvasAssetTests() {
window.__TAURI__ = { core: { invoke } };
renderAppAt('/');
submitChat('/canvas canvas-project-1');
await submitChat('/canvas canvas-project-1');
expect(
await screen.findByText('准备打开画板项目:canvas-project-1'),
@@ -1918,10 +1913,7 @@ export function registerCanvasAssetTests() {
),
).not.toBeNull();
fireEvent.click(screen.getByRole('button', { name: '同步此画板' }));
expect(screen.getByLabelText('创作想法')).toHaveProperty(
'value',
'/sync-canvas-project canvas-project-1',
);
expect(await composerText()).toBe('/sync-canvas-project canvas-project-1');
});
it('syncs canvas project assets from chat after project confirmation', async () => {
@@ -1998,11 +1990,11 @@ export function registerCanvasAssetTests() {
window.__TAURI__ = { core: { invoke } };
renderAppAt('/');
submitChat('/project /tmp/authorized-game');
await submitChat('/project /tmp/authorized-game');
fireEvent.click(screen.getByRole('button', { name: '确认' }));
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: '确认' }));
expect(
@@ -2083,11 +2075,11 @@ export function registerCanvasAssetTests() {
window.__TAURI__ = { core: { invoke } };
renderAppAt('/');
submitChat('/project /tmp/authorized-game');
await submitChat('/project /tmp/authorized-game');
fireEvent.click(screen.getByRole('button', { name: '确认' }));
expect(await screen.findByText('已打开:authorized-game')).not.toBeNull();
submitChat('/generate-art 月光厨房弹幕主角');
await submitChat('/generate-art 月光厨房弹幕主角');
expect(await screen.findByText('准备生成首版美术素材。')).not.toBeNull();
expect(screen.getByText('canvas.asset_generate')).not.toBeNull();
expect(
@@ -2151,14 +2143,14 @@ export function registerCanvasAssetTests() {
window.__TAURI__ = { core: { invoke } };
renderAppAt('/');
submitChat('/project /tmp/authorized-game');
await submitChat('/project /tmp/authorized-game');
fireEvent.click(screen.getByRole('button', { name: '确认' }));
expect(
await screen.findByText('已设置本地项目:/tmp/authorized-game'),
).not.toBeNull();
invoke.mockClear();
submitChat('/generate-art 月光厨房弹幕主角');
await submitChat('/generate-art 月光厨房弹幕主角');
fireEvent.click(screen.getByRole('button', { name: '确认' }));
expect(await screen.findByText('准备生成首版美术素材。')).not.toBeNull();
@@ -2219,13 +2211,15 @@ export function registerCanvasAssetTests() {
window.__TAURI__ = { core: { invoke } };
renderAppAt('/');
submitChat('/project /tmp/authorized-game');
await submitChat('/project /tmp/authorized-game');
fireEvent.click(screen.getByRole('button', { name: '确认' }));
expect(
await screen.findByText('已设置本地项目:/tmp/authorized-game'),
).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(
await screen.findByText(
/导入 \/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 {
act,
agentRuntimeUserInputRequest,
composerText,
createGameCreationAppManifest,
createGameCreationAppSeedTasks,
createProjectSupervisorRuntimeHarness,
@@ -290,7 +291,7 @@ export function registerProjectConversationTests() {
window.__TAURI__ = { core: { invoke } };
renderAppAt('/');
submitChat('/project /tmp/authorized-game');
await submitChat('/project /tmp/authorized-game');
fireEvent.click(screen.getByRole('button', { name: '确认' }));
expect(await screen.findByText('历史需求:保留弹幕厨房')).not.toBeNull();
@@ -368,19 +369,19 @@ export function registerProjectConversationTests() {
window.__TAURI__ = { core: { invoke } };
renderAppAt('/');
submitChat('/project /tmp/authorized-game');
await submitChat('/project /tmp/authorized-game');
fireEvent.click(screen.getByRole('button', { name: '确认' }));
expect(await screen.findByText('重载历史需求')).not.toBeNull();
submitChat('临时未保存的输入');
await submitChat('临时未保存的输入');
expect(await screen.findByText('临时未保存的输入')).not.toBeNull();
submitChat('/history');
await submitChat('/history');
expect(await screen.findByText('重载历史回复')).not.toBeNull();
expect(screen.queryByText('临时未保存的输入')).toBeNull();
expect(screen.getByText('已读取项目对话历史:2 条')).not.toBeNull();
submitChat('另一条临时未保存的输入');
await submitChat('另一条临时未保存的输入');
expect(await screen.findByText('另一条临时未保存的输入')).not.toBeNull();
fireEvent.click(screen.getByRole('button', { name: '历史' }));
@@ -433,7 +434,7 @@ export function registerProjectConversationTests() {
window.__TAURI__ = { core: { invoke } };
renderAppAt('/');
submitChat('/project /tmp/authorized-game');
await submitChat('/project /tmp/authorized-game');
fireEvent.click(screen.getByRole('button', { name: '确认' }));
expect(await screen.findByText('已打开:authorized-game')).not.toBeNull();
@@ -777,7 +778,7 @@ export function registerProjectConversationTests() {
renderAppAt('/?main&projectPath=%2Ftmp%2Fauthorized-game');
await screen.findByText('想做什么游戏?');
submitChat('/第一条本地命令');
await submitChat('/第一条本地命令');
await waitFor(() => {
expect(appendAttempts).toBe(1);
});
@@ -787,7 +788,7 @@ export function registerProjectConversationTests() {
),
).not.toBeNull();
submitChat('/第二条本地命令');
await submitChat('/第二条本地命令');
await waitFor(() => {
expect(releaseRetryAppend).not.toBeNull();
});
@@ -873,7 +874,7 @@ export function registerProjectConversationTests() {
await screen.findByText('想做什么游戏?');
invoke.mockClear();
submitChat('/需要确认保存');
await submitChat('/需要确认保存');
const conversationWriteCommand =
await screen.findByText('conversation.write');
@@ -961,7 +962,7 @@ export function registerProjectConversationTests() {
await screen.findByText('想做什么游戏?');
invoke.mockClear();
submitChat('/先不保存');
await submitChat('/先不保存');
const firstPrompt = await screen.findByText('conversation.write');
fireEvent.click(
@@ -980,7 +981,7 @@ export function registerProjectConversationTests() {
expect.anything(),
);
submitChat('/继续补充');
await submitChat('/继续补充');
const secondPrompt = await screen.findByText('conversation.write');
fireEvent.click(
@@ -1064,11 +1065,11 @@ export function registerProjectConversationTests() {
renderAppAt('/?main&projectPath=%2Ftmp%2Fauthorized-game');
await screen.findByText('想做什么游戏?');
submitChat('/第一条并发命令');
await submitChat('/第一条并发命令');
await waitFor(() => {
expect(releaseFirstAppend).not.toBeNull();
});
submitChat('/第二条并发命令');
await submitChat('/第二条并发命令');
releaseFirstAppend?.();
@@ -1249,7 +1250,7 @@ export function registerProjectConversationTests() {
window.__TAURI__ = { core: { invoke } };
renderAppAt('/');
submitChat('/project /tmp/authorized-game');
await submitChat('/project /tmp/authorized-game');
fireEvent.click(screen.getByRole('button', { name: '确认' }));
expect(await screen.findByText('已打开:authorized-game')).not.toBeNull();
@@ -1368,10 +1369,7 @@ export function registerProjectConversationTests() {
expect(agentConversationReadCount).toBeGreaterThanOrEqual(2);
fireEvent.click(screen.getByRole('button', { name: '填入同步命令' }));
expect(screen.queryByLabelText('Agent 对话')).toBeNull();
expect(screen.getByLabelText('创作想法')).toHaveProperty(
'value',
'/sync-canvas-project ',
);
expect(await composerText()).toBe('/sync-canvas-project ');
fireEvent.click(screen.getByRole('button', { name: /拆解创作方向/ }));
const reopenedAgentDialog = await screen.findByLabelText('Agent 对话');
fireEvent.click(
@@ -1380,10 +1378,7 @@ export function registerProjectConversationTests() {
}),
);
expect(screen.queryByLabelText('Agent 对话')).toBeNull();
expect(screen.getByLabelText('创作想法')).toHaveProperty(
'value',
'/read .agent/spec.md',
);
expect(await composerText()).toBe('/read .agent/spec.md');
expect(invoke).not.toHaveBeenCalledWith(
'sync_canvas_project_assets',
expect.anything(),
@@ -17,19 +17,25 @@ import {
agentRuntimeUserInputRequest,
App,
cleanup,
composerDisabled,
composerText,
composerValue,
createGameCreationAppManifest,
createGameCreationAppSeedTasks,
createProjectSupervisorRuntimeHarness,
emptyProjectPolicy,
expect,
findResourceDetailButton,
fireEvent,
GAME_CREATION_AGENT_RUN_SCHEMA_VERSION,
type GameCreationAgentRunTrace,
getResourceDetailButton,
it,
mockRoleAgentReply,
pickProjectFromLauncher,
ProjectDevelopmentView,
projectSupervisorResponseStream,
queryResourceDetailButton,
React,
readFileSync,
render,
@@ -37,6 +43,7 @@ import {
renderLauncherProjectsAt,
resolve,
screen,
setComposerText,
submitChat,
vi,
waitFor,
@@ -710,7 +717,7 @@ export function registerProjectWorkbenchFoundationTests() {
),
).toEqual(['设计文档', '美术资源', '音乐音效', '项目版本']);
expect(graphResourceIds).toContain('asset:section-code');
expect(screen.queryByRole('button', { name: /section\.js/ })).toBeNull();
expect(queryResourceDetailButton('section.js')).toBeNull();
expect(screen.queryByRole('region', { name: '游戏代码' })).toBeNull();
expect(outline.querySelectorAll('small')).toHaveLength(0);
expect(screen.getByRole('region', { name: '设计文档' })).not.toBeNull();
@@ -1282,7 +1289,7 @@ export function registerProjectWorkbenchFoundationTests() {
.getByLabelText(/资源(?:依赖|类型)视图/)
.classList.contains('game-resource-canvas--paged'),
).toBe(false);
expect(screen.queryByRole('button', { name: /game\.js/ })).toBeNull();
expect(queryResourceDetailButton('game.js')).toBeNull();
expect(screen.queryByRole('region', { name: '游戏代码' })).toBeNull();
});
});
@@ -1604,9 +1611,7 @@ export function registerProjectWorkbenchFoundationTests() {
const showResourcePage = (label: string) => openResourceBookCategory(label);
await showResourcePage('美术资源');
let heroDetailButton = await screen.findByRole('button', {
name: /打开资源详情:美术资源 hero\.png/,
});
let heroDetailButton = await findResourceDetailButton('hero.png');
const heroCard = heroDetailButton.closest('.game-resource-card');
expect(heroCard?.textContent).not.toContain('hero.png');
expect(heroCard?.textContent).not.toContain('assets/hero.png');
@@ -1724,9 +1729,7 @@ export function registerProjectWorkbenchFoundationTests() {
expect(screen.queryByRole('dialog', { name: 'theme.mp3' })).toBeNull();
await showResourcePage('美术资源');
heroDetailButton = await screen.findByRole('button', {
name: /打开资源详情:美术资源 hero\.png/,
});
heroDetailButton = await findResourceDetailButton('hero.png');
fireEvent.click(heroDetailButton);
const heroFocus = await screen.findByRole('dialog', { name: 'hero.png' });
expect(within(heroFocus).getByText('assets/hero.png')).not.toBeNull();
@@ -1769,11 +1772,11 @@ export function registerProjectWorkbenchFoundationTests() {
const search = screen.getByLabelText('搜索项目资源');
fireEvent.change(search, { target: { value: 'assets/hero.png' } });
await waitFor(() => expect(pause.mock.instances).toContain(typeVideo));
expect(screen.getByRole('button', { name: /hero\.png/ })).not.toBeNull();
expect(screen.queryByRole('button', { name: /intro\.mp4/ })).toBeNull();
const typeHeroCard = screen
.getByRole('button', { name: /hero\.png/ })
.closest('.game-resource-card');
expect(getResourceDetailButton('hero.png')).not.toBeNull();
expect(queryResourceDetailButton('intro.mp4')).toBeNull();
const typeHeroCard = getResourceDetailButton('hero.png').closest(
'.game-resource-card',
);
await waitFor(() =>
expect(
typeHeroCard?.querySelector('.game-resource-card-visual > img'),
@@ -1899,9 +1902,9 @@ export function registerProjectWorkbenchFoundationTests() {
document.querySelectorAll('.game-resource-card-visual > img'),
).toHaveLength(48);
});
const wideImageCard = screen
.getByRole('button', { name: /image-0.png/ })
.closest<HTMLElement>('.game-resource-card');
const wideImageCard = getResourceDetailButton(
'image-0.png',
).closest<HTMLElement>('.game-resource-card');
await waitFor(() => {
expect(
wideImageCard?.style.getPropertyValue('--resource-card-width'),
@@ -1935,7 +1938,7 @@ export function registerProjectWorkbenchFoundationTests() {
);
await openResourceBookCategory('美术资源');
await waitFor(() =>
expect(screen.getByRole('button', { name: /next\.png/ })).not.toBeNull(),
expect(getResourceDetailButton('next.png')).not.toBeNull(),
);
await waitFor(() => expect(observer.observedCount()).toBe(1));
act(() => observer.triggerVisible());
@@ -2059,18 +2062,18 @@ export function registerProjectWorkbenchFoundationTests() {
name: /美术资源/,
}),
);
const playerCard = screen
.getByRole('button', { name: /player\.png/ })
.closest('.game-resource-card');
const playerCard = getResourceDetailButton('player.png').closest(
'.game-resource-card',
);
expect(playerCard?.classList.contains('is-relation-version-binding')).toBe(
true,
);
fireEvent.click(screen.getByRole('button', { name: '按依赖' }));
await waitFor(() => {
const dependencyPlayerCard = screen
.getByRole('button', { name: /player\.png/ })
.closest('.game-resource-card');
const dependencyPlayerCard = getResourceDetailButton(
'player.png',
).closest('.game-resource-card');
expect(
dependencyPlayerCard?.classList.contains('is-relation-version-binding'),
).toBe(true);
@@ -2427,7 +2430,7 @@ export function registerProjectWorkbenchFoundationTests() {
);
await openResourceBookCategory('设计文档');
fireEvent.click(await screen.findByRole('button', { name: /design\.md/ }));
fireEvent.click(await findResourceDetailButton('design.md'));
const documentFocus = await screen.findByRole('dialog', {
name: 'design.md',
});
@@ -2439,7 +2442,7 @@ export function registerProjectWorkbenchFoundationTests() {
expect(within(documentFocus).getByText('图片:远程图片')).not.toBeNull();
expect(documentFocus.hasAttribute('aria-modal')).toBe(false);
await openResourceBookCategory('美术资源');
fireEvent.click(screen.getByRole('button', { name: /icon\.svg/ }));
fireEvent.click(getResourceDetailButton('icon.svg'));
expect(screen.queryByRole('dialog', { name: 'design.md' })).toBeNull();
const artDetails = await screen.findByRole('dialog', { name: 'icon.svg' });
expect(within(artDetails).getByText('assets/icon.svg')).not.toBeNull();
@@ -2504,7 +2507,7 @@ export function registerProjectWorkbenchFoundationTests() {
);
await openResourceBookCategory('设计文档');
fireEvent.click(screen.getByRole('button', { name: /blocked\.md/ }));
fireEvent.click(getResourceDetailButton('blocked.md'));
expect((await screen.findByRole('alert')).textContent).toBe(
'当前项目策略要求先确认读取文档,确认后请关闭详情并重试',
);
@@ -2585,11 +2588,7 @@ export function registerProjectWorkbenchFoundationTests() {
);
await openResourceBookCategory('音乐音效');
fireEvent.click(
await screen.findByRole('button', {
name: '打开资源详情:音乐音效 focus.mp3',
}),
);
fireEvent.click(await findResourceDetailButton('focus.mp3'));
const audioDetails = await screen.findByRole('dialog', {
name: 'focus.mp3',
});
@@ -2904,12 +2903,8 @@ export function registerProjectWorkbenchFoundationTests() {
);
fireEvent.change(search, { target: { value: '' } });
let sourceCard = screen.getByRole('button', {
name: /spec-source\.json/,
});
const targetCard = screen.getByRole('button', {
name: /ui-dependency\.json/,
});
let sourceCard = getResourceDetailButton('spec-source.json');
const targetCard = getResourceDetailButton('ui-dependency.json');
const referenceSelector =
'[data-edge-kind="asset-reference"]' +
'[data-source-resource-id="asset:dependency-spec"]' +
@@ -2984,7 +2979,7 @@ export function registerProjectWorkbenchFoundationTests() {
within(targetFocus).getByRole('button', { name: '收起资源' }),
);
overlay = await screen.findByTestId('resource-dependency-overlay-document');
sourceCard = screen.getByRole('button', { name: /spec-source\.json/ });
sourceCard = getResourceDetailButton('spec-source.json');
await waitFor(() =>
expect(overlay.querySelector(referenceSelector)).not.toBeNull(),
);
@@ -3730,11 +3725,9 @@ export function registerProjectWorkbenchFoundationTests() {
await waitFor(() => expect(layoutReads).toBe(1));
await openResourceBookCategory('设计文档');
const cards = [0, 1, 2].map((depth) =>
screen
.getByRole('button', {
name: new RegExp(`truncated-depth-${depth}\\.json`, 'u'),
})
.closest('.game-resource-card'),
getResourceDetailButton(`truncated-depth-${depth}.json`).closest(
'.game-resource-card',
),
);
await waitFor(() => {
expect(cards[0]?.getAttribute('style')).toContain('--resource-x: 0px');
@@ -3962,9 +3955,7 @@ export function registerProjectWorkbenchFoundationTests() {
);
await openResourceBookCategory('美术资源');
const cardButton = await screen.findByRole('button', {
name: '打开资源详情:美术资源 clickable.png',
});
const cardButton = await findResourceDetailButton('clickable.png');
fireEvent.pointerDown(cardButton, {
pointerId: 7,
button: 0,
@@ -4049,9 +4040,7 @@ export function registerProjectWorkbenchFoundationTests() {
);
await openResourceBookCategory('美术资源');
const cardButton = await screen.findByRole('button', {
name: '打开资源详情:美术资源 cancelable.png',
});
const cardButton = await findResourceDetailButton('cancelable.png');
const card = cardButton.closest<HTMLElement>('.game-resource-card');
expect(card).not.toBeNull();
Object.defineProperties(card!, {
@@ -4534,7 +4523,7 @@ export function registerProjectWorkbenchFoundationTests() {
fireEvent.change(screen.getByLabelText('搜索项目资源'), {
target: { value: 'hero.png' },
});
fireEvent.click(await screen.findByRole('button', { name: /hero\.png/ }));
fireEvent.click(await findResourceDetailButton('hero.png'));
const heroDetails = screen.getByRole('dialog', { name: 'hero.png' });
expect(within(heroDetails).getByText('assets/hero.png')).not.toBeNull();
expect(within(heroDetails).getByText('image/png')).not.toBeNull();
@@ -4587,9 +4576,9 @@ export function registerProjectWorkbenchFoundationTests() {
);
await openResourceBookCategory('美术资源');
const candidate = await screen.findByRole('button', {
name: /ui-prototype\.png(待视觉验收)/,
});
const candidate = await findResourceDetailButton(
'ui-prototype.png(待视觉验收)',
);
const candidateCard = candidate.closest('.game-resource-card');
expect(candidateCard?.textContent).not.toContain('画板 · 候选界面图');
expect(candidateCard?.textContent).not.toContain('assets/ui-prototype.png');
@@ -4869,10 +4858,9 @@ export function registerUserSurfaceBoundaryTests() {
).disabled,
).toBe(true);
fireEvent.click(screen.getByRole('button', { name: '打开画板' }));
expect(composerInput).toHaveProperty('value', '/canvas ');
expect(await composerValue(composerInput)).toBe('/canvas ');
fireEvent.click(screen.getByRole('button', { name: '灵感草稿' }));
expect(composerInput).toHaveProperty(
'value',
expect(await composerValue(composerInput)).toBe(
'像素风厨房弹幕小游戏:玩家用方向键躲避飞来的食材,收集调料加分,60 秒内尽量高分,失败后可一键重开。',
);
expect(screen.queryByText('game.generate_draft')).toBeNull();
@@ -5095,8 +5083,8 @@ export function registerProjectSupervisorSurfaceTests() {
within(supervisorSurface).queryByLabelText('项目总控 Agent 状态'),
).toBeNull();
expect(
within(supervisorSurface).getByPlaceholderText(
'告诉陶泥儿接下来要做什么',
within(supervisorSurface).getByText(
'告诉陶泥儿接下来要做什么,或输入 @ 选择资源',
),
).not.toBeNull();
expect(
@@ -5347,9 +5335,7 @@ export function registerProjectSupervisorSurfaceTests() {
expect.anything(),
);
const input = within(supervisorSurface).getByLabelText('陶泥儿对话内容');
fireEvent.change(input, {
target: { value: '从旧任务状态继续生成,但使用 direct Codex' },
});
await setComposerText(input, '从旧任务状态继续生成,但使用 direct Codex');
fireEvent.submit(input.closest('form') as HTMLFormElement);
expect(
@@ -5423,9 +5409,10 @@ export function registerProjectSupervisorSurfaceTests() {
expect(screen.queryByLabelText('项目总控 Agent 状态')).toBeNull();
expect(screen.queryByLabelText('专业 Agent 协作状态')).toBeNull();
fireEvent.change(within(surface).getByLabelText('项目总控对话内容'), {
target: { value: '继续完成可玩原型' },
});
await setComposerText(
within(surface).getByLabelText('项目总控对话内容'),
'继续完成可玩原型',
);
fireEvent.click(within(surface).getByRole('button', { name: '发送' }));
await waitFor(() => {
@@ -5470,15 +5457,16 @@ export function registerProjectSupervisorSurfaceTests() {
renderSupervisorChat();
const firstSurface = await screen.findByLabelText('项目总控 Agent 纯聊天');
await within(firstSurface).findByLabelText('项目总控消息');
fireEvent.change(within(firstSurface).getByLabelText('项目总控对话内容'), {
target: { value: '这条草稿还没有发送' },
});
await setComposerText(
within(firstSurface).getByLabelText('项目总控对话内容'),
'这条草稿还没有发送',
);
cleanup();
renderSupervisorChat();
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 () => {
@@ -5673,13 +5661,9 @@ export function registerProjectSupervisorSurfaceTests() {
within(card).getByText('首版角色规范图采用哪种美术方向?'),
).not.toBeNull();
expect(within(card).getByText('优先验证轮廓与动作可读性。')).not.toBeNull();
expect(
(
within(surface).getByLabelText(
'项目总控对话内容',
) as HTMLTextAreaElement
).disabled,
).toBe(true);
expect(await composerDisabled('项目总控对话内容', within(surface))).toBe(
true,
);
fireEvent.click(within(card).getByRole('button', { name: /像素风/ }));
fireEvent.click(within(card).getByRole('button', { name: '提交回答' }));
@@ -5809,9 +5793,10 @@ export function registerProjectSupervisorSurfaceTests() {
},
);
fireEvent.change(screen.getByLabelText('陶泥儿对话内容'), {
target: { value: '先完成正式客户端玩法拆解' },
});
await setComposerText(
screen.getByLabelText('陶泥儿对话内容'),
'先完成正式客户端玩法拆解',
);
fireEvent.click(
within(supervisorSurface).getByRole('button', { name: '发送' }),
);
@@ -6083,9 +6068,10 @@ export function registerProjectSupervisorSurfaceTests() {
).toBe(false);
});
fireEvent.change(screen.getByLabelText('陶泥儿对话内容'), {
target: { value: '补充:优先复用现有素材' },
});
await setComposerText(
screen.getByLabelText('陶泥儿对话内容'),
'补充:优先复用现有素材',
);
fireEvent.click(
within(supervisorSurface).getByRole('button', { name: '发送' }),
);
@@ -6431,19 +6417,28 @@ export function registerProjectSupervisorSurfaceTests() {
).toBe(false);
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(
true,
false,
);
// composerValue 会先等编辑器提交这一帧,再校验草稿保留且确实插入了换行。
expect(await composerValue(composer)).toBe('修改玩家移动脚本');
expect(
composer.querySelector('br:not([data-lexical-managed-linebreak])'),
).not.toBeNull();
expect(
invoke.mock.calls.some(
([command]) => command === 'chat_with_game_creator_direct_codex',
),
).toBe(false);
// 组合态 Enter 同样不发送:按键被编辑器消化,草稿原样保留。
expect(
fireEvent.keyDown(composer, { key: 'Enter', isComposing: true }),
).toBe(true);
).toBe(false);
expect(await composerValue(composer)).toBe('修改玩家移动脚本');
expect(
invoke.mock.calls.some(
([command]) => command === 'chat_with_game_creator_direct_codex',
@@ -6481,7 +6476,7 @@ export function registerProjectWorkbenchNavigationTests() {
'open_game_creator_launcher_window',
);
submitChat('/switch-project');
await submitChat('/switch-project');
expect(
screen.getAllByText('请回到首页的项目组切换项目。').length,
@@ -6556,7 +6551,7 @@ export function registerProjectWorkbenchNavigationTests() {
});
expect(await screen.findByText('已打开项目目录。')).not.toBeNull();
submitChat('/open-project');
await submitChat('/open-project');
await waitFor(() => {
expect(
@@ -6640,8 +6635,7 @@ export function registerProjectWorkbenchNavigationTests() {
expect(document.activeElement).toBe(screen.getByLabelText('创作想法')),
);
expect(screen.getByLabelText('创作想法')).toHaveProperty(
'value',
expect(await composerText()).toBe(
'/asset-register assets/uploads/hero.png image image/png',
);
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 { messageBubble } from './message-bubble';
@@ -14,7 +21,7 @@ export async function assertPlanningAndStatusShortcutFlow(
const runLocalCountBeforeRisks = invoke.mock.calls.filter(
([command]) => command === 'run_limited_local_command',
).length;
submitChat('/risks');
await submitChat('/risks');
expect(await screen.findByText(/项目风险:/)).not.toBeNull();
expect(
screen.getByText(/最近 run 已通过,但当前本地预览未运行。 建议:\/run/),
@@ -23,7 +30,7 @@ export async function assertPlanningAndStatusShortcutFlow(
screen.getByText(/还有 3 个 ready 任务等待处理。 建议:\/tasks/),
).not.toBeNull();
fireEvent.click(screen.getByRole('button', { name: '处理首个风险' }));
expect(screen.getByLabelText('创作想法')).toHaveProperty('value', '/run');
expect(await composerText()).toBe('/run');
expect(
invoke.mock.calls.filter(
([command]) => command === 'get_local_game_manifest',
@@ -69,7 +76,7 @@ export async function assertPlanningAndStatusShortcutFlow(
([command]) => command === 'control_agent_run',
).length,
};
submitChat('/blockers');
await submitChat('/blockers');
const blockerMessages = await screen.findAllByText(/当前阻塞项:/);
const blockerMessage = messageBubble(
blockerMessages[blockerMessages.length - 1],
@@ -97,7 +104,7 @@ export async function assertPlanningAndStatusShortcutFlow(
blockerMessageList as HTMLElement,
).getAllByRole('button', { name: '处理首个阻塞' });
fireEvent.click(blockerDraftButtons[blockerDraftButtons.length - 1]);
expect(screen.getByLabelText('创作想法')).toHaveProperty('value', '/run');
expect(await composerText()).toBe('/run');
expect(
invoke.mock.calls.filter(
([command]) => command === 'get_local_game_manifest',
@@ -171,7 +178,7 @@ export async function assertPlanningAndStatusShortcutFlow(
([command]) => command === 'control_agent_run',
).length,
};
submitChat('/ready');
await submitChat('/ready');
const readyMessages = await screen.findAllByText(/试玩就绪度:/);
const readyMessage = messageBubble(readyMessages[readyMessages.length - 1]);
expect(readyMessage.textContent).toContain('项目:未命名游戏原型');
@@ -197,7 +204,7 @@ export async function assertPlanningAndStatusShortcutFlow(
readyMessageList as HTMLElement,
).getAllByRole('button', { name: '启动试玩' });
fireEvent.click(readyDraftButtons[readyDraftButtons.length - 1]);
expect(screen.getByLabelText('创作想法')).toHaveProperty('value', '/run');
expect(await composerText()).toBe('/run');
expect(
invoke.mock.calls.filter(
([command]) => command === 'get_local_game_manifest',
@@ -271,7 +278,7 @@ export async function assertPlanningAndStatusShortcutFlow(
([command]) => command === 'control_agent_run',
).length,
};
submitChat('/evidence');
await submitChat('/evidence');
const evidenceMessages = await screen.findAllByText(/验证证据台账:/);
const evidenceMessage = messageBubble(
evidenceMessages[evidenceMessages.length - 1],
@@ -306,7 +313,7 @@ export async function assertPlanningAndStatusShortcutFlow(
evidenceMessageList as HTMLElement,
).getAllByRole('button', { name: '补齐首个证据缺口' });
fireEvent.click(evidenceDraftButtons[evidenceDraftButtons.length - 1]);
expect(screen.getByLabelText('创作想法')).toHaveProperty('value', '/run');
expect(await composerText()).toBe('/run');
expect(
invoke.mock.calls.filter(
([command]) => command === 'get_local_game_manifest',
@@ -380,7 +387,7 @@ export async function assertPlanningAndStatusShortcutFlow(
([command]) => command === 'control_agent_run',
).length,
};
submitChat('/deps');
await submitChat('/deps');
const dependencyMessages = await screen.findAllByText(/任务依赖链:/);
const dependencyMessage = messageBubble(
dependencyMessages[dependencyMessages.length - 1],
@@ -407,10 +414,7 @@ export async function assertPlanningAndStatusShortcutFlow(
dependencyMessageList as HTMLElement,
).getAllByRole('button', { name: '查看验收标准' });
fireEvent.click(dependencyDraftButtons[dependencyDraftButtons.length - 1]);
expect(screen.getByLabelText('创作想法')).toHaveProperty(
'value',
'/criteria',
);
expect(await composerText()).toBe('/criteria');
expect(
invoke.mock.calls.filter(
([command]) => command === 'get_local_game_manifest',
@@ -484,7 +488,7 @@ export async function assertPlanningAndStatusShortcutFlow(
([command]) => command === 'control_agent_run',
).length,
};
submitChat('/revise');
await submitChat('/revise');
const revisionMessages = await screen.findAllByText(/改版草稿:/);
const revisionMessage = messageBubble(
revisionMessages[revisionMessages.length - 1],
@@ -534,8 +538,7 @@ export async function assertPlanningAndStatusShortcutFlow(
revisionMessageList as HTMLElement,
).getAllByRole('button', { name: '填入改版说明' });
fireEvent.click(revisionDraftButtons[revisionDraftButtons.length - 1]);
expect(screen.getByLabelText('创作想法')).toHaveProperty(
'value',
expect(await composerText()).toEqual(
expect.stringContaining('/agent-resume 改版说明:'),
);
expect(
@@ -611,7 +614,7 @@ export async function assertPlanningAndStatusShortcutFlow(
([command]) => command === 'control_agent_run',
).length,
};
submitChat('/privacy');
await submitChat('/privacy');
const privacyMessages = await screen.findAllByText(/隐私与导出边界:/);
const privacyMessage = messageBubble(
privacyMessages[privacyMessages.length - 1],
@@ -651,7 +654,7 @@ export async function assertPlanningAndStatusShortcutFlow(
privacyMessageList as HTMLElement,
).getAllByRole('button', { name: '查看素材来源' });
fireEvent.click(privacyDraftButtons[privacyDraftButtons.length - 1]);
expect(screen.getByLabelText('创作想法')).toHaveProperty('value', '/credits');
expect(await composerText()).toBe('/credits');
expect(
invoke.mock.calls.filter(
([command]) => command === 'get_local_game_manifest',
@@ -705,7 +708,7 @@ export async function assertPlanningAndStatusShortcutFlow(
const runLocalCountBeforeCriteria = invoke.mock.calls.filter(
([command]) => command === 'run_limited_local_command',
).length;
submitChat('/criteria');
await submitChat('/criteria');
expect(await screen.findByText(/当前验收标准:/)).not.toBeNull();
const criteriaMessages = screen.getAllByText(/当前验收标准:/);
const criteriaMessage = messageBubble(
@@ -724,7 +727,7 @@ export async function assertPlanningAndStatusShortcutFlow(
name: '查看任务',
});
fireEvent.click(criteriaDraftButtons[criteriaDraftButtons.length - 1]);
expect(screen.getByLabelText('创作想法')).toHaveProperty('value', '/tasks');
expect(await composerText()).toBe('/tasks');
expect(
invoke.mock.calls.filter(
([command]) => command === 'read_local_project_file',
@@ -750,7 +753,7 @@ export async function assertPlanningAndStatusShortcutFlow(
const runLocalCountBeforeGroups = invoke.mock.calls.filter(
([command]) => command === 'run_limited_local_command',
).length;
submitChat('/groups');
await submitChat('/groups');
expect(await screen.findByText(/专业组进度:/)).not.toBeNull();
const groupMessages = screen.getAllByText(/专业组进度:/);
const groupMessage = messageBubble(groupMessages[groupMessages.length - 1]);
@@ -763,7 +766,7 @@ export async function assertPlanningAndStatusShortcutFlow(
name: '查看任务',
});
fireEvent.click(groupDraftButtons[groupDraftButtons.length - 1]);
expect(screen.getByLabelText('创作想法')).toHaveProperty('value', '/tasks');
expect(await composerText()).toBe('/tasks');
expect(
invoke.mock.calls.filter(
([command]) => command === 'read_local_project_file',
@@ -803,7 +806,7 @@ export async function assertPlanningAndStatusShortcutFlow(
([command]) => command === 'export_local_project_package',
).length,
};
submitChat('/balance');
await submitChat('/balance');
expect(await screen.findByText(/数值状态:/)).not.toBeNull();
const balanceMessages = screen.getAllByText(/数值状态:/);
const balanceMessage = messageBubble(
@@ -837,8 +840,7 @@ export async function assertPlanningAndStatusShortcutFlow(
balanceMessageList as HTMLElement,
).getAllByRole('button', { name: '填写数值反馈' });
fireEvent.click(balanceDraftButtons[balanceDraftButtons.length - 1]);
expect(screen.getByLabelText('创作想法')).toHaveProperty(
'value',
expect(await composerText()).toBe(
'/agent-resume 数值调整:前 30 秒更易上手;得分反馈更明显;失败后重开节奏更快',
);
expect(
@@ -886,7 +888,7 @@ export async function assertPlanningAndStatusShortcutFlow(
const runLocalCountBeforeBudget = invoke.mock.calls.filter(
([command]) => command === 'run_limited_local_command',
).length;
submitChat('/budget');
await submitChat('/budget');
expect(await screen.findByText(/运行预算:/)).not.toBeNull();
const budgetMessages = screen.getAllByText(/运行预算:/);
const budgetMessage = messageBubble(
@@ -905,7 +907,7 @@ export async function assertPlanningAndStatusShortcutFlow(
name: '查看发布准备',
});
fireEvent.click(budgetDraftButtons[budgetDraftButtons.length - 1]);
expect(screen.getByLabelText('创作想法')).toHaveProperty('value', '/publish');
expect(await composerText()).toBe('/publish');
expect(
invoke.mock.calls.filter(
([command]) => command === 'read_local_project_file',
@@ -937,7 +939,7 @@ export async function assertPlanningAndStatusShortcutFlow(
const openPreviewCountBeforeQa = invoke.mock.calls.filter(
([command]) => command === 'activate_local_game_preview',
).length;
submitChat('/qa');
await submitChat('/qa');
expect(await screen.findByText(/质量检查:/)).not.toBeNull();
const qaMessages = screen.getAllByText(/质量检查:/);
const qaMessage = messageBubble(qaMessages[qaMessages.length - 1]);
@@ -954,10 +956,7 @@ export async function assertPlanningAndStatusShortcutFlow(
name: '查看试玩状态',
});
fireEvent.click(qaDraftButtons[qaDraftButtons.length - 1]);
expect(screen.getByLabelText('创作想法')).toHaveProperty(
'value',
'/playtest',
);
expect(await composerText()).toBe('/playtest');
expect(
invoke.mock.calls.filter(
([command]) => command === 'read_local_project_file',
@@ -1002,7 +1001,7 @@ export async function assertPlanningAndStatusShortcutFlow(
const openPreviewCountBeforeChanges = invoke.mock.calls.filter(
([command]) => command === 'activate_local_game_preview',
).length;
submitChat('/changes');
await submitChat('/changes');
expect(await screen.findByText(/最近变更:/)).not.toBeNull();
const changeMessages = screen.getAllByText(/最近变更:/);
const changeMessage = messageBubble(
@@ -1030,10 +1029,7 @@ export async function assertPlanningAndStatusShortcutFlow(
name: '读取首个产物',
});
fireEvent.click(changeDraftButtons[changeDraftButtons.length - 1]);
expect(screen.getByLabelText('创作想法')).toHaveProperty(
'value',
'/read exports/README.md',
);
expect(await composerText()).toBe('/read exports/README.md');
expect(
invoke.mock.calls.filter(
([command]) => command === 'read_local_project_file',
@@ -1080,7 +1076,7 @@ export async function assertPlanningAndStatusShortcutFlow(
const openPreviewCountBeforeReview = invoke.mock.calls.filter(
([command]) => command === 'activate_local_game_preview',
).length;
submitChat('/review');
await submitChat('/review');
expect(await screen.findByText(/评审状态:/)).not.toBeNull();
const reviewMessages = screen.getAllByText(/评审状态:/);
const reviewMessage = messageBubble(
@@ -1095,10 +1091,7 @@ export async function assertPlanningAndStatusShortcutFlow(
'Evaluator #1 · passed · 质量评审通过',
);
fireEvent.click(screen.getByRole('button', { name: '读取评审记录' }));
expect(screen.getByLabelText('创作想法')).toHaveProperty(
'value',
'/read .agent/findings.md',
);
expect(await composerText()).toBe('/read .agent/findings.md');
expect(
invoke.mock.calls.filter(
([command]) => command === 'read_local_project_file',
@@ -1128,7 +1121,7 @@ export async function assertPlanningAndStatusShortcutFlow(
const fileReadCountBeforeContext = invoke.mock.calls.filter(
([command]) => command === 'read_local_project_file',
).length;
submitChat('/context');
await submitChat('/context');
expect(await screen.findByText(/上下文来源:/)).not.toBeNull();
expect(screen.getByText(/项目对话:\/history/)).not.toBeNull();
expect(screen.getByText(/项目黑板:\/memory blackboard/)).not.toBeNull();
@@ -1143,10 +1136,7 @@ export async function assertPlanningAndStatusShortcutFlow(
name: '读取首个上下文',
});
fireEvent.click(contextDraftButtons[contextDraftButtons.length - 1]);
expect(screen.getByLabelText('创作想法')).toHaveProperty(
'value',
'/read .agent/spec.md',
);
expect(await composerText()).toBe('/read .agent/spec.md');
expect(
invoke.mock.calls.filter(
([command]) => command === 'read_local_project_file',
@@ -1168,7 +1158,7 @@ export async function assertPlanningAndStatusShortcutFlow(
const openPreviewCountBeforeTimeline = invoke.mock.calls.filter(
([command]) => command === 'activate_local_game_preview',
).length;
submitChat('/timeline');
await submitChat('/timeline');
expect(await screen.findByText(/项目时间线:/)).not.toBeNull();
const timelineMessages = screen.getAllByText(/项目时间线:/);
const timelineMessage = messageBubble(
@@ -1190,10 +1180,7 @@ export async function assertPlanningAndStatusShortcutFlow(
name: '读取最近日志',
});
fireEvent.click(timelineDraftButtons[timelineDraftButtons.length - 1]);
expect(screen.getByLabelText('创作想法')).toHaveProperty(
'value',
'/read .agent/logs/command.log',
);
expect(await composerText()).toBe('/read .agent/logs/command.log');
expect(
invoke.mock.calls.filter(
([command]) => command === 'read_local_project_file',
@@ -1229,7 +1216,7 @@ export async function assertPlanningAndStatusShortcutFlow(
const runLocalCountBeforeHandoff = invoke.mock.calls.filter(
([command]) => command === 'run_limited_local_command',
).length;
submitChat('/handoff');
await submitChat('/handoff');
expect(await screen.findByText(/项目交接:/)).not.toBeNull();
const handoffMessages = screen.getAllByText(/项目交接:/);
const handoffMessage = messageBubble(
@@ -1246,7 +1233,7 @@ export async function assertPlanningAndStatusShortcutFlow(
name: '查看下一步',
});
fireEvent.click(nextActionButtons[nextActionButtons.length - 1]);
expect(screen.getByLabelText('创作想法')).toHaveProperty('value', '/next');
expect(await composerText()).toBe('/next');
expect(
invoke.mock.calls.filter(
([command]) => command === 'get_local_game_manifest',
@@ -1266,13 +1253,13 @@ export async function assertPlanningAndStatusShortcutFlow(
const fileReadCountBeforeRuns = invoke.mock.calls.filter(
([command]) => command === 'read_local_project_file',
).length;
submitChat('/runs');
await submitChat('/runs');
expect(await screen.findByText(/Run 历史读取命令:/)).not.toBeNull();
expect(
screen.getByText(/当前指针 run-main-shortcut-trace .*\/trace/),
).not.toBeNull();
fireEvent.click(screen.getByRole('button', { name: '查看最近 Run' }));
expect(screen.getByLabelText('创作想法')).toHaveProperty('value', '/trace');
expect(await composerText()).toBe('/trace');
expect(
invoke.mock.calls.filter(
([command]) => command === 'read_local_project_file',
@@ -1282,7 +1269,7 @@ export async function assertPlanningAndStatusShortcutFlow(
const fileReadCountBeforeRunFiles = invoke.mock.calls.filter(
([command]) => command === 'read_local_project_file',
).length;
submitChat('/run-files');
await submitChat('/run-files');
expect(
await screen.findByText(/Agent 运行辅助文件读取命令:/),
).not.toBeNull();
@@ -1297,10 +1284,7 @@ export async function assertPlanningAndStatusShortcutFlow(
name: '读输出流',
}),
);
expect(screen.getByLabelText('创作想法')).toHaveProperty(
'value',
'/read .agent/output.jsonl',
);
expect(await composerText()).toBe('/read .agent/output.jsonl');
expect(
invoke.mock.calls.filter(
([command]) => command === 'read_local_project_file',
@@ -1310,7 +1294,7 @@ export async function assertPlanningAndStatusShortcutFlow(
const fileReadCountBeforeInternals = invoke.mock.calls.filter(
([command]) => command === 'read_local_project_file',
).length;
submitChat('/internals');
await submitChat('/internals');
expect(await screen.findByText(/项目内部真相源读取命令:/)).not.toBeNull();
expect(
screen.getByText(/读 manifest · \.agent\/manifest\.json/),
@@ -1322,10 +1306,7 @@ export async function assertPlanningAndStatusShortcutFlow(
name: '读 manifest',
});
fireEvent.click(internalDraftButtons[internalDraftButtons.length - 1]);
expect(screen.getByLabelText('创作想法')).toHaveProperty(
'value',
'/read .agent/manifest.json',
);
expect(await composerText()).toBe('/read .agent/manifest.json');
expect(
invoke.mock.calls.filter(
([command]) => command === 'read_local_project_file',
@@ -1358,7 +1339,7 @@ export async function assertPlanningAndStatusShortcutFlow(
([command]) => command === 'list_local_project_export_packages',
).length,
};
submitChat('/todo');
await submitChat('/todo');
const todoMessages = await screen.findAllByText(/下一轮小步:/);
const todoMessage = messageBubble(todoMessages[todoMessages.length - 1]);
expect(todoMessage.textContent).toContain('项目:未命名游戏原型');
@@ -1380,7 +1361,7 @@ export async function assertPlanningAndStatusShortcutFlow(
{ name: '查看任务' },
);
fireEvent.click(todoDraftButtons[todoDraftButtons.length - 1]);
expect(screen.getByLabelText('创作想法')).toHaveProperty('value', '/tasks');
expect(await composerText()).toBe('/tasks');
expect(
invoke.mock.calls.filter(
([command]) => command === 'get_local_game_manifest',
@@ -1451,7 +1432,7 @@ export async function assertPlanningAndStatusShortcutFlow(
([command]) => command === 'control_agent_run',
).length,
};
submitChat('/plan');
await submitChat('/plan');
const planMessages = await screen.findAllByText(/下一轮分工计划:/);
const planMessage = messageBubble(planMessages[planMessages.length - 1]);
expect(planMessage.textContent).toContain('项目:未命名游戏原型');
@@ -1479,8 +1460,7 @@ export async function assertPlanningAndStatusShortcutFlow(
{ name: '继续执行计划' },
);
fireEvent.click(planDraftButtons[planDraftButtons.length - 1]);
expect(screen.getByLabelText('创作想法')).toHaveProperty(
'value',
expect(await composerText()).toBe(
'/agent-resume 下一轮计划:美术组 / Asset 生成首版美术素材',
);
expect(
@@ -1556,7 +1536,7 @@ export async function assertPlanningAndStatusShortcutFlow(
([command]) => command === 'control_agent_run',
).length,
};
submitChat('/guide');
await submitChat('/guide');
const guideMessages = await screen.findAllByText(/使用导引:/);
const guideMessage = messageBubble(guideMessages[guideMessages.length - 1]);
expect(guideMessage.textContent).toContain('项目:未命名游戏原型');
@@ -1576,7 +1556,7 @@ export async function assertPlanningAndStatusShortcutFlow(
guideMessageList as HTMLElement,
).getAllByRole('button', { name: '执行导引建议' });
fireEvent.click(guideDraftButtons[guideDraftButtons.length - 1]);
expect(screen.getByLabelText('创作想法')).toHaveProperty('value', '/run');
expect(await composerText()).toBe('/run');
expect(
invoke.mock.calls.filter(
([command]) => command === 'get_local_game_manifest',
@@ -1650,7 +1630,7 @@ export async function assertPlanningAndStatusShortcutFlow(
([command]) => command === 'control_agent_run',
).length,
};
submitChat('/progress');
await submitChat('/progress');
const progressMessages = await screen.findAllByText(/项目进度:/);
const progressMessage = messageBubble(
progressMessages[progressMessages.length - 1],
@@ -1680,7 +1660,7 @@ export async function assertPlanningAndStatusShortcutFlow(
progressMessageList as HTMLElement,
).getAllByRole('button', { name: '启动预览' });
fireEvent.click(progressDraftButtons[progressDraftButtons.length - 1]);
expect(screen.getByLabelText('创作想法')).toHaveProperty('value', '/run');
expect(await composerText()).toBe('/run');
expect(
invoke.mock.calls.filter(
([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 { messageBubble } from './message-bubble';
@@ -8,7 +15,7 @@ export async function assertPlaytestAndReleaseShortcutFlow(
const runLocalCountBeforeNext = invoke.mock.calls.filter(
([command]) => command === 'run_limited_local_command',
).length;
submitChat('/next');
await submitChat('/next');
expect(await screen.findByText(/下一步建议:/)).not.toBeNull();
expect(screen.getByText(/运行自检并启动本地预览:\/run/)).not.toBeNull();
expect(screen.getByText(/查看创作目标:\/goal/)).not.toBeNull();
@@ -86,7 +93,7 @@ export async function assertPlaytestAndReleaseShortcutFlow(
name: '运行自检并启动本地预览',
}),
);
expect(screen.getByLabelText('创作想法')).toHaveProperty('value', '/run');
expect(await composerText()).toBe('/run');
expect(
invoke.mock.calls.filter(
([command]) => command === 'run_limited_local_command',
@@ -105,7 +112,7 @@ export async function assertPlaytestAndReleaseShortcutFlow(
const startPreviewCountBeforePublish = invoke.mock.calls.filter(
([command]) => command === 'start_local_game_preview',
).length;
submitChat('/publish');
await submitChat('/publish');
expect(await screen.findByText(/发布准备:/)).not.toBeNull();
expect(
screen.getByText(/原型:最近 run 已通过 run-main-shortcut-trace/),
@@ -121,7 +128,7 @@ export async function assertPlaytestAndReleaseShortcutFlow(
name: '启动预览',
});
fireEvent.click(publishDraftButtons[publishDraftButtons.length - 1]);
expect(screen.getByLabelText('创作想法')).toHaveProperty('value', '/run');
expect(await composerText()).toBe('/run');
expect(
invoke.mock.calls.filter(
([command]) => command === 'get_local_game_manifest',
@@ -169,7 +176,7 @@ export async function assertPlaytestAndReleaseShortcutFlow(
([command]) => command === 'list_local_project_export_packages',
).length,
};
submitChat('/listing');
await submitChat('/listing');
const listingMessages = await screen.findAllByText(/作品页草稿:/);
const listingMessage = messageBubble(
listingMessages[listingMessages.length - 1],
@@ -203,10 +210,7 @@ export async function assertPlaytestAndReleaseShortcutFlow(
listingMessageList as HTMLElement,
).getAllByRole('button', { name: '读发布说明' });
fireEvent.click(listingDraftButtons[listingDraftButtons.length - 1]);
expect(screen.getByLabelText('创作想法')).toHaveProperty(
'value',
'/read exports/README.md',
);
expect(await composerText()).toBe('/read exports/README.md');
expect(
invoke.mock.calls.filter(
([command]) => command === 'get_local_game_manifest',
@@ -263,7 +267,7 @@ export async function assertPlaytestAndReleaseShortcutFlow(
const openPreviewCountBeforePlaytest = invoke.mock.calls.filter(
([command]) => command === 'activate_local_game_preview',
).length;
submitChat('/playtest');
await submitChat('/playtest');
const playtestMessages = await screen.findAllByText(/试玩状态:/);
const playtestMessage = messageBubble(
playtestMessages[playtestMessages.length - 1],
@@ -282,7 +286,7 @@ export async function assertPlaytestAndReleaseShortcutFlow(
name: '启动试玩',
});
fireEvent.click(playtestDraftButtons[playtestDraftButtons.length - 1]);
expect(screen.getByLabelText('创作想法')).toHaveProperty('value', '/run');
expect(await composerText()).toBe('/run');
expect(
invoke.mock.calls.filter(
([command]) => command === 'read_local_project_file',
@@ -332,7 +336,7 @@ export async function assertPlaytestAndReleaseShortcutFlow(
([command]) => command === 'export_local_project_package',
).length,
};
submitChat('/test-plan');
await submitChat('/test-plan');
const testPlanMessages = await screen.findAllByText(/手动测试计划:/);
const testPlanMessage = messageBubble(
testPlanMessages[testPlanMessages.length - 1],
@@ -369,7 +373,7 @@ export async function assertPlaytestAndReleaseShortcutFlow(
testPlanMessageList as HTMLElement,
).getAllByRole('button', { name: '启动试玩' });
fireEvent.click(testPlanDraftButtons[testPlanDraftButtons.length - 1]);
expect(screen.getByLabelText('创作想法')).toHaveProperty('value', '/run');
expect(await composerText()).toBe('/run');
expect(
invoke.mock.calls.filter(
([command]) => command === 'get_local_game_manifest',
@@ -435,7 +439,7 @@ export async function assertPlaytestAndReleaseShortcutFlow(
([command]) => command === 'control_agent_run',
).length,
};
submitChat('/audience');
await submitChat('/audience');
const audienceMessages = await screen.findAllByText(/首批试玩对象:/);
const audienceMessage = messageBubble(
audienceMessages[audienceMessages.length - 1],
@@ -476,7 +480,7 @@ export async function assertPlaytestAndReleaseShortcutFlow(
audienceMessageList as HTMLElement,
).getAllByRole('button', { name: '启动试玩' });
fireEvent.click(audienceDraftButtons[audienceDraftButtons.length - 1]);
expect(screen.getByLabelText('创作想法')).toHaveProperty('value', '/run');
expect(await composerText()).toBe('/run');
expect(
invoke.mock.calls.filter(
([command]) => command === 'get_local_game_manifest',
@@ -550,7 +554,7 @@ export async function assertPlaytestAndReleaseShortcutFlow(
([command]) => command === 'control_agent_run',
).length,
};
submitChat('/invite');
await submitChat('/invite');
const inviteMessages = await screen.findAllByText(/试玩邀请:/);
const inviteMessage = messageBubble(
inviteMessages[inviteMessages.length - 1],
@@ -583,7 +587,7 @@ export async function assertPlaytestAndReleaseShortcutFlow(
inviteMessageList as HTMLElement,
).getAllByRole('button', { name: '启动试玩' });
fireEvent.click(inviteDraftButtons[inviteDraftButtons.length - 1]);
expect(screen.getByLabelText('创作想法')).toHaveProperty('value', '/run');
expect(await composerText()).toBe('/run');
expect(
invoke.mock.calls.filter(
([command]) => command === 'get_local_game_manifest',
@@ -730,7 +734,7 @@ export async function assertPlaytestAndReleaseShortcutFlow(
([command]) => command === 'control_agent_run',
).length,
};
submitChat('/survey');
await submitChat('/survey');
const surveyMessages = await screen.findAllByText(/试玩问卷:/);
const surveyMessage = messageBubble(
surveyMessages[surveyMessages.length - 1],
@@ -765,7 +769,7 @@ export async function assertPlaytestAndReleaseShortcutFlow(
surveyMessageList as HTMLElement,
).getAllByRole('button', { name: '启动试玩' });
fireEvent.click(surveyDraftButtons[surveyDraftButtons.length - 1]);
expect(screen.getByLabelText('创作想法')).toHaveProperty('value', '/run');
expect(await composerText()).toBe('/run');
expect(
invoke.mock.calls.filter(
([command]) => command === 'get_local_game_manifest',
@@ -839,7 +843,7 @@ export async function assertPlaytestAndReleaseShortcutFlow(
([command]) => command === 'control_agent_run',
).length,
};
submitChat('/cover');
await submitChat('/cover');
const coverMessages = await screen.findAllByText(/封面与缩略图:/);
const coverMessage = messageBubble(coverMessages[coverMessages.length - 1]);
expect(coverMessage.textContent).toContain('项目:未命名游戏原型');
@@ -874,7 +878,7 @@ export async function assertPlaytestAndReleaseShortcutFlow(
coverMessageList as HTMLElement,
).getAllByRole('button', { name: '启动试玩' });
fireEvent.click(coverDraftButtons[coverDraftButtons.length - 1]);
expect(screen.getByLabelText('创作想法')).toHaveProperty('value', '/run');
expect(await composerText()).toBe('/run');
expect(
invoke.mock.calls.filter(
([command]) => command === 'get_local_game_manifest',
@@ -948,7 +952,7 @@ export async function assertPlaytestAndReleaseShortcutFlow(
([command]) => command === 'control_agent_run',
).length,
};
submitChat('/screenshots');
await submitChat('/screenshots');
const screenshotMessages = await screen.findAllByText(/宣传截图:/);
const screenshotMessage = messageBubble(
screenshotMessages[screenshotMessages.length - 1],
@@ -985,7 +989,7 @@ export async function assertPlaytestAndReleaseShortcutFlow(
screenshotMessageList as HTMLElement,
).getAllByRole('button', { name: '启动试玩' });
fireEvent.click(screenshotDraftButtons[screenshotDraftButtons.length - 1]);
expect(screen.getByLabelText('创作想法')).toHaveProperty('value', '/run');
expect(await composerText()).toBe('/run');
expect(
invoke.mock.calls.filter(
([command]) => command === 'get_local_game_manifest',
@@ -1059,7 +1063,7 @@ export async function assertPlaytestAndReleaseShortcutFlow(
([command]) => command === 'control_agent_run',
).length,
};
submitChat('/trailer');
await submitChat('/trailer');
const trailerMessages = await screen.findAllByText(/试玩短视频:/);
const trailerMessage = messageBubble(
trailerMessages[trailerMessages.length - 1],
@@ -1097,7 +1101,7 @@ export async function assertPlaytestAndReleaseShortcutFlow(
trailerMessageList as HTMLElement,
).getAllByRole('button', { name: '启动试玩' });
fireEvent.click(trailerDraftButtons[trailerDraftButtons.length - 1]);
expect(screen.getByLabelText('创作想法')).toHaveProperty('value', '/run');
expect(await composerText()).toBe('/run');
expect(
invoke.mock.calls.filter(
([command]) => command === 'get_local_game_manifest',
@@ -1171,7 +1175,7 @@ export async function assertPlaytestAndReleaseShortcutFlow(
([command]) => command === 'control_agent_run',
).length,
};
submitChat('/faq');
await submitChat('/faq');
const faqMessages = await screen.findAllByText(/试玩 FAQ/);
const faqMessage = messageBubble(faqMessages[faqMessages.length - 1]);
expect(faqMessage.textContent).toContain('项目:未命名游戏原型');
@@ -1202,7 +1206,7 @@ export async function assertPlaytestAndReleaseShortcutFlow(
{ name: '启动试玩' },
);
fireEvent.click(faqDraftButtons[faqDraftButtons.length - 1]);
expect(screen.getByLabelText('创作想法')).toHaveProperty('value', '/run');
expect(await composerText()).toBe('/run');
expect(
invoke.mock.calls.filter(
([command]) => command === 'get_local_game_manifest',
@@ -1276,7 +1280,7 @@ export async function assertPlaytestAndReleaseShortcutFlow(
([command]) => command === 'control_agent_run',
).length,
};
submitChat('/post');
await submitChat('/post');
const postMessages = await screen.findAllByText(/社区发布文案:/);
const postMessage = messageBubble(postMessages[postMessages.length - 1]);
expect(postMessage.textContent).toContain('项目:未命名游戏原型');
@@ -1313,7 +1317,7 @@ export async function assertPlaytestAndReleaseShortcutFlow(
{ name: '启动试玩' },
);
fireEvent.click(postDraftButtons[postDraftButtons.length - 1]);
expect(screen.getByLabelText('创作想法')).toHaveProperty('value', '/run');
expect(await composerText()).toBe('/run');
expect(
invoke.mock.calls.filter(
([command]) => command === 'get_local_game_manifest',
@@ -1387,7 +1391,7 @@ export async function assertPlaytestAndReleaseShortcutFlow(
([command]) => command === 'control_agent_run',
).length,
};
submitChat('/store');
await submitChat('/store');
const storeMessages = await screen.findAllByText(/上架资料:/);
const storeMessage = messageBubble(storeMessages[storeMessages.length - 1]);
expect(storeMessage.textContent).toContain('项目:未命名游戏原型');
@@ -1423,7 +1427,7 @@ export async function assertPlaytestAndReleaseShortcutFlow(
storeMessageList as HTMLElement,
).getAllByRole('button', { name: '启动试玩' });
fireEvent.click(storeDraftButtons[storeDraftButtons.length - 1]);
expect(screen.getByLabelText('创作想法')).toHaveProperty('value', '/run');
expect(await composerText()).toBe('/run');
expect(
invoke.mock.calls.filter(
([command]) => command === 'get_local_game_manifest',
@@ -1497,7 +1501,7 @@ export async function assertPlaytestAndReleaseShortcutFlow(
([command]) => command === 'control_agent_run',
).length,
};
submitChat('/media-kit');
await submitChat('/media-kit');
const mediaKitMessages = await screen.findAllByText(/媒体资料包:/);
const mediaKitMessage = messageBubble(
mediaKitMessages[mediaKitMessages.length - 1],
@@ -1532,7 +1536,7 @@ export async function assertPlaytestAndReleaseShortcutFlow(
mediaKitMessageList as HTMLElement,
).getAllByRole('button', { name: '启动试玩' });
fireEvent.click(mediaKitDraftButtons[mediaKitDraftButtons.length - 1]);
expect(screen.getByLabelText('创作想法')).toHaveProperty('value', '/run');
expect(await composerText()).toBe('/run');
expect(
invoke.mock.calls.filter(
([command]) => command === 'get_local_game_manifest',
@@ -1606,7 +1610,7 @@ export async function assertPlaytestAndReleaseShortcutFlow(
([command]) => command === 'control_agent_run',
).length,
};
submitChat('/release-notes');
await submitChat('/release-notes');
const releaseNotesMessages = await screen.findAllByText(/试玩更新说明:/);
const releaseNotesMessage = messageBubble(
releaseNotesMessages[releaseNotesMessages.length - 1],
@@ -1651,7 +1655,7 @@ export async function assertPlaytestAndReleaseShortcutFlow(
fireEvent.click(
releaseNotesDraftButtons[releaseNotesDraftButtons.length - 1],
);
expect(screen.getByLabelText('创作想法')).toHaveProperty('value', '/run');
expect(await composerText()).toBe('/run');
expect(
invoke.mock.calls.filter(
([command]) => command === 'get_local_game_manifest',
@@ -1725,7 +1729,7 @@ export async function assertPlaytestAndReleaseShortcutFlow(
([command]) => command === 'control_agent_run',
).length,
};
submitChat('/known-issues');
await submitChat('/known-issues');
const knownIssueMessages = await screen.findAllByText(/已知问题清单:/);
const knownIssueMessage = messageBubble(
knownIssueMessages[knownIssueMessages.length - 1],
@@ -1756,7 +1760,7 @@ export async function assertPlaytestAndReleaseShortcutFlow(
knownIssueMessageList as HTMLElement,
).getAllByRole('button', { name: '启动试玩' });
fireEvent.click(knownIssueDraftButtons[knownIssueDraftButtons.length - 1]);
expect(screen.getByLabelText('创作想法')).toHaveProperty('value', '/run');
expect(await composerText()).toBe('/run');
expect(
invoke.mock.calls.filter(
([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 { messageBubble } from './message-bubble';
@@ -36,14 +43,14 @@ export async function assertProjectAndDesignShortcutFlow(
expect(screen.queryByText(/planner-secret/)).toBeNull();
expect(screen.queryByText(/art-secret/)).toBeNull();
submitChat('/agents');
await submitChat('/agents');
await screen.findByText(/Agent 状态:/);
expect(
/Agent 状态:[\s\S]*美术组 \/ Asset · 生成首版美术素材 · 待处理[\s\S]*智能服务:已连接/.test(
screen.getByLabelText('聊天').textContent ?? '',
),
).toBe(true);
submitChat('/agent-conversations');
await submitChat('/agent-conversations');
const agentConversationMessage =
await screen.findByText('Agent 对话读取命令:');
const agentConversationBubble = messageBubble(agentConversationMessage);
@@ -51,8 +58,7 @@ export async function assertProjectAndDesignShortcutFlow(
'美术组 / Asset · 生成首版美术素材:/read .agent/conversations/agents/art-asset-plan.jsonl',
);
fireEvent.click(screen.getByRole('button', { name: '读取拆解创作方向对话' }));
expect(screen.getByLabelText('创作想法')).toHaveProperty(
'value',
expect(await composerText()).toBe(
'/read .agent/conversations/agents/design-director.jsonl',
);
expect(invoke).not.toHaveBeenCalledWith(
@@ -61,7 +67,7 @@ export async function assertProjectAndDesignShortcutFlow(
relativePath: '.agent/conversations/agents/design-director.jsonl',
}),
);
submitChat('/agent-memories');
await submitChat('/agent-memories');
const agentMemoryMessage =
await screen.findByText('Agent 私有记忆读取命令:');
const agentMemoryBubble = messageBubble(agentMemoryMessage);
@@ -69,10 +75,7 @@ export async function assertProjectAndDesignShortcutFlow(
'美术组 / Asset · 生成首版美术素材:/read memory/agents/art/asset.md',
);
fireEvent.click(screen.getByRole('button', { name: '读取拆解创作方向记忆' }));
expect(screen.getByLabelText('创作想法')).toHaveProperty(
'value',
'/read memory/agents/design/director.md',
);
expect(await composerText()).toBe('/read memory/agents/design/director.md');
expect(invoke).not.toHaveBeenCalledWith(
'read_local_project_file',
expect.objectContaining({
@@ -105,12 +108,12 @@ export async function assertProjectAndDesignShortcutFlow(
const manifestReadCountBeforeBrief = invoke.mock.calls.filter(
([command]) => command === 'get_local_game_manifest',
).length;
submitChat('/brief');
await submitChat('/brief');
expect(await screen.findByText(/项目简报:/)).not.toBeNull();
expect(screen.getByText(/任务:完成 0\/16 · ready 3/)).not.toBeNull();
expect(screen.getByText(/最近 Runrun-main-shortcut-trace/)).not.toBeNull();
fireEvent.click(screen.getByRole('button', { name: '查看下一步' }));
expect(screen.getByLabelText('创作想法')).toHaveProperty('value', '/next');
expect(await composerText()).toBe('/next');
expect(
invoke.mock.calls.filter(
([command]) => command === 'get_local_game_manifest',
@@ -126,7 +129,7 @@ export async function assertProjectAndDesignShortcutFlow(
const runLocalCountBeforeGoal = invoke.mock.calls.filter(
([command]) => command === 'run_limited_local_command',
).length;
submitChat('/goal');
await submitChat('/goal');
expect(await screen.findByText(/创作目标:/)).not.toBeNull();
const goalMessages = screen.getAllByText(/创作目标:/);
const goalMessage = messageBubble(goalMessages[goalMessages.length - 1]);
@@ -139,10 +142,7 @@ export async function assertProjectAndDesignShortcutFlow(
expect(goalMessage.textContent).toContain('上下文:/context');
expect(goalMessage.textContent).toContain('建议:/agent-resume 细化目标:');
fireEvent.click(screen.getByRole('button', { name: '补充目标' }));
expect(screen.getByLabelText('创作想法')).toHaveProperty(
'value',
'/agent-resume 细化目标:',
);
expect(await composerText()).toBe('/agent-resume 细化目标:');
expect(
invoke.mock.calls.filter(
([command]) => command === 'get_local_game_manifest',
@@ -185,7 +185,7 @@ export async function assertProjectAndDesignShortcutFlow(
([command]) => command === 'list_local_project_export_packages',
).length,
};
submitChat('/spec');
await submitChat('/spec');
const specMessages = await screen.findAllByText(/创作规格包:/);
const specMessage = messageBubble(specMessages[specMessages.length - 1]);
expect(specMessage.textContent).toContain('项目:未命名游戏原型');
@@ -213,10 +213,7 @@ export async function assertProjectAndDesignShortcutFlow(
{ name: '读取规格' },
);
fireEvent.click(specDraftButtons[specDraftButtons.length - 1]);
expect(screen.getByLabelText('创作想法')).toHaveProperty(
'value',
'/read .agent/spec.md',
);
expect(await composerText()).toBe('/read .agent/spec.md');
expect(
invoke.mock.calls.filter(
([command]) => command === 'get_local_game_manifest',
@@ -284,7 +281,7 @@ export async function assertProjectAndDesignShortcutFlow(
([command]) => command === 'list_local_project_export_packages',
).length,
};
submitChat('/mvp');
await submitChat('/mvp');
expect(await screen.findByText(/MVP 范围:/)).not.toBeNull();
const mvpMessages = screen.getAllByText(/MVP 范围:/);
const mvpMessage = messageBubble(mvpMessages[mvpMessages.length - 1]);
@@ -313,7 +310,7 @@ export async function assertProjectAndDesignShortcutFlow(
{ name: '启动预览' },
);
fireEvent.click(mvpDraftButtons[mvpDraftButtons.length - 1]);
expect(screen.getByLabelText('创作想法')).toHaveProperty('value', '/run');
expect(await composerText()).toBe('/run');
expect(
invoke.mock.calls.filter(
([command]) => command === 'get_local_game_manifest',
@@ -381,7 +378,7 @@ export async function assertProjectAndDesignShortcutFlow(
([command]) => command === 'list_local_project_export_packages',
).length,
};
submitChat('/pitch');
await submitChat('/pitch');
const pitchMessages = await screen.findAllByText(/试玩定位:/);
const pitchMessage = messageBubble(pitchMessages[pitchMessages.length - 1]);
expect(pitchMessage.textContent).toContain('项目:未命名游戏原型');
@@ -408,7 +405,7 @@ export async function assertProjectAndDesignShortcutFlow(
pitchMessageList as HTMLElement,
).getAllByRole('button', { name: '启动预览' });
fireEvent.click(pitchDraftButtons[pitchDraftButtons.length - 1]);
expect(screen.getByLabelText('创作想法')).toHaveProperty('value', '/run');
expect(await composerText()).toBe('/run');
expect(
invoke.mock.calls.filter(
([command]) => command === 'get_local_game_manifest',
@@ -476,7 +473,7 @@ export async function assertProjectAndDesignShortcutFlow(
([command]) => command === 'list_local_project_export_packages',
).length,
};
submitChat('/demo');
await submitChat('/demo');
const demoMessages = await screen.findAllByText(/试玩讲解稿:/);
const demoMessage = messageBubble(demoMessages[demoMessages.length - 1]);
expect(demoMessage.textContent).toContain('项目:未命名游戏原型');
@@ -510,7 +507,7 @@ export async function assertProjectAndDesignShortcutFlow(
{ name: '启动预览' },
);
fireEvent.click(demoDraftButtons[demoDraftButtons.length - 1]);
expect(screen.getByLabelText('创作想法')).toHaveProperty('value', '/run');
expect(await composerText()).toBe('/run');
expect(
invoke.mock.calls.filter(
([command]) => command === 'get_local_game_manifest',
@@ -575,7 +572,7 @@ export async function assertProjectAndDesignShortcutFlow(
([command]) => command === 'export_local_project_package',
).length,
};
submitChat('/rules');
await submitChat('/rules');
expect(await screen.findByText(/玩法操作:/)).not.toBeNull();
const controlMessages = screen.getAllByText(/玩法操作:/);
const controlMessage = messageBubble(
@@ -616,8 +613,7 @@ export async function assertProjectAndDesignShortcutFlow(
controlMessageList as HTMLElement,
).getAllByRole('button', { name: '补充操作说明' });
fireEvent.click(controlDraftButtons[controlDraftButtons.length - 1]);
expect(screen.getByLabelText('创作想法')).toHaveProperty(
'value',
expect(await composerText()).toBe(
'/agent-resume 操作说明:在首屏明确移动/点击操作、胜负目标、失败后重开方式',
);
expect(
@@ -682,7 +678,7 @@ export async function assertProjectAndDesignShortcutFlow(
([command]) => command === 'list_local_project_export_packages',
).length,
};
submitChat('/tutorial');
await submitChat('/tutorial');
const tutorialMessages = await screen.findAllByText(/新手引导:/);
const tutorialMessage = messageBubble(
tutorialMessages[tutorialMessages.length - 1],
@@ -716,8 +712,7 @@ export async function assertProjectAndDesignShortcutFlow(
tutorialMessageList as HTMLElement,
).getAllByRole('button', { name: '补充新手引导' });
fireEvent.click(tutorialDraftButtons[tutorialDraftButtons.length - 1]);
expect(screen.getByLabelText('创作想法')).toHaveProperty(
'value',
expect(await composerText()).toBe(
'/agent-resume 新手引导:在首屏加入目标、操作、反馈、失败重开提示',
);
expect(
@@ -787,7 +782,7 @@ export async function assertProjectAndDesignShortcutFlow(
([command]) => command === 'list_local_project_export_packages',
).length,
};
submitChat('/mobile');
await submitChat('/mobile');
const mobileMessages = await screen.findAllByText(/移动试玩:/);
const mobileMessage = messageBubble(
mobileMessages[mobileMessages.length - 1],
@@ -824,8 +819,7 @@ export async function assertProjectAndDesignShortcutFlow(
mobileMessageList as HTMLElement,
).getAllByRole('button', { name: '补充移动试玩' });
fireEvent.click(mobileDraftButtons[mobileDraftButtons.length - 1]);
expect(screen.getByLabelText('创作想法')).toHaveProperty(
'value',
expect(await composerText()).toBe(
'/agent-resume 移动试玩:补充触屏操作、响应式画布、横竖屏提示、重开按钮',
);
expect(
@@ -898,7 +892,7 @@ export async function assertProjectAndDesignShortcutFlow(
([command]) => command === 'control_agent_run',
).length,
};
submitChat('/compatibility');
await submitChat('/compatibility');
const compatibilityMessages = await screen.findAllByText(/兼容性说明:/);
const compatibilityMessage = messageBubble(
compatibilityMessages[compatibilityMessages.length - 1],
@@ -940,7 +934,7 @@ export async function assertProjectAndDesignShortcutFlow(
fireEvent.click(
compatibilityDraftButtons[compatibilityDraftButtons.length - 1],
);
expect(screen.getByLabelText('创作想法')).toHaveProperty('value', '/run');
expect(await composerText()).toBe('/run');
expect(
invoke.mock.calls.filter(
([command]) => command === 'get_local_game_manifest',
@@ -1014,7 +1008,7 @@ export async function assertProjectAndDesignShortcutFlow(
([command]) => command === 'control_agent_run',
).length,
};
submitChat('/accessibility');
await submitChat('/accessibility');
const accessibilityMessages = await screen.findAllByText(/可读性与无障碍:/);
const accessibilityMessage = messageBubble(
accessibilityMessages[accessibilityMessages.length - 1],
@@ -1061,8 +1055,7 @@ export async function assertProjectAndDesignShortcutFlow(
fireEvent.click(
accessibilityDraftButtons[accessibilityDraftButtons.length - 1],
);
expect(screen.getByLabelText('创作想法')).toHaveProperty(
'value',
expect(await composerText()).toBe(
'/agent-resume 可读性与无障碍:补充文字对比、清晰按钮标签、键盘等价操作、非颜色唯一反馈、静音可玩',
);
expect(
@@ -1138,7 +1131,7 @@ export async function assertProjectAndDesignShortcutFlow(
([command]) => command === 'control_agent_run',
).length,
};
submitChat('/localization');
await submitChat('/localization');
const localizationMessages = await screen.findAllByText(/本地化与文案:/);
const localizationMessage = messageBubble(
localizationMessages[localizationMessages.length - 1],
@@ -1192,10 +1185,7 @@ export async function assertProjectAndDesignShortcutFlow(
fireEvent.click(
localizationDraftButtons[localizationDraftButtons.length - 1],
);
expect(screen.getByLabelText('创作想法')).toHaveProperty(
'value',
'/read exports/README.md',
);
expect(await composerText()).toBe('/read exports/README.md');
expect(
invoke.mock.calls.filter(
([command]) => command === 'get_local_game_manifest',
@@ -1269,7 +1259,7 @@ export async function assertProjectAndDesignShortcutFlow(
([command]) => command === 'control_agent_run',
).length,
};
submitChat('/performance');
await submitChat('/performance');
const performanceMessages = await screen.findAllByText(/性能与加载:/);
const performanceMessage = messageBubble(
performanceMessages[performanceMessages.length - 1],
@@ -1310,10 +1300,7 @@ export async function assertProjectAndDesignShortcutFlow(
performanceMessageList as HTMLElement,
).getAllByRole('button', { name: '列出 Run 产物' });
fireEvent.click(performanceDraftButtons[performanceDraftButtons.length - 1]);
expect(screen.getByLabelText('创作想法')).toHaveProperty(
'value',
'/run-artifacts',
);
expect(await composerText()).toBe('/run-artifacts');
expect(
invoke.mock.calls.filter(
([command]) => command === 'get_local_game_manifest',
@@ -1387,7 +1374,7 @@ export async function assertProjectAndDesignShortcutFlow(
([command]) => command === 'control_agent_run',
).length,
};
submitChat('/polish');
await submitChat('/polish');
const polishMessages = await screen.findAllByText(/试玩前打磨:/);
const polishMessage = messageBubble(
polishMessages[polishMessages.length - 1],
@@ -1427,8 +1414,7 @@ export async function assertProjectAndDesignShortcutFlow(
polishMessageList as HTMLElement,
).getAllByRole('button', { name: '补充打磨' });
fireEvent.click(polishDraftButtons[polishDraftButtons.length - 1]);
expect(screen.getByLabelText('创作想法')).toHaveProperty(
'value',
expect(await composerText()).toBe(
'/agent-resume 打磨:补齐新手引导、触屏操作、可读性、性能、素材署名和试玩反馈',
);
expect(
@@ -1501,7 +1487,7 @@ export async function assertProjectAndDesignShortcutFlow(
([command]) => command === 'list_local_project_export_packages',
).length,
};
submitChat('/credits');
await submitChat('/credits');
const creditMessages = await screen.findAllByText(/素材署名:/);
const creditMessage = messageBubble(
creditMessages[creditMessages.length - 1],
@@ -1527,7 +1513,7 @@ export async function assertProjectAndDesignShortcutFlow(
creditMessageList as HTMLElement,
).getAllByRole('button', { name: '查看资产' });
fireEvent.click(creditDraftButtons[creditDraftButtons.length - 1]);
expect(screen.getByLabelText('创作想法')).toHaveProperty('value', '/assets');
expect(await composerText()).toBe('/assets');
expect(
invoke.mock.calls.filter(
([command]) => command === 'get_local_game_manifest',
@@ -1,4 +1,6 @@
import {
composerText,
composerValue,
expect,
fireEvent,
screen,
@@ -35,7 +37,7 @@ export async function assertProjectToolsAndPreviewFlow(
([command]) => command === 'export_local_project_package',
).length,
};
submitChat('/feedback');
await submitChat('/feedback');
const feedbackMessages = await screen.findAllByText(/试玩反馈:/);
const feedbackMessage = messageBubble(
feedbackMessages[feedbackMessages.length - 1],
@@ -60,7 +62,7 @@ export async function assertProjectToolsAndPreviewFlow(
feedbackMessageList as HTMLElement,
).getAllByRole('button', { name: '启动预览' });
fireEvent.click(feedbackDraftButtons[feedbackDraftButtons.length - 1]);
expect(screen.getByLabelText('创作想法')).toHaveProperty('value', '/run');
expect(await composerText()).toBe('/run');
expect(
invoke.mock.calls.filter(
([command]) => command === 'get_local_game_manifest',
@@ -126,7 +128,7 @@ export async function assertProjectToolsAndPreviewFlow(
([command]) => command === 'control_agent_run',
).length,
};
submitChat('/retention');
await submitChat('/retention');
const retentionMessages = await screen.findAllByText(/复玩观察:/);
const retentionMessage = messageBubble(
retentionMessages[retentionMessages.length - 1],
@@ -165,7 +167,7 @@ export async function assertProjectToolsAndPreviewFlow(
retentionMessageList as HTMLElement,
).getAllByRole('button', { name: '启动试玩' });
fireEvent.click(retentionDraftButtons[retentionDraftButtons.length - 1]);
expect(screen.getByLabelText('创作想法')).toHaveProperty('value', '/run');
expect(await composerText()).toBe('/run');
expect(
invoke.mock.calls.filter(
([command]) => command === 'get_local_game_manifest',
@@ -236,7 +238,7 @@ export async function assertProjectToolsAndPreviewFlow(
([command]) => command === 'list_local_project_export_packages',
).length,
};
submitChat('/share');
await submitChat('/share');
const shareMessages = await screen.findAllByText(/试玩交付:/);
const shareMessage = messageBubble(shareMessages[shareMessages.length - 1]);
expect(shareMessage.textContent).toContain('项目:未命名游戏原型');
@@ -260,7 +262,7 @@ export async function assertProjectToolsAndPreviewFlow(
shareMessageList as HTMLElement,
).getAllByRole('button', { name: '导出试玩包' });
fireEvent.click(shareDraftButtons[shareDraftButtons.length - 1]);
expect(screen.getByLabelText('创作想法')).toHaveProperty('value', '/export');
expect(await composerText()).toBe('/export');
expect(
invoke.mock.calls.filter(
([command]) => command === 'get_local_game_manifest',
@@ -306,39 +308,39 @@ export async function assertProjectToolsAndPreviewFlow(
expect(await screen.findByText(/策略:\.agent\/policy\.json/)).not.toBeNull();
const composerInput = screen.getByLabelText('创作想法');
fireEvent.click(screen.getByRole('button', { name: '索引确认' }));
expect(composerInput).toHaveProperty(
'value',
expect(await composerValue(composerInput)).toBe(
'/policy-confirm project.index',
);
fireEvent.click(screen.getByRole('button', { name: '资产登记确认' }));
expect(composerInput).toHaveProperty(
'value',
expect(await composerValue(composerInput)).toBe(
'/policy-confirm asset.register',
);
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: '预览确认' }));
expect(composerInput).toHaveProperty(
'value',
expect(await composerValue(composerInput)).toBe(
'/policy-confirm preview.start',
);
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: '停止预览确认' }));
expect(composerInput).toHaveProperty('value', '/policy-confirm preview.stop');
expect(await composerValue(composerInput)).toBe(
'/policy-confirm preview.stop',
);
fireEvent.click(screen.getByRole('button', { name: 'Agent确认' }));
expect(composerInput).toHaveProperty(
'value',
expect(await composerValue(composerInput)).toBe(
'/policy-confirm agent.run_status',
);
fireEvent.click(screen.getByRole('button', { name: '读对话确认' }));
expect(composerInput).toHaveProperty(
'value',
expect(await composerValue(composerInput)).toBe(
'/policy-confirm conversation.read',
);
fireEvent.click(screen.getByRole('button', { name: '存对话确认' }));
expect(composerInput).toHaveProperty(
'value',
expect(await composerValue(composerInput)).toBe(
'/policy-confirm conversation.write',
);
expect(screen.queryByText('project.policy_write')).toBeNull();
@@ -366,7 +368,7 @@ export async function assertProjectToolsAndPreviewFlow(
'relativePath' in args &&
args.relativePath === 'assets/manifest.art.json',
).length;
submitChat('/art');
await submitChat('/art');
expect(await screen.findByText(/美术素材:1 个/)).not.toBeNull();
expect(screen.getByText(/来源:画板 1/)).not.toBeNull();
expect(
@@ -376,8 +378,7 @@ export async function assertProjectToolsAndPreviewFlow(
name: '读美术清单',
});
fireEvent.click(visualDraftButtons[visualDraftButtons.length - 1]);
expect(composerInput).toHaveProperty(
'value',
expect(await composerValue(composerInput)).toBe(
'/read assets/manifest.art.json',
);
expect(
@@ -396,7 +397,7 @@ export async function assertProjectToolsAndPreviewFlow(
'relativePath' in args &&
args.relativePath === 'assets/manifest.audio.json',
).length;
submitChat('/audio');
await submitChat('/audio');
expect(await screen.findByText(/音频素材:暂无登记音频/)).not.toBeNull();
expect(
screen.getByText(/可先登记项目内音效,或把已有画板音频作为素材导入/),
@@ -405,8 +406,7 @@ export async function assertProjectToolsAndPreviewFlow(
name: '登记音效',
});
fireEvent.click(audioDraftButtons[audioDraftButtons.length - 1]);
expect(composerInput).toHaveProperty(
'value',
expect(await composerValue(composerInput)).toBe(
'/asset-register assets/audio/sfx.wav audio audio/wav',
);
expect(
@@ -431,25 +431,25 @@ export async function assertProjectToolsAndPreviewFlow(
name: buttonName,
}),
);
expect(composerInput).toHaveProperty('value', draftValue);
expect(await composerValue(composerInput)).toBe(draftValue);
await waitFor(() => expect(document.activeElement).toBe(composerInput));
}
submitChat('/artifacts');
await submitChat('/artifacts');
expect(await screen.findByText(/常用生成产物:/)).not.toBeNull();
expect(screen.getByText(/读入口 · game\/index\.html/)).not.toBeNull();
expect(screen.getByText(/读发布说明 · exports\/README\.md/)).not.toBeNull();
const readEntryButtons = screen.getAllByRole('button', { name: '读入口' });
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));
submitChat('/run-artifacts');
await submitChat('/run-artifacts');
const runArtifactsHeading =
await screen.findByText('最近 Run 产物读取命令:');
expect(messageBubble(runArtifactsHeading).textContent).toContain(
'exports/README.md · 128B · fnv1a64:exports/read exports/README.md',
);
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(
'read_local_project_file',
expect.objectContaining({
@@ -459,7 +459,7 @@ export async function assertProjectToolsAndPreviewFlow(
const fileReadCountBeforePasses = invoke.mock.calls.filter(
([command]) => command === 'read_local_project_file',
).length;
submitChat('/passes');
await submitChat('/passes');
const passArtifactMessages =
await screen.findAllByText(/Agent 轮次产物读取命令:/);
const passArtifactMessage = messageBubble(
@@ -475,8 +475,7 @@ export async function assertProjectToolsAndPreviewFlow(
name: '读取首个轮次产物',
});
fireEvent.click(passArtifactButtons[passArtifactButtons.length - 1]);
expect(composerInput).toHaveProperty(
'value',
expect(await composerValue(composerInput)).toBe(
'/read .agent/passes/pass-1/agenda.md',
);
expect(
@@ -484,7 +483,7 @@ export async function assertProjectToolsAndPreviewFlow(
([command]) => command === 'read_local_project_file',
),
).toHaveLength(fileReadCountBeforePasses);
submitChat('/logs');
await submitChat('/logs');
expect(await screen.findByText(/常用日志读取命令:/)).not.toBeNull();
expect(
screen.getByText(/读命令日志 · \.agent\/logs\/command\.log/),
@@ -506,8 +505,7 @@ export async function assertProjectToolsAndPreviewFlow(
name: '读命令日志',
}),
);
expect(composerInput).toHaveProperty(
'value',
expect(await composerValue(composerInput)).toBe(
'/read .agent/logs/command.log',
);
expect(
@@ -520,30 +518,28 @@ export async function assertProjectToolsAndPreviewFlow(
),
).toHaveLength(commandLogReadCountBefore);
fireEvent.click(screen.getByRole('button', { name: '打开画板' }));
expect(composerInput).toHaveProperty('value', '/canvas ');
expect(await composerValue(composerInput)).toBe('/canvas ');
await waitFor(() => expect(document.activeElement).toBe(composerInput));
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));
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));
fireEvent.click(
within(screen.getByLabelText('预览快捷操作')).getByRole('button', {
name: '登记音效',
}),
);
expect(composerInput).toHaveProperty(
'value',
expect(await composerValue(composerInput)).toBe(
'/asset-register assets/audio/sfx.wav audio audio/wav',
);
await waitFor(() => expect(document.activeElement).toBe(composerInput));
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));
fireEvent.click(screen.getByRole('button', { name: '导入画板音频' }));
expect(composerInput).toHaveProperty(
'value',
expect(await composerValue(composerInput)).toBe(
'/import-canvas-asset assets/audio/sfx.wav ',
);
await waitFor(() => expect(document.activeElement).toBe(composerInput));
@@ -560,8 +556,7 @@ export async function assertProjectToolsAndPreviewFlow(
name: '填入读取 assets/uploads/hero.txt',
}),
);
expect(composerInput).toHaveProperty(
'value',
expect(await composerValue(composerInput)).toBe(
'/read assets/uploads/hero.txt',
);
@@ -581,7 +576,7 @@ export async function assertProjectToolsAndPreviewFlow(
screen.getByText(/exports\/README\.md · fnv1a64:exports/),
).not.toBeNull();
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', {
projectPath: '/tmp/authorized-game',
relativePath: 'exports/README.md',
@@ -601,14 +596,13 @@ export async function assertProjectToolsAndPreviewFlow(
name: '填入读取 game/index.html',
}),
);
expect(composerInput).toHaveProperty('value', '/read game/index.html');
expect(await composerValue(composerInput)).toBe('/read game/index.html');
fireEvent.click(
within(screen.getByLabelText('最近项目文件')).getByRole('button', {
name: '登记资产 game/index.html',
}),
);
expect(composerInput).toHaveProperty(
'value',
expect(await composerValue(composerInput)).toBe(
'/asset-register game/index.html document text/html',
);
expect(invoke).not.toHaveBeenCalledWith(
@@ -631,11 +625,11 @@ export async function assertProjectToolsAndPreviewFlow(
expect(await screen.findByText(/黑板记忆:/)).not.toBeNull();
expect(screen.getByText(/跨 agent 共享约束/)).not.toBeNull();
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: '覆盖黑板' }));
expect(composerInput).toHaveProperty('value', '/memory-set blackboard ');
expect(await composerValue(composerInput)).toBe('/memory-set blackboard ');
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: '快照' }));
expect(await screen.findByText('project.checkpoint')).not.toBeNull();
@@ -661,13 +655,13 @@ export async function assertProjectToolsAndPreviewFlow(
name: '填入对比 checkpoint-main',
}),
);
expect(composerInput).toHaveProperty('value', '/diff checkpoint-main');
expect(await composerValue(composerInput)).toBe('/diff checkpoint-main');
fireEvent.click(
within(checkpointList).getByRole('button', {
name: '填入回滚 checkpoint-main',
}),
);
expect(composerInput).toHaveProperty('value', '/restore checkpoint-main');
expect(await composerValue(composerInput)).toBe('/restore checkpoint-main');
fireEvent.click(
within(checkpointList).getByRole('button', {
name: '回滚 checkpoint-main',
@@ -717,12 +711,9 @@ export async function assertProjectToolsAndPreviewFlow(
name: '显示目录',
});
fireEvent.click(exportRevealButtons[exportRevealButtons.length - 1]);
expect(screen.getByLabelText('创作想法')).toHaveProperty(
'value',
'/open-project',
);
expect(await composerText()).toBe('/open-project');
submitChat('/export');
await submitChat('/export');
await waitFor(() =>
expect(screen.getAllByText('准备导出本地试玩包。').length).toBeGreaterThan(
1,
@@ -733,7 +724,7 @@ export async function assertProjectToolsAndPreviewFlow(
);
fireEvent.click(screen.getByRole('button', { name: '取消' }));
submitChat('/exports');
await submitChat('/exports');
expect(await screen.findByText(/本地试玩包:/)).not.toBeNull();
expect(
screen.getByText(/exports\/playtest-package-002\.zip · 2048B/),
@@ -748,10 +739,7 @@ export async function assertProjectToolsAndPreviewFlow(
name: '显示目录',
});
fireEvent.click(exportListRevealButtons[exportListRevealButtons.length - 1]);
expect(screen.getByLabelText('创作想法')).toHaveProperty(
'value',
'/open-project',
);
expect(await composerText()).toBe('/open-project');
fireEvent.click(screen.getByRole('button', { name: '启动预览' }));
expect(await screen.findByText('preview.start')).not.toBeNull();
@@ -1,4 +1,5 @@
import {
composerText,
createGameCreationAppManifest,
createGameCreationAppSeedTasks,
emptyProjectPolicy,
@@ -455,7 +456,7 @@ export function registerProjectRunHistoryTests() {
const runReadCountBeforeRunsCommand = invoke.mock.calls.filter(
([command]) => command === 'read_local_project_file',
).length;
submitChat('/runs');
await submitChat('/runs');
expect(await screen.findByText(/Run 历史读取命令:/)).not.toBeNull();
expect(
screen.getByText(
@@ -463,8 +464,7 @@ export function registerProjectRunHistoryTests() {
),
).not.toBeNull();
fireEvent.click(screen.getByRole('button', { name: '读取首个历史 Run' }));
expect(screen.getByLabelText('创作想法')).toHaveProperty(
'value',
expect(await composerText()).toBe(
'/read .agent/runs/run-hidden-from-chat.json',
);
expect(
@@ -1,4 +1,5 @@
import {
composerText,
createGameCreationAppManifest,
createGameCreationAppSeedTasks,
emptyProjectPolicy,
@@ -162,10 +163,7 @@ export function registerProjectRunStatusTests() {
name: '填入读取产物 exports/artifact-1.json',
}),
);
expect(screen.getByLabelText('创作想法')).toHaveProperty(
'value',
'/read exports/artifact-1.json',
);
expect(await composerText()).toBe('/read exports/artifact-1.json');
fireEvent.click(screen.getByRole('button', { name: '刷新状态' }));
expect(
@@ -2,6 +2,7 @@ import {
act,
agentRuntimeUserInputRequest,
cleanup,
composerDisabled,
createGameCreationAppManifest,
createGameCreationAppSeedTasks,
createProjectSupervisorRuntimeHarness,
@@ -40,7 +41,7 @@ export function registerSupervisorRuntimeTests() {
);
});
submitChat('做一个反弹弹幕厨房游戏');
await submitChat('做一个反弹弹幕厨房游戏');
await waitFor(() => {
expect(harness.invoke).toHaveBeenCalledWith(
@@ -284,7 +285,7 @@ export function registerSupervisorRuntimeTests() {
await openMainProject(harness.projectPath);
expect(await screen.findByText('项目总控 Agent · 已完成')).not.toBeNull();
submitChat('开始真正的新一轮实现');
await submitChat('开始真正的新一轮实现');
await waitFor(() => {
expect(
harness.invoke.mock.calls.filter(
@@ -429,7 +430,7 @@ export function registerSupervisorRuntimeTests() {
),
);
submitChat('切换为自主构建并完成可玩原型');
await submitChat('切换为自主构建并完成可玩原型');
await waitFor(() => {
expect(
harness.invoke.mock.calls.filter(
@@ -479,7 +480,7 @@ export function registerSupervisorRuntimeTests() {
),
);
submitChat('补充:先检查已有素材');
await submitChat('补充:先检查已有素材');
await waitFor(() => {
expect(harness.invoke).toHaveBeenCalledWith(
@@ -689,9 +690,7 @@ export function registerSupervisorRuntimeTests() {
const card = await screen.findByLabelText('Needs input');
expect(screen.getByText('项目总控 Agent · 需要回答')).not.toBeNull();
expect(
(screen.getByLabelText('创作想法') as HTMLInputElement).disabled,
).toBe(true);
expect(await composerDisabled()).toBe(true);
expect(screen.getByRole('button', { name: '等待回答' })).not.toBeNull();
fireEvent.click(within(card).getByRole('button', { name: /像素风/ }));
fireEvent.click(within(card).getByRole('button', { name: '提交回答' }));
@@ -1110,7 +1109,7 @@ export function registerSupervisorRuntimeTests() {
expect.any(Function),
);
});
submitChat('完成本轮总控任务');
await submitChat('完成本轮总控任务');
await waitFor(() => {
expect(
harness.invoke.mock.calls.filter(
@@ -1533,7 +1532,7 @@ export function registerSupervisorRuntimeTests() {
window.__TAURI__ = { core: { invoke }, event: { listen } };
renderAppAt('/');
submitChat('/project /tmp/authorized-game');
await submitChat('/project /tmp/authorized-game');
fireEvent.click(screen.getByRole('button', { name: '确认' }));
expect(
await screen.findByText('已设置本地项目:/tmp/authorized-game'),
@@ -1840,13 +1839,13 @@ export function registerSupervisorRuntimeTests() {
window.__TAURI__ = { core: { invoke } };
renderAppAt('/');
submitChat('/project /tmp/authorized-game');
await submitChat('/project /tmp/authorized-game');
fireEvent.click(screen.getByRole('button', { name: '确认' }));
expect(
await screen.findByText('已设置本地项目:/tmp/authorized-game'),
).not.toBeNull();
submitChat('/generate 做一个反弹弹幕厨房游戏');
await submitChat('/generate 做一个反弹弹幕厨房游戏');
fireEvent.click(screen.getByRole('button', { name: '确认' }));
expect(
@@ -1950,14 +1949,14 @@ export function registerSupervisorRuntimeTests() {
window.__TAURI__ = { core: { invoke } };
renderAppAt('/');
submitChat('/project /tmp/authorized-game');
await submitChat('/project /tmp/authorized-game');
fireEvent.click(screen.getByRole('button', { name: '确认' }));
expect(
await screen.findByText('已设置本地项目:/tmp/authorized-game'),
).not.toBeNull();
invoke.mockClear();
submitChat('/generate 做一个厨房弹幕游戏');
await submitChat('/generate 做一个厨房弹幕游戏');
fireEvent.click(screen.getByRole('button', { name: '确认' }));
expect(await screen.findByText('生成结果项目路径无效')).not.toBeNull();
@@ -2033,13 +2032,13 @@ export function registerSupervisorRuntimeTests() {
window.__TAURI__ = { core: { invoke } };
renderAppAt('/');
submitChat('/project /tmp/authorized-game');
await submitChat('/project /tmp/authorized-game');
fireEvent.click(screen.getByRole('button', { name: '确认' }));
expect(
await screen.findByText('已设置本地项目:/tmp/authorized-game'),
).not.toBeNull();
submitChat('/generate 做一个厨房弹幕游戏');
await submitChat('/generate 做一个厨房弹幕游戏');
fireEvent.click(screen.getByRole('button', { name: '确认' }));
expect(
@@ -13,6 +13,7 @@ import ProjectDevelopmentView from '../src/view/project-development';
import {
act,
createGameCreationAppManifest,
findResourceDetailButton,
fireEvent,
render,
screen,
@@ -476,7 +477,7 @@ describe('project resource live canvas integration', () => {
const { deriveCalls } = installTauri({ failFirstDerive: true });
render(<DerivedWorkbench />);
fireEvent.click(await screen.findByRole('button', { name: /rules\.md/ }));
fireEvent.click(await findResourceDetailButton('rules.md'));
fireEvent.click(screen.getByRole('button', { name: '编辑资源' }));
expect(await screen.findByText('编辑现有资源')).not.toBeNull();
fireEvent.change(screen.getByLabelText('编辑提示词'), {