import 'package:flutter_test/flutter_test.dart'; import 'package:shared_preferences/shared_preferences.dart'; import 'package:smtias_capture/services/raw_capture_service.dart'; import 'package:smtias_capture/services/sound_service.dart'; /// テスト用: RawCaptureService をスタブする. /// /// プラットフォームチャネルを呼び出す代わりに呼び出し記録のみを行う. class _FakeRawCaptureService extends RawCaptureService { int shutterSoundCallCount = 0; int saveCompleteSoundCallCount = 0; bool shouldThrow = false; @override Future playShutterSound() async { if (shouldThrow) throw Exception('テスト用エラー'); shutterSoundCallCount++; } @override Future 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); }); }); }); }