This commit is contained in:
Vadim 2023-10-02 11:53:26 +02:00
parent 602e31f425
commit 3925cc1ddf
38 changed files with 646 additions and 173 deletions

View file

@ -0,0 +1,51 @@
timeline_name="$1"
csv_filename="$2"
if [[ -n "$timeline_name" ]]; then
if [[ -z "$csv_filename" ]]; then
csv_filename="${timeline_name}"
fi
echo "====== Extracting & merging ${timeline_name} timelines ======"
echo "" >>${csv_filename}.csv
echo "${timeline_name}" >>${csv_filename}.csv
extent_micros="timeExtentMicros"
timelines=$(find ./build -maxdepth 1 -name "${timeline_name}*.timeline.json" -print)
for i in "${timelines[@]}"; do
benchextract $i
extent_micros+="$(grep -A0 -h '"timeExtentMicros":' $i | grep -o " [0-9]*")"
done
echo $extent_micros | tr ' ' ',' >>${csv_filename}.csv
metrics=(
"average_frame_build_time_millis"
"90th_percentile_frame_build_time_millis"
"99th_percentile_frame_build_time_millis"
"worst_frame_build_time_millis"
"average_rasterizer_time_millis"
"90th_percentile_rasterizer_time_millis"
"99th_percentile_rasterizer_time_millis"
"worst_frame_rasterizer_time_millis"
)
timeline_summaries=$(find ./build -maxdepth 1 -name "${timeline_name}*.timeline_summary.json" -print)
for i in "${timeline_summaries[@]}"; do
metrics[0]+="$(grep -A0 -h '"average_frame_build_time_millis":' $i | grep -o " [0-9.]*")"
metrics[1]+="$(grep -A0 -h '"90th_percentile_frame_build_time_millis":' $i | grep -o " [0-9.]*")"
metrics[2]+="$(grep -A0 -h '"99th_percentile_frame_build_time_millis":' $i | grep -o " [0-9.]*")"
metrics[3]+="$(grep -A0 -h '"worst_frame_build_time_millis":' $i | grep -o " [0-9.]*")"
metrics[4]+="$(grep -A0 -h '"average_frame_rasterizer_time_millis":' $i | grep -o " [0-9.]*")"
metrics[5]+="$(grep -A0 -h '"90th_percentile_frame_rasterizer_time_millis":' $i | grep -o " [0-9.]*")"
metrics[6]+="$(grep -A0 -h '"99th_percentile_frame_rasterizer_time_millis":' $i | grep -o " [0-9.]*")"
metrics[7]+="$(grep -A0 -h '"worst_frame_rasterizer_time_millis":' $i | grep -o " [0-9.]*")"
done
for metric in "${metrics[@]}"; do
echo $metric | tr ' ' ',' >>${csv_filename}.csv
done
benchmarks=$(find ./build -maxdepth 1 -name "${timeline_name}*.timeline.benchmark" -print)
benchmerge $benchmarks
else
echo "Provide the timeline name"
fi

View file

@ -0,0 +1,249 @@
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/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/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/equipment_profiles/components/equipment_profile_screen/components/equipment_profile_container/widget_container_equipment_profile.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: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 'package:shared_preferences/shared_preferences.dart';
import 'utils/widget_tester_extension.dart';
class _MockSharedPreferences extends Mock implements SharedPreferences {}
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 {}
//https://stackoverflow.com/a/67186625/13167574
void main() {
late _MockUserPreferencesService mockUserPreferencesService;
late _MockCaffeineService mockCaffeineService;
late _MockHapticsService mockHapticsService;
late _MockPermissionsService mockPermissionsService;
late _MockLightSensorService mockLightSensorService;
late _MockVolumeEventsService mockVolumeEventsService;
final binding = IntegrationTestWidgetsFlutterBinding();
IntegrationTestWidgetsFlutterBinding.ensureInitialized();
setUpAll(() {
mockUserPreferencesService = _MockUserPreferencesService();
when(() => mockUserPreferencesService.evSourceType).thenReturn(EvSourceType.camera);
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(const IsoValue(400, StopType.full));
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: false,
});
when(() => mockUserPreferencesService.themeType).thenReturn(ThemeType.light);
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) async {
await tester.pumpWidget(
IAPProviders(
sharedPreferences: _MockSharedPreferences(),
child: EquipmentProfiles(
selected: _mockEquipmentProfiles[0],
values: _mockEquipmentProfiles,
child: Films(
selected: const Film('Ilford HP5+', 400),
values: const [Film.other(), Film('Ilford HP5+', 400)],
filmsInUse: const [Film.other(), Film('Ilford HP5+', 400)],
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();
}
/// Generates several screenshots with the light theme
/// and the additionally the first one with the dark theme
void generateScreenshots(Color color) {
testWidgets('${color.value}_light', (tester) async {
when(() => mockUserPreferencesService.themeType).thenReturn(ThemeType.light);
when(() => mockUserPreferencesService.primaryColor).thenReturn(color);
await pumpApplication(tester);
await tester.takePhoto();
await tester.takeScreenshot(binding, '${color.value}_metering_reflected');
await tester.tap(find.byTooltip(S.current.tooltipUseLightSensor));
await tester.pumpAndSettle();
await tester.tap(find.byType(MeteringMeasureButton));
await tester.tap(find.byType(MeteringMeasureButton));
await tester.takeScreenshot(binding, '${color.value}_metering_incident');
expect(find.byType(IsoValuePicker), findsOneWidget);
await tester.tap(find.byType(IsoValuePicker));
await tester.pumpAndSettle(Dimens.durationL);
expect(find.byType(DialogPicker<IsoValue>), findsOneWidget);
await tester.takeScreenshot(binding, '${color.value}_metering_iso_picker');
await tester.tapCancelButton();
expect(find.byType(DialogPicker<IsoValue>), findsNothing);
await tester.openSettings();
await tester.takeScreenshot(binding, '${color.value}_settings');
await tester.tapListTile(S.current.meteringScreenLayout);
await tester.takeScreenshot(binding, '${color.value}_settings_metering_screen_layout');
await tester.tapCancelButton();
await tester.tapListTile(S.current.equipmentProfiles);
expect(find.byType(EquipmentProfilesScreen), findsOneWidget);
await tester.tap(find.byType(EquipmentProfileContainer).first);
await tester.pumpAndSettle();
await tester.takeScreenshot(binding, '${color.value}-equipment_profiles');
await tester.tap(find.byIcon(Icons.iso).first);
await tester.pumpAndSettle();
await tester.takeScreenshot(binding, '${color.value}_equipment_profiles_iso_picker');
});
testWidgets(
'${color.value}_dark',
(tester) async {
when(() => mockUserPreferencesService.themeType).thenReturn(ThemeType.dark);
when(() => mockUserPreferencesService.primaryColor).thenReturn(color);
await pumpApplication(tester);
await tester.takePhoto();
await tester.takeScreenshot(binding, '${color.value}_metering_reflected_dark');
await tester.tap(find.byTooltip(S.current.tooltipUseLightSensor));
await tester.pumpAndSettle();
await tester.tap(find.byType(MeteringMeasureButton));
await tester.tap(find.byType(MeteringMeasureButton));
await tester.takeScreenshot(binding, '${color.value}_metering_incident_dark');
},
);
}
generateScreenshots(primaryColorsList[5]);
generateScreenshots(primaryColorsList[3]);
generateScreenshots(primaryColorsList[9]);
}
final _mockEquipmentProfiles = [
const EquipmentProfile(
id: '',
name: '',
apertureValues: ApertureValue.values,
ndValues: NdValue.values,
shutterSpeedValues: ShutterSpeedValue.values,
isoValues: IsoValue.values,
),
EquipmentProfile(
id: '1',
name: 'Praktica + Zenitar',
apertureValues: ApertureValue.values.sublist(
ApertureValue.values.indexOf(const ApertureValue(1.7, StopType.half)),
ApertureValue.values.indexOf(const ApertureValue(16, StopType.full)) + 1,
),
ndValues: NdValue.values.sublist(0, 3),
shutterSpeedValues: ShutterSpeedValue.values.sublist(
ShutterSpeedValue.values.indexOf(const ShutterSpeedValue(1000, true, StopType.full)),
ShutterSpeedValue.values.indexOf(const ShutterSpeedValue(16, false, StopType.full)) + 1,
),
isoValues: const [
IsoValue(50, StopType.full),
IsoValue(100, StopType.full),
IsoValue(200, StopType.full),
IsoValue(250, StopType.third),
IsoValue(400, StopType.full),
IsoValue(500, StopType.third),
IsoValue(800, StopType.full),
IsoValue(1600, StopType.full),
IsoValue(3200, StopType.full),
],
),
const EquipmentProfile(
id: '2',
name: 'Praktica + Jupiter',
apertureValues: ApertureValue.values,
ndValues: NdValue.values,
shutterSpeedValues: ShutterSpeedValue.values,
isoValues: IsoValue.values,
),
];

View file

@ -0,0 +1,10 @@
flutter drive \
--dart-define="cameraPreviewAspectRatio=240/320" \
--dart-define="cameraStubImage=assets/camera_stub_image.jpg" \
--driver=test_driver/performance_driver.dart \
--target=integration_test/metering_test.dart \
--profile \
--flavor=dev \
--no-dds \
--endless-trace-buffer \
--purge-persistent-cache

View file

@ -0,0 +1,209 @@
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/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/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/equipment_profiles/components/equipment_profile_screen/components/equipment_profile_container/widget_container_equipment_profile.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: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 'package:shared_preferences/shared_preferences.dart';
import 'utils/widget_tester_extension.dart';
class _MockSharedPreferences extends Mock implements SharedPreferences {}
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 {}
//https://stackoverflow.com/a/67186625/13167574
void main() {
late _MockUserPreferencesService mockUserPreferencesService;
late _MockCaffeineService mockCaffeineService;
late _MockHapticsService mockHapticsService;
late _MockPermissionsService mockPermissionsService;
late _MockLightSensorService mockLightSensorService;
late _MockVolumeEventsService mockVolumeEventsService;
final binding = IntegrationTestWidgetsFlutterBinding();
IntegrationTestWidgetsFlutterBinding.ensureInitialized();
setUpAll(() {
mockUserPreferencesService = _MockUserPreferencesService();
when(() => mockUserPreferencesService.evSourceType).thenReturn(EvSourceType.camera);
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(const IsoValue(400, StopType.full));
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: false,
});
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) async {
await tester.pumpWidget(
IAPProviders(
sharedPreferences: _MockSharedPreferences(),
child: EquipmentProfiles(
selected: _mockEquipmentProfiles[0],
values: _mockEquipmentProfiles,
child: Films(
selected: const Film('Ilford HP5+', 400),
values: const [Film.other(), Film('Ilford HP5+', 400)],
filmsInUse: const [Film.other(), Film('Ilford HP5+', 400)],
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();
}
testWidgets('Pickers test', (tester) async {
await pumpApplication(tester);
await tester.takePhoto();
expect(find.byType(IsoValuePicker), findsOneWidget);
await binding.traceActionNamed(
() async {
await tester.tap(find.byType(IsoValuePicker));
await tester.pumpAndSettle(Dimens.durationL);
},
"open_iso_picker",
);
expect(find.byType(DialogPicker<IsoValue>), findsOneWidget);
await tester.tapCancelButton();
expect(find.byType(DialogPicker<IsoValue>), findsNothing);
});
}
extension on IntegrationTestWidgetsFlutterBinding {
Future<void> traceActionNamed(Future<dynamic> Function() action, String timelineName) async {
final nowString = DateTime.now().toIso8601String().replaceAll(':', '-');
await traceAction(action, reportKey: "${timelineName}_$nowString");
}
}
final _mockEquipmentProfiles = [
const EquipmentProfile(
id: '',
name: '',
apertureValues: ApertureValue.values,
ndValues: NdValue.values,
shutterSpeedValues: ShutterSpeedValue.values,
isoValues: IsoValue.values,
),
EquipmentProfile(
id: '1',
name: 'Praktica + Zenitar',
apertureValues: ApertureValue.values.sublist(
ApertureValue.values.indexOf(const ApertureValue(1.7, StopType.half)),
ApertureValue.values.indexOf(const ApertureValue(16, StopType.full)) + 1,
),
ndValues: NdValue.values.sublist(0, 3),
shutterSpeedValues: ShutterSpeedValue.values.sublist(
ShutterSpeedValue.values.indexOf(const ShutterSpeedValue(1000, true, StopType.full)),
ShutterSpeedValue.values.indexOf(const ShutterSpeedValue(16, false, StopType.full)) + 1,
),
isoValues: const [
IsoValue(50, StopType.full),
IsoValue(100, StopType.full),
IsoValue(200, StopType.full),
IsoValue(250, StopType.third),
IsoValue(400, StopType.full),
IsoValue(500, StopType.third),
IsoValue(800, StopType.full),
IsoValue(1600, StopType.full),
IsoValue(3200, StopType.full),
],
),
const EquipmentProfile(
id: '2',
name: 'Praktica + Jupiter',
apertureValues: ApertureValue.values,
ndValues: NdValue.values,
shutterSpeedValues: ShutterSpeedValue.values,
isoValues: IsoValue.values,
),
];

View file

@ -1,61 +0,0 @@
import 'package:flutter/material.dart';
import 'package:flutter_localizations/flutter_localizations.dart';
import 'package:lightmeter/data/models/theme_type.dart';
import 'package:lightmeter/data/shared_prefs_service.dart';
import 'package:lightmeter/generated/l10n.dart';
import 'package:lightmeter/providers/theme_provider.dart';
import 'package:lightmeter/utils/inherited_generics.dart';
import 'package:mocktail/mocktail.dart';
class _MockUserPreferencesService extends Mock implements UserPreferencesService {}
class ApplicationMock extends StatefulWidget {
final Widget child;
const ApplicationMock({required this.child, super.key});
@override
State<ApplicationMock> createState() => _ApplicationMockState();
}
class _ApplicationMockState extends State<ApplicationMock> {
late final _MockUserPreferencesService userPreferencesService;
@override
void initState() {
super.initState();
userPreferencesService = _MockUserPreferencesService();
when(() => userPreferencesService.themeType).thenReturn(ThemeType.light);
when(() => userPreferencesService.primaryColor)
.thenReturn(ThemeProvider.primaryColorsList.first);
when(() => userPreferencesService.dynamicColor).thenReturn(false);
}
@override
Widget build(BuildContext context) {
return InheritedWidgetBase<UserPreferencesService>(
data: userPreferencesService,
child: ThemeProvider(
child: Builder(
builder: (context) {
return MaterialApp(
theme: context.listen<ThemeData>(),
localizationsDelegates: const [
S.delegate,
GlobalMaterialLocalizations.delegate,
GlobalWidgetsLocalizations.delegate,
GlobalCupertinoLocalizations.delegate,
],
supportedLocales: S.delegate.supportedLocales,
builder: (context, child) => MediaQuery(
data: MediaQuery.of(context).copyWith(textScaleFactor: 1.0),
child: child!,
),
home: widget.child,
);
},
),
),
);
}
}

View file

@ -0,0 +1,56 @@
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/generated/l10n.dart';
import 'package:lightmeter/screens/metering/components/bottom_controls/components/measure_button/widget_button_measure.dart';
import 'package:lightmeter/screens/settings/screen_settings.dart';
extension WidgetTesterFinder on WidgetTester {
Future<void> takeScreenshot(IntegrationTestWidgetsFlutterBinding binding, String name) async {
if (Platform.isAndroid) {
await binding.convertFlutterSurfaceToImage();
await pumpAndSettle();
}
await binding.takeScreenshot(name);
await pumpAndSettle();
}
Future<void> takePhoto() async {
await tap(find.byType(MeteringMeasureButton));
await pump(const Duration(seconds: 2)); // wait for circular progress indicator
await pump(const Duration(seconds: 1)); // wait for circular progress indicator
await pumpAndSettle();
}
Future<void> tapCancelButton() async {
final cancelButton = find.byWidgetPredicate(
(widget) =>
widget is TextButton &&
widget.child is Text &&
(widget.child as Text?)?.data == S.current.cancel,
);
expect(cancelButton, findsOneWidget);
await tap(cancelButton);
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();
}
Future<void> openSettings() async {
final settingsButton = find.byTooltip(S.current.tooltipOpenSettings);
expect(settingsButton, findsOneWidget);
await tap(settingsButton);
await pumpAndSettle();
expect(find.byType(SettingsScreen), findsOneWidget);
}
}

View file

@ -1,94 +0,0 @@
import 'package:flutter/material.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:integration_test/integration_test.dart';
import 'package:lightmeter/data/models/film.dart';
import 'package:lightmeter/res/dimens.dart';
import 'package:lightmeter/screens/metering/components/shared/readings_container/components/animated_dialog_picker/widget_picker_dialog_animated.dart';
import 'package:lightmeter/screens/metering/components/shared/readings_container/components/reading_value_container/widget_container_reading_value.dart';
import 'mocks/application_mock.dart';
void main() {
runApp(const ApplicationMock(child: AnimatedPickerTest()));
}
void main2() {
final binding = IntegrationTestWidgetsFlutterBinding.ensureInitialized();
group('AnimatedDialogPicker test', () {
testWidgets('Tap on `ReadingValueContainer`, verify opened', (tester) async {
await tester.pumpWidget(const ApplicationMock(child: AnimatedPickerTest()));
expect(find.text('Film'), findsOneWidget);
expect(find.text('None'), findsOneWidget);
await binding.traceAction(
() async {
await tester.tap(find.byType(AnimatedDialogPicker<Film>));
await tester.pumpAndSettle(Dimens.durationL);
},
reportKey: 'dialog_opening_timeline',
);
expect(find.text('Film'), findsNWidgets(3));
expect(find.text('None'), findsNWidgets(3));
});
});
}
class AnimatedPickerTest extends StatefulWidget {
const AnimatedPickerTest({super.key});
@override
State<AnimatedPickerTest> createState() => _AnimatedPickerTestState();
}
class _AnimatedPickerTestState extends State<AnimatedPickerTest> {
Film _selectedFilm = Film.values.first;
@override
Widget build(BuildContext context) {
return Scaffold(
body: Center(
child: _FilmPicker(
values: Film.values,
selectedValue: _selectedFilm,
onChanged: (value) {
setState(() {
_selectedFilm = value;
});
},
),
),
);
}
}
class _FilmPicker extends StatelessWidget {
final List<Film> values;
final Film selectedValue;
final ValueChanged<Film> onChanged;
const _FilmPicker({
required this.values,
required this.selectedValue,
required this.onChanged,
});
@override
Widget build(BuildContext context) {
return AnimatedDialogPicker<Film>(
icon: Icons.camera_roll,
title: "Film",
selectedValue: selectedValue,
values: values,
itemTitleBuilder: (_, value) => Text(value.name.isEmpty ? 'None' : value.name),
onChanged: onChanged,
closedChild: ReadingValueContainer.singleValue(
value: ReadingValue(
label: "Film",
value: selectedValue.name.isEmpty ? 'None' : selectedValue.name,
),
),
);
}
}

View file

@ -42,7 +42,7 @@ dependencies:
dev_dependencies: dev_dependencies:
bloc_test: 9.1.3 bloc_test: 9.1.3
build_runner: ^2.1.7 build_runner: 2.4.6
flutter_driver: flutter_driver:
sdk: flutter sdk: flutter
flutter_launcher_icons: 0.11.0 flutter_launcher_icons: 0.11.0

Binary file not shown.

After

Width:  |  Height:  |  Size: 97 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 106 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 124 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 130 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 110 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 561 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 562 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 128 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 166 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 97 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 106 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 130 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 124 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 110 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 564 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 560 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 128 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 165 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 96 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 106 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 123 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 125 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 110 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 560 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 561 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 127 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 165 KiB

View file

@ -1,26 +1,25 @@
import 'package:flutter_driver/flutter_driver.dart' as driver; import 'package:flutter_driver/flutter_driver.dart' as driver;
import 'package:integration_test/integration_test_driver.dart'; import 'package:integration_test/integration_test_driver.dart';
Future<void> main() { import 'utils/android_camera_permission.dart';
return integrationDriver(
Future<void> main() async {
await grandCameraPermission();
await integrationDriver(
responseDataCallback: (data) async { responseDataCallback: (data) async {
if (data != null) { if (data != null) {
final timeline = for (final String timelineName in data.keys) {
driver.Timeline.fromJson(data['dialog_opening_timeline'] as Map<String, dynamic>); final timeline = driver.Timeline.fromJson(data[timelineName] as Map<String, dynamic>);
final summary = driver.TimelineSummary.summarize(timeline);
// Convert the Timeline into a TimelineSummary that's easier to // Write the entire timeline and summary to disk in a json format.
// read and understand. // This file can be opened in the Chrome browser's tracing tools
final summary = driver.TimelineSummary.summarize(timeline); // found by navigating to chrome://tracing.
await summary.writeTimelineToFile(
// Then, write the entire timeline to disk in a json format. timelineName,
// This file can be opened in the Chrome browser's tracing tools pretty: true,
// found by navigating to chrome://tracing. );
// Optionally, save the summary to disk by setting includeSummary }
// to true
await summary.writeTimelineToFile(
'dialog_opening_timeline(fade)',
pretty: true,
);
} }
}, },
); );

View file

@ -0,0 +1,20 @@
import 'dart:developer';
import 'dart:io';
import 'package:integration_test/integration_test_driver_extended.dart';
import 'utils/android_camera_permission.dart';
Future<void> main() async {
try {
await grandCameraPermission();
await integrationDriver(
onScreenshot: (name, bytes, [args]) async {
final File image = await File('screenshots/$name.png').create(recursive: true);
image.writeAsBytesSync(bytes);
return true;
},
);
} catch (e) {
log('Error occured: $e');
}
}

View file

@ -0,0 +1,34 @@
import 'dart:developer';
import 'dart:io';
Future<void> grandCameraPermission() async {
try {
final bool adbExists = Process.runSync('which', <String>['adb']).exitCode == 0;
if (!adbExists) {
log(r'This test needs ADB to exist on the $PATH. Skipping...');
exit(0);
}
final deviceId = await Process.run('adb', ["-s", 'shell', 'devices']).then((value) {
if (value.stdout is String) {
return RegExp(r"(?:List of devices attached\n)([A-Z0-9]*)(?:\sdevice\n)")
.firstMatch(value.stdout as String)!
.group(1);
}
});
if (deviceId == null) {
log('This test needs at least one device connected');
exit(0);
}
await Process.run('adb', [
"-s",
deviceId, // https://github.com/flutter/flutter/issues/86295#issuecomment-1192766368
'shell',
'pm',
'grant',
'com.vodemn.lightmeter.dev',
'android.permission.CAMERA'
]);
} catch (e) {
log('Error occured: $e');
}
}