# 機能設計書 71-@vue/runtime-test

## 概要

本ドキュメントは、Vue 3フレームワークにおけるテスト環境向けランタイム実装パッケージ `@vue/runtime-test` の機能設計を記述する。このパッケージは、ブラウザのDOM環境に依存せずにVueコンポーネントのレンダリングやイベント処理をテスト可能にするための軽量なランタイム実装を提供する。

### 本機能の処理概要

`@vue/runtime-test` は、Vueの `createRenderer` APIを利用して、ブラウザDOMの代わりにJavaScriptオブジェクトベースの仮想ノードツリーを操作するカスタムレンダラーを構築する。これにより、Node.jsなどのDOM環境が存在しない環境でも、Vueコンポーネントの完全なレンダリングテストが可能となる。

**業務上の目的・背景**：Vue 3のランタイムコア（`@vue/runtime-core`）は、DOM操作を抽象化した `RendererOptions` インターフェースを通じてプラットフォーム固有の操作を注入する設計となっている。`@vue/runtime-test` はこの抽象化を活用し、ブラウザDOMの代替となる軽量なテスト用ノードシステムを提供する。これにより、Vue本体の内部テスト（特にランタイムコアの単体テスト）をJSDOM等のDOMエミュレーションなしに高速かつ信頼性高く実行できる。

**機能の利用シーン**：主にVueフレームワーク自体の内部テストスイートで使用される。`@vue/runtime-core` パッケージの各種テスト（コンポーネントのマウント、更新、アンマウント、イベント処理、ライフサイクルフック等）は、このテストレンダラーを通じて実行される。また、Vue Test Utilsと組み合わせることで、外部開発者もDOM非依存のテスト環境を構築できる。

**主要な処理内容**：
1. テスト用ノード（TestElement, TestText, TestComment）のCRUD操作を提供する `nodeOps` モジュール
2. プロパティ・イベントリスナーのパッチ処理を行う `patchProp` モジュール
3. テスト用ノードツリーをHTML文字列にシリアライズする `serialize` / `serializeInner` モジュール
4. テスト用ノードに対してイベントを発火する `triggerEvent` モジュール
5. ノード操作ログ（NodeOp）の記録・取得・リセット機能
6. `createRenderer` を利用したカスタムレンダラー（`render`、`createApp`）の構築
7. ワンショットレンダリング検証用の `renderToString` ヘルパー

**関連システム・外部連携**：`@vue/runtime-core` の `createRenderer` APIに依存し、`@vue/reactivity` の `markRaw` を使用してテストノードがリアクティブプロキシ化されるのを防止している。`@vue/shared` のユーティリティ関数（`isOn`, `isArray`, `extend`）も利用する。

**権限による制御**：本パッケージはテスト専用であり、権限制御の概念は存在しない。`package.json` の `"private": true` 設定により、npmレジストリへの公開が防止されている。

## 関連画面

| 画面No | 画面名 | 関連種別 | 関連する操作・処理 |
|--------|--------|----------|------------------|
| - | - | - | テスト専用パッケージのため、画面との直接的な関連はない |

## 機能種別

テストインフラストラクチャ / ランタイム抽象化

## 入力仕様

### 入力パラメータ

| パラメータ名 | 型 | 必須 | 説明 | バリデーション |
|-------------|-----|-----|------|---------------|
| vnode | VNode | Yes | レンダリング対象のVNode | @vue/runtime-coreのVNode型に準拠 |
| container | TestElement | Yes | レンダリング先のルートテスト要素 | nodeOps.createElement()で生成 |
| tag | string | Yes | createElement()で生成する要素のタグ名 | 空文字列は許可されるが推奨されない |
| text | string | Yes | createText() / createComment()で設定するテキスト | 任意の文字列 |
| event | string | Yes | triggerEvent()で発火するイベント名 | 要素のeventListenersに登録済みのイベント名 |
| payload | any[] | No | triggerEvent()でイベントハンドラに渡す引数 | デフォルトは空配列 |
| indent | number | No | serialize()の出力インデント幅 | デフォルトは0（インデントなし） |

### 入力データソース

- テストコードからの直接呼び出し（nodeOps.createElement、render、triggerEvent等）
- @vue/runtime-coreのcreateRendererが提供するレンダリングパイプラインからの内部呼び出し

## 出力仕様

### 出力データ

| 項目名 | 型 | 説明 |
|--------|-----|------|
| TestElement | TestElement | 要素ノードオブジェクト（id, type, tag, children, props, eventListeners） |
| TestText | TestText | テキストノードオブジェクト（id, type, text, parentNode） |
| TestComment | TestComment | コメントノードオブジェクト（id, type, text, parentNode） |
| NodeOp[] | NodeOp[] | dumpOps()で取得する操作ログ配列 |
| serialized | string | serialize() / serializeInner()で生成されるHTML文字列表現 |

### 出力先

- テストコード内のアサーション検証（expect等）
- コンソール出力（エラー発生時のデバッグ情報）

## 処理フロー

### 処理シーケンス

```
1. テストレンダラーの初期化
   └─ createRenderer(extend({ patchProp }, nodeOps))により、patchPropとnodeOpsを統合したカスタムレンダラーを生成

2. ルート要素の作成
   └─ nodeOps.createElement('div')でルートテスト要素を生成し、markRaw()でリアクティブ化を防止

3. VNodeのレンダリング
   └─ render(vnode, root)でVNodeツリーをテストノードツリーに変換
       ├─ createElement: 要素ノード生成 + 操作ログ記録
       ├─ createText: テキストノード生成 + 操作ログ記録
       ├─ insert: 親要素への子ノード挿入 + 操作ログ記録
       ├─ patchProp: プロパティ設定 + イベントリスナー登録 + 操作ログ記録
       └─ setElementText: テキストコンテンツ設定 + 操作ログ記録

4. 操作ログの検証
   └─ dumpOps()で記録された操作ログを取得し、テストアサーションで検証

5. イベントのシミュレーション
   └─ triggerEvent(el, event, payload)で要素のイベントリスナーを直接呼び出し

6. シリアライズによる検証
   └─ serialize(root) / serializeInner(root)でノードツリーをHTML文字列に変換し、期待値と比較
```

### フローチャート

```mermaid
flowchart TD
    A[テスト開始] --> B[ルート要素生成<br>nodeOps.createElement]
    B --> C[resetOps: 操作ログリセット]
    C --> D[render: VNodeレンダリング]
    D --> E{検証方法}
    E -->|操作ログ| F[dumpOps: ログ取得]
    F --> G[操作ログアサーション]
    E -->|DOM構造| H[serialize: 文字列化]
    H --> I[HTML文字列アサーション]
    E -->|イベント| J[triggerEvent: イベント発火]
    J --> K[状態変更確認]
    K --> L[nextTick待機]
    L --> H
    G --> M[テスト終了]
    I --> M
```

## ビジネスルール

### 業務ルール

| ルールNo | ルール名 | 内容 | 適用条件 |
|---------|---------|------|---------|
| BR-71-01 | ノードID一意性 | 各テストノードには自動インクリメントの一意なIDが割り当てられる | すべてのノード生成時 |
| BR-71-02 | リアクティブ化防止 | テストノードはmarkRaw()によりリアクティブプロキシ化が防止される | createElement, createText, createComment時 |
| BR-71-03 | 操作ログ記録 | すべてのノード操作（CREATE, INSERT, REMOVE, SET_TEXT, SET_ELEMENT_TEXT, PATCH）はlogNodeOp()で記録される | すべてのノード操作時 |
| BR-71-04 | イベントリスナー登録 | `on`プレフィックス（isOn判定）を持つプロパティはeventListenersに登録される | patchProp実行時 |
| BR-71-05 | 挿入前の自動削除 | insert操作時、子ノードが既に別の親に属している場合は自動的に削除される（ログなし） | insert操作時 |
| BR-71-06 | シリアライズフィルタ | イベントリスナー（isOn判定）とnull値のプロパティはシリアライズ時に除外される | serialize実行時 |

### 計算ロジック

- イベント名の抽出: `key[2] === ':'` の場合は `key.slice(3)`、それ以外は `key.slice(2).toLowerCase()` でイベント名を決定する（例: `onClick` -> `click`、`on:custom` -> `custom`）

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

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

本機能はデータベースを使用しない。すべてのデータはインメモリのJavaScriptオブジェクトとして管理される。

## エラー処理

### エラーケース一覧

| エラーコード | エラー種別 | 発生条件 | 対処方法 |
|------------|----------|---------|---------|
| - | Error | insert時にrefNodeが親の子要素に存在しない場合 | `'ref is not a child of parent'`エラーをスロー |
| - | Error | remove時にchildが親の子要素に存在しない場合 | `'target is not a childNode of parent'`エラーをスロー |
| - | Error | querySelector()を呼び出した場合 | `'querySelector not supported in test renderer.'`エラーをスロー |

### リトライ仕様

リトライは不要。テスト用途のため即時失敗が適切。

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

トランザクション概念は適用されない。すべてのノード操作は即座に反映される。

## パフォーマンス要件

テスト用途のため、明示的なパフォーマンス要件は定義されていない。ただし、ブラウザDOMを使用しないことでテスト実行速度の向上が期待される。

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

- `"private": true` により公開パッケージとして配布されないことが保証される
- テスト専用パッケージのため、本番環境での使用は想定されていない
- ノードのプロパティには任意の値を設定可能だが、テスト環境でのみ使用されるため、サニタイズ処理は不要

## 備考

- `@vue/runtime-core` パッケージの大部分のテストは、このテストレンダラーを基盤として実行される
- `@vue/runtime-core` からのすべてのエクスポートは `export * from '@vue/runtime-core'` によって再エクスポートされるため、テストコードでは `@vue/runtime-test` からVueのコアAPIも直接インポート可能
- ノードIDはモジュールレベルの変数 `nodeId` で管理されており、テスト間でリセットされない点に注意

---

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

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

### 推奨読解順序

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

テストレンダラーが操作するノードの型定義を理解することが最初のステップである。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 1-1 | nodeOps.ts | `packages/runtime-test/src/nodeOps.ts` | TestElement, TestText, TestComment型の定義を理解する |
| 1-2 | nodeOps.ts | `packages/runtime-test/src/nodeOps.ts` | NodeOp型（操作ログエントリ）の構造を理解する |

**読解のコツ**:
- **3-7行目**: `TestNodeTypes` 列挙型で3種類のノードタイプ（TEXT, ELEMENT, COMMENT）を定義
- **9-16行目**: `NodeOpTypes` 列挙型で6種類のノード操作タイプを定義
- **18-26行目**: `TestElement` インターフェースがブラウザDOMの `Element` に相当する。`children: TestNode[]` でツリー構造を形成し、`eventListeners` でイベントハンドラを保持する
- **28-42行目**: `TestText` と `TestComment` はそれぞれブラウザDOMの `Text` と `Comment` に相当する
- **44-55行目**: `NodeOp` インターフェースは操作ログの1エントリを表現し、操作種別に応じてオプショナルなフィールドを持つ

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

カスタムレンダラーの構築と外部APIの定義を理解する。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 2-1 | index.ts | `packages/runtime-test/src/index.ts` | createRendererの呼び出しとAPIエクスポートを理解する |

**主要処理フロー**:
1. **1-6行目**: `@vue/runtime-core` から `createRenderer` をインポート。これがカスタムレンダラー構築の核となるAPI
2. **12-14行目**: `createRenderer(extend({ patchProp }, nodeOps))` でpatchPropとnodeOpsを統合してレンダラーを生成。`extend` は `Object.assign` のエイリアス
3. **16-17行目**: 生成された `baseRender` と `baseCreateApp` を型付きで再エクスポート
4. **20-24行目**: `renderToString` ヘルパー。ルート要素生成 -> render -> serializeInner の一連の処理をワンライナーで実行
5. **26-29行目**: triggerEvent, serialize, nodeOps, @vue/runtime-coreのすべてのエクスポートを再エクスポート

#### Step 3: ノード操作（nodeOps）を理解する

ブラウザDOMの代替となるノード操作関数群を理解する。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 3-1 | nodeOps.ts | `packages/runtime-test/src/nodeOps.ts` | createElement, createText, createComment, insert, remove等の実装 |

**主要処理フロー**:
- **57-58行目**: `nodeId` カウンターと `recordedNodeOps` 配列がモジュールレベルで宣言。操作ログ機能の基盤
- **60-62行目**: `logNodeOp` - すべてのノード操作で呼び出されるログ記録関数
- **64-72行目**: `resetOps` / `dumpOps` - テスト前にログをリセットし、テスト後にログを取得・クリア
- **74-93行目**: `createElement` - TestElementオブジェクト生成 + ログ記録 + `markRaw()` でリアクティブ化防止
- **140-171行目**: `insert` - refNodeに基づく挿入位置決定。既存の親からの自動削除（ログなし）、refIndex再計算後のsplice挿入
- **173-193行目**: `remove` - 子ノードの親からの削除。`logOp` フラグでログ記録の制御が可能
- **231-233行目**: `querySelector` - テストレンダラーでは未サポート、常にエラーをスロー

#### Step 4: プロパティパッチ（patchProp）を理解する

プロパティとイベントリスナーのパッチ処理を理解する。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 4-1 | patchProp.ts | `packages/runtime-test/src/patchProp.ts` | プロパティの更新とイベントリスナーの登録ロジック |

**主要処理フロー**:
- **4-22行目**: `patchProp` 関数全体。ログ記録 -> プロパティ設定 -> イベントリスナー登録の3段階
- **10-16行目**: NodeOpTypes.PATCHタイプのログを記録。prevValueとnextValueの両方を保持
- **17行目**: `el.props[key] = nextValue` で直接プロパティを設定
- **18-21行目**: `isOn(key)` でイベントハンドラかどうかを判定。`key[2] === ':'` の場合はカスタムイベント構文、それ以外はCamelCaseからlowerCaseへ変換

#### Step 5: イベント発火（triggerEvent）を理解する

テスト用イベントシミュレーションの仕組みを理解する。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 5-1 | triggerEvent.ts | `packages/runtime-test/src/triggerEvent.ts` | イベント発火の実装 |

**主要処理フロー**:
- **4-22行目**: `triggerEvent` 関数。要素のeventListenersオブジェクトからイベント名でリスナーを検索し、直接呼び出す
- **13-16行目**: リスナーが配列の場合は全リスナーを順次実行（複数リスナー対応）
- **17-19行目**: リスナーが単一関数の場合はそのまま実行

#### Step 6: シリアライズ（serialize）を理解する

ノードツリーのHTML文字列化を理解する。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 6-1 | serialize.ts | `packages/runtime-test/src/serialize.ts` | serialize, serializeInner, serializeElement, serializeTextの実装 |

**主要処理フロー**:
- **10-20行目**: `serialize` - ノードタイプに応じてserializeElementまたはserializeTextを呼び分ける
- **22-33行目**: `serializeInner` - 子要素のみをシリアライズ。テスト時にルートコンテナを除いた内容の検証に使用
- **35-57行目**: `serializeElement` - プロパティを文字列化（イベントリスナーとnull値は除外）、開始タグ + 子要素 + 終了タグを構築
- **40-49行目**: プロパティフィルタリング。`isOn(key)` や `value == null` のプロパティを除外し、空文字列値は属性名のみ出力
- **51行目**: インデントの計算。`' '.repeat(indent).repeat(depth)` で深さに応じたインデントを生成

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

```
@vue/runtime-test/src/index.ts (エントリーポイント)
    |
    +-- createRenderer() [@vue/runtime-core]
    |       |
    |       +-- nodeOps (ノード操作群)
    |       |       +-- createElement()
    |       |       +-- createText()
    |       |       +-- createComment()
    |       |       +-- insert()
    |       |       +-- remove()
    |       |       +-- setText()
    |       |       +-- setElementText()
    |       |       +-- parentNode()
    |       |       +-- nextSibling()
    |       |       +-- querySelector() [未サポート]
    |       |       +-- setScopeId()
    |       |
    |       +-- patchProp()
    |               +-- logNodeOp()
    |               +-- isOn() [@vue/shared]
    |
    +-- render() -> baseRender [createRendererの戻り値]
    |
    +-- createApp() -> baseCreateApp [createRendererの戻り値]
    |
    +-- renderToString()
    |       +-- nodeOps.createElement()
    |       +-- render()
    |       +-- serializeInner()
    |
    +-- triggerEvent()
    |       +-- isArray() [@vue/shared]
    |
    +-- serialize() / serializeInner()
            +-- serializeElement()
            +-- serializeText()
```

### データフロー図

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

VNode (仮想ノード)    -->  createRenderer内部の           -->  TestElement/TestText/TestComment
                           patch/mount処理                     (テストノードツリー)

nodeOps関数呼び出し   -->  logNodeOp()                   -->  recordedNodeOps[]
                                                              (操作ログ配列)

TestElement           -->  triggerEvent()                 -->  イベントハンドラ実行
+ イベント名                                                   (状態変更)

TestNode              -->  serialize() /                  -->  HTML文字列
(テストノードツリー)        serializeInner()                    (検証用文字列表現)

recordedNodeOps[]     -->  dumpOps()                      -->  NodeOp[]
(蓄積された操作ログ)                                           (取得済みログ + ログリセット)
```

### 関連ファイル一覧

| ファイル | パス | 種別 | 役割 |
|---------|------|------|------|
| index.ts | `packages/runtime-test/src/index.ts` | ソース | エントリーポイント。createRendererによるカスタムレンダラー構築、render/createApp/renderToStringの定義とエクスポート |
| nodeOps.ts | `packages/runtime-test/src/nodeOps.ts` | ソース | テスト用ノード型定義とCRUD操作、操作ログ記録機能 |
| patchProp.ts | `packages/runtime-test/src/patchProp.ts` | ソース | プロパティパッチとイベントリスナー登録 |
| triggerEvent.ts | `packages/runtime-test/src/triggerEvent.ts` | ソース | テスト用イベント発火ユーティリティ |
| serialize.ts | `packages/runtime-test/src/serialize.ts` | ソース | テストノードツリーのHTML文字列シリアライズ |
| index.js | `packages/runtime-test/index.js` | エントリー | CJS形式エントリー。環境変数に応じてdev/prodビルドを切り替え |
| package.json | `packages/runtime-test/package.json` | 設定 | パッケージ定義。private: trueにより非公開 |
| testRuntime.spec.ts | `packages/runtime-test/__tests__/testRuntime.spec.ts` | テスト | テストレンダラー自体のテストスイート |
| renderer.ts | `packages/runtime-core/src/renderer.ts` | ソース(外部) | createRenderer関数の定義。テストレンダラーが依存する基盤API |
