Compare commits

..

4 commits

Author SHA1 Message Date
Vadim
a70ce5012a lints 2024-03-05 11:27:06 +01:00
Vadim
ecf3643ffe debug prints :) 2024-03-05 11:19:52 +01:00
Vadim
80f33d3b4d removed ipa signing for ios test 2024-03-05 10:49:19 +01:00
Vadim
7edd18bb41 combined all tests in one file 2024-03-05 10:42:05 +01:00
8 changed files with 213 additions and 244 deletions

View file

@ -65,64 +65,5 @@ jobs:
flutter analyze lib --fatal-infos flutter analyze lib --fatal-infos
run-integration-tests: run-integration-tests:
name: Run integration tests uses: ./.github/workflows/run_integration_tests.yml
timeout-minutes: 30 secrets: inherit
strategy:
fail-fast: false
matrix:
os: [ubuntu-latest, macos-13]
runs-on: ${{ matrix.os }}
steps:
- uses: actions/checkout@v3
with:
submodules: recursive
- name: Override iap package with stub
id: override-iap
run: bash ./.github/scripts/stub_iap.sh
- name: Restore secrets
run: |
bash .github/scripts/restore_from_base64.sh "${{ secrets.CONSTANTS }}" "lib/constants.dart"
bash .github/scripts/restore_from_base64.sh "${{ secrets.GOOGLE_SERVICES_JSON_ANDROID }}" "android/app/google-services.json"
bash .github/scripts/restore_from_base64.sh "${{ secrets.GOOGLE_SERVICES_JSON_IOS }}" "ios/Runner/GoogleService-Info.plist"
- uses: subosito/flutter-action@v2
with:
channel: "stable"
flutter-version: "3.10.0"
- name: Prepare flutter project
run: |
flutter --version
flutter pub get
flutter pub run intl_utils:generate
- name: Analyze project source
run: flutter analyze lib --fatal-infos
- name: Enable KVM
if: ${{ matrix.os == 'ubuntu-latest' }}
run: |
echo 'KERNEL=="kvm", GROUP="kvm", MODE="0666", OPTIONS+="static_node=kvm"' | sudo tee /etc/udev/rules.d/99-kvm4all.rules
sudo udevadm control --reload-rules
sudo udevadm trigger --name-match=kvm
- name: Launch Android simulator & Run tests
if: ${{ matrix.os == 'ubuntu-latest' }}
uses: reactivecircus/android-emulator-runner@v2
with:
api-level: 33
target: default
arch: x86_64
profile: pixel_6
script: flutter test integration_test --flavor dev --dart-define cameraStubImage=assets/camera_stub_image.jpg
- name: Launch iOS simulator
uses: futureware-tech/simulator-action@v3
if: ${{ matrix.os == 'macos-13' }}
with:
model: "iPhone 15 Pro"
- name: Run tests
if: ${{ matrix.os == 'macos-13' }}
run: flutter test integration_test --flavor dev --dart-define cameraStubImage=assets/camera_stub_image.jpg

View file

@ -7,14 +7,20 @@ name: Run integration tests
on: on:
workflow_dispatch: workflow_dispatch:
workflow_call:
env:
BUILD_ARGS: --flavor dev --dart-define cameraStubImage=assets/camera_stub_image.jpg
TARGET: integration_test/run_all_tests.dart
jobs: jobs:
analyze_and_test: run-integration-tests:
name: Run integration tests name: Run integration tests
timeout-minutes: 30 timeout-minutes: 30
strategy: strategy:
fail-fast: false
matrix: matrix:
os: [ubuntu-latest, macos-11] os: [ubuntu-latest, macos-13]
runs-on: ${{ matrix.os }} runs-on: ${{ matrix.os }}
steps: steps:
- uses: actions/checkout@v3 - uses: actions/checkout@v3
@ -25,22 +31,30 @@ jobs:
id: override-iap id: override-iap
run: bash ./.github/scripts/stub_iap.sh run: bash ./.github/scripts/stub_iap.sh
- name: Restore constants.dart - name: Restore secrets
run: bash .github/scripts/restore_from_base64.sh "${{ secrets.CONSTANTS }}" "lib/constants.dart" run: |
bash .github/scripts/restore_from_base64.sh "${{ secrets.CONSTANTS }}" "lib/constants.dart"
bash .github/scripts/restore_from_base64.sh "${{ secrets.GOOGLE_SERVICES_JSON_ANDROID }}" "android/app/google-services.json"
bash .github/scripts/restore_from_base64.sh "${{ secrets.GOOGLE_SERVICES_JSON_IOS }}" "ios/Runner/GoogleService-Info.plist"
- uses: subosito/flutter-action@v2 - uses: subosito/flutter-action@v2
with: with:
channel: "stable" channel: "stable"
flutter-version: "3.10.0" flutter-version: "3.10.0"
- name: Prepare flutter project - name: Build app
run: | run: |
flutter --version flutter --version
flutter pub get flutter pub get
flutter pub run intl_utils:generate flutter pub run intl_utils:generate
flutter analyze lib --fatal-infos
- name: Analyze project source if [ "${{ matrix.os }}" == "macos-13" ]
run: flutter analyze lib --fatal-infos then
cd ios
pod install
cd ..
fi
flutter build ${{ matrix.os == 'ubuntu-latest' && 'apk --debug' || 'ios --no-codesign --simulator --debug' }} $BUILD_ARGS -t $TARGET
- name: Enable KVM - name: Enable KVM
if: ${{ matrix.os == 'ubuntu-latest' }} if: ${{ matrix.os == 'ubuntu-latest' }}
@ -56,14 +70,14 @@ jobs:
api-level: 33 api-level: 33
target: default target: default
arch: x86_64 arch: x86_64
profile: Pixel 6 profile: pixel_6
script: flutter test integration_test --flavor dev --dart-define cameraStubImage=assets/camera_stub_image.jpg script: flutter test $TARGET $BUILD_ARGS
- name: Launch iOS simulator - name: Launch iOS simulator
uses: futureware-tech/simulator-action@v3 uses: futureware-tech/simulator-action@v3
if: ${{ matrix.os == 'macos-11' }} if: ${{ matrix.os == 'macos-13' }}
with: with:
model: "iPhone 15" model: "iPhone 15 Pro"
- name: Run tests - name: Run tests
if: ${{ matrix.os == 'macos-11' }} if: ${{ matrix.os == 'macos-13' }}
run: flutter test integration_test --flavor dev --dart-define cameraStubImage=assets/camera_stub_image.jpg run: flutter test $TARGET $BUILD_ARGS

View file

@ -2,7 +2,6 @@ import 'dart:convert';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_test/flutter_test.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/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';
@ -15,6 +14,7 @@ import 'package:lightmeter/screens/metering/components/shared/readings_container
import 'package:lightmeter/screens/metering/screen_metering.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:m3_lightmeter_resources/m3_lightmeter_resources.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';
@ -22,159 +22,164 @@ import 'mocks/paid_features_mock.dart';
const _mockPhotoEv100 = 8.3; const _mockPhotoEv100 = 8.3;
void main() { @isTestGroup
IntegrationTestWidgetsFlutterBinding.ensureInitialized(); void testToggleLayoutFeatures(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(),
),
});
});
void mockSharedPrefs() { testWidgets(
SharedPreferences.setMockInitialValues({ 'Equipment profile picker',
/// Metering values (tester) async {
UserPreferencesService.evSourceTypeKey: EvSourceType.camera.index, await tester.pumpApplication(selectedEquipmentProfileId: mockEquipmentProfiles.first.id);
UserPreferencesService.meteringScreenLayoutKey: json.encode( await tester.takePhoto();
{ _expectPickerTitle<EquipmentProfilePicker>(mockEquipmentProfiles.first.name);
MeteringScreenLayoutFeature.equipmentProfiles: true, _expectExtremeExposurePairs('f/1.8 - 1/100', 'f/16 - 1/1.3');
MeteringScreenLayoutFeature.extremeExposurePairs: true, _expectExposurePairsListItem(tester, 'f/1.8', '1/100');
MeteringScreenLayoutFeature.filmPicker: true, await tester.scrollToTheLastExposurePair(mockEquipmentProfiles.first);
}.toJson(), _expectExposurePairsListItem(tester, 'f/16', '1/1.3');
),
});
}
setUp(() { // Disable layout feature
mockSharedPrefs(); await tester.toggleLayoutFeature(S.current.meteringScreenLayoutHintEquipmentProfiles);
}); expect(
find.byType(EquipmentProfilePicker),
findsNothing,
reason:
'Equipment profile picker must be hidden from the metering screen when the corresponding layout feature is disabled.',
);
_expectExtremeExposurePairs(
'f/1.0 - 1/320',
'f/45 - 6"',
reason: 'Aperture and shutter speed ranges must be reset to default values when equipment profile is reset',
);
_expectExposurePairsListItem(
tester,
'f/1.0',
'1/320',
reason:
'Aperture and shutter speed ranges must be reset to default values when equipment profile is reset.',
);
await tester.scrollToTheLastExposurePair();
_expectExposurePairsListItem(
tester,
'f/45',
'6"',
reason:
'Aperture and shutter speed ranges must be reset to default values when equipment profile is reset.',
);
testWidgets( // Enable layout feature
'Hide equipment profile picker', await tester.toggleLayoutFeature(S.current.meteringScreenLayoutHintEquipmentProfiles);
(tester) async { _expectPickerTitle<EquipmentProfilePicker>(
await tester.pumpApplication(selectedEquipmentProfileId: mockEquipmentProfiles.first.id); S.current.none,
await tester.takePhoto(); reason: 'Equipment profile must remain unselected when the corresponding layout feature is re-enabled.',
_expectPickerTitle<EquipmentProfilePicker>(mockEquipmentProfiles.first.name); );
_expectExtremeExposurePairs('f/1.8 - 1/100', 'f/16 - 1/1.3'); },
_expectExposurePairsListItem(tester, 'f/1.8', '1/100');
await tester.scrollToTheLastExposurePair(mockEquipmentProfiles.first);
_expectExposurePairsListItem(tester, 'f/16', '1/1.3');
// Disable layout feature
await tester.toggleLayoutFeature(S.current.meteringScreenLayoutHintEquipmentProfiles);
expect(
find.byType(EquipmentProfilePicker),
findsNothing,
reason:
'Equipment profile picker must be hidden from the metering screen when the corresponding layout feature is disabled.',
);
_expectExtremeExposurePairs(
'f/1.0 - 1/320',
'f/45 - 6"',
reason: 'Aperture and shutter speed ranges must be reset to default values when equipment profile is reset',
);
_expectExposurePairsListItem(
tester,
'f/1.0',
'1/320',
reason: 'Aperture and shutter speed ranges must be reset to default values when equipment profile is reset.',
);
await tester.scrollToTheLastExposurePair();
_expectExposurePairsListItem(
tester,
'f/45',
'6"',
reason: 'Aperture and shutter speed ranges must be reset to default values when equipment profile is reset.',
); );
// Enable layout feature testWidgets(
await tester.toggleLayoutFeature(S.current.meteringScreenLayoutHintEquipmentProfiles); 'Extreme exposure pairs container',
_expectPickerTitle<EquipmentProfilePicker>( (tester) async {
S.current.none, await tester.pumpApplication();
reason: 'Equipment profile must remain unselected when the corresponding layout feature is re-enabled.', await tester.takePhoto();
); _expectExtremeExposurePairs('f/1.0 - 1/320', 'f/45 - 6"');
}, _expectExposurePairsListItem(tester, 'f/1.0', '1/320');
); await tester.scrollToTheLastExposurePair();
_expectExposurePairsListItem(tester, 'f/45', '6"');
testWidgets( // Disable layout feature
'Hide extreme exposure pairs container', await tester.toggleLayoutFeature(S.current.meteringScreenFeatureExtremeExposurePairs);
(tester) async { expect(
await tester.pumpApplication(); find.byType(ExtremeExposurePairsContainer),
await tester.takePhoto(); findsNothing,
_expectExtremeExposurePairs('f/1.0 - 1/320', 'f/45 - 6"'); reason:
_expectExposurePairsListItem(tester, 'f/1.0', '1/320'); 'Extreme exposure pairs container must be hidden from the metering screen when the corresponding layout feature is disabled.',
await tester.scrollToTheLastExposurePair(); );
_expectExposurePairsListItem(tester, 'f/45', '6"'); _expectExposurePairsListItem(
tester,
'f/1.0',
'1/320',
reason:
'Exposure pairs list must not be affected by the visibility of the extreme exposure pairs container.',
);
await tester.scrollToTheLastExposurePair();
_expectExposurePairsListItem(
tester,
'f/45',
'6"',
reason:
'Exposure pairs list must not be affected by the visibility of the extreme exposure pairs container.',
);
// Disable layout feature // Enable layout feature
await tester.toggleLayoutFeature(S.current.meteringScreenFeatureExtremeExposurePairs); await tester.toggleLayoutFeature(S.current.meteringScreenFeatureExtremeExposurePairs);
expect( _expectExtremeExposurePairs(
find.byType(ExtremeExposurePairsContainer), 'f/1.0 - 1/320',
findsNothing, 'f/45 - 6"',
reason: reason:
'Extreme exposure pairs container must be hidden from the metering screen when the corresponding layout feature is disabled.', 'Exposure pairs list must not be affected by the visibility of the extreme exposure pairs container.',
); );
_expectExposurePairsListItem( },
tester,
'f/1.0',
'1/320',
reason: 'Exposure pairs list must not be affected by the visibility of the extreme exposure pairs container.',
);
await tester.scrollToTheLastExposurePair();
_expectExposurePairsListItem(
tester,
'f/45',
'6"',
reason: 'Exposure pairs list must not be affected by the visibility of the extreme exposure pairs container.',
); );
// Enable layout feature testWidgets(
await tester.toggleLayoutFeature(S.current.meteringScreenFeatureExtremeExposurePairs); 'Film picker',
_expectExtremeExposurePairs( (tester) async {
'f/1.0 - 1/320', await tester.pumpApplication(selectedFilm: mockFilms.first);
'f/45 - 6"', await tester.takePhoto();
reason: 'Exposure pairs list must not be affected by the visibility of the extreme exposure pairs container.', _expectPickerTitle<FilmPicker>(mockFilms.first.name);
); _expectExtremeExposurePairs('f/1.0 - 1/320', 'f/45 - 12"');
}, _expectExposurePairsListItem(tester, 'f/1.0', '1/320');
); await tester.scrollToTheLastExposurePair();
_expectExposurePairsListItem(tester, 'f/45', '12"');
testWidgets( // Disable layout feature
'Hide film picker', await tester.toggleLayoutFeature(S.current.meteringScreenFeatureFilmPicker);
(tester) async { expect(
await tester.pumpApplication(selectedFilm: mockFilms.first); find.byType(FilmPicker),
await tester.takePhoto(); findsNothing,
_expectPickerTitle<FilmPicker>(mockFilms.first.name); reason:
_expectExtremeExposurePairs('f/1.0 - 1/320', 'f/45 - 12"'); 'Film picker must be hidden from the metering screen when the corresponding layout feature is disabled.',
_expectExposurePairsListItem(tester, 'f/1.0', '1/320'); );
await tester.scrollToTheLastExposurePair(); _expectExtremeExposurePairs(
_expectExposurePairsListItem(tester, 'f/45', '12"'); 'f/1.0 - 1/320',
'f/45 - 6"',
reason: 'Shutter speed must not be affected by reciprocity when film is discarded.',
);
_expectExposurePairsListItem(
tester,
'f/1.0',
'1/320',
reason: 'Shutter speed must not be affected by reciprocity when film is discarded.',
);
await tester.scrollToTheLastExposurePair();
_expectExposurePairsListItem(
tester,
'f/45',
'6"',
reason: 'Shutter speed must not be affected by reciprocity when film is discarded.',
);
// Disable layout feature // Enable layout feature
await tester.toggleLayoutFeature(S.current.meteringScreenFeatureFilmPicker); await tester.toggleLayoutFeature(S.current.meteringScreenFeatureFilmPicker);
expect( _expectPickerTitle<FilmPicker>(
find.byType(FilmPicker), S.current.none,
findsNothing, reason: 'Film must remain unselected when the corresponding layout feature is re-enabled.',
reason: );
'Film picker must be hidden from the metering screen when the corresponding layout feature is disabled.', },
);
_expectExtremeExposurePairs(
'f/1.0 - 1/320',
'f/45 - 6"',
reason: 'Shutter speed must not be affected by reciprocity when film is discarded.',
);
_expectExposurePairsListItem(
tester,
'f/1.0',
'1/320',
reason: 'Shutter speed must not be affected by reciprocity when film is discarded.',
);
await tester.scrollToTheLastExposurePair();
_expectExposurePairsListItem(
tester,
'f/45',
'6"',
reason: 'Shutter speed must not be affected by reciprocity when film is discarded.',
);
// Enable layout feature
await tester.toggleLayoutFeature(S.current.meteringScreenFeatureFilmPicker);
_expectPickerTitle<FilmPicker>(
S.current.none,
reason: 'Film must remain unselected when the corresponding layout feature is re-enabled.',
); );
}, },
); );

View file

@ -2,7 +2,6 @@ import 'dart:convert';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_test/flutter_test.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/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';
@ -16,37 +15,30 @@ import 'package:lightmeter/screens/metering/components/shared/readings_container
import 'package:lightmeter/screens/settings/components/shared/disable/widget_disable.dart'; import 'package:lightmeter/screens/settings/components/shared/disable/widget_disable.dart';
import 'package:lightmeter/screens/settings/screen_settings.dart'; import 'package:lightmeter/screens/settings/screen_settings.dart';
import 'package:m3_lightmeter_iap/m3_lightmeter_iap.dart'; import 'package:m3_lightmeter_iap/m3_lightmeter_iap.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/iap_products_mock.dart'; import 'mocks/iap_products_mock.dart';
void main() { @isTest
IntegrationTestWidgetsFlutterBinding.ensureInitialized(); void testPurchases(String description) {
void mockSharedPrefs() {
// ignore: invalid_use_of_visible_for_testing_member
SharedPreferences.setMockInitialValues({
/// Metering values
UserPreferencesService.evSourceTypeKey: EvSourceType.camera.index,
UserPreferencesService.showEv100Key: true,
UserPreferencesService.meteringScreenLayoutKey: json.encode(
{
MeteringScreenLayoutFeature.equipmentProfiles: true,
MeteringScreenLayoutFeature.extremeExposurePairs: true,
MeteringScreenLayoutFeature.filmPicker: true,
}.toJson(),
),
});
}
setUpAll(() {
mockSharedPrefs();
});
testWidgets( testWidgets(
'Purchase & refund premium features', description,
(tester) async { (tester) async {
SharedPreferences.setMockInitialValues({
/// Metering values
UserPreferencesService.evSourceTypeKey: EvSourceType.camera.index,
UserPreferencesService.showEv100Key: true,
UserPreferencesService.meteringScreenLayoutKey: json.encode(
{
MeteringScreenLayoutFeature.equipmentProfiles: true,
MeteringScreenLayoutFeature.extremeExposurePairs: true,
MeteringScreenLayoutFeature.filmPicker: true,
}.toJson(),
),
});
await tester.pumpApplication(productStatus: IAPProductStatus.purchasable); await tester.pumpApplication(productStatus: IAPProductStatus.purchasable);
await tester.takePhoto(); await tester.takePhoto();

View file

@ -0,0 +1,11 @@
import 'package:integration_test/integration_test.dart';
import 'metering_screen_layout_test.dart';
import 'purchases_test.dart';
void main() {
IntegrationTestWidgetsFlutterBinding.ensureInitialized();
testPurchases('Purchase & refund premium features');
testToggleLayoutFeatures('Toggle metering screen layout features');
}

View file

@ -50,6 +50,8 @@ class CameraContainerBloc extends EvSourceBlocBase<CameraContainerEvent, CameraC
communicationBloc, communicationBloc,
const CameraInitState(), const CameraInitState(),
) { ) {
// ignore: avoid_print
print('CameraContainerBloc');
_observer = _WidgetsBindingObserver(_appLifecycleStateObserver); _observer = _WidgetsBindingObserver(_appLifecycleStateObserver);
WidgetsBinding.instance.addObserver(_observer); WidgetsBinding.instance.addObserver(_observer);

View file

@ -5,7 +5,10 @@ class MockCameraContainerBloc extends CameraContainerBloc {
super._meteringInteractor, super._meteringInteractor,
super.communicationBloc, super.communicationBloc,
super._analytics, super._analytics,
); ) {
// ignore: avoid_print
print('MockCameraContainerBloc');
}
@override @override
Future<void> _onRequestPermission(_, Emitter emit) async { Future<void> _onRequestPermission(_, Emitter emit) async {

View file

@ -52,6 +52,7 @@ dev_dependencies:
integration_test: integration_test:
sdk: flutter sdk: flutter
lint: 2.1.2 lint: 2.1.2
meta: 1.9.1
mocktail: 0.3.0 mocktail: 0.3.0
test: 1.24.1 test: 1.24.1