mcpField
W
write-unit-tests

write-unit-tests

github.com/tldraw测试与质量51.1k499 次安装今天更新
代码仓库

关于 write-unit-tests

write-unit-tests 是面向 tldraw 工作区的测试技能,指导贡献者使用 Vitest 编写单元和集成测试,涵盖工作区选择、测试放置、编辑器注意事项与测试约定。

主要功能

  1. 01使用 Vitest 编写单元和集成测试
  2. 02工作区选择指南
  3. 03测试文件放置约定
  4. 04编辑器事件与动画注意事项
  5. 05清理资源和恢复模拟规则
  6. 06断言与测试可靠性指南

适用场景

  1. 01为编辑器基础类型添加测试
  2. 02测试 tldraw 图形或工具
  3. 03编写无 UI 的管理器测试
  4. 04排查事件或动画相关的不稳定测试

安装

复制下面的命令,在终端中运行。

$npx skills add https://github.com/tldraw/tldraw --skill write-unit-tests

安装后在 Claude Code 中输入/write-unit-tests确认已加载。

详细介绍

编写测试

单元测试和集成测试使用 Vitest,并从工作区目录运行,而不是从仓库根目录运行。

编写新测试前先阅读一个相邻的测试——现有测试套件就是我们测试方式的规范,而且它们始终保持最新,这一点不是 prose 能做到的。以下是几个不错的起点:

  • packages/tldraw/src/test/SelectTool.test.ts — 工具状态机断言
  • packages/tldraw/src/test/resizing.test.ts — 由指针驱动的控点交互
  • packages/tldraw/src/lib/shapes/arrow/ArrowShapeUtil.test.ts — 形状工具类及绑定
  • packages/editor/src/lib/editor/managers/ClickManager/ClickManager.test.ts — 无 UI 的管理器
  • packages/editor/src/lib/primitives/Vec.test.ts — 普通基元

如需了解可用的 TestEditor 方法,请直接阅读类本身,而不是查阅此处的列表:packages/tldraw/src/test/TestEditor.ts。

使用哪个工作区

  • packages/editor — 核心基元、几何、管理器,以及不得依赖默认形状或 UI 的基础编辑器行为。
  • packages/tldraw — 任何需要默认形状或工具的内容,大多数集成测试都属于此类。

每个包都有自己的 TestEditor,两者不能互换:packages/editor/src/lib/test/TestEditor.ts 不包含默认形状或工具,而 packages/tldraw/src/test/TestEditor.ts 会接入完整的 SDK。请从你要测试的包中导入。

cd packages/tldraw && pnpm test run
cd packages/tldraw && pnpm test run -t "SelectTool"
cd packages/tldraw && pnpm test          # watch mode

放置位置

单元测试与其覆盖的文件放在一起(Vec.ts → Vec.test.ts)。跨领域集成测试放在 packages/tldraw/src/test/。形状和工具测试应与实现放在一起,而不是放在 src/test/ 中。

注意事项

阅读现有测试时不会了解到以下内容。

滚轮和捏合事件会批量处理。 单独调用 dispatch() 不会应用它们——请发送一个 tick 来刷新:

editor.dispatch(wheelEvent)
editor.emit('tick', 16)

完整模式请参见 packages/editor/src/lib/editor/Editor.test.ts。

toCloselyMatchObject 是我们自己的,不是 Vitest 的。 涉及浮点几何时,请用它代替 toMatchObject,否则测试可能因舍入误差失败。它接受可选的 roundToNearest 参数。定义于 packages/tldraw/src/test/TestEditor.ts。

依赖动画的代码需要 rAF 替身。 仅调用 vi.useFakeTimers() 不够,因为 requestAnimationFrame 不受虚拟时钟驱动。包含动画的测试会在模块作用域替换它——参见 packages/tldraw/src/lib/shapes/arrow/ArrowShapeUtil.test.ts 的开头。

在 afterEach 中释放编辑器。 editor?.dispose() 会释放编辑器持有的响应式订阅和定时器;若不释放,它们会泄漏到同一文件中的后续测试。会创建形状的测试套件通常也会在 beforeEach 中清除形状,确保每个测试都从已知页面状态开始。

对编辑器使用 vi.spyOn 后,务必调用 mockRestore()。 编辑器实例的生命周期会跨越测试套件中的各项断言,因此未恢复的 spy 会悄无声息地影响后续测试。

在 finally 或 afterEach 中恢复所有全局状态。 process.env.NODE_ENV、vi.useFakeTimers()(恢复为 vi.useRealTimers())、Postgres search_path、window 全局变量。若在测试主体末尾恢复,当断言抛出异常时,状态就会泄漏到所有后续测试中。

不要将相对于当前时间的日期与固定截止日期比较。 如果测试用 Date.now() - 1 day 构造日期,再与硬编码的发布日期比较,它迟早会自行失败。请使用固定日期或 vi.setSystemTime。

确保测试在出错时会失败。 断言具体结果(数量、哪个元素获得焦点、重新连接后消息仍会到达),而不只是断言某个东西存在。测试生产路径,而不是为测试搭建的替代路径;新增代码还要覆盖失败分支,而不只是正常分支。如果测试只因在异步验证完成前就执行断言而通过,那就不算测试。

约定

  • 使用 createShapeId() 生成形状 ID,以确保其稳定且类型安全。
  • 如果能让失败原因更清晰,优先比较整个对象,而不是逐个断言字段。
  • 对状态机进行断言时,使用 editor.expectToBeIn('select.idle'),而不是深入访问内部实现。
  • 使用 @ts-expect-error 断言无效属性会在类型层面被拒绝。

评论

支持 Markdown