From 4d549055fb1596c4bbca3cdbfdf374b2b0c51274 Mon Sep 17 00:00:00 2001 From: Mohit Maulekhi Date: Wed, 25 Dec 2024 20:54:34 +0530 Subject: [PATCH] Test updated --- .../login_view_model.dart | 37 ++-- .../signup_details_view_model.dart | 39 +++-- .../custom_alert_dialog_with_checkbox.dart | 7 - lib/widgets/directly_login.dart | 19 ++- .../login_view_model_test.dart | 85 ++++++--- .../signup_details_view_model_test.dart | 68 ++++++++ ...ustom_alert_dialog_with_checkbox_test.dart | 161 +++++++++++++----- .../widgets/directly_login_test.dart | 18 ++ 8 files changed, 331 insertions(+), 103 deletions(-) diff --git a/lib/view_model/pre_auth_view_models/login_view_model.dart b/lib/view_model/pre_auth_view_models/login_view_model.dart index 7bb922b0f..0a92e5675 100644 --- a/lib/view_model/pre_auth_view_models/login_view_model.dart +++ b/lib/view_model/pre_auth_view_models/login_view_model.dart @@ -173,19 +173,7 @@ class LoginViewModel extends BaseModel { arguments: MainScreenArgs(mainScreenIndex: 0, fromSignUp: false), ); } - try { - await secureStorage.write( - key: "userEmail", - value: this.email.text, - ); - await secureStorage.write( - key: "userPassword", - value: this.password.text, - ); - } catch (e) { - // Handle secure storage write failure - print("Failed to save credentials: $e"); - } + await storingCredentialsInSecureStorage(); }, onActionException: (e) async { print('here'); @@ -195,6 +183,29 @@ class LoginViewModel extends BaseModel { } } + /// Storing credentials in secure storage. + /// + /// **params**: + /// None + /// + /// **returns**: + /// None + Future storingCredentialsInSecureStorage() async { + try { + await secureStorage.write( + key: "userEmail", + value: this.email.text, + ); + await secureStorage.write( + key: "userPassword", + value: this.password.text, + ); + } catch (e) { + // Handle secure storage write failure + print("Failed to save credentials: $e"); + } + } + /// Fetch the previous user credentials. /// /// **params**: diff --git a/lib/view_model/pre_auth_view_models/signup_details_view_model.dart b/lib/view_model/pre_auth_view_models/signup_details_view_model.dart index af29c8c07..7d2c975fb 100644 --- a/lib/view_model/pre_auth_view_models/signup_details_view_model.dart +++ b/lib/view_model/pre_auth_view_models/signup_details_view_model.dart @@ -27,7 +27,7 @@ class SignupDetailsViewModel extends BaseModel { late OrgInfo selectedOrganization; /// Secure local storage instance. - final secureStorage = const FlutterSecureStorage(); + FlutterSecureStorage secureStorage = const FlutterSecureStorage(); /// TextEditingController for handling confirmation password input field. TextEditingController confirmPassword = TextEditingController(); @@ -202,19 +202,7 @@ class SignupDetailsViewModel extends BaseModel { Routes.splashScreen, ); } - try { - await secureStorage.write( - key: "userEmail", - value: this.email.text, - ); - await secureStorage.write( - key: "userPassword", - value: this.password.text, - ); - } catch (e) { - // Handle secure storage write failure - print("Failed to save credentials: $e"); - } + await storingCredentialsInSecureStorage(); } } }, @@ -228,4 +216,27 @@ class SignupDetailsViewModel extends BaseModel { ); } } + + /// Storing credentials in secure storage. + /// + /// **params**: + /// None + /// + /// **returns**: + /// None + Future storingCredentialsInSecureStorage() async { + try { + await secureStorage.write( + key: "userEmail", + value: this.email.text, + ); + await secureStorage.write( + key: "userPassword", + value: this.password.text, + ); + } catch (e) { + // Handle secure storage write failure + print("Failed to save credentials: $e"); + } + } } diff --git a/lib/widgets/custom_alert_dialog_with_checkbox.dart b/lib/widgets/custom_alert_dialog_with_checkbox.dart index ad3ebe579..6617eb232 100644 --- a/lib/widgets/custom_alert_dialog_with_checkbox.dart +++ b/lib/widgets/custom_alert_dialog_with_checkbox.dart @@ -22,7 +22,6 @@ class CustomAlertDialogWithCheckbox extends StatefulWidget { required this.dialogSubTitle, required this.checkboxLabel, this.initialCheckboxValue = false, - this.onCheckboxChanged, }); /// Indicates whether the order of action buttons should be reversed. @@ -52,9 +51,6 @@ class CustomAlertDialogWithCheckbox extends StatefulWidget { /// Initial value for the checkbox. final bool initialCheckboxValue; - /// Callback for checkbox value change. - final ValueChanged? onCheckboxChanged; - @override _CustomAlertDialogWithCheckboxState createState() => _CustomAlertDialogWithCheckboxState(); @@ -128,9 +124,6 @@ class _CustomAlertDialogWithCheckboxState setState(() { _checkboxValue = val!; }); - if (widget.onCheckboxChanged != null) { - widget.onCheckboxChanged?.call(_checkboxValue); - } }, ), ], diff --git a/lib/widgets/directly_login.dart b/lib/widgets/directly_login.dart index e525c189c..c147fb077 100644 --- a/lib/widgets/directly_login.dart +++ b/lib/widgets/directly_login.dart @@ -45,11 +45,7 @@ class DirectlyLogin extends StatelessWidget { color: const Color(0xFF4285F4), ), recognizer: TapGestureRecognizer() - ..onTap = () { - model.email.text = model.prevUserEmail ?? ''; - model.password.text = model.prevUserPassword ?? ''; - model.login(); - }, + ..onTap = loginUsingPrevCredentials, ), ], ), @@ -59,4 +55,17 @@ class DirectlyLogin extends StatelessWidget { }, ); } + + /// This method is used to login useing saved detials. + /// + /// **params**: + /// None + /// + /// **returns**: + /// None + Future loginUsingPrevCredentials() async { + model.email.text = model.prevUserEmail ?? ''; + model.password.text = model.prevUserPassword ?? ''; + await model.login(); + } } diff --git a/test/view_model_tests/pre_auth_view_models/login_view_model_test.dart b/test/view_model_tests/pre_auth_view_models/login_view_model_test.dart index dad04ffa5..0507ecbf0 100644 --- a/test/view_model_tests/pre_auth_view_models/login_view_model_test.dart +++ b/test/view_model_tests/pre_auth_view_models/login_view_model_test.dart @@ -27,6 +27,42 @@ import '../../helpers/test_locator.dart'; // import '../../helpers/test_helpers.dart'; +/// Mock Class for Flutter Secure Storage for error detection. +class MockFlutterSecureStorage extends Mock implements FlutterSecureStorage { + @override + Future write({ + required String key, + required String? value, + IOSOptions? iOptions, + AndroidOptions? aOptions, + LinuxOptions? lOptions, + WebOptions? webOptions, + MacOsOptions? mOptions, + WindowsOptions? wOptions, + }) async { + if (key == "userEmail" || key == "userPassword") { + throw Exception("Storing error"); + } + return Future.value(null); + } + + @override + Future read({ + required String key, + IOSOptions? iOptions, + AndroidOptions? aOptions, + LinuxOptions? lOptions, + WebOptions? webOptions, + MacOsOptions? mOptions, + WindowsOptions? wOptions, + }) async { + if (key == "userEmail" || key == "userPassword") { + throw Exception("Unable to read"); + } + return Future.value(null); + } +} + final data = { 'login': { 'user': { @@ -214,6 +250,36 @@ Future main() async { contains("Unable to read"), ); }); + test('Should handle exception while storing data', () async { + final model = LoginViewModel(); + FlutterSecureStorage.setMockInitialValues( + {"userEmail": "test@example.com", "userPassword": "password123"}, + ); + final mockSecureStorage = MockFlutterSecureStorage(); + model.secureStorage = mockSecureStorage; + + String log = ""; + + await runZonedGuarded( + () async { + await model.storingCredentialsInSecureStorage(); + }, + (error, stack) { + expect(error, isA()); + expect(error.toString(), contains("Storing error")); + expect(stack, isNotNull); + }, + zoneSpecification: ZoneSpecification( + print: (self, parent, zone, line) { + log = line; + }, + ), + ); + expect( + log, + contains("Storing error"), + ); + }); }); } @@ -235,22 +301,3 @@ class MockUserConfig extends Mock implements UserConfig { @override Future updateUser(User user) async => true; } - -/// Mock Class for Flutter Secure Storage. -class MockFlutterSecureStorage extends Mock implements FlutterSecureStorage { - @override - Future read({ - required String key, - IOSOptions? iOptions, - AndroidOptions? aOptions, - LinuxOptions? lOptions, - WebOptions? webOptions, - MacOsOptions? mOptions, - WindowsOptions? wOptions, - }) async { - if (key == "userEmail" || key == "userPassword") { - throw Exception("Unable to read"); - } - return Future.value(null); - } -} diff --git a/test/view_model_tests/pre_auth_view_models/signup_details_view_model_test.dart b/test/view_model_tests/pre_auth_view_models/signup_details_view_model_test.dart index 6b726fb22..1f695308b 100644 --- a/test/view_model_tests/pre_auth_view_models/signup_details_view_model_test.dart +++ b/test/view_model_tests/pre_auth_view_models/signup_details_view_model_test.dart @@ -1,6 +1,8 @@ // ignore_for_file: talawa_api_doc // ignore_for_file: talawa_good_doc_comments +import 'dart:async'; + import 'package:flutter/material.dart'; import 'package:flutter_secure_storage/flutter_secure_storage.dart'; import 'package:flutter_test/flutter_test.dart'; @@ -554,6 +556,36 @@ void main() { ), ); }); + test('Should handle exception while storing data', () async { + final model = SignupDetailsViewModel(); + FlutterSecureStorage.setMockInitialValues( + {"userEmail": "test@example.com", "userPassword": "password123"}, + ); + final mockSecureStorage = MockFlutterSecureStorage(); + model.secureStorage = mockSecureStorage; + + String log = ""; + + await runZonedGuarded( + () async { + await model.storingCredentialsInSecureStorage(); + }, + (error, stack) { + expect(error, isA()); + expect(error.toString(), contains("Storing error")); + expect(stack, isNotNull); + }, + zoneSpecification: ZoneSpecification( + print: (self, parent, zone, line) { + log = line; + }, + ), + ); + expect( + log, + contains("Storing error"), + ); + }); }); } @@ -579,3 +611,39 @@ class MockUserConfig extends Mock implements UserConfig { @override Future updateUserMemberRequestOrg(List orgs) async => null; } + +/// Mock Class for Flutter Secure Storage for error detection. +class MockFlutterSecureStorage extends Mock implements FlutterSecureStorage { + @override + Future write({ + required String key, + required String? value, + IOSOptions? iOptions, + AndroidOptions? aOptions, + LinuxOptions? lOptions, + WebOptions? webOptions, + MacOsOptions? mOptions, + WindowsOptions? wOptions, + }) async { + if (key == "userEmail" || key == "userPassword") { + throw Exception("Storing error"); + } + return Future.value(null); + } + + @override + Future read({ + required String key, + IOSOptions? iOptions, + AndroidOptions? aOptions, + LinuxOptions? lOptions, + WebOptions? webOptions, + MacOsOptions? mOptions, + WindowsOptions? wOptions, + }) async { + if (key == "userEmail" || key == "userPassword") { + throw Exception("Unable to read"); + } + return Future.value(null); + } +} diff --git a/test/widget_tests/widgets/custom_alert_dialog_with_checkbox_test.dart b/test/widget_tests/widgets/custom_alert_dialog_with_checkbox_test.dart index 850d0e1ce..932f56d80 100644 --- a/test/widget_tests/widgets/custom_alert_dialog_with_checkbox_test.dart +++ b/test/widget_tests/widgets/custom_alert_dialog_with_checkbox_test.dart @@ -9,40 +9,58 @@ import 'package:talawa/widgets/raised_round_edge_button.dart'; import '../../helpers/test_locator.dart'; -/// success variable for checkBox. -bool success = false; - -/// cancellation variabe for checkBox. -bool cancelled = false; - -/// Checkbox variable. -bool checkboxValue = false; - -/// Function to run on pressing success. -/// -/// Running this function will idicate that success is chosed and success is working properly. -/// -/// **params**: -/// * `value`: Represents the new value of check -/// -/// **returns**: -/// None -void onSuccess(bool? value) { - success = true; - checkboxValue = value!; -} - -/// Function to run on pressing cancellation. -/// -/// Running this function will idicate that cancel is chosed and cancellation is working properly. -/// -/// **params**: -/// None -/// -/// **returns**: -/// None -void onCancel() { - cancelled = true; +/// Test fixture for all the variables used to test the widget. +class TestFixture { + /// success variable for checkBox. + bool success = false; + + /// cancellation variable for checkBox. + bool cancelled = false; + + /// true when check box is ticked and false when check is not ticked. + bool checkboxValue = false; + + /// Function to reset all values. + /// + /// + /// **params**: + /// None + /// + /// **returns**: + /// None + + void reset() { + success = false; + cancelled = false; + checkboxValue = false; + } + + /// Function to run on pressing success. + /// + /// Running this function will idicate that success is chosed and success is working properly. + /// + /// **params**: + /// * `value`: Represents the new value of check + /// + /// **returns**: + /// None + void onSuccess(bool? value) { + success = true; + checkboxValue = value ?? false; + } + + /// Function to run on pressing cancellation. + /// + /// Running this function will idicate that cancel is chosed and cancellation is working properly. + /// + /// **params**: + /// None + /// + /// **returns**: + /// None + void onCancel() { + cancelled = true; + } } /// Function to run on pressing cancellation. @@ -50,6 +68,7 @@ void onCancel() { /// Running this function will idicate that cancel is chosed and cancellation is working properly. /// /// **params**: +/// * `fixture`: fixture of variable for testing /// * `reverse`: Indicates whether the order of action buttons should be reversed. /// * `dialogTitle`: Title displayed in the dialog. /// * `initialCheckboxValue`: Initial value for the checkbox. @@ -58,6 +77,7 @@ void onCancel() { /// **returns**: /// * `Widget`: Widget that we will be using for testing Widget createCustomAlertDialogWithCheckbox({ + required TestFixture fixture, bool reverse = false, String? dialogTitle, bool initialCheckboxValue = false, @@ -88,8 +108,8 @@ Widget createCustomAlertDialogWithCheckbox({ onPressed: () { navigationService.pushDialog( CustomAlertDialogWithCheckbox( - success: onSuccess, - secondaryButtonTap: passSecondaryFunc ? onCancel : null, + success: fixture.onSuccess, + secondaryButtonTap: passSecondaryFunc ? fixture.onCancel : null, dialogSubTitle: 'This is a subtitle', checkboxLabel: 'Remember me', reverse: reverse, @@ -103,15 +123,22 @@ Widget createCustomAlertDialogWithCheckbox({ } void main() { + final fixture = TestFixture(); + setUpAll(() { TestWidgetsFlutterBinding.ensureInitialized(); SizeConfig().test(); testSetupLocator(); }); + setUp(() { + fixture.reset(); + }); + group('Test for CustomAlertDialogWithCheckbox', () { testWidgets('Check if the AlertDialog shows up', (tester) async { - await tester.pumpWidget(createCustomAlertDialogWithCheckbox()); + await tester + .pumpWidget(createCustomAlertDialogWithCheckbox(fixture: fixture)); await tester.pump(); await tester.tap(find.textContaining('Open')); await tester.pump(const Duration(seconds: 1)); @@ -120,7 +147,8 @@ void main() { }); testWidgets('Check for the RaisedRoundedButtons', (tester) async { - await tester.pumpWidget(createCustomAlertDialogWithCheckbox()); + await tester + .pumpWidget(createCustomAlertDialogWithCheckbox(fixture: fixture)); await tester.pump(); await tester.tap(find.textContaining('Open')); await tester.pump(const Duration(seconds: 1)); @@ -129,7 +157,8 @@ void main() { }); testWidgets('Check if the Confirm button works', (tester) async { - await tester.pumpWidget(createCustomAlertDialogWithCheckbox()); + await tester + .pumpWidget(createCustomAlertDialogWithCheckbox(fixture: fixture)); await tester.pump(); await tester.tap(find.textContaining('Open')); await tester.pump(const Duration(seconds: 1)); @@ -139,12 +168,13 @@ void main() { await tester.tap(buttonFinder.last); await tester.pump(); - expect(success, true); + expect(fixture.success, true); }); testWidgets('Check if the Cancel button works (with secondary func)', (tester) async { - await tester.pumpWidget(createCustomAlertDialogWithCheckbox()); + await tester + .pumpWidget(createCustomAlertDialogWithCheckbox(fixture: fixture)); await tester.pump(); await tester.tap(find.textContaining('Open')); await tester.pump(const Duration(seconds: 1)); @@ -160,7 +190,10 @@ void main() { testWidgets('Check if the Cancel button works (without secondary func)', (tester) async { await tester.pumpWidget( - createCustomAlertDialogWithCheckbox(passSecondaryFunc: false), + createCustomAlertDialogWithCheckbox( + fixture: fixture, + passSecondaryFunc: false, + ), ); await tester.pump(); await tester.tap(find.textContaining('Open')); @@ -177,7 +210,10 @@ void main() { testWidgets('Check if the checkbox is present and clickable', (tester) async { await tester.pumpWidget( - createCustomAlertDialogWithCheckbox(initialCheckboxValue: false), + createCustomAlertDialogWithCheckbox( + fixture: fixture, + initialCheckboxValue: false, + ), ); await tester.pump(); await tester.tap(find.textContaining('Open')); @@ -197,7 +233,10 @@ void main() { testWidgets('Check if the checkbox value is passed to success function', (tester) async { await tester.pumpWidget( - createCustomAlertDialogWithCheckbox(initialCheckboxValue: false), + createCustomAlertDialogWithCheckbox( + fixture: fixture, + initialCheckboxValue: false, + ), ); await tester.pump(); await tester.tap(find.textContaining('Open')); @@ -211,7 +250,39 @@ void main() { await tester.tap(buttonFinder.last); await tester.pump(); - expect(checkboxValue, true); + expect(fixture.checkboxValue, true); + }); + + testWidgets('Check if reverse parameter changes button order', + (tester) async { + await tester.pumpWidget( + createCustomAlertDialogWithCheckbox( + fixture: fixture, + reverse: true, + ), + ); + await tester.pump(); + await tester.tap(find.textContaining('Open')); + await tester.pump(const Duration(seconds: 1)); + + final buttons = find.byType(RaisedRoundedButton); + expect(buttons, findsNWidgets(2)); + + // When reversed, the Confirm button should be first and Close button second + expect( + find.descendant( + of: buttons.first, + matching: find.text('Confirm'), + ), + findsOneWidget, + ); + expect( + find.descendant( + of: buttons.last, + matching: find.text('Close'), + ), + findsOneWidget, + ); }); }); } diff --git a/test/widget_tests/widgets/directly_login_test.dart b/test/widget_tests/widgets/directly_login_test.dart index cf3757c14..6764a69c6 100644 --- a/test/widget_tests/widgets/directly_login_test.dart +++ b/test/widget_tests/widgets/directly_login_test.dart @@ -13,6 +13,9 @@ class MockLoginViewModel extends LoginViewModel { /// Mock email for testing. String? mockPassword; + /// To verify if model's login function called. + bool isLoginCalled = false; + @override Future fetchPrevUser() async { await Future.delayed(const Duration(milliseconds: 500)); @@ -26,6 +29,7 @@ class MockLoginViewModel extends LoginViewModel { @override Future login() async { + isLoginCalled = true; await Future.delayed(const Duration(milliseconds: 500)); } } @@ -35,6 +39,7 @@ void main() { setUp(() { mockLoginViewModel = MockLoginViewModel(); + mockLoginViewModel.isLoginCalled = false; }); testWidgets('Displays email and triggers login on tap', @@ -140,4 +145,17 @@ void main() { expect(mockLoginViewModel.password.text, ""); expect(find.byType(SizedBox), findsOneWidget); }); + test( + "test to check if login and instantiated and value of prev credential is set to current data when email inside gesture detector is tapped", + () async { + final model = MockLoginViewModel(); + final directLogin = DirectlyLogin(model: model); + await model.fetchPrevUser(); + model.mockEmail = "test@gmail.com"; + model.mockPassword = "Test@1234"; + await directLogin.loginUsingPrevCredentials(); + expect(model.email.text, "test@gmail.com"); + expect(model.password.text, "Test@1234"); + expect(model.isLoginCalled, true); + }); }