write-unit-tests について
write-unit-tests は tldraw ワークスペース向けのテストスキルで、Vitest によるユニットテストと統合テストを支援します。ワークスペースの選択、配置、エディター固有の注意点、規約を説明します。
主な機能
- 01Vitest によるユニット・統合テスト
- 02ワークスペース選択の指針
- 03テストファイル配置の規約
- 04エディターのイベントとアニメーションの注意点
- 05リソース解放とモック復元の規則
- 06アサーションとテスト信頼性の指針
活用シーン
- 01エディターのプリミティブにテストを追加
- 02tldraw の図形やツールをテスト
- 03UI に依存しないマネージャーテストの作成
- 04イベントやアニメーションの不安定なテストの調査
インストール
コマンドをコピーしてターミナルで実行します。
npx skills add https:/ / github.com/ tldraw/ tldraw --skill write-unit-testsインストール後、Claude Code で/write-unit-testsを入力して読み込まれたか確認します。
詳細
テストの作成
ユニットテストと統合テストには Vitest を使用し、リポジトリのルートではなくワークスペースのディレクトリから実行します。
新しいテストを書く前に、近くにあるテストを読んでください。既存のテストスイートはテスト方法を示す仕様であり、文章では難しい最新の状態が保たれています。まずは、次のテストから読むとよいでしょう。
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() を呼び出してください。 エディターのインスタンスはテストスイート内の個々のアサーションより長く存続するため、スパイを復元しないと後続のテストに気づかないうちに影響します。
グローバルなものはすべて finally または afterEach で復元してください。 process.env.NODE_ENV、vi.useFakeTimers()(vi.useRealTimers() に戻す)、Postgres の search_path、window のグローバル変数などが該当します。テスト本体の最後で復元すると、アサーションが失敗した場合に後続のすべてのテストへ影響します。
固定の基準日時に対して「現在からの相対日付」を使わないでください。 Date.now() - 1 day を作り、ハードコードされたリリース日と比較するテストは、それ自体が原因で失敗するようになります。固定の日付を使うか、vi.setSystemTime を使用してください。
テストが失敗することを確認してください。 何かが存在することだけでなく、具体的な結果(件数、フォーカスがある要素、再接続後もメッセージが届くことなど)をアサートしてください。テスト用に作った代替処理ではなく、本番の処理経路を実行し、新しいコードの正常系だけでなく失敗分岐もテストしてください。非同期の検証が完了する前にアサートしているために通るだけのテストは、テストとはいえません。
規約
- 安定性と型安全性を確保するため、シェイプ ID には
createShapeId()を使用します。 - より明確なエラーになる場合は、フィールドごとにアサーションするのではなく、オブジェクト全体を比較します。
- ステートマシンのアサーションには、内部に立ち入るのではなく
editor.expectToBeIn('select.idle')を使用します。 - 無効なプロパティが型レベルで拒否されることをアサーションするには、
@ts-expect-errorを使用します。