Skip to content

useTTSフックをリファクタリングして独立テスト可能なモジュールに分離#5344

Merged
TinyKitten merged 6 commits intodevfrom
chore/tts-refactor
Feb 19, 2026
Merged

useTTSフックをリファクタリングして独立テスト可能なモジュールに分離#5344
TinyKitten merged 6 commits intodevfrom
chore/tts-refactor

Conversation

@TinyKitten
Copy link
Member

@TinyKitten TinyKitten commented Feb 19, 2026

Co-Authored-By: Claude Opus 4.6 noreply@anthropic.com

Summary by CodeRabbit

  • New Features
    • 多言語TTSの順序再生(日本語優先、英語の遅延開始対応)、生成音声の取得とキャッシュ、安定した再生ハンドリングを導入しました。
    • Base64→バイト配列変換ユーティリティを追加しました。
  • Bug Fixes
    • 再生エラー・APIエラー・タイムアウト時の挙動を改善し、不要な再生やネットワークを抑制、アンマウント時の確実なクリーンアップを強化しました。
  • Tests
    • フェッチ、再生、デコード、シーケンス、エラー/タイムアウト/クリーンアップを網羅する包括的なテストを追加しました。

@TinyKitten TinyKitten self-assigned this Feb 19, 2026
@github-actions github-actions bot added the react label Feb 19, 2026
@coderabbitai
Copy link
Contributor

coderabbitai bot commented Feb 19, 2026

No actionable comments were generated in the recent review. 🎉


📝 Walkthrough

Walkthrough

TTS再生実装を大幅にリファクタし、Expo Audio直接利用から抽象化された ttsAudioPlayerttsSpeechFetcher を導入。フェッチ→キャッシュ→再生の流れ、JA優先の遅延EN再生、タイムアウト・エラーハンドリング、全プレイヤーの一元クリーンアップ、および関連テスト群を追加。

Changes

Cohort / File(s) Summary
useTTS フック
src/hooks/useTTS.ts, src/hooks/useTTS.test.ts
useTTS を ttsAudioPlayer/ttsSpeechFetcher ベースへ再設計。JA→EN の遅延再生制御(EN_PLAYBACK_DELAY_MS)、シーケンス保護、タイムアウト/クリーンアップ経路を実装。テストをモック強化・多言語シナリオで拡張。
TTS オーディオ再生ユーティリティ
src/utils/ttsAudioPlayer.ts, src/utils/ttsAudioPlayer.test.ts
新規モジュール追加:playAudioPlayAudioHandlesafeRemoveListenersafeRemovePlayer を実装し再生・リスナー管理と安全なクリーンアップをカプセル化。包括的な単体テストを追加。
TTS 音声取得ユーティリティ
src/utils/ttsSpeechFetcher.ts, src/utils/ttsSpeechFetcher.test.ts
新規 fetchSpeechAudio 実装:SSML 包装、POST リクエスト、レスポンス検証、base64→ファイル書き出し(cache)を実装。成功/失敗パスとエッジケースのテスト群を追加。
Base64 ユーティリティ
src/utils/base64ToUint8Array.ts, src/utils/base64ToUint8Array.test.ts
base64 文字列を Uint8Array に変換するユーティリティを追加。パディング/無効文字の取り扱いと各種デコードケースを検証するテストを追加。
テスト用 Expo モック
src/utils/test/ttsMocks.ts
テスト用の mockFetch と簡易 expo-file-system スタブを追加し、テスト内のネットワーク/ファイル操作を制御可能に。

Sequence Diagram(s)

sequenceDiagram
    participant Hook as useTTS Hook
    participant Fetcher as ttsSpeechFetcher
    participant FS as FileSystem
    participant PlayerMgr as ttsAudioPlayer
    participant AudioAPI as Expo Audio API

    Hook->>Fetcher: fetchSpeechAudio(textJa, textEn, apiUrl, token)
    Fetcher->>Fetcher: SSML組立・POSTボディ作成
    Fetcher->>AudioAPI: POST to TTS API (ネットワーク)
    Fetcher-->>Fetcher: JSONレスポンス受領・検証
    Fetcher->>FS: write jaAudioContent -> pathJa
    Fetcher->>FS: write enAudioContent -> pathEn
    Fetcher-->>Hook: return {id, pathJa, pathEn}

    Hook->>PlayerMgr: playAudio(pathJa, onFinish, onError)
    PlayerMgr->>AudioAPI: createAudioPlayer(pathJa)
    AudioAPI-->>PlayerMgr: playback status updates
    PlayerMgr-->>Hook: onFinish()

    Note over Hook: 必要なら EN_PLAYBACK_DELAY_MS 待機

    Hook->>PlayerMgr: playAudio(pathEn, onFinish, onError)
    PlayerMgr->>AudioAPI: createAudioPlayer(pathEn)
    AudioAPI-->>PlayerMgr: playback status updates
    PlayerMgr-->>Hook: onFinish()

    Hook->>PlayerMgr: cleanupAllPlayers()
    PlayerMgr->>AudioAPI: pause/remove listeners / remove players
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related PRs

Poem

🐰 よしよし、音を詰めて走るよ
まずは日本語、少しだけ待って英語へ
ファイルに書いて、プレイヤーつないで
エラーもタイムアウトもきれいに片付ける
うさぎは跳ねて、クリーンアップでにっこり 🎶

🚥 Pre-merge checks | ✅ 3
✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed プルリクエストのタイトルは、変更セットの主要な変更内容「useTTSフックをリファクタリングして独立テスト可能なモジュールに分離」を正確に説明しており、変更内容と完全に関連している。
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch chore/tts-refactor

Comment @coderabbitai help to get the list of available commands and usage tips.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 4

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@src/utils/base64ToUint8Array.test.ts`:
- Around line 3-43: Add an afterEach hook that calls jest.clearAllMocks() in
this test file to ensure mocks are reset between tests: add afterEach(() => {
jest.clearAllMocks(); }) near the top of the describe block (or immediately
after it) so all tests that call base64ToUint8Array run with cleared mocks;
reference the existing describe('base64ToUint8Array', ...) and the
base64ToUint8Array tests when inserting the hook.

In `@src/utils/ttsAudioPlayer.test.ts`:
- Around line 87-91: The test setup currently calls jest.clearAllMocks() only in
the beforeEach block inside the describe('playAudio') suite; change this to
follow guidelines by adding an afterEach hook that calls jest.clearAllMocks()
(or move the existing jest.clearAllMocks() into an afterEach) so mocks are
cleared after each test; update the describe('playAudio') block to include
afterEach(() => { jest.clearAllMocks(); }) referencing the existing beforeEach
and the test suite name to locate the change.

In `@src/utils/ttsAudioPlayer.ts`:
- Around line 1-3: 現在のインポートと依存 (AudioPlayer 型 と createAudioPlayer の使用) は
expo-audio に依存しているため、expo-av の Sound ベース API に置き換えてください: import を 'expo-audio'
から削除して 'expo-av' の Audio.Sound を使い、createAudioPlayer と AudioPlayer
型を参照している箇所をすべて Audio.Sound
インスタンスの生成・再生制御(loadAsync/playAsync/pauseAsync/unloadAsync/setOnPlaybackStatusUpdate
など)に書き換え、再生開始・停止・シーク・ボリューム・ループ状態の管理ロジックを Sound のメソッドに移植して不要な型や createAudioPlayer
呼び出しを削除してください。エラーハンドリングと後始末(unloadAsync)を追加し、既存コードの関数名(createAudioPlayer、AudioPlayer)を参照して実装箇所を特定して置換してください。

In `@src/utils/ttsSpeechFetcher.test.ts`:
- Around line 29-33: The test suite for fetchSpeechAudio currently calls
jest.clearAllMocks() in beforeEach; add an afterEach(() => {
jest.clearAllMocks(); }) (or move the existing call) inside the
describe('fetchSpeechAudio', ...) block so mocks are cleared after each test as
required by the guideline; keep any existing beforeEach setup intact and ensure
jest.mock(...) usages remain for network/Firebase layers.

TinyKitten and others added 3 commits February 19, 2026 20:19
Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick comments (1)
src/utils/ttsSpeechFetcher.test.ts (1)

3-20: src/utils/test/ に共通モック関数を抽出

expo/fetchexpo-file-system のモックが src/hooks/useTTS.test.tssrc/utils/ttsSpeechFetcher.test.ts で重複しています。コーディングガイドラインに従い、src/utils/test/ にモック設定を集約する共通ヘルパーを作成し、両テストファイルで再利用してください。

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/utils/ttsSpeechFetcher.test.ts` around lines 3 - 20, Extract the
duplicated Jest mocks for expo/fetch and expo-file-system into a shared helper
(e.g., export a setupTestMocks function) under src/utils/test/, then replace the
inline mocks in both src/utils/ttsSpeechFetcher.test.ts and
src/hooks/useTTS.test.ts with a single import and call to that helper; the
helper should define mockFetch and jest.mock('expo/fetch', ...) and
jest.mock('expo-file-system', ...) with the same Paths and File class behavior
so tests continue to use the same mockFetch, Paths.cache and File(uri) semantics
while removing the duplicated mock blocks from each test file.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@src/utils/ttsSpeechFetcher.test.ts`:
- Around line 3-20: Extract the duplicated Jest mocks for expo/fetch and
expo-file-system into a shared helper (e.g., export a setupTestMocks function)
under src/utils/test/, then replace the inline mocks in both
src/utils/ttsSpeechFetcher.test.ts and src/hooks/useTTS.test.ts with a single
import and call to that helper; the helper should define mockFetch and
jest.mock('expo/fetch', ...) and jest.mock('expo-file-system', ...) with the
same Paths and File class behavior so tests continue to use the same mockFetch,
Paths.cache and File(uri) semantics while removing the duplicated mock blocks
from each test file.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@TinyKitten TinyKitten merged commit fe48221 into dev Feb 19, 2026
6 checks passed
@TinyKitten TinyKitten deleted the chore/tts-refactor branch February 19, 2026 11:40
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant