mirror of
https://github.com/vodemn/m3_lightmeter.git
synced 2024-11-26 01:10:39 +00:00
Compare commits
No commits in common. "3c4d959cc68ab48c8f7c1221626e7a7264e9e2c0" and "24804a119e7b7d5cbb694ad5a944069c727cce3d" have entirely different histories.
3c4d959cc6
...
24804a119e
12 changed files with 709 additions and 107 deletions
2
.gitignore
vendored
2
.gitignore
vendored
|
@ -61,4 +61,4 @@ ios/Runner/GoogleService-Info.plist
|
|||
|
||||
coverage/
|
||||
test/coverage_helper_test.dart
|
||||
screenshots/*.png
|
||||
screenshots/
|
|
@ -10,19 +10,18 @@ import 'package:lightmeter/data/models/theme_type.dart';
|
|||
import 'package:lightmeter/data/models/volume_action.dart';
|
||||
import 'package:lightmeter/data/shared_prefs_service.dart';
|
||||
import 'package:lightmeter/generated/l10n.dart';
|
||||
import 'package:lightmeter/res/dimens.dart';
|
||||
import 'package:lightmeter/res/theme.dart';
|
||||
import 'package:lightmeter/screens/metering/components/bottom_controls/components/measure_button/widget_button_measure.dart';
|
||||
import 'package:lightmeter/screens/metering/components/shared/readings_container/components/iso_picker/widget_picker_iso.dart';
|
||||
import 'package:lightmeter/screens/settings/components/metering/components/equipment_profiles/components/equipment_profile_screen/screen_equipment_profile.dart';
|
||||
import 'package:lightmeter/screens/settings/screen_settings.dart';
|
||||
import 'package:lightmeter/screens/settings/components/metering/components/equipment_profiles/components/equipment_profile_screen/components/equipment_profile_container/widget_container_equipment_profile.dart';
|
||||
import 'package:m3_lightmeter_resources/m3_lightmeter_resources.dart';
|
||||
import 'package:shared_preferences/shared_preferences.dart';
|
||||
|
||||
import '../integration_test/mocks/paid_features_mock.dart';
|
||||
import '../integration_test/utils/widget_tester_actions.dart';
|
||||
import 'utils/platform_channel_mock.dart';
|
||||
import 'utils/widget_tester_actions.dart';
|
||||
|
||||
//https://stackoverflow.com/a/67186625/13167574
|
||||
|
||||
/// Just a screenshot generator. No expectations here.
|
||||
void main() {
|
||||
final binding = IntegrationTestWidgetsFlutterBinding();
|
||||
IntegrationTestWidgetsFlutterBinding.ensureInitialized();
|
||||
|
@ -30,7 +29,6 @@ void main() {
|
|||
final Color darkThemeColor = primaryColorsList[3];
|
||||
|
||||
void mockSharedPrefs(ThemeType theme, Color color) {
|
||||
// ignore: invalid_use_of_visible_for_testing_member
|
||||
SharedPreferences.setMockInitialValues({
|
||||
/// Metering values
|
||||
UserPreferencesService.evSourceTypeKey: EvSourceType.camera.index,
|
||||
|
@ -76,11 +74,14 @@ void main() {
|
|||
if (Platform.isAndroid) {
|
||||
await tester.tap(find.byTooltip(S.current.tooltipUseLightSensor));
|
||||
await tester.pumpAndSettle();
|
||||
await tester.toggleIncidentMetering(7.3);
|
||||
await tester.tap(find.byType(MeteringMeasureButton));
|
||||
await sendMockIncidentEv(7.3);
|
||||
await tester.tap(find.byType(MeteringMeasureButton));
|
||||
await tester.takeScreenshot(binding, '${lightThemeColor.value}_metering_incident');
|
||||
}
|
||||
|
||||
await tester.openAnimatedPicker<IsoValuePicker>();
|
||||
await tester.tap(find.byType(IsoValuePicker));
|
||||
await tester.pumpAndSettle(Dimens.durationL);
|
||||
await tester.takeScreenshot(binding, '${lightThemeColor.value}_metering_iso_picker');
|
||||
|
||||
await tester.tapCancelButton();
|
||||
|
@ -88,13 +89,12 @@ void main() {
|
|||
await tester.pumpAndSettle();
|
||||
await tester.takeScreenshot(binding, '${lightThemeColor.value}_settings');
|
||||
|
||||
await tester.tapDescendantTextOf<SettingsScreen>(S.current.meteringScreenLayout);
|
||||
await tester.tapListTile(S.current.meteringScreenLayout);
|
||||
await tester.takeScreenshot(binding, '${lightThemeColor.value}_settings_metering_screen_layout');
|
||||
|
||||
await tester.tapCancelButton();
|
||||
await tester.tapDescendantTextOf<SettingsScreen>(S.current.equipmentProfiles);
|
||||
await tester.pumpAndSettle();
|
||||
await tester.tapDescendantTextOf<EquipmentProfilesScreen>(mockEquipmentProfiles.first.name);
|
||||
await tester.tapListTile(S.current.equipmentProfiles);
|
||||
await tester.tap(find.byType(EquipmentProfileContainer).first);
|
||||
await tester.pumpAndSettle();
|
||||
await tester.takeScreenshot(binding, '${lightThemeColor.value}-equipment_profiles');
|
||||
|
||||
|
@ -117,7 +117,9 @@ void main() {
|
|||
if (Platform.isAndroid) {
|
||||
await tester.tap(find.byTooltip(S.current.tooltipUseLightSensor));
|
||||
await tester.pumpAndSettle();
|
||||
await tester.toggleIncidentMetering(7.3);
|
||||
await tester.tap(find.byType(MeteringMeasureButton));
|
||||
await sendMockIncidentEv(7.3);
|
||||
await tester.tap(find.byType(MeteringMeasureButton));
|
||||
await tester.takeScreenshot(binding, '${darkThemeColor.value}_metering_incident');
|
||||
}
|
||||
},
|
||||
|
@ -133,4 +135,13 @@ extension on WidgetTester {
|
|||
await binding.takeScreenshot(name);
|
||||
await pumpAndSettle();
|
||||
}
|
||||
|
||||
Future<void> tapListTile(String title) async {
|
||||
final listTile = find.byWidgetPredicate(
|
||||
(widget) => widget is ListTile && widget.title is Text && (widget.title as Text?)?.data == title,
|
||||
);
|
||||
expect(listTile, findsOneWidget);
|
||||
await tap(listTile);
|
||||
await pumpAndSettle();
|
||||
}
|
||||
}
|
|
@ -2,7 +2,7 @@ flutter drive \
|
|||
--dart-define="cameraPreviewAspectRatio=240/320" \
|
||||
--dart-define="cameraStubImage=assets/camera_stub_image.jpg" \
|
||||
--driver=test_driver/screenshot_driver.dart \
|
||||
--target=screenshots/generate_screenshots.dart \
|
||||
--target=integration_test/generate_screenshots.dart \
|
||||
--profile \
|
||||
--flavor=dev \
|
||||
--no-dds \
|
303
integration_test/metering_screen_test.dart
Normal file
303
integration_test/metering_screen_test.dart
Normal file
|
@ -0,0 +1,303 @@
|
|||
import 'dart:io';
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:integration_test/integration_test.dart';
|
||||
import 'package:lightmeter/data/models/ev_source_type.dart';
|
||||
import 'package:lightmeter/data/models/exposure_pair.dart';
|
||||
import 'package:lightmeter/data/shared_prefs_service.dart';
|
||||
import 'package:lightmeter/generated/l10n.dart';
|
||||
import 'package:lightmeter/screens/metering/components/bottom_controls/components/measure_button/widget_button_measure.dart';
|
||||
import 'package:lightmeter/screens/metering/components/camera_container/widget_container_camera.dart';
|
||||
import 'package:lightmeter/screens/metering/components/light_sensor_container/widget_container_light_sensor.dart';
|
||||
import 'package:lightmeter/screens/metering/components/shared/exposure_pairs_list/widget_list_exposure_pairs.dart';
|
||||
import 'package:lightmeter/screens/metering/components/shared/readings_container/components/extreme_exposure_pairs_container/widget_container_extreme_exposure_pairs.dart';
|
||||
import 'package:lightmeter/screens/metering/components/shared/readings_container/components/film_picker/widget_picker_film.dart';
|
||||
import 'package:lightmeter/screens/metering/components/shared/readings_container/components/iso_picker/widget_picker_iso.dart';
|
||||
import 'package:lightmeter/screens/metering/components/shared/readings_container/components/nd_picker/widget_picker_nd.dart';
|
||||
import 'package:lightmeter/screens/metering/components/shared/readings_container/components/shared/animated_dialog_picker/components/dialog_picker/widget_picker_dialog.dart';
|
||||
import 'package:lightmeter/screens/metering/screen_metering.dart';
|
||||
import 'package:lightmeter/screens/shared/icon_placeholder/widget_icon_placeholder.dart';
|
||||
import 'package:m3_lightmeter_iap/m3_lightmeter_iap.dart';
|
||||
import 'package:m3_lightmeter_resources/m3_lightmeter_resources.dart';
|
||||
import 'package:shared_preferences/shared_preferences.dart';
|
||||
|
||||
import 'mocks/paid_features_mock.dart';
|
||||
import 'utils/expectations.dart';
|
||||
import 'utils/platform_channel_mock.dart';
|
||||
import 'utils/widget_tester_actions.dart';
|
||||
|
||||
const defaultIsoValue = IsoValue(100, StopType.full);
|
||||
const mockPhotoEv100 = 8.3;
|
||||
const mockPhotoFastestAperture = ApertureValue(1, StopType.full);
|
||||
const mockPhotoSlowestAperture = ApertureValue(45, StopType.full);
|
||||
const mockPhotoFastestShutterSpeed = ShutterSpeedValue(320, true, StopType.third);
|
||||
const mockPhotoSlowestShutterSpeed = ShutterSpeedValue(6, false, StopType.third);
|
||||
const mockPhotoFastestExposurePair = ExposurePair(mockPhotoFastestAperture, mockPhotoFastestShutterSpeed);
|
||||
const mockPhotoSlowestExposurePair = ExposurePair(mockPhotoSlowestAperture, mockPhotoSlowestShutterSpeed);
|
||||
|
||||
//https://stackoverflow.com/a/67186625/13167574
|
||||
void main() {
|
||||
IntegrationTestWidgetsFlutterBinding.ensureInitialized();
|
||||
|
||||
group(
|
||||
'[Light sensor availability]',
|
||||
() {
|
||||
testWidgets(
|
||||
'Android - has sensor',
|
||||
(tester) async {
|
||||
SharedPreferences.setMockInitialValues({UserPreferencesService.evSourceTypeKey: EvSourceType.sensor.index});
|
||||
setLightSensorAvilability(hasSensor: true);
|
||||
await tester.pumpApplication(productStatus: IAPProductStatus.purchasable);
|
||||
|
||||
/// Verify that [LightSensorContainer] is shown in correspondance with the saved ev source
|
||||
expect(find.byType(LightSensorContainer), findsOneWidget);
|
||||
},
|
||||
skip: Platform.isIOS,
|
||||
);
|
||||
|
||||
testWidgets(
|
||||
'Android - no sensor',
|
||||
(tester) async {
|
||||
SharedPreferences.setMockInitialValues({UserPreferencesService.evSourceTypeKey: EvSourceType.sensor.index});
|
||||
setLightSensorAvilability(hasSensor: false);
|
||||
await tester.pumpApplication(productStatus: IAPProductStatus.purchasable);
|
||||
|
||||
/// Verify that [CameraContainer] is shown instead of [LightSensorContainer]
|
||||
expect(find.byType(CameraContainer), findsOneWidget);
|
||||
|
||||
/// and there is no ability to switch to the incident metering
|
||||
expect(find.byTooltip(S.current.tooltipUseLightSensor), findsNothing);
|
||||
},
|
||||
skip: Platform.isIOS,
|
||||
);
|
||||
|
||||
testWidgets(
|
||||
'iOS - no sensor',
|
||||
(tester) async {
|
||||
SharedPreferences.setMockInitialValues({UserPreferencesService.evSourceTypeKey: EvSourceType.sensor.index});
|
||||
await tester.pumpApplication(productStatus: IAPProductStatus.purchasable);
|
||||
|
||||
/// verify no button to switch to the incident light mode
|
||||
expect(find.byType(CameraContainer), findsOneWidget);
|
||||
|
||||
/// and there is no ability to switch to the incident metering
|
||||
expect(find.byTooltip(S.current.tooltipUseLightSensor), findsNothing);
|
||||
},
|
||||
skip: Platform.isAndroid,
|
||||
);
|
||||
},
|
||||
);
|
||||
|
||||
group(
|
||||
'[Match extreme exposure pairs & pairs list edge values]',
|
||||
() {
|
||||
Future<List<ExposurePair>> scrollToTheLastExposurePair(WidgetTester tester) async {
|
||||
final exposurePairs = MeteringContainerBuidler.buildExposureValues(
|
||||
mockPhotoEv100,
|
||||
StopType.third,
|
||||
defaultEquipmentProfile,
|
||||
);
|
||||
await tester.scrollUntilVisible(
|
||||
find.byWidgetPredicate((widget) => widget is Row && widget.key == ValueKey(exposurePairs.length - 1)),
|
||||
56,
|
||||
scrollable: find.descendant(of: find.byType(ExposurePairsList), matching: find.byType(Scrollable)),
|
||||
);
|
||||
return exposurePairs;
|
||||
}
|
||||
|
||||
void expectExposurePairsListItem(int index, String aperture, String shutterSpeed) {
|
||||
final firstPairRow = find.byWidgetPredicate((widget) => widget is Row && widget.key == ValueKey(index));
|
||||
expect(find.descendant(of: firstPairRow, matching: find.text(aperture)), findsOneWidget);
|
||||
expect(find.descendant(of: firstPairRow, matching: find.text(shutterSpeed)), findsOneWidget);
|
||||
}
|
||||
|
||||
setUpAll(() {
|
||||
SharedPreferences.setMockInitialValues({UserPreferencesService.evSourceTypeKey: EvSourceType.camera.index});
|
||||
});
|
||||
|
||||
testWidgets(
|
||||
'No exposure pairs',
|
||||
(tester) async {
|
||||
await tester.pumpApplication(productStatus: IAPProductStatus.purchasable);
|
||||
|
||||
/// Verify that no exposure pairs are shown in [ExtremeExposurePairsContainer]
|
||||
final pickerFinder = find.byType(ExtremeExposurePairsContainer);
|
||||
expect(pickerFinder, findsOneWidget);
|
||||
expect(find.descendant(of: pickerFinder, matching: find.text(S.current.fastestExposurePair)), findsOneWidget);
|
||||
expect(find.descendant(of: pickerFinder, matching: find.text(S.current.slowestExposurePair)), findsOneWidget);
|
||||
expect(find.descendant(of: pickerFinder, matching: find.text('-')), findsNWidgets(2));
|
||||
|
||||
/// Verify that the exposure pairs list is empty
|
||||
expect(
|
||||
find.descendant(
|
||||
of: find.byType(ExposurePairsList),
|
||||
matching: find.byWidgetPredicate(
|
||||
(widget) =>
|
||||
widget is IconPlaceholder &&
|
||||
widget.icon == Icons.not_interested &&
|
||||
widget.text == S.current.noExposurePairs,
|
||||
),
|
||||
),
|
||||
findsOneWidget,
|
||||
);
|
||||
},
|
||||
);
|
||||
|
||||
testWidgets(
|
||||
'Multiple exposure pairs w/o reciprocity',
|
||||
(tester) async {
|
||||
await tester.pumpApplication(productStatus: IAPProductStatus.purchasable);
|
||||
await tester.takePhoto();
|
||||
|
||||
/// Verify that reciprocity is not applied to the slowest exposure pair in the container
|
||||
expectExposurePairsContainer('$mockPhotoFastestAperture - 1/320', '$mockPhotoSlowestAperture - 6"');
|
||||
expectMeasureButton(mockPhotoEv100);
|
||||
|
||||
/// Verify that reciprocity is not applied to the slowest exposure pair in the list
|
||||
expectExposurePairsListItem(0, '$mockPhotoFastestAperture', '1/320');
|
||||
final exposurePairs = await scrollToTheLastExposurePair(tester);
|
||||
expectExposurePairsListItem(exposurePairs.length - 1, '$mockPhotoSlowestAperture', '6"');
|
||||
expectMeasureButton(mockPhotoEv100);
|
||||
},
|
||||
);
|
||||
|
||||
testWidgets(
|
||||
'Multiple exposure pairs w/ reciprocity',
|
||||
(tester) async {
|
||||
await tester.pumpApplication(selectedFilm: mockFilms.first);
|
||||
await tester.takePhoto();
|
||||
|
||||
/// Verify that reciprocity is applied to the slowest exposure pair in the container
|
||||
expectExposurePairsContainer('$mockPhotoFastestAperture - 1/320', '$mockPhotoSlowestAperture - 12"');
|
||||
expectMeasureButton(mockPhotoEv100);
|
||||
|
||||
/// Verify that reciprocity is applied to the slowest exposure pair in the list
|
||||
expectExposurePairsListItem(0, '$mockPhotoFastestAperture', '1/320');
|
||||
final exposurePairs = await scrollToTheLastExposurePair(tester);
|
||||
expectExposurePairsListItem(exposurePairs.length - 1, '$mockPhotoSlowestAperture', '12"');
|
||||
expectMeasureButton(mockPhotoEv100);
|
||||
},
|
||||
);
|
||||
},
|
||||
);
|
||||
|
||||
group(
|
||||
'[Pickers tests]',
|
||||
() {
|
||||
group('Select film', () {
|
||||
testWidgets(
|
||||
'with the same ISO',
|
||||
(tester) async {
|
||||
await tester.pumpApplication();
|
||||
await tester.takePhoto();
|
||||
|
||||
// Verify that reciprocity failure is applies for the film is not selected
|
||||
expectAnimatedPickerWith<FilmPicker>(title: S.current.film, value: S.current.none);
|
||||
expectExposurePairsContainer('$mockPhotoFastestExposurePair', '$mockPhotoSlowestExposurePair');
|
||||
expectMeasureButton(mockPhotoEv100);
|
||||
|
||||
await tester.openAnimatedPicker<FilmPicker>();
|
||||
await tester.tapDescendantTextOf<DialogPicker<Film>>(mockFilms.first.name);
|
||||
await tester.tapSelectButton();
|
||||
|
||||
/// Verify that exposure pairs are the same, except that the reciprocity failure is applied
|
||||
expectExposurePairsContainer(
|
||||
'$mockPhotoFastestExposurePair',
|
||||
'$mockPhotoSlowestAperture - ${mockFilms.first.reciprocityFailure(mockPhotoSlowestShutterSpeed)}',
|
||||
);
|
||||
expectMeasureButton(mockPhotoEv100);
|
||||
|
||||
/// Make sure, that the EV is not changed
|
||||
await tester.takePhoto();
|
||||
expectExposurePairsContainer(
|
||||
'$mockPhotoFastestExposurePair',
|
||||
'$mockPhotoSlowestAperture - ${mockFilms.first.reciprocityFailure(mockPhotoSlowestShutterSpeed)}',
|
||||
);
|
||||
expectMeasureButton(mockPhotoEv100);
|
||||
},
|
||||
);
|
||||
|
||||
testWidgets(
|
||||
'with greater ISO',
|
||||
(tester) async {
|
||||
await tester.pumpApplication();
|
||||
await tester.takePhoto();
|
||||
|
||||
// Verify that reciprocity failure is applies for the film is not selected
|
||||
expectAnimatedPickerWith<FilmPicker>(title: S.current.film, value: S.current.none);
|
||||
expectExposurePairsContainer('$mockPhotoFastestExposurePair', '$mockPhotoSlowestExposurePair');
|
||||
expectMeasureButton(mockPhotoEv100);
|
||||
|
||||
await tester.openAnimatedPicker<FilmPicker>();
|
||||
await tester.tapDescendantTextOf<DialogPicker<Film>>(mockFilms[1].name);
|
||||
await tester.tapSelectButton();
|
||||
|
||||
/// Verify that exposure pairs are the same, except that the reciprocity failure is applied
|
||||
expectExposurePairsContainer(
|
||||
'$mockPhotoFastestExposurePair',
|
||||
'$mockPhotoSlowestAperture - ${mockFilms[1].reciprocityFailure(mockPhotoSlowestShutterSpeed)}',
|
||||
);
|
||||
expectMeasureButton(mockPhotoEv100);
|
||||
|
||||
/// Make sure, that the EV is not changed
|
||||
await tester.takePhoto();
|
||||
expectExposurePairsContainer(
|
||||
'$mockPhotoFastestExposurePair',
|
||||
'$mockPhotoSlowestAperture - ${mockFilms[1].reciprocityFailure(mockPhotoSlowestShutterSpeed)}',
|
||||
);
|
||||
expectMeasureButton(mockPhotoEv100);
|
||||
},
|
||||
);
|
||||
});
|
||||
|
||||
testWidgets(
|
||||
'Select ISO +1 EV',
|
||||
(tester) async {
|
||||
await tester.pumpApplication(productStatus: IAPProductStatus.purchased);
|
||||
expectExposurePairsContainer('f/1.0 - 1/320', 'f/45 - 13"');
|
||||
expectMeasureButton(mockPhotoEv100);
|
||||
|
||||
await tester.openAnimatedPicker<IsoValuePicker>();
|
||||
expect(find.byType(DialogPicker<IsoValue>), findsOneWidget);
|
||||
await tester.tapRadioListTile<IsoValue>('800');
|
||||
await tester.tapSelectButton();
|
||||
expectExposurePairsContainer('f/1.0 - 1/320', 'f/45 - 6"');
|
||||
expectMeasureButton(8.3);
|
||||
|
||||
/// Make sure, that current ISO is used in metering
|
||||
await tester.tap(find.byType(MeteringMeasureButton));
|
||||
await tester.tap(find.byType(MeteringMeasureButton));
|
||||
await tester.pumpAndSettle();
|
||||
expectExposurePairsContainer('f/1.0 - 1/320', 'f/45 - 6"');
|
||||
expectMeasureButton(8.3);
|
||||
},
|
||||
skip: true,
|
||||
);
|
||||
|
||||
testWidgets(
|
||||
'Select ND -1 EV',
|
||||
(tester) async {
|
||||
await tester.pumpApplication(productStatus: IAPProductStatus.purchased);
|
||||
expectExposurePairsContainer('f/1.0 - 1/320', 'f/45 - 13"');
|
||||
expectMeasureButton(mockPhotoEv100);
|
||||
|
||||
await tester.openAnimatedPicker<NdValuePicker>();
|
||||
expect(find.byType(DialogPicker<NdValue>), findsOneWidget);
|
||||
await tester.tapRadioListTile<NdValue>('2');
|
||||
await tester.tapSelectButton();
|
||||
expectExposurePairsContainer('f/1.0 - 1/80', 'f/36 - 16"');
|
||||
expectMeasureButton(6.3);
|
||||
|
||||
/// Make sure, that current ISO is used in metering
|
||||
await tester.tap(find.byType(MeteringMeasureButton));
|
||||
await tester.tap(find.byType(MeteringMeasureButton));
|
||||
await tester.pumpAndSettle();
|
||||
expectExposurePairsContainer('f/1.0 - 1/80', 'f/36 - 16"');
|
||||
expectMeasureButton(6.3);
|
||||
},
|
||||
skip: true,
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
17
integration_test/run_integration_tests.sh
Normal file
17
integration_test/run_integration_tests.sh
Normal file
|
@ -0,0 +1,17 @@
|
|||
# https://github.com/flutter/flutter/issues/86295#issuecomment-1192766368
|
||||
devices=$(adb devices)
|
||||
devicesIds=$(echo $devices | grep -Eo '[A-Z0-9]{2,}')
|
||||
firstDeviceId=$(echo $devicesIds | cut -d " " -f 1)
|
||||
# adb -s $firstDeviceId shell pm grant com.vodemn.lightmeter.dev android.permission.CAMERA
|
||||
|
||||
flutter drive \
|
||||
--dart-define="cameraPreviewAspectRatio=240/320" \
|
||||
--dart-define="cameraStubImage=assets/camera_stub_image.jpg" \
|
||||
--driver=test_driver/integration_driver.dart \
|
||||
--target=integration_test/metering_screen_test.dart \
|
||||
--profile \
|
||||
--flavor=dev \
|
||||
--no-dds \
|
||||
--endless-trace-buffer \
|
||||
--purge-persistent-cache \
|
||||
-d $firstDeviceId
|
294
integration_test/settings_screen_test.dart
Normal file
294
integration_test/settings_screen_test.dart
Normal file
|
@ -0,0 +1,294 @@
|
|||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:integration_test/integration_test.dart';
|
||||
import 'package:lightmeter/application.dart';
|
||||
import 'package:lightmeter/data/caffeine_service.dart';
|
||||
import 'package:lightmeter/data/haptics_service.dart';
|
||||
import 'package:lightmeter/data/light_sensor_service.dart';
|
||||
import 'package:lightmeter/data/models/ev_source_type.dart';
|
||||
import 'package:lightmeter/data/models/metering_screen_layout_config.dart';
|
||||
import 'package:lightmeter/data/models/supported_locale.dart';
|
||||
import 'package:lightmeter/data/models/theme_type.dart';
|
||||
import 'package:lightmeter/data/models/volume_action.dart';
|
||||
import 'package:lightmeter/data/permissions_service.dart';
|
||||
import 'package:lightmeter/data/shared_prefs_service.dart';
|
||||
import 'package:lightmeter/data/volume_events_service.dart';
|
||||
import 'package:lightmeter/environment.dart';
|
||||
import 'package:lightmeter/generated/l10n.dart';
|
||||
import 'package:lightmeter/providers/services_provider.dart';
|
||||
import 'package:lightmeter/providers/user_preferences_provider.dart';
|
||||
import 'package:lightmeter/res/theme.dart';
|
||||
import 'package:lightmeter/screens/metering/components/shared/readings_container/components/equipment_profile_picker/widget_picker_equipment_profiles.dart';
|
||||
import 'package:lightmeter/screens/metering/components/shared/readings_container/components/extreme_exposure_pairs_container/widget_container_extreme_exposure_pairs.dart';
|
||||
import 'package:lightmeter/screens/metering/components/shared/readings_container/components/film_picker/widget_picker_film.dart';
|
||||
import 'package:lightmeter/screens/metering/components/shared/readings_container/components/shared/animated_dialog_picker/components/dialog_picker/widget_picker_dialog.dart';
|
||||
import 'package:lightmeter/screens/settings/components/metering/components/metering_screen_layout/components/meterins_screen_layout_features_dialog/widget_dialog_metering_screen_layout_features.dart';
|
||||
import 'package:lightmeter/screens/settings/components/shared/dialog_filter/widget_dialog_filter.dart';
|
||||
import 'package:lightmeter/screens/settings/screen_settings.dart';
|
||||
import 'package:m3_lightmeter_iap/m3_lightmeter_iap.dart';
|
||||
import 'package:m3_lightmeter_resources/m3_lightmeter_resources.dart';
|
||||
import 'package:mocktail/mocktail.dart';
|
||||
import 'package:permission_handler/permission_handler.dart';
|
||||
|
||||
import 'mocks/paid_features_mock.dart';
|
||||
import 'utils/expectations.dart';
|
||||
import 'utils/widget_tester_actions.dart';
|
||||
|
||||
class _MockUserPreferencesService extends Mock implements UserPreferencesService {}
|
||||
|
||||
class _MockCaffeineService extends Mock implements CaffeineService {}
|
||||
|
||||
class _MockHapticsService extends Mock implements HapticsService {}
|
||||
|
||||
class _MockPermissionsService extends Mock implements PermissionsService {}
|
||||
|
||||
class _MockLightSensorService extends Mock implements LightSensorService {}
|
||||
|
||||
class _MockVolumeEventsService extends Mock implements VolumeEventsService {}
|
||||
|
||||
const _defaultIsoValue = IsoValue(400, StopType.full);
|
||||
|
||||
//https://stackoverflow.com/a/67186625/13167574
|
||||
void main() {
|
||||
IntegrationTestWidgetsFlutterBinding.ensureInitialized();
|
||||
|
||||
late _MockUserPreferencesService mockUserPreferencesService;
|
||||
late _MockCaffeineService mockCaffeineService;
|
||||
late _MockHapticsService mockHapticsService;
|
||||
late _MockPermissionsService mockPermissionsService;
|
||||
late _MockLightSensorService mockLightSensorService;
|
||||
late _MockVolumeEventsService mockVolumeEventsService;
|
||||
|
||||
setUpAll(() {
|
||||
mockUserPreferencesService = _MockUserPreferencesService();
|
||||
when(() => mockUserPreferencesService.evSourceType).thenReturn(EvSourceType.sensor);
|
||||
when(() => mockUserPreferencesService.stopType).thenReturn(StopType.third);
|
||||
when(() => mockUserPreferencesService.locale).thenReturn(SupportedLocale.en);
|
||||
when(() => mockUserPreferencesService.caffeine).thenReturn(true);
|
||||
when(() => mockUserPreferencesService.volumeAction).thenReturn(VolumeAction.shutter);
|
||||
when(() => mockUserPreferencesService.cameraEvCalibration).thenReturn(0.0);
|
||||
when(() => mockUserPreferencesService.lightSensorEvCalibration).thenReturn(0.0);
|
||||
when(() => mockUserPreferencesService.iso).thenReturn(_defaultIsoValue);
|
||||
when(() => mockUserPreferencesService.ndFilter).thenReturn(NdValue.values.first);
|
||||
when(() => mockUserPreferencesService.haptics).thenReturn(true);
|
||||
when(() => mockUserPreferencesService.meteringScreenLayout).thenReturn({
|
||||
MeteringScreenLayoutFeature.equipmentProfiles: true,
|
||||
MeteringScreenLayoutFeature.extremeExposurePairs: true,
|
||||
MeteringScreenLayoutFeature.filmPicker: true,
|
||||
MeteringScreenLayoutFeature.histogram: true,
|
||||
});
|
||||
when(() => mockUserPreferencesService.themeType).thenReturn(ThemeType.light);
|
||||
when(() => mockUserPreferencesService.primaryColor).thenReturn(primaryColorsList[5]);
|
||||
when(() => mockUserPreferencesService.dynamicColor).thenReturn(false);
|
||||
|
||||
mockCaffeineService = _MockCaffeineService();
|
||||
when(() => mockCaffeineService.isKeepScreenOn()).thenAnswer((_) async => false);
|
||||
when(() => mockCaffeineService.keepScreenOn(true)).thenAnswer((_) async => true);
|
||||
when(() => mockCaffeineService.keepScreenOn(false)).thenAnswer((_) async => false);
|
||||
|
||||
mockHapticsService = _MockHapticsService();
|
||||
when(() => mockHapticsService.quickVibration()).thenAnswer((_) async {});
|
||||
when(() => mockHapticsService.responseVibration()).thenAnswer((_) async {});
|
||||
when(() => mockHapticsService.errorVibration()).thenAnswer((_) async {});
|
||||
|
||||
mockPermissionsService = _MockPermissionsService();
|
||||
when(() => mockPermissionsService.requestCameraPermission()).thenAnswer((_) async => PermissionStatus.granted);
|
||||
when(() => mockPermissionsService.checkCameraPermission()).thenAnswer((_) async => PermissionStatus.granted);
|
||||
|
||||
mockLightSensorService = _MockLightSensorService();
|
||||
when(() => mockLightSensorService.hasSensor()).thenAnswer((_) async => true);
|
||||
when(() => mockLightSensorService.luxStream()).thenAnswer((_) => Stream.fromIterable([100]));
|
||||
|
||||
mockVolumeEventsService = _MockVolumeEventsService();
|
||||
when(() => mockVolumeEventsService.setVolumeHandling(true)).thenAnswer((_) async => true);
|
||||
when(() => mockVolumeEventsService.setVolumeHandling(false)).thenAnswer((_) async => false);
|
||||
when(() => mockVolumeEventsService.volumeButtonsEventStream()).thenAnswer((_) => const Stream<int>.empty());
|
||||
|
||||
when(() => mockHapticsService.quickVibration()).thenAnswer((_) async {});
|
||||
when(() => mockHapticsService.responseVibration()).thenAnswer((_) async {});
|
||||
});
|
||||
|
||||
Future<void> pumpApplication(
|
||||
WidgetTester tester,
|
||||
IAPProductStatus purchaseStatus, {
|
||||
String selectedEquipmentProfileId = '',
|
||||
Film selectedFilm = const Film.other(),
|
||||
}) async {
|
||||
await tester.pumpWidget(
|
||||
MockIAPProviders(
|
||||
purchaseStatus: purchaseStatus,
|
||||
selectedEquipmentProfileId: selectedEquipmentProfileId,
|
||||
selectedFilm: selectedFilm,
|
||||
child: ServicesProvider(
|
||||
environment: const Environment.prod().copyWith(hasLightSensor: true),
|
||||
userPreferencesService: mockUserPreferencesService,
|
||||
caffeineService: mockCaffeineService,
|
||||
hapticsService: mockHapticsService,
|
||||
permissionsService: mockPermissionsService,
|
||||
lightSensorService: mockLightSensorService,
|
||||
volumeEventsService: mockVolumeEventsService,
|
||||
child: const UserPreferencesProvider(
|
||||
child: Application(),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
await tester.pumpAndSettle();
|
||||
}
|
||||
|
||||
group(
|
||||
'[Metering layout features]',
|
||||
() {
|
||||
Future<void> toggleFeatureAndClose(WidgetTester tester, String feature) async {
|
||||
await tester.openSettings();
|
||||
expect(find.byType(SettingsScreen), findsOneWidget);
|
||||
await tester.tap(find.text(S.current.meteringScreenLayout));
|
||||
await tester.pumpAndSettle();
|
||||
expect(find.byType(MeteringScreenLayoutFeaturesDialog), findsOneWidget);
|
||||
await tester.tap(
|
||||
find.descendant(
|
||||
of: find.byType(SwitchListTile),
|
||||
matching: find.text(feature),
|
||||
),
|
||||
);
|
||||
await tester.tapSaveButton();
|
||||
expect(find.byType(MeteringScreenLayoutFeaturesDialog), findsNothing);
|
||||
await tester.tap(find.byIcon(Icons.close));
|
||||
await tester.pumpAndSettle();
|
||||
}
|
||||
|
||||
testWidgets(
|
||||
'Toggle equipmentProfiles & discard selected',
|
||||
(tester) async {
|
||||
await pumpApplication(
|
||||
tester,
|
||||
IAPProductStatus.purchased,
|
||||
selectedEquipmentProfileId: mockEquipmentProfiles[0].id,
|
||||
);
|
||||
await tester.toggleIncidentMetering();
|
||||
expectAnimatedPickerWith<EquipmentProfilePicker>(value: mockEquipmentProfiles[0].name);
|
||||
expectExposurePairsContainer('f/1.8 - 1/50', 'f/16 - 1.6"');
|
||||
expectMeasureButton(7.3);
|
||||
|
||||
await toggleFeatureAndClose(tester, S.current.meteringScreenLayoutHintEquipmentProfiles);
|
||||
expect(find.byType(EquipmentProfilePicker), findsNothing);
|
||||
expectExposurePairsContainer('f/1.0 - 1/160', 'f/45 - 13"');
|
||||
expectMeasureButton(7.3);
|
||||
|
||||
await toggleFeatureAndClose(tester, S.current.meteringScreenLayoutHintEquipmentProfiles);
|
||||
expectAnimatedPickerWith<EquipmentProfilePicker>(value: S.current.none);
|
||||
expectExposurePairsContainer('f/1.0 - 1/160', 'f/45 - 13"');
|
||||
expectMeasureButton(7.3);
|
||||
},
|
||||
);
|
||||
|
||||
testWidgets(
|
||||
'Toggle extremeExposurePairs',
|
||||
(tester) async {
|
||||
await pumpApplication(tester, IAPProductStatus.purchased);
|
||||
await tester.toggleIncidentMetering();
|
||||
expectExposurePairsContainer('f/1.0 - 1/160', 'f/45 - 13"');
|
||||
expectMeasureButton(7.3);
|
||||
|
||||
await toggleFeatureAndClose(tester, S.current.meteringScreenFeatureExtremeExposurePairs);
|
||||
expect(find.byType(ExtremeExposurePairsContainer), findsNothing);
|
||||
expectMeasureButton(7.3);
|
||||
|
||||
await toggleFeatureAndClose(tester, S.current.meteringScreenFeatureExtremeExposurePairs);
|
||||
expectExposurePairsContainer('f/1.0 - 1/160', 'f/45 - 13"');
|
||||
expectMeasureButton(7.3);
|
||||
},
|
||||
);
|
||||
|
||||
testWidgets(
|
||||
'Toggle film & discard selected',
|
||||
(tester) async {
|
||||
await pumpApplication(
|
||||
tester,
|
||||
IAPProductStatus.purchased,
|
||||
selectedFilm: mockFilms.first,
|
||||
);
|
||||
await tester.toggleIncidentMetering();
|
||||
expectAnimatedPickerWith<FilmPicker>(value: mockFilms.first.name);
|
||||
expectExposurePairsContainer('f/1.0 - 1/160', 'f/45 - 26"');
|
||||
expectMeasureButton(7.3);
|
||||
|
||||
await toggleFeatureAndClose(tester, S.current.meteringScreenFeatureFilmPicker);
|
||||
expect(find.byType(FilmPicker), findsNothing);
|
||||
expectExposurePairsContainer('f/1.0 - 1/160', 'f/45 - 13"');
|
||||
expectMeasureButton(7.3);
|
||||
|
||||
await toggleFeatureAndClose(tester, S.current.meteringScreenFeatureFilmPicker);
|
||||
expectAnimatedPickerWith<FilmPicker>(value: S.current.none);
|
||||
expectExposurePairsContainer('f/1.0 - 1/160', 'f/45 - 13"');
|
||||
expectMeasureButton(7.3);
|
||||
},
|
||||
);
|
||||
|
||||
testWidgets(
|
||||
'Toggle histogram',
|
||||
(tester) async {
|
||||
await pumpApplication(tester, IAPProductStatus.purchased);
|
||||
},
|
||||
skip: true, // TODO(@vodemn)
|
||||
);
|
||||
},
|
||||
);
|
||||
|
||||
testWidgets(
|
||||
'[Films in use] Deselect current',
|
||||
(tester) async {
|
||||
await pumpApplication(
|
||||
tester,
|
||||
IAPProductStatus.purchased,
|
||||
selectedFilm: mockFilms[0],
|
||||
);
|
||||
|
||||
// Check that film is selected and reciprocity is applied
|
||||
await tester.toggleIncidentMetering();
|
||||
expectAnimatedPickerWith<FilmPicker>(value: mockFilms[0].name);
|
||||
expectExposurePairsContainer('f/1.0 - 1/160', 'f/45 - 26"');
|
||||
expectMeasureButton(7.3);
|
||||
|
||||
// Deselect the first films
|
||||
await tester.openSettings();
|
||||
expect(find.byType(SettingsScreen), findsOneWidget);
|
||||
await tester.tap(find.text(S.current.filmsInUse));
|
||||
await tester.pumpAndSettle();
|
||||
expect(find.byType(DialogFilter<Film>), findsOneWidget);
|
||||
await tester.tap(
|
||||
find.descendant(
|
||||
of: find.byType(CheckboxListTile),
|
||||
matching: find.text(mockFilms[0].name),
|
||||
),
|
||||
);
|
||||
await tester.tapSaveButton();
|
||||
expect(find.byType(DialogFilter<Film>), findsNothing);
|
||||
await tester.tap(find.byIcon(Icons.close));
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
// The previously selected films is no longer in use and therefore is discarded to None
|
||||
expectAnimatedPickerWith<FilmPicker>(value: S.current.none);
|
||||
expectMeasureButton(7.3);
|
||||
|
||||
// The previously selected films is no longer in use and therefore is not present in the picker
|
||||
await tester.openAnimatedPicker<FilmPicker>();
|
||||
expect(find.byType(DialogPicker<Film>), findsOneWidget);
|
||||
expect(
|
||||
find.descendant(
|
||||
of: find.byWidgetPredicate((widget) => widget is RadioListTile<Film> && widget.selected),
|
||||
matching: find.text(mockFilms[0].name),
|
||||
),
|
||||
findsNothing,
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
extension _WidgetTesterActions on WidgetTester {
|
||||
Future<void> openSettings() async {
|
||||
expect(find.byTooltip(S.current.tooltipOpenSettings), findsOneWidget);
|
||||
await tap(find.byTooltip(S.current.tooltipOpenSettings));
|
||||
await pumpAndSettle();
|
||||
}
|
||||
}
|
42
integration_test/utils/expectations.dart
Normal file
42
integration_test/utils/expectations.dart
Normal file
|
@ -0,0 +1,42 @@
|
|||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:lightmeter/generated/l10n.dart';
|
||||
import 'package:lightmeter/screens/metering/components/bottom_controls/components/measure_button/widget_button_measure.dart';
|
||||
import 'package:lightmeter/screens/metering/components/shared/readings_container/components/extreme_exposure_pairs_container/widget_container_extreme_exposure_pairs.dart';
|
||||
import 'package:lightmeter/screens/settings/components/shared/dialog_picker/widget_dialog_picker.dart';
|
||||
|
||||
/// Expects exactly one picker of the specified type and verifies `title` or/and `value` if any of the values is not null.
|
||||
void expectAnimatedPickerWith<T>({String? title, String? value}) {
|
||||
final pickerFinder = find.byType(T);
|
||||
expect(pickerFinder, findsOneWidget);
|
||||
if (title != null) expect(find.descendant(of: pickerFinder, matching: find.text(title)), findsOneWidget);
|
||||
if (value != null) expect(find.descendant(of: pickerFinder, matching: find.text(value)), findsOneWidget);
|
||||
}
|
||||
|
||||
/// Finds exactly one dialog picker of the provided value type
|
||||
void expectDialogPicker<T>() {
|
||||
expect(find.byType(DialogPicker<T>), findsOneWidget);
|
||||
}
|
||||
|
||||
void expectMeasureButton(double ev) {
|
||||
find.descendant(
|
||||
of: find.byType(MeteringMeasureButton),
|
||||
matching: find.text('${ev.toStringAsFixed(1)}\n${S.current.ev}'),
|
||||
);
|
||||
}
|
||||
|
||||
void expectExposurePairsContainer(String fastest, String slowest) {
|
||||
final pickerFinder = find.byType(ExtremeExposurePairsContainer);
|
||||
expect(pickerFinder, findsOneWidget);
|
||||
expect(find.descendant(of: pickerFinder, matching: find.text(S.current.fastestExposurePair)), findsOneWidget);
|
||||
expect(find.descendant(of: pickerFinder, matching: find.text(fastest)), findsOneWidget);
|
||||
expect(find.descendant(of: pickerFinder, matching: find.text(S.current.slowestExposurePair)), findsOneWidget);
|
||||
expect(find.descendant(of: pickerFinder, matching: find.text(slowest)), findsOneWidget);
|
||||
}
|
||||
|
||||
void expectRadioListTile<T>(String text, {bool isSelected = false}) {
|
||||
expect(
|
||||
find.descendant(of: find.byWidgetPredicate((widget) => widget is RadioListTile<T>), matching: find.text(text)),
|
||||
findsOneWidget,
|
||||
);
|
||||
}
|
|
@ -3,12 +3,25 @@ import 'dart:math';
|
|||
import 'package:flutter/services.dart';
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
|
||||
const _systemFeatureMethodChannel = MethodChannel('system_feature');
|
||||
const _lightSensorMethodChannel = MethodChannel("light.eventChannel");
|
||||
Future<void> sendMockLux([int lux = 100]) async {
|
||||
await TestDefaultBinaryMessengerBinding.instance.defaultBinaryMessenger.handlePlatformMessage(
|
||||
"light.eventChannel",
|
||||
const StandardMethodCodec().encodeSuccessEnvelope(lux),
|
||||
(ByteData? data) {},
|
||||
);
|
||||
}
|
||||
|
||||
Future<void> sendMockIncidentEv(double ev) async {
|
||||
await TestDefaultBinaryMessengerBinding.instance.defaultBinaryMessenger.handlePlatformMessage(
|
||||
"light.eventChannel",
|
||||
const StandardMethodCodec().encodeSuccessEnvelope((2.5 * pow(2, ev)).toInt()),
|
||||
(ByteData? data) {},
|
||||
);
|
||||
}
|
||||
|
||||
void setLightSensorAvilability({required bool hasSensor}) {
|
||||
TestDefaultBinaryMessengerBinding.instance.defaultBinaryMessenger.setMockMethodCallHandler(
|
||||
_systemFeatureMethodChannel,
|
||||
const MethodChannel('system_feature'),
|
||||
(methodCall) async {
|
||||
switch (methodCall.method) {
|
||||
case "sensor":
|
||||
|
@ -19,43 +32,3 @@ void setLightSensorAvilability({required bool hasSensor}) {
|
|||
},
|
||||
);
|
||||
}
|
||||
|
||||
void resetLightSensorAvilability() {
|
||||
TestDefaultBinaryMessengerBinding.instance.defaultBinaryMessenger.setMockMethodCallHandler(
|
||||
_systemFeatureMethodChannel,
|
||||
null,
|
||||
);
|
||||
}
|
||||
|
||||
Future<void> sendMockIncidentEv(double ev) => sendMockLux((2.5 * pow(2, ev)).toInt());
|
||||
|
||||
Future<void> sendMockLux([int lux = 100]) async {
|
||||
await TestDefaultBinaryMessengerBinding.instance.defaultBinaryMessenger.handlePlatformMessage(
|
||||
_lightSensorMethodChannel.name,
|
||||
const StandardMethodCodec().encodeSuccessEnvelope(lux),
|
||||
(ByteData? data) {},
|
||||
);
|
||||
}
|
||||
|
||||
void setupLightSensorStreamHandler() {
|
||||
TestDefaultBinaryMessengerBinding.instance.defaultBinaryMessenger.setMockMethodCallHandler(
|
||||
_lightSensorMethodChannel,
|
||||
(methodCall) async {
|
||||
switch (methodCall.method) {
|
||||
case "listen":
|
||||
return;
|
||||
case "cancel":
|
||||
return;
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
void resetLightSensorStreamHandler() {
|
||||
TestDefaultBinaryMessengerBinding.instance.defaultBinaryMessenger.setMockMethodCallHandler(
|
||||
_lightSensorMethodChannel,
|
||||
null,
|
||||
);
|
||||
}
|
||||
|
|
|
@ -19,13 +19,8 @@ extension WidgetTesterCommonActions on WidgetTester {
|
|||
Film selectedFilm = const Film.other(),
|
||||
}) async {
|
||||
await pumpWidget(
|
||||
IAPProducts(
|
||||
products: [
|
||||
IAPProduct(
|
||||
storeId: IAPProductType.paidFeatures.storeId,
|
||||
status: productStatus,
|
||||
),
|
||||
],
|
||||
MockIAPProductsProvider(
|
||||
productStatus: productStatus,
|
||||
child: ApplicationWrapper(
|
||||
const Environment.dev(),
|
||||
child: MockIAPProviders(
|
||||
|
@ -60,6 +55,10 @@ extension WidgetTesterCommonActions on WidgetTester {
|
|||
}
|
||||
|
||||
extension WidgetTesterListTileActions on WidgetTester {
|
||||
Future<void> tapRadioListTile<T>(String text) async {
|
||||
await tap(find.descendant(of: find.byType(RadioListTile<T>), matching: find.text(text)));
|
||||
}
|
||||
|
||||
/// Useful for tapping a specific [ListTile] inside a specific screen or dialog
|
||||
Future<void> tapDescendantTextOf<T>(String text) async {
|
||||
await tap(find.descendant(of: find.byType(T), matching: find.text(text)));
|
||||
|
|
|
@ -7,9 +7,9 @@ import 'package:m3_lightmeter_iap/m3_lightmeter_iap.dart';
|
|||
Future<void> main() async {
|
||||
WidgetsFlutterBinding.ensureInitialized();
|
||||
runApp(
|
||||
IAPProducts(
|
||||
products: [IAPProduct(storeId: IAPProductType.paidFeatures.storeId)],
|
||||
child: const ApplicationWrapper(
|
||||
const MockIAPProductsProvider(
|
||||
productStatus: IAPProductStatus.purchasable,
|
||||
child: ApplicationWrapper(
|
||||
Environment.dev(),
|
||||
child: Application(),
|
||||
),
|
||||
|
|
|
@ -1,37 +0,0 @@
|
|||
# Screenshots
|
||||
|
||||
The easiest way to create several sets of identical screenshots for Android and iOS is to generate them instead of taking them manually. Generating screenshots will save time and effort while also providing a consistent output.
|
||||
|
||||
## Context
|
||||
|
||||
As a user I want to see the most relevant screenshots in the store, so that I can see the actual state of the app.
|
||||
|
||||
## Screenshot cases
|
||||
|
||||
- Metering screen
|
||||
|
||||
1. Reflected light metering mode*
|
||||
2. Incident light metering mode* **
|
||||
3. Opened ISO picker
|
||||
|
||||
- Settings screen
|
||||
|
||||
1. Just the screen
|
||||
2. Opened metering screen layout features dialog
|
||||
|
||||
- Equipment profiles screen
|
||||
|
||||
1. Just the screen
|
||||
2. Opened equipment profile ISO picker
|
||||
|
||||
> *also in dark mode
|
||||
|
||||
> **Android only
|
||||
|
||||
## Run the generator
|
||||
|
||||
```console
|
||||
sh screenshots/generate_screenshots.sh
|
||||
```
|
||||
|
||||
Screenshots will be stored in the _screenshots/_ folder.
|
|
@ -7,7 +7,7 @@ Future<void> main() async {
|
|||
await grantCameraPermission();
|
||||
await integrationDriver(
|
||||
onScreenshot: (name, bytes, [args]) async {
|
||||
final File image = await File('screenshots/$name.png').create(recursive: true);
|
||||
final File image = await File('screenshots/TEST_$name.png').create(recursive: true);
|
||||
image.writeAsBytesSync(bytes);
|
||||
return true;
|
||||
},
|
||||
|
|
Loading…
Reference in a new issue