m3_lightmeter/test/providers/equipment_profile_provider_test.dart
Vadim 7ad47c0636
ML-203 Logging EXIF data (#239)
* typos

* added `LogbookPhotosProvider`

* implemented `LogbookScreen`

* implemented `LogbookPhotoEditScreen`

* added photo update

* save geolocation

* added `CameraSettingsSection`

* adjusted logbook grid

* added hero animation

* fixed logbook list updates

* added empty logbook state

* added `saveLogbookPhotos` option

* fixed updating photos

* made `DialogPicker` content scrollable

* added tests for `LogbookPhotosProvider`

* made image preview full-width

* made note field multiline

* wip

* migrated to new iap service

* fixed unit tests

* typo

* fixed arb formatting

* stub logbook photos for tests

* implemented integration test for logbook

* moved date to title

* redundant bottom padding

* added logbook photo screen to screenshots generator

* Update settings.gradle

* aligned iap stub with iap release

* sync

* made logbook iap

* debug screenshots

* Update runner.dart

* fixed dialog picker of optional values

* added bottom padding to logbook edit screen

* fixed tests

* Create camera_stub_image.jpg

* Update films_provider_test.dart

* rename

* Update pubspec.yaml

* added logbook to pro features
2025-07-29 12:38:48 +02:00

307 lines
10 KiB
Dart

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(_customProfiles.first);
when(() => storageService.addEquipmentProfile(any<EquipmentProfile>())).thenAnswer((_) async {});
when(
() => storageService.updateEquipmentProfile(
id: any<String>(named: 'id'),
name: any<String>(named: 'name'),
isUsed: any<bool>(named: 'isUsed'),
),
).thenAnswer((_) async {});
when(() => storageService.deleteEquipmentProfile(any<String>())).thenAnswer((_) async {});
when(() => storageService.getEquipmentProfiles()).thenAnswer((_) => Future.value(_customProfiles.toTogglableMap()));
});
tearDown(() {
reset(storageService);
});
Future<void> 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.getEquipmentProfiles()).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.updateEquipmentProfile(id: _customProfiles.first.id, isUsed: false)).called(1);
verify(() => storageService.selectedEquipmentProfileId = '').called(1);
},
);
testWidgets(
'EquipmentProfilesProvider CRUD',
(tester) async {
when(() => storageService.getEquipmentProfiles()).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.addEquipmentProfile(any<EquipmentProfile>())).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.updateEquipmentProfile(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.deleteEquipmentProfile(_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.deleteEquipmentProfile(_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<EquipmentProfile> _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),
],
),
];