import 'package:flutter/material.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:lightmeter/providers/equipment_profile_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 _MockEquipmentProfilesStorageService extends Mock implements IapStorageService {} void main() { TestWidgetsFlutterBinding.ensureInitialized(); late _MockEquipmentProfilesStorageService storageService; setUpAll(() { storageService = _MockEquipmentProfilesStorageService(); }); setUp(() { registerFallbackValue(_mockEquipmentProfiles.first); when(() => storageService.addEquipmentProfile(any())).thenAnswer((_) async {}); when( () => storageService.updateEquipmentProfile( id: any(named: 'id'), name: any(named: 'name'), isUsed: any(named: 'isUsed'), ), ).thenAnswer((_) async {}); when(() => storageService.deleteEquipmentProfile(any())).thenAnswer((_) async {}); when(() => storageService.getEquipmentProfiles()) .thenAnswer((_) => Future.value(_mockEquipmentProfiles.toTogglableMap())); when(() => storageService.getPinholeEquipmentProfiles()) .thenAnswer((_) => Future.value(_mockPinholeEquipmentProfiles.toTogglableMap())); }); tearDown(() { reset(storageService); }); Future pumpTestWidget(WidgetTester tester, bool isPro) async { await tester.pumpWidget( IAPProducts( isPro: isPro, child: EquipmentProfilesProvider( storageService: storageService, child: const _Application(), ), ), ); await tester.pumpAndSettle(); } void expectEquipmentProfilesCount(int count) { expect(find.text(_EquipmentProfilesCount.text(count)), findsOneWidget); } void expectEquipmentProfilesInUseCount(int count) { expect(find.text(_EquipmentProfilesInUseCount.text(count)), findsOneWidget); } void expectSelectedEquipmentProfileName(String name) { expect(find.text(_SelectedEquipmentProfile.text(name)), findsOneWidget); } group( 'EquipmentProfilesProvider dependency on IAPProductStatus', () { setUp(() { when(() => storageService.selectedEquipmentProfileId).thenReturn(_mockProfiles.first.id); }); testWidgets( 'Pro - show all saved profiles', (tester) async { await pumpTestWidget(tester, true); expectEquipmentProfilesCount(_mockProfiles.length + 1); expectSelectedEquipmentProfileName(_mockProfiles.first.name); }, ); testWidgets( 'Not Pro - show only default', (tester) async { await pumpTestWidget(tester, false); expectEquipmentProfilesCount(1); expectSelectedEquipmentProfileName(''); }, ); }, ); testWidgets( 'toggleProfile', (tester) async { when(() => storageService.selectedEquipmentProfileId).thenReturn(_mockEquipmentProfiles.first.id); await pumpTestWidget(tester, true); expectEquipmentProfilesCount(_mockProfiles.length + 1); expectEquipmentProfilesInUseCount(_mockProfiles.length + 1); expectSelectedEquipmentProfileName(_mockEquipmentProfiles.first.name); await tester.equipmentProfilesProvider.toggleProfile(_mockEquipmentProfiles.first.id, false); await tester.pump(); expectEquipmentProfilesCount(_mockProfiles.length + 1); expectEquipmentProfilesInUseCount(_mockProfiles.length + 1 - 1); expectSelectedEquipmentProfileName(''); verify(() => storageService.updateEquipmentProfile(id: _mockEquipmentProfiles.first.id, isUsed: false)).called(1); verify(() => storageService.selectedEquipmentProfileId = '').called(1); }, ); testWidgets( 'EquipmentProfilesProvider CRUD', (tester) async { when(() => storageService.getEquipmentProfiles()).thenAnswer((_) async => {}); when(() => storageService.getPinholeEquipmentProfiles()).thenAnswer((_) async => {}); when(() => storageService.selectedEquipmentProfileId).thenReturn(''); await pumpTestWidget(tester, true); expectEquipmentProfilesCount(1); expectSelectedEquipmentProfileName(''); /// Add first profile and verify await tester.equipmentProfilesProvider.addProfile(_mockEquipmentProfiles.first); await tester.pump(); expectEquipmentProfilesCount(2); expectSelectedEquipmentProfileName(''); verify(() => storageService.addEquipmentProfile(any())).called(1); /// Add the other profiles and select the 1st one for (final profile in _mockEquipmentProfiles.skip(1)) { await tester.equipmentProfilesProvider.addProfile(profile); } tester.equipmentProfilesProvider.selectProfile(_mockEquipmentProfiles.first.id); await tester.pump(); expectEquipmentProfilesCount(1 + _mockEquipmentProfiles.length); expectSelectedEquipmentProfileName(_mockEquipmentProfiles.first.name); /// Edit the selected profile final updatedName = "${_mockEquipmentProfiles.first} updated"; await tester.equipmentProfilesProvider.updateProfile(_mockEquipmentProfiles.first.copyWith(name: updatedName)); await tester.pump(); expectEquipmentProfilesCount(1 + _mockEquipmentProfiles.length); expectSelectedEquipmentProfileName(updatedName); verify(() => storageService.updateEquipmentProfile(id: _mockEquipmentProfiles.first.id, name: updatedName)) .called(1); /// Delete a non-selected profile await tester.equipmentProfilesProvider.deleteProfile(_mockEquipmentProfiles.last); await tester.pump(); expectEquipmentProfilesCount(1 + _mockEquipmentProfiles.length - 1); expectSelectedEquipmentProfileName(updatedName); verifyNever(() => storageService.selectedEquipmentProfileId = ''); verify(() => storageService.deleteEquipmentProfile(_mockEquipmentProfiles.last.id)).called(1); /// Delete the selected profile await tester.equipmentProfilesProvider.deleteProfile(_mockEquipmentProfiles.first); await tester.pump(); expectEquipmentProfilesCount(1 + _mockEquipmentProfiles.length - 2); expectSelectedEquipmentProfileName(''); verify(() => storageService.selectedEquipmentProfileId = '').called(1); verify(() => storageService.deleteEquipmentProfile(_mockEquipmentProfiles.first.id)).called(1); }, ); } extension on WidgetTester { EquipmentProfilesProviderState get equipmentProfilesProvider { final BuildContext context = element(find.byType(_Application)); return EquipmentProfilesProvider.of(context); } } class _Application extends StatelessWidget { const _Application(); @override Widget build(BuildContext context) { return const MaterialApp( home: Scaffold( body: Center( child: Column( children: [ _EquipmentProfilesCount(), _EquipmentProfilesInUseCount(), _SelectedEquipmentProfile(), ], ), ), ), ); } } class _EquipmentProfilesCount extends StatelessWidget { static String text(int count) => "Profiles count: $count"; const _EquipmentProfilesCount(); @override Widget build(BuildContext context) { return Text(text(EquipmentProfiles.of(context).length)); } } class _EquipmentProfilesInUseCount extends StatelessWidget { static String text(int count) => "Profiles in use count: $count"; const _EquipmentProfilesInUseCount(); @override Widget build(BuildContext context) { return Text(text(EquipmentProfiles.inUseOf(context).length)); } } class _SelectedEquipmentProfile extends StatelessWidget { static String text(String name) => "Selected profile: $name}"; const _SelectedEquipmentProfile(); @override Widget build(BuildContext context) { return Text(text(EquipmentProfiles.selectedOf(context).name)); } } final List _mockProfiles = [..._mockEquipmentProfiles, ..._mockPinholeEquipmentProfiles]; final List _mockEquipmentProfiles = [ const EquipmentProfile( id: '1', name: 'Test 1', apertureValues: [ ApertureValue(4.0, StopType.full), ApertureValue(4.5, StopType.third), ApertureValue(4.8, StopType.half), ApertureValue(5.0, StopType.third), ApertureValue(5.6, StopType.full), ApertureValue(6.3, StopType.third), ApertureValue(6.7, StopType.half), ApertureValue(7.1, StopType.third), ApertureValue(8, StopType.full), ], ndValues: [ NdValue(0), NdValue(2), NdValue(4), NdValue(8), NdValue(16), NdValue(32), NdValue(64), ], shutterSpeedValues: ShutterSpeedValue.values, isoValues: [ IsoValue(100, StopType.full), IsoValue(125, StopType.third), IsoValue(160, StopType.third), IsoValue(200, StopType.full), IsoValue(250, StopType.third), IsoValue(320, StopType.third), IsoValue(400, StopType.full), ], ), const EquipmentProfile( id: '2', name: 'Test 2', apertureValues: [ ApertureValue(4.0, StopType.full), ApertureValue(4.5, StopType.third), ApertureValue(4.8, StopType.half), ApertureValue(5.0, StopType.third), ApertureValue(5.6, StopType.full), ApertureValue(6.3, StopType.third), ApertureValue(6.7, StopType.half), ApertureValue(7.1, StopType.third), ApertureValue(8, StopType.full), ], ndValues: [ NdValue(0), NdValue(2), NdValue(4), NdValue(8), NdValue(16), NdValue(32), NdValue(64), ], shutterSpeedValues: ShutterSpeedValue.values, isoValues: [ IsoValue(100, StopType.full), IsoValue(125, StopType.third), IsoValue(160, StopType.third), IsoValue(200, StopType.full), IsoValue(250, StopType.third), IsoValue(320, StopType.third), IsoValue(400, StopType.full), ], ), ]; final _mockPinholeEquipmentProfiles = [ const PinholeEquipmentProfile( id: '3', name: 'Pinhole Camera f/64', aperture: 64.0, isoValues: [ IsoValue(100, StopType.full), IsoValue(200, StopType.full), IsoValue(400, StopType.full), IsoValue(800, StopType.full), ], ndValues: [ NdValue(0), NdValue(2), NdValue(4), ], ), const PinholeEquipmentProfile( id: '4', name: 'Pinhole Camera f/128', aperture: 128.0, isoValues: [ IsoValue(50, StopType.full), IsoValue(100, StopType.full), IsoValue(200, StopType.full), IsoValue(400, StopType.full), IsoValue(800, StopType.full), IsoValue(1600, StopType.full), ], ndValues: [ NdValue(0), NdValue(1), NdValue(2), NdValue(4), NdValue(8), ], ), ];