From 30828fb776857397303941c5d332608cf5455097 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=8E=8B=E5=BE=B7=E5=AE=87?= Date: Sat, 8 Aug 2026 12:00:03 +0800 Subject: [PATCH] =?UTF-8?q?=E4=BF=AE=E5=A4=8D=E7=94=BB=E5=B8=83=E6=96=87?= =?UTF-8?q?=E6=9C=AC=E8=BE=93=E5=85=A5=E5=9B=9E=E5=BD=92=E6=B5=8B=E8=AF=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 迁移角色生成测试至 Lexical 输入契约 等待画布 Agent 失败后的草稿恢复完成 修正编辑器测试导入排序并补充测试准则 --- ...前端测试】React组件测试准则-2026-06-26.md | 2 ++ .../EditorAgentConversationPanelView.test.tsx | 6 ++-- ...asCharacterGenerationComposerView.test.tsx | 28 +++++++++++++------ .../ImageCanvasEditorView.test.tsx | 2 +- 4 files changed, 26 insertions(+), 12 deletions(-) diff --git a/docs/technical/【前端测试】React组件测试准则-2026-06-26.md b/docs/technical/【前端测试】React组件测试准则-2026-06-26.md index 6b32b7ce5..eca1179c6 100644 --- a/docs/technical/【前端测试】React组件测试准则-2026-06-26.md +++ b/docs/technical/【前端测试】React组件测试准则-2026-06-26.md @@ -38,6 +38,8 @@ - 当测试是为防止历史回归,应在测试名或邻近注释中说明防的是什么行为,而不是记录实现步骤。 - 同一用例既要验证定时器调度参数,又要断言确定的中间帧或中间状态时,必须 mock 定时器回调或使用可控假时钟;不得让真实墙上时间在异步交互期间推进被断言的状态,否则本地通过的用例会在较慢 CI 中偶发失败。 - 测试 React effect 中注册的事件监听时,触发事件前先用可观测的 listener 调用确认注册已完成;异步请求已开始应用有界 `waitFor` 断言确认,不要用无界手工 Promise 等待一次性信号。解除挂起请求时将 Promise 收尾纳入异步 `act`,确保后续 React 更新在断言前已冲刷。 +- 公共 `AutoGrowTextArea` 使用 Lexical `contenteditable`,业务测试通过 `setPlainTextEditorValue` 写入文本、通过 `getPlainTextEditorHost` 断言外层样式;占位文案是独立渲染节点,不读取原生 `placeholder` 属性,也不对编辑根触发 textarea 专属的 `change` 事件。 +- 异步请求失败时,错误状态与调用方的草稿 / 附件恢复可能分属连续两次 React 更新;用例必须对最终恢复结果使用有界 `waitFor`,不能把错误文案刚出现的中间帧当成恢复已经完成。 ## 试点调整 diff --git a/src/components/image-editor/EditorAgentConversation/EditorAgentConversationPanelView.test.tsx b/src/components/image-editor/EditorAgentConversation/EditorAgentConversationPanelView.test.tsx index 1eeabed63..51c8a35cc 100644 --- a/src/components/image-editor/EditorAgentConversation/EditorAgentConversationPanelView.test.tsx +++ b/src/components/image-editor/EditorAgentConversation/EditorAgentConversationPanelView.test.tsx @@ -1377,8 +1377,10 @@ describe('EditorAgentConversationPanelView', () => { fireEvent.click(screen.getByRole('button', { name: '发送' })); expect(await screen.findByText('Network error')).toBeTruthy(); - expect(screen.getByLabelText('发送给画布 Agent').textContent).toBe( - '失败后恢复这条草稿', + await waitFor(() => + expect(screen.getByLabelText('发送给画布 Agent').textContent).toBe( + '失败后恢复这条草稿', + ), ); expect(screen.getByText('角色图层')).toBeTruthy(); expect( diff --git a/src/components/image-editor/ImageCanvasCharacterGenerationComposerView.test.tsx b/src/components/image-editor/ImageCanvasCharacterGenerationComposerView.test.tsx index d99b8bc9d..8946494bc 100644 --- a/src/components/image-editor/ImageCanvasCharacterGenerationComposerView.test.tsx +++ b/src/components/image-editor/ImageCanvasCharacterGenerationComposerView.test.tsx @@ -4,6 +4,10 @@ import { fireEvent, render, screen } from '@testing-library/react'; import { createRef, useState } from 'react'; import { describe, expect, it, vi } from 'vitest'; +import { + getPlainTextEditorHost, + setPlainTextEditorValue, +} from '../common/AutoGrowTextArea.test-utils'; import { ImageCanvasCharacterGenerationComposerView } from './ImageCanvasCharacterGenerationComposerView'; import type { GenerateDialogState, @@ -94,21 +98,26 @@ function CharacterGenerationHarness({ describe('ImageCanvasCharacterGenerationComposerView', () => { it('keeps the prompt as a bordered single text input with a question placeholder', () => { - render(); + render( + , + ); const panel = screen.getByRole('dialog', { name: '生成角色形象' }); const prompt = screen.getByRole('textbox', { name: '角色设定' }); + const promptHost = getPlainTextEditorHost(prompt); expect( Array.from(panel.querySelectorAll('.image-canvas-editor__field-title')) .map((node) => node.textContent) .join(''), ).not.toContain('角色设定'); - expect(prompt.getAttribute('placeholder')).toBe('你希望角色如何设计?'); - expect(prompt.className).toContain('auto-grow-text-area'); - expect(prompt.className).not.toContain('platform-text-field'); - expect(prompt.className).toContain('image-canvas-editor__generation-prompt'); - expect(prompt.className).not.toContain( + expect(screen.getByText('你希望角色如何设计?')).toBeTruthy(); + expect(promptHost.className).toContain('auto-grow-text-area'); + expect(promptHost.className).not.toContain('platform-text-field'); + expect(promptHost.className).toContain( + 'image-canvas-editor__generation-prompt', + ); + expect(promptHost.className).not.toContain( 'image-canvas-editor__generation-prompt--borderless', ); }); @@ -125,9 +134,10 @@ describe('ImageCanvasCharacterGenerationComposerView', () => { />, ); - fireEvent.change(screen.getByLabelText('角色设定'), { - target: { value: '新的角色设定' }, - }); + setPlainTextEditorValue( + screen.getByLabelText('角色设定'), + '新的角色设定', + ); fireEvent.click(screen.getByRole('button', { name: '生成' })); expect(screen.getByLabelText('当前角色设定').textContent).toBe( diff --git a/src/components/image-editor/ImageCanvasEditorView.test.tsx b/src/components/image-editor/ImageCanvasEditorView.test.tsx index 13d07c3ef..158f2bea4 100644 --- a/src/components/image-editor/ImageCanvasEditorView.test.tsx +++ b/src/components/image-editor/ImageCanvasEditorView.test.tsx @@ -12,8 +12,8 @@ import userEvent from '@testing-library/user-event'; import JSZip from 'jszip'; import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'; -import { setPlainTextEditorValue } from '../common/AutoGrowTextArea.test-utils'; import { usePlatformWalletStore } from '../../stores/usePlatformWalletStore'; +import { setPlainTextEditorValue } from '../common/AutoGrowTextArea.test-utils'; import type { EditorAgentConversationClient } from './EditorAgentConversation/useEditorAgentConversation'; import { ApiClientError,