import 'package:flutter/material.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:lightmeter/data/models/ev_source_type.dart'; import 'package:lightmeter/data/shared_prefs_service.dart'; import 'package:lightmeter/generated/l10n.dart'; import 'package:lightmeter/res/dimens.dart'; import 'package:lightmeter/screens/logbook_photo_edit/components/picker_list_tile/widget_list_tile_picker.dart'; import 'package:lightmeter/screens/logbook_photo_edit/screen_logbook_photo_edit.dart'; import 'package:lightmeter/screens/logbook_photos/components/grid_tile/widget_grid_tile_logbook_photo.dart'; import 'package:lightmeter/screens/settings/components/shared/dialog_picker/widget_dialog_picker.dart'; import 'package:lightmeter/screens/settings/screen_settings.dart'; import 'package:lightmeter/utils/platform_utils.dart'; import 'package:m3_lightmeter_resources/m3_lightmeter_resources.dart'; import 'package:meta/meta.dart'; import 'package:shared_preferences/shared_preferences.dart'; import '../integration_test/utils/widget_tester_actions.dart'; import 'mocks/paid_features_mock.dart'; @isTest void testLogbook(String description) { setUp(() async { SharedPreferences.setMockInitialValues({ UserPreferencesService.evSourceTypeKey: EvSourceType.camera.index, UserPreferencesService.seenChangelogVersionKey: await const PlatformUtils().version, }); }); testWidgets( description, (tester) async { await tester.pumpApplication( selectedEquipmentProfileId: mockEquipmentProfiles.first.id, selectedFilmId: mockFilms.first.id, customFilms: {}, ); await tester.takePhoto(); /// Open logbook await tester.openSettings(); await tester.tapDescendantTextOf(S.current.logbook); /// Verify that photo is present expect(find.byType(LogbookPhotoGridTile), findsOneWidget); /// Open the first photo await tester.tap(find.byType(LogbookPhotoGridTile).first); await tester.pumpAndSettle(); await tester.ensureVisible(find.text(mockEquipmentProfiles.first.name)); await tester.ensureVisible(find.text(mockFilms.first.name)); /// Add a note, select aperture value and shutter speed value await tester.enterText( find.descendant( of: find.byType(LogbookPhotoEditScreen), matching: find.byType(TextField), ), 'Test note', ); await tester.pumpAndSettle(); await tester.ensureVisible(find.text(S.current.shutterSpeedValue)); await tester.pumpAndSettle(); await tester.openPickerAndSelect(S.current.apertureValue, 'f/5.6'); await tester.openPickerAndSelect(S.current.shutterSpeedValue, '1/125'); _expectPickerListTileValue( S.current.equipmentProfile, mockEquipmentProfiles.first.name, ); _expectPickerListTileValue( S.current.film, mockFilms.first.name, ); await tester.openPickerAndSelect(S.current.film, S.current.notSet); await tester.pumpAndSettle(); /// Save the edits await tester.tap(find.byIcon(Icons.save_outlined)); await tester.pumpAndSettle(); /// Disable logbook await tester.tap(find.text(S.current.saveNewPhotos)); await tester.navigatorPop(); await tester.navigatorPop(); await tester.takePhoto(); /// Open logbook await tester.openSettings(); await tester.tapDescendantTextOf(S.current.logbook); /// Verify that only one photo is present expect(find.byType(LogbookPhotoGridTile), findsOneWidget); /// Got back and delete the equipment profile used to take the first picture await tester.navigatorPop(); await tester.tapDescendantTextOf(S.current.equipmentProfiles); await tester.editEquipmentProfile(mockEquipmentProfiles.first.name); await tester.tap(find.byIcon(Icons.delete_outlined)); await tester.pumpAndSettle(Dimens.durationML); expect(find.text(mockEquipmentProfiles[0].name), findsNothing); expect(find.text(mockEquipmentProfiles[1].name), findsOneWidget); await tester.navigatorPop(); /// Open photo again await tester.tapDescendantTextOf(S.current.logbook); await tester.tap(find.byType(LogbookPhotoGridTile).first); await tester.pumpAndSettle(); /// Verify the edits were saved _expectPickerListTileValue( S.current.equipmentProfile, S.current.notSet, ); expect(find.text('Test note'), findsOneWidget); expect(find.text('f/5.6'), findsOneWidget); expect(find.text('1/125'), findsOneWidget); /// Delete the photo await tester.tap(find.byIcon(Icons.delete_outlined)); await tester.pumpAndSettle(); /// Verify the photo was deleted expect(find.byType(LogbookPhotoGridTile), findsNothing); expect(find.text(S.current.noPhotos), findsOneWidget); }, ); } @isTest void testLogbookEquipmentProfileChanges(String description) { setUp(() async { SharedPreferences.setMockInitialValues({ UserPreferencesService.evSourceTypeKey: EvSourceType.camera.index, UserPreferencesService.seenChangelogVersionKey: await const PlatformUtils().version, }); }); testWidgets( description, (tester) async { await tester.pumpApplication( selectedEquipmentProfileId: mockEquipmentProfiles.first.id, selectedFilmId: mockFilms.first.id, customFilms: {}, ); await tester.takePhoto(); await tester.openSettings(); await tester.tapDescendantTextOf(S.current.logbook); await tester.tap(find.byType(LogbookPhotoGridTile).first); await tester.pumpAndSettle(); await tester.ensureVisible(find.text(mockEquipmentProfiles.first.name)); _expectPickerListTileValue( S.current.equipmentProfile, mockEquipmentProfiles.first.name, ); await tester.openPickerAndSelect(S.current.equipmentProfile, mockEquipmentProfiles[1].name); await tester.pumpAndSettle(); _expectPickerListTileValue( S.current.equipmentProfile, mockEquipmentProfiles[1].name, ); await tester.openPickerAndSelect( S.current.equipmentProfile, mockPinholeEquipmentProfiles.first.name, ); await tester.pumpAndSettle(); _expectPickerListTileValue( S.current.equipmentProfile, mockPinholeEquipmentProfiles.first.name, ); _expectPickerListTileValue( S.current.apertureValue, ApertureValue(mockPinholeEquipmentProfiles.first.aperture, StopType.full).toString(), reason: 'Aperture value must be automatically set when selecting a pinhole profile', ); final aperturePickerFinder = find.descendant( of: find.byWidgetPredicate( (widget) => widget is PickerListTile && widget.title == S.current.apertureValue, ), matching: find.byType(PickerListTile), ); expect(aperturePickerFinder, findsOneWidget); await tester.tap(aperturePickerFinder); await tester.pumpAndSettle(); expect( find.byType(DialogPicker>), findsNothing, reason: 'Aperture picker dialog must not open when pinhole profile is selected', ); await tester.openPickerAndSelect( S.current.equipmentProfile, mockPinholeEquipmentProfiles[1].name, ); await tester.pumpAndSettle(); _expectPickerListTileValue( S.current.equipmentProfile, mockPinholeEquipmentProfiles[1].name, ); _expectPickerListTileValue( S.current.apertureValue, ApertureValue(mockPinholeEquipmentProfiles[1].aperture, StopType.full).toString(), reason: 'Aperture value must be updated when switching to a different pinhole profile', ); await tester.tap(aperturePickerFinder); await tester.pumpAndSettle(); expect( find.byType(DialogPicker>), findsNothing, reason: 'Aperture picker dialog must not open when switching between pinhole profiles', ); await tester.openPickerAndSelect(S.current.equipmentProfile, mockEquipmentProfiles.first.name); await tester.pumpAndSettle(); _expectPickerListTileValue( S.current.equipmentProfile, mockEquipmentProfiles.first.name, ); _expectPickerListTileValue( S.current.apertureValue, S.current.notSet, reason: 'Aperture value must be cleared when switching from pinhole to regular profile', ); await tester.tap(aperturePickerFinder); await tester.pumpAndSettle(); expect( find.byType(DialogPicker>), findsOneWidget, reason: 'Aperture picker dialog must open when regular profile is selected', ); await tester.tap(find.text('Cancel')); await tester.pumpAndSettle(); await tester.openPickerAndSelect(S.current.equipmentProfile, S.current.notSet); await tester.pumpAndSettle(); _expectPickerListTileValue( S.current.equipmentProfile, S.current.notSet, ); await tester.tap(find.byIcon(Icons.save_outlined)); await tester.pumpAndSettle(); await tester.tap(find.byType(LogbookPhotoGridTile).first); await tester.pumpAndSettle(); _expectPickerListTileValue( S.current.equipmentProfile, S.current.notSet, ); }, ); } extension on WidgetTester { Future openPickerAndSelect(String title, String valueToSelect) async { await tap(find.text(title)); await pumpAndSettle(); final dialogFinder = find.byType(DialogPicker>); final listTileFinder = find.descendant(of: dialogFinder, matching: find.text(valueToSelect)); await scrollUntilVisible( listTileFinder, 56, scrollable: find.descendant(of: dialogFinder, matching: find.byType(Scrollable)), ); await tap(listTileFinder); await tapSelectButton(); } } void _expectPickerListTileValue(String title, String value, {String? reason}) { expect( find.descendant( of: find.byWidgetPredicate( (widget) => widget is PickerListTile && widget.title == title, ), matching: find.text(value), ), findsOneWidget, reason: reason, ); }