diff --git a/test/providers/films_provider_test.dart b/test/providers/films_provider_test.dart new file mode 100644 index 0000000..0294379 --- /dev/null +++ b/test/providers/films_provider_test.dart @@ -0,0 +1,264 @@ +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 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(''); + }, + ); + + 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); + }, + ); + + 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(''); + + 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.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(''); + }, + ); + }, + ); +} + +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; +}