diff --git a/apps/ai-game-creator-shell/tests/resourceCanvasQuickEditDraft.test.tsx b/apps/ai-game-creator-shell/tests/resourceCanvasQuickEditDraft.test.tsx index b9383a561..c98db582b 100644 --- a/apps/ai-game-creator-shell/tests/resourceCanvasQuickEditDraft.test.tsx +++ b/apps/ai-game-creator-shell/tests/resourceCanvasQuickEditDraft.test.tsx @@ -3,6 +3,7 @@ import { afterEach, describe, expect, it, vi } from 'vitest'; import type { GameCreationAppManifest } from '../../../packages/shared/src/contracts/gameCreationApp'; import { + composerValue, createGameCreationAppManifest, findResourceSelectButton, fireEvent, @@ -368,6 +369,17 @@ function panelPromptText(panel: HTMLElement) { return within(panel).getByLabelText('快速编辑提示词').textContent ?? ''; } +/** + * 「重开面板后提示词应当回填」不能同步读。 + * + * 面板(dialog)先出现,Lexical 的编辑器值随后才落;同步读 `textContent` 在并发跑全量时 + * 会偶发读到空串——CI run 2702 与本地一次全量各命中过一次(同一条用例、`expected '' to + * include '把角色头发改成红色'`)。这里等值真的落位,不再赌时序。 + */ +async function expectPanelPromptContains(panel: HTMLElement, expected: string) { + await waitFor(() => expect(panelPromptText(panel)).toContain(expected)); +} + /** 关掉画布浮层:Esc 与点外部同一条既有链路(`clearResourceCanvasFocus`)。 */ async function dismissPanel() { fireEvent.keyDown(document, { key: 'Escape' }); @@ -408,7 +420,7 @@ describe('快速编辑草稿的保留与恢复', () => { await dismissPanel(); const reopened = await openQuickEditPanel('source-art.png'); - expect(panelPromptText(reopened)).toContain('把夜色改成星空'); + await expectPanelPromptContains(reopened, '把夜色改成星空'); expect( document.querySelector('[data-resource-reference-id="source-rules"]'), ).not.toBeNull(); @@ -438,13 +450,15 @@ describe('快速编辑草稿的保留与恢复', () => { ); await dismissPanel(); - expect( - panelPromptText(await openQuickEditPanel('source-art.png')), - ).toContain('第一张的草稿'); + await expectPanelPromptContains( + await openQuickEditPanel('source-art.png'), + '第一张的草稿', + ); await dismissPanel(); - expect( - panelPromptText(await openQuickEditPanel('source-art-2.png')), - ).toContain('第二张的草稿'); + await expectPanelPromptContains( + await openQuickEditPanel('source-art-2.png'), + '第二张的草稿', + ); }); it('恢复入口按草稿计数,「继续编辑」重开面板并回填,丢弃后入口消失', async () => { @@ -486,7 +500,7 @@ describe('快速编辑草稿的保留与恢复', () => { const resumed = await screen.findByRole('dialog', { name: '快速编辑图片', }); - expect(panelPromptText(resumed)).toContain('把角色头发改成红色'); + await expectPanelPromptContains(resumed, '把角色头发改成红色'); await dismissPanel(); fireEvent.click( @@ -539,7 +553,7 @@ describe('快速编辑草稿的保留与恢复', () => { ).not.toBeNull(); const resumed = await openQuickEditPanel('source-art.png'); - expect(panelPromptText(resumed)).toContain('把夜色改成星空'); + await expectPanelPromptContains(resumed, '把夜色改成星空'); }); it('正规化换掉卡片投影后,草稿跟着资源走、重开在正式素材上继续', async () => { @@ -605,7 +619,7 @@ describe('快速编辑草稿的保留与恢复', () => { // 旧卡片已经不在画布上:草稿按**路径**归属,新投影的正式素材卡照样命中,否则这一笔 // 就再也点不到。 const reopened = await openQuickEditPanel('task-art.png'); - expect(panelPromptText(reopened)).toContain('把夜色改成星空'); + await expectPanelPromptContains(reopened, '把夜色改成星空'); }); /** @@ -664,7 +678,7 @@ describe('快速编辑草稿的保留与恢复', () => { ); const reopened = await openQuickEditPanel('task-art.png'); - expect(panelPromptText(reopened)).toContain('把夜色改成星空'); + await expectPanelPromptContains(reopened, '把夜色改成星空'); }); it('原生恢复队列与本地草稿共用同一枚入口,两边都能继续', async () => { @@ -732,7 +746,11 @@ describe('快速编辑草稿的保留与恢复', () => { ); const reopened = await openQuickEditPanel('source-art.png'); - expect(panelPromptText(reopened)).not.toContain('把夜色改成星空'); + // 用 `composerValue` 读(它内部先让 Lexical 落值):同步读会在值未落时拿到空串, + // 让这条否定断言空过——那正是上面那条回填断言的同一类竞态。 + expect( + await composerValue(within(reopened).getByLabelText('快速编辑提示词')), + ).not.toContain('把夜色改成星空'); }); }); @@ -815,7 +833,7 @@ describe('提交中的快速编辑进「生成任务」侧栏', () => { // 回到第一张:草稿照旧回填,但这一笔已经在跑——再点提交必须被拦下。 const reopened = await openQuickEditPanel('source-art.png'); - expect(panelPromptText(reopened)).toContain('把嘴改小一点'); + await expectPanelPromptContains(reopened, '把嘴改小一点'); fireEvent.click(within(reopened).getByRole('button', { name: '修改' })); expect( await within(reopened).findByText(