Compare commits

..

No commits in common. "a70ce5012a22c507e0e9cae0545d98f9c52d1787" and "df32f6a30e9f04eb37ae5a9d4e8ff7ca9d284279" have entirely different histories.

8 changed files with 245 additions and 214 deletions

View file

@ -65,5 +65,64 @@ jobs:
flutter analyze lib --fatal-infos
run-integration-tests:
uses: ./.github/workflows/run_integration_tests.yml
secrets: inherit
name: Run integration tests
timeout-minutes: 30
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,20 +7,14 @@ name: Run integration tests
on:
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:
run-integration-tests:
analyze_and_test:
name: Run integration tests
timeout-minutes: 30
strategy:
fail-fast: false
matrix:
os: [ubuntu-latest, macos-13]
os: [ubuntu-latest, macos-11]
runs-on: ${{ matrix.os }}
steps:
- uses: actions/checkout@v3
@ -31,30 +25,22 @@ jobs:
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"
- name: Restore constants.dart
run: bash .github/scripts/restore_from_base64.sh "${{ secrets.CONSTANTS }}" "lib/constants.dart"
- uses: subosito/flutter-action@v2
with:
channel: "stable"
flutter-version: "3.10.0"
- name: Build app
- name: Prepare flutter project
run: |
flutter --version
flutter pub get
flutter pub run intl_utils:generate
flutter analyze lib --fatal-infos
if [ "${{ matrix.os }}" == "macos-13" ]
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: Analyze project source
run: flutter analyze lib --fatal-infos
- name: Enable KVM
if: ${{ matrix.os == 'ubuntu-latest' }}
@ -70,14 +56,14 @@ jobs:
api-level: 33
target: default
arch: x86_64
profile: pixel_6
script: flutter test $TARGET $BUILD_ARGS
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' }}
if: ${{ matrix.os == 'macos-11' }}
with:
model: "iPhone 15 Pro"
model: "iPhone 15"
- name: Run tests
if: ${{ matrix.os == 'macos-13' }}
run: flutter test $TARGET $BUILD_ARGS
if: ${{ matrix.os == 'macos-11' }}
run: flutter test integration_test --flavor dev --dart-define cameraStubImage=assets/camera_stub_image.jpg

View file

@ -2,6 +2,7 @@ import 'dart:convert';
import 'package:flutter/material.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:integration_test/integration_test.dart';
import 'package:lightmeter/data/models/ev_source_type.dart';
import 'package:lightmeter/data/models/metering_screen_layout_config.dart';
import 'package:lightmeter/data/shared_prefs_service.dart';
@ -14,7 +15,6 @@ import 'package:lightmeter/screens/metering/components/shared/readings_container
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';
@ -22,164 +22,159 @@ import 'mocks/paid_features_mock.dart';
const _mockPhotoEv100 = 8.3;
@isTestGroup
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 main() {
IntegrationTestWidgetsFlutterBinding.ensureInitialized();
testWidgets(
'Equipment profile picker',
(tester) async {
await tester.pumpApplication(selectedEquipmentProfileId: mockEquipmentProfiles.first.id);
await tester.takePhoto();
_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');
void mockSharedPrefs() {
SharedPreferences.setMockInitialValues({
/// Metering values
UserPreferencesService.evSourceTypeKey: EvSourceType.camera.index,
UserPreferencesService.meteringScreenLayoutKey: json.encode(
{
MeteringScreenLayoutFeature.equipmentProfiles: true,
MeteringScreenLayoutFeature.extremeExposurePairs: true,
MeteringScreenLayoutFeature.filmPicker: true,
}.toJson(),
),
});
}
// 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.',
);
setUp(() {
mockSharedPrefs();
});
// Enable layout feature
await tester.toggleLayoutFeature(S.current.meteringScreenLayoutHintEquipmentProfiles);
_expectPickerTitle<EquipmentProfilePicker>(
S.current.none,
reason: 'Equipment profile must remain unselected when the corresponding layout feature is re-enabled.',
);
},
testWidgets(
'Hide equipment profile picker',
(tester) async {
await tester.pumpApplication(selectedEquipmentProfileId: mockEquipmentProfiles.first.id);
await tester.takePhoto();
_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.',
);
testWidgets(
'Extreme exposure pairs container',
(tester) async {
await tester.pumpApplication();
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"');
// Enable layout feature
await tester.toggleLayoutFeature(S.current.meteringScreenLayoutHintEquipmentProfiles);
_expectPickerTitle<EquipmentProfilePicker>(
S.current.none,
reason: 'Equipment profile must remain unselected when the corresponding layout feature is re-enabled.',
);
},
);
// Disable layout feature
await tester.toggleLayoutFeature(S.current.meteringScreenFeatureExtremeExposurePairs);
expect(
find.byType(ExtremeExposurePairsContainer),
findsNothing,
reason:
'Extreme exposure pairs container must be hidden from the metering screen when the corresponding layout feature is disabled.',
);
_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.',
);
testWidgets(
'Hide extreme exposure pairs container',
(tester) async {
await tester.pumpApplication();
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"');
// Enable layout feature
await tester.toggleLayoutFeature(S.current.meteringScreenFeatureExtremeExposurePairs);
_expectExtremeExposurePairs(
'f/1.0 - 1/320',
'f/45 - 6"',
reason:
'Exposure pairs list must not be affected by the visibility of the extreme exposure pairs container.',
);
},
// Disable layout feature
await tester.toggleLayoutFeature(S.current.meteringScreenFeatureExtremeExposurePairs);
expect(
find.byType(ExtremeExposurePairsContainer),
findsNothing,
reason:
'Extreme exposure pairs container must be hidden from the metering screen when the corresponding layout feature is disabled.',
);
_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.',
);
testWidgets(
'Film picker',
(tester) async {
await tester.pumpApplication(selectedFilm: mockFilms.first);
await tester.takePhoto();
_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"');
// Enable layout feature
await tester.toggleLayoutFeature(S.current.meteringScreenFeatureExtremeExposurePairs);
_expectExtremeExposurePairs(
'f/1.0 - 1/320',
'f/45 - 6"',
reason: 'Exposure pairs list must not be affected by the visibility of the extreme exposure pairs container.',
);
},
);
// Disable layout feature
await tester.toggleLayoutFeature(S.current.meteringScreenFeatureFilmPicker);
expect(
find.byType(FilmPicker),
findsNothing,
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.',
);
testWidgets(
'Hide film picker',
(tester) async {
await tester.pumpApplication(selectedFilm: mockFilms.first);
await tester.takePhoto();
_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"');
// 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.',
);
},
// Disable layout feature
await tester.toggleLayoutFeature(S.current.meteringScreenFeatureFilmPicker);
expect(
find.byType(FilmPicker),
findsNothing,
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,6 +2,7 @@ import 'dart:convert';
import 'package:flutter/material.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:integration_test/integration_test.dart';
import 'package:lightmeter/data/models/ev_source_type.dart';
import 'package:lightmeter/data/models/metering_screen_layout_config.dart';
import 'package:lightmeter/data/shared_prefs_service.dart';
@ -15,30 +16,37 @@ 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/screen_settings.dart';
import 'package:m3_lightmeter_iap/m3_lightmeter_iap.dart';
import 'package:meta/meta.dart';
import 'package:shared_preferences/shared_preferences.dart';
import '../integration_test/utils/widget_tester_actions.dart';
import 'mocks/iap_products_mock.dart';
@isTest
void testPurchases(String description) {
testWidgets(
description,
(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(),
),
});
void main() {
IntegrationTestWidgetsFlutterBinding.ensureInitialized();
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(
'Purchase & refund premium features',
(tester) async {
await tester.pumpApplication(productStatus: IAPProductStatus.purchasable);
await tester.takePhoto();

View file

@ -1,11 +0,0 @@
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,8 +50,6 @@ class CameraContainerBloc extends EvSourceBlocBase<CameraContainerEvent, CameraC
communicationBloc,
const CameraInitState(),
) {
// ignore: avoid_print
print('CameraContainerBloc');
_observer = _WidgetsBindingObserver(_appLifecycleStateObserver);
WidgetsBinding.instance.addObserver(_observer);

View file

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

View file

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