m3_lightmeter/test/providers/films_provider_test.dart
Vadim f3b08868be
ML-62 Providers tests + Platform & Application mocks (#131)
- Fixed test coverage calculation
- Removed `mockito` from the application mock
- Implemented platform channel mocks to mimic incident light metering
- Covered providers with unit tests
- Covered metering screen pickers with widget tests
- Laid foundation for integration tests
2023-10-20 16:12:43 +02:00

275 lines
8.7 KiB
Dart

import 'package:flutter/material.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:lightmeter/providers/films_provider.dart';
import 'package:m3_lightmeter_iap/m3_lightmeter_iap.dart';
import 'package:m3_lightmeter_resources/m3_lightmeter_resources.dart';
import 'package:mocktail/mocktail.dart';
class _MockIAPStorageService extends Mock implements IAPStorageService {}
void main() {
TestWidgetsFlutterBinding.ensureInitialized();
late _MockIAPStorageService mockIAPStorageService;
setUpAll(() {
mockIAPStorageService = _MockIAPStorageService();
});
tearDown(() {
reset(mockIAPStorageService);
});
Future<void> pumpTestWidget(WidgetTester tester, IAPProductStatus productStatus) async {
await tester.pumpWidget(
IAPProducts(
products: [
IAPProduct(
storeId: IAPProductType.paidFeatures.storeId,
status: productStatus,
)
],
child: FilmsProvider(
storageService: mockIAPStorageService,
availableFilms: mockFilms,
child: const _Application(),
),
),
);
}
void expectFilmsCount(int count) {
expect(find.text('Films count: $count'), findsOneWidget);
}
void expectFilmsInUseCount(int count) {
expect(find.text('Films in use count: $count'), findsOneWidget);
}
void expectSelectedFilmName(String name) {
expect(find.text('Selected film: $name'), findsOneWidget);
}
group(
'FilmsProvider dependency on IAPProductStatus',
() {
setUp(() {
when(() => mockIAPStorageService.selectedFilm).thenReturn(mockFilms.first);
when(() => mockIAPStorageService.filmsInUse).thenReturn(mockFilms);
});
testWidgets(
'IAPProductStatus.purchased - show all saved films',
(tester) async {
await pumpTestWidget(tester, IAPProductStatus.purchased);
expectFilmsCount(mockFilms.length + 1);
expectFilmsInUseCount(mockFilms.length + 1);
expectSelectedFilmName(mockFilms.first.name);
},
);
testWidgets(
'IAPProductStatus.purchasable - show only default',
(tester) async {
await pumpTestWidget(tester, IAPProductStatus.purchasable);
expectFilmsCount(mockFilms.length + 1);
expectFilmsInUseCount(1);
expectSelectedFilmName('');
},
);
testWidgets(
'IAPProductStatus.pending - show only default',
(tester) async {
await pumpTestWidget(tester, IAPProductStatus.pending);
expectFilmsCount(mockFilms.length + 1);
expectFilmsInUseCount(1);
expectSelectedFilmName('');
},
);
},
);
group(
'FilmsProvider CRUD',
() {
testWidgets(
'Select films in use',
(tester) async {
when(() => mockIAPStorageService.selectedFilm).thenReturn(const Film.other());
when(() => mockIAPStorageService.filmsInUse).thenReturn([]);
/// Init
await pumpTestWidget(tester, IAPProductStatus.purchased);
expectFilmsCount(mockFilms.length + 1);
expectFilmsInUseCount(1);
expectSelectedFilmName('');
/// Select all filmsInUse
await tester.tap(find.byKey(_Application.saveFilmsButtonKey(0)));
await tester.pumpAndSettle();
expectFilmsCount(mockFilms.length + 1);
expectFilmsInUseCount(mockFilms.length + 1);
expectSelectedFilmName('');
verify(() => mockIAPStorageService.filmsInUse = mockFilms.skip(0).toList()).called(1);
verifyNever(() => mockIAPStorageService.selectedFilm = const Film.other());
},
);
testWidgets(
'Select film',
(tester) async {
when(() => mockIAPStorageService.selectedFilm).thenReturn(const Film.other());
when(() => mockIAPStorageService.filmsInUse).thenReturn(mockFilms);
/// Init
await pumpTestWidget(tester, IAPProductStatus.purchased);
expectFilmsCount(mockFilms.length + 1);
expectFilmsInUseCount(mockFilms.length + 1);
expectSelectedFilmName('');
/// Select all filmsInUse
await tester.tap(find.byKey(_Application.setFilmButtonKey(0)));
await tester.pumpAndSettle();
expectFilmsCount(mockFilms.length + 1);
expectFilmsInUseCount(mockFilms.length + 1);
expectSelectedFilmName(mockFilms.first.name);
verifyNever(() => mockIAPStorageService.filmsInUse = any<List<Film>>());
verify(() => mockIAPStorageService.selectedFilm = mockFilms.first).called(1);
},
);
group(
'Coming from free app',
() {
testWidgets(
'Has selected film',
(tester) async {
when(() => mockIAPStorageService.selectedFilm).thenReturn(mockFilms[2]);
when(() => mockIAPStorageService.filmsInUse).thenReturn([]);
/// Init
await pumpTestWidget(tester, IAPProductStatus.purchased);
expectFilmsInUseCount(1);
expectSelectedFilmName('');
verifyNever(() => mockIAPStorageService.filmsInUse = any<List<Film>>());
verify(() => mockIAPStorageService.selectedFilm = const Film.other()).called(1);
},
);
testWidgets(
'None film selected',
(tester) async {
when(() => mockIAPStorageService.selectedFilm).thenReturn(const Film.other());
when(() => mockIAPStorageService.filmsInUse).thenReturn([]);
/// Init
await pumpTestWidget(tester, IAPProductStatus.purchased);
expectFilmsInUseCount(1);
expectSelectedFilmName('');
verifyNever(() => mockIAPStorageService.filmsInUse = any<List<Film>>());
verifyNever(() => mockIAPStorageService.selectedFilm = const Film.other());
},
);
},
);
testWidgets(
'Discard selected (by filmsInUse list update)',
(tester) async {
when(() => mockIAPStorageService.selectedFilm).thenReturn(mockFilms.first);
when(() => mockIAPStorageService.filmsInUse).thenReturn(mockFilms);
/// Init
await pumpTestWidget(tester, IAPProductStatus.purchased);
expectFilmsCount(mockFilms.length + 1);
expectFilmsInUseCount(mockFilms.length + 1);
expectSelectedFilmName(mockFilms.first.name);
/// Select all filmsInUse except the first one
await tester.tap(find.byKey(_Application.saveFilmsButtonKey(1)));
await tester.pumpAndSettle();
expectFilmsCount(mockFilms.length + 1);
expectFilmsInUseCount((mockFilms.length - 1) + 1);
expectSelectedFilmName('');
verify(() => mockIAPStorageService.filmsInUse = mockFilms.skip(1).toList()).called(1);
verify(() => mockIAPStorageService.selectedFilm = const Film.other()).called(1);
},
);
},
);
}
class _Application extends StatelessWidget {
const _Application();
static ValueKey saveFilmsButtonKey(int index) => ValueKey('saveFilmsButtonKey$index');
static ValueKey setFilmButtonKey(int index) => ValueKey('setFilmButtonKey$index');
@override
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
body: Center(
child: Column(
children: [
Text("Films count: ${Films.of(context).length}"),
Text("Films in use count: ${Films.inUseOf(context).length}"),
Text("Selected film: ${Films.selectedOf(context).name}"),
_filmRow(context, 0),
_filmRow(context, 1),
],
),
),
),
);
}
Widget _filmRow(BuildContext context, int index) {
return Row(
children: [
ElevatedButton(
key: saveFilmsButtonKey(index),
onPressed: () {
FilmsProvider.of(context).saveFilms(mockFilms.skip(index).toList());
},
child: const Text("Save filmsInUse"),
),
ElevatedButton(
key: setFilmButtonKey(index),
onPressed: () {
FilmsProvider.of(context).setFilm(mockFilms[index]);
},
child: const Text("Set film"),
),
],
);
}
}
const mockFilms = [_MockFilm2x(), _MockFilm3x(), _MockFilm4x()];
class _MockFilm2x extends Film {
const _MockFilm2x() : super('Mock film 2x', 400);
@override
double reciprocityFormula(double t) => t * 2;
}
class _MockFilm3x extends Film {
const _MockFilm3x() : super('Mock film 3x', 800);
@override
double reciprocityFormula(double t) => t * 3;
}
class _MockFilm4x extends Film {
const _MockFilm4x() : super('Mock film 4x', 1600);
@override
double reciprocityFormula(double t) => t * 4;
}