diff --git a/integration_test/app_test.dart b/integration_test/app_test.dart new file mode 100644 index 0000000..f303140 --- /dev/null +++ b/integration_test/app_test.dart @@ -0,0 +1,12 @@ +import 'package:flutter_test/flutter_test.dart'; +import 'package:integration_test/integration_test.dart'; + +import 'bottom_nav_bar_test.dart'; + +void main() { + IntegrationTestWidgetsFlutterBinding.ensureInitialized(); + + group('Development App', () { + bottomNavBarTest(); + }); +} diff --git a/integration_test/bottom_nav_bar_test.dart b/integration_test/bottom_nav_bar_test.dart new file mode 100644 index 0000000..e917694 --- /dev/null +++ b/integration_test/bottom_nav_bar_test.dart @@ -0,0 +1,85 @@ +import 'package:flutter/widgets.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:fpb/core/domain/user.dart'; +import 'package:fpb/core/shared/helpers/value_injector.dart'; +import 'package:fpb/home/home_screen.dart'; +import 'package:fpb/home/view/budget_screen.dart'; +import 'package:fpb/home/view/dashboard.dart'; +import 'package:fpb/home/view/home_container.dart'; +import 'package:fpb/home/view/widgets/bottom_nav_bar.dart'; +import 'package:fpb/savings/view/savings_page.dart'; + +import '../test/helpers/helpers.dart'; + +void bottomNavBarTest() { + late MockCached cached; + late MockIFacebookRepositoryFacade mockIFacebookRepositoryFacade; + late MockIGoogleRepositoryFacade mockIGoogleRepositoryFacade; + late MockIAuthFacade mockIAuthFacade; + + setUp(() { + cached = MockCached(); + mockIAuthFacade = MockIAuthFacade(); + mockIFacebookRepositoryFacade = MockIFacebookRepositoryFacade(); + mockIGoogleRepositoryFacade = MockIGoogleRepositoryFacade(); + }); + + testWidgets('renders home bottom nav bar buttons', (tester) async { + arrangeAuthRepositoryReturnsStreamWithUser(mockIAuthFacade); + arrangeAuthRepositoryReturnsCurrentUser(mockIAuthFacade); + arrangeCachedReturnsLastView(cached, 0); + + await tester.pumpApp( + ValueInjector( + value: testUser, + child: HomeBody( + user: testUser, + ), + ), + mockCachedForHomeBloc: cached, + mockIAuthFacadeForAuthBloc: mockIAuthFacade, + ); + + await tester.pumpAndSettle(); + + // await tester.pump(); + + expect(find.byType(HomeContainer), findsOneWidget); + expect(find.byType(BottomNavBar), findsOneWidget); + expect(find.byType(DashBoard), findsOneWidget); + + await tester.tap( + find.byKey( + Key("BNB-savings-page-button"), + ), + ); + + await tester.pumpAndSettle(); + + expect(find.byType(SavingsPage), findsOneWidget); + + await tester.tap( + find.byKey( + Key("BNB-budget-page-button"), + ), + ); + + await tester.pumpAndSettle(); + + expect(find.byType(BudgetScreen), findsOneWidget); + + // await tester.tap( + // find.byKey( + // Key("BNB-userSearch-page-button"), + // ), + // ); + + // await tester.pumpAndSettle(); + + // expect(find.byType(UserSearchScreen), findsOneWidget); + + await Future.delayed( + const Duration(seconds: 3), + ); + }); +} diff --git a/lib/app/view/app.dart b/lib/app/view/app.dart index e4938d9..ed5cbbe 100644 --- a/lib/app/view/app.dart +++ b/lib/app/view/app.dart @@ -1,10 +1,9 @@ import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; -import 'package:fpb/core/presentation/theming/themes/theme.dart'; - import 'package:fpb/authentication_mock_without_backend/application/bloc/authentication_bloc.dart'; import 'package:fpb/authentication_with_firebase/application/bloc/auth_bloc.dart'; import 'package:fpb/core/application/internet_and_time_bloc/internet_and_time_bloc.dart'; +import 'package:fpb/core/presentation/theming/themes/theme.dart'; import 'package:fpb/injection.dart'; import 'package:fpb/l10n/l10n.dart'; import 'package:fpb/router/app_route.gr.dart'; diff --git a/lib/home/application/home_view_bloc/home_view_bloc.dart b/lib/home/application/home_view_bloc/home_view_bloc.dart index c1ca297..5bbdc8e 100644 --- a/lib/home/application/home_view_bloc/home_view_bloc.dart +++ b/lib/home/application/home_view_bloc/home_view_bloc.dart @@ -1,18 +1,18 @@ // ignore_for_file: inference_failure_on_untyped_parameter import 'package:bloc/bloc.dart'; -import 'package:fpb/core/settings/app_settings_helper.dart'; +import 'package:fpb/core/settings/cached.dart'; import 'package:freezed_annotation/freezed_annotation.dart'; import 'package:injectable/injectable.dart'; +part 'home_view_bloc.freezed.dart'; part 'home_view_event.dart'; part 'home_view_state.dart'; -part 'home_view_bloc.freezed.dart'; @injectable class HomeViewBloc extends Bloc { - final AppSettingsHelper _appSettingsHelper; - HomeViewBloc(this._appSettingsHelper) : super(HomeViewState.home()) { + final Cached _cached; + HomeViewBloc(this._cached) : super(HomeViewState.home()) { on<_HomeE>(_home); on<_SavingsE>(_savings); on<_QuickCashE>(_quickCash); @@ -21,33 +21,41 @@ class HomeViewBloc extends Bloc { on<_SearchE>(_search); } + int? lastView; + void _search(event, Emitter emit) { emit(HomeViewState.search()); - _appSettingsHelper.appCache.setLastView = 4; + lastView = 4; } void _home(event, Emitter emit) { emit(HomeViewState.home()); - _appSettingsHelper.appCache.setLastView = 0; + lastView = 0; } void _savings(event, Emitter emit) { emit(HomeViewState.savings()); - _appSettingsHelper.appCache.setLastView = 1; + lastView = 1; } void _quickCash(event, Emitter emit) { emit(HomeViewState.quickCash()); - _appSettingsHelper.appCache.setLastView = 2; + lastView = 2; } void _budget(event, Emitter emit) { emit(HomeViewState.budget()); - _appSettingsHelper.appCache.setLastView = 3; + lastView = 3; } void _lastState(event, Emitter emit) { - final lastState = _appSettingsHelper.appCache.getLastView; + late int lastState; + if (lastView != null) { + lastState = lastView!; + } else { + lastState = _cached.getLastView; + } + switch (lastState) { case 0: _home(event, emit); @@ -69,4 +77,10 @@ class HomeViewBloc extends Bloc { break; } } + + @override + Future close() { + if (lastView != null) _cached.setLastView = lastView!; + return super.close(); + } } diff --git a/lib/home/view/dashboard.dart b/lib/home/view/dashboard.dart index 96c0ab0..b9d5bba 100644 --- a/lib/home/view/dashboard.dart +++ b/lib/home/view/dashboard.dart @@ -1,6 +1,5 @@ import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; - import 'package:fpb/authentication_with_firebase/application/bloc/auth_bloc.dart'; import 'package:fpb/core/domain/user.dart'; import 'package:fpb/core/presentation/animations/slide_up_route_transition.dart'; diff --git a/lib/home/view/widgets/bottom_nav_bar.dart b/lib/home/view/widgets/bottom_nav_bar.dart index 5fe8b44..0d31b69 100644 --- a/lib/home/view/widgets/bottom_nav_bar.dart +++ b/lib/home/view/widgets/bottom_nav_bar.dart @@ -25,6 +25,7 @@ class _BottomNavBarState extends State { IconButton( enableFeedback: false, padding: EdgeInsets.zero, + key: Key("BNB-home-page-button"), onPressed: () { setState(() { pageIndex = 0; @@ -56,6 +57,7 @@ class _BottomNavBarState extends State { IconButton( enableFeedback: false, padding: EdgeInsets.zero, + key: Key("BNB-savings-page-button"), onPressed: () { setState(() { pageIndex = 1; @@ -86,6 +88,7 @@ class _BottomNavBarState extends State { IconButton( enableFeedback: false, padding: EdgeInsets.zero, + key: Key("BNB-quickCash-page-button"), onPressed: () { setState(() { pageIndex = 2; @@ -116,6 +119,7 @@ class _BottomNavBarState extends State { IconButton( enableFeedback: false, padding: EdgeInsets.zero, + key: Key("BNB-budget-page-button"), onPressed: () { setState(() { pageIndex = 3; @@ -147,6 +151,7 @@ class _BottomNavBarState extends State { IconButton( enableFeedback: false, padding: EdgeInsets.zero, + key: Key("BNB-userSearch-page-button"), onPressed: () { setState(() { pageIndex = 4; diff --git a/lib/injection.config.dart b/lib/injection.config.dart index e23a1e0..36981d4 100644 --- a/lib/injection.config.dart +++ b/lib/injection.config.dart @@ -15,7 +15,7 @@ import 'package:flutter_facebook_auth/flutter_facebook_auth.dart' as _i5; import 'package:fpb/authentication_mock_without_backend/application/bloc/authentication_bloc.dart' as _i17; import 'package:fpb/authentication_mock_without_backend/infrastructure/authentication_mock_module_injection.dart' - as _i28; + as _i30; import 'package:fpb/authentication_with_facebook/application/facebook_auth_bloc.dart' as _i19; import 'package:fpb/authentication_with_facebook/domain/i_facebook_repository_facade.dart' @@ -23,32 +23,32 @@ import 'package:fpb/authentication_with_facebook/domain/i_facebook_repository_fa import 'package:fpb/authentication_with_facebook/infrastructure/facebook_auth_repository.dart' as _i11; import 'package:fpb/authentication_with_facebook/infrastructure/facebook_authentication_injectable_module.dart' - as _i29; + as _i32; import 'package:fpb/authentication_with_firebase/application/bloc/auth_bloc.dart' - as _i24; + as _i25; import 'package:fpb/authentication_with_firebase/domain/i_auth_facade.dart' - as _i21; -import 'package:fpb/authentication_with_firebase/infrastructure/firebase_auth_facade_impl.dart' as _i22; +import 'package:fpb/authentication_with_firebase/infrastructure/firebase_auth_facade_impl.dart' + as _i23; import 'package:fpb/authentication_with_firebase/infrastructure/firebase_auth_injectable_module.dart' - as _i30; + as _i31; import 'package:fpb/authentication_with_google/application/google_auth_bloc/google_sign_in_bloc.dart' as _i20; import 'package:fpb/authentication_with_google/domain/i_google_repository_facade.dart' as _i12; import 'package:fpb/authentication_with_google/infrastructure/google_authentication_injectable_module.dart' - as _i31; + as _i28; import 'package:fpb/authentication_with_google/infrastructure/google_authentication_repository.dart' as _i13; import 'package:fpb/core/application/email_password_bloc/email_password_bloc.dart' - as _i25; + as _i26; import 'package:fpb/core/application/internet_and_time_bloc/internet_and_time_bloc.dart' as _i27; -import 'package:fpb/core/infrastructure/core_injectable_module.dart' as _i32; -import 'package:fpb/core/settings/app_settings_helper.dart' as _i23; +import 'package:fpb/core/infrastructure/core_injectable_module.dart' as _i29; +import 'package:fpb/core/settings/app_settings_helper.dart' as _i24; import 'package:fpb/core/settings/cached.dart' as _i18; import 'package:fpb/home/application/home_view_bloc/home_view_bloc.dart' - as _i26; + as _i21; import 'package:get_it/get_it.dart' as _i1; import 'package:google_sign_in/google_sign_in.dart' as _i9; import 'package:injectable/injectable.dart' as _i2; @@ -119,35 +119,34 @@ extension GetItInjectableX on _i1.GetIt { authenticationRepository: gh<_i10.IFacebookRepositoryFacade>())); gh.factory<_i20.GoogleSignInBloc>(() => _i20.GoogleSignInBloc( authenticationRepository: gh<_i12.IGoogleRepositoryFacade>())); - gh.lazySingleton<_i21.IAuthFacade>(() => _i22.FirebaseAuthFacade( + gh.factory<_i21.HomeViewBloc>(() => _i21.HomeViewBloc(gh<_i18.Cached>())); + gh.lazySingleton<_i22.IAuthFacade>(() => _i23.FirebaseAuthFacade( gh<_i7.FirebaseAuth>(), gh<_i18.Cached>(), )); - gh.lazySingleton<_i23.AppSettingsHelper>(() => _i23.AppSettingsHelper( + gh.lazySingleton<_i24.AppSettingsHelper>(() => _i24.AppSettingsHelper( gh<_i18.Cached>(), gh<_i4.Connectivity>(), )); - gh.factory<_i24.AuthBloc>(() => _i24.AuthBloc(gh<_i21.IAuthFacade>())); - gh.singleton<_i25.EmailPasswordBloc>(_i25.EmailPasswordBloc( - authenticationRepository: gh<_i21.IAuthFacade>())); - gh.factory<_i26.HomeViewBloc>( - () => _i26.HomeViewBloc(gh<_i23.AppSettingsHelper>())); + gh.factory<_i25.AuthBloc>(() => _i25.AuthBloc(gh<_i22.IAuthFacade>())); + gh.singleton<_i26.EmailPasswordBloc>(_i26.EmailPasswordBloc( + authenticationRepository: gh<_i22.IAuthFacade>())); gh.factory<_i27.InternetAndTimeBloc>( - () => _i27.InternetAndTimeBloc(gh<_i23.AppSettingsHelper>())); + () => _i27.InternetAndTimeBloc(gh<_i24.AppSettingsHelper>())); return this; } } -class _$AuthenticationMockModuleInjection - extends _i28.AuthenticationMockModuleInjection {} +class _$GoogleAuthenticationInjectableModule + extends _i28.GoogleAuthenticationInjectableModule {} -class _$FacebookAuthenticationInjectableModule - extends _i29.FacebookAuthenticationInjectableModule {} +class _$CoreInjectableModule extends _i29.CoreInjectableModule {} -class _$FirebaseAuthInjectableModule - extends _i30.FirebaseAuthInjectableModule {} +class _$AuthenticationMockModuleInjection + extends _i30.AuthenticationMockModuleInjection {} -class _$GoogleAuthenticationInjectableModule - extends _i31.GoogleAuthenticationInjectableModule {} +class _$FirebaseAuthInjectableModule + extends _i31.FirebaseAuthInjectableModule {} -class _$CoreInjectableModule extends _i32.CoreInjectableModule {} +class _$FacebookAuthenticationInjectableModule + extends _i32.FacebookAuthenticationInjectableModule {} diff --git a/lib/sign_in/view/sign_in_page.dart b/lib/sign_in/view/sign_in_page.dart index ca8c867..055b805 100644 --- a/lib/sign_in/view/sign_in_page.dart +++ b/lib/sign_in/view/sign_in_page.dart @@ -1,9 +1,7 @@ import 'package:auto_route/auto_route.dart'; -import 'package:firebase_auth/firebase_auth.dart'; import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_svg/svg.dart'; - import 'package:fpb/assets/fpb_icons/fpb_icons_icons.dart'; import 'package:fpb/assets/fpb_svg.dart'; import 'package:fpb/authenticate_with_biometrics/application/bloc/biometric_auth_bloc.dart'; @@ -60,7 +58,6 @@ class SignInBody extends StatefulWidget { class _SignInBodyState extends State with SingleTickerProviderStateMixin { late TabController tabController; - final user = FirebaseAuth.instance.currentUser; @override void initState() { @@ -163,15 +160,17 @@ class _SignInBodyState extends State physics: const BouncingScrollPhysics(), controller: tabController, children: [ - Column( - crossAxisAlignment: - CrossAxisAlignment.end, - mainAxisSize: MainAxisSize.min, - children: [ - EmailInput(box: cts), - PasswordInput(box: cts), - Text(l10n.signInForgotPasswordText), - ], + SingleChildScrollView( + child: Column( + crossAxisAlignment: + CrossAxisAlignment.end, + mainAxisSize: MainAxisSize.min, + children: [ + EmailInput(box: cts), + PasswordInput(box: cts), + Text(l10n.signInForgotPasswordText), + ], + ), ), Column( mainAxisAlignment: diff --git a/pubspec.lock b/pubspec.lock index 270dd13..7575968 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -13,10 +13,10 @@ packages: dependency: transitive description: name: _flutterfire_internals - sha256: "330d7fcbb72624f5b6d374af8b059b0ef4ba96ba5b8987f874964a1287eb617d" + sha256: "64fcb0dbca4386356386c085142fa6e79c00a3326ceaa778a2d25f5d9ba61441" url: "https://pub.dev" source: hosted - version: "1.0.18" + version: "1.0.16" analyzer: dependency: transitive description: @@ -25,6 +25,14 @@ packages: url: "https://pub.dev" source: hosted version: "4.7.0" + archive: + dependency: transitive + description: + name: archive + sha256: "80e5141fafcb3361653ce308776cfd7d45e6e9fbb429e14eec571382c0c5fecb" + url: "https://pub.dev" + source: hosted + version: "3.3.2" args: dependency: transitive description: @@ -180,26 +188,26 @@ packages: dependency: "direct main" description: name: cloud_firestore - sha256: "0ad93f01816ab7e59560bffeece2d19cec872fedfea04a7ad5be418cad25d574" + sha256: "65f148d9f5b4f389320abb45847120cf5e46094c1a8cbc64934ffc1e29688596" url: "https://pub.dev" source: hosted - version: "4.4.5" + version: "4.4.3" cloud_firestore_platform_interface: dependency: transitive description: name: cloud_firestore_platform_interface - sha256: "6255cf3ad845d44c6c69a7db51139c8d90691e435dd175578f6365f10c023fcc" + sha256: "43ccae09f7e0c82752e69c251c6dc5efcdff4ddcfc09564175a28657bbd74188" url: "https://pub.dev" source: hosted - version: "5.11.5" + version: "5.11.3" cloud_firestore_web: dependency: transitive description: name: cloud_firestore_web - sha256: "3bc08abdeb21a59e80491aee827895aaa3c6da602bad9ab68d83e1ab13c951b1" + sha256: e054c007217e28e07179bbae0564c2a4f6338a60bddb0c139e4834e953f4b95c url: "https://pub.dev" source: hosted - version: "3.3.5" + version: "3.3.3" code_builder: dependency: transitive description: @@ -356,34 +364,34 @@ packages: dependency: "direct main" description: name: firebase_auth - sha256: "94c229e296a5b9ee5c8cda918e0b320e3a0cc4f6a349cd410c427da347f2a244" + sha256: "9907d80446466e638dad31c195150b305dffd145dc57610fcd12c72289432143" url: "https://pub.dev" source: hosted - version: "4.3.0" + version: "4.2.9" firebase_auth_platform_interface: dependency: "direct dev" description: name: firebase_auth_platform_interface - sha256: "1217d8aa313b49d58b489aa8879544563abc8793d9612ff20d8df193f202aedc" + sha256: c645fec50b0391aa878288f58fa4fe9762c271380c457aedf5c7c9b718604f68 url: "https://pub.dev" source: hosted - version: "6.12.0" + version: "6.11.11" firebase_auth_web: dependency: transitive description: name: firebase_auth_web - sha256: bf7f1a87995a58b0f07dc617806dabd7ff25c64be7fa47b41ab1bb9a485b0062 + sha256: "2dcf2a36852b9091741b4a4047a02e1f2c43a62c6cacec7df573a793a6543e6d" url: "https://pub.dev" source: hosted - version: "5.2.10" + version: "5.2.8" firebase_core: dependency: "direct main" description: name: firebase_core - sha256: "75f747cafd7cbd6c00b908e3a7aa59fc31593d46ba8165d9ee8a79e69464a394" + sha256: fe30ac230f12f8836bb97e6e09197340d3c584526825b1746ea362a82e1e43f7 url: "https://pub.dev" source: hosted - version: "2.8.0" + version: "2.7.0" firebase_core_platform_interface: dependency: "direct main" description: @@ -396,10 +404,10 @@ packages: dependency: transitive description: name: firebase_core_web - sha256: "0c1cf1f1022d2245ac117443bb95207952ca770281524d2908e323bc063fb8ff" + sha256: "291fbcace608aca6c860652e1358ef89752be8cc3ef227f8bbcd1e62775b833a" url: "https://pub.dev" source: hosted - version: "2.2.2" + version: "2.2.1" fixnum: dependency: transitive description: @@ -421,6 +429,11 @@ packages: url: "https://pub.dev" source: hosted version: "8.1.2" + flutter_driver: + dependency: transitive + description: flutter + source: sdk + version: "0.0.0" flutter_facebook_auth: dependency: "direct main" description: @@ -518,10 +531,10 @@ packages: dependency: "direct main" description: name: flutter_svg - sha256: "12006889e2987c549c4c1ec1a5ba4ec4b24d34d2469ee5f9476c926dcecff266" + sha256: "97c5b291b4fd34ae4f55d6a4c05841d4d0ed94952e033c5a6529e1b47b4d2a29" url: "https://pub.dev" source: hosted - version: "2.0.4" + version: "2.0.2" flutter_test: dependency: "direct dev" description: flutter @@ -564,6 +577,11 @@ packages: url: "https://pub.dev" source: hosted version: "3.2.0" + fuchsia_remote_debug_protocol: + dependency: transitive + description: flutter + source: sdk + version: "0.0.0" get_it: dependency: "direct main" description: @@ -600,18 +618,18 @@ packages: dependency: transitive description: name: google_sign_in_android - sha256: "6a740e8498920ecf5fc2849ae0e4412536d700230bc3169493a1d031fdfe1cca" + sha256: f27bd56527c567594167bd0a46f7ceb93122ed064d2eee612413d6af0bb2e2e5 url: "https://pub.dev" source: hosted - version: "6.1.8" + version: "6.1.7" google_sign_in_ios: dependency: transitive description: name: google_sign_in_ios - sha256: "2e1df687b17f7fddcaf9a0c7f994d0d19b5d41e8ce1d943013befd0a0ae67403" + sha256: "2575ef0d06dbe6923cedf39766da8305d407bb891d9f4a59502c642719776c5c" url: "https://pub.dev" source: hosted - version: "5.6.1" + version: "5.6.0" google_sign_in_platform_interface: dependency: transitive description: @@ -676,6 +694,11 @@ packages: url: "https://pub.dev" source: hosted version: "2.1.4" + integration_test: + dependency: "direct dev" + description: flutter + source: sdk + version: "0.0.0" intl: dependency: "direct main" description: @@ -736,18 +759,18 @@ packages: dependency: transitive description: name: local_auth_android - sha256: "2ccfadbb6fbc63e6674ad58a350b06188829e62669d67a0c752c4e43cb88272a" + sha256: "6e97e997530f7bfcc696cc0ed84a019209719dd6e854675da81803d02f10d8fc" url: "https://pub.dev" source: hosted - version: "1.0.21" + version: "1.0.20" local_auth_ios: dependency: transitive description: name: local_auth_ios - sha256: "604078f6492fe7730fc5bb8e4f2cfe2bc287a9b499ea0ff30a29925fc1873728" + sha256: b4b7a02eec2bd8ffea78026658264e7a720c841cca7732dfa27e36af2f727dd8 url: "https://pub.dev" source: hosted - version: "1.1.1" + version: "1.1.0" local_auth_platform_interface: dependency: transitive description: @@ -760,10 +783,10 @@ packages: dependency: transitive description: name: local_auth_windows - sha256: bfe0deede77fb36faa62799977074f35ac096d7cafce0c29a44a173d2a2a4b94 + sha256: "69c4a6b1201e7b5467e7180c7dd84cf96c308982680cc1778984552bea84b0bc" url: "https://pub.dev" source: hosted - version: "1.0.7" + version: "1.0.6" logging: dependency: "direct main" description: @@ -872,34 +895,34 @@ packages: dependency: "direct main" description: name: path_provider - sha256: c7edf82217d4b2952b2129a61d3ad60f1075b9299e629e149a8d2e39c2e6aad4 + sha256: "04890b994ee89bfa80bf3080bfec40d5a92c5c7a785ebb02c13084a099d2b6f9" url: "https://pub.dev" source: hosted - version: "2.0.14" + version: "2.0.13" path_provider_android: dependency: transitive description: name: path_provider_android - sha256: "019f18c9c10ae370b08dce1f3e3b73bc9f58e7f087bb5e921f06529438ac0ae7" + sha256: "7623b7d4be0f0f7d9a8b5ee6879fc13e4522d4c875ab86801dee4af32b54b83e" url: "https://pub.dev" source: hosted - version: "2.0.24" + version: "2.0.23" path_provider_foundation: dependency: transitive description: name: path_provider_foundation - sha256: "12eee51abdf4d34c590f043f45073adbb45514a108bd9db4491547a2fd891059" + sha256: eec003594f19fe2456ea965ae36b3fc967bc5005f508890aafe31fa75e41d972 url: "https://pub.dev" source: hosted - version: "2.2.0" + version: "2.1.2" path_provider_linux: dependency: transitive description: name: path_provider_linux - sha256: "2ae08f2216225427e64ad224a24354221c2c7907e448e6e0e8b57b1eb9f10ad1" + sha256: "525ad5e07622d19447ad740b1ed5070031f7a5437f44355ae915ff56e986429a" url: "https://pub.dev" source: hosted - version: "2.1.10" + version: "2.1.9" path_provider_platform_interface: dependency: transitive description: @@ -912,10 +935,10 @@ packages: dependency: transitive description: name: path_provider_windows - sha256: f53720498d5a543f9607db4b0e997c4b5438884de25b0f73098cc2671a51b130 + sha256: "642ddf65fde5404f83267e8459ddb4556316d3ee6d511ed193357e25caa3632d" url: "https://pub.dev" source: hosted - version: "2.1.5" + version: "2.1.4" petitparser: dependency: transitive description: @@ -1048,26 +1071,26 @@ packages: dependency: transitive description: name: shared_preferences_android - sha256: ad423a80fe7b4e48b50d6111b3ea1027af0e959e49d485712e134863d9c1c521 + sha256: a51a4f9375097f94df1c6e0a49c0374440d31ab026b59d58a7e7660675879db4 url: "https://pub.dev" source: hosted - version: "2.0.17" + version: "2.0.16" shared_preferences_foundation: dependency: transitive description: name: shared_preferences_foundation - sha256: "1e755f8583229f185cfca61b1d80fb2344c9d660e1c69ede5450d8f478fa5310" + sha256: "6b84fdf06b32bb336f972d373cd38b63734f3461ba56ac2ba01b56d052796259" url: "https://pub.dev" source: hosted - version: "2.1.5" + version: "2.1.4" shared_preferences_linux: dependency: transitive description: name: shared_preferences_linux - sha256: "3a59ed10890a8409ad0faad7bb2957dab4b92b8fbe553257b05d30ed8af2c707" + sha256: d7fb71e6e20cd3dfffcc823a28da3539b392e53ed5fc5c2b90b55fdaa8a7e8fa url: "https://pub.dev" source: hosted - version: "2.1.5" + version: "2.1.4" shared_preferences_platform_interface: dependency: transitive description: @@ -1080,18 +1103,18 @@ packages: dependency: transitive description: name: shared_preferences_web - sha256: "0dc2633f215a3d4aa3184c9b2c5766f4711e4e5a6b256e62aafee41f89f1bfb8" + sha256: "6737b757e49ba93de2a233df229d0b6a87728cea1684da828cbc718b65dcf9d7" url: "https://pub.dev" source: hosted - version: "2.0.6" + version: "2.0.5" shared_preferences_windows: dependency: transitive description: name: shared_preferences_windows - sha256: "71bcd669bb9cdb6b39f22c4a7728b6d49e934f6cba73157ffa5a54f1eed67436" + sha256: bd014168e8484837c39ef21065b78f305810ceabc1d4f90be6e3b392ce81b46d url: "https://pub.dev" source: hosted - version: "2.1.5" + version: "2.1.4" shelf: dependency: transitive description: @@ -1201,6 +1224,14 @@ packages: url: "https://pub.dev" source: hosted version: "1.2.0" + sync_http: + dependency: transitive + description: + name: sync_http + sha256: "7f0cd72eca000d2e026bcd6f990b81d0ca06022ef4e32fb257b30d3d1014a961" + url: "https://pub.dev" + source: hosted + version: "0.3.1" term_glyph: dependency: transitive description: @@ -1300,26 +1331,26 @@ packages: dependency: transitive description: name: vector_graphics - sha256: "4cf8e60dbe4d3a693d37dff11255a172594c0793da542183cbfe7fe978ae4aaa" + sha256: e43c38822e7d2facd790fd617ce16b10cc359a4b094d5772e3198904270918ef url: "https://pub.dev" source: hosted - version: "1.1.4" + version: "1.1.1" vector_graphics_codec: dependency: transitive description: name: vector_graphics_codec - sha256: "278ad5f816f58b1967396d1f78ced470e3e58c9fe4b27010102c0a595c764468" + sha256: "4a85a8563405bfe223052a85d6f8bc276ba3a22e12acfa3fd9a7108c67b32076" url: "https://pub.dev" source: hosted - version: "1.1.4" + version: "1.1.1" vector_graphics_compiler: dependency: transitive description: name: vector_graphics_compiler - sha256: "0bf61ad56e6fd6688a2865d3ceaea396bc6a0a90ea0d7ad5049b1b76c09d6163" + sha256: "9e8066d2f18e88f2fb1cf6c8ca567417bdfb0145ac28519dcca3c15d42a10b3e" url: "https://pub.dev" source: hosted - version: "1.1.4" + version: "1.1.1" vector_math: dependency: transitive description: @@ -1360,6 +1391,14 @@ packages: url: "https://pub.dev" source: hosted version: "2.3.0" + webdriver: + dependency: transitive + description: + name: webdriver + sha256: ef67178f0cc7e32c1494645b11639dd1335f1d18814aa8435113a92e9ef9d841 + url: "https://pub.dev" + source: hosted + version: "3.0.1" webkit_inspection_protocol: dependency: transitive description: diff --git a/pubspec.yaml b/pubspec.yaml index 73d449b..2b4e302 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -57,6 +57,8 @@ dev_dependencies: bloc_test: ^9.1.1 flutter_test: sdk: flutter + integration_test: + sdk: flutter freezed: ^2.2.0 mocktail: ^0.3.0 very_good_analysis: ^4.0.0+1 diff --git a/test/app/view/app_test.dart b/test/app/view/app_test.dart deleted file mode 100644 index c15f5d9..0000000 --- a/test/app/view/app_test.dart +++ /dev/null @@ -1,21 +0,0 @@ -// Copyright (c) 2022, Very Good Ventures -// https://verygood.ventures -// -// Use of this source code is governed by an MIT-style -// license that can be found in the LICENSE file or at -// https://opensource.org/licenses/MIT. - -import 'package:flutter_test/flutter_test.dart'; -import 'package:fpb/app/app.dart'; -import 'package:fpb/onboarding/view/onboarding_screens.dart'; - -void main() { - group('App', () { - testWidgets('renders SignInPage', (tester) async { - await tester.pumpWidget( - App(), - ); - expect(find.byType(OnboardingScreen), findsOneWidget); - }); - }); -} diff --git a/test/authentication_with_facebook/application/facebook_auth_bloc_test.dart b/test/authentication_with_facebook/application/facebook_auth_bloc_test.dart new file mode 100644 index 0000000..597aa49 --- /dev/null +++ b/test/authentication_with_facebook/application/facebook_auth_bloc_test.dart @@ -0,0 +1,144 @@ +import 'package:bloc_test/bloc_test.dart'; +import 'package:dartz/dartz.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:fpb/authentication_with_facebook/application/facebook_auth_bloc.dart'; +import 'package:fpb/core/domain/user.dart'; +import 'package:fpb/core/failures/auth_failure.dart'; +import 'package:mocktail/mocktail.dart'; + +import '../../helpers/helpers.dart'; + +void main() { + late FacebookAuthBloc bloc; + late MockIFacebookRepositoryFacade facebookRepositoryFacade; + + setUp(() { + facebookRepositoryFacade = MockIFacebookRepositoryFacade(); + + bloc = FacebookAuthBloc( + authenticationRepository: facebookRepositoryFacade, + ); + }); + + test("Facebook Auth Bloc initial state should be FacebookAuthState.initial", + () { + //assert + expect(bloc.state, equals(FacebookAuthState.initial())); + }); + + group("Sign in with facebook", () { + blocTest( + "should emit [loading, failed] with proper message for the error when facebook auth fails", + build: () { + arrangeFacebookRepositoryReturnsFailureOnSignin( + facebookRepositoryFacade); + return bloc; + }, + act: (_) { + _.add(FacebookAuthEvent.signIn()); + }, + expect: () => containsAllInOrder([ + FacebookAuthState.initial().copyWith( + isLoading: true, + ), + FacebookAuthState( + failureOrUser: left( + AuthFailure.cancelledByUser(), + ), + isLoading: false, + ), + ]), + verify: (bloc) { + verify( + () => facebookRepositoryFacade.signInWithFacebook(), + ).called(1); + }, + ); + + blocTest( + "should sign in the user with his facebook account", + build: () { + arrangeFacebookRepositoryReturnsSuccessOnSignin( + facebookRepositoryFacade); + return bloc; + }, + act: (_) { + _.add(FacebookAuthEvent.signIn()); + }, + expect: () => containsAllInOrder([ + FacebookAuthState.initial().copyWith( + isLoading: true, + ), + FacebookAuthState( + failureOrUser: right( + testUser, + ), + isLoading: false, + ), + ]), + verify: (bloc) { + verify( + () => facebookRepositoryFacade.signInWithFacebook(), + ).called(1); + }, + ); + }); + + group("Sign out with facebook", () { + blocTest( + "should emit [loading, failed] with proper message for the error when facebook logout fails", + build: () { + arrangeFacebookRepositoryReturnsFailureOnSignout( + facebookRepositoryFacade); + return bloc; + }, + act: (_) { + _.add(FacebookAuthEvent.signOut()); + }, + expect: () => containsAllInOrder([ + FacebookAuthState.initial().copyWith( + isLoading: true, + ), + FacebookAuthState( + failureOrUser: left( + AuthFailure.logoutFailed(), + ), + isLoading: false, + ), + ]), + verify: (bloc) { + verify( + () => facebookRepositoryFacade.signOut(), + ).called(1); + }, + ); + + blocTest( + "should sign out the user with facebook", + build: () { + arrangeFacebookRepositoryReturnsSuccessOnSignout( + facebookRepositoryFacade); + return bloc; + }, + act: (_) { + _.add(FacebookAuthEvent.signOut()); + }, + expect: () => containsAllInOrder([ + FacebookAuthState.initial().copyWith( + isLoading: true, + ), + FacebookAuthState( + failureOrUser: right( + User.empty, + ), + isLoading: false, + ), + ]), + verify: (bloc) { + verify( + () => facebookRepositoryFacade.signOut(), + ).called(1); + }, + ); + }); +} diff --git a/test/authentication_with_firebase/application/bloc/auth_bloc_test.dart b/test/authentication_with_firebase/application/bloc/auth_bloc_test.dart index d82fa2d..3c5ceec 100644 --- a/test/authentication_with_firebase/application/bloc/auth_bloc_test.dart +++ b/test/authentication_with_firebase/application/bloc/auth_bloc_test.dart @@ -1,5 +1,109 @@ +import 'package:bloc_test/bloc_test.dart'; +import 'package:dartz/dartz.dart'; import 'package:flutter_test/flutter_test.dart'; +import 'package:fpb/authentication_with_firebase/application/bloc/auth_bloc.dart'; +import 'package:fpb/core/domain/user.dart'; +import 'package:mocktail/mocktail.dart'; + +import '../../../helpers/helpers.dart'; void main() { - testWidgets('auth bloc ...', (tester) async {}); + late AuthBloc bloc; + late MockIAuthFacade authFacade; + + setUp(() { + authFacade = MockIAuthFacade(); + + arrangeAuthRepositoryReturnsStreamWithEmptyUser(authFacade); + + bloc = AuthBloc(authFacade); + }); + + test("Auth Bloc initial state should be _Splash", () { + //assert + expect(bloc.state, equals(AuthState.splash())); + }); + + blocTest( + "Trigger auth request", + build: () { + arrangeAuthRepositoryReturnsCurrentUserAsEmpty(authFacade); + + return bloc; + }, + act: (_) { + _.add(AuthEvent.triggerAuthRequest()); + }, + expect: () => [], + verify: (_) { + verify( + () => authFacade.user, + ).called(1); + + verify( + () => authFacade.currentUser, + ).called(1); + }, + ); + + blocTest( + """emits [AuthState.authenticated(), AuthState.unauthenticated()] + when AuthEvent.userChanged is added + and then AuthEvent.logoutRequest is added""", + build: () { + return bloc; + }, + act: (_) async { + when(() => authFacade.signOut()).thenAnswer( + (invocation) async { + _.add(AuthEvent.userChanged(User.empty)); + return right(unit); + }, + ); + + _.add( + AuthEvent.userChanged( + testUser, + ), + ); + _.add(AuthEvent.logoutRequest()); + }, + expect: () => containsAllInOrder([ + AuthState.authenticated(user: testUser), + AuthState.unauthenticated(), + ]), + verify: (bloc) { + verify(() => authFacade.signOut()).called(1); + }, + ); + + group("user changed", () { + blocTest( + "emits [AuthState.authenticated()] when AuthEvent.userChanged is added", + build: () { + return bloc; + }, + act: (_) { + _.add(AuthEvent.userChanged(testUser)); + }, + expect: () => contains( + AuthState.authenticated(user: testUser), + ), + ); + + blocTest( + "emits [AuthState.unauthenticated()] when AuthEvent.userChanged is added", + build: () { + return bloc; + }, + act: (_) { + _.add(AuthEvent.userChanged(testUser)); + _.add(AuthEvent.userChanged(User.empty)); + }, + skip: 1, + expect: () => contains( + AuthState.unauthenticated(), + ), + ); + }); } diff --git a/test/authentication_with_google/application/google_sign_in_bloc_test.dart b/test/authentication_with_google/application/google_sign_in_bloc_test.dart new file mode 100644 index 0000000..ec0cf92 --- /dev/null +++ b/test/authentication_with_google/application/google_sign_in_bloc_test.dart @@ -0,0 +1,140 @@ +import 'package:bloc_test/bloc_test.dart'; +import 'package:dartz/dartz.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:fpb/authentication_with_google/application/google_auth_bloc/google_sign_in_bloc.dart'; +import 'package:fpb/core/domain/user.dart'; +import 'package:fpb/core/failures/auth_failure.dart'; +import 'package:mocktail/mocktail.dart'; + +import '../../helpers/helpers.dart'; + +void main() { + late GoogleSignInBloc bloc; + late MockIGoogleRepositoryFacade googleRepositoryFacade; + + setUp(() { + googleRepositoryFacade = MockIGoogleRepositoryFacade(); + + bloc = GoogleSignInBloc( + authenticationRepository: googleRepositoryFacade, + ); + }); + + test("Google Auth Bloc initial state should be GoogleSignInState.initial", + () { + //assert + expect(bloc.state, equals(GoogleSignInState.initial())); + }); + + group("Sign in with google", () { + blocTest( + "should emit [loading, failed] with proper message for the error when google auth fails", + build: () { + arrangeGoogleRepositoryReturnsFailureOnSignin(googleRepositoryFacade); + return bloc; + }, + act: (_) { + _.add(GoogleSignInEvent.signIn()); + }, + expect: () => containsAllInOrder([ + GoogleSignInState.initial().copyWith( + isLoading: true, + ), + GoogleSignInState( + failureOrUser: left( + AuthFailure.cancelledByUser(), + ), + isLoading: false, + ), + ]), + verify: (bloc) { + verify( + () => googleRepositoryFacade.signInWithGoogle(), + ).called(1); + }, + ); + + blocTest( + "should sign in the user with his google account", + build: () { + arrangeGoogleRepositoryReturnsSuccessOnSignin(googleRepositoryFacade); + return bloc; + }, + act: (_) { + _.add(GoogleSignInEvent.signIn()); + }, + expect: () => containsAllInOrder([ + GoogleSignInState.initial().copyWith( + isLoading: true, + ), + GoogleSignInState( + failureOrUser: right( + testUser, + ), + isLoading: false, + ), + ]), + verify: (bloc) { + verify( + () => googleRepositoryFacade.signInWithGoogle(), + ).called(1); + }, + ); + }); + + group("Sign out with google", () { + blocTest( + "should emit [loading, failed] with proper message for the error when google logout fails", + build: () { + arrangeGoogleRepositoryReturnsFailureOnSignout(googleRepositoryFacade); + return bloc; + }, + act: (_) { + _.add(GoogleSignInEvent.signOut()); + }, + expect: () => containsAllInOrder([ + GoogleSignInState.initial().copyWith( + isLoading: true, + ), + GoogleSignInState( + failureOrUser: left( + AuthFailure.logoutFailed(), + ), + isLoading: false, + ), + ]), + verify: (bloc) { + verify( + () => googleRepositoryFacade.signOut(), + ).called(1); + }, + ); + + blocTest( + "should sign out the user with google", + build: () { + arrangeGoogleRepositoryReturnsSuccessOnSignout(googleRepositoryFacade); + return bloc; + }, + act: (_) { + _.add(GoogleSignInEvent.signOut()); + }, + expect: () => containsAllInOrder([ + GoogleSignInState.initial().copyWith( + isLoading: true, + ), + GoogleSignInState( + failureOrUser: right( + User.empty, + ), + isLoading: false, + ), + ]), + verify: (bloc) { + verify( + () => googleRepositoryFacade.signOut(), + ).called(1); + }, + ); + }); +} diff --git a/test/core/application/email_password_bloc/email_password_bloc_test.dart b/test/core/application/email_password_bloc/email_password_bloc_test.dart new file mode 100644 index 0000000..d8284e9 --- /dev/null +++ b/test/core/application/email_password_bloc/email_password_bloc_test.dart @@ -0,0 +1,166 @@ +import 'package:bloc_test/bloc_test.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:formz/formz.dart'; +import 'package:fpb/core/application/email_password_bloc/email_password_bloc.dart'; +import 'package:fpb/sign_in/domain/email.dart'; +import 'package:fpb/sign_in/domain/password.dart'; +import 'package:mocktail/mocktail.dart'; + +import '../../../helpers/helpers.dart'; + +void main() { + late EmailPasswordBloc bloc; + late MockIAuthFacade authFacade; + + setUp(() { + authFacade = MockIAuthFacade(); + + bloc = EmailPasswordBloc( + authenticationRepository: authFacade, + ); + }); + + group("Update user email address", () { + blocTest( + "should emit [invalid] for the state email param when the given email is wrong", + build: () { + return bloc; + }, + act: (_) { + _.add(EmailPasswordEvent.emailChanged(invalidEmail)); + }, + expect: () => contains( + bloc.state.copyWith( + email: Email.dirty(invalidEmail), + ), + ), + ); + + blocTest( + "should emit [valid] for the state email param when the given email is correct", + build: () { + return bloc; + }, + act: (_) { + _.add(EmailPasswordEvent.emailChanged(validEmail)); + }, + expect: () => contains( + bloc.state.copyWith( + email: Email.dirty(validEmail), + ), + ), + ); + }); + + group("Update user password", () { + blocTest( + "should emit [invalid] for the state password param when the given password is wrong", + build: () { + return bloc; + }, + act: (_) { + _.add(EmailPasswordEvent.passwordChanged(invalidPassword)); + }, + expect: () => contains( + bloc.state.copyWith( + password: Password.dirty(invalidPassword), + ), + ), + ); + + blocTest( + "should emit [valid] for the state password param when the given password is correct", + build: () { + return bloc; + }, + act: (_) { + _.add(EmailPasswordEvent.passwordChanged(validPassword)); + }, + expect: () => contains( + bloc.state.copyWith( + password: Password.dirty(validPassword), + ), + ), + ); + }); + + group("Sign in with email and password", () { + blocTest( + "should emit [] when the form state is invalid", + build: () { + return bloc; + }, + act: (_) { + _.add(EmailPasswordEvent.emailChanged(invalidEmail)); + _.add(EmailPasswordEvent.passwordChanged(invalidPassword)); + _.add(EmailPasswordEvent.submitted()); + }, + skip: 2, + expect: () => [], + ); + + blocTest( + "should emit [loading, failed] with proper message for the error when Email&Password password sign in fails", + build: () { + arrangeAuthRepositoryReturnsFailureOnSignin(authFacade); + return bloc; + }, + act: (_) { + _.add(EmailPasswordEvent.emailChanged(validEmail)); + _.add(EmailPasswordEvent.passwordChanged(validPassword)); + _.add(EmailPasswordEvent.submitted()); + }, + expect: () => containsAllInOrder( + [ + bloc.state.copyWith( + status: FormzStatus.submissionInProgress, + ), + bloc.state.copyWith( + status: FormzStatus.submissionFailure, + ) + ], + ), + skip: 2, + verify: (bloc) { + verify( + () => authFacade.signInWithEmailAndPassword( + email: any(named: "email"), + password: any(named: "password"), + ), + ).called(1); + }, + ); + + blocTest( + "should sign in the user with his email and password", + build: () { + arrangeAuthRepositoryReturnsSuccessOnSignin(authFacade); + return bloc; + }, + act: (_) { + _.add(EmailPasswordEvent.emailChanged(validEmail)); + _.add(EmailPasswordEvent.passwordChanged(validPassword)); + _.add(EmailPasswordEvent.submitted()); + }, + skip: 2, + expect: () => containsAllInOrder( + [ + bloc.state.copyWith( + status: FormzStatus.submissionInProgress, + ), + bloc.state.copyWith( + status: FormzStatus.submissionSuccess, + ) + ], + ), + verify: (bloc) { + verify( + () => authFacade.signInWithEmailAndPassword( + email: any(named: "email"), + password: any(named: "password"), + ), + ).called(1); + }, + ); + }); +} diff --git a/test/core/application/internet_and_time_bloc/internet_and_time_bloc_test.dart b/test/core/application/internet_and_time_bloc/internet_and_time_bloc_test.dart new file mode 100644 index 0000000..6b01fa2 --- /dev/null +++ b/test/core/application/internet_and_time_bloc/internet_and_time_bloc_test.dart @@ -0,0 +1,22 @@ +import 'package:flutter_test/flutter_test.dart'; +import 'package:fpb/core/application/internet_and_time_bloc/internet_and_time_bloc.dart'; + +import '../../../helpers/helpers.dart'; + +void main() { + late InternetAndTimeBloc bloc; + late MockAppSettingsHelper appSettings; + + setUp(() { + appSettings = MockAppSettingsHelper(); + + bloc = InternetAndTimeBloc(appSettings); + }); + + test("InternetAndTimeBloc initial state should be InternetAndTimeState.fresh", + () { + //assert + expect(bloc.state, equals(InternetAndTimeState.fresh())); + }); + group("user changed", () {}); +} diff --git a/test/helpers/arrange_auth_repository.dart b/test/helpers/arrange_auth_repository.dart new file mode 100644 index 0000000..99df2f9 --- /dev/null +++ b/test/helpers/arrange_auth_repository.dart @@ -0,0 +1,91 @@ +import 'package:dartz/dartz.dart'; +import 'package:fpb/core/domain/user.dart'; +import 'package:fpb/core/failures/auth_failure.dart'; +import 'package:mocktail/mocktail.dart'; + +import 'mocks.dart'; +import 'test_data.dart'; + +void arrangeAuthRepositoryReturnsCurrentUser(MockIAuthFacade authFacade) { + when( + () => authFacade.currentUser, + ).thenReturn( + testUser, + ); +} + +void arrangeAuthRepositoryReturnsCurrentUserAsEmpty( + MockIAuthFacade authFacade) { + when( + () => authFacade.currentUser, + ).thenReturn( + User.empty, + ); +} + +void arrangeAuthRepositoryReturnsStreamWithEmptyUser( + MockIAuthFacade authFacade) { + when( + () => authFacade.user, + ).thenAnswer( + (_) async* { + yield User.empty; + }, + ); +} + +void arrangeAuthRepositoryReturnsStreamWithUser(MockIAuthFacade authFacade) { + when( + () => authFacade.user, + ).thenAnswer( + (_) async* { + yield testUser; + }, + ); +} + +void arrangeAuthRepositoryReturnsFailureOnSignin(MockIAuthFacade authFacade) { + when( + () => authFacade.signInWithEmailAndPassword( + email: validEmail, + password: validPassword, + ), + ).thenAnswer( + (invocation) async { + return left( + AuthFailure.invalidEmailAndPasswordCombination(), + ); + }, + ); +} + +void arrangeAuthRepositoryReturnsSuccessOnSignin(MockIAuthFacade authFacade) { + when( + () => authFacade.signInWithEmailAndPassword( + email: validEmail, + password: validPassword, + ), + ).thenAnswer( + (invocation) async => right( + testUser, + ), + ); +} + +void arrangeAuthRepositoryReturnsFailureOnLogoutRequest( + MockIAuthFacade authFacade) { + when(() => authFacade.signOut()).thenAnswer( + (invocation) async { + return left(AuthFailure.logoutFailed()); + }, + ); +} + +void arrangeAuthRepositoryReturnsSuccessOnLogoutRequest( + MockIAuthFacade authFacade) { + when(() => authFacade.signOut()).thenAnswer( + (invocation) async { + return right(unit); + }, + ); +} diff --git a/test/helpers/arrange_cached.dart b/test/helpers/arrange_cached.dart new file mode 100644 index 0000000..9f397f8 --- /dev/null +++ b/test/helpers/arrange_cached.dart @@ -0,0 +1,11 @@ +import 'package:mocktail/mocktail.dart'; + +import 'mocks.dart'; + +void arrangeCachedReturnsLastView(MockCached cached, int intendedValue) { + when( + () => cached.getLastView, + ).thenReturn( + intendedValue, + ); +} diff --git a/test/helpers/arrange_facebook_repository.dart b/test/helpers/arrange_facebook_repository.dart new file mode 100644 index 0000000..c24d6a6 --- /dev/null +++ b/test/helpers/arrange_facebook_repository.dart @@ -0,0 +1,50 @@ +import 'package:dartz/dartz.dart'; +import 'package:fpb/core/failures/auth_failure.dart'; +import 'package:mocktail/mocktail.dart'; + +import 'mocks.dart'; +import 'test_data.dart'; + +void arrangeFacebookRepositoryReturnsFailureOnSignin( + MockIFacebookRepositoryFacade facebookRepositoryFacade) { + when( + () => facebookRepositoryFacade.signInWithFacebook(), + ).thenAnswer( + (invocation) async => left( + AuthFailure.cancelledByUser(), + ), + ); +} + +void arrangeFacebookRepositoryReturnsSuccessOnSignin( + MockIFacebookRepositoryFacade facebookRepositoryFacade) { + when( + () => facebookRepositoryFacade.signInWithFacebook(), + ).thenAnswer( + (invocation) async => right( + testUser, + ), + ); +} + +void arrangeFacebookRepositoryReturnsFailureOnSignout( + MockIFacebookRepositoryFacade facebookRepositoryFacade) { + when( + () => facebookRepositoryFacade.signOut(), + ).thenAnswer( + (invocation) async => left( + AuthFailure.logoutFailed(), + ), + ); +} + +void arrangeFacebookRepositoryReturnsSuccessOnSignout( + MockIFacebookRepositoryFacade facebookRepositoryFacade) { + when( + () => facebookRepositoryFacade.signOut(), + ).thenAnswer( + (invocation) async => right( + unit, + ), + ); +} diff --git a/test/helpers/arrange_google_repository.dart b/test/helpers/arrange_google_repository.dart new file mode 100644 index 0000000..f9a7a79 --- /dev/null +++ b/test/helpers/arrange_google_repository.dart @@ -0,0 +1,50 @@ +import 'package:dartz/dartz.dart'; +import 'package:fpb/core/failures/auth_failure.dart'; +import 'package:mocktail/mocktail.dart'; + +import 'mocks.dart'; +import 'test_data.dart'; + +void arrangeGoogleRepositoryReturnsFailureOnSignin( + MockIGoogleRepositoryFacade googleRepositoryFacade) { + when( + () => googleRepositoryFacade.signInWithGoogle(), + ).thenAnswer( + (invocation) async => left( + AuthFailure.cancelledByUser(), + ), + ); +} + +void arrangeGoogleRepositoryReturnsSuccessOnSignin( + MockIGoogleRepositoryFacade googleRepositoryFacade) { + when( + () => googleRepositoryFacade.signInWithGoogle(), + ).thenAnswer( + (invocation) async => right( + testUser, + ), + ); +} + +void arrangeGoogleRepositoryReturnsFailureOnSignout( + MockIGoogleRepositoryFacade googleRepositoryFacade) { + when( + () => googleRepositoryFacade.signOut(), + ).thenAnswer( + (invocation) async => left( + AuthFailure.logoutFailed(), + ), + ); +} + +void arrangeGoogleRepositoryReturnsSuccessOnSignout( + MockIGoogleRepositoryFacade googleRepositoryFacade) { + when( + () => googleRepositoryFacade.signOut(), + ).thenAnswer( + (invocation) async => right( + unit, + ), + ); +} diff --git a/test/helpers/helpers.dart b/test/helpers/helpers.dart index e843b27..a764a0e 100644 --- a/test/helpers/helpers.dart +++ b/test/helpers/helpers.dart @@ -5,4 +5,10 @@ // license that can be found in the LICENSE file or at // https://opensource.org/licenses/MIT. +export 'arrange_auth_repository.dart'; +export 'arrange_cached.dart'; +export 'arrange_facebook_repository.dart'; +export 'arrange_google_repository.dart'; +export 'mocks.dart'; export 'pump_app.dart'; +export 'test_data.dart'; diff --git a/test/helpers/mocks.dart b/test/helpers/mocks.dart new file mode 100644 index 0000000..c47bc76 --- /dev/null +++ b/test/helpers/mocks.dart @@ -0,0 +1,18 @@ +import 'package:fpb/authentication_with_facebook/domain/i_facebook_repository_facade.dart'; +import 'package:fpb/authentication_with_firebase/domain/i_auth_facade.dart'; +import 'package:fpb/authentication_with_google/domain/i_google_repository_facade.dart'; +import 'package:fpb/core/settings/app_settings_helper.dart'; +import 'package:fpb/core/settings/cached.dart'; +import 'package:mocktail/mocktail.dart'; + +class MockIGoogleRepositoryFacade extends Mock + implements IGoogleRepositoryFacade {} + +class MockIAuthFacade extends Mock implements IAuthFacade {} + +class MockIFacebookRepositoryFacade extends Mock + implements IFacebookRepositoryFacade {} + +class MockAppSettingsHelper extends Mock implements AppSettingsHelper {} + +class MockCached extends Mock implements Cached {} diff --git a/test/helpers/pump_app.dart b/test/helpers/pump_app.dart index 40304c3..5e6da51 100644 --- a/test/helpers/pump_app.dart +++ b/test/helpers/pump_app.dart @@ -6,16 +6,80 @@ // https://opensource.org/licenses/MIT. import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_test/flutter_test.dart'; +import 'package:fpb/authentication_with_facebook/application/facebook_auth_bloc.dart'; +import 'package:fpb/authentication_with_firebase/application/bloc/auth_bloc.dart'; +import 'package:fpb/authentication_with_google/application/google_auth_bloc/google_sign_in_bloc.dart'; +import 'package:fpb/core/application/email_password_bloc/email_password_bloc.dart'; +import 'package:fpb/core/application/internet_and_time_bloc/internet_and_time_bloc.dart'; +import 'package:fpb/home/application/home_view_bloc/home_view_bloc.dart'; import 'package:fpb/l10n/l10n.dart'; +import 'helpers.dart'; + extension PumpApp on WidgetTester { - Future pumpApp(Widget widget) { + Future pumpApp( + Widget widget, { + MockAppSettingsHelper? mockAppSettingsHelper, + MockIAuthFacade? mockIAuthFacadeForAuthBloc, + MockIGoogleRepositoryFacade? mockIGoogleRepositoryFacade, + MockIFacebookRepositoryFacade? mockIFacebookRepositoryFacade, + MockIAuthFacade? mockIAuthFacadeFroEmailPasswordBloc, + MockCached? mockCachedForHomeBloc, + }) { return pumpWidget( - MaterialApp( - localizationsDelegates: AppLocalizations.localizationsDelegates, - supportedLocales: AppLocalizations.supportedLocales, - home: widget, + MultiBlocProvider( + providers: [ + // BlocProvider( + // create: (context) => getIt()), + if (mockAppSettingsHelper != null) + BlocProvider( + create: (context) => InternetAndTimeBloc( + mockAppSettingsHelper, + ), + ), + if (mockIAuthFacadeForAuthBloc != null) + BlocProvider( + create: (context) => AuthBloc( + mockIAuthFacadeForAuthBloc, + )..add(const AuthEvent.triggerAuthRequest()), + ), + if (mockIGoogleRepositoryFacade != null) + BlocProvider( + create: (context) => GoogleSignInBloc( + authenticationRepository: mockIGoogleRepositoryFacade, + ), + ), + if (mockIFacebookRepositoryFacade != null) + BlocProvider( + create: (context) => FacebookAuthBloc( + authenticationRepository: mockIFacebookRepositoryFacade, + ), + ), + if (mockIAuthFacadeFroEmailPasswordBloc != null) + BlocProvider( + create: (context) => EmailPasswordBloc( + authenticationRepository: mockIAuthFacadeFroEmailPasswordBloc, + ), + ), + if (mockCachedForHomeBloc != null) + BlocProvider( + create: (context) => HomeViewBloc(mockCachedForHomeBloc) + ..add( + HomeViewEvent.lastState(), + ), + ), + // BlocProvider( + // create: (context) => BiometricAuthBloc( + // authenticationRepository: LocalAuthentication()), + // ), + ], + child: MaterialApp( + localizationsDelegates: AppLocalizations.localizationsDelegates, + supportedLocales: AppLocalizations.supportedLocales, + home: widget, + ), ), ); } diff --git a/test/helpers/test_data.dart b/test/helpers/test_data.dart new file mode 100644 index 0000000..174788e --- /dev/null +++ b/test/helpers/test_data.dart @@ -0,0 +1,18 @@ +import 'package:fpb/core/domain/user.dart'; + +const User testUser = User( + id: "dede", + isNewUser: true, + photo: + "https://avatars.githubusercontent.com/u/52233891?s=400&u=e5da06a4923b3a269085775e6738f06b4f292607&v=4", + providerId: "providerId", + name: "Kengne Jeffrey", + email: "darrelntangu@gmail.com", + phoneNumber: "+237652432246", +); + +const String invalidEmail = "dev.flutter@flu"; +const String validEmail = "devflutter@flutterplaza.com"; + +const String invalidPassword = "3e2fcw"; +const String validPassword = "32rfrfeQds"; diff --git a/test/home/application/home_view_bloc/home_view_bloc_test.dart b/test/home/application/home_view_bloc/home_view_bloc_test.dart new file mode 100644 index 0000000..71983f2 --- /dev/null +++ b/test/home/application/home_view_bloc/home_view_bloc_test.dart @@ -0,0 +1,65 @@ +import 'package:bloc_test/bloc_test.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:fpb/home/application/home_view_bloc/home_view_bloc.dart'; +import 'package:mocktail/mocktail.dart'; + +import '../../../helpers/helpers.dart'; + +void main() { + late HomeViewBloc bloc; + late MockCached cached; + + setUp(() { + cached = MockCached(); + + bloc = HomeViewBloc(cached); + }); + + test("Home View Bloc initial state should be _Home", () { + //assert + expect(bloc.state, equals(HomeViewState.home())); + }); + + blocTest( + "emits [_Home] when HomeViewEvent.home is added", + build: () { + when( + () => cached.getLastView, + ).thenReturn( + 0, + ); + return bloc; + }, + act: (_) { + _.add(HomeViewEvent.home()); + }, + expect: () => contains(HomeViewState.home()), + verify: (bloc) { + verify( + () => cached.setLastView = bloc.lastView ?? 0, + ).called(1); + }, + ); + + blocTest( + "emits [_Home, _Budget] when HomeViewEvent.home is added, after HomeViewEvent.budget is added, then HomeViewEvent.lastState is added", + build: () { + arrangeCachedReturnsLastView(cached, 0); + return bloc; + }, + act: (_) { + _.add(HomeViewEvent.home()); + _.add(HomeViewEvent.budget()); + _.add(HomeViewEvent.lastState()); + }, + expect: () => containsAllInOrder([ + HomeViewState.home(), + HomeViewState.budget(), + ]), + verify: (bloc) { + verify( + () => cached.setLastView = bloc.lastView ?? 0, + ).called(1); + }, + ); +} diff --git a/test/sign_in/view/sign_in_screen_test.dart b/test/sign_in/view/sign_in_screen_test.dart new file mode 100644 index 0000000..c69bc6a --- /dev/null +++ b/test/sign_in/view/sign_in_screen_test.dart @@ -0,0 +1,35 @@ +import 'package:flutter_test/flutter_test.dart'; +import 'package:fpb/sign_in/sign_in.dart'; +import 'package:fpb/sign_in/view/widgets/login_button.dart'; + +import '../../helpers/helpers.dart'; + +void main() { + late MockIFacebookRepositoryFacade mockIFacebookRepositoryFacade; + late MockIGoogleRepositoryFacade mockIGoogleRepositoryFacade; + late MockIAuthFacade mockIAuthFacade; + + setUp(() { + mockIAuthFacade = MockIAuthFacade(); + mockIFacebookRepositoryFacade = MockIFacebookRepositoryFacade(); + mockIGoogleRepositoryFacade = MockIGoogleRepositoryFacade(); + }); + + group("Sign in screen widget test", () { + testWidgets("check for login button", (widgetTester) async { + arrangeAuthRepositoryReturnsStreamWithEmptyUser(mockIAuthFacade); + arrangeAuthRepositoryReturnsCurrentUserAsEmpty(mockIAuthFacade); + + await widgetTester.pumpApp( + SignInBody(), + mockIAuthFacadeFroEmailPasswordBloc: mockIAuthFacade, + mockIFacebookRepositoryFacade: mockIFacebookRepositoryFacade, + mockIGoogleRepositoryFacade: mockIGoogleRepositoryFacade, + ); + + await widgetTester.pump(); + + expect(find.byType(LoginButton), findsOneWidget); + }); + }); +} diff --git a/test/widget_test.dart b/test/widget_test.dart deleted file mode 100644 index 4bc9e45..0000000 --- a/test/widget_test.dart +++ /dev/null @@ -1,36 +0,0 @@ -// This is a basic Flutter widget test. -// -// To perform an interaction with a widget in your test, use the WidgetTester -// utility in the flutter_test package. For example, you can send tap and scroll -// gestures. You can also use WidgetTester to find child widgets in the widget -// tree, read text, and verify that the values of widget properties are correct. - -import 'package:flutter/material.dart'; -import 'package:flutter_test/flutter_test.dart'; -import 'package:fpb/app/view/app.dart'; - -void main() { - testWidgets('Counter increments smoke test', (WidgetTester tester) async { - // Build our app and trigger a frame. - await tester.pumpWidget( - App(), - ); - - // Verify that our counter starts at 0. - expect(find.text('0'), findsOneWidget); - expect(find.text('1'), findsNothing); - - // Tap the '+' icon and trigger a frame. - await tester.tap(find.byIcon(Icons.add)); - await tester.pump(); - - // Verify that our counter has incremented. - expect(find.text('0'), findsNothing); - expect(find.text('1'), findsOneWidget); - }); -} - -//commands to run after code -// flutter analyze lib test -// dart fix --dry-run -//flutter format lib test