mirror of
https://github.com/vodemn/m3_lightmeter.git
synced 2024-12-03 12:50:40 +00:00
ML-166 Golden tests (#167)
* setup golden toolkit * implemented `GoldenTestApplicationMock` * added devices with dark theme * implemented MeteringScreen golden test * moved platform channel logic to app mock * implemented SettingsScreen golden test * gitignore golden tests failures * Create dart_test.yaml * adjusted `RulerSlider` ticks height * set master screenshots * run golden tests on ci * fixed `LightSensorService` tests * removed golden workflow call from PR check * Update pr_check.yml
This commit is contained in:
parent
2f8bb983d5
commit
27d56d1061
14 changed files with 501 additions and 47 deletions
8
.github/workflows/pr_check.yml
vendored
8
.github/workflows/pr_check.yml
vendored
|
@ -12,9 +12,9 @@ on:
|
||||||
branches: ["main"]
|
branches: ["main"]
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
analyze_and_test:
|
analyze-and-test:
|
||||||
name: Analyze & test
|
name: Analyze & test
|
||||||
runs-on: macos-11
|
runs-on: macos-latest
|
||||||
timeout-minutes: 10
|
timeout-minutes: 10
|
||||||
steps:
|
steps:
|
||||||
- uses: 8BitJonny/gh-get-current-pr@2.2.0
|
- uses: 8BitJonny/gh-get-current-pr@2.2.0
|
||||||
|
@ -54,11 +54,11 @@ jobs:
|
||||||
run: flutter analyze lib --fatal-infos
|
run: flutter analyze lib --fatal-infos
|
||||||
|
|
||||||
- name: Run tests
|
- name: Run tests
|
||||||
run: flutter test
|
run: flutter test --dart-define cameraStubImage=assets/camera_stub_image.jpg
|
||||||
|
|
||||||
- name: Analyze project source with stub
|
- name: Analyze project source with stub
|
||||||
if: steps.override-iap.conclusion != 'success'
|
if: steps.override-iap.conclusion != 'success'
|
||||||
run: |
|
run: |
|
||||||
bash ./.github/scripts/stub_iap.sh
|
bash ./.github/scripts/stub_iap.sh
|
||||||
flutter pub get
|
flutter pub get
|
||||||
flutter analyze lib --fatal-infos
|
flutter analyze lib --fatal-infos
|
||||||
|
|
62
.github/workflows/run_golden_tests.yml
vendored
Normal file
62
.github/workflows/run_golden_tests.yml
vendored
Normal file
|
@ -0,0 +1,62 @@
|
||||||
|
# This workflow uses actions that are not certified by GitHub.
|
||||||
|
# They are provided by a third-party and are governed by
|
||||||
|
# separate terms of service, privacy policy, and support
|
||||||
|
# documentation.
|
||||||
|
|
||||||
|
name: Run golden tests
|
||||||
|
|
||||||
|
on:
|
||||||
|
workflow_dispatch:
|
||||||
|
inputs:
|
||||||
|
update-goldens:
|
||||||
|
type: boolean
|
||||||
|
description: Update goldens
|
||||||
|
default: false
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
run-golden-tests:
|
||||||
|
name: Run golden tests
|
||||||
|
timeout-minutes: 5
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v3
|
||||||
|
with:
|
||||||
|
submodules: recursive
|
||||||
|
|
||||||
|
- uses: subosito/flutter-action@v2
|
||||||
|
with:
|
||||||
|
channel: "stable"
|
||||||
|
flutter-version: "3.13.9"
|
||||||
|
|
||||||
|
- name: Prepare app
|
||||||
|
run: |
|
||||||
|
flutter --version
|
||||||
|
flutter pub get
|
||||||
|
flutter pub run intl_utils:generate
|
||||||
|
|
||||||
|
- name: Run tests
|
||||||
|
env:
|
||||||
|
UPDATE_GOLDENS: ${{inputs.update-goldens && '--update-goldens' || '' }}ƒ
|
||||||
|
run: |
|
||||||
|
goldens=$(find ./test -name "*_golden_test.dart" -print)
|
||||||
|
for f in $goldens; do
|
||||||
|
flutter test "$f"\
|
||||||
|
--dart-define cameraStubImage=assets/camera_stub_image.jpg \
|
||||||
|
$UPDATE_GOLDENS
|
||||||
|
done
|
||||||
|
|
||||||
|
- name: Commit changes
|
||||||
|
if: ${{ inputs.update-goldens }}
|
||||||
|
run: |
|
||||||
|
git config --local user.email "github-actions[bot]@users.noreply.github.com"
|
||||||
|
git config --local user.name "github-actions[bot]"
|
||||||
|
git add -A
|
||||||
|
git commit -m "Updated goldens"
|
||||||
|
|
||||||
|
- name: Push to main
|
||||||
|
if: ${{ inputs.update-goldens }}
|
||||||
|
uses: CasperWA/push-protected@v2
|
||||||
|
with:
|
||||||
|
token: ${{ secrets.PUSH_TO_MAIN_TOKEN }}
|
||||||
|
branch: ${{ github.ref_name }}
|
||||||
|
unprotect_reviews: true
|
1
.gitignore
vendored
1
.gitignore
vendored
|
@ -62,4 +62,5 @@ ios/Runner/GoogleService-Info.plist
|
||||||
|
|
||||||
coverage/
|
coverage/
|
||||||
test/coverage_helper_test.dart
|
test/coverage_helper_test.dart
|
||||||
|
**/failures/*.png
|
||||||
screenshots/generated/
|
screenshots/generated/
|
2
dart_test.yaml
Normal file
2
dart_test.yaml
Normal file
|
@ -0,0 +1,2 @@
|
||||||
|
tags:
|
||||||
|
golden:
|
|
@ -7,7 +7,7 @@ class LightSensorService {
|
||||||
const LightSensorService(this.localPlatform);
|
const LightSensorService(this.localPlatform);
|
||||||
|
|
||||||
Future<bool> hasSensor() async {
|
Future<bool> hasSensor() async {
|
||||||
if (!localPlatform.isAndroid) {
|
if (localPlatform.isIOS) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
|
@ -18,7 +18,7 @@ class LightSensorService {
|
||||||
}
|
}
|
||||||
|
|
||||||
Stream<int> luxStream() {
|
Stream<int> luxStream() {
|
||||||
if (!localPlatform.isAndroid) {
|
if (localPlatform.isIOS) {
|
||||||
return const Stream<int>.empty();
|
return const Stream<int>.empty();
|
||||||
}
|
}
|
||||||
return LightSensor.luxStream();
|
return LightSensor.luxStream();
|
||||||
|
|
|
@ -77,43 +77,48 @@ class _Ruler extends StatelessWidget {
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
|
final mainTicksFontSize = Theme.of(context).textTheme.bodySmall!.fontSize!;
|
||||||
return LayoutBuilder(
|
return LayoutBuilder(
|
||||||
builder: (context, constraints) {
|
builder: (context, constraints) {
|
||||||
final bool showAllMainTicks = Dimens.cameraSliderHandleArea * mainTicksCount <= constraints.maxHeight;
|
final bool showAllMainTicks =
|
||||||
return Column(
|
mainTicksFontSize * mainTicksCount + (1 * mainTicksCount - 1) <= constraints.maxHeight;
|
||||||
crossAxisAlignment: CrossAxisAlignment.end,
|
return Padding(
|
||||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
padding: EdgeInsets.symmetric(vertical: (Dimens.cameraSliderHandleArea - mainTicksFontSize) / 2),
|
||||||
children: List.generate(
|
child: Column(
|
||||||
itemsCount,
|
crossAxisAlignment: CrossAxisAlignment.end,
|
||||||
(index) {
|
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||||
final bool isMainTick = index % 2 == 0.0;
|
children: List.generate(
|
||||||
if (!showAllMainTicks && !isMainTick) {
|
itemsCount,
|
||||||
return const SizedBox();
|
(index) {
|
||||||
}
|
final bool isMainTick = index % 2 == 0.0;
|
||||||
final bool showValue = (index % (showAllMainTicks ? 2 : 4) == 0.0);
|
if (!showAllMainTicks && !isMainTick) {
|
||||||
return SizedBox(
|
return const SizedBox();
|
||||||
height: index == itemsCount - 1 || showValue ? Dimens.cameraSliderHandleArea : 1,
|
}
|
||||||
child: Row(
|
final bool showValue = (index % (showAllMainTicks ? 2 : 4) == 0.0);
|
||||||
mainAxisAlignment: MainAxisAlignment.end,
|
return SizedBox(
|
||||||
children: [
|
height: index == itemsCount - 1 || showValue ? mainTicksFontSize : 1,
|
||||||
if (showValue)
|
child: Row(
|
||||||
Text(
|
mainAxisAlignment: MainAxisAlignment.end,
|
||||||
rulerValueAdapter(index / 2 + min),
|
children: [
|
||||||
style: Theme.of(context).textTheme.bodySmall,
|
if (showValue)
|
||||||
|
Text(
|
||||||
|
rulerValueAdapter(index / 2 + min),
|
||||||
|
style: Theme.of(context).textTheme.bodySmall,
|
||||||
|
),
|
||||||
|
const SizedBox(width: Dimens.grid4),
|
||||||
|
ColoredBox(
|
||||||
|
color: Theme.of(context).colorScheme.onBackground,
|
||||||
|
child: SizedBox(
|
||||||
|
height: 1,
|
||||||
|
width: isMainTick ? Dimens.grid8 : Dimens.grid4,
|
||||||
|
),
|
||||||
),
|
),
|
||||||
const SizedBox(width: Dimens.grid4),
|
],
|
||||||
ColoredBox(
|
),
|
||||||
color: Theme.of(context).colorScheme.onBackground,
|
);
|
||||||
child: SizedBox(
|
},
|
||||||
height: 1,
|
).reversed.toList(),
|
||||||
width: isMainTick ? Dimens.grid8 : Dimens.grid4,
|
),
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
);
|
|
||||||
},
|
|
||||||
).reversed.toList(),
|
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
|
@ -49,6 +49,7 @@ dev_dependencies:
|
||||||
flutter_native_splash: 2.3.5
|
flutter_native_splash: 2.3.5
|
||||||
flutter_test:
|
flutter_test:
|
||||||
sdk: flutter
|
sdk: flutter
|
||||||
|
golden_toolkit: 0.15.0
|
||||||
google_fonts: 3.0.1
|
google_fonts: 3.0.1
|
||||||
integration_test:
|
integration_test:
|
||||||
sdk: flutter
|
sdk: flutter
|
||||||
|
|
|
@ -1,16 +1,23 @@
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_localizations/flutter_localizations.dart';
|
import 'package:flutter_localizations/flutter_localizations.dart';
|
||||||
|
import 'package:flutter_test/flutter_test.dart';
|
||||||
|
import 'package:light_sensor/light_sensor.dart';
|
||||||
|
import 'package:lightmeter/application_wrapper.dart';
|
||||||
|
import 'package:lightmeter/data/models/supported_locale.dart';
|
||||||
|
import 'package:lightmeter/environment.dart';
|
||||||
import 'package:lightmeter/generated/l10n.dart';
|
import 'package:lightmeter/generated/l10n.dart';
|
||||||
|
import 'package:lightmeter/providers/user_preferences_provider.dart';
|
||||||
import 'package:lightmeter/res/theme.dart';
|
import 'package:lightmeter/res/theme.dart';
|
||||||
import 'package:m3_lightmeter_iap/m3_lightmeter_iap.dart';
|
import 'package:m3_lightmeter_iap/m3_lightmeter_iap.dart';
|
||||||
|
|
||||||
|
import '../integration_test/mocks/paid_features_mock.dart';
|
||||||
|
import '../integration_test/utils/platform_channel_mock.dart';
|
||||||
|
|
||||||
/// Provides [MaterialApp] with default theme and "en" localization
|
/// Provides [MaterialApp] with default theme and "en" localization
|
||||||
class WidgetTestApplicationMock extends StatelessWidget {
|
class WidgetTestApplicationMock extends StatelessWidget {
|
||||||
final IAPProductStatus productStatus;
|
|
||||||
final Widget child;
|
final Widget child;
|
||||||
|
|
||||||
const WidgetTestApplicationMock({
|
const WidgetTestApplicationMock({
|
||||||
this.productStatus = IAPProductStatus.purchased,
|
|
||||||
required this.child,
|
required this.child,
|
||||||
super.key,
|
super.key,
|
||||||
});
|
});
|
||||||
|
@ -35,3 +42,86 @@ class WidgetTestApplicationMock extends StatelessWidget {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
class GoldenTestApplicationMock extends StatefulWidget {
|
||||||
|
final IAPProductStatus productStatus;
|
||||||
|
final Widget child;
|
||||||
|
|
||||||
|
const GoldenTestApplicationMock({
|
||||||
|
this.productStatus = IAPProductStatus.purchased,
|
||||||
|
required this.child,
|
||||||
|
super.key,
|
||||||
|
});
|
||||||
|
|
||||||
|
@override
|
||||||
|
State<GoldenTestApplicationMock> createState() => _GoldenTestApplicationMockState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _GoldenTestApplicationMockState extends State<GoldenTestApplicationMock> {
|
||||||
|
@override
|
||||||
|
void initState() {
|
||||||
|
super.initState();
|
||||||
|
TestDefaultBinaryMessengerBinding.instance.defaultBinaryMessenger.setMockMethodCallHandler(
|
||||||
|
LightSensor.methodChannel,
|
||||||
|
(methodCall) async {
|
||||||
|
switch (methodCall.method) {
|
||||||
|
case "sensor":
|
||||||
|
return true;
|
||||||
|
default:
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
);
|
||||||
|
setupLightSensorStreamHandler();
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void dispose() {
|
||||||
|
TestDefaultBinaryMessengerBinding.instance.defaultBinaryMessenger.setMockMethodCallHandler(
|
||||||
|
LightSensor.methodChannel,
|
||||||
|
null,
|
||||||
|
);
|
||||||
|
super.dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return IAPProducts(
|
||||||
|
products: [
|
||||||
|
IAPProduct(
|
||||||
|
storeId: IAPProductType.paidFeatures.storeId,
|
||||||
|
status: widget.productStatus,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
child: ApplicationWrapper(
|
||||||
|
const Environment.dev(),
|
||||||
|
child: MockIAPProviders(
|
||||||
|
equipmentProfiles: mockEquipmentProfiles,
|
||||||
|
selectedEquipmentProfileId: mockEquipmentProfiles.first.id,
|
||||||
|
films: films,
|
||||||
|
child: Builder(
|
||||||
|
builder: (context) {
|
||||||
|
return MaterialApp(
|
||||||
|
debugShowCheckedModeBanner: false,
|
||||||
|
theme: UserPreferencesProvider.themeOf(context),
|
||||||
|
locale: Locale(UserPreferencesProvider.localeOf(context).intlName),
|
||||||
|
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,
|
||||||
|
);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -44,19 +44,19 @@ void main() {
|
||||||
});
|
});
|
||||||
|
|
||||||
test('true - Android', () async {
|
test('true - Android', () async {
|
||||||
when(() => localPlatform.isAndroid).thenReturn(true);
|
when(() => localPlatform.isIOS).thenReturn(false);
|
||||||
setMockSensorAvailability(hasSensor: true);
|
setMockSensorAvailability(hasSensor: true);
|
||||||
expectLater(service.hasSensor(), completion(true));
|
expectLater(service.hasSensor(), completion(true));
|
||||||
});
|
});
|
||||||
|
|
||||||
test('false - Android', () async {
|
test('false - Android', () async {
|
||||||
when(() => localPlatform.isAndroid).thenReturn(true);
|
when(() => localPlatform.isIOS).thenReturn(false);
|
||||||
setMockSensorAvailability(hasSensor: false);
|
setMockSensorAvailability(hasSensor: false);
|
||||||
expectLater(service.hasSensor(), completion(false));
|
expectLater(service.hasSensor(), completion(false));
|
||||||
});
|
});
|
||||||
|
|
||||||
test('false - iOS', () async {
|
test('false - iOS', () async {
|
||||||
when(() => localPlatform.isAndroid).thenReturn(false);
|
when(() => localPlatform.isIOS).thenReturn(true);
|
||||||
expectLater(service.hasSensor(), completion(false));
|
expectLater(service.hasSensor(), completion(false));
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
@ -64,7 +64,7 @@ void main() {
|
||||||
|
|
||||||
group('luxStream', () {
|
group('luxStream', () {
|
||||||
test('Android', () async {
|
test('Android', () async {
|
||||||
when(() => localPlatform.isAndroid).thenReturn(true);
|
when(() => localPlatform.isIOS).thenReturn(false);
|
||||||
final stream = service.luxStream();
|
final stream = service.luxStream();
|
||||||
final List<int> result = [];
|
final List<int> result = [];
|
||||||
final subscription = stream.listen(result.add);
|
final subscription = stream.listen(result.add);
|
||||||
|
@ -77,7 +77,7 @@ void main() {
|
||||||
});
|
});
|
||||||
|
|
||||||
test('iOS', () async {
|
test('iOS', () async {
|
||||||
when(() => localPlatform.isAndroid).thenReturn(false);
|
when(() => localPlatform.isIOS).thenReturn(true);
|
||||||
expect(service.luxStream(), const Stream<int>.empty());
|
expect(service.luxStream(), const Stream<int>.empty());
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
47
test/flutter_test_config.dart
Normal file
47
test/flutter_test_config.dart
Normal file
|
@ -0,0 +1,47 @@
|
||||||
|
import 'dart:async';
|
||||||
|
import 'package:flutter/foundation.dart';
|
||||||
|
import 'package:flutter/widgets.dart';
|
||||||
|
import 'package:golden_toolkit/golden_toolkit.dart';
|
||||||
|
|
||||||
|
Future<void> testExecutable(FutureOr<void> Function() testMain) async {
|
||||||
|
return GoldenToolkit.runWithConfiguration(
|
||||||
|
() async {
|
||||||
|
await loadAppFonts();
|
||||||
|
await testMain();
|
||||||
|
},
|
||||||
|
config: GoldenToolkitConfiguration(
|
||||||
|
defaultDevices: _defaultDevices +
|
||||||
|
_defaultDevices
|
||||||
|
.map(
|
||||||
|
(d) => Device(
|
||||||
|
name: '${d.name} (Dark)',
|
||||||
|
size: d.size,
|
||||||
|
devicePixelRatio: d.devicePixelRatio,
|
||||||
|
safeArea: d.safeArea,
|
||||||
|
brightness: Brightness.dark,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
.toList(),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const _defaultDevices = <Device>[
|
||||||
|
Device(
|
||||||
|
name: 'iPhone 8',
|
||||||
|
size: Size(375, 667),
|
||||||
|
devicePixelRatio: 2.0,
|
||||||
|
),
|
||||||
|
Device(
|
||||||
|
name: 'iPhone 13 Pro',
|
||||||
|
size: Size(390, 844),
|
||||||
|
devicePixelRatio: 3.0,
|
||||||
|
safeArea: EdgeInsets.only(top: 44, bottom: 34),
|
||||||
|
),
|
||||||
|
Device(
|
||||||
|
name: 'iPhone 15 Pro Max',
|
||||||
|
size: Size(430, 932),
|
||||||
|
devicePixelRatio: 3.0,
|
||||||
|
safeArea: EdgeInsets.only(top: 44, bottom: 34),
|
||||||
|
),
|
||||||
|
];
|
BIN
test/screens/metering/goldens/metering_screen.png
Normal file
BIN
test/screens/metering/goldens/metering_screen.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 1.6 MiB |
144
test/screens/metering/screen_metering_golden_test.dart
Normal file
144
test/screens/metering/screen_metering_golden_test.dart
Normal file
|
@ -0,0 +1,144 @@
|
||||||
|
import 'dart:convert';
|
||||||
|
|
||||||
|
import 'package:flutter/widgets.dart';
|
||||||
|
import 'package:flutter_test/flutter_test.dart';
|
||||||
|
import 'package:golden_toolkit/golden_toolkit.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/theme_type.dart';
|
||||||
|
import 'package:lightmeter/data/shared_prefs_service.dart';
|
||||||
|
import 'package:lightmeter/providers/user_preferences_provider.dart';
|
||||||
|
import 'package:lightmeter/screens/metering/components/bottom_controls/components/measure_button/widget_button_measure.dart';
|
||||||
|
import 'package:lightmeter/screens/metering/flow_metering.dart';
|
||||||
|
import 'package:m3_lightmeter_iap/m3_lightmeter_iap.dart';
|
||||||
|
import 'package:shared_preferences/shared_preferences.dart';
|
||||||
|
|
||||||
|
import '../../../integration_test/utils/platform_channel_mock.dart';
|
||||||
|
import '../../application_mock.dart';
|
||||||
|
|
||||||
|
class _MeteringScreenConfig {
|
||||||
|
final IAPProductStatus iapProductStatus;
|
||||||
|
final EvSourceType evSourceType;
|
||||||
|
|
||||||
|
_MeteringScreenConfig(
|
||||||
|
this.iapProductStatus,
|
||||||
|
this.evSourceType,
|
||||||
|
);
|
||||||
|
|
||||||
|
@override
|
||||||
|
String toString() {
|
||||||
|
final buffer = StringBuffer();
|
||||||
|
buffer.write(iapProductStatus.toString().split('.')[1]);
|
||||||
|
buffer.write(' - ');
|
||||||
|
buffer.write(evSourceType.toString().split('.')[1]);
|
||||||
|
return buffer.toString();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
final _testScenarios = [IAPProductStatus.purchased, IAPProductStatus.purchasable].expand(
|
||||||
|
(iapProductStatus) => EvSourceType.values.map(
|
||||||
|
(evSourceType) => _MeteringScreenConfig(iapProductStatus, evSourceType),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
void main() {
|
||||||
|
Future<void> setEvSource(WidgetTester tester, Key scenarioWidgetKey, EvSourceType evSourceType) async {
|
||||||
|
final flow = find.descendant(
|
||||||
|
of: find.byKey(scenarioWidgetKey),
|
||||||
|
matching: find.byType(MeteringFlow),
|
||||||
|
);
|
||||||
|
final BuildContext context = tester.element(flow);
|
||||||
|
if (UserPreferencesProvider.evSourceTypeOf(context) != evSourceType) {
|
||||||
|
UserPreferencesProvider.of(context).toggleEvSourceType();
|
||||||
|
}
|
||||||
|
await tester.pumpAndSettle();
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> setTheme(WidgetTester tester, Key scenarioWidgetKey, ThemeType themeType) async {
|
||||||
|
final flow = find.descendant(
|
||||||
|
of: find.byKey(scenarioWidgetKey),
|
||||||
|
matching: find.byType(MeteringFlow),
|
||||||
|
);
|
||||||
|
final BuildContext context = tester.element(flow);
|
||||||
|
UserPreferencesProvider.of(context).setThemeType(themeType);
|
||||||
|
await tester.pumpAndSettle();
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> takePhoto(WidgetTester tester, Key scenarioWidgetKey) async {
|
||||||
|
final button = find.descendant(
|
||||||
|
of: find.byKey(scenarioWidgetKey),
|
||||||
|
matching: find.byType(MeteringMeasureButton),
|
||||||
|
);
|
||||||
|
await tester.tap(button);
|
||||||
|
await tester.pump(const Duration(seconds: 2)); // wait for circular progress indicator
|
||||||
|
await tester.pump(const Duration(seconds: 1)); // wait for circular progress indicator
|
||||||
|
await tester.pumpAndSettle();
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> toggleIncidentMetering(WidgetTester tester, Key scenarioWidgetKey, double ev) async {
|
||||||
|
final button = find.descendant(
|
||||||
|
of: find.byKey(scenarioWidgetKey),
|
||||||
|
matching: find.byType(MeteringMeasureButton),
|
||||||
|
);
|
||||||
|
await tester.tap(button);
|
||||||
|
await sendMockIncidentEv(ev);
|
||||||
|
await tester.tap(button);
|
||||||
|
await tester.pumpAndSettle();
|
||||||
|
}
|
||||||
|
|
||||||
|
setUpAll(() {
|
||||||
|
SharedPreferences.setMockInitialValues({
|
||||||
|
UserPreferencesService.evSourceTypeKey: EvSourceType.camera.index,
|
||||||
|
UserPreferencesService.meteringScreenLayoutKey: json.encode(
|
||||||
|
{
|
||||||
|
MeteringScreenLayoutFeature.equipmentProfiles: true,
|
||||||
|
MeteringScreenLayoutFeature.extremeExposurePairs: true,
|
||||||
|
MeteringScreenLayoutFeature.filmPicker: true,
|
||||||
|
}.toJson(),
|
||||||
|
),
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
testGoldens(
|
||||||
|
'MeteringScreen golden test',
|
||||||
|
(tester) async {
|
||||||
|
final builder = DeviceBuilder();
|
||||||
|
for (final scenario in _testScenarios) {
|
||||||
|
builder.addScenario(
|
||||||
|
name: scenario.toString(),
|
||||||
|
widget: _MockMeteringFlow(productStatus: scenario.iapProductStatus),
|
||||||
|
onCreate: (scenarioWidgetKey) async {
|
||||||
|
await setEvSource(tester, scenarioWidgetKey, scenario.evSourceType);
|
||||||
|
if (scenarioWidgetKey.toString().contains('Dark')) {
|
||||||
|
await setTheme(tester, scenarioWidgetKey, ThemeType.dark);
|
||||||
|
}
|
||||||
|
if (scenario.evSourceType == EvSourceType.camera) {
|
||||||
|
await takePhoto(tester, scenarioWidgetKey);
|
||||||
|
} else {
|
||||||
|
await toggleIncidentMetering(tester, scenarioWidgetKey, 7.3);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
await tester.pumpDeviceBuilder(builder);
|
||||||
|
await screenMatchesGolden(
|
||||||
|
tester,
|
||||||
|
'metering_screen',
|
||||||
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
class _MockMeteringFlow extends StatelessWidget {
|
||||||
|
final IAPProductStatus productStatus;
|
||||||
|
|
||||||
|
const _MockMeteringFlow({required this.productStatus});
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return GoldenTestApplicationMock(
|
||||||
|
productStatus: productStatus,
|
||||||
|
child: const MeteringFlow(),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
BIN
test/screens/settings/goldens/settings_screen.png
Normal file
BIN
test/screens/settings/goldens/settings_screen.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 500 KiB |
102
test/screens/settings/settings_screen_golden_test.dart
Normal file
102
test/screens/settings/settings_screen_golden_test.dart
Normal file
|
@ -0,0 +1,102 @@
|
||||||
|
import 'dart:convert';
|
||||||
|
|
||||||
|
import 'package:flutter/widgets.dart';
|
||||||
|
import 'package:flutter_test/flutter_test.dart';
|
||||||
|
import 'package:golden_toolkit/golden_toolkit.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/theme_type.dart';
|
||||||
|
import 'package:lightmeter/data/shared_prefs_service.dart';
|
||||||
|
import 'package:lightmeter/providers/user_preferences_provider.dart';
|
||||||
|
import 'package:lightmeter/screens/settings/flow_settings.dart';
|
||||||
|
import 'package:m3_lightmeter_iap/m3_lightmeter_iap.dart';
|
||||||
|
import 'package:package_info_plus/package_info_plus.dart';
|
||||||
|
import 'package:shared_preferences/shared_preferences.dart';
|
||||||
|
|
||||||
|
import '../../application_mock.dart';
|
||||||
|
|
||||||
|
class _SettingsScreenConfig {
|
||||||
|
final IAPProductStatus iapProductStatus;
|
||||||
|
|
||||||
|
_SettingsScreenConfig(this.iapProductStatus);
|
||||||
|
|
||||||
|
@override
|
||||||
|
String toString() {
|
||||||
|
final buffer = StringBuffer();
|
||||||
|
buffer.write(iapProductStatus.toString().split('.')[1]);
|
||||||
|
return buffer.toString();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
final _testScenarios = [IAPProductStatus.purchased, IAPProductStatus.purchasable].map(
|
||||||
|
(iapProductStatus) => _SettingsScreenConfig(iapProductStatus),
|
||||||
|
);
|
||||||
|
|
||||||
|
void main() {
|
||||||
|
Future<void> setTheme(WidgetTester tester, Key scenarioWidgetKey, ThemeType themeType) async {
|
||||||
|
final flow = find.descendant(
|
||||||
|
of: find.byKey(scenarioWidgetKey),
|
||||||
|
matching: find.byType(SettingsFlow),
|
||||||
|
);
|
||||||
|
final BuildContext context = tester.element(flow);
|
||||||
|
UserPreferencesProvider.of(context).setThemeType(themeType);
|
||||||
|
await tester.pumpAndSettle();
|
||||||
|
}
|
||||||
|
|
||||||
|
setUpAll(() {
|
||||||
|
SharedPreferences.setMockInitialValues({
|
||||||
|
UserPreferencesService.evSourceTypeKey: EvSourceType.camera.index,
|
||||||
|
UserPreferencesService.meteringScreenLayoutKey: json.encode(
|
||||||
|
{
|
||||||
|
MeteringScreenLayoutFeature.equipmentProfiles: true,
|
||||||
|
MeteringScreenLayoutFeature.extremeExposurePairs: true,
|
||||||
|
MeteringScreenLayoutFeature.filmPicker: true,
|
||||||
|
}.toJson(),
|
||||||
|
),
|
||||||
|
});
|
||||||
|
PackageInfo.setMockInitialValues(
|
||||||
|
appName: 'Lightmeter',
|
||||||
|
packageName: 'com.vodemn.lightmeter',
|
||||||
|
version: '0.18.0',
|
||||||
|
buildNumber: '48',
|
||||||
|
buildSignature: '',
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
testGoldens(
|
||||||
|
'SettingsScreen golden test',
|
||||||
|
(tester) async {
|
||||||
|
final builder = DeviceBuilder();
|
||||||
|
for (final scenario in _testScenarios) {
|
||||||
|
builder.addScenario(
|
||||||
|
name: scenario.toString(),
|
||||||
|
widget: _MockSettingsFlow(productStatus: scenario.iapProductStatus),
|
||||||
|
onCreate: (scenarioWidgetKey) async {
|
||||||
|
if (scenarioWidgetKey.toString().contains('Dark')) {
|
||||||
|
await setTheme(tester, scenarioWidgetKey, ThemeType.dark);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
await tester.pumpDeviceBuilder(builder);
|
||||||
|
await screenMatchesGolden(
|
||||||
|
tester,
|
||||||
|
'settings_screen',
|
||||||
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
class _MockSettingsFlow extends StatelessWidget {
|
||||||
|
final IAPProductStatus productStatus;
|
||||||
|
|
||||||
|
const _MockSettingsFlow({required this.productStatus});
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return GoldenTestApplicationMock(
|
||||||
|
productStatus: productStatus,
|
||||||
|
child: const SettingsFlow(),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in a new issue