# 機能設計書 49-スナップショットテスト

## 概要

本ドキュメントは、Bunテストランナーのスナップショットテスト機能に関する設計書である。toMatchSnapshot、toMatchInlineSnapshotによるスナップショット比較について詳述する。

### 本機能の処理概要

スナップショットテスト機能は、テスト実行時に生成された値を保存されたスナップショットと比較し、予期しない変更を検出する機能である。ファイルスナップショットとインラインスナップショットの両方をサポートする。

**業務上の目的・背景**：UIコンポーネント、API レスポンス、複雑なデータ構造等の出力を手動でチェックするのは困難である。スナップショットテストにより、期待される出力を自動保存し、変更があれば差分を視覚的に表示できる。リグレッションテストの自動化とコードレビューの効率化に貢献する。

**機能の利用シーン**：
- `expect(value).toMatchSnapshot()` でファイルスナップショット
- `expect(value).toMatchInlineSnapshot()` でインラインスナップショット
- `bun test --update-snapshots` でスナップショット更新
- CI環境でのスナップショット不一致検出

**主要な処理内容**：
1. テスト対象値のシリアライズ
2. 既存スナップショットの読み込み
3. 値の比較とdiff生成
4. 新規スナップショットの保存
5. インラインスナップショットのソースコード更新
6. CI環境でのスナップショット作成禁止

**関連システム・外部連携**：
- __snapshots__ディレクトリ
- *.snap ファイル
- ソースファイル（インラインスナップショット）

**権限による制御**：特になし

## 関連画面

| 画面No | 画面名 | 関連種別 | 関連する操作・処理 |
|--------|--------|----------|------------------|
| 4 | test | 主機能 | スナップショット比較を含むテスト実行 |

## 機能種別

テスト機能（スナップショット比較）

## 入力仕様

### 入力パラメータ

| パラメータ名 | 型 | 必須 | 説明 | バリデーション |
|-------------|-----|-----|------|---------------|
| value | any | Yes | スナップショット対象値 | シリアライズ可能 |
| hint | string | No | スナップショット名のヒント | 任意の文字列 |
| update_snapshots | boolean | No | スナップショット更新モード | true/false |

### 入力データソース

- テスト対象の値
- __snapshots__/*.snap ファイル
- ソースファイル（インラインスナップショット）

## 出力仕様

### 出力データ

| 項目名 | 型 | 説明 |
|--------|-----|------|
| total | number | 総スナップショット数 |
| added | number | 新規追加数 |
| passed | number | 一致数 |
| failed | number | 不一致数 |
| snapshot_file | path | スナップショットファイルパス |

### 出力先

- __snapshots__/*.snap（ファイルスナップショット）
- ソースファイル（インラインスナップショット更新時）
- 標準エラー（不一致時のdiff）

## 処理フロー

### 処理シーケンス

```
1. スナップショット名の生成
   └─ テスト名 + カウンター
2. 既存スナップショット検索
   └─ ファイル読み込みまたはインライン確認
3. 値のシリアライズ
   └─ JavaScript値を文字列化
4. 比較
   └─ 既存と新規の文字列比較
5. 結果処理
   └─ 一致: pass / 不一致: fail+diff表示 / 新規: add+保存
6. ファイル書き出し
   └─ 更新モード時またはCI以外での新規作成時
```

### フローチャート

```mermaid
flowchart TD
    A[toMatchSnapshot呼び出し] --> B[スナップショット名生成]
    B --> C[値をシリアライズ]
    C --> D{既存スナップショットあり?}
    D -->|Yes| E[既存と比較]
    D -->|No| F{CI環境?}
    F -->|Yes| G[エラー: CI環境では新規作成不可]
    F -->|No| H[新規スナップショット保存]
    E --> I{一致?}
    I -->|Yes| J[pass]
    I -->|No| K{--update-snapshots?}
    K -->|Yes| L[スナップショット更新]
    K -->|No| M[fail + diff表示]
    H --> N[added カウント増加]
    L --> N
    J --> O[passed カウント増加]
    M --> P[failed カウント増加]
    N --> Q[終了]
    O --> Q
    P --> Q
    G --> Q
```

## ビジネスルール

### 業務ルール

| ルールNo | ルール名 | 内容 | 適用条件 |
|---------|---------|------|---------|
| BR-49-01 | ファイル形式 | スナップショットはJavaScript export形式 | ファイルスナップショット |
| BR-49-02 | インライン形式 | テンプレートリテラル形式で埋め込み | インラインスナップショット |
| BR-49-03 | CI保護 | CI環境では新規作成禁止（--update必須） | CI環境 |
| BR-49-04 | カウンター | 同名テストには連番付与 | 複数スナップショット時 |
| BR-49-05 | ディレクトリ | __snapshots__に保存 | ファイルスナップショット |
| BR-49-06 | 拡張子 | .snapファイル | ファイルスナップショット |

### 計算ロジック

スナップショット名の生成:
```
snapshot_name = describe_names + " " + test_name + (hint ? ": " + hint : "") + " " + counter
```

## データベース操作仕様

本機能はデータベースを使用しない。

### 操作別データベース影響一覧

該当なし

## エラー処理

### エラーケース一覧

| エラーコード | エラー種別 | 発生条件 | 対処方法 |
|------------|----------|---------|---------|
| SnapshotMismatch | 不一致 | 既存と新規が一致しない | コードまたはスナップショット更新 |
| SnapshotCreationNotAllowedInCI | CI禁止 | CI環境での新規作成試行 | --update-snapshotsを使用 |
| FailedToMakeSnapshotDirectory | ディレクトリエラー | __snapshots__作成失敗 | 権限を確認 |
| FailedToOpenSnapshotFile | ファイルエラー | .snapファイルオープン失敗 | パスを確認 |
| FailedToWriteSnapshotFile | 書き込みエラー | .snapファイル書き込み失敗 | 権限を確認 |
| ParseError | パースエラー | .snapファイル形式エラー | ファイルを再生成 |

### リトライ仕様

スナップショット操作は自動リトライしない。

## トランザクション仕様

スナップショットファイルの書き込みはテスト完了後にまとめて行う。

## パフォーマンス要件

- スナップショット比較: 10ms/スナップショット以下
- ファイル書き出し: 100ms/ファイル以下
- シリアライズ: 値のサイズに比例

## セキュリティ考慮事項

- スナップショットは平文で保存（コードレビュー可能）
- 機密情報を含めないよう注意

## 備考

- Jest toMatchSnapshot互換
- toMatchInlineSnapshotのソースコード更新対応
- カスタムシリアライザー対応検討

---

## コードリーディングガイド

本機能を理解するために参照すべきファイルと、推奨する読み解き順序を以下に示す。

### 推奨読解順序

#### Step 1: データ構造を理解する

スナップショット管理で使用される主要なデータ構造を理解する。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 1-1 | snapshot.zig | `src/bun.js/test/snapshot.zig` | Snapshots構造体 |
| 1-2 | jest.zig | `src/bun.js/test/jest.zig` | TestRunner.snapshots |

**読解のコツ**: Snapshots構造体がスナップショットの状態（total、added、passed、failed）を管理。

#### Step 2: スナップショット取得・保存を理解する

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 2-1 | snapshot.zig | `src/bun.js/test/snapshot.zig` | Snapshots.getOrPut関数 |

**主要処理フロー**:
- **1-4行目**: ファイルヘッダーとディレクトリ名の定義
- **6-19行目**: Snapshots構造体のフィールド定義
- **21-37行目**: InlineSnapshotToWrite構造体（インラインスナップショット更新用）
- **44-55行目**: addCount関数でスナップショットカウントを管理
- **56-114行目**: getOrPut関数でスナップショットの取得・保存

#### Step 3: ファイル解析を理解する

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 3-1 | snapshot.zig | `src/bun.js/test/snapshot.zig` | Snapshots.parseFile関数 |

**主要処理フロー**:
- **116-188行目**: parseFile関数で既存スナップショットファイルを解析
- **142-148行目**: JSパーサーを使用してスナップショットファイルをパース
- **159-186行目**: exports[`name`] = `value` 形式を抽出

#### Step 4: インラインスナップショット更新を理解する

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 4-1 | snapshot.zig | `src/bun.js/test/snapshot.zig` | Snapshots.writeInlineSnapshots関数 |

**主要処理フロー**:
- **222-482行目**: writeInlineSnapshots関数でソースファイルを更新
- **241行目**: 行・列番号でソート
- **302-396行目**: TSXパーサーを使用して更新位置を特定
- **448-452行目**: バッククォートでエスケープして書き込み

### プログラム呼び出し階層図

```
expect(value).toMatchSnapshot()
    │
    └─ Snapshots.getOrPut()
           │
           ├─ getSnapshotFile()
           │      ├─ ディレクトリ存在確認
           │      └─ ファイル作成/オープン
           │
           ├─ addCount()
           │      └─ カウンター管理
           │
           ├─ values.get()
           │      └─ 既存スナップショット検索
           │
           └─ 新規の場合
                  ├─ CI環境チェック
                  └─ file_buf に追記

expect(value).toMatchInlineSnapshot()
    │
    └─ addInlineSnapshotToWrite()
           └─ inline_snapshots_to_write に追加

テスト終了時
    │
    ├─ writeSnapshotFile()
    │      └─ .snap ファイル書き出し
    │
    └─ writeInlineSnapshots()
           └─ ソースファイル更新
```

### データフロー図

```
[入力]                    [処理]                         [出力]

テスト対象値 ─────────────▶ シリアライズ ────────────────▶ 文字列
                              │
                              ▼
既存.snapファイル ─────────▶ parseFile() ────────────────▶ ValuesHashMap
                              │
                              ▼
                      スナップショット名生成 ──────────▶ name + counter
                              │
                              ▼
                      比較 ────────────────────────▶ 一致/不一致
                              │
                              ▼
一致時 ──────────────────▶ passed++
                              │
不一致時 ─────────────────▶ failed++ / diff表示
                              │
新規時 ──────────────────▶ added++ / file_buf追記
                              │
                              ▼
                      writeSnapshotFile() ──────────▶ *.snap
                              │
                      writeInlineSnapshots() ───────▶ ソースファイル
```

### 関連ファイル一覧

| ファイル | パス | 種別 | 役割 |
|---------|------|------|------|
| snapshot.zig | `src/bun.js/test/snapshot.zig` | ソース | スナップショット管理の中核 |
| jest.zig | `src/bun.js/test/jest.zig` | ソース | TestRunner.snapshots統合 |
| expect.zig | `src/bun.js/test/expect.zig` | ソース | toMatchSnapshot実装 |
| diff_format.zig | `src/bun.js/test/diff_format.zig` | ソース | diff表示フォーマット |
| js_parser.zig | `src/js_parser.zig` | ソース | スナップショットファイルパース |
| js_printer.zig | `src/js_printer.zig` | ソース | 値のシリアライズ |
