Newer
Older
MiniTias / test / services / sound_service_test.dart
import 'package:flutter_test/flutter_test.dart';
import 'package:shared_preferences/shared_preferences.dart';

import 'package:mini_tias/services/raw_capture_service.dart';
import 'package:mini_tias/services/sound_service.dart';

/// テスト用: RawCaptureService をスタブする.
///
/// プラットフォームチャネルを呼び出す代わりに呼び出し記録のみを行う.
class _FakeRawCaptureService extends RawCaptureService {
  int shutterSoundCallCount = 0;
  int saveCompleteSoundCallCount = 0;
  bool shouldThrow = false;

  @override
  Future<void> playShutterSound() async {
    if (shouldThrow) throw Exception('テスト用エラー');
    shutterSoundCallCount++;
  }

  @override
  Future<void> playSaveCompleteSound() async {
    if (shouldThrow) throw Exception('テスト用エラー');
    saveCompleteSoundCallCount++;
  }
}

void main() {
  TestWidgetsFlutterBinding.ensureInitialized();

  setUp(() {
    SharedPreferences.setMockInitialValues({});
  });

  group('SoundService', () {
    group('初期状態', () {
      test('デフォルトで isSoundEnabled が true である', () {
        final service = SoundService(
          rawCaptureService: _FakeRawCaptureService(),
        );
        expect(service.isSoundEnabled, isTrue);
      });
    });

    group('toggleSound()', () {
      test('1 回呼び出すと isSoundEnabled が false になる', () async {
        final service = SoundService(
          rawCaptureService: _FakeRawCaptureService(),
        );
        await service.toggleSound();
        expect(service.isSoundEnabled, isFalse);
      });

      test('2 回呼び出すと isSoundEnabled が true に戻る', () async {
        final service = SoundService(
          rawCaptureService: _FakeRawCaptureService(),
        );
        await service.toggleSound();
        await service.toggleSound();
        expect(service.isSoundEnabled, isTrue);
      });

      test('切り替え後の値が SharedPreferences に永続化される', () async {
        final service = SoundService(
          rawCaptureService: _FakeRawCaptureService(),
        );
        await service.toggleSound(); // false に切り替え

        final prefs = await SharedPreferences.getInstance();
        expect(prefs.getBool('sound_enabled'), isFalse);
      });

      test('再度切り替えると SharedPreferences の値も true に更新される', () async {
        final service = SoundService(
          rawCaptureService: _FakeRawCaptureService(),
        );
        await service.toggleSound(); // false
        await service.toggleSound(); // true

        final prefs = await SharedPreferences.getInstance();
        expect(prefs.getBool('sound_enabled'), isTrue);
      });
    });

    group('init()', () {
      test(
        'SharedPreferences に sound_enabled=false が保存されていれば false を読み込む',
        () async {
          SharedPreferences.setMockInitialValues({'sound_enabled': false});
          final service = SoundService(
            rawCaptureService: _FakeRawCaptureService(),
          );
          await service.init();
          expect(service.isSoundEnabled, isFalse);
        },
      );

      test(
        'SharedPreferences に sound_enabled=true が保存されていれば true を読み込む',
        () async {
          SharedPreferences.setMockInitialValues({'sound_enabled': true});
          final service = SoundService(
            rawCaptureService: _FakeRawCaptureService(),
          );
          await service.init();
          expect(service.isSoundEnabled, isTrue);
        },
      );

      test('SharedPreferences にキーが存在しない場合はデフォルト true を使う', () async {
        SharedPreferences.setMockInitialValues({});
        final service = SoundService(
          rawCaptureService: _FakeRawCaptureService(),
        );
        await service.init();
        expect(service.isSoundEnabled, isTrue);
      });
    });

    group('playShutterSound()', () {
      test(
        'isSoundEnabled が true のとき RawCaptureService.playShutterSound() を呼ぶ',
        () async {
          final fake = _FakeRawCaptureService();
          final service = SoundService(rawCaptureService: fake);
          // デフォルトで isSoundEnabled == true
          await service.playShutterSound();
          expect(fake.shutterSoundCallCount, 1);
        },
      );

      test(
        'isSoundEnabled が false のとき RawCaptureService.playShutterSound() を呼ばない',
        () async {
          final fake = _FakeRawCaptureService();
          final service = SoundService(rawCaptureService: fake);
          await service.toggleSound(); // false に切り替え
          await service.playShutterSound();
          expect(fake.shutterSoundCallCount, 0);
        },
      );

      test('RawCaptureService がエラーを投げても例外がリスロウされない', () async {
        final fake = _FakeRawCaptureService()..shouldThrow = true;
        final service = SoundService(rawCaptureService: fake);
        // 例外なく完了することを確認
        await expectLater(service.playShutterSound(), completes);
      });
    });

    group('playSaveCompleteSound()', () {
      test(
        'isSoundEnabled が true のとき RawCaptureService.playSaveCompleteSound() を呼ぶ',
        () async {
          final fake = _FakeRawCaptureService();
          final service = SoundService(rawCaptureService: fake);
          await service.playSaveCompleteSound();
          expect(fake.saveCompleteSoundCallCount, 1);
        },
      );

      test(
        'isSoundEnabled が false のとき RawCaptureService.playSaveCompleteSound() を呼ばない',
        () async {
          final fake = _FakeRawCaptureService();
          final service = SoundService(rawCaptureService: fake);
          await service.toggleSound(); // false に切り替え
          await service.playSaveCompleteSound();
          expect(fake.saveCompleteSoundCallCount, 0);
        },
      );

      test('RawCaptureService がエラーを投げても例外がリスロウされない', () async {
        final fake = _FakeRawCaptureService()..shouldThrow = true;
        final service = SoundService(rawCaptureService: fake);
        await expectLater(service.playSaveCompleteSound(), completes);
      });
    });

    group('複合シナリオ', () {
      test('init() 後に toggleSound() すると値が反転する', () async {
        SharedPreferences.setMockInitialValues({'sound_enabled': false});
        final service = SoundService(
          rawCaptureService: _FakeRawCaptureService(),
        );
        await service.init();
        expect(service.isSoundEnabled, isFalse);

        await service.toggleSound();
        expect(service.isSoundEnabled, isTrue);
      });

      test('サウンド ON の状態で複数回 playShutterSound() を呼ぶと呼び出し回数分だけ実行される', () async {
        final fake = _FakeRawCaptureService();
        final service = SoundService(rawCaptureService: fake);
        await service.playShutterSound();
        await service.playShutterSound();
        await service.playShutterSound();
        expect(fake.shutterSoundCallCount, 3);
      });
    });
  });
}