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 EquipmentProfilesStorageService {} void main() { TestWidgetsFlutterBinding.ensureInitialized(); late _MockEquipmentProfilesStorageService storageService; setUpAll(() { storageService = _MockEquipmentProfilesStorageService(); }); setUp(() { registerFallbackValue(_customProfiles.first); when(() => storageService.addProfile(any())).thenAnswer((_) async {}); when( () => storageService.updateProfile( id: any(named: 'id'), name: any(named: 'name'), isUsed: any(named: 'isUsed'), ), ).thenAnswer((_) async {}); when(() => storageService.deleteProfile(any())).thenAnswer((_) async {}); when(() => storageService.getProfiles()).thenAnswer((_) => Future.value(_customProfiles.toTogglableMap())); }); 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: 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(_customProfiles.first.id); when(() => storageService.getProfiles()).thenAnswer((_) => Future.value(_customProfiles.toTogglableMap())); }); testWidgets( 'IAPProductStatus.purchased - show all saved profiles', (tester) async { await pumpTestWidget(tester, IAPProductStatus.purchased); expectEquipmentProfilesCount(_customProfiles.length + 1); expectSelectedEquipmentProfileName(_customProfiles.first.name); }, ); testWidgets( 'IAPProductStatus.purchasable - show only default', (tester) async { await pumpTestWidget(tester, IAPProductStatus.purchasable); expectEquipmentProfilesCount(1); expectSelectedEquipmentProfileName(''); }, ); testWidgets( 'IAPProductStatus.pending - show only default', (tester) async { await pumpTestWidget(tester, IAPProductStatus.pending); expectEquipmentProfilesCount(1); expectSelectedEquipmentProfileName(''); }, ); }, ); testWidgets( 'toggleProfile', (tester) async { when(() => storageService.selectedEquipmentProfileId).thenReturn(_customProfiles.first.id); await pumpTestWidget(tester, IAPProductStatus.purchased); expectEquipmentProfilesCount(_customProfiles.length + 1); expectEquipmentProfilesInUseCount(_customProfiles.length + 1); expectSelectedEquipmentProfileName(_customProfiles.first.name); await tester.equipmentProfilesProvider.toggleProfile(_customProfiles.first, false); await tester.pump(); expectEquipmentProfilesCount(_customProfiles.length + 1); expectEquipmentProfilesInUseCount(_customProfiles.length + 1 - 1); expectSelectedEquipmentProfileName(''); verify(() => storageService.updateProfile(id: _customProfiles.first.id, isUsed: false)).called(1); verify(() => storageService.selectedEquipmentProfileId = '').called(1); }, ); testWidgets( 'EquipmentProfilesProvider CRUD', (tester) async { when(() => storageService.getProfiles()).thenAnswer((_) async => {}); when(() => storageService.selectedEquipmentProfileId).thenReturn(''); await pumpTestWidget(tester, IAPProductStatus.purchased); expectEquipmentProfilesCount(1); expectSelectedEquipmentProfileName(''); /// Add first profile and verify await tester.equipmentProfilesProvider.addProfile(_customProfiles.first); await tester.pump(); expectEquipmentProfilesCount(2); expectSelectedEquipmentProfileName(''); verify(() => storageService.addProfile(any())).called(1); /// Add the other profiles and select the 1st one for (final profile in _customProfiles.skip(1)) { await tester.equipmentProfilesProvider.addProfile(profile); } tester.equipmentProfilesProvider.selectProfile(_customProfiles.first); await tester.pump(); expectEquipmentProfilesCount(1 + _customProfiles.length); expectSelectedEquipmentProfileName(_customProfiles.first.name); /// Edit the selected profile final updatedName = "${_customProfiles.first} updated"; await tester.equipmentProfilesProvider.updateProfile(_customProfiles.first.copyWith(name: updatedName)); await tester.pump(); expectEquipmentProfilesCount(1 + _customProfiles.length); expectSelectedEquipmentProfileName(updatedName); verify(() => storageService.updateProfile(id: _customProfiles.first.id, name: updatedName)).called(1); /// Delete a non-selected profile await tester.equipmentProfilesProvider.deleteProfile(_customProfiles.last); await tester.pump(); expectEquipmentProfilesCount(1 + _customProfiles.length - 1); expectSelectedEquipmentProfileName(updatedName); verifyNever(() => storageService.selectedEquipmentProfileId = ''); verify(() => storageService.deleteProfile(_customProfiles.last.id)).called(1); /// Delete the selected profile await tester.equipmentProfilesProvider.deleteProfile(_customProfiles.first); await tester.pump(); expectEquipmentProfilesCount(1 + _customProfiles.length - 2); expectSelectedEquipmentProfileName(''); verify(() => storageService.selectedEquipmentProfileId = '').called(1); verify(() => storageService.deleteProfile(_customProfiles.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 _customProfiles = [ 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), ], ), ];