diff --git a/lib/core/state/command.dart b/lib/core/state/command.dart index fc6e3eb..5a85a12 100644 --- a/lib/core/state/command.dart +++ b/lib/core/state/command.dart @@ -65,3 +65,25 @@ class Command0 extends Command { return _result!; } } + +class Command1 extends Command { + final Future> Function(A) action; + + Command1(this.action); + + Future> execute(A arg1) async { + await _execute(() => action(arg1)); + return result!; + } +} + +class Command2 extends Command { + final Future> Function(A, B) action; + + Command2(this.action); + + Future> execute(A arg1, B arg2) async { + await _execute(() => action(arg1, arg2)); + return result!; + } +} diff --git a/lib/main.dart b/lib/main.dart index 5a41dd4..d31e610 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -4,6 +4,7 @@ import 'package:aranduapp/core/di/locator.dart'; import 'package:flutter/material.dart'; void main() { + setupLocator(); runApp(const MyApp()); } @@ -12,9 +13,6 @@ class MyApp extends StatelessWidget { @override Widget build(BuildContext context) { - - setupLocator(); - return MaterialApp( theme: ThemeApp.themeData(), darkTheme: ThemeApp.darkThemeData(), diff --git a/lib/ui/edit_password/view/edit_password_view.dart b/lib/ui/edit_password/view/edit_password_view.dart index 11d1238..1351f8c 100644 --- a/lib/ui/edit_password/view/edit_password_view.dart +++ b/lib/ui/edit_password/view/edit_password_view.dart @@ -1,5 +1,5 @@ import 'package:aranduapp/ui/edit_password/viewmodel/edit_password_viewmodel.dart'; -import 'package:aranduapp/ui/shared/request_button.dart'; +import 'package:aranduapp/ui/shared/command_button.dart'; import 'package:flutter/material.dart'; import 'package:provider/provider.dart'; @@ -80,7 +80,8 @@ class EditPasswordScreen extends StatelessWidget { } Widget _button(BuildContext context, EditPasswordViewModel viewModel) { - return Requestbutton( + return CommandButton( + tap: viewModel.editCommand.execute , command: viewModel.editCommand, nameButton: "Enviar", onErrorCallback: (e) { diff --git a/lib/ui/edit_profile/view/edit_profile_view.dart b/lib/ui/edit_profile/view/edit_profile_view.dart index 4395a6c..d58d3fe 100644 --- a/lib/ui/edit_profile/view/edit_profile_view.dart +++ b/lib/ui/edit_profile/view/edit_profile_view.dart @@ -2,7 +2,7 @@ import 'package:aranduapp/ui/edit_profile/viewmodel/edit_profile_viewmodel.dart' import 'package:aranduapp/ui/login/viewmodel/login_viewmodel.dart'; import 'package:aranduapp/ui/shared/text_email.dart'; import 'package:aranduapp/ui/shared/text_name.dart'; -import 'package:aranduapp/ui/shared/request_button.dart'; +import 'package:aranduapp/ui/shared/command_button.dart'; import 'package:flutter/material.dart'; import 'package:get_it/get_it.dart'; import 'package:provider/provider.dart'; @@ -89,7 +89,8 @@ class EditProfileScreen extends StatelessWidget { } Widget _saveButton(BuildContext context, EditProfileViewModel viewModel) { - return Requestbutton( + return CommandButton( + tap: viewModel.editCommand.execute , command: viewModel.editCommand, nameButton: "Salvar", onErrorCallback: (e) { diff --git a/lib/ui/login/di/di.dart b/lib/ui/login/di/di.dart index c9133a5..a4c8476 100644 --- a/lib/ui/login/di/di.dart +++ b/lib/ui/login/di/di.dart @@ -1,12 +1,14 @@ import 'package:aranduapp/ui/login/service/login_service.dart'; import 'package:aranduapp/ui/login/viewmodel/login_viewmodel.dart'; import 'package:get_it/get_it.dart'; +import 'package:local_auth/local_auth.dart'; final GetIt locator = GetIt.instance; void setupLoginDI() { locator.registerLazySingleton(() => LoginService()); + locator.registerLazySingleton(() => LocalAuthentication()); locator.registerFactory(() => LoginViewModel()); } diff --git a/lib/ui/login/service/login_service.dart b/lib/ui/login/service/login_service.dart index b888c36..a4edd8a 100644 --- a/lib/ui/login/service/login_service.dart +++ b/lib/ui/login/service/login_service.dart @@ -7,7 +7,7 @@ import 'package:aranduapp/ui/login/model/login_response.dart'; import 'package:dio/dio.dart'; class LoginService { - Future login(LoginRequest loginRequest) async { + Future login(LoginRequest loginRequest) async { Log.d('${loginRequest.email} ${loginRequest.password}'); Response response = await BaseApi.getInstance(auth: false).post( @@ -24,7 +24,6 @@ class LoginService { await StorageValue.getInstance() .setRefreshToken(loginResponse.refreshToken); - return response; } Future validateToken() async { diff --git a/lib/ui/login/view/login_view.dart b/lib/ui/login/view/login_view.dart index c5ea304..6a6a022 100644 --- a/lib/ui/login/view/login_view.dart +++ b/lib/ui/login/view/login_view.dart @@ -1,7 +1,8 @@ import 'package:aranduapp/core/log/log.dart'; +import 'package:aranduapp/ui/login/model/login_request.dart'; import 'package:aranduapp/ui/navbar/view/navbar_view.dart'; import 'package:aranduapp/ui/shared/text_and_link.dart'; -import 'package:aranduapp/ui/shared/request_button.dart'; +import 'package:aranduapp/ui/shared/command_button.dart'; import 'package:flutter/material.dart'; import 'package:get_it/get_it.dart'; import 'package:provider/provider.dart'; @@ -24,13 +25,17 @@ class Login extends StatelessWidget { Widget build(BuildContext context) { return ChangeNotifierProvider.value( value: GetIt.instance(), - child: const LoginScreen(), + child: LoginScreen(), ); } } class LoginScreen extends StatelessWidget { - const LoginScreen({super.key}); + LoginScreen({super.key}); + + final GlobalKey formKey = GlobalKey(); + final TextEditingController emailController = TextEditingController(); + final TextEditingController passwordController = TextEditingController(); @override Widget build(BuildContext context) { @@ -133,16 +138,16 @@ class LoginScreen extends StatelessWidget { Widget _formSection(LoginViewModel viewModel) { return Form( - key: viewModel.formKey, + key: formKey, child: Column( children: [ TextEmail( padding: const EdgeInsets.symmetric(vertical: 10, horizontal: 20), - controller: viewModel.emailController, + controller: emailController, ), TextPassWord( padding: const EdgeInsets.symmetric(vertical: 10, horizontal: 20), - controller: viewModel.passwordController, + controller: passwordController, ), ], ), @@ -177,7 +182,13 @@ class LoginScreen extends StatelessWidget { Widget _loginButtonSection(BuildContext context) { LoginViewModel viewModel = Provider.of(context); - return Requestbutton( + return CommandButton( + tap: () { + if (formKey.currentState!.validate()) { + viewModel.loginCommand.execute( + LoginRequest(emailController.text, passwordController.text)); + } + }, command: viewModel.loginCommand, onErrorCallback: (String e) { showDialog( diff --git a/lib/ui/login/viewmodel/login_viewmodel.dart b/lib/ui/login/viewmodel/login_viewmodel.dart index b60cae1..c56bc59 100644 --- a/lib/ui/login/viewmodel/login_viewmodel.dart +++ b/lib/ui/login/viewmodel/login_viewmodel.dart @@ -8,30 +8,19 @@ import 'package:aranduapp/ui/login/service/login_service.dart'; import 'package:aranduapp/ui/login/model/login_request.dart'; class LoginViewModel extends ChangeNotifier { - late Command0 loginCommand; + late Command1 loginCommand; late Command0 validadeTokenCommand; - final GlobalKey formKey; - final TextEditingController emailController; - final TextEditingController passwordController; - - LoginViewModel() - : formKey = GlobalKey(), - emailController = TextEditingController(), - passwordController = TextEditingController() { - loginCommand = Command0(loginWithEmailAndPassword); + LoginViewModel() { + loginCommand = Command1(loginWithEmailAndPassword); validadeTokenCommand = Command0(validateToken); validadeTokenCommand.execute(); } - Future> loginWithEmailAndPassword() async { - if (!formKey.currentState!.validate()) { - return Result.error(Exception('Valores inválidos')); - } - - await GetIt.instance().login( - LoginRequest(emailController.text, passwordController.text)); + Future> loginWithEmailAndPassword( + LoginRequest loginRequest) async { + await GetIt.instance().login(loginRequest); return Result.value(null); } @@ -44,7 +33,14 @@ class LoginViewModel extends ChangeNotifier { Future loginWithDeviceAuth() async { Log.d('init loginWithDeviceAuth'); - return await LocalAuthentication() - .authenticate(localizedReason: 'Toque com o dedo no sensor para logar'); + var auth = GetIt.instance(); + + if (await auth.canCheckBiometrics && await auth.isDeviceSupported()) { + return auth.authenticate( + localizedReason: 'Toque com o dedo no sensor para logar'); + } else { + Log.d('Device authentication not available, returning true'); + return true; + } } } diff --git a/lib/ui/recover_account/view/recover_account_view.dart b/lib/ui/recover_account/view/recover_account_view.dart index 5baf84c..93bfa77 100644 --- a/lib/ui/recover_account/view/recover_account_view.dart +++ b/lib/ui/recover_account/view/recover_account_view.dart @@ -4,7 +4,7 @@ import 'package:aranduapp/ui/shared/error_popup.dart'; import 'package:aranduapp/ui/shared/text_and_link.dart'; import 'package:aranduapp/ui/shared/text_email.dart'; import 'package:aranduapp/ui/shared/title_slogan.dart'; -import 'package:aranduapp/ui/shared/request_button.dart'; +import 'package:aranduapp/ui/shared/command_button.dart'; import 'package:flutter/material.dart'; import 'package:provider/provider.dart'; @@ -52,7 +52,8 @@ class RecoverAccountScreen extends StatelessWidget { ])), Padding( padding: const EdgeInsets.only(top: 80), - child: Requestbutton( + child: CommandButton( + tap: viewModel.recoverCommand.execute , command: viewModel.recoverCommand, onErrorCallback: (String e) { showDialog( diff --git a/lib/ui/register_account/view/register_account_view.dart b/lib/ui/register_account/view/register_account_view.dart index 3eb4ddb..d107f2b 100644 --- a/lib/ui/register_account/view/register_account_view.dart +++ b/lib/ui/register_account/view/register_account_view.dart @@ -1,7 +1,7 @@ import 'package:aranduapp/ui/shared/or_divider.dart'; import 'package:aranduapp/ui/shared/text_and_link.dart'; import 'package:aranduapp/ui/shared/text_name.dart'; -import 'package:aranduapp/ui/shared/request_button.dart'; +import 'package:aranduapp/ui/shared/command_button.dart'; import 'package:flutter/material.dart'; import 'package:get_it/get_it.dart'; import 'package:provider/provider.dart'; @@ -110,7 +110,8 @@ class RegisterAccountScreen extends StatelessWidget { Widget _buildRegisterButton(BuildContext context) { final viewModel = Provider.of(context); - return Requestbutton( + return CommandButton( + tap: viewModel.registerCommand.execute, command: viewModel.registerCommand, nameButton: 'Registrar', onSuccessCallback: () { diff --git a/lib/ui/shared/request_button.dart b/lib/ui/shared/command_button.dart similarity index 84% rename from lib/ui/shared/request_button.dart rename to lib/ui/shared/command_button.dart index b163008..a59fad8 100644 --- a/lib/ui/shared/request_button.dart +++ b/lib/ui/shared/command_button.dart @@ -1,16 +1,18 @@ import 'package:aranduapp/core/state/command.dart'; import 'package:flutter/material.dart'; -class Requestbutton extends StatelessWidget { - final Command0 command; +class CommandButton extends StatelessWidget { + final Command command; final String nameButton; final VoidCallback onSuccessCallback; + final VoidCallback tap; final ValueChanged onErrorCallback; - const Requestbutton({ + const CommandButton({ super.key, required this.command, + required this.tap, required this.nameButton, required this.onSuccessCallback, required this.onErrorCallback, @@ -37,8 +39,9 @@ class Requestbutton extends StatelessWidget { width: 291, height: 64, child: ElevatedButton( + key: const Key('elevated_button_key'), onPressed: () async { - command.execute(); + tap(); }, child: command.running ? const CircularProgressIndicator(value: null) diff --git a/test/ui/login/view/login_view_test.dart b/test/ui/login/view/login_view_test.dart index 6113b52..0d356bc 100644 --- a/test/ui/login/view/login_view_test.dart +++ b/test/ui/login/view/login_view_test.dart @@ -1,7 +1,9 @@ import 'package:aranduapp/core/state/command.dart'; +import 'package:aranduapp/ui/login/model/login_request.dart'; import 'package:aranduapp/ui/login/view/login_view.dart'; import 'package:aranduapp/ui/login/viewmodel/login_viewmodel.dart'; import 'package:aranduapp/ui/navbar/view/navbar_view.dart'; +import 'package:aranduapp/ui/shared/command_button.dart'; import 'package:aranduapp/ui/shared/text_email.dart'; import 'package:aranduapp/ui/shared/text_password.dart'; import 'package:async/async.dart'; @@ -11,23 +13,23 @@ import 'package:get_it/get_it.dart'; import 'package:mockito/annotations.dart'; import 'package:mockito/mockito.dart'; -@GenerateNiceMocks([MockSpec(), MockSpec()]) +@GenerateNiceMocks( + [MockSpec(), MockSpec(), MockSpec()]) import 'login_view_test.mocks.dart'; void main() { late MockLoginViewModel mockViewModel; - late MockCommand0 mockLoginCommand; + late MockCommand1 mockLoginCommand; late MockCommand0 mockValidadeTokenCommand; setUp(() async { mockViewModel = MockLoginViewModel(); - when(mockViewModel.formKey).thenReturn(GlobalKey()); - when(mockViewModel.emailController).thenReturn(TextEditingController()); - when(mockViewModel.passwordController).thenReturn(TextEditingController()); - mockLoginCommand = MockCommand0(); - when(mockLoginCommand.execute()) + mockLoginCommand = MockCommand1(); + + when(mockLoginCommand.execute(any)) .thenAnswer((_) async => Result.value(null)); + when(mockViewModel.loginCommand).thenReturn(mockLoginCommand); mockValidadeTokenCommand = MockCommand0(); @@ -86,6 +88,7 @@ void main() { expect(find.byType(TextEmail), findsOneWidget); expect(find.byType(TextPassWord), findsOneWidget); + expect(find.byType(CommandButton), findsOneWidget); expect(find.text('Entrar'), findsOneWidget); }); @@ -102,28 +105,38 @@ void main() { await tester.enterText(find.byType(TextEmail), email); await tester.enterText(find.byType(TextPassWord), password); - expect(mockViewModel.emailController.text, email); - expect(mockViewModel.passwordController.text, password); + await tester.tap(find.byKey(const Key('elevated_button_key'))); + + verify(mockLoginCommand.execute(argThat( + predicate( + (req) => req.email == email && req.password == password), + ))).called(1); }); testWidgets('Login is successful', (WidgetTester tester) async { when(mockValidadeTokenCommand.isError).thenReturn(true); + when(mockLoginCommand.execute(any)) + .thenAnswer((_) async => Result.value(null)); await tester.pumpWidget(createLoginScreen()); await tester.pumpAndSettle(); - when(mockLoginCommand.execute()) - .thenAnswer((_) async => Result.value(null)); + const email = 'test@example.com'; + const password = 'password123'; - await tester.tap(find.text('Entrar')); + await tester.enterText(find.byType(TextEmail), email); + await tester.enterText(find.byType(TextPassWord), password); + await tester.tap(find.byKey(const Key('elevated_button_key'))); await tester.pumpAndSettle(); - verify(mockLoginCommand.execute()).called(1); + verify(mockLoginCommand.execute(argThat( + predicate( + (req) => req.email == email && req.password == password) + ))).called(1); - //TODO: verify navigation to navbar + // TODO: Verify navigation to navbar }); - testWidgets('Displays error when login fails', (WidgetTester tester) async { //TODO: }); diff --git a/test/ui/login/view/login_view_test.mocks.dart b/test/ui/login/view/login_view_test.mocks.dart index a58f803..d77796b 100644 --- a/test/ui/login/view/login_view_test.mocks.dart +++ b/test/ui/login/view/login_view_test.mocks.dart @@ -6,9 +6,9 @@ import 'dart:async' as _i6; import 'package:aranduapp/core/state/command.dart' as _i2; -import 'package:aranduapp/ui/login/viewmodel/login_viewmodel.dart' as _i5; -import 'package:async/async.dart' as _i4; -import 'package:flutter/material.dart' as _i3; +import 'package:aranduapp/ui/login/model/login_request.dart' as _i5; +import 'package:aranduapp/ui/login/viewmodel/login_viewmodel.dart' as _i4; +import 'package:async/async.dart' as _i3; import 'package:mockito/mockito.dart' as _i1; // ignore_for_file: type=lint @@ -24,8 +24,9 @@ import 'package:mockito/mockito.dart' as _i1; // ignore_for_file: camel_case_types // ignore_for_file: subtype_of_sealed_class -class _FakeCommand0_0 extends _i1.SmartFake implements _i2.Command0 { - _FakeCommand0_0( +class _FakeCommand1_0 extends _i1.SmartFake + implements _i2.Command1 { + _FakeCommand1_0( Object parent, Invocation parentInvocation, ) : super( @@ -34,9 +35,8 @@ class _FakeCommand0_0 extends _i1.SmartFake implements _i2.Command0 { ); } -class _FakeGlobalKey_1> - extends _i1.SmartFake implements _i3.GlobalKey { - _FakeGlobalKey_1( +class _FakeCommand0_1 extends _i1.SmartFake implements _i2.Command0 { + _FakeCommand0_1( Object parent, Invocation parentInvocation, ) : super( @@ -45,19 +45,8 @@ class _FakeGlobalKey_1> ); } -class _FakeTextEditingController_2 extends _i1.SmartFake - implements _i3.TextEditingController { - _FakeTextEditingController_2( - Object parent, - Invocation parentInvocation, - ) : super( - parent, - parentInvocation, - ); -} - -class _FakeResult_3 extends _i1.SmartFake implements _i4.Result { - _FakeResult_3( +class _FakeResult_2 extends _i1.SmartFake implements _i3.Result { + _FakeResult_2( Object parent, Invocation parentInvocation, ) : super( @@ -69,22 +58,23 @@ class _FakeResult_3 extends _i1.SmartFake implements _i4.Result { /// A class which mocks [LoginViewModel]. /// /// See the documentation for Mockito's code generation for more information. -class MockLoginViewModel extends _i1.Mock implements _i5.LoginViewModel { +class MockLoginViewModel extends _i1.Mock implements _i4.LoginViewModel { @override - _i2.Command0 get loginCommand => (super.noSuchMethod( + _i2.Command1 get loginCommand => (super.noSuchMethod( Invocation.getter(#loginCommand), - returnValue: _FakeCommand0_0( + returnValue: _FakeCommand1_0( this, Invocation.getter(#loginCommand), ), - returnValueForMissingStub: _FakeCommand0_0( + returnValueForMissingStub: _FakeCommand1_0( this, Invocation.getter(#loginCommand), ), - ) as _i2.Command0); + ) as _i2.Command1); @override - set loginCommand(_i2.Command0? _loginCommand) => super.noSuchMethod( + set loginCommand(_i2.Command1? _loginCommand) => + super.noSuchMethod( Invocation.setter( #loginCommand, _loginCommand, @@ -95,11 +85,11 @@ class MockLoginViewModel extends _i1.Mock implements _i5.LoginViewModel { @override _i2.Command0 get validadeTokenCommand => (super.noSuchMethod( Invocation.getter(#validadeTokenCommand), - returnValue: _FakeCommand0_0( + returnValue: _FakeCommand0_1( this, Invocation.getter(#validadeTokenCommand), ), - returnValueForMissingStub: _FakeCommand0_0( + returnValueForMissingStub: _FakeCommand0_1( this, Invocation.getter(#validadeTokenCommand), ), @@ -115,45 +105,6 @@ class MockLoginViewModel extends _i1.Mock implements _i5.LoginViewModel { returnValueForMissingStub: null, ); - @override - _i3.GlobalKey<_i3.FormState> get formKey => (super.noSuchMethod( - Invocation.getter(#formKey), - returnValue: _FakeGlobalKey_1<_i3.FormState>( - this, - Invocation.getter(#formKey), - ), - returnValueForMissingStub: _FakeGlobalKey_1<_i3.FormState>( - this, - Invocation.getter(#formKey), - ), - ) as _i3.GlobalKey<_i3.FormState>); - - @override - _i3.TextEditingController get emailController => (super.noSuchMethod( - Invocation.getter(#emailController), - returnValue: _FakeTextEditingController_2( - this, - Invocation.getter(#emailController), - ), - returnValueForMissingStub: _FakeTextEditingController_2( - this, - Invocation.getter(#emailController), - ), - ) as _i3.TextEditingController); - - @override - _i3.TextEditingController get passwordController => (super.noSuchMethod( - Invocation.getter(#passwordController), - returnValue: _FakeTextEditingController_2( - this, - Invocation.getter(#passwordController), - ), - returnValueForMissingStub: _FakeTextEditingController_2( - this, - Invocation.getter(#passwordController), - ), - ) as _i3.TextEditingController); - @override bool get hasListeners => (super.noSuchMethod( Invocation.getter(#hasListeners), @@ -162,36 +113,37 @@ class MockLoginViewModel extends _i1.Mock implements _i5.LoginViewModel { ) as bool); @override - _i6.Future<_i4.Result> loginWithEmailAndPassword() => + _i6.Future<_i3.Result> loginWithEmailAndPassword( + _i5.LoginRequest? loginRequest) => (super.noSuchMethod( Invocation.method( #loginWithEmailAndPassword, - [], + [loginRequest], ), - returnValue: _i6.Future<_i4.Result>.value(_FakeResult_3( + returnValue: _i6.Future<_i3.Result>.value(_FakeResult_2( this, Invocation.method( #loginWithEmailAndPassword, - [], + [loginRequest], ), )), returnValueForMissingStub: - _i6.Future<_i4.Result>.value(_FakeResult_3( + _i6.Future<_i3.Result>.value(_FakeResult_2( this, Invocation.method( #loginWithEmailAndPassword, - [], + [loginRequest], ), )), - ) as _i6.Future<_i4.Result>); + ) as _i6.Future<_i3.Result>); @override - _i6.Future<_i4.Result> validateToken() => (super.noSuchMethod( + _i6.Future<_i3.Result> validateToken() => (super.noSuchMethod( Invocation.method( #validateToken, [], ), - returnValue: _i6.Future<_i4.Result>.value(_FakeResult_3( + returnValue: _i6.Future<_i3.Result>.value(_FakeResult_2( this, Invocation.method( #validateToken, @@ -199,14 +151,14 @@ class MockLoginViewModel extends _i1.Mock implements _i5.LoginViewModel { ), )), returnValueForMissingStub: - _i6.Future<_i4.Result>.value(_FakeResult_3( + _i6.Future<_i3.Result>.value(_FakeResult_2( this, Invocation.method( #validateToken, [], ), )), - ) as _i6.Future<_i4.Result>); + ) as _i6.Future<_i3.Result>); @override _i6.Future loginWithDeviceAuth() => (super.noSuchMethod( @@ -260,18 +212,18 @@ class MockLoginViewModel extends _i1.Mock implements _i5.LoginViewModel { /// See the documentation for Mockito's code generation for more information. class MockCommand0 extends _i1.Mock implements _i2.Command0 { @override - _i6.Future<_i4.Result> Function() get action => (super.noSuchMethod( + _i6.Future<_i3.Result> Function() get action => (super.noSuchMethod( Invocation.getter(#action), - returnValue: () => _i6.Future<_i4.Result>.value(_FakeResult_3( + returnValue: () => _i6.Future<_i3.Result>.value(_FakeResult_2( this, Invocation.getter(#action), )), returnValueForMissingStub: () => - _i6.Future<_i4.Result>.value(_FakeResult_3( + _i6.Future<_i3.Result>.value(_FakeResult_2( this, Invocation.getter(#action), )), - ) as _i6.Future<_i4.Result> Function()); + ) as _i6.Future<_i3.Result> Function()); @override bool get isError => (super.noSuchMethod( @@ -302,12 +254,12 @@ class MockCommand0 extends _i1.Mock implements _i2.Command0 { ) as bool); @override - _i6.Future<_i4.Result> execute() => (super.noSuchMethod( + _i6.Future<_i3.Result> execute() => (super.noSuchMethod( Invocation.method( #execute, [], ), - returnValue: _i6.Future<_i4.Result>.value(_FakeResult_3( + returnValue: _i6.Future<_i3.Result>.value(_FakeResult_2( this, Invocation.method( #execute, @@ -315,14 +267,121 @@ class MockCommand0 extends _i1.Mock implements _i2.Command0 { ), )), returnValueForMissingStub: - _i6.Future<_i4.Result>.value(_FakeResult_3( + _i6.Future<_i3.Result>.value(_FakeResult_2( this, Invocation.method( #execute, [], ), )), - ) as _i6.Future<_i4.Result>); + ) as _i6.Future<_i3.Result>); + + @override + void addListener(dynamic listener) => super.noSuchMethod( + Invocation.method( + #addListener, + [listener], + ), + returnValueForMissingStub: null, + ); + + @override + void removeListener(dynamic listener) => super.noSuchMethod( + Invocation.method( + #removeListener, + [listener], + ), + returnValueForMissingStub: null, + ); + + @override + void dispose() => super.noSuchMethod( + Invocation.method( + #dispose, + [], + ), + returnValueForMissingStub: null, + ); + + @override + void notifyListeners() => super.noSuchMethod( + Invocation.method( + #notifyListeners, + [], + ), + returnValueForMissingStub: null, + ); +} + +/// A class which mocks [Command1]. +/// +/// See the documentation for Mockito's code generation for more information. +class MockCommand1 extends _i1.Mock implements _i2.Command1 { + @override + _i6.Future<_i3.Result> Function(A) get action => (super.noSuchMethod( + Invocation.getter(#action), + returnValue: (A __p0) => + _i6.Future<_i3.Result>.value(_FakeResult_2( + this, + Invocation.getter(#action), + )), + returnValueForMissingStub: (A __p0) => + _i6.Future<_i3.Result>.value(_FakeResult_2( + this, + Invocation.getter(#action), + )), + ) as _i6.Future<_i3.Result> Function(A)); + + @override + bool get isError => (super.noSuchMethod( + Invocation.getter(#isError), + returnValue: false, + returnValueForMissingStub: false, + ) as bool); + + @override + bool get isOk => (super.noSuchMethod( + Invocation.getter(#isOk), + returnValue: false, + returnValueForMissingStub: false, + ) as bool); + + @override + bool get running => (super.noSuchMethod( + Invocation.getter(#running), + returnValue: false, + returnValueForMissingStub: false, + ) as bool); + + @override + bool get hasListeners => (super.noSuchMethod( + Invocation.getter(#hasListeners), + returnValue: false, + returnValueForMissingStub: false, + ) as bool); + + @override + _i6.Future<_i3.Result> execute(A? arg1) => (super.noSuchMethod( + Invocation.method( + #execute, + [arg1], + ), + returnValue: _i6.Future<_i3.Result>.value(_FakeResult_2( + this, + Invocation.method( + #execute, + [arg1], + ), + )), + returnValueForMissingStub: + _i6.Future<_i3.Result>.value(_FakeResult_2( + this, + Invocation.method( + #execute, + [arg1], + ), + )), + ) as _i6.Future<_i3.Result>); @override void addListener(dynamic listener) => super.noSuchMethod( diff --git a/test/ui/login/viewmodel/login_viewmodel_test.dart b/test/ui/login/viewmodel/login_viewmodel_test.dart new file mode 100644 index 0000000..d9c3294 --- /dev/null +++ b/test/ui/login/viewmodel/login_viewmodel_test.dart @@ -0,0 +1,90 @@ +import 'package:aranduapp/ui/login/model/login_request.dart'; +import 'package:aranduapp/ui/login/service/login_service.dart'; +import 'package:aranduapp/ui/login/viewmodel/login_viewmodel.dart'; +import 'package:async/async.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:get_it/get_it.dart'; +import 'package:local_auth/local_auth.dart'; +import 'package:mockito/annotations.dart'; +import 'package:mockito/mockito.dart'; + +import 'login_viewmodel_test.mocks.dart'; + +@GenerateNiceMocks([MockSpec(), MockSpec()]) +void main() { + TestWidgetsFlutterBinding.ensureInitialized(); + + late MockLocalAuthentication mockLocalAuthentication; + late MockLoginService mockLoginService; + late LoginViewModel loginViewModel; + + setUp(() async { + loginViewModel = LoginViewModel(); + mockLocalAuthentication = MockLocalAuthentication(); + mockLoginService = MockLoginService(); + + await GetIt.instance.reset(); + GetIt.I.registerLazySingleton( + () => mockLocalAuthentication); + GetIt.I.registerLazySingleton(() => mockLoginService); + }); + + test('Should validate email and password and login successfully', () async { + const String email = 'test@example.com'; + const String password = 'password123'; + + var loginRequest = LoginRequest(email, password); + + when(mockLoginService.login(LoginRequest(email, password))) + .thenAnswer((_) async { + await Future.delayed(const Duration(milliseconds: 500)); + }); + + await loginViewModel.loginCommand.execute(loginRequest); + + expect(loginViewModel.loginCommand.isOk, isTrue); + verify(mockLoginService.login(loginRequest)).called(1); + }); + + + test('Should validate token successfully', () async { + when(mockLoginService.validateToken()) + .thenAnswer((_) async => Result.value(null)); + + await loginViewModel.validadeTokenCommand.execute(); + + expect(loginViewModel.validadeTokenCommand.isOk, isTrue); + verify(mockLoginService.validateToken()).called(1); + }); + + test('Should authenticate with biometrics if supported', () async { + when(mockLocalAuthentication.canCheckBiometrics) + .thenAnswer((_) async => true); + when(mockLocalAuthentication.isDeviceSupported()) + .thenAnswer((_) async => true); + when(mockLocalAuthentication.authenticate( + localizedReason: anyNamed('localizedReason'))) + .thenAnswer((_) async => true); + + final result = await loginViewModel.loginWithDeviceAuth(); + + expect(result, isTrue); + verify(mockLocalAuthentication.authenticate( + localizedReason: anyNamed('localizedReason'))) + .called(1); + }); + + test('Should return true if device authentication is not available', + () async { + when(mockLocalAuthentication.canCheckBiometrics) + .thenAnswer((_) async => false); + when(mockLocalAuthentication.isDeviceSupported()) + .thenAnswer((_) async => false); + + final result = await loginViewModel.loginWithDeviceAuth(); + + expect(result, isTrue); + verifyNever(mockLocalAuthentication.authenticate( + localizedReason: anyNamed('localizedReason'))); + }); +} diff --git a/test/ui/login/viewmodel/login_viewmodel_test.mocks.dart b/test/ui/login/viewmodel/login_viewmodel_test.mocks.dart new file mode 100644 index 0000000..2b320dd --- /dev/null +++ b/test/ui/login/viewmodel/login_viewmodel_test.mocks.dart @@ -0,0 +1,122 @@ +// Mocks generated by Mockito 5.4.4 from annotations +// in aranduapp/test/ui/login/viewmodel/login_viewmodel_test.dart. +// Do not manually edit this file. + +// ignore_for_file: no_leading_underscores_for_library_prefixes +import 'dart:async' as _i3; + +import 'package:aranduapp/ui/login/model/login_request.dart' as _i8; +import 'package:aranduapp/ui/login/service/login_service.dart' as _i7; +import 'package:local_auth/src/local_auth.dart' as _i2; +import 'package:local_auth_android/local_auth_android.dart' as _i4; +import 'package:local_auth_darwin/local_auth_darwin.dart' as _i5; +import 'package:local_auth_windows/local_auth_windows.dart' as _i6; +import 'package:mockito/mockito.dart' as _i1; + +// ignore_for_file: type=lint +// ignore_for_file: avoid_redundant_argument_values +// ignore_for_file: avoid_setters_without_getters +// ignore_for_file: comment_references +// ignore_for_file: deprecated_member_use +// ignore_for_file: deprecated_member_use_from_same_package +// ignore_for_file: implementation_imports +// ignore_for_file: invalid_use_of_visible_for_testing_member +// ignore_for_file: prefer_const_constructors +// ignore_for_file: unnecessary_parenthesis +// ignore_for_file: camel_case_types +// ignore_for_file: subtype_of_sealed_class + +/// A class which mocks [LocalAuthentication]. +/// +/// See the documentation for Mockito's code generation for more information. +class MockLocalAuthentication extends _i1.Mock + implements _i2.LocalAuthentication { + @override + _i3.Future get canCheckBiometrics => (super.noSuchMethod( + Invocation.getter(#canCheckBiometrics), + returnValue: _i3.Future.value(false), + returnValueForMissingStub: _i3.Future.value(false), + ) as _i3.Future); + + @override + _i3.Future authenticate({ + required String? localizedReason, + Iterable<_i4.AuthMessages>? authMessages = const [ + _i5.IOSAuthMessages(), + _i4.AndroidAuthMessages(), + _i6.WindowsAuthMessages(), + ], + _i4.AuthenticationOptions? options = const _i4.AuthenticationOptions(), + }) => + (super.noSuchMethod( + Invocation.method( + #authenticate, + [], + { + #localizedReason: localizedReason, + #authMessages: authMessages, + #options: options, + }, + ), + returnValue: _i3.Future.value(false), + returnValueForMissingStub: _i3.Future.value(false), + ) as _i3.Future); + + @override + _i3.Future stopAuthentication() => (super.noSuchMethod( + Invocation.method( + #stopAuthentication, + [], + ), + returnValue: _i3.Future.value(false), + returnValueForMissingStub: _i3.Future.value(false), + ) as _i3.Future); + + @override + _i3.Future isDeviceSupported() => (super.noSuchMethod( + Invocation.method( + #isDeviceSupported, + [], + ), + returnValue: _i3.Future.value(false), + returnValueForMissingStub: _i3.Future.value(false), + ) as _i3.Future); + + @override + _i3.Future> getAvailableBiometrics() => + (super.noSuchMethod( + Invocation.method( + #getAvailableBiometrics, + [], + ), + returnValue: + _i3.Future>.value(<_i4.BiometricType>[]), + returnValueForMissingStub: + _i3.Future>.value(<_i4.BiometricType>[]), + ) as _i3.Future>); +} + +/// A class which mocks [LoginService]. +/// +/// See the documentation for Mockito's code generation for more information. +class MockLoginService extends _i1.Mock implements _i7.LoginService { + @override + _i3.Future login(_i8.LoginRequest? loginRequest) => (super.noSuchMethod( + Invocation.method( + #login, + [loginRequest], + ), + returnValue: _i3.Future.value(), + returnValueForMissingStub: _i3.Future.value(), + ) as _i3.Future); + + @override + _i3.Future validateToken() => (super.noSuchMethod( + Invocation.method( + #validateToken, + [], + ), + returnValue: _i3.Future.value(), + returnValueForMissingStub: _i3.Future.value(), + ) as _i3.Future); +} diff --git a/test/ui/recover_account/view/recover_account_test.dart b/test/ui/recover_account/view/recover_account_test.dart index f3371a0..8dbffc5 100644 --- a/test/ui/recover_account/view/recover_account_test.dart +++ b/test/ui/recover_account/view/recover_account_test.dart @@ -2,7 +2,7 @@ import 'package:aranduapp/core/state/command.dart'; import 'package:aranduapp/ui/shared/text_and_link.dart'; import 'package:aranduapp/ui/shared/text_email.dart'; import 'package:aranduapp/ui/shared/title_slogan.dart'; -import 'package:aranduapp/ui/shared/request_button.dart'; +import 'package:aranduapp/ui/shared/command_button.dart'; import 'package:async/async.dart'; import 'package:flutter/material.dart'; import 'package:flutter_test/flutter_test.dart'; @@ -48,7 +48,7 @@ void main() { expect(find.byType(TitleSlogan), findsOneWidget); expect(find.byType(TextEmail), findsOneWidget); - expect(find.byType(Requestbutton), findsOneWidget); + expect(find.byType(CommandButton), findsOneWidget); expect(find.byType(TextAndLink), findsOneWidget); });