import 'package:flutter/material.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:lightmeter/data/geolocation_service.dart'; import 'package:lightmeter/providers/logbook_photos_provider.dart'; import 'package:m3_lightmeter_iap/m3_lightmeter_iap.dart'; import 'package:m3_lightmeter_resources/m3_lightmeter_resources.dart'; import 'package:mocktail/mocktail.dart'; class _MockLogbookPhotosStorageService extends Mock implements IapStorageService {} class _MockGeolocationService extends Mock implements GeolocationService {} void main() { TestWidgetsFlutterBinding.ensureInitialized(); late _MockLogbookPhotosStorageService storageService; late _MockGeolocationService geolocationService; setUpAll(() { storageService = _MockLogbookPhotosStorageService(); geolocationService = _MockGeolocationService(); registerFallbackValue(ApertureValue.values.first); registerFallbackValue(_customPhotos.first); registerFallbackValue(ShutterSpeedValue.values.first); }); setUp(() { when(() => storageService.addPhoto(any())).thenAnswer((_) async {}); when( () => storageService.updatePhoto( id: any(named: 'id'), note: any(named: 'note'), apertureValue: any?>(named: 'apertureValue'), shutterSpeedValue: any?>(named: 'shutterSpeedValue'), ), ).thenAnswer((_) async {}); when(() => storageService.deletePhoto(any())).thenAnswer((_) async {}); when(() => storageService.getPhotos()).thenAnswer((_) => Future.value(_customPhotos)); when(() => geolocationService.getCurrentPosition()).thenAnswer((_) => Future.value()); }); tearDown(() { reset(storageService); }); Future pumpTestWidget(WidgetTester tester, IAPProductStatus productStatus) async { await tester.pumpWidget( IAPProducts( products: [ IAPProduct( storeId: IAPProductType.paidFeatures.storeId, status: productStatus, price: '0.0\$', ), ], child: LogbookPhotosProvider( storageService: storageService, geolocationService: geolocationService, child: const _Application(), ), ), ); await tester.pumpAndSettle(); } void expectLogbookPhotosCount(int count) { expect(find.text(_LogbookPhotosCount.text(count)), findsOneWidget); } void expectLogbookPhotosEnabled(bool enabled) { expect(find.text(_LogbookPhotosEnabled.text(enabled)), findsOneWidget); } group( 'LogbookPhotosProvider dependency on IAPProductStatus', () { setUp(() { when(() => storageService.getPhotos()).thenAnswer((_) => Future.value(_customPhotos)); }); testWidgets( 'IAPProductStatus.purchased - show all saved photos', (tester) async { await pumpTestWidget(tester, IAPProductStatus.purchased); expectLogbookPhotosCount(_customPhotos.length); expectLogbookPhotosEnabled(true); }, ); testWidgets( 'IAPProductStatus.purchasable - show empty list', (tester) async { await pumpTestWidget(tester, IAPProductStatus.purchasable); expectLogbookPhotosCount(0); expectLogbookPhotosEnabled(true); }, ); testWidgets( 'IAPProductStatus.pending - show empty list', (tester) async { await pumpTestWidget(tester, IAPProductStatus.pending); expectLogbookPhotosCount(0); expectLogbookPhotosEnabled(true); }, ); }, ); testWidgets( 'saveLogbookPhotos', (tester) async { when(() => storageService.getPhotos()).thenAnswer((_) => Future.value(_customPhotos)); await pumpTestWidget(tester, IAPProductStatus.purchased); expectLogbookPhotosCount(_customPhotos.length); expectLogbookPhotosEnabled(true); tester.logbookPhotosProvider.saveLogbookPhotos(false); await tester.pump(); expectLogbookPhotosCount(_customPhotos.length); expectLogbookPhotosEnabled(false); tester.logbookPhotosProvider.saveLogbookPhotos(true); await tester.pump(); expectLogbookPhotosCount(_customPhotos.length); expectLogbookPhotosEnabled(true); }, ); testWidgets( 'LogbookPhotosProvider CRUD', (tester) async { when(() => storageService.getPhotos()).thenAnswer((_) async => []); await pumpTestWidget(tester, IAPProductStatus.purchased); expectLogbookPhotosCount(0); expectLogbookPhotosEnabled(true); /// Create a photo final photo = await tester.logbookPhotosProvider.addPhotoIfPossible( _customPhotos.first.name, ev100: _customPhotos.first.ev, iso: _customPhotos.first.iso, nd: _customPhotos.first.nd, ); /// Update a photo final updatedPhoto = photo!.copyWith(note: 'Updated note'); await tester.logbookPhotosProvider.updateLogbookPhoto(updatedPhoto); await tester.pump(); expectLogbookPhotosCount(1); // No photos loaded initially verify( () => storageService.updatePhoto( id: updatedPhoto.id, note: 'Updated note', ), ).called(1); /// Delete a photo await tester.logbookPhotosProvider.deleteLogbookPhoto(updatedPhoto); await tester.pump(); expectLogbookPhotosCount(0); verify(() => storageService.deletePhoto(updatedPhoto.id)).called(1); }, ); testWidgets( 'addPhotoIfPossible when disabled', (tester) async { when(() => storageService.getPhotos()).thenAnswer((_) async => []); await pumpTestWidget(tester, IAPProductStatus.purchased); // Disable logbook photos tester.logbookPhotosProvider.saveLogbookPhotos(false); await tester.pump(); // Try to add photo when disabled await tester.logbookPhotosProvider.addPhotoIfPossible( 'assets/camera_stub_image.jpg', ev100: 12.0, iso: 100, nd: 0, ); await tester.pump(); // Should not add photo when disabled expectLogbookPhotosCount(0); verifyNever(() => storageService.addPhoto(any())); }, ); } extension on WidgetTester { LogbookPhotosProviderState get logbookPhotosProvider { final BuildContext context = element(find.byType(_Application)); return LogbookPhotosProvider.of(context); } } class _Application extends StatelessWidget { const _Application(); @override Widget build(BuildContext context) { return const MaterialApp( home: Scaffold( body: Center( child: Column( children: [ _LogbookPhotosCount(), _LogbookPhotosEnabled(), ], ), ), ), ); } } class _LogbookPhotosCount extends StatelessWidget { static String text(int count) => "Photos count: $count"; const _LogbookPhotosCount(); @override Widget build(BuildContext context) { return Text(text(LogbookPhotos.of(context).length)); } } class _LogbookPhotosEnabled extends StatelessWidget { static String text(bool enabled) => "Photos enabled: $enabled"; const _LogbookPhotosEnabled(); @override Widget build(BuildContext context) { return Text(text(LogbookPhotos.isEnabledOf(context))); } } final List _customPhotos = [ LogbookPhoto( id: '1', name: 'assets/camera_stub_image.jpg', timestamp: DateTime(2024, 1, 1, 12), ev: 12.0, iso: 100, nd: 0, note: 'Test photo 1', ), LogbookPhoto( id: '2', name: 'assets/camera_stub_image.jpg', timestamp: DateTime(2024, 1, 2, 12), ev: 13.0, iso: 200, nd: 1, note: 'Test photo 2', ), ];