# 通知設計書 2-テスト失敗通知

## 概要

本ドキュメントは、Jestテストフレームワークにおけるデスクトップ通知機能のうち、テスト実行完了時に失敗テストがある場合に送信される「テスト失敗通知」の設計を記載する。

### 本通知の処理概要

テスト失敗通知は、Jestのテスト実行が完了し、1件以上のテストが失敗した場合にデスクトップ通知としてユーザーに失敗を通知する機能である。node-notifierパッケージを利用してOS標準の通知システムを通じて視覚的なフィードバックを提供する。watchモード時には「Run again」「Exit tests」のアクションボタンが表示される。

**業務上の目的・背景**：開発者がテスト実行中に他の作業を行っている場合でも、テストが失敗したことを即座に把握できるようにすることで、問題の早期発見・修正を促進する。特にwatchモードでは、失敗通知からワンクリックでテストを再実行したり、テストを終了したりできるため、開発フローが効率化される。

**通知の送信タイミング**：テスト実行が完了し（onRunComplete イベント）、かつ失敗テストがある場合（numFailedTests > 0 または numRuntimeErrorTestSuites > 0）に送信される。ただし、notifyMode設定により通知条件が制御される。

**通知の受信者**：テストを実行した開発者本人のデスクトップに通知が表示される。node-notifierが利用するOS標準の通知システムを通じて配信される。

**通知内容の概要**：タイトルにはパッケージ名と失敗率（例: 「25% Failed」）が表示され、メッセージには失敗テスト数と総テスト数（例: 「5 of 20 tests failed」）が表示される。macOSでは禁止絵文字が付加される。

**期待されるアクション**：開発者は失敗したテストがあることを認識し、コンソール出力を確認して問題を修正する。watchモード時は、通知のアクションボタンから直接テストを再実行するか、テストを終了することができる。

## 通知種別

デスクトップ通知（OS標準通知システム経由）

## 送信仕様

### 基本情報

| 項目 | 内容 |
|-----|------|
| 送信方式 | 同期（onRunComplete内で直接呼び出し）※watchモード時はコールバック付き |
| 優先度 | 高 |
| リトライ | なし |

### 送信先決定ロジック

テストを実行した開発者のローカルマシンに対して、OS標準の通知システム（macOS: Notification Center、Windows: Toast Notifications、Linux: libnotify等）を通じて通知を表示する。node-notifierパッケージがOSに応じた適切な通知メカニズムを自動選択する。

## 通知テンプレート

### デスクトップ通知の場合

| 項目 | 内容 |
|-----|------|
| アイコン | Jestロゴ（`jest_logo.png`） |
| タイトル | `{packageName} - {failedPercentage}% Failed` |
| メッセージ | macOS: `⛔️ X of Y tests failed` / 他OS: `X of Y tests failed` |
| アクションボタン | watchモード時: 「Run again」「Exit tests」 |

### 本文テンプレート

```
タイトル: {packageName} - {failedPercentage}% Failed
メッセージ: ⛔️ {numFailedTests} of {numTotalTests} tests failed
```

### 添付ファイル

| ファイル名 | 形式 | 条件 | 説明 |
|----------|------|------|------|
| jest_logo.png | PNG | 常に | 通知アイコンとして使用 |

## テンプレート変数

| 変数名 | 説明 | データ取得元 | 必須 |
|--------|------|-------------|-----|
| packageName | プロジェクト名またはルートディレクトリ | hasteFS.getModuleName() または globalConfig.rootDir | Yes |
| failedPercentage | 失敗率（切り上げ） | Math.ceil((numFailedTests / numTotalTests) * 100) | Yes |
| numFailedTests | 失敗したテスト数 | AggregatedResult.numFailedTests | Yes |
| numTotalTests | 総テスト数 | AggregatedResult.numTotalTests | Yes |

## 送信トリガー・条件

### トリガー一覧

| トリガー種別 | トリガーイベント | 送信条件 | 説明 |
|------------|----------------|---------|------|
| イベント | onRunComplete | テスト失敗 かつ notifyMode条件合致 | テスト実行完了時に発火 |

### 送信条件詳細

以下の全ての条件を満たす場合に通知が送信される：

1. `testsHaveRun = true`（numTotalTests !== 0）
2. `success = false`（numFailedTests > 0 または numRuntimeErrorTestSuites > 0）
3. notifyModeが以下のいずれかに該当：
   - `always`: 常に通知
   - `failure`: 失敗時は常に通知
   - `failure-change`: 失敗時は常に通知
   - `change`: ステータスが変化した場合のみ通知
   - `success-change`: ステータスが変化した場合のみ通知

### 送信抑止条件

| 条件 | 説明 |
|-----|------|
| numTotalTests === 0 | テストが1件も実行されていない場合 |
| notifyMode === 'success' | 成功時のみ通知の設定 |
| notifyMode === 'change' かつ statusChanged === false | ステータス変化なしの場合 |
| node-notifier未インストール | 必須の依存パッケージがない場合（エラーがスローされる） |

## 処理フロー

### 送信フロー

```mermaid
flowchart TD
    A[onRunComplete発火] --> B{テスト実行あり?}
    B -->|No| Z[終了]
    B -->|Yes| C{テスト失敗あり?}
    C -->|No| Z
    C -->|Yes| D{notifyMode条件合致?}
    D -->|No| Z
    D -->|Yes| E[パッケージ名取得]
    E --> F[失敗率計算]
    F --> G[タイトル生成: packageName - X% Failed]
    G --> H[メッセージ生成: X of Y tests failed]
    H --> I{watchモード?}
    I -->|Yes| J[アクションボタン付き通知]
    J --> K[コールバック設定]
    K --> L{ユーザーアクション}
    L -->|Run again| M[startRun呼び出し]
    L -->|Exit tests| N[exit(0)呼び出し]
    L -->|その他| O[何もしない]
    I -->|No| P[通常通知]
    M --> Z
    N --> Z
    O --> Z
    P --> Q[コンテキスト更新]
    Q --> Z
```

## データベース参照・更新仕様

### 参照テーブル一覧

本通知はデータベースを使用しない。メモリ上のAggregatedResult構造体からデータを取得する。

| データソース | 用途 | 備考 |
|-----------|------|------|
| AggregatedResult | テスト実行結果の取得 | onRunComplete引数として渡される |
| ReporterContext | 前回の成功状態・初回実行フラグ・startRun関数 | ステータス変化判定とテスト再実行に使用 |
| GlobalConfig | watchモード判定・notifyMode取得 | 設定値の参照 |
| hasteFS | パッケージ名の取得 | TestContextから取得 |

### 参照項目詳細

#### AggregatedResult

| 参照項目 | 用途 | 取得条件 |
|--------|------|---------|
| numFailedTests | 失敗判定・失敗率計算・メッセージ表示 | なし |
| numRuntimeErrorTestSuites | 失敗判定 | なし |
| numTotalTests | 実行有無判定・失敗率計算・メッセージ表示 | なし |

#### GlobalConfig

| 参照項目 | 用途 | 取得条件 |
|--------|------|---------|
| watch | watchモード判定 | なし |
| watchAll | watchモード判定 | なし |
| notifyMode | 通知条件判定 | なし |

#### ReporterContext

| 参照項目 | 用途 | 取得条件 |
|--------|------|---------|
| previousSuccess | ステータス変化判定 | なし |
| firstRun | 初回実行判定 | なし |
| startRun | テスト再実行関数 | watchモード時のみ使用 |

### 更新テーブル一覧

| データソース | 操作 | 概要 |
|-----------|------|------|
| ReporterContext | UPDATE | 成功状態とfirstRunフラグを更新 |

#### ReporterContext更新

| 操作 | 項目 | 更新値 | 備考 |
|-----|------|-------|------|
| UPDATE | previousSuccess | success（true/false） | 次回判定用 |
| UPDATE | firstRun | false | 初回実行完了を記録 |

## エラー処理

### エラーケース一覧

| エラー種別 | 発生条件 | 対処方法 |
|----------|---------|---------|
| MODULE_NOT_FOUND | node-notifierがインストールされていない | エラーメッセージを表示してスロー |
| 通知コールバックエラー | watchモードでコールバック実行時にエラー | errをチェックして早期リターン |
| 通知システムエラー | OS通知システムが利用不可 | node-notifierがハンドリング（サイレント失敗） |

### リトライ仕様

| 項目 | 内容 |
|-----|------|
| リトライ回数 | 0（リトライなし） |
| リトライ間隔 | N/A |
| リトライ対象エラー | N/A |

## 配信設定

### レート制限

| 項目 | 内容 |
|-----|------|
| 1分あたり上限 | 制限なし |
| 1日あたり上限 | 制限なし |

### 配信時間帯

制限なし（テスト実行完了時に即座に通知）

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

- 通知内容にはパッケージ名とテスト数のみが含まれ、機密情報は含まれない
- ローカルマシン内でのみ通知が表示されるため、外部への情報漏洩リスクはない
- node-notifierは外部サービスとの通信を行わない
- watchモードのアクションボタンはローカル操作のみを実行

## 備考

- node-notifierパッケージはオプショナルなピア依存関係として定義されている
- macOSでは通知メッセージの先頭に禁止絵文字（⛔️）が付加される（isDarwin判定）
- hint: 'int:transient:1' オプションにより、一時的な通知として扱われる
- timeout: false により、通知はユーザーが閉じるまで表示される
- watchモード時のアクションボタンは一部のOSでのみサポートされる（@ts-expect-errorコメントあり）
- 失敗率の計算では、NaNを防ぐために`Number.isNaN(failed) ? 0 : failed * 100`の処理がある

---

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

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

### 推奨読解順序

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

まず、通知処理で使用されるデータ構造を理解することが重要である。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 1-1 | types.ts | `packages/jest-reporters/src/types.ts` | ReporterContext型の定義を確認（startRun関数を含む） |
| 1-2 | Config.ts | `packages/jest-types/src/Config.ts` | watch, watchAll, notifyMode等の設定値の型定義を確認 |

**読解のコツ**: ReporterContext.startRunは、watchモードでテストを再実行するための関数参照である。

#### Step 2: エントリーポイントを理解する

処理の起点となるファイル・関数を特定する。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 2-1 | NotifyReporter.ts | `packages/jest-reporters/src/NotifyReporter.ts` | onRunCompleteメソッドが通知の起点 |

**主要処理フロー**:
1. **34行目**: `onRunComplete`メソッドの開始
2. **38-39行目**: 成功/失敗判定（numFailedTests === 0 かつ numRuntimeErrorTestSuites === 0）
3. **88-95行目**: notifyMode条件の評価（失敗時）
4. **97-109行目**: 失敗通知のタイトル・メッセージ生成
5. **110-141行目**: watchモード判定とアクションボタン付き通知

#### Step 3: watchモードのアクション処理を理解する

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 3-1 | NotifyReporter.ts | `packages/jest-reporters/src/NotifyReporter.ts` | 114-141行目でwatchモードのコールバック処理 |

**主要処理フロー**:
- **111-112行目**: アクションボタンのラベル定義
- **115-126行目**: アクションボタン付き通知オプション
- **126-141行目**: コールバック関数でのアクション処理
  - **130-131行目**: Exit testsアクション → exit(0)
  - **134-138行目**: Run againアクション → startRun()

#### Step 4: 失敗率計算ロジックを理解する

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 4-1 | NotifyReporter.ts | `packages/jest-reporters/src/NotifyReporter.ts` | 97-103行目で失敗率計算 |

**主要処理フロー**:
- **97行目**: 失敗率計算（numFailedTests / numTotalTests）
- **99-103行目**: タイトル生成でMath.ceilで切り上げ、NaN防止処理

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

```
jest-core/runJest
    │
    ├─ ReporterDispatcher.onRunComplete()
    │      │
    │      └─ NotifyReporter.onRunComplete()
    │             │
    │             ├─ 失敗判定処理
    │             │
    │             ├─ notifyMode条件評価
    │             │
    │             ├─ パッケージ名取得
    │             │      └─ hasteFS.getModuleName()
    │             │
    │             ├─ 失敗率計算
    │             │
    │             └─ node-notifier.notify()
    │                    │
    │                    ├─ [watchモード]
    │                    │      └─ コールバック
    │                    │             ├─ "Exit tests" → exit(0)
    │                    │             └─ "Run again" → startRun()
    │                    │
    │                    └─ OS標準通知システム
    │
    └─ コンテキスト更新
           ├─ previousSuccess = success
           └─ firstRun = false
```

### データフロー図

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

AggregatedResult ───────▶ NotifyReporter
  numFailedTests              │
  numRuntimeErrorTestSuites   ├─▶ 失敗判定
  numTotalTests               ├─▶ 失敗率計算
                              │
GlobalConfig ───────────────▶ │
  notifyMode                  ├─▶ 通知条件判定
  watch                       ├─▶ watchモード判定
  watchAll                    │
  rootDir                     │
                              │
TestContext ────────────────▶ │
  hasteFS                     ├─▶ パッケージ名取得
                              │
ReporterContext ────────────▶ │
  previousSuccess             ├─▶ ステータス変化判定
  firstRun                    │
  startRun                    ├─▶ [watchモード] テスト再実行 ───▶ デスクトップ通知
                              │                                   - title (X% Failed)
                              └─▶ コンテキスト更新                 - message
                                                                   - icon
                                                                   - actions (watchモード)
```

### 関連ファイル一覧

| ファイル | パス | 種別 | 役割 |
|---------|------|------|------|
| NotifyReporter.ts | `packages/jest-reporters/src/NotifyReporter.ts` | ソース | メイン通知処理 |
| BaseReporter.ts | `packages/jest-reporters/src/BaseReporter.ts` | ソース | 基底クラス |
| types.ts | `packages/jest-reporters/src/types.ts` | ソース | ReporterContext型定義（startRun含む） |
| Config.ts | `packages/jest-types/src/Config.ts` | ソース | GlobalConfig型定義（watch, watchAll, notifyMode含む） |
| jest_logo.png | `packages/jest-reporters/assets/jest_logo.png` | アセット | 通知アイコン |
| package.json | `packages/jest-reporters/package.json` | 設定 | node-notifierの依存定義 |
| watch.ts | `packages/jest-core/src/watch.ts` | ソース | startRun関数の実装元 |
