From 691d0a8ce60fdfc171e523983896a069be8c5f80 Mon Sep 17 00:00:00 2001 From: Suzumiya Date: Thu, 10 Sep 2026 10:15:50 +0800 Subject: [PATCH 1/2] =?UTF-8?q?=E4=BF=AE=E6=AD=A3=20AGC=20=E8=B5=84?= =?UTF-8?q?=E6=BA=90=E5=8D=A1=E6=B5=8B=E8=AF=95=E6=9F=A5=E8=AF=A2=E7=9A=84?= =?UTF-8?q?=E8=B5=84=E6=BA=90=E8=AF=A6=E6=83=85=E6=8C=89=E9=92=AE=E9=80=89?= =?UTF-8?q?=E6=8B=A9=E5=99=A8=E6=AD=A7=E4=B9=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - appSurface/harness 新增 getResourceDetailButton / findResourceDetailButton / queryResourceDetailButton,按产品可访问名模板「打开资源详情:<分类标签> <资源文件名>」精确匹配,不再用会同时命中 @ 引用按钮的文件名正则 - project-development.suite 的 23 处资源卡按钮查询改用精确查询助手 - home.suite 的 7 处资源卡按钮查询改用精确查询助手 - projectResourceLiveIntegration.test 的 /rules\.md/ 按钮查询改用精确查询助手 - 未改动 src 下任何产品代码,未放宽任何断言 --- .../tests/appSurface/harness.ts | 35 +++++++ .../tests/appSurface/home.suite.ts | 31 +++---- .../appSurface/project-development.suite.ts | 91 ++++++++----------- .../projectResourceLiveIntegration.test.tsx | 3 +- 4 files changed, 87 insertions(+), 73 deletions(-) diff --git a/apps/ai-game-creator-shell/tests/appSurface/harness.ts b/apps/ai-game-creator-shell/tests/appSurface/harness.ts index 01f3dd0c0..d42312188 100644 --- a/apps/ai-game-creator-shell/tests/appSurface/harness.ts +++ b/apps/ai-game-creator-shell/tests/appSurface/harness.ts @@ -199,6 +199,38 @@ function submitChat(value: string) { 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', @@ -1094,8 +1126,10 @@ export { describe, emptyProjectPolicy, expect, + findResourceDetailButton, fireEvent, GAME_CREATION_AGENT_RUN_SCHEMA_VERSION, + getResourceDetailButton, it, mockRoleAgentReply, nativeClipboardMock, @@ -1103,6 +1137,7 @@ export { pickProjectFromLauncher, ProjectDevelopmentView, projectSupervisorResponseStream, + queryResourceDetailButton, React, readFileSync, render, diff --git a/apps/ai-game-creator-shell/tests/appSurface/home.suite.ts b/apps/ai-game-creator-shell/tests/appSurface/home.suite.ts index fdf120ea6..83cb9242a 100644 --- a/apps/ai-game-creator-shell/tests/appSurface/home.suite.ts +++ b/apps/ai-game-creator-shell/tests/appSurface/home.suite.ts @@ -9,10 +9,13 @@ import { createGameCreationAppManifest, createProjectSupervisorRuntimeHarness, expect, + findResourceDetailButton, fireEvent, + getResourceDetailButton, it, nativeClipboardMock, pickProjectFromLauncher, + queryResourceDetailButton, React, render, renderAppAt, @@ -312,14 +315,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 +461,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 +487,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 +662,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 () => { diff --git a/apps/ai-game-creator-shell/tests/appSurface/project-development.suite.ts b/apps/ai-game-creator-shell/tests/appSurface/project-development.suite.ts index 1323d0e6d..806c64f5b 100644 --- a/apps/ai-game-creator-shell/tests/appSurface/project-development.suite.ts +++ b/apps/ai-game-creator-shell/tests/appSurface/project-development.suite.ts @@ -22,14 +22,17 @@ import { createProjectSupervisorRuntimeHarness, emptyProjectPolicy, expect, + findResourceDetailButton, fireEvent, GAME_CREATION_AGENT_RUN_SCHEMA_VERSION, type GameCreationAgentRunTrace, + getResourceDetailButton, it, mockRoleAgentReply, pickProjectFromLauncher, ProjectDevelopmentView, projectSupervisorResponseStream, + queryResourceDetailButton, React, readFileSync, render, @@ -710,7 +713,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 +1285,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 +1607,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 +1725,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 +1768,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 +1898,9 @@ export function registerProjectWorkbenchFoundationTests() { document.querySelectorAll('.game-resource-card-visual > img'), ).toHaveLength(48); }); - const wideImageCard = screen - .getByRole('button', { name: /image-0.png/ }) - .closest('.game-resource-card'); + const wideImageCard = getResourceDetailButton( + 'image-0.png', + ).closest('.game-resource-card'); await waitFor(() => { expect( wideImageCard?.style.getPropertyValue('--resource-card-width'), @@ -1935,7 +1934,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 +2058,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 +2426,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 +2438,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 +2503,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 +2584,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 +2899,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 +2975,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 +3721,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 +3951,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 +4036,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('.game-resource-card'); expect(card).not.toBeNull(); Object.defineProperties(card!, { @@ -4534,7 +4519,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 +4572,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'); diff --git a/apps/ai-game-creator-shell/tests/projectResourceLiveIntegration.test.tsx b/apps/ai-game-creator-shell/tests/projectResourceLiveIntegration.test.tsx index 5c7c00307..5293b7e64 100644 --- a/apps/ai-game-creator-shell/tests/projectResourceLiveIntegration.test.tsx +++ b/apps/ai-game-creator-shell/tests/projectResourceLiveIntegration.test.tsx @@ -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(); - 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('编辑提示词'), { From 53afc8531982791c122899e6933b2b84e319609c Mon Sep 17 00:00:00 2001 From: Suzumiya Date: Thu, 10 Sep 2026 11:39:35 +0800 Subject: [PATCH 2/2] =?UTF-8?q?=E4=BF=AE=E6=AD=A3=20AGC=20=E6=B5=8B?= =?UTF-8?q?=E8=AF=95=E6=8C=89=E6=96=B0=E7=9A=84=20Lexical=20=E8=81=8A?= =?UTF-8?q?=E5=A4=A9=E8=BE=93=E5=85=A5=E5=8C=BA=E9=A9=B1=E5=8A=A8=E4=B8=8E?= =?UTF-8?q?=E6=96=AD=E8=A8=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - appSurface/harness 补齐三处 jsdom 缺口:ClipboardEvent、DragEvent、Range.prototype.getBoundingClientRect;否则 Lexical 的粘贴通路直接抛 ReferenceError / TypeError - appSurface/harness 的 submitChat 改为 async:全选并让编辑器吸收选区、清空、走产品真实 paste 通路写入,再让出一帧让 React 追平 draft,最后点发送 - 新增 composerText / composerValue / composerDisabled / setComposerText 助手,按原生控件与 Lexical contenteditable 两种 DOM 口径读写输入区 - 15 个测试文件中 116 处 toHaveProperty('value', …) 断言等义改写为 await composerText() / await composerValue();1 处 placeholder 断言改查输入区占位文案;2 处 disabled 断言改用 composerDisabled(同时覆盖 data-disabled 与 contenteditable=false);9 处 fireEvent.change 写入改用 setComposerText - 328 处 submitChat 调用补 await,9 个非 async 用例补 async - Godot 输入区键盘语义用例按 Lexical 实际行为改写:Shift+Enter 与组合态 Enter 由编辑器消化并插入换行、不发送、草稿保留 - 未改动 src 下任何产品代码,未放宽或删除断言 --- .../tests/appSurface/agent-runtime.suite.ts | 123 ++++----- .../developer-agent-window.suite.ts | 40 +-- .../tests/appSurface/harness.ts | 176 +++++++++++- .../tests/appSurface/home.suite.ts | 3 +- .../tests/appSurface/project-assets.suite.ts | 124 ++++----- .../appSurface/project-commands.suite.ts | 257 +++++++++--------- .../appSurface/project-conversation.suite.ts | 39 ++- .../appSurface/project-development.suite.ts | 79 +++--- .../assert-planning-and-status-shortcuts.ts | 132 ++++----- .../assert-playtest-and-release-shortcuts.ts | 80 +++--- .../assert-project-and-design-shortcuts.ts | 100 +++---- .../assert-project-tools-and-preview.ts | 122 ++++----- .../project-preview/run-history.suite.ts | 6 +- .../project-preview/run-status.suite.ts | 6 +- .../appSurface/supervisor-runtime.suite.ts | 29 +- 15 files changed, 711 insertions(+), 605 deletions(-) diff --git a/apps/ai-game-creator-shell/tests/appSurface/agent-runtime.suite.ts b/apps/ai-game-creator-shell/tests/appSurface/agent-runtime.suite.ts index de87f3548..3b628ca11 100644 --- a/apps/ai-game-creator-shell/tests/appSurface/agent-runtime.suite.ts +++ b/apps/ai-game-creator-shell/tests/appSurface/agent-runtime.suite.ts @@ -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(/Run:run-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(/run:run-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', { diff --git a/apps/ai-game-creator-shell/tests/appSurface/developer-agent-window.suite.ts b/apps/ai-game-creator-shell/tests/appSurface/developer-agent-window.suite.ts index 9c2516496..a39b53478 100644 --- a/apps/ai-game-creator-shell/tests/appSurface/developer-agent-window.suite.ts +++ b/apps/ai-game-creator-shell/tests/appSurface/developer-agent-window.suite.ts @@ -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(); diff --git a/apps/ai-game-creator-shell/tests/appSurface/harness.ts b/apps/ai-game-creator-shell/tests/appSurface/harness.ts index d42312188..843b4b079 100644 --- a/apps/ai-game-creator-shell/tests/appSurface/harness.ts +++ b/apps/ai-game-creator-shell/tests/appSurface/harness.ts @@ -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 也没有 getBoundingClientRect,Lexical 计算选区矩形时会抛 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(); return { @@ -192,10 +224,111 @@ 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 读 value,Lexical 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: '发送' })); } @@ -1036,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)}`), @@ -1057,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, @@ -1097,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, @@ -1118,6 +1282,9 @@ export { App, AuthenticatedClient, cleanup, + composerDisabled, + composerText, + composerValue, createGameCreationAppManifest, createGameCreationAppSeedTasks, createPlanGddStateView, @@ -1149,6 +1316,7 @@ export { roleAgentMockReply, screen, selectDeveloperAgentChatMode, + setComposerText, submitChat, TEST_LOCAL_PROJECT_PATH, testAuthUser, diff --git a/apps/ai-game-creator-shell/tests/appSurface/home.suite.ts b/apps/ai-game-creator-shell/tests/appSurface/home.suite.ts index 83cb9242a..12907344c 100644 --- a/apps/ai-game-creator-shell/tests/appSurface/home.suite.ts +++ b/apps/ai-game-creator-shell/tests/appSurface/home.suite.ts @@ -24,6 +24,7 @@ import { renderLauncherProjectsAt, screen, selectDeveloperAgentChatMode, + setComposerText, testAuthUser, vi, waitFor, @@ -1657,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( diff --git a/apps/ai-game-creator-shell/tests/appSurface/project-assets.suite.ts b/apps/ai-game-creator-shell/tests/appSurface/project-assets.suite.ts index ef3335a59..e1b9f0b75 100644 --- a/apps/ai-game-creator-shell/tests/appSurface/project-assets.suite.ts +++ b/apps/ai-game-creator-shell/tests/appSurface/project-assets.suite.ts @@ -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(''); @@ -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/, diff --git a/apps/ai-game-creator-shell/tests/appSurface/project-commands.suite.ts b/apps/ai-game-creator-shell/tests/appSurface/project-commands.suite.ts index 528710a1e..a7dc65789 100644 --- a/apps/ai-game-creator-shell/tests/appSurface/project-commands.suite.ts +++ b/apps/ai-game-creator-shell/tests/appSurface/project-commands.suite.ts @@ -1,5 +1,6 @@ import { act, + composerText, createGameCreationAppManifest, createGameCreationAppSeedTasks, emptyProjectPolicy, @@ -238,11 +239,11 @@ export function registerProjectCommandTests() { 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(/6 组协作证据:通过/)).not.toBeNull(); @@ -255,20 +256,20 @@ export function registerProjectCommandTests() { expect(screen.getByText(/返工路由\/Carry-over:通过/)).not.toBeNull(); }); - it('does not create a generation command before a local project is initialized', () => { + it('does not create a generation command before a local project is initialized', async () => { renderAppAt('/?dev'); - submitChat('做一个反弹弹幕厨房游戏'); + await submitChat('做一个反弹弹幕厨房游戏'); expect(screen.getByText('请先用 /project 设置本地项目。')).not.toBeNull(); expect(screen.queryByText('game.generate_draft')).toBeNull(); expect(screen.queryByRole('button', { name: '确认' })).toBeNull(); }); - it('reports unknown slash commands instead of falling back to generation', () => { + it('reports unknown slash commands instead of falling back to generation', async () => { renderAppAt('/?dev'); - submitChat('/unknown-command'); + await submitChat('/unknown-command'); expect( screen.getByText('未知命令:/unknown-command。输入 /help 查看可用命令。'), @@ -277,19 +278,19 @@ export function registerProjectCommandTests() { expect(screen.queryByRole('button', { name: '确认' })).toBeNull(); }); - it('reports missing slash command arguments instead of falling back to generation', () => { + it('reports missing slash command arguments instead of falling back to generation', async () => { renderAppAt('/'); - submitChat('/project'); + await submitChat('/project'); expect(screen.getByText('格式:/project /绝对路径')).not.toBeNull(); - submitChat('/remember'); + await submitChat('/remember'); expect(screen.getByText('请提供要追加的记忆内容。')).not.toBeNull(); - submitChat('/sync-canvas-project'); + await submitChat('/sync-canvas-project'); expect(screen.getByText('请提供画板项目 ID。')).not.toBeNull(); - submitChat('/import-canvas-asset'); + await submitChat('/import-canvas-asset'); expect( screen.getByText( '格式:/import-canvas-asset assets/hero.png 画板项目ID 资源ID|object:资产对象ID', @@ -299,19 +300,19 @@ export function registerProjectCommandTests() { expect(screen.queryByRole('button', { name: '确认' })).toBeNull(); }); - it('rejects relative project paths before confirmation', () => { + it('rejects relative project paths before confirmation', async () => { const invoke = vi.fn(); window.__TAURI__ = { core: { invoke } }; renderAppAt('/'); - submitChat('/project relative-game'); + await submitChat('/project relative-game'); expect(screen.getByText('请提供本地项目绝对路径。')).not.toBeNull(); expect(screen.queryByText('project.create')).toBeNull(); expect(screen.queryByRole('button', { name: '确认' })).toBeNull(); expect(invoke).not.toHaveBeenCalled(); - submitChat('/project /tmp/bad\u0007path'); + await submitChat('/project /tmp/bad\u0007path'); expect(screen.getByText('本地项目路径不能包含控制字符。')).not.toBeNull(); expect(screen.queryByText('project.create')).toBeNull(); @@ -338,7 +339,7 @@ export function registerProjectCommandTests() { const confirm = vi.spyOn(window, 'confirm').mockReturnValue(false); renderAppAt('/'); - submitChat('/project /tmp/main-non-empty-game'); + await submitChat('/project /tmp/main-non-empty-game'); fireEvent.click(screen.getByRole('button', { name: '确认' })); expect( @@ -387,7 +388,7 @@ export function registerProjectCommandTests() { const confirm = vi.spyOn(window, 'confirm').mockReturnValue(true); renderAppAt('/'); - submitChat('/project /tmp/main-non-empty-game'); + await submitChat('/project /tmp/main-non-empty-game'); fireEvent.click(screen.getByRole('button', { name: '确认' })); expect( @@ -489,13 +490,13 @@ export function registerProjectCommandTests() { 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('/restore checkpoint-1'); + await submitChat('/restore checkpoint-1'); fireEvent.click(screen.getByRole('button', { name: '确认' })); expect( @@ -603,13 +604,13 @@ export function registerProjectCommandTests() { 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('/checkpoints'); + await submitChat('/checkpoints'); expect(await screen.findByText(/最近 checkpoint:/)).not.toBeNull(); expect( @@ -703,14 +704,14 @@ export function registerProjectCommandTests() { 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('/checkpoints'); + await submitChat('/checkpoints'); expect( await screen.findByText('准备读取 checkpoint manifest。'), @@ -778,14 +779,14 @@ export function registerProjectCommandTests() { 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('/restore checkpoint-1'); + await submitChat('/restore checkpoint-1'); expect(await screen.findByText(/project\.restore/)).not.toBeNull(); expect(invoke).not.toHaveBeenCalledWith( @@ -846,13 +847,13 @@ export function registerProjectCommandTests() { 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('/diff checkpoint-1'); + await submitChat('/diff checkpoint-1'); expect(await screen.findByText(/checkpoint:checkpoint-1/)).not.toBeNull(); expect(screen.getByText(/- game\/file-20\.ts/)).not.toBeNull(); @@ -908,14 +909,14 @@ export function registerProjectCommandTests() { 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('/diff checkpoint-1'); + await submitChat('/diff checkpoint-1'); expect(await screen.findByText('准备对比项目 checkpoint。')).not.toBeNull(); expect(screen.getByText('project.diff')).not.toBeNull(); @@ -979,16 +980,16 @@ export function registerProjectCommandTests() { 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('/diff ../checkpoint-1'); + await submitChat('/diff ../checkpoint-1'); expect(screen.getByText('checkpoint id 非法。')).not.toBeNull(); - submitChat('/restore checkpoint/evil'); + await submitChat('/restore checkpoint/evil'); expect(screen.getAllByText('checkpoint id 非法。').length).toBe(2); expect(screen.queryByText('project.restore')).toBeNull(); expect(screen.queryByRole('button', { name: '确认' })).toBeNull(); @@ -1055,13 +1056,13 @@ export function registerProjectCommandTests() { 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('/policy'); + await submitChat('/policy'); expect( await screen.findByText(/策略:\.agent\/policy\.json/), @@ -1127,13 +1128,13 @@ export function registerProjectCommandTests() { 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('/policy-deny file.write'); + await submitChat('/policy-deny file.write'); expect(await screen.findByText('准备拒绝命令:file.write')).not.toBeNull(); expect(screen.getByText('project.policy_write')).not.toBeNull(); expect( @@ -1213,13 +1214,13 @@ export function registerProjectCommandTests() { 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-policy-deny design-director file.read'); + await submitChat('/agent-policy-deny design-director file.read'); expect( await screen.findByText('准备拒绝 Agent design-director 命令:file.read'), ).not.toBeNull(); @@ -1295,13 +1296,13 @@ export function registerProjectCommandTests() { 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('/policy-confirm project.index'); + await submitChat('/policy-confirm project.index'); expect( await screen.findByText('准备确认命令:project.index'), ).not.toBeNull(); @@ -1321,7 +1322,7 @@ export function registerProjectCommandTests() { }, }); - submitChat('/policy-auto project.index'); + await submitChat('/policy-auto project.index'); expect( await screen.findByText('准备自动执行命令:project.index'), ).not.toBeNull(); @@ -1378,13 +1379,13 @@ export function registerProjectCommandTests() { 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('/policy-deny file.write'); + await submitChat('/policy-deny file.write'); expect( await screen.findByText( /写入 \/tmp\/authorized-game\/\.agent\/policy\.json · 拒绝:project\.index、file\.write · 确认:无/, @@ -1401,7 +1402,7 @@ export function registerProjectCommandTests() { }); }); - submitChat('/policy-confirm project.index'); + await submitChat('/policy-confirm project.index'); expect( await screen.findByText( /写入 \/tmp\/authorized-game\/\.agent\/policy\.json · 拒绝:file\.write · 确认:project\.index/, @@ -1418,7 +1419,7 @@ export function registerProjectCommandTests() { }); }); - submitChat('/policy-confirm project.diff'); + await submitChat('/policy-confirm project.diff'); expect( await screen.findByText( /写入 \/tmp\/authorized-game\/\.agent\/policy\.json · 拒绝:file\.write · 确认:project\.index、project\.diff/, @@ -1435,7 +1436,7 @@ export function registerProjectCommandTests() { }); }); - submitChat('/policy-confirm preview.stop'); + await submitChat('/policy-confirm preview.stop'); expect( await screen.findByText( /写入 \/tmp\/authorized-game\/\.agent\/policy\.json · 拒绝:file\.write · 确认:project\.index、project\.diff、preview\.stop/, @@ -1452,7 +1453,7 @@ export function registerProjectCommandTests() { }); }); - submitChat('/policy-confirm preview.open'); + await submitChat('/policy-confirm preview.open'); expect( await screen.findByText( /写入 \/tmp\/authorized-game\/\.agent\/policy\.json · 拒绝:file\.write · 确认:project\.index、project\.diff、preview\.stop、preview\.open/, @@ -1474,7 +1475,7 @@ export function registerProjectCommandTests() { }); }); - submitChat('/policy-confirm preview.start'); + await submitChat('/policy-confirm preview.start'); expect( await screen.findByText( /写入 \/tmp\/authorized-game\/\.agent\/policy\.json · 拒绝:file\.write · 确认:project\.index、project\.diff、preview\.stop、preview\.open、preview\.start/, @@ -1497,7 +1498,7 @@ export function registerProjectCommandTests() { }); }); - submitChat('/policy-confirm agent.run_status'); + await submitChat('/policy-confirm agent.run_status'); expect( await screen.findByText( /写入 \/tmp\/authorized-game\/\.agent\/policy\.json · 拒绝:file\.write · 确认:project\.index、project\.diff、preview\.stop、preview\.open、preview\.start、agent\.run_status/, @@ -1521,7 +1522,7 @@ export function registerProjectCommandTests() { }); }); - submitChat('/policy-confirm agent.kill'); + await submitChat('/policy-confirm agent.kill'); expect( await screen.findByText( /写入 \/tmp\/authorized-game\/\.agent\/policy\.json · 拒绝:file\.write · 确认:project\.index、project\.diff、preview\.stop、preview\.open、preview\.start、agent\.run_status、agent\.kill/, @@ -1546,7 +1547,7 @@ export function registerProjectCommandTests() { }); }); - submitChat('/policy-confirm agent.retry'); + await submitChat('/policy-confirm agent.retry'); expect( await screen.findByText( /写入 \/tmp\/authorized-game\/\.agent\/policy\.json · 拒绝:file\.write · 确认:project\.index、project\.diff、preview\.stop、preview\.open、preview\.start、agent\.run_status、agent\.kill、agent\.retry/, @@ -1572,7 +1573,7 @@ export function registerProjectCommandTests() { }); }); - submitChat('/policy-confirm agent.resume'); + await submitChat('/policy-confirm agent.resume'); expect( await screen.findByText( /写入 \/tmp\/authorized-game\/\.agent\/policy\.json · 拒绝:file\.write · 确认:project\.index、project\.diff、preview\.stop、preview\.open、preview\.start、agent\.run_status、agent\.kill、agent\.retry、agent\.resume/, @@ -1599,7 +1600,7 @@ export function registerProjectCommandTests() { }); }); - submitChat('/policy-confirm asset.register'); + await submitChat('/policy-confirm asset.register'); expect(await screen.findByText(/确认:.*asset\.register/)).not.toBeNull(); fireEvent.click(screen.getByRole('button', { name: '确认' })); await waitFor(() => { @@ -1646,18 +1647,18 @@ export function registerProjectCommandTests() { 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('/policy-allow file.write'); + await submitChat('/policy-allow file.write'); expect( await screen.findByText('命令不在拒绝列表中:file.write'), ).not.toBeNull(); - submitChat('/policy-auto project.index'); + await submitChat('/policy-auto project.index'); expect( await screen.findByText('命令不在确认列表中:project.index'), ).not.toBeNull(); @@ -1709,14 +1710,14 @@ export function registerProjectCommandTests() { 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('/policy-confirm project.checkpoint'); + await submitChat('/policy-confirm project.checkpoint'); expect(await screen.findByText(/确认:project\.checkpoint/)).not.toBeNull(); fireEvent.click(screen.getByRole('button', { name: '确认' })); @@ -1730,7 +1731,7 @@ export function registerProjectCommandTests() { }); }); - submitChat('/policy-confirm project.restore'); + await submitChat('/policy-confirm project.restore'); expect( await screen.findByText(/确认:project\.checkpoint、project\.restore/), @@ -1746,7 +1747,7 @@ export function registerProjectCommandTests() { }); }); - submitChat('/policy-confirm project.export_list'); + await submitChat('/policy-confirm project.export_list'); expect( await screen.findByText( @@ -1810,13 +1811,13 @@ export function registerProjectCommandTests() { 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('/policy-confirm canvas.project_sync'); + await submitChat('/policy-confirm canvas.project_sync'); expect( await screen.findByText(/确认:canvas\.project_sync/), ).not.toBeNull(); @@ -1831,7 +1832,7 @@ export function registerProjectCommandTests() { }); }); - submitChat('/policy-confirm canvas.asset_generate'); + await submitChat('/policy-confirm canvas.asset_generate'); expect( await screen.findByText( /确认:canvas\.project_sync、canvas\.asset_generate/, @@ -1876,7 +1877,7 @@ export function registerProjectCommandTests() { 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'), @@ -1888,7 +1889,7 @@ export function registerProjectCommandTests() { }); invoke.mockClear(); - submitChat('/policy-confirm file.write'); + await submitChat('/policy-confirm file.write'); expect( await screen.findByText( @@ -1946,7 +1947,7 @@ export function registerProjectCommandTests() { 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'), @@ -1958,7 +1959,7 @@ export function registerProjectCommandTests() { }); invoke.mockClear(); - submitChat('/policy-deny not.a.command'); + await submitChat('/policy-deny not.a.command'); expect( await screen.findByText('未知内置命令:not.a.command'), @@ -2018,13 +2019,13 @@ export function registerProjectCommandTests() { 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('/policy-deny file.write'); + await submitChat('/policy-deny file.write'); expect(await screen.findByText('准备拒绝命令:file.write')).not.toBeNull(); delete window.__TAURI__; fireEvent.click(screen.getByRole('button', { name: '确认' })); @@ -2032,12 +2033,12 @@ export function registerProjectCommandTests() { expect(await screen.findByText('需要在 Tauri App 内运行。')).not.toBeNull(); }); - it('does not stop a preview before a local project is initialized', () => { + it('does not stop a preview before a local project is initialized', async () => { const invoke = vi.fn(); window.__TAURI__ = { core: { invoke } }; renderAppAt('/'); - submitChat('/preview-stop'); + await submitChat('/preview-stop'); expect(screen.getByText('请先用 /project 设置本地项目。')).not.toBeNull(); expect(invoke).not.toHaveBeenCalled(); @@ -2078,13 +2079,13 @@ export function registerProjectCommandTests() { 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('/preview-stop'); + await submitChat('/preview-stop'); expect(await screen.findByText('预览已停止。')).not.toBeNull(); expect(invoke).toHaveBeenCalledWith('stop_local_game_preview', { @@ -2138,14 +2139,14 @@ export function registerProjectCommandTests() { 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('/preview-stop'); + await submitChat('/preview-stop'); expect(await screen.findByText('准备停止本地预览。')).not.toBeNull(); expect(screen.getByText('preview.stop')).not.toBeNull(); @@ -2222,23 +2223,23 @@ export function registerProjectCommandTests() { }); }); - it('does not show preview status before a local project is initialized', () => { + it('does not show preview status before a local project is initialized', async () => { const invoke = vi.fn(); window.__TAURI__ = { core: { invoke } }; renderAppAt('/'); - submitChat('/preview-status'); + await submitChat('/preview-status'); expect(screen.getByText('请先用 /project 设置本地项目。')).not.toBeNull(); expect(invoke).not.toHaveBeenCalled(); }); - it('does not open a preview before a local project is initialized', () => { + it('does not open a preview before a local project is initialized', async () => { const invoke = vi.fn(); window.__TAURI__ = { core: { invoke } }; renderAppAt('/'); - submitChat('/open-preview'); + await submitChat('/open-preview'); expect(screen.getByText('请先用 /project 设置本地项目。')).not.toBeNull(); expect(screen.queryByText('preview.open')).toBeNull(); @@ -2281,13 +2282,13 @@ export function registerProjectCommandTests() { 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('/open-preview'); + await submitChat('/open-preview'); fireEvent.click(screen.getByRole('button', { name: '确认' })); expect( @@ -2339,14 +2340,14 @@ export function registerProjectCommandTests() { 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('/open-preview'); + await submitChat('/open-preview'); expect(await screen.findByText('准备打开当前本地预览。')).not.toBeNull(); expect(screen.getByText('preview.open')).not.toBeNull(); fireEvent.click(screen.getByRole('button', { name: '确认' })); @@ -2399,13 +2400,13 @@ export function registerProjectCommandTests() { 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('/open-preview'); + await submitChat('/open-preview'); const previewCommand = screen.getByText('preview.open'); fireEvent.click( within( @@ -2466,13 +2467,13 @@ export function registerProjectCommandTests() { 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('/preview'); + await submitChat('/preview'); expect(screen.getByText('preview.start')).not.toBeNull(); expect( screen.getByText('启动 /tmp/authorized-game/game/ 并载入客户端运行视图'), @@ -2555,14 +2556,14 @@ export function registerProjectCommandTests() { 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('/preview'); + await submitChat('/preview'); expect(screen.getByText('preview.start')).not.toBeNull(); fireEvent.click(screen.getByRole('button', { name: '确认' })); @@ -2625,13 +2626,13 @@ export function registerProjectCommandTests() { 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('/preview'); + await submitChat('/preview'); fireEvent.click(screen.getByRole('button', { name: '确认' })); expect( @@ -2679,13 +2680,13 @@ export function registerProjectCommandTests() { 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('/preview'); + await submitChat('/preview'); await act(async () => { fireEvent.click(screen.getByRole('button', { name: '确认' })); }); @@ -2847,22 +2848,19 @@ export function registerProjectCommandTests() { window.__TAURI__ = { core: { invoke } }; renderAppAt('/?dev'); - 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('/preview-status'); + await submitChat('/preview-status'); expect( await screen.findByText('预览运行中:http://127.0.0.1:3210/'), ).not.toBeNull(); fireEvent.click(screen.getByRole('button', { name: '填入打开预览命令' })); - expect(screen.getByLabelText('创作想法')).toHaveProperty( - 'value', - '/open-preview', - ); + expect(await composerText()).toBe('/open-preview'); expect(screen.getByText('preview.status')).not.toBeNull(); expect(invoke).toHaveBeenCalledWith('get_local_game_preview_status', { projectPath: '/tmp/authorized-game', @@ -2915,13 +2913,13 @@ export function registerProjectCommandTests() { window.__TAURI__ = { core: { invoke } }; renderAppAt('/?dev'); - 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('/preview-status'); + await submitChat('/preview-status'); expect(await screen.findByText('准备查看预览状态。')).not.toBeNull(); expect(screen.getByText('preview.status')).not.toBeNull(); @@ -2937,10 +2935,7 @@ export function registerProjectCommandTests() { expect(await screen.findByText('预览未启动。')).not.toBeNull(); fireEvent.click(screen.getByRole('button', { name: '填入启动预览命令' })); - expect(screen.getByLabelText('创作想法')).toHaveProperty( - 'value', - '/preview', - ); + expect(await composerText()).toBe('/preview'); expect(invoke).toHaveBeenCalledWith('get_local_game_preview_status', { projectPath: '/tmp/authorized-game', }); @@ -3016,13 +3011,13 @@ export function registerProjectCommandTests() { 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('/run'); + await submitChat('/run'); expect(screen.getByText('game.run_local')).not.toBeNull(); expect( screen.getByText('运行自检并启动 /tmp/authorized-game/game/'), @@ -3069,11 +3064,11 @@ export function registerProjectCommandTests() { 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.findAllByText('本地项目路径无效')).toHaveLength(2); - submitChat('/run'); + await submitChat('/run'); expect( await screen.findByText('请先用 /project 设置本地项目。'), @@ -3135,13 +3130,13 @@ export function registerProjectCommandTests() { 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('/smoke'); + await submitChat('/smoke'); expect( screen.getByText('command.run_limited · 静态入口自检'), ).not.toBeNull(); @@ -3170,7 +3165,7 @@ export function registerProjectCommandTests() { window.__TAURI__ = { core: { invoke } }; renderAppAt('/'); - submitChat('/commands'); + await submitChat('/commands'); expect(await screen.findByText(/可运行受限命令:/)).not.toBeNull(); expect(screen.getByText(/game\.custom_smoke · 自定义自检/)).not.toBeNull(); @@ -3547,13 +3542,13 @@ export function registerProjectCommandTests() { 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('/status'); + await submitChat('/status'); const statusMessage = await screen.findByText( (_, element) => @@ -3611,13 +3606,13 @@ export function registerProjectCommandTests() { 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('/status'); + await submitChat('/status'); expect(await screen.findByText('准备读取项目状态。')).not.toBeNull(); expect(screen.getByText('project.status')).not.toBeNull(); @@ -3684,13 +3679,13 @@ export function registerProjectCommandTests() { 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('/status'); + await submitChat('/status'); expect( await screen.findByText('项目权限策略拒绝执行:project.status'), @@ -3737,13 +3732,13 @@ export function registerProjectCommandTests() { 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('/status'); + await submitChat('/status'); const statusCommand = await screen.findByText('project.status'); fireEvent.click( @@ -3796,13 +3791,13 @@ export function registerProjectCommandTests() { 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('/files'); + await submitChat('/files'); expect(await screen.findByText(/本地项目文件:/)).not.toBeNull(); expect( @@ -3858,14 +3853,14 @@ export function registerProjectCommandTests() { 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('/files'); + await submitChat('/files'); expect(await screen.findByText('准备列出项目文件。')).not.toBeNull(); expect(screen.getByText('file.list')).not.toBeNull(); @@ -3929,14 +3924,14 @@ export function registerProjectCommandTests() { 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('/exports'); + await submitChat('/exports'); expect(await screen.findByText('准备列出本地试玩包。')).not.toBeNull(); expect(screen.getByText('project.export_list')).not.toBeNull(); @@ -4003,13 +3998,13 @@ export function registerProjectCommandTests() { 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('/index'); + await submitChat('/index'); expect(await screen.findByText(/索引:14 个文件,231B/)).not.toBeNull(); expect( @@ -4071,13 +4066,13 @@ export function registerProjectCommandTests() { 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('/index'); + await submitChat('/index'); expect(await screen.findByText('准备刷新本地项目索引。')).not.toBeNull(); expect(screen.getByText('project.index')).not.toBeNull(); @@ -4161,7 +4156,7 @@ export function registerProjectCommandTests() { window.__TAURI__ = { core: { invoke } }; renderAppAt('/'); - submitChat('/llm-status'); + await submitChat('/llm-status'); expect(await screen.findByText(/官方智能服务:已连接/)).not.toBeNull(); expect(screen.getByLabelText('聊天').textContent).toContain( @@ -4248,7 +4243,7 @@ export function registerProjectCommandTests() { window.__TAURI__ = { core: { invoke } }; renderAppAt('/'); - submitChat('/llm-routes'); + await submitChat('/llm-routes'); expect(await screen.findByText(/Agent 智能服务状态:/)).not.toBeNull(); const chatText = screen.getByLabelText('聊天').textContent ?? ''; diff --git a/apps/ai-game-creator-shell/tests/appSurface/project-conversation.suite.ts b/apps/ai-game-creator-shell/tests/appSurface/project-conversation.suite.ts index 12c67d5ea..6643aede5 100644 --- a/apps/ai-game-creator-shell/tests/appSurface/project-conversation.suite.ts +++ b/apps/ai-game-creator-shell/tests/appSurface/project-conversation.suite.ts @@ -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(), diff --git a/apps/ai-game-creator-shell/tests/appSurface/project-development.suite.ts b/apps/ai-game-creator-shell/tests/appSurface/project-development.suite.ts index 806c64f5b..6e6c1bba9 100644 --- a/apps/ai-game-creator-shell/tests/appSurface/project-development.suite.ts +++ b/apps/ai-game-creator-shell/tests/appSurface/project-development.suite.ts @@ -17,6 +17,9 @@ import { agentRuntimeUserInputRequest, App, cleanup, + composerDisabled, + composerText, + composerValue, createGameCreationAppManifest, createGameCreationAppSeedTasks, createProjectSupervisorRuntimeHarness, @@ -40,6 +43,7 @@ import { renderLauncherProjectsAt, resolve, screen, + setComposerText, submitChat, vi, waitFor, @@ -4854,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(); @@ -5080,8 +5083,8 @@ export function registerProjectSupervisorSurfaceTests() { within(supervisorSurface).queryByLabelText('项目总控 Agent 状态'), ).toBeNull(); expect( - within(supervisorSurface).getByPlaceholderText( - '告诉陶泥儿接下来要做什么', + within(supervisorSurface).getByText( + '告诉陶泥儿接下来要做什么,或输入 @ 选择资源', ), ).not.toBeNull(); expect( @@ -5332,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( @@ -5408,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(() => { @@ -5455,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 () => { @@ -5658,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: '提交回答' })); @@ -5794,9 +5793,10 @@ export function registerProjectSupervisorSurfaceTests() { }, ); - fireEvent.change(screen.getByLabelText('陶泥儿对话内容'), { - target: { value: '先完成正式客户端玩法拆解' }, - }); + await setComposerText( + screen.getByLabelText('陶泥儿对话内容'), + '先完成正式客户端玩法拆解', + ); fireEvent.click( within(supervisorSurface).getByRole('button', { name: '发送' }), ); @@ -6068,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: '发送' }), ); @@ -6416,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', @@ -6466,7 +6476,7 @@ export function registerProjectWorkbenchNavigationTests() { 'open_game_creator_launcher_window', ); - submitChat('/switch-project'); + await submitChat('/switch-project'); expect( screen.getAllByText('请回到首页的项目组切换项目。').length, @@ -6541,7 +6551,7 @@ export function registerProjectWorkbenchNavigationTests() { }); expect(await screen.findByText('已打开项目目录。')).not.toBeNull(); - submitChat('/open-project'); + await submitChat('/open-project'); await waitFor(() => { expect( @@ -6625,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( diff --git a/apps/ai-game-creator-shell/tests/appSurface/project-preview/preview-shortcuts/assert-planning-and-status-shortcuts.ts b/apps/ai-game-creator-shell/tests/appSurface/project-preview/preview-shortcuts/assert-planning-and-status-shortcuts.ts index 30d180a03..0b6126a85 100644 --- a/apps/ai-game-creator-shell/tests/appSurface/project-preview/preview-shortcuts/assert-planning-and-status-shortcuts.ts +++ b/apps/ai-game-creator-shell/tests/appSurface/project-preview/preview-shortcuts/assert-planning-and-status-shortcuts.ts @@ -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', diff --git a/apps/ai-game-creator-shell/tests/appSurface/project-preview/preview-shortcuts/assert-playtest-and-release-shortcuts.ts b/apps/ai-game-creator-shell/tests/appSurface/project-preview/preview-shortcuts/assert-playtest-and-release-shortcuts.ts index 2e7cf976c..2467320d1 100644 --- a/apps/ai-game-creator-shell/tests/appSurface/project-preview/preview-shortcuts/assert-playtest-and-release-shortcuts.ts +++ b/apps/ai-game-creator-shell/tests/appSurface/project-preview/preview-shortcuts/assert-playtest-and-release-shortcuts.ts @@ -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', diff --git a/apps/ai-game-creator-shell/tests/appSurface/project-preview/preview-shortcuts/assert-project-and-design-shortcuts.ts b/apps/ai-game-creator-shell/tests/appSurface/project-preview/preview-shortcuts/assert-project-and-design-shortcuts.ts index 4d0019dff..82cadb2be 100644 --- a/apps/ai-game-creator-shell/tests/appSurface/project-preview/preview-shortcuts/assert-project-and-design-shortcuts.ts +++ b/apps/ai-game-creator-shell/tests/appSurface/project-preview/preview-shortcuts/assert-project-and-design-shortcuts.ts @@ -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(/最近 Run:run-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', diff --git a/apps/ai-game-creator-shell/tests/appSurface/project-preview/preview-shortcuts/assert-project-tools-and-preview.ts b/apps/ai-game-creator-shell/tests/appSurface/project-preview/preview-shortcuts/assert-project-tools-and-preview.ts index 408b6041e..6f9d5a52e 100644 --- a/apps/ai-game-creator-shell/tests/appSurface/project-preview/preview-shortcuts/assert-project-tools-and-preview.ts +++ b/apps/ai-game-creator-shell/tests/appSurface/project-preview/preview-shortcuts/assert-project-tools-and-preview.ts @@ -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(); diff --git a/apps/ai-game-creator-shell/tests/appSurface/project-preview/run-history.suite.ts b/apps/ai-game-creator-shell/tests/appSurface/project-preview/run-history.suite.ts index 5e1584212..77e5f3ad8 100644 --- a/apps/ai-game-creator-shell/tests/appSurface/project-preview/run-history.suite.ts +++ b/apps/ai-game-creator-shell/tests/appSurface/project-preview/run-history.suite.ts @@ -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( diff --git a/apps/ai-game-creator-shell/tests/appSurface/project-preview/run-status.suite.ts b/apps/ai-game-creator-shell/tests/appSurface/project-preview/run-status.suite.ts index b100d0832..b9f6a27f9 100644 --- a/apps/ai-game-creator-shell/tests/appSurface/project-preview/run-status.suite.ts +++ b/apps/ai-game-creator-shell/tests/appSurface/project-preview/run-status.suite.ts @@ -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( diff --git a/apps/ai-game-creator-shell/tests/appSurface/supervisor-runtime.suite.ts b/apps/ai-game-creator-shell/tests/appSurface/supervisor-runtime.suite.ts index bab83e693..c2c18e0bb 100644 --- a/apps/ai-game-creator-shell/tests/appSurface/supervisor-runtime.suite.ts +++ b/apps/ai-game-creator-shell/tests/appSurface/supervisor-runtime.suite.ts @@ -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(