修复快速编辑草稿回填用例的竞态:面板先出现、Lexical 的值后落
Project CI / AI game creator shell Rust lane 1/2 (pull_request) Has been cancelled
Project CI / AI game creator shell Rust lane 2/2 (pull_request) Has been cancelled
Project CI / AI game creator shell Rust smoke (pull_request) Has been cancelled
Project CI / AI game creator shell Rust crates (pull_request) Has been cancelled
Project CI / Backend tests (pull_request) Has been cancelled
Project CI / Native shell tests (pull_request) Has been cancelled
Project CI / Frontend tests (pull_request) Has been cancelled
Project CI / Repository checks (pull_request) Has been cancelled
Project CI / AI game creator shell web tests (pull_request) Has been cancelled

- 同一条用例在 CI run 2702 与本地一次全量里各命中过一次「expected '' to include '把角色头发改成红色'」:面板(dialog)一出现就同步读 textContent,编辑器值还没落
- 回填类断言统一改走 expectPanelPromptContains(内部 waitFor),不再赌时序
- 「提交后重开是空面板」那条否定断言改用 composerValue 读(它先让编辑器落值),避免空串让断言空过
- 连跑该文件 9 次 + 全量 3 次(173 文件 / 1826 通过 / 18 跳过)均全绿
This commit is contained in:
2026-09-22 16:13:10 +08:00
parent 3b2a6c8853
commit 0434647978
@@ -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(