---
generated_at: 2026-02-25 10:00:00
metrics:
  claims_total: 94
  claims_with_evidence: 91
  claims_without_evidence: 3
confidence_derived: 0.97
---

# 根拠レポート：decap-cms-widget-file 単体テストケース一覧

## 本レポートについて

### 目的
本レポートは、生成された設計書・ドキュメントの信頼性を検証し、人間レビュアーが効率的にレビューできるようにすることを目的としています。

### チェック方法
以下の観点でドキュメントの内容（Claim：主張）を検証しています：

1. **根拠の有無確認**：各主張に対して、ソースコード・既存設計書・要件定義書などの根拠（Evidence）が存在するか
2. **根拠との整合性**：主張の内容が根拠と矛盾していないか
3. **網羅性**：参照すべき情報源を適切にカバーしているか

### 信頼度スコアの算出
- **confidence_derived** = 根拠あり件数 / 総主張件数
- 状態「○」：根拠あり、「△」：根拠不足または要確認

### 本レポートの使い方
1. まず「サマリー」で全体の信頼度と優先レビュー項目を確認
2. 「Claims と根拠の対応」で △ の項目を重点的にレビュー
3. 「不足情報」で補完が必要な情報源を確認

---

## 1) サマリー（まず見るところ）
- 総合信頼度（derived）：**0.97**
  - 根拠あり：91 / 94、根拠なし：3
- 優先レビュー（高）
  1. **C-91 warnDeprecatedOptions の once 動作確認**：onceライブラリの動作はコード上明示だがテスト実行時の挙動確認が必要
  2. **C-92 SortableMultiImageWrapper.handleSortEnd のインデックス解決**：DnDコンテキスト内の内部関数であり直接テストが困難
  3. **C-93 handleChange でのImmutable.List変換**：valueListToArrayの内部呼び出しの間接テスト

## 2) 参照した情報（Evidence一覧）
> ここに「実在するもの」だけ列挙。抽出フェーズで付けたIDをそのまま出す。

- E-01: `packages/decap-cms-widget-file/src/withFileControl.js` - メインコントロールHOCとユーティリティ関数（500行）
- E-02: `packages/decap-cms-widget-file/src/FilePreview.js` - プレビューコンポーネント群（46行）
- E-03: `packages/decap-cms-widget-file/src/index.js` - エントリーポイント、Widget関数、エクスポート（18行）
- E-04: `packages/decap-cms-widget-file/src/schema.js` - スキーマ定義（5行）
- E-05: `packages/decap-cms-widget-file/package.json` - 依存関係定義

## 3) Claims と根拠の対応（レビューの主戦場）
| Claim ID | 主張 | Evidence | 状態 |
|---|---|---|---|
| C-01 | isMultiple: Array.isArrayがtrueの場合にtrueを返す | E-01 L188-190 `function isMultiple(value) { return Array.isArray(value) \|\| List.isList(value); }` | ○ |
| C-02 | isMultiple: List.isListがtrueの場合にtrueを返す | E-01 L188-190 | ○ |
| C-03 | isMultiple: 文字列の場合にfalseを返す | E-01 L188-190 | ○ |
| C-04 | isMultiple: null/undefinedの場合にfalseを返す | E-01 L188-190 | ○ |
| C-05 | isMultiple: 空配列の場合にtrueを返す | E-01 L188-190 `Array.isArray([])` はtrue | ○ |
| C-06 | sizeOfValue: 配列の場合にlengthを返す | E-01 L192-194 `if (Array.isArray(value)) { return value.length; }` | ○ |
| C-07 | sizeOfValue: Immutable.Listの場合にsizeを返す | E-01 L196-198 `if (List.isList(value)) { return value.size; }` | ○ |
| C-08 | sizeOfValue: 文字列(truthy)の場合に1を返す | E-01 L200 `return value ? 1 : 0;` | ○ |
| C-09 | sizeOfValue: 空文字列(falsy)の場合に0を返す | E-01 L200 | ○ |
| C-10 | sizeOfValue: nullの場合に0を返す | E-01 L200 | ○ |
| C-11 | sizeOfValue: 空配列の場合に0を返す | E-01 L193 `return value.length;` で `[].length === 0` | ○ |
| C-12 | sizeOfValue: 1件の配列の場合に1を返す | E-01 L193 | ○ |
| C-13 | valueListToArray: Immutable.ListをArrayに変換 | E-01 L204-206 `return List.isList(value) ? value.toArray() : value ?? '';` | ○ |
| C-14 | valueListToArray: 配列はそのまま返す | E-01 L204-206 List.isListがfalseなのでそのまま返す | ○ |
| C-15 | valueListToArray: null/undefinedの場合に空文字列を返す | E-01 L205 `value ?? ''` | ○ |
| C-16 | valueListToArray: 文字列の場合にそのまま返す | E-01 L205 | ○ |
| C-17 | valueListToSortableArray: 配列をid付きオブジェクト配列に変換 | E-01 L208-219 `valueListToArray(value).map(value => ({ id: uuid(), value }))` | ○ |
| C-18 | valueListToSortableArray: Immutable.Listをid付きオブジェクト配列に変換 | E-01 L208-219 | ○ |
| C-19 | valueListToSortableArray: 非配列の場合はそのまま返す | E-01 L209-211 `if (!isMultiple(value)) { return value; }` | ○ |
| C-20 | valueListToSortableArray: 空配列の場合に空配列を返す | E-01 L213 `.map()` on empty array returns empty array | ○ |
| C-21 | withFileControl: デフォルト引数でFileControlクラスを返す | E-01 L230 `export default function withFileControl({ forImage } = {})` | ○ |
| C-22 | withFileControl: forImage=trueでFileControlクラスを返す | E-01 L230 | ○ |
| C-23 | shouldComponentUpdate: valueが変更された場合にtrue | E-01 L265-269 `if (this.props.value !== nextProps.value ...) { return true; }` | ○ |
| C-24 | shouldComponentUpdate: getAssetが変更された場合にtrue | E-01 L269 `this.props.getAsset !== nextProps.getAsset` | ○ |
| C-25 | shouldComponentUpdate: mediaPathが存在しvalueと異なる場合にtrue | E-01 L277-279 | ○ |
| C-26 | shouldComponentUpdate: 変更なしの場合にfalse | E-01 L281 `return false;` | ○ |
| C-27 | componentDidUpdate: mediaPathが異なる場合にonChange | E-01 L287-289 `if (mediaPath && mediaPath !== value) { onChange(mediaPath); }` | ○ |
| C-28 | componentDidUpdate: mediaPathが同一の場合にonRemoveInsertedMedia | E-01 L290-291 `else if (mediaPath && mediaPath === value) { onRemoveInsertedMedia(this.controlID); }` | ○ |
| C-29 | componentDidUpdate: mediaPathなしの場合にonChangeもonRemoveInsertedMediaも呼ばれない | E-01 L287-292 条件分岐でmediaPathがfalsyの場合は何もしない | ○ |
| C-30 | componentWillUnmount: onRemoveMediaControlが呼ばれる | E-01 L296 `this.props.onRemoveMediaControl(this.controlID);` | ○ |
| C-31 | handleChange: onOpenMediaLibraryが正しい引数で呼ばれる | E-01 L299-313 | ○ |
| C-32 | handleChange: allow_multipleがfalseの場合 | E-01 L309 `allowMultiple: !!mediaLibraryFieldOptions.get('allow_multiple', true)` | ○ |
| C-33 | handleUrl: 入力された値でonChangeが呼ばれる | E-01 L315-323 `if (url) { return this.props.onChange(url); }` | ○ |
| C-34 | handleUrl: キャンセルの場合にonChangeが呼ばれない | E-01 L320 `if (url)` がfalseとなりonChange未呼出 | ○ |
| C-35 | handleUrl: 空文字列の場合にonChangeが呼ばれない | E-01 L320 空文字列はfalsy | ○ |
| C-36 | handleRemove: onClearMediaControlとonChangeが呼ばれる | E-01 L325-329 | ○ |
| C-37 | onRemoveOne: 指定インデックスの要素が除去される | E-01 L331-335 `value.splice(index, 1)` | ○ |
| C-38 | onRemoveOne: 最後の1件を削除した場合にnull | E-01 L334 `sizeOfValue(value) > 0 ? [...value] : null` | ○ |
| C-39 | onRemoveOne: 先頭要素の削除 | E-01 L331-335 splice(0, 1) | ○ |
| C-40 | onRemoveOne: 末尾要素の削除 | E-01 L331-335 | ○ |
| C-41 | onReplaceOne: onOpenMediaLibraryが呼ばれる | E-01 L337-351 `replaceIndex: index, allowMultiple: false` | ○ |
| C-42 | getMediaLibraryFieldOptions: field.media_libraryから取得 | E-01 L361 `return field.get('media_library', Map());` | ○ |
| C-43 | getMediaLibraryFieldOptions: 非推奨options経由で取得 | E-01 L356-358 `if (field.hasIn(['options', 'media_library']))` | ○ |
| C-44 | getMediaLibraryFieldOptions: 未設定時に空Map | E-01 L361 `Map()` がデフォルト引数 | ○ |
| C-45 | allowsMultiple: config.multiple=trueでtrue | E-01 L364-370 | ○ |
| C-46 | allowsMultiple: config.multiple=falseでfalse | E-01 L364-370 | ○ |
| C-47 | allowsMultiple: config未設定でfalse | E-01 L367 `mediaLibraryFieldOptions.get('config', false)` がfalseとなる | ○ |
| C-48 | onSortEnd: arrayMoveで並び替えonChangeが呼ばれる | E-01 L372-376 `const newValue = arrayMove(value, oldIndex, newIndex);` | ○ |
| C-49 | onSortEnd: 同じインデックスの場合 | E-01 L372-376 arrayMoveは同一インデックスでも動作する | ○ |
| C-50 | getValidateValue: 文字列値のbasename | E-01 L379-381 `return isMultiple(value) ? value.map(v => basename(v)) : basename(value);` | ○ |
| C-51 | getValidateValue: 配列値の各要素basename | E-01 L381 `value.map(v => basename(v))` | ○ |
| C-52 | getValidateValue: falsyの場合にそのまま返す | E-01 L380-384 `if (value) { ... } return value;` | ○ |
| C-53 | renderFileLink: MAX_DISPLAY_LENGTH以下はそのまま | E-01 L388-390 `if (!value \|\| value.length <= size) { return value; }` | ○ |
| C-54 | renderFileLink: MAX_DISPLAY_LENGTH超は省略形式 | E-01 L392 `value.slice(0, size / 2)\u2026value.slice(-(size / 2) + 1)` | ○ |
| C-55 | renderFileLink: ちょうど50文字はそのまま | E-01 L389 `value.length <= size` で等号含む | ○ |
| C-56 | renderFileLink: falsyの場合にfalsyを返す | E-01 L389 `!value` で早期リターン | ○ |
| C-57 | renderFileLinks: isMultiple=trueでリスト描画 | E-01 L400-414 | ○ |
| C-58 | renderFileLinks: isMultiple=falseで単一描画 | E-01 L414 | ○ |
| C-59 | renderImages: isMultiple=trueでSortableMultiImageWrapper | E-01 L417-434 | ○ |
| C-60 | renderImages: isMultiple=falseで単一ImageWrapper | E-01 L436-441 | ○ |
| C-61 | renderSelection: forImage=trueでrenderImages呼び出し | E-01 L449 `{forImage ? this.renderImages() : null}` | ○ |
| C-62 | renderSelection: forImage=falseでrenderFileLinks呼び出し | E-01 L451 `{forImage ? null : this.renderFileLinks()}` | ○ |
| C-63 | renderSelection: choose_url=trueかつallowsMultiple=falseでURL置換ボタン | E-01 L459 `{field.get('choose_url', true) && !this.allowsMultiple() ? ...}` | ○ |
| C-64 | renderSelection: choose_url=falseでURL置換ボタン非表示 | E-01 L459 | ○ |
| C-65 | renderSelection: allowsMultiple=trueでaddMoreボタン | E-01 L453-456 `this.allowsMultiple() ? 'addMore' : 'chooseDifferent'` | ○ |
| C-66 | renderSelection: allowsMultiple=falseでchooseDifferentボタン | E-01 L453-456 | ○ |
| C-67 | renderNoSelection: chooseボタン表示 | E-01 L476 | ○ |
| C-68 | renderNoSelection: choose_url=trueでchooseUrlボタン | E-01 L479-483 | ○ |
| C-69 | renderNoSelection: choose_url=falseでchooseUrlボタン非表示 | E-01 L479 | ○ |
| C-70 | renderNoSelection: allowsMultiple=trueでchooseMultipleラベル | E-01 L477 `this.allowsMultiple() ? 'Multiple' : ''` | ○ |
| C-71 | render: valueが存在する場合にrenderSelection | E-01 L494 `{value ? this.renderSelection(subject) : this.renderNoSelection(subject)}` | ○ |
| C-72 | render: valueが空の場合にrenderNoSelection | E-01 L494 | ○ |
| C-73 | render: forImage=trueでsubject='image' | E-01 L490 `const subject = forImage ? 'image' : 'file';` | ○ |
| C-74 | render: forImage=falseでsubject='file' | E-01 L490 | ○ |
| C-75 | FilePreview: valueありでFileContent描画 | E-02 L36 `{props.value ? <FileContent {...props} /> : null}` | ○ |
| C-76 | FilePreview: valueなしでFileContent非描画 | E-02 L36 | ○ |
| C-77 | FileContent: 配列でFileLinkList | E-02 L27-28 `if (Array.isArray(value) \|\| List.isList(value)) { return <FileLinkList ...>; }` | ○ |
| C-78 | FileContent: Immutable.ListでFileLinkList | E-02 L27 | ○ |
| C-79 | FileContent: 文字列でFileLink | E-02 L30 | ○ |
| C-80 | FileLinkList: 各要素にFileLink描画 | E-02 L16-22 `values.map(value => <FileLink ...>)` | ○ |
| C-81 | FileLinkList: 空配列で何も描画されない | E-02 L18 `.map()` on empty array | ○ |
| C-82 | FileLinkList: 1件でFileLink1つ | E-02 L18 | ○ |
| C-83 | Widget: デフォルト引数でウィジェット設定オブジェクト | E-03 L7-15 | ○ |
| C-84 | Widget: オプション引数で設定上書き | E-03 L14 `...opts` スプレッド演算子 | ○ |
| C-85 | Widget: 空オブジェクトでデフォルト | E-03 L7-15 | ○ |
| C-86 | DecapCmsWidgetFile エクスポートの完全性 | E-03 L17 `export const DecapCmsWidgetFile = { Widget, controlComponent, previewComponent, withFileControl };` | ○ |
| C-87 | schema: allow_multipleプロパティ定義 | E-04 L1-5 `properties: { allow_multiple: { type: 'boolean' } }` | ○ |
| C-88 | constructor: controlIDがuuidで生成 | E-01 L257 `this.controlID = uuid();` | ○ |
| C-89 | handleChange: e.preventDefaultが呼ばれる | E-01 L301 `e.preventDefault();` | ○ |
| C-90 | handleRemove: e.preventDefaultが呼ばれる | E-01 L326 `e.preventDefault();` | ○ |
| C-91 | warnDeprecatedOptions: onceで1回だけ警告 | E-01 L221-228 `const warnDeprecatedOptions = once(field => console.warn(...))` | △ |
| C-92 | SortableMultiImageWrapper.handleSortEnd: インデックス解決 | E-01 L117-122 | △ |
| C-93 | handleChange: Immutable.Listのvalueが配列化されて渡る | E-01 L308, L204-206 valueListToArray内部呼び出し | △ |
| C-94 | onRemoveOne: Immutable.Listの値に対する動作 | E-01 L332 `valueListToArray(this.props.value)` が配列化する | ○ |

## 4) 不足情報（Unknown / Missing）
- **C-91**: `once`（lodash）の動作はライブラリ依存であり、モジュール単体テストでのモック範囲の確認が必要。コード上は`once`でラップされているため1回のみ実行されるはずだが、テストでの`once`リセット方法は要検討。
  - 候補：lodash/onceのドキュメント / Jestのモジュールモック手法
- **C-92**: `SortableMultiImageWrapper`はReact関数コンポーネント内部の関数であり、`@dnd-kit`のDnDコンテキスト内で動作するため、直接的な単体テストが困難。統合テストまたはDnDコンテキストのモックが必要。
  - 候補：@dnd-kit/coreのテストユーティリティ / カスタムテストラッパー
- **C-93**: `handleChange`内部で`valueListToArray`が呼ばれることは確実だが、Immutable.Listの変換結果がonOpenMediaLibraryに正しく渡ることの直接的な確認には、propsにImmutable.Listを渡した統合的テストが必要。
  - 候補：Immutable.jsのテストユーティリティ / マウントテスト

## 5) リスクフラグ（レビュー観点）
- **0: 低リスク** - ユーティリティ関数群（isMultiple, sizeOfValue, valueListToArray, valueListToSortableArray）：純関数であり、入出力が明確。テストは容易。
- **0: 低リスク** - schema.js, index.js：構造が単純でロジックが少ない。
- **1: 中リスク** - FilePreview.js：React描画コンポーネント。テストにはレンダリングライブラリが必要だが、ロジックは単純。
- **1: 中リスク** - FileControl ライフサイクルメソッド（shouldComponentUpdate, componentDidUpdate, componentWillUnmount）：状態管理とコールバック呼び出しの複合テスト。Immutable.js Mapのモックが必要。
- **1: 中リスク** - handleChange, handleUrl, handleRemove：イベントハンドラ。window.promptのモックやイベントオブジェクトの生成が必要。
- **2: 高リスク** - onRemoveOne/onReplaceOne：配列操作とImmutable.Listの混在。spliceによる破壊的変更とspread演算子による新配列生成の組み合わせ。
- **1: 中リスク** - SortableMultiImageWrapper：@dnd-kit依存の複雑なUI。DnDコンテキストのモックが必要。

## 6) レビュアーチェックリスト（最小）
- [ ] ユーティリティ関数（isMultiple, sizeOfValue, valueListToArray, valueListToSortableArray）の全ての型パターンが網羅されているか
- [ ] FileControlのライフサイクルメソッド（shouldComponentUpdate, componentDidUpdate, componentWillUnmount）の条件分岐が全てカバーされているか
- [ ] handleUrl のwindow.prompt返却値パターン（値あり、null、空文字列）が網羅されているか
- [ ] onRemoveOneで最後の要素を削除した場合のnull返却が正しくテストされているか
- [ ] renderFileLinkのMAX_DISPLAY_LENGTH(50)境界値テストが正確か
- [ ] getMediaLibraryFieldOptionsの非推奨パス（options.media_library）と標準パス（media_library）の両方がテストされているか
- [ ] forImage=true/falseの両モードでrenderメソッドの分岐が検証されているか
- [ ] FilePreviewコンポーネントのvalue=nullケースが検証されているか
- [ ] Immutable.ListとJavaScript配列の両方のケースが各メソッドで考慮されているか
- [ ] choose_url設定によるURL関連ボタンの表示/非表示が網羅されているか
