Compare commits

...

4 commits

Author SHA1 Message Date
Vadim
2a7463184b EquipmentProfileChangedEvent tests 2023-06-15 15:58:48 +02:00
Vadim
dca34f6721 improved CameraContainerBloc test coverage 2023-06-15 15:15:41 +02:00
Vadim
e230a05b5f added test coverage script 2023-06-15 15:15:24 +02:00
Vadim
35af009f99 renamed test groups 2023-06-15 14:57:03 +02:00
7 changed files with 207 additions and 39 deletions

2
.gitignore vendored
View file

@ -58,3 +58,5 @@ android/app/google-services.json
ios/firebase_app_id_file.json ios/firebase_app_id_file.json
ios/Runner/GoogleService-Info.plist ios/Runner/GoogleService-Info.plist
lib/firebase_options.dart lib/firebase_options.dart
coverage/

View file

@ -90,7 +90,7 @@ class MeteringBloc extends Bloc<MeteringEvent, MeteringState> {
iso = event.equipmentProfileData.isoValues.first; iso = event.equipmentProfileData.isoValues.first;
_meteringInteractor.film = Film.values.first; _meteringInteractor.film = Film.values.first;
film = Film.values.first; film = Film.values.first;
willUpdateMeasurements &= true; willUpdateMeasurements = true;
} }
/// The same for ND filter /// The same for ND filter
@ -98,7 +98,7 @@ class MeteringBloc extends Bloc<MeteringEvent, MeteringState> {
if (!event.equipmentProfileData.ndValues.any((v) => state.nd.value == v.value)) { if (!event.equipmentProfileData.ndValues.any((v) => state.nd.value == v.value)) {
_meteringInteractor.ndFilter = event.equipmentProfileData.ndValues.first; _meteringInteractor.ndFilter = event.equipmentProfileData.ndValues.first;
nd = event.equipmentProfileData.ndValues.first; nd = event.equipmentProfileData.ndValues.first;
willUpdateMeasurements &= true; willUpdateMeasurements = true;
} }
if (willUpdateMeasurements) { if (willUpdateMeasurements) {

View file

@ -50,7 +50,7 @@ void main() {
}); });
group( group(
'`MeasureEvent` tests', '`MeasureEvent`',
() { () {
blocTest<MeteringBloc, MeteringState>( blocTest<MeteringBloc, MeteringState>(
'`MeasureEvent` -> success', '`MeasureEvent` -> success',
@ -144,7 +144,7 @@ void main() {
); );
group( group(
'`IsoChangedEvent` tests', '`IsoChangedEvent`',
() { () {
blocTest<MeteringBloc, MeteringState>( blocTest<MeteringBloc, MeteringState>(
'Pick different ISO (ev100 != null)', 'Pick different ISO (ev100 != null)',
@ -263,7 +263,7 @@ void main() {
); );
group( group(
'`NdChangedEvent` tests', '`NdChangedEvent`',
() { () {
blocTest<MeteringBloc, MeteringState>( blocTest<MeteringBloc, MeteringState>(
'Pick different ND (ev100 != null)', 'Pick different ND (ev100 != null)',
@ -378,7 +378,7 @@ void main() {
); );
group( group(
'`FilmChangedEvent` tests', '`FilmChangedEvent`',
() { () {
blocTest<MeteringBloc, MeteringState>( blocTest<MeteringBloc, MeteringState>(
'Pick different film with different ISO', 'Pick different film with different ISO',
@ -485,11 +485,126 @@ void main() {
}, },
); );
// TODO(vodemn): when this feautre is enabled group(
// group( '`EquipmentProfileChangedEvent`',
// '`EquipmentProfileChangedEvent` tests', () {
// () { final reducedProfile = EquipmentProfileData(
// id: '0',
// }, name: 'Reduced',
// ); apertureValues: ApertureValue.values,
ndValues: NdValue.values.getRange(0, 3).toList(),
shutterSpeedValues: ShutterSpeedValue.values,
isoValues: IsoValue.values.getRange(4, 23).toList(),
);
blocTest<MeteringBloc, MeteringState>(
'New profile has current ISO & ND',
build: () => bloc,
seed: () => MeteringDataState(
ev100: 1.0,
film: Film.values[1],
iso: const IsoValue(100, StopType.full),
nd: NdValue.values.first,
isMetering: false,
),
act: (bloc) async {
bloc.add(EquipmentProfileChangedEvent(reducedProfile));
},
verify: (_) {
verifyNever(() => meteringInteractor.film = const Film.other());
verifyNever(() => meteringInteractor.iso = reducedProfile.isoValues.first);
verifyNever(() => meteringInteractor.ndFilter = reducedProfile.ndValues.first);
verifyNever(() => meteringInteractor.responseVibration());
},
expect: () => [],
);
blocTest<MeteringBloc, MeteringState>(
'New profile has new ISO & current ND',
build: () => bloc,
seed: () => MeteringDataState(
ev100: 1.0,
film: Film.values[1],
iso: IsoValue.values[2],
nd: NdValue.values.first,
isMetering: false,
),
act: (bloc) async {
bloc.add(EquipmentProfileChangedEvent(reducedProfile));
},
verify: (_) {
verify(() => meteringInteractor.film = const Film.other()).called(1);
verify(() => meteringInteractor.iso = reducedProfile.isoValues.first).called(1);
verifyNever(() => meteringInteractor.ndFilter = reducedProfile.ndValues.first);
verify(() => meteringInteractor.responseVibration()).called(1);
},
expect: () => [
isA<MeteringDataState>()
.having((state) => state.ev100, 'ev100', 1.0)
.having((state) => state.film, 'film', const Film.other())
.having((state) => state.iso, 'iso', reducedProfile.isoValues.first)
.having((state) => state.nd, 'nd', NdValue.values.first)
.having((state) => state.isMetering, 'isMetering', false),
],
);
blocTest<MeteringBloc, MeteringState>(
'New profile has current ISO & new ND',
build: () => bloc,
seed: () => MeteringDataState(
ev100: 1.0,
film: Film.values[1],
iso: const IsoValue(100, StopType.full),
nd: NdValue.values[4],
isMetering: false,
),
act: (bloc) async {
bloc.add(EquipmentProfileChangedEvent(reducedProfile));
},
verify: (_) {
verifyNever(() => meteringInteractor.film = const Film.other());
verifyNever(() => meteringInteractor.iso = reducedProfile.isoValues.first);
verify(() => meteringInteractor.ndFilter = reducedProfile.ndValues.first).called(1);
verify(() => meteringInteractor.responseVibration()).called(1);
},
expect: () => [
isA<MeteringDataState>()
.having((state) => state.ev100, 'ev100', 1.0)
.having((state) => state.film, 'film', Film.values[1])
.having((state) => state.iso, 'iso', const IsoValue(100, StopType.full))
.having((state) => state.nd, 'nd', reducedProfile.ndValues.first)
.having((state) => state.isMetering, 'isMetering', false),
],
);
blocTest<MeteringBloc, MeteringState>(
'New profile has new ISO & new ND',
build: () => bloc,
seed: () => MeteringDataState(
ev100: 1.0,
film: Film.values[1],
iso: IsoValue.values[2],
nd: NdValue.values[4],
isMetering: false,
),
act: (bloc) async {
bloc.add(EquipmentProfileChangedEvent(reducedProfile));
},
verify: (_) {
verify(() => meteringInteractor.film = const Film.other()).called(1);
verify(() => meteringInteractor.iso = reducedProfile.isoValues.first).called(1);
verify(() => meteringInteractor.ndFilter = reducedProfile.ndValues.first).called(1);
verify(() => meteringInteractor.responseVibration()).called(1);
},
expect: () => [
isA<MeteringDataState>()
.having((state) => state.ev100, 'ev100', 1.0)
.having((state) => state.film, 'film', const Film.other())
.having((state) => state.iso, 'iso', reducedProfile.isoValues.first)
.having((state) => state.nd, 'nd', reducedProfile.ndValues.first)
.having((state) => state.isMetering, 'isMetering', false),
],
);
},
);
} }

View file

@ -16,7 +16,7 @@ void main() {
}); });
group( group(
'`MeasureEvent` tests', '`MeasureEvent`',
() { () {
blocTest<MeteringCommunicationBloc, MeteringCommunicationState>( blocTest<MeteringCommunicationBloc, MeteringCommunicationState>(
'Multiple consequtive measure events', 'Multiple consequtive measure events',
@ -60,7 +60,7 @@ void main() {
); );
group( group(
'`MeteringInProgressEvent` tests', '`MeteringInProgressEvent`',
() { () {
blocTest<MeteringCommunicationBloc, MeteringCommunicationState>( blocTest<MeteringCommunicationBloc, MeteringCommunicationState>(
'Multiple consequtive in progress events', 'Multiple consequtive in progress events',
@ -83,7 +83,7 @@ void main() {
); );
group( group(
'`MeteringEndedEvent` tests', '`MeteringEndedEvent`',
() { () {
blocTest<MeteringCommunicationBloc, MeteringCommunicationState>( blocTest<MeteringCommunicationBloc, MeteringCommunicationState>(
'Multiple consequtive ended events', 'Multiple consequtive ended events',

View file

@ -41,10 +41,25 @@ void main() {
"sensorOrientation": 0, "sensorOrientation": 0,
}, },
]; ];
Future<Object?>? cameraMethodCallSuccessHandler(MethodCall methodCall) async { const frontCameras = [
{
"name": "front",
"lensFacing": "front",
"sensorOrientation": 0,
},
{
"name": "front2",
"lensFacing": "front",
"sensorOrientation": 0,
},
];
Future<Object?>? cameraMethodCallSuccessHandler(
MethodCall methodCall, {
List<Map<String, Object>> cameras = availableCameras,
}) async {
switch (methodCall.method) { switch (methodCall.method) {
case "availableCameras": case "availableCameras":
return availableCameras; return cameras;
case "create": case "create":
return {"cameraId": 1}; return {"cameraId": 1};
case "initialize": case "initialize":
@ -119,7 +134,7 @@ void main() {
}); });
group( group(
'`RequestPermissionEvent` tests', '`RequestPermissionEvent`',
() { () {
blocTest<CameraContainerBloc, CameraContainerState>( blocTest<CameraContainerBloc, CameraContainerState>(
'Request denied', 'Request denied',
@ -168,15 +183,33 @@ void main() {
verify(() => meteringInteractor.requestPermission()).called(1); verify(() => meteringInteractor.requestPermission()).called(1);
verify(() => meteringInteractor.checkCameraPermission()).called(1); verify(() => meteringInteractor.checkCameraPermission()).called(1);
}, },
expect: () => [ expect: () => initializedStateSequence,
...initializedStateSequence,
],
); );
}, },
); );
group( group(
'`InitializeEvent`/`DeinitializeEvent` tests', '`OpenAppSettingsEvent`',
() {
blocTest<CameraContainerBloc, CameraContainerState>(
'App settings opened',
setUp: () {
when(() => meteringInteractor.openAppSettings()).thenAnswer((_) {});
},
build: () => bloc,
act: (bloc) async {
bloc.add(const OpenAppSettingsEvent());
},
verify: (_) {
verify(() => meteringInteractor.openAppSettings()).called(1);
},
expect: () => [],
);
},
);
group(
'`InitializeEvent`/`DeinitializeEvent`',
() { () {
blocTest<CameraContainerBloc, CameraContainerState>( blocTest<CameraContainerBloc, CameraContainerState>(
'No cameras detected error', 'No cameras detected error',
@ -185,14 +218,7 @@ void main() {
TestDefaultBinaryMessengerBinding.instance.defaultBinaryMessenger TestDefaultBinaryMessengerBinding.instance.defaultBinaryMessenger
.setMockMethodCallHandler( .setMockMethodCallHandler(
cameraMethodChannel, cameraMethodChannel,
(methodCall) async { (methodCall) async => cameraMethodCallSuccessHandler(methodCall, cameras: const []),
switch (methodCall.method) {
case "availableCameras":
return const [];
default:
return null;
}
},
); );
}, },
tearDown: () { tearDown: () {
@ -211,6 +237,28 @@ void main() {
], ],
); );
blocTest<CameraContainerBloc, CameraContainerState>(
'No back facing cameras available',
setUp: () {
when(() => meteringInteractor.checkCameraPermission()).thenAnswer((_) async => true);
TestDefaultBinaryMessengerBinding.instance.defaultBinaryMessenger
.setMockMethodCallHandler(
cameraMethodChannel,
(methodCall) async => cameraMethodCallSuccessHandler(methodCall, cameras: frontCameras),
);
},
tearDown: () {
TestDefaultBinaryMessengerBinding.instance.defaultBinaryMessenger
.setMockMethodCallHandler(cameraMethodChannel, null);
},
build: () => bloc,
act: (bloc) => bloc.add(const InitializeEvent()),
verify: (_) {
verify(() => meteringInteractor.checkCameraPermission()).called(1);
},
expect: () => initializedStateSequence,
);
blocTest<CameraContainerBloc, CameraContainerState>( blocTest<CameraContainerBloc, CameraContainerState>(
'Catch other initialization errors', 'Catch other initialization errors',
setUp: () { setUp: () {
@ -269,7 +317,7 @@ void main() {
); );
group( group(
'`_takePicture()` tests', '`_takePicture()`',
() { () {
blocTest<CameraContainerBloc, CameraContainerState>( blocTest<CameraContainerBloc, CameraContainerState>(
'Returned ev100 == null', 'Returned ev100 == null',
@ -281,14 +329,15 @@ void main() {
bloc.add(const InitializeEvent()); bloc.add(const InitializeEvent());
await Future.delayed(Duration.zero); await Future.delayed(Duration.zero);
bloc.onCommunicationState(const communication_states.MeasureState()); bloc.onCommunicationState(const communication_states.MeasureState());
bloc.onCommunicationState(const communication_states.MeasureState());
bloc.onCommunicationState(const communication_states.MeasureState());
bloc.onCommunicationState(const communication_states.MeasureState());
}, },
verify: (_) { verify: (_) {
verify(() => meteringInteractor.checkCameraPermission()).called(1); verify(() => meteringInteractor.checkCameraPermission()).called(1);
verifyNever(() => meteringInteractor.cameraEvCalibration); verifyNever(() => meteringInteractor.cameraEvCalibration);
}, },
expect: () => [ expect: () => initializedStateSequence,
...initializedStateSequence,
],
); );
// TODO(vodemn): figure out how to mock `_file.readAsBytes()` // TODO(vodemn): figure out how to mock `_file.readAsBytes()`
@ -321,11 +370,10 @@ void main() {
// ], // ],
// ); // );
}, },
skip: true,
); );
group( group(
'`ZoomChangedEvent` tests', '`ZoomChangedEvent`',
() { () {
blocTest<CameraContainerBloc, CameraContainerState>( blocTest<CameraContainerBloc, CameraContainerState>(
'Set zoom multiple times', 'Set zoom multiple times',
@ -372,7 +420,7 @@ void main() {
); );
group( group(
'`ExposureOffsetChangedEvent`/`ExposureOffsetResetEvent` tests', '`ExposureOffsetChangedEvent`/`ExposureOffsetResetEvent`',
() { () {
blocTest<CameraContainerBloc, CameraContainerState>( blocTest<CameraContainerBloc, CameraContainerState>(
'Set exposure offset multiple times and reset', 'Set exposure offset multiple times and reset',

View file

@ -39,7 +39,7 @@ void main() {
}); });
group( group(
'`LuxMeteringEvent` tests', '`LuxMeteringEvent`',
() { () {
const List<int> luxIterable = [1, 2, 2, 2, 3]; const List<int> luxIterable = [1, 2, 2, 2, 3];
final List<double> resultList = luxIterable.map((lux) => log2(lux / 2.5)).toList(); final List<double> resultList = luxIterable.map((lux) => log2(lux / 2.5)).toList();

3
test_coverage.sh Normal file
View file

@ -0,0 +1,3 @@
flutter test --coverage
genhtml coverage/lcov.info -o coverage/html
open coverage/html/index.html