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(''); 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>()); 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>()); 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>()); 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; }