Files
Genarrative/docs/technical/【前端测试】React组件测试准则-2026-06-26.md
k88936 676bd524ee
Project CI / Repository checks (push) Successful in 1m23s
Project CI / Frontend tests (push) Successful in 3m1s
Project CI / Backend tests (push) Successful in 3m51s
Project CI / Native shell tests (push) Successful in 13m32s
滑动条样式调整 (#149)
示例来自微信:
![shotmd-1786091231.jpg](/attachments/b59bae77-00ca-4669-8a54-a9786ae53c93)

before:
- firefox:
![shotmd-1786091298.jpg](/attachments/80bbd46e-551d-4b44-abe4-d3d0ecaa34dd)

- edge:
![shotmd-1786091062.jpg](/attachments/7918c523-1b5b-4439-926d-591e0029c5b9)

after:
- firefox:
![shotmd-1786110119.jpg](/attachments/31b22537-236b-4782-bc47-28ac5ecb0f9f)
![shotmd-1786110060.jpg](/attachments/b48d5343-fd07-451d-943a-55000600e248)

- edge:

![shotmd-1786110050.jpg](/attachments/cfd0d8e4-7d52-45f2-bab8-1b573b3a43b0)

- 使用 Lexical + OverlayScrollbars 实现统一可控的滑动条样式, 取代原来的textarea

Reviewed-on: http://192.168.35.82/git/GenarrativeAI/Genarrative/pulls/149
Co-authored-by: 王德宇 <kvtodev@outlook.com>
Co-committed-by: 王德宇 <kvtodev@outlook.com>
2026-08-08 15:10:16 +08:00

5.0 KiB

React 组件测试准则

更新时间:2026-08-07

背景

当前前端测试已经覆盖大量 React 组件、hook 和页面流程,但部分用例把组件内部状态、DOM 探针、按钮顺序、图标 class 或完整文案当成契约。这样的测试能快速发现改动,却也会让 UI 结构、交互文案和实现拆分变得很难迭代。

后续新增或重写 React 测试时,默认按本准则选择断言层级。已有测试不要求一次性批量迁移;当某个测试因正常重构频繁破碎,或本次任务正好修改该区域,就顺手收紧到稳定契约。

分层口径

  1. 用户流程测试验证用户可感知结果。

    优先使用 Testing Library 的 userEvent、role、label、可见弹窗、URL、提交结果、错误提示和外部 callback。测试名应描述业务行为,不描述组件内部状态机步骤。

  2. 稳定契约测试验证对外边界。

    对请求 DTO、callback payload、路由变化、持久化边界和后端回包映射,使用关键字段断言或 expect.objectContaining(...)。对象仍在演化时,不要断言完整对象、完整数组顺序或所有默认字段。

  3. 模型和 hook 测试验证纯逻辑。

    复杂状态机优先沉到 model 或 hook 的公开返回契约中测试。hook 测试使用 renderHook 直接调用公开方法和读取公开状态,不额外制造 data-testid 仪表盘组件来拼接内部字段。

避免的写法

  • 不为了读取内部状态新建测试专用 DOM,例如 <span data-testid="active-id">textContent 拼接内部数组或状态名。
  • 不把图标实现、第三方库 class、DOM 层级、完整按钮顺序当成稳定契约,除非产品明确要求该顺序或可访问语义本身就是行为。
  • 不用精确长文案锁死可变提示词、分享文案、错误文案或 UI 标签;需要断言时只断言稳定语义片段,或改断言 payload / 状态码 / 目标 callback。
  • 不在页面级测试里 mock 出一套与真实页面差异很大的“假壳”,再把假壳内部状态当作用户验收结果。页面壳测试可以保留,但应尽量断言 URL、标题、公开 callback 和真实可见行为。

推荐写法

  • 交互优先用 userEvent,只有测试低层 pointer / wheel / drag 等浏览器事件细节时再用 fireEvent
  • 查询优先用 role / label / alt / text 的用户语义;只有无可访问语义的画布参考线、不可见测量节点或渲染边界,才使用 data-testid
  • data-testid 名称必须描述用户或稳定渲染边界,例如 image-canvas-editor-snap-guide-vertical;不要描述 React 私有 state 名称。
  • 测试用 fixture 只包含本行为需要的字段。演化中的 payload 使用 expect.objectContaining(...) 或 helper 生成默认对象,避免一处契约加字段导致大量无关用例碎裂。
  • 当测试是为防止历史回归,应在测试名或邻近注释中说明防的是什么行为,而不是记录实现步骤。
  • 同一用例既要验证定时器调度参数,又要断言确定的中间帧或中间状态时,必须 mock 定时器回调或使用可控假时钟;不得让真实墙上时间在异步交互期间推进被断言的状态,否则本地通过的用例会在较慢 CI 中偶发失败。
  • 测试 React effect 中注册的事件监听时,触发事件前先用可观测的 listener 调用确认注册已完成;异步请求已开始应用有界 waitFor 断言确认,不要用无界手工 Promise 等待一次性信号。解除挂起请求时将 Promise 收尾纳入异步 act,确保后续 React 更新在断言前已冲刷。
  • 公共 AutoGrowTextArea 使用 Lexical contenteditable,业务测试通过 setPlainTextEditorValue 写入文本、通过 getPlainTextEditorHost 断言外层样式;占位文案是独立渲染节点,不读取原生 placeholder 属性,也不对编辑根触发 textarea 专属的 change 事件。
  • 异步请求失败时,错误状态与调用方的草稿 / 附件恢复可能分属连续两次 React 更新;用例必须对最终恢复结果使用有界 waitFor,不能把错误文案刚出现的中间帧当成恢复已经完成。

试点调整

src/components/image-editor/useCanvasGenerationDialogs.test.tsx 已从测试专用 DOM 仪表盘改为 renderHook,直接验证 hook 公开契约:打开、归档、激活、更新、删除、恢复和 ID 递增。

src/components/image-editor/ImageCanvasBottomToolbarView.test.tsx 已去掉图标 class 和完整按钮顺序快照式断言,保留用户可操作按钮、工具切换 callback、可访问 pressed 状态和 hover / focus 打开选项的契约。

验证

修改 React 测试后,优先运行触达文件的定向测试,例如:

npm run test -- src/components/image-editor/useCanvasGenerationDialogs.test.tsx src/components/image-editor/ImageCanvasBottomToolbarView.test.tsx --reporter=dot

涉及共享组件、路由壳或跨页面交互时,再追加对应页面测试、npm run typechecknpm run check:encodinggit diff --check