refined tester extension and expectations

This commit is contained in:
Vadim 2024-03-11 21:51:46 +01:00
parent a70ce5012a
commit b80c46fd3a
4 changed files with 346 additions and 71 deletions

View file

@ -6,21 +6,16 @@ import 'package:lightmeter/data/models/ev_source_type.dart';
import 'package:lightmeter/data/models/metering_screen_layout_config.dart'; import 'package:lightmeter/data/models/metering_screen_layout_config.dart';
import 'package:lightmeter/data/shared_prefs_service.dart'; import 'package:lightmeter/data/shared_prefs_service.dart';
import 'package:lightmeter/generated/l10n.dart'; import 'package:lightmeter/generated/l10n.dart';
import 'package:lightmeter/screens/metering/components/shared/exposure_pairs_list/components/exposure_pairs_list_item/widget_item_list_exposure_pairs.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/equipment_profile_picker/widget_picker_equipment_profiles.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/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/film_picker/widget_picker_film.dart';
import 'package:lightmeter/screens/metering/screen_metering.dart';
import 'package:lightmeter/screens/settings/screen_settings.dart'; import 'package:lightmeter/screens/settings/screen_settings.dart';
import 'package:m3_lightmeter_resources/m3_lightmeter_resources.dart';
import 'package:meta/meta.dart'; import 'package:meta/meta.dart';
import 'package:shared_preferences/shared_preferences.dart'; import 'package:shared_preferences/shared_preferences.dart';
import '../integration_test/utils/widget_tester_actions.dart'; import '../integration_test/utils/widget_tester_actions.dart';
import 'mocks/paid_features_mock.dart'; import 'mocks/paid_features_mock.dart';
import 'utils/expectations.dart';
const _mockPhotoEv100 = 8.3;
@isTestGroup @isTestGroup
void testToggleLayoutFeatures(String description) { void testToggleLayoutFeatures(String description) {
@ -46,11 +41,11 @@ void testToggleLayoutFeatures(String description) {
(tester) async { (tester) async {
await tester.pumpApplication(selectedEquipmentProfileId: mockEquipmentProfiles.first.id); await tester.pumpApplication(selectedEquipmentProfileId: mockEquipmentProfiles.first.id);
await tester.takePhoto(); await tester.takePhoto();
_expectPickerTitle<EquipmentProfilePicker>(mockEquipmentProfiles.first.name); expectPickerTitle<EquipmentProfilePicker>(mockEquipmentProfiles.first.name);
_expectExtremeExposurePairs('f/1.8 - 1/100', 'f/16 - 1/1.3'); expectExtremeExposurePairs('f/1.8 - 1/100', 'f/16 - 1/1.3');
_expectExposurePairsListItem(tester, 'f/1.8', '1/100'); expectExposurePairsListItem(tester, 'f/1.8', '1/100');
await tester.scrollToTheLastExposurePair(mockEquipmentProfiles.first); await tester.scrollToTheLastExposurePair(equipmentProfile: mockEquipmentProfiles.first);
_expectExposurePairsListItem(tester, 'f/16', '1/1.3'); expectExposurePairsListItem(tester, 'f/16', '1/1.3');
// Disable layout feature // Disable layout feature
await tester.toggleLayoutFeature(S.current.meteringScreenLayoutHintEquipmentProfiles); await tester.toggleLayoutFeature(S.current.meteringScreenLayoutHintEquipmentProfiles);
@ -60,12 +55,12 @@ void testToggleLayoutFeatures(String description) {
reason: reason:
'Equipment profile picker must be hidden from the metering screen when the corresponding layout feature is disabled.', 'Equipment profile picker must be hidden from the metering screen when the corresponding layout feature is disabled.',
); );
_expectExtremeExposurePairs( expectExtremeExposurePairs(
'f/1.0 - 1/320', 'f/1.0 - 1/320',
'f/45 - 6"', 'f/45 - 6"',
reason: 'Aperture and shutter speed ranges must be reset to default values when equipment profile is reset', reason: 'Aperture and shutter speed ranges must be reset to default values when equipment profile is reset',
); );
_expectExposurePairsListItem( expectExposurePairsListItem(
tester, tester,
'f/1.0', 'f/1.0',
'1/320', '1/320',
@ -73,7 +68,7 @@ void testToggleLayoutFeatures(String description) {
'Aperture and shutter speed ranges must be reset to default values when equipment profile is reset.', 'Aperture and shutter speed ranges must be reset to default values when equipment profile is reset.',
); );
await tester.scrollToTheLastExposurePair(); await tester.scrollToTheLastExposurePair();
_expectExposurePairsListItem( expectExposurePairsListItem(
tester, tester,
'f/45', 'f/45',
'6"', '6"',
@ -83,7 +78,7 @@ void testToggleLayoutFeatures(String description) {
// Enable layout feature // Enable layout feature
await tester.toggleLayoutFeature(S.current.meteringScreenLayoutHintEquipmentProfiles); await tester.toggleLayoutFeature(S.current.meteringScreenLayoutHintEquipmentProfiles);
_expectPickerTitle<EquipmentProfilePicker>( expectPickerTitle<EquipmentProfilePicker>(
S.current.none, S.current.none,
reason: 'Equipment profile must remain unselected when the corresponding layout feature is re-enabled.', reason: 'Equipment profile must remain unselected when the corresponding layout feature is re-enabled.',
); );
@ -95,10 +90,10 @@ void testToggleLayoutFeatures(String description) {
(tester) async { (tester) async {
await tester.pumpApplication(); await tester.pumpApplication();
await tester.takePhoto(); await tester.takePhoto();
_expectExtremeExposurePairs('f/1.0 - 1/320', 'f/45 - 6"'); expectExtremeExposurePairs('f/1.0 - 1/320', 'f/45 - 6"');
_expectExposurePairsListItem(tester, 'f/1.0', '1/320'); expectExposurePairsListItem(tester, 'f/1.0', '1/320');
await tester.scrollToTheLastExposurePair(); await tester.scrollToTheLastExposurePair();
_expectExposurePairsListItem(tester, 'f/45', '6"'); expectExposurePairsListItem(tester, 'f/45', '6"');
// Disable layout feature // Disable layout feature
await tester.toggleLayoutFeature(S.current.meteringScreenFeatureExtremeExposurePairs); await tester.toggleLayoutFeature(S.current.meteringScreenFeatureExtremeExposurePairs);
@ -108,7 +103,7 @@ void testToggleLayoutFeatures(String description) {
reason: reason:
'Extreme exposure pairs container must be hidden from the metering screen when the corresponding layout feature is disabled.', 'Extreme exposure pairs container must be hidden from the metering screen when the corresponding layout feature is disabled.',
); );
_expectExposurePairsListItem( expectExposurePairsListItem(
tester, tester,
'f/1.0', 'f/1.0',
'1/320', '1/320',
@ -116,7 +111,7 @@ void testToggleLayoutFeatures(String description) {
'Exposure pairs list must not be affected by the visibility of the extreme exposure pairs container.', 'Exposure pairs list must not be affected by the visibility of the extreme exposure pairs container.',
); );
await tester.scrollToTheLastExposurePair(); await tester.scrollToTheLastExposurePair();
_expectExposurePairsListItem( expectExposurePairsListItem(
tester, tester,
'f/45', 'f/45',
'6"', '6"',
@ -126,7 +121,7 @@ void testToggleLayoutFeatures(String description) {
// Enable layout feature // Enable layout feature
await tester.toggleLayoutFeature(S.current.meteringScreenFeatureExtremeExposurePairs); await tester.toggleLayoutFeature(S.current.meteringScreenFeatureExtremeExposurePairs);
_expectExtremeExposurePairs( expectExtremeExposurePairs(
'f/1.0 - 1/320', 'f/1.0 - 1/320',
'f/45 - 6"', 'f/45 - 6"',
reason: reason:
@ -140,11 +135,11 @@ void testToggleLayoutFeatures(String description) {
(tester) async { (tester) async {
await tester.pumpApplication(selectedFilm: mockFilms.first); await tester.pumpApplication(selectedFilm: mockFilms.first);
await tester.takePhoto(); await tester.takePhoto();
_expectPickerTitle<FilmPicker>(mockFilms.first.name); expectPickerTitle<FilmPicker>(mockFilms.first.name);
_expectExtremeExposurePairs('f/1.0 - 1/320', 'f/45 - 12"'); expectExtremeExposurePairs('f/1.0 - 1/320', 'f/45 - 12"');
_expectExposurePairsListItem(tester, 'f/1.0', '1/320'); expectExposurePairsListItem(tester, 'f/1.0', '1/320');
await tester.scrollToTheLastExposurePair(); await tester.scrollToTheLastExposurePair();
_expectExposurePairsListItem(tester, 'f/45', '12"'); expectExposurePairsListItem(tester, 'f/45', '12"');
// Disable layout feature // Disable layout feature
await tester.toggleLayoutFeature(S.current.meteringScreenFeatureFilmPicker); await tester.toggleLayoutFeature(S.current.meteringScreenFeatureFilmPicker);
@ -154,19 +149,19 @@ void testToggleLayoutFeatures(String description) {
reason: reason:
'Film picker must be hidden from the metering screen when the corresponding layout feature is disabled.', 'Film picker must be hidden from the metering screen when the corresponding layout feature is disabled.',
); );
_expectExtremeExposurePairs( expectExtremeExposurePairs(
'f/1.0 - 1/320', 'f/1.0 - 1/320',
'f/45 - 6"', 'f/45 - 6"',
reason: 'Shutter speed must not be affected by reciprocity when film is discarded.', reason: 'Shutter speed must not be affected by reciprocity when film is discarded.',
); );
_expectExposurePairsListItem( expectExposurePairsListItem(
tester, tester,
'f/1.0', 'f/1.0',
'1/320', '1/320',
reason: 'Shutter speed must not be affected by reciprocity when film is discarded.', reason: 'Shutter speed must not be affected by reciprocity when film is discarded.',
); );
await tester.scrollToTheLastExposurePair(); await tester.scrollToTheLastExposurePair();
_expectExposurePairsListItem( expectExposurePairsListItem(
tester, tester,
'f/45', 'f/45',
'6"', '6"',
@ -175,7 +170,7 @@ void testToggleLayoutFeatures(String description) {
// Enable layout feature // Enable layout feature
await tester.toggleLayoutFeature(S.current.meteringScreenFeatureFilmPicker); await tester.toggleLayoutFeature(S.current.meteringScreenFeatureFilmPicker);
_expectPickerTitle<FilmPicker>( expectPickerTitle<FilmPicker>(
S.current.none, S.current.none,
reason: 'Film must remain unselected when the corresponding layout feature is re-enabled.', reason: 'Film must remain unselected when the corresponding layout feature is re-enabled.',
); );
@ -193,46 +188,4 @@ extension on WidgetTester {
await tapSaveButton(); await tapSaveButton();
await navigatorPop(); await navigatorPop();
} }
Future<void> scrollToTheLastExposurePair([EquipmentProfile equipmentProfile = defaultEquipmentProfile]) async {
final exposurePairs = MeteringContainerBuidler.buildExposureValues(
_mockPhotoEv100,
StopType.third,
equipmentProfile,
);
await 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)),
);
}
}
void _expectPickerTitle<T>(String title, {String? reason}) {
expect(find.descendant(of: find.byType(T), matching: find.text(title)), findsOneWidget, reason: reason);
}
void _expectExtremeExposurePairs(String fastest, String slowest, {String? reason}) {
final pickerFinder = find.byType(ExtremeExposurePairsContainer);
expect(find.descendant(of: pickerFinder, matching: find.text(fastest)), findsOneWidget, reason: reason);
expect(find.descendant(of: pickerFinder, matching: find.text(slowest)), findsOneWidget, reason: reason);
}
void _expectExposurePairsListItem(WidgetTester tester, String aperture, String shutterSpeed, {String? reason}) {
Key? findKey<T extends PhotographyStopValue<num>>(String value) => tester
.widget<Row>(
find.ancestor(
of: find.ancestor(
of: find.text(value),
matching: find.byType(ExposurePairsListItem<T>),
),
matching: find.descendant(of: find.byType(ExposurePairsList), matching: find.byType(Row)),
),
)
.key;
expect(
findKey<ApertureValue>(aperture),
findKey<ShutterSpeedValue>(shutterSpeed),
reason: reason,
);
} }

View file

@ -0,0 +1,264 @@
import 'dart:convert';
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/models/exposure_pair.dart';
import 'package:lightmeter/data/models/metering_screen_layout_config.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/shared/exposure_pairs_list/components/exposure_pairs_list_item/widget_item_list_exposure_pairs.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/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/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/settings/screen_settings.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';
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);
class MeteringValuesExpectation {
final String fastestExposurePair;
final String slowestExposurePair;
final double ev;
const MeteringValuesExpectation(
this.fastestExposurePair,
this.slowestExposurePair,
this.ev,
);
}
@isTestGroup
void testMeteringScreenPickers(String description) {
group(
description,
() {
setUp(() {
SharedPreferences.setMockInitialValues({
/// Metering values
UserPreferencesService.evSourceTypeKey: EvSourceType.camera.index,
UserPreferencesService.meteringScreenLayoutKey: json.encode(
{
MeteringScreenLayoutFeature.equipmentProfiles: true,
MeteringScreenLayoutFeature.extremeExposurePairs: true,
MeteringScreenLayoutFeature.filmPicker: true,
}.toJson(),
),
});
});
group(
'Select film',
() {
testMeteringPicker<FilmPicker, Film>(
'with the same ISO',
expectBefore: MeteringValuesExpectation(
mockPhotoFastestExposurePair.toString(),
mockPhotoSlowestExposurePair.toString(),
mockPhotoEv100,
),
valueToSelect: mockFilms[0].name,
expectAfter: MeteringValuesExpectation(
mockPhotoFastestExposurePair.toString(),
'$mockPhotoSlowestAperture - ${mockFilms[0].reciprocityFailure(mockPhotoSlowestShutterSpeed)}',
mockPhotoEv100,
),
);
testMeteringPicker<FilmPicker, Film>(
'with greater ISO',
expectBefore: MeteringValuesExpectation(
mockPhotoFastestExposurePair.toString(),
mockPhotoSlowestExposurePair.toString(),
mockPhotoEv100,
),
valueToSelect: mockFilms[1].name,
expectAfter: MeteringValuesExpectation(
mockPhotoFastestExposurePair.toString(),
'$mockPhotoSlowestAperture - ${mockFilms[1].reciprocityFailure(mockPhotoSlowestShutterSpeed)}',
mockPhotoEv100,
),
);
},
);
testMeteringPicker<IsoValuePicker, IsoValue>(
'Select ISO +1 EV',
expectBefore: MeteringValuesExpectation(
mockPhotoFastestExposurePair.toString(),
mockPhotoSlowestExposurePair.toString(),
mockPhotoEv100,
),
valueToSelect: '400',
expectAfter: MeteringValuesExpectation(
'$mockPhotoFastestAperture - 1/1250',
'$mockPhotoSlowestAperture - 1.6"',
mockPhotoEv100 + 2,
),
);
testMeteringPicker<NdValuePicker, NdValue>(
'Select ND -1 EV',
expectBefore: MeteringValuesExpectation(
mockPhotoFastestExposurePair.toString(),
mockPhotoSlowestExposurePair.toString(),
mockPhotoEv100,
),
valueToSelect: '2',
expectAfter: MeteringValuesExpectation(
'$mockPhotoFastestAperture - 1/160',
'$mockPhotoSlowestAperture - 13"',
mockPhotoEv100 - 1,
),
);
testWidgets(
description,
(tester) async {
Future<void> selectAndExpect<P, V>(
String valueToSelect,
MeteringValuesExpectation expectation, {
String? reason,
}) async {
/// Verify, that EV is recalculated with a new setting
await tester.openPickerAndSelect<P, V>(valueToSelect);
_expectPickerTitle<P>(valueToSelect);
expectExposurePairsContainer(expectation.fastestExposurePair, expectation.slowestExposurePair);
expectMeasureButton(expectation.ev);
/// Make sure, that the selected setting is applied in the subsequent measurements
await tester.takePhoto();
await tester.takePhoto();
expectExposurePairsContainer(expectation.fastestExposurePair, expectation.slowestExposurePair);
expectMeasureButton(expectation.ev);
}
await tester.pumpApplication();
await tester.takePhoto();
await selectAndExpect<IsoValuePicker, IsoValue>(
'400',
MeteringValuesExpectation(
'$mockPhotoFastestAperture - 1/1250',
'$mockPhotoSlowestAperture - 1.6"',
mockPhotoEv100 + 2,
),
reason: 'Selecting ISO value must change EV value and therefore exposure pairs.',
);
await selectAndExpect<NdValuePicker, NdValue>(
'2',
MeteringValuesExpectation(
'$mockPhotoFastestAperture - 1/640',
'$mockPhotoSlowestAperture - 3"',
mockPhotoEv100 - 1,
),
reason: 'Selecting ND value must change EV value and therefore exposure pairs.',
);
await selectAndExpect<FilmPicker, Film>(
mockFilms[0].name,
MeteringValuesExpectation(
mockPhotoFastestExposurePair.toString(),
'$mockPhotoSlowestAperture - ${mockFilms[0].reciprocityFailure(mockPhotoSlowestShutterSpeed)}',
mockPhotoEv100,
),
reason: 'Selecting with the same ISO must change nothing exept exposure pairs due to reciprocity.',
);
await selectAndExpect<FilmPicker, Film>(
mockFilms[1].name,
MeteringValuesExpectation(
mockPhotoFastestExposurePair.toString(),
'$mockPhotoSlowestAperture - ${mockFilms[0].reciprocityFailure(mockPhotoSlowestShutterSpeed)}',
mockPhotoEv100,
),
reason:
'Selecting with a different ISO must must indicate push/pull and can change nothing exept exposure pairs due to reciprocity.',
);
},
);
},
);
}
/// Runs the picker test
///
/// 1. Takes photo and verifies `expectBefore` values
/// 2. Opens a picker and select the provided value
/// 3. Verifies `expectAfter`
/// 4. Takes photo and verifies `expectAfter` values
@isTest
void testMeteringPicker<P, V>(
String description, {
required MeteringValuesExpectation expectBefore,
required String valueToSelect,
required MeteringValuesExpectation expectAfter,
bool? skip,
}) {
testWidgets(
description,
(tester) async {
await tester.pumpApplication();
// Verify initial EV
await tester.toggleIncidentMetering(expectBefore.ev);
expectExposurePairsContainer(expectBefore.fastestExposurePair, expectBefore.slowestExposurePair);
expectMeasureButton(expectBefore.ev);
/// Verify, that EV is recalculated with a new setting
await tester.openPickerAndSelect<P, V>(valueToSelect);
expectExposurePairsContainer(expectAfter.fastestExposurePair, expectAfter.slowestExposurePair);
expectMeasureButton(expectAfter.ev);
/// Make sure, that the selected setting is applied in the subsequent measurements
await tester.toggleIncidentMetering(expectBefore.ev);
expectExposurePairsContainer(expectAfter.fastestExposurePair, expectAfter.slowestExposurePair);
expectMeasureButton(expectAfter.ev);
},
skip: skip,
);
}
extension on WidgetTester {
Future<void> openPickerAndSelect<P, V>(String valueToSelect) async {
await openAnimatedPicker<P>();
await tapDescendantTextOf<DialogPicker<V>>(valueToSelect);
await tapSelectButton();
}
}
void _expectPickerTitle<T>(String title, {String? reason}) {
expect(find.descendant(of: find.byType(T), matching: find.text(title)), findsOneWidget, reason: reason);
}
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 expectMeasureButton(double ev) {
find.descendant(
of: find.byType(MeteringMeasureButton),
matching: find.text('${ev.toStringAsFixed(1)}\n${S.current.ev}'),
);
}

View file

@ -0,0 +1,35 @@
import 'package:flutter/widgets.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:lightmeter/screens/metering/components/shared/exposure_pairs_list/components/exposure_pairs_list_item/widget_item_list_exposure_pairs.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:m3_lightmeter_resources/m3_lightmeter_resources.dart';
void expectPickerTitle<P extends Widget>(String title, {String? reason}) {
expect(find.descendant(of: find.byType(P), matching: find.text(title)), findsOneWidget, reason: reason);
}
void expectExtremeExposurePairs(String fastest, String slowest, {String? reason}) {
final pickerFinder = find.byType(ExtremeExposurePairsContainer);
expect(find.descendant(of: pickerFinder, matching: find.text(fastest)), findsOneWidget, reason: reason);
expect(find.descendant(of: pickerFinder, matching: find.text(slowest)), findsOneWidget, reason: reason);
}
void expectExposurePairsListItem(WidgetTester tester, String aperture, String shutterSpeed, {String? reason}) {
Key? findKey<T extends PhotographyStopValue<num>>(String value) => tester
.widget<Row>(
find.ancestor(
of: find.ancestor(
of: find.text(value),
matching: find.byType(ExposurePairsListItem<T>),
),
matching: find.descendant(of: find.byType(ExposurePairsList), matching: find.byType(Row)),
),
)
.key;
expect(
findKey<ApertureValue>(aperture),
findKey<ShutterSpeedValue>(shutterSpeed),
reason: reason,
);
}

View file

@ -6,6 +6,8 @@ import 'package:lightmeter/environment.dart';
import 'package:lightmeter/generated/l10n.dart'; import 'package:lightmeter/generated/l10n.dart';
import 'package:lightmeter/res/dimens.dart'; import 'package:lightmeter/res/dimens.dart';
import 'package:lightmeter/screens/metering/components/bottom_controls/components/measure_button/widget_button_measure.dart'; import 'package:lightmeter/screens/metering/components/bottom_controls/components/measure_button/widget_button_measure.dart';
import 'package:lightmeter/screens/metering/components/shared/exposure_pairs_list/widget_list_exposure_pairs.dart';
import 'package:lightmeter/screens/metering/screen_metering.dart';
import 'package:m3_lightmeter_iap/m3_lightmeter_iap.dart'; import 'package:m3_lightmeter_iap/m3_lightmeter_iap.dart';
import 'package:m3_lightmeter_resources/m3_lightmeter_resources.dart'; import 'package:m3_lightmeter_resources/m3_lightmeter_resources.dart';
@ -13,6 +15,8 @@ import '../mocks/iap_products_mock.dart';
import '../mocks/paid_features_mock.dart'; import '../mocks/paid_features_mock.dart';
import 'platform_channel_mock.dart'; import 'platform_channel_mock.dart';
const mockPhotoEv100 = 8.3;
extension WidgetTesterCommonActions on WidgetTester { extension WidgetTesterCommonActions on WidgetTester {
Future<void> pumpApplication({ Future<void> pumpApplication({
IAPProductStatus productStatus = IAPProductStatus.purchased, IAPProductStatus productStatus = IAPProductStatus.purchased,
@ -89,3 +93,22 @@ extension WidgetTesterTextButtonActions on WidgetTester {
await pumpAndSettle(); await pumpAndSettle();
} }
} }
extension WidgetTesterExposurePairsListActions on WidgetTester {
Future<void> scrollToTheLastExposurePair({
double ev = mockPhotoEv100,
StopType stopType = StopType.third,
EquipmentProfile equipmentProfile = defaultEquipmentProfile,
}) async {
final exposurePairs = MeteringContainerBuidler.buildExposureValues(
ev,
StopType.third,
equipmentProfile,
);
await 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)),
);
}
}