From 0653cdb6d7fc3eeea99b98679b8f98b3b60d9b82 Mon Sep 17 00:00:00 2001 From: amirrezaghabeli Date: Mon, 6 Oct 2025 08:58:36 +0330 Subject: [PATCH 1/2] changes --- lib/main.dart | 89 +++++- lib/view_models/auth_view_model.dart | 8 +- lib/view_models/profile_view_model.dart | 78 +++++ lib/views/profile/pages/m_profile_page.dart | 28 ++ test/fixtures/test_fixtures.dart | 283 ++++++++++++++++++ test/mocks/mock_auth_repository.dart | 97 ++++++ test/mocks/mock_your_tenders_repository.dart | 37 +++ .../auth_view_model_forgot_password_test.dart | 107 +++++++ .../auth_view_model_initialization_test.dart | 62 ++++ .../auth_view_model_login_test.dart | 86 ++++++ .../auth_view_model_logout_test.dart | 84 ++++++ .../view_models/auth_view_model_otp_test.dart | 122 ++++++++ .../auth_view_model_reset_password_test.dart | 140 +++++++++ .../auth_view_model_utility_test.dart | 128 ++++++++ .../auth_view_model_validation_test.dart | 94 ++++++ ...your_tenders_view_model_feedback_test.dart | 119 ++++++++ .../your_tenders_view_model_filter_test.dart | 137 +++++++++ ...r_tenders_view_model_get_tenders_test.dart | 146 +++++++++ ...enders_view_model_initialization_test.dart | 45 +++ ...ur_tenders_view_model_pagination_test.dart | 116 +++++++ .../your_tenders_view_model_utility_test.dart | 199 ++++++++++++ 21 files changed, 2190 insertions(+), 15 deletions(-) create mode 100644 test/fixtures/test_fixtures.dart create mode 100644 test/mocks/mock_auth_repository.dart create mode 100644 test/mocks/mock_your_tenders_repository.dart create mode 100644 test/view_models/auth_view_model_forgot_password_test.dart create mode 100644 test/view_models/auth_view_model_initialization_test.dart create mode 100644 test/view_models/auth_view_model_login_test.dart create mode 100644 test/view_models/auth_view_model_logout_test.dart create mode 100644 test/view_models/auth_view_model_otp_test.dart create mode 100644 test/view_models/auth_view_model_reset_password_test.dart create mode 100644 test/view_models/auth_view_model_utility_test.dart create mode 100644 test/view_models/auth_view_model_validation_test.dart create mode 100644 test/view_models/your_tenders_view_model_feedback_test.dart create mode 100644 test/view_models/your_tenders_view_model_filter_test.dart create mode 100644 test/view_models/your_tenders_view_model_get_tenders_test.dart create mode 100644 test/view_models/your_tenders_view_model_initialization_test.dart create mode 100644 test/view_models/your_tenders_view_model_pagination_test.dart create mode 100644 test/view_models/your_tenders_view_model_utility_test.dart diff --git a/lib/main.dart b/lib/main.dart index ea5eace..c33f307 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -63,25 +63,88 @@ class _MyAppState extends State { _handleMessage(initialMessage); } - if (kIsWeb) { - FirebaseMessaging.onMessage.listen((event) { - final overlayCtx = rootNavigatorKey.currentContext; - if (overlayCtx != null) { - if (overlayCtx.mounted) { - AppToast.notification( - overlayCtx, - event.notification?.title ?? 'New Notification', - event.notification?.body ?? 'New Notification', - ); - } + // if (kIsWeb) { + FirebaseMessaging.onMessage.listen((event) { + final overlayCtx = rootNavigatorKey.currentContext; + AppLogger().error('Notification received: ${event.notification?.web}'); + AppLogger().error('web link: ${event.notification?.title}'); + AppLogger().error('web link: ${event.notification?.web?.link}'); + print('Notification received1: ${event.notification?.web.toString()}'); + print( + 'Notification received1: ${event.notification?.web?.analyticsLabel}', + ); + + print('web link: ${event.notification?.web?.link}'); + print('Notification received: ${event.notification?.title}'); + AppLogger().error('notif data: ${event.data}'); + AppLogger().error('notif from: ${event.from}'); + AppLogger().error('notif type: ${event.messageType}'); + AppLogger().error('notif keys: ${event.data.keys}'); + AppLogger().info('notif title: ${event.notification?.title}'); + AppLogger().info('notif body: ${event.notification?.body}'); + AppLogger().info('notif data: ${event.data}'); + AppLogger().info('notif from: ${event.from}'); + AppLogger().info('notif type: ${event.messageType}'); + AppLogger().info('notif keys: ${event.data.keys}'); + if (overlayCtx != null) { + if (overlayCtx.mounted) { + AppLogger().error( + 'Notification received: ${event.notification?.web}', + ); + AppLogger().error( + ' link android: ${event.notification?.android?.link}', + ); + AppLogger().error( + 'android link image: ${event.notification?.android?.imageUrl}', + ); + AppLogger().error( + 'android link click action: ${event.notification?.android?.clickAction}', + ); + AppLogger().error( + 'web link image: ${event.notification?.web?.image}', + ); + print('Notification received: ${event.notification?.web}'); + print('web link: ${event.notification?.web?.link}'); + AppToast.notification( + overlayCtx, + event.notification?.title ?? 'New Notification', + event.notification?.body ?? 'New Notification', + ); } - }); - } + } + }); + // } FirebaseMessaging.onMessageOpenedApp.listen(_handleMessage); } void _handleMessage(RemoteMessage message) { + SharedPreferences.getInstance().then((value) { + value.setString('notif_data', message.data.toString()); + value.setString('notif_from', message.from.toString()); + value.setString('notif_type', message.messageType.toString()); + value.setString('notif_keys', message.data.keys.toString()); + value.setString('notif_title', message.notification?.title ?? ''); + value.setString('notif_body', message.notification?.body ?? ''); + value.setString('notif_data', message.contentAvailable.toString()); + value.setString('notif_web_link', message.notification?.web?.link ?? ''); + value.setString( + 'notif_android_link', + message.notification?.android?.link ?? '', + ); + value.setString( + 'notif_web_image', + message.notification?.web?.image ?? '', + ); + value.setString( + 'notif_android_image', + message.notification?.android?.imageUrl ?? '', + ); + value.setString( + 'notif_web_click_action', + message.notification?.web?.link ?? '', + ); + }); appRouter.go( const SplashScreenRoute(navigateToNotification: true).location, ); diff --git a/lib/view_models/auth_view_model.dart b/lib/view_models/auth_view_model.dart index 93f53b0..e3bfecd 100644 --- a/lib/view_models/auth_view_model.dart +++ b/lib/view_models/auth_view_model.dart @@ -147,7 +147,11 @@ class AuthViewModel with ChangeNotifier { } Future localLogout() async { - await _authRepository.localLogout(); + try { + await _authRepository.localLogout(); + } catch (e) { + // Handle local logout exceptions gracefully + } _loggedInUser = null; notifyListeners(); } @@ -160,7 +164,7 @@ class AuthViewModel with ChangeNotifier { switch (result) { case Ok(): - localLogout(); + await localLogout(); break; case Error(): _errorMessage = result.error.toString(); diff --git a/lib/view_models/profile_view_model.dart b/lib/view_models/profile_view_model.dart index 5e2f0f1..5f10ed3 100644 --- a/lib/view_models/profile_view_model.dart +++ b/lib/view_models/profile_view_model.dart @@ -1,4 +1,5 @@ import 'package:flutter/material.dart'; +import 'package:shared_preferences/shared_preferences.dart'; import 'package:tm_app/data/repositories/auth_repository.dart'; import 'package:tm_app/data/services/model/company_profile_data/company_profile_data.dart'; import 'package:tm_app/data/services/model/company_profile_response/company_profile_response.dart'; @@ -29,12 +30,65 @@ class ProfileViewModel with ChangeNotifier { CompanyProfileData? _companyProfileData; bool _loggedOut = false; + // Notification info saved from FCM message in SharedPreferences + String? _notifData; + String? _notifFrom; + String? _notifType; + String? _notifKeys; + String? _notifTitle; + String? _notifBody; + String? _notifWebLink; + String? _notifAndroidLink; + String? _notifWebImage; + String? _notifAndroidImage; + String? _notifWebClickAction; + bool get isLoading => _isLoading; bool get loggedOut => _loggedOut; String? get errorMessage => _errorMessage; ProfileData? get profileData => _profileData; CompanyProfileData? get companyProfileData => _companyProfileData; + // Expose notification info + String? get notifData => _notifData; + String? get notifFrom => _notifFrom; + String? get notifType => _notifType; + String? get notifKeys => _notifKeys; + String? get notifTitle => _notifTitle; + String? get notifBody => _notifBody; + String? get notifWebLink => _notifWebLink; + String? get notifAndroidLink => _notifAndroidLink; + String? get notifWebImage => _notifWebImage; + String? get notifAndroidImage => _notifAndroidImage; + String? get notifWebClickAction => _notifWebClickAction; + + String get notifSummaryText { + final parts = []; + if (_notifTitle != null && _notifTitle!.isNotEmpty) + parts.add('Title: ${_notifTitle!}'); + if (_notifBody != null && _notifBody!.isNotEmpty) + parts.add('Body: ${_notifBody!}'); + if (_notifType != null && _notifType!.isNotEmpty) + parts.add('Type: ${_notifType!}'); + if (_notifFrom != null && _notifFrom!.isNotEmpty) + parts.add('From: ${_notifFrom!}'); + if (_notifKeys != null && _notifKeys!.isNotEmpty) + parts.add('Keys: ${_notifKeys!}'); + if (_notifWebLink != null && _notifWebLink!.isNotEmpty) + parts.add('Web: ${_notifWebLink!}'); + if (_notifAndroidLink != null && _notifAndroidLink!.isNotEmpty) + parts.add('Android: ${_notifAndroidLink!}'); + if (_notifWebImage != null && _notifWebImage!.isNotEmpty) + parts.add('WebImg: ${_notifWebImage!}'); + if (_notifAndroidImage != null && _notifAndroidImage!.isNotEmpty) + parts.add('AndroidImg: ${_notifAndroidImage!}'); + if (_notifWebClickAction != null && _notifWebClickAction!.isNotEmpty) + parts.add('Click: ${_notifWebClickAction!}'); + if (_notifData != null && _notifData!.isNotEmpty) + parts.add('Data: ${_notifData!}'); + return parts.isEmpty ? '-' : parts.join(' | '); + } + Future getProfile() async { _isLoading = true; _errorMessage = null; @@ -98,4 +152,28 @@ class ProfileViewModel with ChangeNotifier { _loggedOut = false; notifyListeners(); } + + Future loadNotificationInfoFromPrefs() async { + // Do not toggle global loading; this is a lightweight local load + try { + final prefs = await SharedPreferences.getInstance(); + _notifData = prefs.getString('notif_data'); + _notifFrom = prefs.getString('notif_from'); + _notifType = prefs.getString('notif_type'); + _notifKeys = prefs.getString('notif_keys'); + _notifTitle = prefs.getString('notif_title'); + _notifBody = prefs.getString('notif_body'); + _notifWebLink = prefs.getString('notif_web_link'); + _notifAndroidLink = prefs.getString('notif_android_link'); + _notifWebImage = prefs.getString('notif_web_image'); + _notifAndroidImage = prefs.getString('notif_android_image'); + _notifWebClickAction = prefs.getString('notif_web_click_action'); + } catch (e) { + // Non-fatal; surface as errorMessage so UI can toast if listening + _errorMessage = e.toString(); + } + notifyListeners(); + _errorMessage = null; + notifyListeners(); + } } diff --git a/lib/views/profile/pages/m_profile_page.dart b/lib/views/profile/pages/m_profile_page.dart index b244b8f..d99325a 100644 --- a/lib/views/profile/pages/m_profile_page.dart +++ b/lib/views/profile/pages/m_profile_page.dart @@ -28,6 +28,10 @@ class _MobileProfilePageState extends State { super.initState(); viewModel = context.read(); viewModel.addListener(_profileListener); + // Load notification info saved in SharedPreferences + WidgetsBinding.instance.addPostFrameCallback((_) { + viewModel.loadNotificationInfoFromPrefs(); + }); } void _profileListener() { @@ -75,6 +79,30 @@ class _MobileProfilePageState extends State { child: Column( children: [ SizedBox(height: 36.0.h()), + Align( + alignment: AlignmentDirectional.centerStart, + child: Text( + 'Last Notification', + style: TextStyle( + fontSize: 18.0.sp(), + fontWeight: FontWeight.w600, + color: AppColors.grey70, + ), + ), + ), + SizedBox(height: 8.0.h()), + Align( + alignment: AlignmentDirectional.centerStart, + child: Text( + viewModel.notifSummaryText, + style: TextStyle( + fontSize: 14.0.sp(), + fontWeight: FontWeight.w400, + color: AppColors.grey60, + ), + ), + ), + SizedBox(height: 24.0.h()), TitleDescription( title: ProfileStrings.companyName, description: viewModel.companyProfileData!.name!, diff --git a/test/fixtures/test_fixtures.dart b/test/fixtures/test_fixtures.dart new file mode 100644 index 0000000..1e1cb14 --- /dev/null +++ b/test/fixtures/test_fixtures.dart @@ -0,0 +1,283 @@ +import 'package:tm_app/data/services/model/customer/customer.dart'; +import 'package:tm_app/data/services/model/forgot_password_response/forgot_password_response_model.dart'; +import 'package:tm_app/data/services/model/liked_tender_feedback/liked_tender_feedback.dart'; +import 'package:tm_app/data/services/model/liked_tenders_data/liked_tenders_data.dart'; +import 'package:tm_app/data/services/model/liked_tenders_response/liked_tenders_response.dart'; +import 'package:tm_app/data/services/model/login_data/login_data.dart'; +import 'package:tm_app/data/services/model/login_response/login_response_model.dart'; +import 'package:tm_app/data/services/model/logout_response/logout_response.dart'; +import 'package:tm_app/data/services/model/meta/meta.dart'; +import 'package:tm_app/data/services/model/reset_password_response/reset_password_response.dart'; +import 'package:tm_app/data/services/model/tender_approvals_data/tender_approvals_data.dart'; +import 'package:tm_app/data/services/model/tender_approvals_data_warpper/tender_approvals_data_wrapper.dart'; +import 'package:tm_app/data/services/model/tender_approvals_response/tender_approvals_response.dart'; +import 'package:tm_app/data/services/model/tender_data/tender_data.dart'; +import 'package:tm_app/data/services/model/verify_otp_data/verify_otp_data.dart'; +import 'package:tm_app/data/services/model/verify_otp_response/verify_otp_response_model.dart'; + +class TestFixtures { + static const customer = Customer( + id: '1', + type: 'individual', + status: 'active', + firstName: 'John', + lastName: 'Doe', + fullName: 'John Doe', + email: 'john.doe@example.com', + phone: '+1234567890', + mobile: '+1234567890', + registrationNumber: 'REG123', + taxId: 'TAX123', + industry: 'Technology', + businessType: 'Software', + employeeCount: 10, + annualRevenue: 1000000, + foundedYear: 2020, + isVerified: true, + isCompliant: true, + complianceNotes: 'Compliant', + language: 'en', + username: 'johndoe', + currency: 'USD', + timezone: 'UTC', + createdAt: 1640995200, + updatedAt: 1640995200, + createdBy: 'admin', + updatedBy: 'admin', + address: null, + contactPersons: null, + ); + + static const loginData = LoginData( + customer: customer, + error: null, + accessToken: 'test_token', + refreshToken: 'test_refresh_token', + expiresAt: 1640995200, + ); + + static const loginResponse = LoginResponseModel( + success: true, + message: 'Login successful', + data: loginData, + ); + + static const logoutResponse = LogoutResponse( + success: true, + message: 'Logout successful', + error: null, + ); + + static const forgotPasswordResponse = ForgotPasswordResponseModel( + success: true, + message: 'OTP sent to email', + data: null, + error: null, + meta: null, + ); + + static const verifyOtpData = VerifyOtpData( + message: 'OTP verified successfully', + success: true, + token: 'reset_token_123', + ); + + static const verifyOtpResponse = VerifyOtpResponseModel( + success: true, + message: 'OTP verified successfully', + data: verifyOtpData, + ); + + static const resetPasswordResponse = ResetPasswordResponse( + success: true, + message: 'Password reset successfully', + error: null, + ); + + // Tender test fixtures + static const testTenderData = TenderData( + success: true, + message: 'Success', + id: '1', + tenderId: '1', + noticePublicationId: '1', + publicationDate: 1640995200, + title: 'Test Tender', + description: 'Test Description', + procurementTypeCode: 'test', + procedureCode: 'test', + estimatedValue: 100000, + currency: 'USD', + duration: '30', + tenderDeadline: 1640995200, + submissionDeadline: 1640995200, + applicationDeadline: 1640995200, + submissionUrl: 'https://test.com', + countryCode: 'US', + durationUnit: 'days', + buyerOrganization: null, + status: 'submitted', + ); + + static const testTenderApprovalsData = TenderApprovalsData( + id: '1', + status: 'submitted', + tenderId: '1', + companyId: '1', + submissionMode: 'online', + createdAt: 1640995200, + tender: testTenderData, + ); + + static const testMeta = Meta( + limit: 10, + offset: 0, + page: 1, + pages: 2, + total: 15, + ); + + static const testTenderApprovalsDataWrapper = TenderApprovalsDataWrapper( + tenders: [testTenderApprovalsData], + metadata: testMeta, + ); + + static const tenderApprovalsResponse = TenderApprovalsResponse( + success: true, + error: null, + data: testTenderApprovalsDataWrapper, + ); + + static const testLikedTenderFeedback = LikedTenderFeedback( + id: '1', + feedbackType: 'liked', + updatedAt: 1640995200, + createdAt: 1640995200, + tenderId: '1', + customerId: '1', + companyId: '1', + tender: testTenderData, + ); + + static const testLikedTenderFeedback2 = LikedTenderFeedback( + id: '2', + feedbackType: 'liked', + updatedAt: 1640995200, + createdAt: 1640995200, + tenderId: '2', + customerId: '1', + companyId: '1', + tender: testTenderData, + ); + + static const testLikedTenderFeedback3 = LikedTenderFeedback( + id: '3', + feedbackType: 'liked', + updatedAt: 1640995200, + createdAt: 1640995200, + tenderId: '3', + customerId: '1', + companyId: '1', + tender: testTenderData, + ); + + static const testLikedTenderFeedback4 = LikedTenderFeedback( + id: '4', + feedbackType: 'liked', + updatedAt: 1640995200, + createdAt: 1640995200, + tenderId: '4', + customerId: '1', + companyId: '1', + tender: testTenderData, + ); + + static const testLikedTenderFeedback5 = LikedTenderFeedback( + id: '5', + feedbackType: 'liked', + updatedAt: 1640995200, + createdAt: 1640995200, + tenderId: '5', + customerId: '1', + companyId: '1', + tender: testTenderData, + ); + + static const testLikedTenderFeedback6 = LikedTenderFeedback( + id: '6', + feedbackType: 'liked', + updatedAt: 1640995200, + createdAt: 1640995200, + tenderId: '6', + customerId: '1', + companyId: '1', + tender: testTenderData, + ); + + static const testLikedTenderFeedback7 = LikedTenderFeedback( + id: '7', + feedbackType: 'liked', + updatedAt: 1640995200, + createdAt: 1640995200, + tenderId: '7', + customerId: '1', + companyId: '1', + tender: testTenderData, + ); + + static const testLikedTenderFeedback8 = LikedTenderFeedback( + id: '8', + feedbackType: 'liked', + updatedAt: 1640995200, + createdAt: 1640995200, + tenderId: '8', + customerId: '1', + companyId: '1', + tender: testTenderData, + ); + + static const testLikedTenderFeedback9 = LikedTenderFeedback( + id: '9', + feedbackType: 'liked', + updatedAt: 1640995200, + createdAt: 1640995200, + tenderId: '9', + customerId: '1', + companyId: '1', + tender: testTenderData, + ); + + static const testLikedTenderFeedback10 = LikedTenderFeedback( + id: '10', + feedbackType: 'liked', + updatedAt: 1640995200, + createdAt: 1640995200, + tenderId: '10', + customerId: '1', + companyId: '1', + tender: testTenderData, + ); + + static const testLikedTendersData = LikedTendersData( + feedback: [ + testLikedTenderFeedback, + testLikedTenderFeedback2, + testLikedTenderFeedback3, + testLikedTenderFeedback4, + testLikedTenderFeedback5, + testLikedTenderFeedback6, + testLikedTenderFeedback7, + testLikedTenderFeedback8, + testLikedTenderFeedback9, + testLikedTenderFeedback10, + ], + meta: testMeta, + ); + + static const likedTendersResponse = LikedTendersResponse( + success: true, + message: 'Success', + data: testLikedTendersData, + error: null, + ); +} diff --git a/test/mocks/mock_auth_repository.dart b/test/mocks/mock_auth_repository.dart new file mode 100644 index 0000000..c88b1f1 --- /dev/null +++ b/test/mocks/mock_auth_repository.dart @@ -0,0 +1,97 @@ +import 'package:tm_app/core/utils/result.dart'; +import 'package:tm_app/data/repositories/auth_repository.dart'; +import 'package:tm_app/data/services/model/forgot_password_response/forgot_password_response_model.dart'; +import 'package:tm_app/data/services/model/login_response/login_response_model.dart'; +import 'package:tm_app/data/services/model/logout_response/logout_response.dart'; +import 'package:tm_app/data/services/model/reset_password_response/reset_password_response.dart'; +import 'package:tm_app/data/services/model/verify_otp_response/verify_otp_response_model.dart'; + +import '../fixtures/test_fixtures.dart'; + +class MockAuthRepository implements AuthRepository { + Result? _loginResult; + Result? _logoutResult; + Result? _forgotPasswordResult; + Result? _verifyOtpResult; + Result? _resetPasswordResult; + Result? _checkIsLoggedInResult; + Exception? _localLogoutException; + + void setLoginResult(Result result) { + _loginResult = result; + } + + void setLogoutResult(Result result) { + _logoutResult = result; + } + + void setForgotPasswordResult(Result result) { + _forgotPasswordResult = result; + } + + void setVerifyOtpResult(Result result) { + _verifyOtpResult = result; + } + + void setResetPasswordResult(Result result) { + _resetPasswordResult = result; + } + + void setCheckIsLoggedInResult(Result result) { + _checkIsLoggedInResult = result; + } + + void setLocalLogoutException(Exception exception) { + _localLogoutException = exception; + } + + @override + Future> login({ + required String username, + required String password, + }) async { + return _loginResult ?? const Result.ok(TestFixtures.loginResponse); + } + + @override + Future> logout() async { + return _logoutResult ?? const Result.ok(TestFixtures.logoutResponse); + } + + @override + Future> resetPassword({ + required String newPassword, + required String token, + }) async { + return _resetPasswordResult ?? + const Result.ok(TestFixtures.resetPasswordResponse); + } + + @override + Future localLogout() async { + if (_localLogoutException != null) { + throw _localLogoutException!; + } + } + + @override + Future> forgotPassword({ + required String email, + }) async { + return _forgotPasswordResult ?? + const Result.ok(TestFixtures.forgotPasswordResponse); + } + + @override + Future> verifyOtp({ + required String code, + required String email, + }) async { + return _verifyOtpResult ?? const Result.ok(TestFixtures.verifyOtpResponse); + } + + @override + Future> checkIsLoggedIn() async { + return _checkIsLoggedInResult ?? const Result.ok(true); + } +} diff --git a/test/mocks/mock_your_tenders_repository.dart b/test/mocks/mock_your_tenders_repository.dart new file mode 100644 index 0000000..319f7f2 --- /dev/null +++ b/test/mocks/mock_your_tenders_repository.dart @@ -0,0 +1,37 @@ +import 'package:tm_app/core/utils/result.dart'; +import 'package:tm_app/data/repositories/your_tenders_repository.dart'; +import 'package:tm_app/data/services/model/liked_tenders_response/liked_tenders_response.dart'; +import 'package:tm_app/data/services/model/request_models/get_tenders_request_model/get_tenders_request_model.dart'; +import 'package:tm_app/data/services/model/request_models/tender_feedback_request_model/tender_feedback_request_model.dart'; +import 'package:tm_app/data/services/model/tender_approvals_response/tender_approvals_response.dart'; + +import '../fixtures/test_fixtures.dart'; + +class MockYourTendersRepository implements YourTendersRepository { + Result? _getTendersResult; + Result? _getTendersFeedbackResult; + + void setGetTendersResult(Result result) { + _getTendersResult = result; + } + + void setGetTendersFeedbackResult(Result result) { + _getTendersFeedbackResult = result; + } + + @override + Future> getTenders({ + required GetTendersRequestModel requestModel, + }) async { + return _getTendersResult ?? + const Result.ok(TestFixtures.tenderApprovalsResponse); + } + + @override + Future> getTendersFeedback({ + required TenderFeedbackRequestModel requestModel, + }) async { + return _getTendersFeedbackResult ?? + const Result.ok(TestFixtures.likedTendersResponse); + } +} diff --git a/test/view_models/auth_view_model_forgot_password_test.dart b/test/view_models/auth_view_model_forgot_password_test.dart new file mode 100644 index 0000000..c5049fc --- /dev/null +++ b/test/view_models/auth_view_model_forgot_password_test.dart @@ -0,0 +1,107 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:tm_app/core/utils/result.dart'; +import 'package:tm_app/view_models/auth_view_model.dart'; + +import '../fixtures/test_fixtures.dart'; +import '../mocks/mock_auth_repository.dart'; + +void main() { + group('AuthViewModel Forgot Password', () { + late MockAuthRepository mockRepository; + late AuthViewModel authViewModel; + late BuildContext mockContext; + + setUp(() { + mockRepository = MockAuthRepository(); + authViewModel = AuthViewModel(authRepository: mockRepository); + mockContext = const TestWidget().createElement(); + }); + + tearDown(() { + authViewModel.dispose(); + }); + + test('should send forgot password successfully', () async { + mockRepository.setForgotPasswordResult( + const Result.ok(TestFixtures.forgotPasswordResponse), + ); + + authViewModel.emailController.text = 'test@example.com'; + + await authViewModel.forgotPassword(mockContext); + + expect(authViewModel.isLoadingForgot, false); + expect(authViewModel.successMessage, 'OTP sent to email'); + expect(authViewModel.errorMessageForget, null); + }); + + test('should handle forgot password error', () async { + const errorMessage = 'Email not found'; + mockRepository.setForgotPasswordResult( + Result.error(Exception(errorMessage)), + ); + + authViewModel.emailController.text = 'nonexistent@example.com'; + + await authViewModel.forgotPassword(mockContext); + + // Wait for the microtask to clear the error message + await Future.microtask(() {}); + + expect(authViewModel.isLoadingForgot, false); + expect(authViewModel.errorMessageForget, null); // Cleared after notify + expect(authViewModel.successMessage, null); + }); + + test('should set loading state during forgot password', () async { + mockRepository.setForgotPasswordResult( + const Result.ok(TestFixtures.forgotPasswordResponse), + ); + + authViewModel.emailController.text = 'test@example.com'; + + final forgotPasswordFuture = authViewModel.forgotPassword(mockContext); + + // Check loading state is set + expect(authViewModel.isLoadingForgot, true); + expect(authViewModel.errorMessageForget, null); + expect(authViewModel.successMessage, null); + + await forgotPasswordFuture; + + expect(authViewModel.isLoadingForgot, false); + }); + + test('should clear previous messages before forgot password', () async { + mockRepository.setForgotPasswordResult( + const Result.ok(TestFixtures.forgotPasswordResponse), + ); + + // Set previous error and success messages + authViewModel.emailController.text = 'test@example.com'; + mockRepository.setForgotPasswordResult( + Result.error(Exception('Previous error')), + ); + await authViewModel.forgotPassword(mockContext); + + // Now test successful case + mockRepository.setForgotPasswordResult( + const Result.ok(TestFixtures.forgotPasswordResponse), + ); + await authViewModel.forgotPassword(mockContext); + + expect(authViewModel.errorMessageForget, null); + expect(authViewModel.successMessage, 'OTP sent to email'); + }); + }); +} + +class TestWidget extends StatelessWidget { + const TestWidget({super.key}); + + @override + Widget build(BuildContext context) { + return Container(); + } +} diff --git a/test/view_models/auth_view_model_initialization_test.dart b/test/view_models/auth_view_model_initialization_test.dart new file mode 100644 index 0000000..35d0dff --- /dev/null +++ b/test/view_models/auth_view_model_initialization_test.dart @@ -0,0 +1,62 @@ +import 'package:flutter_test/flutter_test.dart'; +import 'package:tm_app/view_models/auth_view_model.dart'; + +import '../mocks/mock_auth_repository.dart'; + +void main() { + group('AuthViewModel Initialization', () { + late MockAuthRepository mockRepository; + late AuthViewModel authViewModel; + + setUp(() { + mockRepository = MockAuthRepository(); + authViewModel = AuthViewModel(authRepository: mockRepository); + }); + + tearDown(() { + authViewModel.dispose(); + }); + + test('should initialize with default values', () { + expect(authViewModel.isLoading, false); + expect(authViewModel.isLoggedIn, false); + expect(authViewModel.isLoadingForgot, false); + expect(authViewModel.isLoadingOtp, false); + expect(authViewModel.isResetPasswordLoading, false); + expect(authViewModel.errorMessage, null); + expect(authViewModel.errorMessageForget, null); + expect(authViewModel.resetPasswordErrorMessage, null); + expect(authViewModel.errorMessageOtp, null); + expect(authViewModel.successMessageOtp, null); + expect(authViewModel.verifyOtpSuccess, false); + expect(authViewModel.loggedInUser, null); + expect(authViewModel.forgotPasswordMessage, null); + expect(authViewModel.resetToken, null); + expect(authViewModel.resetPasswordSuccess, false); + }); + + test('should initialize controllers with empty text', () { + expect(authViewModel.usernameController.text, isEmpty); + expect(authViewModel.passwordController.text, isEmpty); + expect(authViewModel.emailController.text, isEmpty); + expect(authViewModel.newPasswordController.text, isEmpty); + expect(authViewModel.otpController.text, isEmpty); + }); + + test('should initialize with password visibility obscured', () { + expect(authViewModel.obscurePassword, true); + expect(authViewModel.obscureForgotPassword, true); + }); + + test('should not be login enabled with empty fields', () { + expect(authViewModel.isLoginEnabled, false); + }); + + test('should be login enabled with filled fields', () { + authViewModel.usernameController.text = 'testuser'; + authViewModel.passwordController.text = 'testpass'; + + expect(authViewModel.isLoginEnabled, true); + }); + }); +} diff --git a/test/view_models/auth_view_model_login_test.dart b/test/view_models/auth_view_model_login_test.dart new file mode 100644 index 0000000..e7bc861 --- /dev/null +++ b/test/view_models/auth_view_model_login_test.dart @@ -0,0 +1,86 @@ +import 'package:flutter_test/flutter_test.dart'; +import 'package:tm_app/core/utils/result.dart'; +import 'package:tm_app/view_models/auth_view_model.dart'; + +import '../fixtures/test_fixtures.dart'; +import '../mocks/mock_auth_repository.dart'; + +void main() { + group('AuthViewModel Login', () { + late MockAuthRepository mockRepository; + late AuthViewModel authViewModel; + + setUp(() { + mockRepository = MockAuthRepository(); + authViewModel = AuthViewModel(authRepository: mockRepository); + }); + + tearDown(() { + authViewModel.dispose(); + }); + + test('should login successfully', () async { + mockRepository.setLoginResult( + const Result.ok(TestFixtures.loginResponse), + ); + + authViewModel.usernameController.text = 'testuser'; + authViewModel.passwordController.text = 'testpass'; + + await authViewModel.login(); + + expect(authViewModel.isLoading, false); + expect(authViewModel.loggedInUser, TestFixtures.customer); + expect(authViewModel.usernameController.text, isEmpty); + expect(authViewModel.passwordController.text, isEmpty); + expect(authViewModel.errorMessage, null); + }); + + test('should handle login error', () async { + const errorMessage = 'Invalid credentials'; + mockRepository.setLoginResult(Result.error(Exception(errorMessage))); + + authViewModel.usernameController.text = 'testuser'; + authViewModel.passwordController.text = 'wrongpass'; + + await authViewModel.login(); + + expect(authViewModel.isLoading, false); + expect(authViewModel.loggedInUser, null); + expect(authViewModel.errorMessage, null); // Cleared after notify + }); + + test('should set loading state during login', () async { + mockRepository.setLoginResult( + const Result.ok(TestFixtures.loginResponse), + ); + + authViewModel.usernameController.text = 'testuser'; + authViewModel.passwordController.text = 'testpass'; + + final loginFuture = authViewModel.login(); + + // Check loading state is set + expect(authViewModel.isLoading, true); + expect(authViewModel.errorMessage, null); + + await loginFuture; + + expect(authViewModel.isLoading, false); + }); + + test('should call localLogout before login', () async { + mockRepository.setLoginResult( + const Result.ok(TestFixtures.loginResponse), + ); + + authViewModel.usernameController.text = 'testuser'; + authViewModel.passwordController.text = 'testpass'; + + await authViewModel.login(); + + // Verify that localLogout was called (user is null initially) + expect(authViewModel.loggedInUser, TestFixtures.customer); + }); + }); +} diff --git a/test/view_models/auth_view_model_logout_test.dart b/test/view_models/auth_view_model_logout_test.dart new file mode 100644 index 0000000..19b55a6 --- /dev/null +++ b/test/view_models/auth_view_model_logout_test.dart @@ -0,0 +1,84 @@ +import 'package:flutter_test/flutter_test.dart'; +import 'package:tm_app/core/utils/result.dart'; +import 'package:tm_app/view_models/auth_view_model.dart'; + +import '../fixtures/test_fixtures.dart'; +import '../mocks/mock_auth_repository.dart'; + +void main() { + group('AuthViewModel Logout', () { + late MockAuthRepository mockRepository; + late AuthViewModel authViewModel; + + setUp(() { + mockRepository = MockAuthRepository(); + authViewModel = AuthViewModel(authRepository: mockRepository); + }); + + tearDown(() { + authViewModel.dispose(); + }); + + test('should logout successfully', () async { + mockRepository.setLogoutResult( + const Result.ok(TestFixtures.logoutResponse), + ); + + // Set a logged in user first + authViewModel.usernameController.text = 'testuser'; + authViewModel.passwordController.text = 'testpass'; + mockRepository.setLoginResult( + const Result.ok(TestFixtures.loginResponse), + ); + await authViewModel.login(); + + await authViewModel.logout(); + + expect(authViewModel.loggedInUser, null); + expect(authViewModel.errorMessage, null); // Cleared after notify + }); + + test('should handle logout error', () async { + const errorMessage = 'Logout failed'; + mockRepository.setLogoutResult(Result.error(Exception(errorMessage))); + + await authViewModel.logout(); + + expect(authViewModel.errorMessage, null); // Cleared after notify + }); + + test('should perform local logout', () async { + mockRepository.setLogoutResult( + const Result.ok(TestFixtures.logoutResponse), + ); + + // Set a logged in user first + authViewModel.usernameController.text = 'testuser'; + authViewModel.passwordController.text = 'testpass'; + mockRepository.setLoginResult( + const Result.ok(TestFixtures.loginResponse), + ); + await authViewModel.login(); + + await authViewModel.logout(); + + expect(authViewModel.loggedInUser, null); + }); + + test('should clear error message before logout', () async { + mockRepository.setLogoutResult( + const Result.ok(TestFixtures.logoutResponse), + ); + + // Set an error first + authViewModel.usernameController.text = 'testuser'; + authViewModel.passwordController.text = 'wrongpass'; + mockRepository.setLoginResult(Result.error(Exception('Login failed'))); + await authViewModel.login(); + + await authViewModel.logout(); + + expect(authViewModel.errorMessage, null); + }); + }); +} diff --git a/test/view_models/auth_view_model_otp_test.dart b/test/view_models/auth_view_model_otp_test.dart new file mode 100644 index 0000000..1cf518c --- /dev/null +++ b/test/view_models/auth_view_model_otp_test.dart @@ -0,0 +1,122 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:tm_app/core/utils/result.dart'; +import 'package:tm_app/view_models/auth_view_model.dart'; + +import '../fixtures/test_fixtures.dart'; +import '../mocks/mock_auth_repository.dart'; + +void main() { + group('AuthViewModel OTP Verification', () { + late MockAuthRepository mockRepository; + late AuthViewModel authViewModel; + late BuildContext mockContext; + + setUp(() { + mockRepository = MockAuthRepository(); + authViewModel = AuthViewModel(authRepository: mockRepository); + mockContext = const TestWidget().createElement(); + }); + + tearDown(() { + authViewModel.dispose(); + }); + + test('should verify OTP successfully', () async { + mockRepository.setVerifyOtpResult( + const Result.ok(TestFixtures.verifyOtpResponse), + ); + + authViewModel.otpController.text = '123456'; + authViewModel.emailController.text = 'test@example.com'; + + await authViewModel.verifyOtp(mockContext); + + expect(authViewModel.isLoadingOtp, false); + expect(authViewModel.verifyOtpSuccess, true); + expect(authViewModel.resetToken, 'reset_token_123'); + expect( + authViewModel.successMessageOtp, + null, + ); // Cleared after notify for toast + expect(authViewModel.errorMessageOtp, null); // Cleared after notify + }); + + test('should handle OTP verification error', () async { + const errorMessage = 'Invalid OTP'; + mockRepository.setVerifyOtpResult(Result.error(Exception(errorMessage))); + + authViewModel.otpController.text = '000000'; + authViewModel.emailController.text = 'test@example.com'; + + await authViewModel.verifyOtp(mockContext); + + expect(authViewModel.isLoadingOtp, false); + expect(authViewModel.verifyOtpSuccess, false); + expect(authViewModel.errorMessageOtp, null); // Cleared after notify + expect(authViewModel.successMessageOtp, null); // Cleared after notify + }); + + test('should set loading state during OTP verification', () async { + mockRepository.setVerifyOtpResult( + const Result.ok(TestFixtures.verifyOtpResponse), + ); + + authViewModel.otpController.text = '123456'; + authViewModel.emailController.text = 'test@example.com'; + + final verifyOtpFuture = authViewModel.verifyOtp(mockContext); + + // Check loading state is set + expect(authViewModel.isLoadingOtp, true); + expect(authViewModel.errorMessageOtp, null); + expect(authViewModel.verifyOtpSuccess, false); + + await verifyOtpFuture; + + expect(authViewModel.isLoadingOtp, false); + }); + + test('should clear OTP flow', () { + // Set some state first + authViewModel.otpController.text = '123456'; + authViewModel.resetToken = 'some_token'; + + authViewModel.clearOtpFlow(); + + expect(authViewModel.errorMessageOtp, null); + expect(authViewModel.successMessageOtp, null); + expect(authViewModel.isLoadingOtp, false); + expect(authViewModel.verifyOtpSuccess, false); + expect(authViewModel.otpController.text, isEmpty); + expect(authViewModel.resetToken, 'some_token'); // Should not be cleared + }); + + test('should reset verifyOtpSuccess before verification', () async { + mockRepository.setVerifyOtpResult( + const Result.ok(TestFixtures.verifyOtpResponse), + ); + + // Set verifyOtpSuccess to true first + authViewModel.otpController.text = '123456'; + authViewModel.emailController.text = 'test@example.com'; + await authViewModel.verifyOtp(mockContext); + expect(authViewModel.verifyOtpSuccess, true); + + // Now test with error + mockRepository.setVerifyOtpResult(Result.error(Exception('Invalid OTP'))); + await authViewModel.verifyOtp(mockContext); + + expect(authViewModel.verifyOtpSuccess, false); + }); + }); +} + +class TestWidget extends StatelessWidget { + const TestWidget({super.key}); + + @override + Widget build(BuildContext context) { + return Container(); + } +} diff --git a/test/view_models/auth_view_model_reset_password_test.dart b/test/view_models/auth_view_model_reset_password_test.dart new file mode 100644 index 0000000..1e651c9 --- /dev/null +++ b/test/view_models/auth_view_model_reset_password_test.dart @@ -0,0 +1,140 @@ +import 'package:flutter_test/flutter_test.dart'; +import 'package:tm_app/core/utils/result.dart'; +import 'package:tm_app/view_models/auth_view_model.dart'; + +import '../fixtures/test_fixtures.dart'; +import '../mocks/mock_auth_repository.dart'; + +void main() { + group('AuthViewModel Reset Password', () { + late MockAuthRepository mockRepository; + late AuthViewModel authViewModel; + + setUp(() { + mockRepository = MockAuthRepository(); + authViewModel = AuthViewModel(authRepository: mockRepository); + }); + + tearDown(() { + authViewModel.dispose(); + }); + + test('should reset password successfully', () async { + mockRepository.setResetPasswordResult( + const Result.ok(TestFixtures.resetPasswordResponse), + ); + + authViewModel.resetToken = 'reset_token_123'; + authViewModel.newPasswordController.text = 'NewPass123!'; + + await authViewModel.resetPassword(); + + expect(authViewModel.isResetPasswordLoading, false); + expect(authViewModel.resetPasswordSuccess, true); + expect( + authViewModel.resetPasswordErrorMessage, + null, + ); // Cleared after notify + }); + + test('should handle reset password error', () async { + const errorMessage = 'Invalid token'; + mockRepository.setResetPasswordResult( + Result.error(Exception(errorMessage)), + ); + + authViewModel.resetToken = 'invalid_token'; + authViewModel.newPasswordController.text = 'NewPass123!'; + + await authViewModel.resetPassword(); + + expect(authViewModel.isResetPasswordLoading, false); + expect(authViewModel.resetPasswordSuccess, false); + expect( + authViewModel.resetPasswordErrorMessage, + null, + ); // Cleared after notify + }); + + test('should handle missing reset token', () async { + authViewModel.resetToken = null; + authViewModel.newPasswordController.text = 'NewPass123!'; + + await authViewModel.resetPassword(); + + expect(authViewModel.isResetPasswordLoading, false); + expect(authViewModel.resetPasswordSuccess, false); + expect( + authViewModel.resetPasswordErrorMessage, + 'Reset token is required', + ); + }); + + test('should set loading state during reset password', () async { + mockRepository.setResetPasswordResult( + const Result.ok(TestFixtures.resetPasswordResponse), + ); + + authViewModel.resetToken = 'reset_token_123'; + authViewModel.newPasswordController.text = 'NewPass123!'; + + final resetPasswordFuture = authViewModel.resetPassword(); + + // Check loading state is set + expect(authViewModel.isResetPasswordLoading, true); + expect(authViewModel.resetPasswordErrorMessage, null); + expect(authViewModel.resetPasswordSuccess, false); + + await resetPasswordFuture; + + expect(authViewModel.isResetPasswordLoading, false); + }); + + test('should clear reset password success', () { + // Set reset password success through a successful reset + mockRepository.setResetPasswordResult( + const Result.ok(TestFixtures.resetPasswordResponse), + ); + authViewModel.resetToken = 'reset_token_123'; + authViewModel.newPasswordController.text = 'NewPass123!'; + + authViewModel.clearResetPasswordSuccess(); + + expect(authViewModel.resetPasswordSuccess, false); + }); + + test('should clear reset flow', () { + // Set some state first + authViewModel.resetToken = 'some_token'; + authViewModel.newPasswordController.text = 'some_password'; + + authViewModel.clearResetFlow(); + + expect(authViewModel.resetPasswordErrorMessage, null); + expect(authViewModel.resetPasswordSuccess, false); + expect(authViewModel.isResetPasswordLoading, false); + expect(authViewModel.newPasswordController.text, isEmpty); + expect(authViewModel.resetToken, 'some_token'); // Should not be cleared + }); + + test('should reset success state before reset password', () async { + mockRepository.setResetPasswordResult( + const Result.ok(TestFixtures.resetPasswordResponse), + ); + + // Set resetPasswordSuccess to true first + authViewModel.resetToken = 'reset_token_123'; + authViewModel.newPasswordController.text = 'NewPass123!'; + await authViewModel.resetPassword(); + expect(authViewModel.resetPasswordSuccess, true); + + // Now test with error + mockRepository.setResetPasswordResult( + Result.error(Exception('Reset failed')), + ); + await authViewModel.resetPassword(); + + expect(authViewModel.resetPasswordSuccess, false); + }); + }); +} diff --git a/test/view_models/auth_view_model_utility_test.dart b/test/view_models/auth_view_model_utility_test.dart new file mode 100644 index 0000000..1d72a66 --- /dev/null +++ b/test/view_models/auth_view_model_utility_test.dart @@ -0,0 +1,128 @@ +import 'package:flutter_test/flutter_test.dart'; +import 'package:tm_app/core/utils/result.dart'; +import 'package:tm_app/view_models/auth_view_model.dart'; + +import '../fixtures/test_fixtures.dart'; +import '../mocks/mock_auth_repository.dart'; + +void main() { + group('AuthViewModel Utility Methods', () { + late MockAuthRepository mockRepository; + late AuthViewModel authViewModel; + + setUp(() { + mockRepository = MockAuthRepository(); + authViewModel = AuthViewModel(authRepository: mockRepository); + }); + + tearDown(() { + authViewModel.dispose(); + }); + + group('Password Visibility Toggle', () { + test('should toggle password visibility', () { + expect(authViewModel.obscurePassword, true); + + authViewModel.togglePasswordVisibility(); + expect(authViewModel.obscurePassword, false); + + authViewModel.togglePasswordVisibility(); + expect(authViewModel.obscurePassword, true); + }); + + test('should toggle forgot password visibility', () { + expect(authViewModel.obscureForgotPassword, true); + + authViewModel.toggleForgotPasswordVisibility(); + expect(authViewModel.obscureForgotPassword, false); + + authViewModel.toggleForgotPasswordVisibility(); + expect(authViewModel.obscureForgotPassword, true); + }); + }); + + group('Message Clearing', () { + test('should clear messages', () { + // Set some messages first through login error + mockRepository.setLoginResult(Result.error(Exception('Login failed'))); + authViewModel.usernameController.text = 'testuser'; + authViewModel.passwordController.text = 'wrongpass'; + + authViewModel.clearMessages(); + + expect(authViewModel.successMessage, null); + expect(authViewModel.errorMessage, null); + }); + }); + + group('Check Login Status', () { + test('should check if user is logged in - true', () async { + mockRepository.setCheckIsLoggedInResult(const Result.ok(true)); + + await authViewModel.checkIsLoggedIn(); + + expect(authViewModel.isLoggedIn, true); + }); + + test('should check if user is logged in - false', () async { + mockRepository.setCheckIsLoggedInResult(const Result.ok(false)); + + await authViewModel.checkIsLoggedIn(); + + expect(authViewModel.isLoggedIn, false); + }); + + test('should handle check login error', () async { + mockRepository.setCheckIsLoggedInResult( + Result.error(Exception('Check failed')), + ); + + await authViewModel.checkIsLoggedIn(); + + expect(authViewModel.isLoggedIn, false); + }); + }); + + group('Local Logout', () { + test('should perform local logout', () async { + // Set a logged in user first + authViewModel.usernameController.text = 'testuser'; + authViewModel.passwordController.text = 'testpass'; + mockRepository.setLoginResult( + const Result.ok(TestFixtures.loginResponse), + ); + await authViewModel.login(); + + await authViewModel.localLogout(); + + expect(authViewModel.loggedInUser, null); + }); + + test('should handle local logout exception', () async { + mockRepository.setLocalLogoutException( + Exception('Local logout failed'), + ); + + // Should not throw exception + await authViewModel.localLogout(); + + expect(authViewModel.loggedInUser, null); + }); + }); + + group('Text Change Notification', () { + test('should notify listeners on text change', () { + int notificationCount = 0; + + authViewModel.addListener(() { + notificationCount++; + }); + + // Trigger text change + authViewModel.usernameController.text = 'test'; + + expect(notificationCount, greaterThan(0)); + }); + }); + }); +} diff --git a/test/view_models/auth_view_model_validation_test.dart b/test/view_models/auth_view_model_validation_test.dart new file mode 100644 index 0000000..c790632 --- /dev/null +++ b/test/view_models/auth_view_model_validation_test.dart @@ -0,0 +1,94 @@ +import 'package:flutter_test/flutter_test.dart'; +import 'package:tm_app/view_models/auth_view_model.dart'; + +import '../mocks/mock_auth_repository.dart'; + +void main() { + group('AuthViewModel Validation', () { + late MockAuthRepository mockRepository; + late AuthViewModel authViewModel; + + setUp(() { + mockRepository = MockAuthRepository(); + authViewModel = AuthViewModel(authRepository: mockRepository); + }); + + tearDown(() { + authViewModel.dispose(); + }); + + group('Email Validation', () { + test('should return true for valid email', () { + authViewModel.emailController.text = 'test@example.com'; + expect(authViewModel.isEmailValid, true); + }); + + test('should return false for invalid email', () { + authViewModel.emailController.text = 'invalid-email'; + expect(authViewModel.isEmailValid, false); + }); + + test('should return false for empty email', () { + authViewModel.emailController.text = ''; + expect(authViewModel.isEmailValid, false); + }); + + test('should return false for email without domain', () { + authViewModel.emailController.text = 'test@'; + expect(authViewModel.isEmailValid, false); + }); + }); + + group('OTP Validation', () { + test('should return true for valid 6-digit OTP', () { + authViewModel.otpController.text = '123456'; + expect(authViewModel.isOtpValid, true); + expect(authViewModel.canSubmitOtpPage, true); + }); + + test('should return false for invalid OTP length', () { + authViewModel.otpController.text = '12345'; + expect(authViewModel.isOtpValid, false); + expect(authViewModel.canSubmitOtpPage, false); + }); + + test('should return false for empty OTP', () { + authViewModel.otpController.text = ''; + expect(authViewModel.isOtpValid, false); + expect(authViewModel.canSubmitOtpPage, false); + }); + }); + + group('Password Validation', () { + test('should return true for valid password', () { + authViewModel.newPasswordController.text = 'Test123!'; + expect(authViewModel.isNewPasswordValid, true); + }); + + test('should return false for password without uppercase', () { + authViewModel.newPasswordController.text = 'test123!'; + expect(authViewModel.isNewPasswordValid, false); + }); + + test('should return false for password without lowercase', () { + authViewModel.newPasswordController.text = 'TEST123!'; + expect(authViewModel.isNewPasswordValid, false); + }); + + test('should return false for password without special character', () { + authViewModel.newPasswordController.text = 'Test123'; + expect(authViewModel.isNewPasswordValid, false); + }); + + test('should return false for password too short', () { + authViewModel.newPasswordController.text = 'Te1!'; + expect(authViewModel.isNewPasswordValid, false); + }); + + test('should return false for empty password', () { + authViewModel.newPasswordController.text = ''; + expect(authViewModel.isNewPasswordValid, false); + }); + }); + }); +} diff --git a/test/view_models/your_tenders_view_model_feedback_test.dart b/test/view_models/your_tenders_view_model_feedback_test.dart new file mode 100644 index 0000000..bfabbdc --- /dev/null +++ b/test/view_models/your_tenders_view_model_feedback_test.dart @@ -0,0 +1,119 @@ +import 'package:flutter_test/flutter_test.dart'; +import 'package:tm_app/core/utils/result.dart'; +import 'package:tm_app/view_models/your_tenders_view_model.dart'; + +import '../fixtures/test_fixtures.dart'; +import '../mocks/mock_your_tenders_repository.dart'; + +void main() { + group('YourTendersViewModel GetTendersFeedback', () { + late MockYourTendersRepository mockRepository; + late YourTendersViewModel viewModel; + + setUp(() { + mockRepository = MockYourTendersRepository(); + viewModel = YourTendersViewModel(yourTendersRepository: mockRepository); + }); + + tearDown(() { + viewModel.dispose(); + }); + + test('should get tenders feedback successfully', () async { + mockRepository.setGetTendersFeedbackResult( + const Result.ok(TestFixtures.likedTendersResponse), + ); + + await viewModel.getTendersFeedback(feedbackType: 'liked', reset: true); + + expect(viewModel.isLoading, false); + expect(viewModel.likedTendersData, TestFixtures.likedTendersResponse); + expect(viewModel.errorMessage, null); + expect(viewModel.hasMore, true); + }); + + test('should handle get tenders feedback error', () async { + const errorMessage = 'Network error'; + mockRepository.setGetTendersFeedbackResult( + Result.error(Exception(errorMessage)), + ); + + await viewModel.getTendersFeedback(feedbackType: 'liked', reset: true); + + expect(viewModel.isLoading, false); + expect(viewModel.likedTendersData, null); + expect(viewModel.errorMessage, errorMessage); + }); + + test('should set loading state during get feedback', () async { + mockRepository.setGetTendersFeedbackResult( + const Result.ok(TestFixtures.likedTendersResponse), + ); + + final getFeedbackFuture = viewModel.getTendersFeedback( + feedbackType: 'liked', + reset: true, + ); + + expect(viewModel.isLoading, true); + expect(viewModel.errorMessage, null); + + await getFeedbackFuture; + + expect(viewModel.isLoading, false); + }); + + test('should reset data when reset is true', () async { + // First load + mockRepository.setGetTendersFeedbackResult( + const Result.ok(TestFixtures.likedTendersResponse), + ); + + await viewModel.getTendersFeedback(feedbackType: 'liked', reset: true); + expect(viewModel.likedTendersData, TestFixtures.likedTendersResponse); + + // Reset and load again + await viewModel.getTendersFeedback(feedbackType: 'liked', reset: true); + expect(viewModel.likedTendersData, TestFixtures.likedTendersResponse); + }); + + test('should not load more when already loading', () async { + mockRepository.setGetTendersFeedbackResult( + const Result.ok(TestFixtures.likedTendersResponse), + ); + + viewModel.isLoadingMore = true; + await viewModel.getTendersFeedback(feedbackType: 'liked', reset: false); + + expect(viewModel.likedTendersData, null); + }); + + test('should not load more when no more data', () async { + mockRepository.setGetTendersFeedbackResult( + const Result.ok(TestFixtures.likedTendersResponse), + ); + + viewModel.hasMore = false; + await viewModel.getTendersFeedback(feedbackType: 'liked', reset: false); + + expect(viewModel.likedTendersData, null); + }); + + test('should load more feedback successfully', () async { + mockRepository.setGetTendersFeedbackResult( + const Result.ok(TestFixtures.likedTendersResponse), + ); + + // Set current feedback type + viewModel.getTendersFeedback(feedbackType: 'liked', reset: true); + await viewModel.loadMoreFeedback(); + + expect(viewModel.likedTendersData, TestFixtures.likedTendersResponse); + }); + + test('should not load more feedback when no current type', () async { + await viewModel.loadMoreFeedback(); + expect(viewModel.likedTendersData, null); + }); + }); +} diff --git a/test/view_models/your_tenders_view_model_filter_test.dart b/test/view_models/your_tenders_view_model_filter_test.dart new file mode 100644 index 0000000..cc3e0a4 --- /dev/null +++ b/test/view_models/your_tenders_view_model_filter_test.dart @@ -0,0 +1,137 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:tm_app/core/utils/result.dart'; +import 'package:tm_app/view_models/your_tenders_view_model.dart'; + +import '../fixtures/test_fixtures.dart'; +import '../mocks/mock_your_tenders_repository.dart'; + +void main() { + group('YourTendersViewModel Filter', () { + late MockYourTendersRepository mockRepository; + late YourTendersViewModel viewModel; + + setUp(() { + mockRepository = MockYourTendersRepository(); + viewModel = YourTendersViewModel(yourTendersRepository: mockRepository); + }); + + tearDown(() { + viewModel.dispose(); + }); + + test('should set date range correctly', () { + final dateRange = DateTimeRange( + start: DateTime(2024, 1, 1), + end: DateTime(2024, 1, 31), + ); + + viewModel.setDateRange(dateRange); + + expect(viewModel.selectedDateRange, dateRange); + expect(viewModel.startDateFormatted, '2024/01/01'); + expect(viewModel.endDateFormatted, '2024/01/31'); + expect(viewModel.startDateUnix, '1704067200'); + expect(viewModel.endDateUnix, '1706745600'); + }); + + test('should set status and clear data', () { + // Set some initial data + viewModel.data = TestFixtures.tenderApprovalsResponse; + viewModel.likedTendersData = TestFixtures.likedTendersResponse; + viewModel.isLoading = true; + viewModel.errorMessage = 'Error'; + + viewModel.setStatus('submitted'); + + expect(viewModel.selectedStatus, 'submitted'); + expect(viewModel.data, null); + expect(viewModel.likedTendersData, null); + expect(viewModel.isLoading, false); + expect(viewModel.errorMessage, null); + expect(viewModel.currentPage, 1); + }); + + test('should clear filter and reset state', () { + // Set some initial data + final dateRange = DateTimeRange( + start: DateTime(2024, 1, 1), + end: DateTime(2024, 1, 31), + ); + viewModel.setDateRange(dateRange); + viewModel.setStatus('submitted'); + viewModel.data = TestFixtures.tenderApprovalsResponse; + + viewModel.clearFilter(); + + expect(viewModel.selectedDateRange, null); + expect(viewModel.selectedStatus, 'All'); + expect(viewModel.data, null); + expect(viewModel.likedTendersData, null); + expect(viewModel.isLoading, false); + expect(viewModel.errorMessage, null); + }); + + test('should call with filter for all status', () async { + mockRepository.setGetTendersResult( + const Result.ok(TestFixtures.tenderApprovalsResponse), + ); + + viewModel.selectedStatus = 'All'; + await viewModel.callWithFilter(); + + expect(viewModel.data, TestFixtures.tenderApprovalsResponse); + expect(viewModel.isLoading, false); + }); + + test('should call with filter for submitted status', () async { + mockRepository.setGetTendersResult( + const Result.ok(TestFixtures.tenderApprovalsResponse), + ); + + viewModel.selectedStatus = 'Tenders Submitted'; + await viewModel.callWithFilter(); + + expect(viewModel.data, TestFixtures.tenderApprovalsResponse); + expect(viewModel.isLoading, false); + }); + + test('should call with filter for liked tenders', () async { + mockRepository.setGetTendersFeedbackResult( + const Result.ok(TestFixtures.likedTendersResponse), + ); + + viewModel.selectedStatus = 'Like Tenders'; + await viewModel.callWithFilter(); + + expect(viewModel.likedTendersData, TestFixtures.likedTendersResponse); + expect(viewModel.isLoading, false); + }); + + test('should call with filter for disliked tenders', () async { + mockRepository.setGetTendersFeedbackResult( + const Result.ok(TestFixtures.likedTendersResponse), + ); + + viewModel.selectedStatus = 'Dislike Tenders'; + await viewModel.callWithFilter(); + + expect(viewModel.likedTendersData, TestFixtures.likedTendersResponse); + expect(viewModel.isLoading, false); + }); + + test('should format dates correctly with date range', () { + final dateRange = DateTimeRange( + start: DateTime(2024, 12, 25), + end: DateTime(2024, 12, 31), + ); + + viewModel.setDateRange(dateRange); + + expect(viewModel.startDateFormatted, '2024/12/25'); + expect(viewModel.endDateFormatted, '2024/12/31'); + expect(viewModel.startDateUnix, '1735084800'); + expect(viewModel.endDateUnix, '1735689600'); + }); + }); +} diff --git a/test/view_models/your_tenders_view_model_get_tenders_test.dart b/test/view_models/your_tenders_view_model_get_tenders_test.dart new file mode 100644 index 0000000..bc2f608 --- /dev/null +++ b/test/view_models/your_tenders_view_model_get_tenders_test.dart @@ -0,0 +1,146 @@ +import 'package:flutter_test/flutter_test.dart'; +import 'package:tm_app/core/utils/result.dart'; +import 'package:tm_app/data/services/model/request_models/get_tenders_request_model/get_tenders_request_model.dart'; +import 'package:tm_app/view_models/your_tenders_view_model.dart'; + +import '../fixtures/test_fixtures.dart'; +import '../mocks/mock_your_tenders_repository.dart'; + +void main() { + group('YourTendersViewModel GetTenders', () { + late MockYourTendersRepository mockRepository; + late YourTendersViewModel viewModel; + + setUp(() { + mockRepository = MockYourTendersRepository(); + viewModel = YourTendersViewModel(yourTendersRepository: mockRepository); + }); + + tearDown(() { + viewModel.dispose(); + }); + + test('should get tenders successfully', () async { + mockRepository.setGetTendersResult( + const Result.ok(TestFixtures.tenderApprovalsResponse), + ); + + final requestModel = GetTendersRequestModel( + status: 'submitted', + createdFrom: '1640995200', + createdTo: '1640995200', + limit: 10, + offset: 0, + ); + + await viewModel.getTenders(requestModel: requestModel); + + expect(viewModel.isLoading, false); + expect(viewModel.data, TestFixtures.tenderApprovalsResponse); + expect(viewModel.errorMessage, null); + expect(viewModel.hasMoreTenders, true); + }); + + test('should handle get tenders error', () async { + const errorMessage = 'Network error'; + mockRepository.setGetTendersResult(Result.error(Exception(errorMessage))); + + final requestModel = GetTendersRequestModel( + status: 'submitted', + createdFrom: '1640995200', + createdTo: '1640995200', + limit: 10, + offset: 0, + ); + + await viewModel.getTenders(requestModel: requestModel); + + expect(viewModel.isLoading, false); + expect(viewModel.data, null); + expect(viewModel.errorMessage, errorMessage); + }); + + test('should set loading state during get tenders', () async { + mockRepository.setGetTendersResult( + const Result.ok(TestFixtures.tenderApprovalsResponse), + ); + + final requestModel = GetTendersRequestModel( + status: 'submitted', + createdFrom: '1640995200', + createdTo: '1640995200', + limit: 10, + offset: 0, + ); + + final getTendersFuture = viewModel.getTenders(requestModel: requestModel); + + expect(viewModel.isLoading, true); + expect(viewModel.errorMessage, null); + + await getTendersFuture; + + expect(viewModel.isLoading, false); + }); + + test('should reset data when reset is true', () async { + // First load + mockRepository.setGetTendersResult( + const Result.ok(TestFixtures.tenderApprovalsResponse), + ); + + final requestModel = GetTendersRequestModel( + status: 'submitted', + createdFrom: '1640995200', + createdTo: '1640995200', + limit: 10, + offset: 0, + ); + + await viewModel.getTenders(requestModel: requestModel); + expect(viewModel.data, TestFixtures.tenderApprovalsResponse); + + // Reset and load again + await viewModel.getTenders(requestModel: requestModel, reset: true); + expect(viewModel.data, TestFixtures.tenderApprovalsResponse); + }); + + test('should not load more when already loading', () async { + mockRepository.setGetTendersResult( + const Result.ok(TestFixtures.tenderApprovalsResponse), + ); + + final requestModel = GetTendersRequestModel( + status: 'submitted', + createdFrom: '1640995200', + createdTo: '1640995200', + limit: 10, + offset: 0, + ); + + viewModel.isLoadingMoreTenders = true; + await viewModel.getTenders(requestModel: requestModel); + + expect(viewModel.data, null); + }); + + test('should not load more when no more data', () async { + mockRepository.setGetTendersResult( + const Result.ok(TestFixtures.tenderApprovalsResponse), + ); + + final requestModel = GetTendersRequestModel( + status: 'submitted', + createdFrom: '1640995200', + createdTo: '1640995200', + limit: 10, + offset: 0, + ); + + viewModel.hasMoreTenders = false; + await viewModel.getTenders(requestModel: requestModel); + + expect(viewModel.data, null); + }); + }); +} diff --git a/test/view_models/your_tenders_view_model_initialization_test.dart b/test/view_models/your_tenders_view_model_initialization_test.dart new file mode 100644 index 0000000..b8fdb2c --- /dev/null +++ b/test/view_models/your_tenders_view_model_initialization_test.dart @@ -0,0 +1,45 @@ +import 'package:flutter_test/flutter_test.dart'; +import 'package:tm_app/view_models/your_tenders_view_model.dart'; + +import '../mocks/mock_your_tenders_repository.dart'; + +void main() { + group('YourTendersViewModel Initialization', () { + late MockYourTendersRepository mockRepository; + late YourTendersViewModel viewModel; + + setUp(() { + mockRepository = MockYourTendersRepository(); + viewModel = YourTendersViewModel(yourTendersRepository: mockRepository); + }); + + tearDown(() { + viewModel.dispose(); + }); + + test('should initialize with default values', () { + expect(viewModel.isLoading, false); + expect(viewModel.errorMessage, null); + expect(viewModel.data, null); + expect(viewModel.likedTendersData, null); + expect(viewModel.isLoadingMore, false); + expect(viewModel.hasMore, true); + expect(viewModel.isLoadingMoreTenders, false); + expect(viewModel.hasMoreTenders, true); + expect(viewModel.selectedDateRange, null); + expect(viewModel.selectedStatus, 'All'); + expect(viewModel.currentPage, 1); + }); + + test('should format dates correctly when no date range is selected', () { + expect(viewModel.startDateFormatted, 'YYYY/MM/DD'); + expect(viewModel.endDateFormatted, 'YYYY/MM/DD'); + expect(viewModel.startDateUnix, ''); + expect(viewModel.endDateUnix, ''); + }); + + test('should calculate total pages correctly when no data', () { + expect(viewModel.totalPages, 1); + }); + }); +} diff --git a/test/view_models/your_tenders_view_model_pagination_test.dart b/test/view_models/your_tenders_view_model_pagination_test.dart new file mode 100644 index 0000000..34d6ccf --- /dev/null +++ b/test/view_models/your_tenders_view_model_pagination_test.dart @@ -0,0 +1,116 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:tm_app/core/utils/result.dart'; +import 'package:tm_app/view_models/your_tenders_view_model.dart'; + +import '../fixtures/test_fixtures.dart'; +import '../mocks/mock_your_tenders_repository.dart'; + +void main() { + group('YourTendersViewModel Pagination', () { + late MockYourTendersRepository mockRepository; + late YourTendersViewModel viewModel; + + setUp(() { + mockRepository = MockYourTendersRepository(); + viewModel = YourTendersViewModel(yourTendersRepository: mockRepository); + }); + + tearDown(() { + viewModel.dispose(); + }); + + test('should calculate total pages from tender data', () { + viewModel.data = TestFixtures.tenderApprovalsResponse; + expect(viewModel.totalPages, 1); + }); + + test('should calculate total pages from feedback data', () { + viewModel.likedTendersData = TestFixtures.likedTendersResponse; + expect(viewModel.totalPages, 1); + }); + + test('should return 1 when no data available', () { + expect(viewModel.totalPages, 1); + }); + + test('should jump to page successfully', () async { + mockRepository.setGetTendersResult( + const Result.ok(TestFixtures.tenderApprovalsResponse), + ); + + viewModel.selectedStatus = 'All'; + await viewModel.jumpToPage(2); + + expect(viewModel.currentPage, 2); + expect(viewModel.data, TestFixtures.tenderApprovalsResponse); + expect(viewModel.likedTendersData, null); + expect(viewModel.hasMore, true); + expect(viewModel.hasMoreTenders, true); + }); + + test('should load more tenders for all status', () async { + mockRepository.setGetTendersResult( + const Result.ok(TestFixtures.tenderApprovalsResponse), + ); + + viewModel.selectedStatus = 'All'; + await viewModel.loadMoreTenders(); + + expect(viewModel.data, TestFixtures.tenderApprovalsResponse); + }); + + test('should load more tenders for submitted status', () async { + mockRepository.setGetTendersResult( + const Result.ok(TestFixtures.tenderApprovalsResponse), + ); + + viewModel.selectedStatus = 'Tenders Submitted'; + await viewModel.loadMoreTenders(); + + expect(viewModel.data, TestFixtures.tenderApprovalsResponse); + }); + + test('should not load more tenders when already loading', () async { + viewModel.isLoadingMoreTenders = true; + await viewModel.loadMoreTenders(); + + expect(viewModel.data, null); + }); + + test('should not load more tenders when no more data', () async { + viewModel.hasMoreTenders = false; + await viewModel.loadMoreTenders(); + + expect(viewModel.data, null); + }); + + test('should handle pagination with date range', () async { + mockRepository.setGetTendersResult( + const Result.ok(TestFixtures.tenderApprovalsResponse), + ); + + viewModel.selectedStatus = 'All'; + viewModel.setDateRange( + DateTimeRange(start: DateTime(2024, 1, 1), end: DateTime(2024, 1, 31)), + ); + + await viewModel.loadMoreTenders(); + + expect(viewModel.data, TestFixtures.tenderApprovalsResponse); + }); + + test('should reset pagination state when clearing data', () { + // Set some pagination state + viewModel.currentPage = 3; + viewModel.data = TestFixtures.tenderApprovalsResponse; + viewModel.likedTendersData = TestFixtures.likedTendersResponse; + + viewModel.clearFilter(); + + expect(viewModel.currentPage, 1); + expect(viewModel.data, null); + expect(viewModel.likedTendersData, null); + }); + }); +} diff --git a/test/view_models/your_tenders_view_model_utility_test.dart b/test/view_models/your_tenders_view_model_utility_test.dart new file mode 100644 index 0000000..daba893 --- /dev/null +++ b/test/view_models/your_tenders_view_model_utility_test.dart @@ -0,0 +1,199 @@ +import 'package:flutter_test/flutter_test.dart'; +import 'package:tm_app/core/constants/assets.dart'; +import 'package:tm_app/core/constants/tender_approval_status.dart'; +import 'package:tm_app/core/constants/tender_feedback.dart'; +import 'package:tm_app/core/theme/colors.dart'; +import 'package:tm_app/view_models/your_tenders_view_model.dart'; + +import '../mocks/mock_your_tenders_repository.dart'; + +void main() { + group('YourTendersViewModel Utility Methods', () { + late MockYourTendersRepository mockRepository; + late YourTendersViewModel viewModel; + + setUp(() { + mockRepository = MockYourTendersRepository(); + viewModel = YourTendersViewModel(yourTendersRepository: mockRepository); + }); + + tearDown(() { + viewModel.dispose(); + }); + + group('getBackGroundColorByStatus', () { + test('should return green for liked status', () { + final color = viewModel.getBackGroundColorByStatus( + TenderFeedback.liked.value, + ); + expect(color, AppColors.green0); + }); + + test('should return green for submitted status', () { + final color = viewModel.getBackGroundColorByStatus( + TenderApprovalStatus.submitted.value, + ); + expect(color, AppColors.green0); + }); + + test('should return red for disliked status', () { + final color = viewModel.getBackGroundColorByStatus( + TenderFeedback.disliked.value, + ); + expect(color, AppColors.red0); + }); + + test('should return red for rejected status', () { + final color = viewModel.getBackGroundColorByStatus( + TenderApprovalStatus.rejected.value, + ); + expect(color, AppColors.red0); + }); + + test('should return grey for unknown status', () { + final color = viewModel.getBackGroundColorByStatus('unknown'); + expect(color, AppColors.grey0); + }); + }); + + group('getBorderColorByStatus', () { + test('should return green for liked status', () { + final color = viewModel.getBorderColorByStatus( + TenderFeedback.liked.value, + ); + expect(color, AppColors.green10); + }); + + test('should return green for submitted status', () { + final color = viewModel.getBorderColorByStatus( + TenderApprovalStatus.submitted.value, + ); + expect(color, AppColors.green10); + }); + + test('should return red for disliked status', () { + final color = viewModel.getBorderColorByStatus( + TenderFeedback.disliked.value, + ); + expect(color, AppColors.red10); + }); + + test('should return red for rejected status', () { + final color = viewModel.getBorderColorByStatus( + TenderApprovalStatus.rejected.value, + ); + expect(color, AppColors.red10); + }); + + test('should return grey for unknown status', () { + final color = viewModel.getBorderColorByStatus('unknown'); + expect(color, AppColors.grey30); + }); + }); + + group('getStatusCardColorByStatus', () { + test('should return green for liked status', () { + final color = viewModel.getStatusCardColorByStatus( + TenderFeedback.liked.value, + ); + expect(color, AppColors.green20); + }); + + test('should return green for submitted status', () { + final color = viewModel.getStatusCardColorByStatus( + TenderApprovalStatus.submitted.value, + ); + expect(color, AppColors.green20); + }); + + test('should return red for disliked status', () { + final color = viewModel.getStatusCardColorByStatus( + TenderFeedback.disliked.value, + ); + expect(color, AppColors.red10); + }); + + test('should return red for rejected status', () { + final color = viewModel.getStatusCardColorByStatus( + TenderApprovalStatus.rejected.value, + ); + expect(color, AppColors.red10); + }); + + test('should return grey for unknown status', () { + final color = viewModel.getStatusCardColorByStatus('unknown'); + expect(color, AppColors.grey0); + }); + }); + + group('getStatusTextColorByStatus', () { + test('should return green for liked status', () { + final color = viewModel.getStatusTextColorByStatus( + TenderFeedback.liked.value, + ); + expect(color, AppColors.green30); + }); + + test('should return green for submitted status', () { + final color = viewModel.getStatusTextColorByStatus( + TenderApprovalStatus.submitted.value, + ); + expect(color, AppColors.green30); + }); + + test('should return red for disliked status', () { + final color = viewModel.getStatusTextColorByStatus( + TenderFeedback.disliked.value, + ); + expect(color, AppColors.red20); + }); + + test('should return red for rejected status', () { + final color = viewModel.getStatusTextColorByStatus( + TenderApprovalStatus.rejected.value, + ); + expect(color, AppColors.red20); + }); + + test('should return grey for unknown status', () { + final color = viewModel.getStatusTextColorByStatus('unknown'); + expect(color, AppColors.grey60); + }); + }); + + group('getStatusIconByStatus', () { + test('should return tick circle for liked status', () { + final icon = viewModel.getStatusIconByStatus( + TenderFeedback.liked.value, + ); + expect(icon, AssetsManager.tickCircle); + }); + + test('should return tick circle for submitted status', () { + final icon = viewModel.getStatusIconByStatus( + TenderApprovalStatus.submitted.value, + ); + expect(icon, AssetsManager.tickCircle); + }); + + test('should return close circle for disliked status', () { + final icon = viewModel.getStatusIconByStatus( + TenderFeedback.disliked.value, + ); + expect(icon, AssetsManager.closeCircle); + }); + + test('should return close circle for rejected status', () { + final icon = viewModel.getStatusIconByStatus( + TenderApprovalStatus.rejected.value, + ); + expect(icon, AssetsManager.closeCircle); + }); + + test('should return null for unknown status', () { + final icon = viewModel.getStatusIconByStatus('unknown'); + expect(icon, null); + }); + }); + }); +} From 7e1240dffb312225954ac2f5625508563f4fb34d Mon Sep 17 00:00:00 2001 From: amirrezaghabeli Date: Mon, 6 Oct 2025 10:32:58 +0330 Subject: [PATCH 2/2] some fixes --- lib/core/network/network_manager.dart | 26 +- lib/core/utils/error_utils.dart | 19 ++ lib/view_models/your_tenders_view_model.dart | 5 +- test/fixtures/test_fixtures.dart | 283 ------------------ test/mocks/mock_auth_repository.dart | 97 ------ test/mocks/mock_your_tenders_repository.dart | 37 --- .../auth_view_model_forgot_password_test.dart | 107 ------- .../auth_view_model_initialization_test.dart | 62 ---- .../auth_view_model_login_test.dart | 86 ------ .../auth_view_model_logout_test.dart | 84 ------ .../view_models/auth_view_model_otp_test.dart | 122 -------- .../auth_view_model_reset_password_test.dart | 140 --------- .../auth_view_model_utility_test.dart | 128 -------- .../auth_view_model_validation_test.dart | 94 ------ ...your_tenders_view_model_feedback_test.dart | 119 -------- .../your_tenders_view_model_filter_test.dart | 137 --------- ...r_tenders_view_model_get_tenders_test.dart | 146 --------- ...enders_view_model_initialization_test.dart | 45 --- ...ur_tenders_view_model_pagination_test.dart | 116 ------- .../your_tenders_view_model_utility_test.dart | 199 ------------ 20 files changed, 40 insertions(+), 2012 deletions(-) create mode 100644 lib/core/utils/error_utils.dart delete mode 100644 test/fixtures/test_fixtures.dart delete mode 100644 test/mocks/mock_auth_repository.dart delete mode 100644 test/mocks/mock_your_tenders_repository.dart delete mode 100644 test/view_models/auth_view_model_forgot_password_test.dart delete mode 100644 test/view_models/auth_view_model_initialization_test.dart delete mode 100644 test/view_models/auth_view_model_login_test.dart delete mode 100644 test/view_models/auth_view_model_logout_test.dart delete mode 100644 test/view_models/auth_view_model_otp_test.dart delete mode 100644 test/view_models/auth_view_model_reset_password_test.dart delete mode 100644 test/view_models/auth_view_model_utility_test.dart delete mode 100644 test/view_models/auth_view_model_validation_test.dart delete mode 100644 test/view_models/your_tenders_view_model_feedback_test.dart delete mode 100644 test/view_models/your_tenders_view_model_filter_test.dart delete mode 100644 test/view_models/your_tenders_view_model_get_tenders_test.dart delete mode 100644 test/view_models/your_tenders_view_model_initialization_test.dart delete mode 100644 test/view_models/your_tenders_view_model_pagination_test.dart delete mode 100644 test/view_models/your_tenders_view_model_utility_test.dart diff --git a/lib/core/network/network_manager.dart b/lib/core/network/network_manager.dart index cb70b6a..997c69e 100644 --- a/lib/core/network/network_manager.dart +++ b/lib/core/network/network_manager.dart @@ -2,6 +2,7 @@ import 'dart:io'; import 'package:dio/dio.dart'; import 'package:shared_preferences/shared_preferences.dart'; +import 'package:tm_app/data/services/model/error/error_model.dart'; import 'package:tm_app/data/services/model/login_response/login_response_model.dart'; import '../config/app_config.dart'; @@ -305,15 +306,24 @@ class NetworkManager { return 'Unknown error'; } - if (data is Map) { - return data['message'] ?? - data['error'] ?? - data['detail'] ?? - 'Unknown error'; - } + try { + if (data is Map) { + final Object? nested = data['error']; + final Map json = + nested is Map ? nested : data; + final ErrorModel model = ErrorModel.fromJson(json); + return model.message ?? + model.details ?? + model.code ?? + (data['detail'] as String?) ?? + 'Unknown error'; + } - if (data is String) { - return data; + if (data is String) { + return data; + } + } catch (_) { + // fall through to default message } return 'Unknown error'; diff --git a/lib/core/utils/error_utils.dart b/lib/core/utils/error_utils.dart new file mode 100644 index 0000000..17b876e --- /dev/null +++ b/lib/core/utils/error_utils.dart @@ -0,0 +1,19 @@ +import 'package:tm_app/core/network/app_exceptions.dart'; + +/// Utility class for handling error messages +class ErrorUtils { + /// Extracts a clean error message from an Exception + static String getErrorMessage(Exception error) { + if (error is AppException) { + return error.message; + } + + // For plain Exception, remove "Exception: " prefix + final errorString = error.toString(); + if (errorString.startsWith('Exception: ')) { + return errorString.substring(11); + } + + return errorString; + } +} diff --git a/lib/view_models/your_tenders_view_model.dart b/lib/view_models/your_tenders_view_model.dart index b36c801..bcd0f2d 100644 --- a/lib/view_models/your_tenders_view_model.dart +++ b/lib/view_models/your_tenders_view_model.dart @@ -1,6 +1,7 @@ import 'package:flutter/material.dart'; import 'package:intl/intl.dart'; import 'package:tm_app/core/theme/colors.dart'; +import 'package:tm_app/core/utils/error_utils.dart'; import 'package:tm_app/core/utils/result.dart'; import 'package:tm_app/data/repositories/your_tenders_repository.dart'; import 'package:tm_app/data/services/model/liked_tenders_response/liked_tenders_response.dart'; @@ -109,7 +110,7 @@ class YourTendersViewModel with ChangeNotifier { errorMessage = null; break; case Error(): - errorMessage = result.error.toString(); + errorMessage = ErrorUtils.getErrorMessage(result.error); break; } @@ -175,7 +176,7 @@ class YourTendersViewModel with ChangeNotifier { errorMessage = null; break; case Error(): - errorMessage = result.error.toString(); + errorMessage = ErrorUtils.getErrorMessage(result.error); break; } diff --git a/test/fixtures/test_fixtures.dart b/test/fixtures/test_fixtures.dart deleted file mode 100644 index 1e1cb14..0000000 --- a/test/fixtures/test_fixtures.dart +++ /dev/null @@ -1,283 +0,0 @@ -import 'package:tm_app/data/services/model/customer/customer.dart'; -import 'package:tm_app/data/services/model/forgot_password_response/forgot_password_response_model.dart'; -import 'package:tm_app/data/services/model/liked_tender_feedback/liked_tender_feedback.dart'; -import 'package:tm_app/data/services/model/liked_tenders_data/liked_tenders_data.dart'; -import 'package:tm_app/data/services/model/liked_tenders_response/liked_tenders_response.dart'; -import 'package:tm_app/data/services/model/login_data/login_data.dart'; -import 'package:tm_app/data/services/model/login_response/login_response_model.dart'; -import 'package:tm_app/data/services/model/logout_response/logout_response.dart'; -import 'package:tm_app/data/services/model/meta/meta.dart'; -import 'package:tm_app/data/services/model/reset_password_response/reset_password_response.dart'; -import 'package:tm_app/data/services/model/tender_approvals_data/tender_approvals_data.dart'; -import 'package:tm_app/data/services/model/tender_approvals_data_warpper/tender_approvals_data_wrapper.dart'; -import 'package:tm_app/data/services/model/tender_approvals_response/tender_approvals_response.dart'; -import 'package:tm_app/data/services/model/tender_data/tender_data.dart'; -import 'package:tm_app/data/services/model/verify_otp_data/verify_otp_data.dart'; -import 'package:tm_app/data/services/model/verify_otp_response/verify_otp_response_model.dart'; - -class TestFixtures { - static const customer = Customer( - id: '1', - type: 'individual', - status: 'active', - firstName: 'John', - lastName: 'Doe', - fullName: 'John Doe', - email: 'john.doe@example.com', - phone: '+1234567890', - mobile: '+1234567890', - registrationNumber: 'REG123', - taxId: 'TAX123', - industry: 'Technology', - businessType: 'Software', - employeeCount: 10, - annualRevenue: 1000000, - foundedYear: 2020, - isVerified: true, - isCompliant: true, - complianceNotes: 'Compliant', - language: 'en', - username: 'johndoe', - currency: 'USD', - timezone: 'UTC', - createdAt: 1640995200, - updatedAt: 1640995200, - createdBy: 'admin', - updatedBy: 'admin', - address: null, - contactPersons: null, - ); - - static const loginData = LoginData( - customer: customer, - error: null, - accessToken: 'test_token', - refreshToken: 'test_refresh_token', - expiresAt: 1640995200, - ); - - static const loginResponse = LoginResponseModel( - success: true, - message: 'Login successful', - data: loginData, - ); - - static const logoutResponse = LogoutResponse( - success: true, - message: 'Logout successful', - error: null, - ); - - static const forgotPasswordResponse = ForgotPasswordResponseModel( - success: true, - message: 'OTP sent to email', - data: null, - error: null, - meta: null, - ); - - static const verifyOtpData = VerifyOtpData( - message: 'OTP verified successfully', - success: true, - token: 'reset_token_123', - ); - - static const verifyOtpResponse = VerifyOtpResponseModel( - success: true, - message: 'OTP verified successfully', - data: verifyOtpData, - ); - - static const resetPasswordResponse = ResetPasswordResponse( - success: true, - message: 'Password reset successfully', - error: null, - ); - - // Tender test fixtures - static const testTenderData = TenderData( - success: true, - message: 'Success', - id: '1', - tenderId: '1', - noticePublicationId: '1', - publicationDate: 1640995200, - title: 'Test Tender', - description: 'Test Description', - procurementTypeCode: 'test', - procedureCode: 'test', - estimatedValue: 100000, - currency: 'USD', - duration: '30', - tenderDeadline: 1640995200, - submissionDeadline: 1640995200, - applicationDeadline: 1640995200, - submissionUrl: 'https://test.com', - countryCode: 'US', - durationUnit: 'days', - buyerOrganization: null, - status: 'submitted', - ); - - static const testTenderApprovalsData = TenderApprovalsData( - id: '1', - status: 'submitted', - tenderId: '1', - companyId: '1', - submissionMode: 'online', - createdAt: 1640995200, - tender: testTenderData, - ); - - static const testMeta = Meta( - limit: 10, - offset: 0, - page: 1, - pages: 2, - total: 15, - ); - - static const testTenderApprovalsDataWrapper = TenderApprovalsDataWrapper( - tenders: [testTenderApprovalsData], - metadata: testMeta, - ); - - static const tenderApprovalsResponse = TenderApprovalsResponse( - success: true, - error: null, - data: testTenderApprovalsDataWrapper, - ); - - static const testLikedTenderFeedback = LikedTenderFeedback( - id: '1', - feedbackType: 'liked', - updatedAt: 1640995200, - createdAt: 1640995200, - tenderId: '1', - customerId: '1', - companyId: '1', - tender: testTenderData, - ); - - static const testLikedTenderFeedback2 = LikedTenderFeedback( - id: '2', - feedbackType: 'liked', - updatedAt: 1640995200, - createdAt: 1640995200, - tenderId: '2', - customerId: '1', - companyId: '1', - tender: testTenderData, - ); - - static const testLikedTenderFeedback3 = LikedTenderFeedback( - id: '3', - feedbackType: 'liked', - updatedAt: 1640995200, - createdAt: 1640995200, - tenderId: '3', - customerId: '1', - companyId: '1', - tender: testTenderData, - ); - - static const testLikedTenderFeedback4 = LikedTenderFeedback( - id: '4', - feedbackType: 'liked', - updatedAt: 1640995200, - createdAt: 1640995200, - tenderId: '4', - customerId: '1', - companyId: '1', - tender: testTenderData, - ); - - static const testLikedTenderFeedback5 = LikedTenderFeedback( - id: '5', - feedbackType: 'liked', - updatedAt: 1640995200, - createdAt: 1640995200, - tenderId: '5', - customerId: '1', - companyId: '1', - tender: testTenderData, - ); - - static const testLikedTenderFeedback6 = LikedTenderFeedback( - id: '6', - feedbackType: 'liked', - updatedAt: 1640995200, - createdAt: 1640995200, - tenderId: '6', - customerId: '1', - companyId: '1', - tender: testTenderData, - ); - - static const testLikedTenderFeedback7 = LikedTenderFeedback( - id: '7', - feedbackType: 'liked', - updatedAt: 1640995200, - createdAt: 1640995200, - tenderId: '7', - customerId: '1', - companyId: '1', - tender: testTenderData, - ); - - static const testLikedTenderFeedback8 = LikedTenderFeedback( - id: '8', - feedbackType: 'liked', - updatedAt: 1640995200, - createdAt: 1640995200, - tenderId: '8', - customerId: '1', - companyId: '1', - tender: testTenderData, - ); - - static const testLikedTenderFeedback9 = LikedTenderFeedback( - id: '9', - feedbackType: 'liked', - updatedAt: 1640995200, - createdAt: 1640995200, - tenderId: '9', - customerId: '1', - companyId: '1', - tender: testTenderData, - ); - - static const testLikedTenderFeedback10 = LikedTenderFeedback( - id: '10', - feedbackType: 'liked', - updatedAt: 1640995200, - createdAt: 1640995200, - tenderId: '10', - customerId: '1', - companyId: '1', - tender: testTenderData, - ); - - static const testLikedTendersData = LikedTendersData( - feedback: [ - testLikedTenderFeedback, - testLikedTenderFeedback2, - testLikedTenderFeedback3, - testLikedTenderFeedback4, - testLikedTenderFeedback5, - testLikedTenderFeedback6, - testLikedTenderFeedback7, - testLikedTenderFeedback8, - testLikedTenderFeedback9, - testLikedTenderFeedback10, - ], - meta: testMeta, - ); - - static const likedTendersResponse = LikedTendersResponse( - success: true, - message: 'Success', - data: testLikedTendersData, - error: null, - ); -} diff --git a/test/mocks/mock_auth_repository.dart b/test/mocks/mock_auth_repository.dart deleted file mode 100644 index c88b1f1..0000000 --- a/test/mocks/mock_auth_repository.dart +++ /dev/null @@ -1,97 +0,0 @@ -import 'package:tm_app/core/utils/result.dart'; -import 'package:tm_app/data/repositories/auth_repository.dart'; -import 'package:tm_app/data/services/model/forgot_password_response/forgot_password_response_model.dart'; -import 'package:tm_app/data/services/model/login_response/login_response_model.dart'; -import 'package:tm_app/data/services/model/logout_response/logout_response.dart'; -import 'package:tm_app/data/services/model/reset_password_response/reset_password_response.dart'; -import 'package:tm_app/data/services/model/verify_otp_response/verify_otp_response_model.dart'; - -import '../fixtures/test_fixtures.dart'; - -class MockAuthRepository implements AuthRepository { - Result? _loginResult; - Result? _logoutResult; - Result? _forgotPasswordResult; - Result? _verifyOtpResult; - Result? _resetPasswordResult; - Result? _checkIsLoggedInResult; - Exception? _localLogoutException; - - void setLoginResult(Result result) { - _loginResult = result; - } - - void setLogoutResult(Result result) { - _logoutResult = result; - } - - void setForgotPasswordResult(Result result) { - _forgotPasswordResult = result; - } - - void setVerifyOtpResult(Result result) { - _verifyOtpResult = result; - } - - void setResetPasswordResult(Result result) { - _resetPasswordResult = result; - } - - void setCheckIsLoggedInResult(Result result) { - _checkIsLoggedInResult = result; - } - - void setLocalLogoutException(Exception exception) { - _localLogoutException = exception; - } - - @override - Future> login({ - required String username, - required String password, - }) async { - return _loginResult ?? const Result.ok(TestFixtures.loginResponse); - } - - @override - Future> logout() async { - return _logoutResult ?? const Result.ok(TestFixtures.logoutResponse); - } - - @override - Future> resetPassword({ - required String newPassword, - required String token, - }) async { - return _resetPasswordResult ?? - const Result.ok(TestFixtures.resetPasswordResponse); - } - - @override - Future localLogout() async { - if (_localLogoutException != null) { - throw _localLogoutException!; - } - } - - @override - Future> forgotPassword({ - required String email, - }) async { - return _forgotPasswordResult ?? - const Result.ok(TestFixtures.forgotPasswordResponse); - } - - @override - Future> verifyOtp({ - required String code, - required String email, - }) async { - return _verifyOtpResult ?? const Result.ok(TestFixtures.verifyOtpResponse); - } - - @override - Future> checkIsLoggedIn() async { - return _checkIsLoggedInResult ?? const Result.ok(true); - } -} diff --git a/test/mocks/mock_your_tenders_repository.dart b/test/mocks/mock_your_tenders_repository.dart deleted file mode 100644 index 319f7f2..0000000 --- a/test/mocks/mock_your_tenders_repository.dart +++ /dev/null @@ -1,37 +0,0 @@ -import 'package:tm_app/core/utils/result.dart'; -import 'package:tm_app/data/repositories/your_tenders_repository.dart'; -import 'package:tm_app/data/services/model/liked_tenders_response/liked_tenders_response.dart'; -import 'package:tm_app/data/services/model/request_models/get_tenders_request_model/get_tenders_request_model.dart'; -import 'package:tm_app/data/services/model/request_models/tender_feedback_request_model/tender_feedback_request_model.dart'; -import 'package:tm_app/data/services/model/tender_approvals_response/tender_approvals_response.dart'; - -import '../fixtures/test_fixtures.dart'; - -class MockYourTendersRepository implements YourTendersRepository { - Result? _getTendersResult; - Result? _getTendersFeedbackResult; - - void setGetTendersResult(Result result) { - _getTendersResult = result; - } - - void setGetTendersFeedbackResult(Result result) { - _getTendersFeedbackResult = result; - } - - @override - Future> getTenders({ - required GetTendersRequestModel requestModel, - }) async { - return _getTendersResult ?? - const Result.ok(TestFixtures.tenderApprovalsResponse); - } - - @override - Future> getTendersFeedback({ - required TenderFeedbackRequestModel requestModel, - }) async { - return _getTendersFeedbackResult ?? - const Result.ok(TestFixtures.likedTendersResponse); - } -} diff --git a/test/view_models/auth_view_model_forgot_password_test.dart b/test/view_models/auth_view_model_forgot_password_test.dart deleted file mode 100644 index c5049fc..0000000 --- a/test/view_models/auth_view_model_forgot_password_test.dart +++ /dev/null @@ -1,107 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:flutter_test/flutter_test.dart'; -import 'package:tm_app/core/utils/result.dart'; -import 'package:tm_app/view_models/auth_view_model.dart'; - -import '../fixtures/test_fixtures.dart'; -import '../mocks/mock_auth_repository.dart'; - -void main() { - group('AuthViewModel Forgot Password', () { - late MockAuthRepository mockRepository; - late AuthViewModel authViewModel; - late BuildContext mockContext; - - setUp(() { - mockRepository = MockAuthRepository(); - authViewModel = AuthViewModel(authRepository: mockRepository); - mockContext = const TestWidget().createElement(); - }); - - tearDown(() { - authViewModel.dispose(); - }); - - test('should send forgot password successfully', () async { - mockRepository.setForgotPasswordResult( - const Result.ok(TestFixtures.forgotPasswordResponse), - ); - - authViewModel.emailController.text = 'test@example.com'; - - await authViewModel.forgotPassword(mockContext); - - expect(authViewModel.isLoadingForgot, false); - expect(authViewModel.successMessage, 'OTP sent to email'); - expect(authViewModel.errorMessageForget, null); - }); - - test('should handle forgot password error', () async { - const errorMessage = 'Email not found'; - mockRepository.setForgotPasswordResult( - Result.error(Exception(errorMessage)), - ); - - authViewModel.emailController.text = 'nonexistent@example.com'; - - await authViewModel.forgotPassword(mockContext); - - // Wait for the microtask to clear the error message - await Future.microtask(() {}); - - expect(authViewModel.isLoadingForgot, false); - expect(authViewModel.errorMessageForget, null); // Cleared after notify - expect(authViewModel.successMessage, null); - }); - - test('should set loading state during forgot password', () async { - mockRepository.setForgotPasswordResult( - const Result.ok(TestFixtures.forgotPasswordResponse), - ); - - authViewModel.emailController.text = 'test@example.com'; - - final forgotPasswordFuture = authViewModel.forgotPassword(mockContext); - - // Check loading state is set - expect(authViewModel.isLoadingForgot, true); - expect(authViewModel.errorMessageForget, null); - expect(authViewModel.successMessage, null); - - await forgotPasswordFuture; - - expect(authViewModel.isLoadingForgot, false); - }); - - test('should clear previous messages before forgot password', () async { - mockRepository.setForgotPasswordResult( - const Result.ok(TestFixtures.forgotPasswordResponse), - ); - - // Set previous error and success messages - authViewModel.emailController.text = 'test@example.com'; - mockRepository.setForgotPasswordResult( - Result.error(Exception('Previous error')), - ); - await authViewModel.forgotPassword(mockContext); - - // Now test successful case - mockRepository.setForgotPasswordResult( - const Result.ok(TestFixtures.forgotPasswordResponse), - ); - await authViewModel.forgotPassword(mockContext); - - expect(authViewModel.errorMessageForget, null); - expect(authViewModel.successMessage, 'OTP sent to email'); - }); - }); -} - -class TestWidget extends StatelessWidget { - const TestWidget({super.key}); - - @override - Widget build(BuildContext context) { - return Container(); - } -} diff --git a/test/view_models/auth_view_model_initialization_test.dart b/test/view_models/auth_view_model_initialization_test.dart deleted file mode 100644 index 35d0dff..0000000 --- a/test/view_models/auth_view_model_initialization_test.dart +++ /dev/null @@ -1,62 +0,0 @@ -import 'package:flutter_test/flutter_test.dart'; -import 'package:tm_app/view_models/auth_view_model.dart'; - -import '../mocks/mock_auth_repository.dart'; - -void main() { - group('AuthViewModel Initialization', () { - late MockAuthRepository mockRepository; - late AuthViewModel authViewModel; - - setUp(() { - mockRepository = MockAuthRepository(); - authViewModel = AuthViewModel(authRepository: mockRepository); - }); - - tearDown(() { - authViewModel.dispose(); - }); - - test('should initialize with default values', () { - expect(authViewModel.isLoading, false); - expect(authViewModel.isLoggedIn, false); - expect(authViewModel.isLoadingForgot, false); - expect(authViewModel.isLoadingOtp, false); - expect(authViewModel.isResetPasswordLoading, false); - expect(authViewModel.errorMessage, null); - expect(authViewModel.errorMessageForget, null); - expect(authViewModel.resetPasswordErrorMessage, null); - expect(authViewModel.errorMessageOtp, null); - expect(authViewModel.successMessageOtp, null); - expect(authViewModel.verifyOtpSuccess, false); - expect(authViewModel.loggedInUser, null); - expect(authViewModel.forgotPasswordMessage, null); - expect(authViewModel.resetToken, null); - expect(authViewModel.resetPasswordSuccess, false); - }); - - test('should initialize controllers with empty text', () { - expect(authViewModel.usernameController.text, isEmpty); - expect(authViewModel.passwordController.text, isEmpty); - expect(authViewModel.emailController.text, isEmpty); - expect(authViewModel.newPasswordController.text, isEmpty); - expect(authViewModel.otpController.text, isEmpty); - }); - - test('should initialize with password visibility obscured', () { - expect(authViewModel.obscurePassword, true); - expect(authViewModel.obscureForgotPassword, true); - }); - - test('should not be login enabled with empty fields', () { - expect(authViewModel.isLoginEnabled, false); - }); - - test('should be login enabled with filled fields', () { - authViewModel.usernameController.text = 'testuser'; - authViewModel.passwordController.text = 'testpass'; - - expect(authViewModel.isLoginEnabled, true); - }); - }); -} diff --git a/test/view_models/auth_view_model_login_test.dart b/test/view_models/auth_view_model_login_test.dart deleted file mode 100644 index e7bc861..0000000 --- a/test/view_models/auth_view_model_login_test.dart +++ /dev/null @@ -1,86 +0,0 @@ -import 'package:flutter_test/flutter_test.dart'; -import 'package:tm_app/core/utils/result.dart'; -import 'package:tm_app/view_models/auth_view_model.dart'; - -import '../fixtures/test_fixtures.dart'; -import '../mocks/mock_auth_repository.dart'; - -void main() { - group('AuthViewModel Login', () { - late MockAuthRepository mockRepository; - late AuthViewModel authViewModel; - - setUp(() { - mockRepository = MockAuthRepository(); - authViewModel = AuthViewModel(authRepository: mockRepository); - }); - - tearDown(() { - authViewModel.dispose(); - }); - - test('should login successfully', () async { - mockRepository.setLoginResult( - const Result.ok(TestFixtures.loginResponse), - ); - - authViewModel.usernameController.text = 'testuser'; - authViewModel.passwordController.text = 'testpass'; - - await authViewModel.login(); - - expect(authViewModel.isLoading, false); - expect(authViewModel.loggedInUser, TestFixtures.customer); - expect(authViewModel.usernameController.text, isEmpty); - expect(authViewModel.passwordController.text, isEmpty); - expect(authViewModel.errorMessage, null); - }); - - test('should handle login error', () async { - const errorMessage = 'Invalid credentials'; - mockRepository.setLoginResult(Result.error(Exception(errorMessage))); - - authViewModel.usernameController.text = 'testuser'; - authViewModel.passwordController.text = 'wrongpass'; - - await authViewModel.login(); - - expect(authViewModel.isLoading, false); - expect(authViewModel.loggedInUser, null); - expect(authViewModel.errorMessage, null); // Cleared after notify - }); - - test('should set loading state during login', () async { - mockRepository.setLoginResult( - const Result.ok(TestFixtures.loginResponse), - ); - - authViewModel.usernameController.text = 'testuser'; - authViewModel.passwordController.text = 'testpass'; - - final loginFuture = authViewModel.login(); - - // Check loading state is set - expect(authViewModel.isLoading, true); - expect(authViewModel.errorMessage, null); - - await loginFuture; - - expect(authViewModel.isLoading, false); - }); - - test('should call localLogout before login', () async { - mockRepository.setLoginResult( - const Result.ok(TestFixtures.loginResponse), - ); - - authViewModel.usernameController.text = 'testuser'; - authViewModel.passwordController.text = 'testpass'; - - await authViewModel.login(); - - // Verify that localLogout was called (user is null initially) - expect(authViewModel.loggedInUser, TestFixtures.customer); - }); - }); -} diff --git a/test/view_models/auth_view_model_logout_test.dart b/test/view_models/auth_view_model_logout_test.dart deleted file mode 100644 index 19b55a6..0000000 --- a/test/view_models/auth_view_model_logout_test.dart +++ /dev/null @@ -1,84 +0,0 @@ -import 'package:flutter_test/flutter_test.dart'; -import 'package:tm_app/core/utils/result.dart'; -import 'package:tm_app/view_models/auth_view_model.dart'; - -import '../fixtures/test_fixtures.dart'; -import '../mocks/mock_auth_repository.dart'; - -void main() { - group('AuthViewModel Logout', () { - late MockAuthRepository mockRepository; - late AuthViewModel authViewModel; - - setUp(() { - mockRepository = MockAuthRepository(); - authViewModel = AuthViewModel(authRepository: mockRepository); - }); - - tearDown(() { - authViewModel.dispose(); - }); - - test('should logout successfully', () async { - mockRepository.setLogoutResult( - const Result.ok(TestFixtures.logoutResponse), - ); - - // Set a logged in user first - authViewModel.usernameController.text = 'testuser'; - authViewModel.passwordController.text = 'testpass'; - mockRepository.setLoginResult( - const Result.ok(TestFixtures.loginResponse), - ); - await authViewModel.login(); - - await authViewModel.logout(); - - expect(authViewModel.loggedInUser, null); - expect(authViewModel.errorMessage, null); // Cleared after notify - }); - - test('should handle logout error', () async { - const errorMessage = 'Logout failed'; - mockRepository.setLogoutResult(Result.error(Exception(errorMessage))); - - await authViewModel.logout(); - - expect(authViewModel.errorMessage, null); // Cleared after notify - }); - - test('should perform local logout', () async { - mockRepository.setLogoutResult( - const Result.ok(TestFixtures.logoutResponse), - ); - - // Set a logged in user first - authViewModel.usernameController.text = 'testuser'; - authViewModel.passwordController.text = 'testpass'; - mockRepository.setLoginResult( - const Result.ok(TestFixtures.loginResponse), - ); - await authViewModel.login(); - - await authViewModel.logout(); - - expect(authViewModel.loggedInUser, null); - }); - - test('should clear error message before logout', () async { - mockRepository.setLogoutResult( - const Result.ok(TestFixtures.logoutResponse), - ); - - // Set an error first - authViewModel.usernameController.text = 'testuser'; - authViewModel.passwordController.text = 'wrongpass'; - mockRepository.setLoginResult(Result.error(Exception('Login failed'))); - await authViewModel.login(); - - await authViewModel.logout(); - - expect(authViewModel.errorMessage, null); - }); - }); -} diff --git a/test/view_models/auth_view_model_otp_test.dart b/test/view_models/auth_view_model_otp_test.dart deleted file mode 100644 index 1cf518c..0000000 --- a/test/view_models/auth_view_model_otp_test.dart +++ /dev/null @@ -1,122 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:flutter_test/flutter_test.dart'; -import 'package:tm_app/core/utils/result.dart'; -import 'package:tm_app/view_models/auth_view_model.dart'; - -import '../fixtures/test_fixtures.dart'; -import '../mocks/mock_auth_repository.dart'; - -void main() { - group('AuthViewModel OTP Verification', () { - late MockAuthRepository mockRepository; - late AuthViewModel authViewModel; - late BuildContext mockContext; - - setUp(() { - mockRepository = MockAuthRepository(); - authViewModel = AuthViewModel(authRepository: mockRepository); - mockContext = const TestWidget().createElement(); - }); - - tearDown(() { - authViewModel.dispose(); - }); - - test('should verify OTP successfully', () async { - mockRepository.setVerifyOtpResult( - const Result.ok(TestFixtures.verifyOtpResponse), - ); - - authViewModel.otpController.text = '123456'; - authViewModel.emailController.text = 'test@example.com'; - - await authViewModel.verifyOtp(mockContext); - - expect(authViewModel.isLoadingOtp, false); - expect(authViewModel.verifyOtpSuccess, true); - expect(authViewModel.resetToken, 'reset_token_123'); - expect( - authViewModel.successMessageOtp, - null, - ); // Cleared after notify for toast - expect(authViewModel.errorMessageOtp, null); // Cleared after notify - }); - - test('should handle OTP verification error', () async { - const errorMessage = 'Invalid OTP'; - mockRepository.setVerifyOtpResult(Result.error(Exception(errorMessage))); - - authViewModel.otpController.text = '000000'; - authViewModel.emailController.text = 'test@example.com'; - - await authViewModel.verifyOtp(mockContext); - - expect(authViewModel.isLoadingOtp, false); - expect(authViewModel.verifyOtpSuccess, false); - expect(authViewModel.errorMessageOtp, null); // Cleared after notify - expect(authViewModel.successMessageOtp, null); // Cleared after notify - }); - - test('should set loading state during OTP verification', () async { - mockRepository.setVerifyOtpResult( - const Result.ok(TestFixtures.verifyOtpResponse), - ); - - authViewModel.otpController.text = '123456'; - authViewModel.emailController.text = 'test@example.com'; - - final verifyOtpFuture = authViewModel.verifyOtp(mockContext); - - // Check loading state is set - expect(authViewModel.isLoadingOtp, true); - expect(authViewModel.errorMessageOtp, null); - expect(authViewModel.verifyOtpSuccess, false); - - await verifyOtpFuture; - - expect(authViewModel.isLoadingOtp, false); - }); - - test('should clear OTP flow', () { - // Set some state first - authViewModel.otpController.text = '123456'; - authViewModel.resetToken = 'some_token'; - - authViewModel.clearOtpFlow(); - - expect(authViewModel.errorMessageOtp, null); - expect(authViewModel.successMessageOtp, null); - expect(authViewModel.isLoadingOtp, false); - expect(authViewModel.verifyOtpSuccess, false); - expect(authViewModel.otpController.text, isEmpty); - expect(authViewModel.resetToken, 'some_token'); // Should not be cleared - }); - - test('should reset verifyOtpSuccess before verification', () async { - mockRepository.setVerifyOtpResult( - const Result.ok(TestFixtures.verifyOtpResponse), - ); - - // Set verifyOtpSuccess to true first - authViewModel.otpController.text = '123456'; - authViewModel.emailController.text = 'test@example.com'; - await authViewModel.verifyOtp(mockContext); - expect(authViewModel.verifyOtpSuccess, true); - - // Now test with error - mockRepository.setVerifyOtpResult(Result.error(Exception('Invalid OTP'))); - await authViewModel.verifyOtp(mockContext); - - expect(authViewModel.verifyOtpSuccess, false); - }); - }); -} - -class TestWidget extends StatelessWidget { - const TestWidget({super.key}); - - @override - Widget build(BuildContext context) { - return Container(); - } -} diff --git a/test/view_models/auth_view_model_reset_password_test.dart b/test/view_models/auth_view_model_reset_password_test.dart deleted file mode 100644 index 1e651c9..0000000 --- a/test/view_models/auth_view_model_reset_password_test.dart +++ /dev/null @@ -1,140 +0,0 @@ -import 'package:flutter_test/flutter_test.dart'; -import 'package:tm_app/core/utils/result.dart'; -import 'package:tm_app/view_models/auth_view_model.dart'; - -import '../fixtures/test_fixtures.dart'; -import '../mocks/mock_auth_repository.dart'; - -void main() { - group('AuthViewModel Reset Password', () { - late MockAuthRepository mockRepository; - late AuthViewModel authViewModel; - - setUp(() { - mockRepository = MockAuthRepository(); - authViewModel = AuthViewModel(authRepository: mockRepository); - }); - - tearDown(() { - authViewModel.dispose(); - }); - - test('should reset password successfully', () async { - mockRepository.setResetPasswordResult( - const Result.ok(TestFixtures.resetPasswordResponse), - ); - - authViewModel.resetToken = 'reset_token_123'; - authViewModel.newPasswordController.text = 'NewPass123!'; - - await authViewModel.resetPassword(); - - expect(authViewModel.isResetPasswordLoading, false); - expect(authViewModel.resetPasswordSuccess, true); - expect( - authViewModel.resetPasswordErrorMessage, - null, - ); // Cleared after notify - }); - - test('should handle reset password error', () async { - const errorMessage = 'Invalid token'; - mockRepository.setResetPasswordResult( - Result.error(Exception(errorMessage)), - ); - - authViewModel.resetToken = 'invalid_token'; - authViewModel.newPasswordController.text = 'NewPass123!'; - - await authViewModel.resetPassword(); - - expect(authViewModel.isResetPasswordLoading, false); - expect(authViewModel.resetPasswordSuccess, false); - expect( - authViewModel.resetPasswordErrorMessage, - null, - ); // Cleared after notify - }); - - test('should handle missing reset token', () async { - authViewModel.resetToken = null; - authViewModel.newPasswordController.text = 'NewPass123!'; - - await authViewModel.resetPassword(); - - expect(authViewModel.isResetPasswordLoading, false); - expect(authViewModel.resetPasswordSuccess, false); - expect( - authViewModel.resetPasswordErrorMessage, - 'Reset token is required', - ); - }); - - test('should set loading state during reset password', () async { - mockRepository.setResetPasswordResult( - const Result.ok(TestFixtures.resetPasswordResponse), - ); - - authViewModel.resetToken = 'reset_token_123'; - authViewModel.newPasswordController.text = 'NewPass123!'; - - final resetPasswordFuture = authViewModel.resetPassword(); - - // Check loading state is set - expect(authViewModel.isResetPasswordLoading, true); - expect(authViewModel.resetPasswordErrorMessage, null); - expect(authViewModel.resetPasswordSuccess, false); - - await resetPasswordFuture; - - expect(authViewModel.isResetPasswordLoading, false); - }); - - test('should clear reset password success', () { - // Set reset password success through a successful reset - mockRepository.setResetPasswordResult( - const Result.ok(TestFixtures.resetPasswordResponse), - ); - authViewModel.resetToken = 'reset_token_123'; - authViewModel.newPasswordController.text = 'NewPass123!'; - - authViewModel.clearResetPasswordSuccess(); - - expect(authViewModel.resetPasswordSuccess, false); - }); - - test('should clear reset flow', () { - // Set some state first - authViewModel.resetToken = 'some_token'; - authViewModel.newPasswordController.text = 'some_password'; - - authViewModel.clearResetFlow(); - - expect(authViewModel.resetPasswordErrorMessage, null); - expect(authViewModel.resetPasswordSuccess, false); - expect(authViewModel.isResetPasswordLoading, false); - expect(authViewModel.newPasswordController.text, isEmpty); - expect(authViewModel.resetToken, 'some_token'); // Should not be cleared - }); - - test('should reset success state before reset password', () async { - mockRepository.setResetPasswordResult( - const Result.ok(TestFixtures.resetPasswordResponse), - ); - - // Set resetPasswordSuccess to true first - authViewModel.resetToken = 'reset_token_123'; - authViewModel.newPasswordController.text = 'NewPass123!'; - await authViewModel.resetPassword(); - expect(authViewModel.resetPasswordSuccess, true); - - // Now test with error - mockRepository.setResetPasswordResult( - Result.error(Exception('Reset failed')), - ); - await authViewModel.resetPassword(); - - expect(authViewModel.resetPasswordSuccess, false); - }); - }); -} diff --git a/test/view_models/auth_view_model_utility_test.dart b/test/view_models/auth_view_model_utility_test.dart deleted file mode 100644 index 1d72a66..0000000 --- a/test/view_models/auth_view_model_utility_test.dart +++ /dev/null @@ -1,128 +0,0 @@ -import 'package:flutter_test/flutter_test.dart'; -import 'package:tm_app/core/utils/result.dart'; -import 'package:tm_app/view_models/auth_view_model.dart'; - -import '../fixtures/test_fixtures.dart'; -import '../mocks/mock_auth_repository.dart'; - -void main() { - group('AuthViewModel Utility Methods', () { - late MockAuthRepository mockRepository; - late AuthViewModel authViewModel; - - setUp(() { - mockRepository = MockAuthRepository(); - authViewModel = AuthViewModel(authRepository: mockRepository); - }); - - tearDown(() { - authViewModel.dispose(); - }); - - group('Password Visibility Toggle', () { - test('should toggle password visibility', () { - expect(authViewModel.obscurePassword, true); - - authViewModel.togglePasswordVisibility(); - expect(authViewModel.obscurePassword, false); - - authViewModel.togglePasswordVisibility(); - expect(authViewModel.obscurePassword, true); - }); - - test('should toggle forgot password visibility', () { - expect(authViewModel.obscureForgotPassword, true); - - authViewModel.toggleForgotPasswordVisibility(); - expect(authViewModel.obscureForgotPassword, false); - - authViewModel.toggleForgotPasswordVisibility(); - expect(authViewModel.obscureForgotPassword, true); - }); - }); - - group('Message Clearing', () { - test('should clear messages', () { - // Set some messages first through login error - mockRepository.setLoginResult(Result.error(Exception('Login failed'))); - authViewModel.usernameController.text = 'testuser'; - authViewModel.passwordController.text = 'wrongpass'; - - authViewModel.clearMessages(); - - expect(authViewModel.successMessage, null); - expect(authViewModel.errorMessage, null); - }); - }); - - group('Check Login Status', () { - test('should check if user is logged in - true', () async { - mockRepository.setCheckIsLoggedInResult(const Result.ok(true)); - - await authViewModel.checkIsLoggedIn(); - - expect(authViewModel.isLoggedIn, true); - }); - - test('should check if user is logged in - false', () async { - mockRepository.setCheckIsLoggedInResult(const Result.ok(false)); - - await authViewModel.checkIsLoggedIn(); - - expect(authViewModel.isLoggedIn, false); - }); - - test('should handle check login error', () async { - mockRepository.setCheckIsLoggedInResult( - Result.error(Exception('Check failed')), - ); - - await authViewModel.checkIsLoggedIn(); - - expect(authViewModel.isLoggedIn, false); - }); - }); - - group('Local Logout', () { - test('should perform local logout', () async { - // Set a logged in user first - authViewModel.usernameController.text = 'testuser'; - authViewModel.passwordController.text = 'testpass'; - mockRepository.setLoginResult( - const Result.ok(TestFixtures.loginResponse), - ); - await authViewModel.login(); - - await authViewModel.localLogout(); - - expect(authViewModel.loggedInUser, null); - }); - - test('should handle local logout exception', () async { - mockRepository.setLocalLogoutException( - Exception('Local logout failed'), - ); - - // Should not throw exception - await authViewModel.localLogout(); - - expect(authViewModel.loggedInUser, null); - }); - }); - - group('Text Change Notification', () { - test('should notify listeners on text change', () { - int notificationCount = 0; - - authViewModel.addListener(() { - notificationCount++; - }); - - // Trigger text change - authViewModel.usernameController.text = 'test'; - - expect(notificationCount, greaterThan(0)); - }); - }); - }); -} diff --git a/test/view_models/auth_view_model_validation_test.dart b/test/view_models/auth_view_model_validation_test.dart deleted file mode 100644 index c790632..0000000 --- a/test/view_models/auth_view_model_validation_test.dart +++ /dev/null @@ -1,94 +0,0 @@ -import 'package:flutter_test/flutter_test.dart'; -import 'package:tm_app/view_models/auth_view_model.dart'; - -import '../mocks/mock_auth_repository.dart'; - -void main() { - group('AuthViewModel Validation', () { - late MockAuthRepository mockRepository; - late AuthViewModel authViewModel; - - setUp(() { - mockRepository = MockAuthRepository(); - authViewModel = AuthViewModel(authRepository: mockRepository); - }); - - tearDown(() { - authViewModel.dispose(); - }); - - group('Email Validation', () { - test('should return true for valid email', () { - authViewModel.emailController.text = 'test@example.com'; - expect(authViewModel.isEmailValid, true); - }); - - test('should return false for invalid email', () { - authViewModel.emailController.text = 'invalid-email'; - expect(authViewModel.isEmailValid, false); - }); - - test('should return false for empty email', () { - authViewModel.emailController.text = ''; - expect(authViewModel.isEmailValid, false); - }); - - test('should return false for email without domain', () { - authViewModel.emailController.text = 'test@'; - expect(authViewModel.isEmailValid, false); - }); - }); - - group('OTP Validation', () { - test('should return true for valid 6-digit OTP', () { - authViewModel.otpController.text = '123456'; - expect(authViewModel.isOtpValid, true); - expect(authViewModel.canSubmitOtpPage, true); - }); - - test('should return false for invalid OTP length', () { - authViewModel.otpController.text = '12345'; - expect(authViewModel.isOtpValid, false); - expect(authViewModel.canSubmitOtpPage, false); - }); - - test('should return false for empty OTP', () { - authViewModel.otpController.text = ''; - expect(authViewModel.isOtpValid, false); - expect(authViewModel.canSubmitOtpPage, false); - }); - }); - - group('Password Validation', () { - test('should return true for valid password', () { - authViewModel.newPasswordController.text = 'Test123!'; - expect(authViewModel.isNewPasswordValid, true); - }); - - test('should return false for password without uppercase', () { - authViewModel.newPasswordController.text = 'test123!'; - expect(authViewModel.isNewPasswordValid, false); - }); - - test('should return false for password without lowercase', () { - authViewModel.newPasswordController.text = 'TEST123!'; - expect(authViewModel.isNewPasswordValid, false); - }); - - test('should return false for password without special character', () { - authViewModel.newPasswordController.text = 'Test123'; - expect(authViewModel.isNewPasswordValid, false); - }); - - test('should return false for password too short', () { - authViewModel.newPasswordController.text = 'Te1!'; - expect(authViewModel.isNewPasswordValid, false); - }); - - test('should return false for empty password', () { - authViewModel.newPasswordController.text = ''; - expect(authViewModel.isNewPasswordValid, false); - }); - }); - }); -} diff --git a/test/view_models/your_tenders_view_model_feedback_test.dart b/test/view_models/your_tenders_view_model_feedback_test.dart deleted file mode 100644 index bfabbdc..0000000 --- a/test/view_models/your_tenders_view_model_feedback_test.dart +++ /dev/null @@ -1,119 +0,0 @@ -import 'package:flutter_test/flutter_test.dart'; -import 'package:tm_app/core/utils/result.dart'; -import 'package:tm_app/view_models/your_tenders_view_model.dart'; - -import '../fixtures/test_fixtures.dart'; -import '../mocks/mock_your_tenders_repository.dart'; - -void main() { - group('YourTendersViewModel GetTendersFeedback', () { - late MockYourTendersRepository mockRepository; - late YourTendersViewModel viewModel; - - setUp(() { - mockRepository = MockYourTendersRepository(); - viewModel = YourTendersViewModel(yourTendersRepository: mockRepository); - }); - - tearDown(() { - viewModel.dispose(); - }); - - test('should get tenders feedback successfully', () async { - mockRepository.setGetTendersFeedbackResult( - const Result.ok(TestFixtures.likedTendersResponse), - ); - - await viewModel.getTendersFeedback(feedbackType: 'liked', reset: true); - - expect(viewModel.isLoading, false); - expect(viewModel.likedTendersData, TestFixtures.likedTendersResponse); - expect(viewModel.errorMessage, null); - expect(viewModel.hasMore, true); - }); - - test('should handle get tenders feedback error', () async { - const errorMessage = 'Network error'; - mockRepository.setGetTendersFeedbackResult( - Result.error(Exception(errorMessage)), - ); - - await viewModel.getTendersFeedback(feedbackType: 'liked', reset: true); - - expect(viewModel.isLoading, false); - expect(viewModel.likedTendersData, null); - expect(viewModel.errorMessage, errorMessage); - }); - - test('should set loading state during get feedback', () async { - mockRepository.setGetTendersFeedbackResult( - const Result.ok(TestFixtures.likedTendersResponse), - ); - - final getFeedbackFuture = viewModel.getTendersFeedback( - feedbackType: 'liked', - reset: true, - ); - - expect(viewModel.isLoading, true); - expect(viewModel.errorMessage, null); - - await getFeedbackFuture; - - expect(viewModel.isLoading, false); - }); - - test('should reset data when reset is true', () async { - // First load - mockRepository.setGetTendersFeedbackResult( - const Result.ok(TestFixtures.likedTendersResponse), - ); - - await viewModel.getTendersFeedback(feedbackType: 'liked', reset: true); - expect(viewModel.likedTendersData, TestFixtures.likedTendersResponse); - - // Reset and load again - await viewModel.getTendersFeedback(feedbackType: 'liked', reset: true); - expect(viewModel.likedTendersData, TestFixtures.likedTendersResponse); - }); - - test('should not load more when already loading', () async { - mockRepository.setGetTendersFeedbackResult( - const Result.ok(TestFixtures.likedTendersResponse), - ); - - viewModel.isLoadingMore = true; - await viewModel.getTendersFeedback(feedbackType: 'liked', reset: false); - - expect(viewModel.likedTendersData, null); - }); - - test('should not load more when no more data', () async { - mockRepository.setGetTendersFeedbackResult( - const Result.ok(TestFixtures.likedTendersResponse), - ); - - viewModel.hasMore = false; - await viewModel.getTendersFeedback(feedbackType: 'liked', reset: false); - - expect(viewModel.likedTendersData, null); - }); - - test('should load more feedback successfully', () async { - mockRepository.setGetTendersFeedbackResult( - const Result.ok(TestFixtures.likedTendersResponse), - ); - - // Set current feedback type - viewModel.getTendersFeedback(feedbackType: 'liked', reset: true); - await viewModel.loadMoreFeedback(); - - expect(viewModel.likedTendersData, TestFixtures.likedTendersResponse); - }); - - test('should not load more feedback when no current type', () async { - await viewModel.loadMoreFeedback(); - expect(viewModel.likedTendersData, null); - }); - }); -} diff --git a/test/view_models/your_tenders_view_model_filter_test.dart b/test/view_models/your_tenders_view_model_filter_test.dart deleted file mode 100644 index cc3e0a4..0000000 --- a/test/view_models/your_tenders_view_model_filter_test.dart +++ /dev/null @@ -1,137 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:flutter_test/flutter_test.dart'; -import 'package:tm_app/core/utils/result.dart'; -import 'package:tm_app/view_models/your_tenders_view_model.dart'; - -import '../fixtures/test_fixtures.dart'; -import '../mocks/mock_your_tenders_repository.dart'; - -void main() { - group('YourTendersViewModel Filter', () { - late MockYourTendersRepository mockRepository; - late YourTendersViewModel viewModel; - - setUp(() { - mockRepository = MockYourTendersRepository(); - viewModel = YourTendersViewModel(yourTendersRepository: mockRepository); - }); - - tearDown(() { - viewModel.dispose(); - }); - - test('should set date range correctly', () { - final dateRange = DateTimeRange( - start: DateTime(2024, 1, 1), - end: DateTime(2024, 1, 31), - ); - - viewModel.setDateRange(dateRange); - - expect(viewModel.selectedDateRange, dateRange); - expect(viewModel.startDateFormatted, '2024/01/01'); - expect(viewModel.endDateFormatted, '2024/01/31'); - expect(viewModel.startDateUnix, '1704067200'); - expect(viewModel.endDateUnix, '1706745600'); - }); - - test('should set status and clear data', () { - // Set some initial data - viewModel.data = TestFixtures.tenderApprovalsResponse; - viewModel.likedTendersData = TestFixtures.likedTendersResponse; - viewModel.isLoading = true; - viewModel.errorMessage = 'Error'; - - viewModel.setStatus('submitted'); - - expect(viewModel.selectedStatus, 'submitted'); - expect(viewModel.data, null); - expect(viewModel.likedTendersData, null); - expect(viewModel.isLoading, false); - expect(viewModel.errorMessage, null); - expect(viewModel.currentPage, 1); - }); - - test('should clear filter and reset state', () { - // Set some initial data - final dateRange = DateTimeRange( - start: DateTime(2024, 1, 1), - end: DateTime(2024, 1, 31), - ); - viewModel.setDateRange(dateRange); - viewModel.setStatus('submitted'); - viewModel.data = TestFixtures.tenderApprovalsResponse; - - viewModel.clearFilter(); - - expect(viewModel.selectedDateRange, null); - expect(viewModel.selectedStatus, 'All'); - expect(viewModel.data, null); - expect(viewModel.likedTendersData, null); - expect(viewModel.isLoading, false); - expect(viewModel.errorMessage, null); - }); - - test('should call with filter for all status', () async { - mockRepository.setGetTendersResult( - const Result.ok(TestFixtures.tenderApprovalsResponse), - ); - - viewModel.selectedStatus = 'All'; - await viewModel.callWithFilter(); - - expect(viewModel.data, TestFixtures.tenderApprovalsResponse); - expect(viewModel.isLoading, false); - }); - - test('should call with filter for submitted status', () async { - mockRepository.setGetTendersResult( - const Result.ok(TestFixtures.tenderApprovalsResponse), - ); - - viewModel.selectedStatus = 'Tenders Submitted'; - await viewModel.callWithFilter(); - - expect(viewModel.data, TestFixtures.tenderApprovalsResponse); - expect(viewModel.isLoading, false); - }); - - test('should call with filter for liked tenders', () async { - mockRepository.setGetTendersFeedbackResult( - const Result.ok(TestFixtures.likedTendersResponse), - ); - - viewModel.selectedStatus = 'Like Tenders'; - await viewModel.callWithFilter(); - - expect(viewModel.likedTendersData, TestFixtures.likedTendersResponse); - expect(viewModel.isLoading, false); - }); - - test('should call with filter for disliked tenders', () async { - mockRepository.setGetTendersFeedbackResult( - const Result.ok(TestFixtures.likedTendersResponse), - ); - - viewModel.selectedStatus = 'Dislike Tenders'; - await viewModel.callWithFilter(); - - expect(viewModel.likedTendersData, TestFixtures.likedTendersResponse); - expect(viewModel.isLoading, false); - }); - - test('should format dates correctly with date range', () { - final dateRange = DateTimeRange( - start: DateTime(2024, 12, 25), - end: DateTime(2024, 12, 31), - ); - - viewModel.setDateRange(dateRange); - - expect(viewModel.startDateFormatted, '2024/12/25'); - expect(viewModel.endDateFormatted, '2024/12/31'); - expect(viewModel.startDateUnix, '1735084800'); - expect(viewModel.endDateUnix, '1735689600'); - }); - }); -} diff --git a/test/view_models/your_tenders_view_model_get_tenders_test.dart b/test/view_models/your_tenders_view_model_get_tenders_test.dart deleted file mode 100644 index bc2f608..0000000 --- a/test/view_models/your_tenders_view_model_get_tenders_test.dart +++ /dev/null @@ -1,146 +0,0 @@ -import 'package:flutter_test/flutter_test.dart'; -import 'package:tm_app/core/utils/result.dart'; -import 'package:tm_app/data/services/model/request_models/get_tenders_request_model/get_tenders_request_model.dart'; -import 'package:tm_app/view_models/your_tenders_view_model.dart'; - -import '../fixtures/test_fixtures.dart'; -import '../mocks/mock_your_tenders_repository.dart'; - -void main() { - group('YourTendersViewModel GetTenders', () { - late MockYourTendersRepository mockRepository; - late YourTendersViewModel viewModel; - - setUp(() { - mockRepository = MockYourTendersRepository(); - viewModel = YourTendersViewModel(yourTendersRepository: mockRepository); - }); - - tearDown(() { - viewModel.dispose(); - }); - - test('should get tenders successfully', () async { - mockRepository.setGetTendersResult( - const Result.ok(TestFixtures.tenderApprovalsResponse), - ); - - final requestModel = GetTendersRequestModel( - status: 'submitted', - createdFrom: '1640995200', - createdTo: '1640995200', - limit: 10, - offset: 0, - ); - - await viewModel.getTenders(requestModel: requestModel); - - expect(viewModel.isLoading, false); - expect(viewModel.data, TestFixtures.tenderApprovalsResponse); - expect(viewModel.errorMessage, null); - expect(viewModel.hasMoreTenders, true); - }); - - test('should handle get tenders error', () async { - const errorMessage = 'Network error'; - mockRepository.setGetTendersResult(Result.error(Exception(errorMessage))); - - final requestModel = GetTendersRequestModel( - status: 'submitted', - createdFrom: '1640995200', - createdTo: '1640995200', - limit: 10, - offset: 0, - ); - - await viewModel.getTenders(requestModel: requestModel); - - expect(viewModel.isLoading, false); - expect(viewModel.data, null); - expect(viewModel.errorMessage, errorMessage); - }); - - test('should set loading state during get tenders', () async { - mockRepository.setGetTendersResult( - const Result.ok(TestFixtures.tenderApprovalsResponse), - ); - - final requestModel = GetTendersRequestModel( - status: 'submitted', - createdFrom: '1640995200', - createdTo: '1640995200', - limit: 10, - offset: 0, - ); - - final getTendersFuture = viewModel.getTenders(requestModel: requestModel); - - expect(viewModel.isLoading, true); - expect(viewModel.errorMessage, null); - - await getTendersFuture; - - expect(viewModel.isLoading, false); - }); - - test('should reset data when reset is true', () async { - // First load - mockRepository.setGetTendersResult( - const Result.ok(TestFixtures.tenderApprovalsResponse), - ); - - final requestModel = GetTendersRequestModel( - status: 'submitted', - createdFrom: '1640995200', - createdTo: '1640995200', - limit: 10, - offset: 0, - ); - - await viewModel.getTenders(requestModel: requestModel); - expect(viewModel.data, TestFixtures.tenderApprovalsResponse); - - // Reset and load again - await viewModel.getTenders(requestModel: requestModel, reset: true); - expect(viewModel.data, TestFixtures.tenderApprovalsResponse); - }); - - test('should not load more when already loading', () async { - mockRepository.setGetTendersResult( - const Result.ok(TestFixtures.tenderApprovalsResponse), - ); - - final requestModel = GetTendersRequestModel( - status: 'submitted', - createdFrom: '1640995200', - createdTo: '1640995200', - limit: 10, - offset: 0, - ); - - viewModel.isLoadingMoreTenders = true; - await viewModel.getTenders(requestModel: requestModel); - - expect(viewModel.data, null); - }); - - test('should not load more when no more data', () async { - mockRepository.setGetTendersResult( - const Result.ok(TestFixtures.tenderApprovalsResponse), - ); - - final requestModel = GetTendersRequestModel( - status: 'submitted', - createdFrom: '1640995200', - createdTo: '1640995200', - limit: 10, - offset: 0, - ); - - viewModel.hasMoreTenders = false; - await viewModel.getTenders(requestModel: requestModel); - - expect(viewModel.data, null); - }); - }); -} diff --git a/test/view_models/your_tenders_view_model_initialization_test.dart b/test/view_models/your_tenders_view_model_initialization_test.dart deleted file mode 100644 index b8fdb2c..0000000 --- a/test/view_models/your_tenders_view_model_initialization_test.dart +++ /dev/null @@ -1,45 +0,0 @@ -import 'package:flutter_test/flutter_test.dart'; -import 'package:tm_app/view_models/your_tenders_view_model.dart'; - -import '../mocks/mock_your_tenders_repository.dart'; - -void main() { - group('YourTendersViewModel Initialization', () { - late MockYourTendersRepository mockRepository; - late YourTendersViewModel viewModel; - - setUp(() { - mockRepository = MockYourTendersRepository(); - viewModel = YourTendersViewModel(yourTendersRepository: mockRepository); - }); - - tearDown(() { - viewModel.dispose(); - }); - - test('should initialize with default values', () { - expect(viewModel.isLoading, false); - expect(viewModel.errorMessage, null); - expect(viewModel.data, null); - expect(viewModel.likedTendersData, null); - expect(viewModel.isLoadingMore, false); - expect(viewModel.hasMore, true); - expect(viewModel.isLoadingMoreTenders, false); - expect(viewModel.hasMoreTenders, true); - expect(viewModel.selectedDateRange, null); - expect(viewModel.selectedStatus, 'All'); - expect(viewModel.currentPage, 1); - }); - - test('should format dates correctly when no date range is selected', () { - expect(viewModel.startDateFormatted, 'YYYY/MM/DD'); - expect(viewModel.endDateFormatted, 'YYYY/MM/DD'); - expect(viewModel.startDateUnix, ''); - expect(viewModel.endDateUnix, ''); - }); - - test('should calculate total pages correctly when no data', () { - expect(viewModel.totalPages, 1); - }); - }); -} diff --git a/test/view_models/your_tenders_view_model_pagination_test.dart b/test/view_models/your_tenders_view_model_pagination_test.dart deleted file mode 100644 index 34d6ccf..0000000 --- a/test/view_models/your_tenders_view_model_pagination_test.dart +++ /dev/null @@ -1,116 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:flutter_test/flutter_test.dart'; -import 'package:tm_app/core/utils/result.dart'; -import 'package:tm_app/view_models/your_tenders_view_model.dart'; - -import '../fixtures/test_fixtures.dart'; -import '../mocks/mock_your_tenders_repository.dart'; - -void main() { - group('YourTendersViewModel Pagination', () { - late MockYourTendersRepository mockRepository; - late YourTendersViewModel viewModel; - - setUp(() { - mockRepository = MockYourTendersRepository(); - viewModel = YourTendersViewModel(yourTendersRepository: mockRepository); - }); - - tearDown(() { - viewModel.dispose(); - }); - - test('should calculate total pages from tender data', () { - viewModel.data = TestFixtures.tenderApprovalsResponse; - expect(viewModel.totalPages, 1); - }); - - test('should calculate total pages from feedback data', () { - viewModel.likedTendersData = TestFixtures.likedTendersResponse; - expect(viewModel.totalPages, 1); - }); - - test('should return 1 when no data available', () { - expect(viewModel.totalPages, 1); - }); - - test('should jump to page successfully', () async { - mockRepository.setGetTendersResult( - const Result.ok(TestFixtures.tenderApprovalsResponse), - ); - - viewModel.selectedStatus = 'All'; - await viewModel.jumpToPage(2); - - expect(viewModel.currentPage, 2); - expect(viewModel.data, TestFixtures.tenderApprovalsResponse); - expect(viewModel.likedTendersData, null); - expect(viewModel.hasMore, true); - expect(viewModel.hasMoreTenders, true); - }); - - test('should load more tenders for all status', () async { - mockRepository.setGetTendersResult( - const Result.ok(TestFixtures.tenderApprovalsResponse), - ); - - viewModel.selectedStatus = 'All'; - await viewModel.loadMoreTenders(); - - expect(viewModel.data, TestFixtures.tenderApprovalsResponse); - }); - - test('should load more tenders for submitted status', () async { - mockRepository.setGetTendersResult( - const Result.ok(TestFixtures.tenderApprovalsResponse), - ); - - viewModel.selectedStatus = 'Tenders Submitted'; - await viewModel.loadMoreTenders(); - - expect(viewModel.data, TestFixtures.tenderApprovalsResponse); - }); - - test('should not load more tenders when already loading', () async { - viewModel.isLoadingMoreTenders = true; - await viewModel.loadMoreTenders(); - - expect(viewModel.data, null); - }); - - test('should not load more tenders when no more data', () async { - viewModel.hasMoreTenders = false; - await viewModel.loadMoreTenders(); - - expect(viewModel.data, null); - }); - - test('should handle pagination with date range', () async { - mockRepository.setGetTendersResult( - const Result.ok(TestFixtures.tenderApprovalsResponse), - ); - - viewModel.selectedStatus = 'All'; - viewModel.setDateRange( - DateTimeRange(start: DateTime(2024, 1, 1), end: DateTime(2024, 1, 31)), - ); - - await viewModel.loadMoreTenders(); - - expect(viewModel.data, TestFixtures.tenderApprovalsResponse); - }); - - test('should reset pagination state when clearing data', () { - // Set some pagination state - viewModel.currentPage = 3; - viewModel.data = TestFixtures.tenderApprovalsResponse; - viewModel.likedTendersData = TestFixtures.likedTendersResponse; - - viewModel.clearFilter(); - - expect(viewModel.currentPage, 1); - expect(viewModel.data, null); - expect(viewModel.likedTendersData, null); - }); - }); -} diff --git a/test/view_models/your_tenders_view_model_utility_test.dart b/test/view_models/your_tenders_view_model_utility_test.dart deleted file mode 100644 index daba893..0000000 --- a/test/view_models/your_tenders_view_model_utility_test.dart +++ /dev/null @@ -1,199 +0,0 @@ -import 'package:flutter_test/flutter_test.dart'; -import 'package:tm_app/core/constants/assets.dart'; -import 'package:tm_app/core/constants/tender_approval_status.dart'; -import 'package:tm_app/core/constants/tender_feedback.dart'; -import 'package:tm_app/core/theme/colors.dart'; -import 'package:tm_app/view_models/your_tenders_view_model.dart'; - -import '../mocks/mock_your_tenders_repository.dart'; - -void main() { - group('YourTendersViewModel Utility Methods', () { - late MockYourTendersRepository mockRepository; - late YourTendersViewModel viewModel; - - setUp(() { - mockRepository = MockYourTendersRepository(); - viewModel = YourTendersViewModel(yourTendersRepository: mockRepository); - }); - - tearDown(() { - viewModel.dispose(); - }); - - group('getBackGroundColorByStatus', () { - test('should return green for liked status', () { - final color = viewModel.getBackGroundColorByStatus( - TenderFeedback.liked.value, - ); - expect(color, AppColors.green0); - }); - - test('should return green for submitted status', () { - final color = viewModel.getBackGroundColorByStatus( - TenderApprovalStatus.submitted.value, - ); - expect(color, AppColors.green0); - }); - - test('should return red for disliked status', () { - final color = viewModel.getBackGroundColorByStatus( - TenderFeedback.disliked.value, - ); - expect(color, AppColors.red0); - }); - - test('should return red for rejected status', () { - final color = viewModel.getBackGroundColorByStatus( - TenderApprovalStatus.rejected.value, - ); - expect(color, AppColors.red0); - }); - - test('should return grey for unknown status', () { - final color = viewModel.getBackGroundColorByStatus('unknown'); - expect(color, AppColors.grey0); - }); - }); - - group('getBorderColorByStatus', () { - test('should return green for liked status', () { - final color = viewModel.getBorderColorByStatus( - TenderFeedback.liked.value, - ); - expect(color, AppColors.green10); - }); - - test('should return green for submitted status', () { - final color = viewModel.getBorderColorByStatus( - TenderApprovalStatus.submitted.value, - ); - expect(color, AppColors.green10); - }); - - test('should return red for disliked status', () { - final color = viewModel.getBorderColorByStatus( - TenderFeedback.disliked.value, - ); - expect(color, AppColors.red10); - }); - - test('should return red for rejected status', () { - final color = viewModel.getBorderColorByStatus( - TenderApprovalStatus.rejected.value, - ); - expect(color, AppColors.red10); - }); - - test('should return grey for unknown status', () { - final color = viewModel.getBorderColorByStatus('unknown'); - expect(color, AppColors.grey30); - }); - }); - - group('getStatusCardColorByStatus', () { - test('should return green for liked status', () { - final color = viewModel.getStatusCardColorByStatus( - TenderFeedback.liked.value, - ); - expect(color, AppColors.green20); - }); - - test('should return green for submitted status', () { - final color = viewModel.getStatusCardColorByStatus( - TenderApprovalStatus.submitted.value, - ); - expect(color, AppColors.green20); - }); - - test('should return red for disliked status', () { - final color = viewModel.getStatusCardColorByStatus( - TenderFeedback.disliked.value, - ); - expect(color, AppColors.red10); - }); - - test('should return red for rejected status', () { - final color = viewModel.getStatusCardColorByStatus( - TenderApprovalStatus.rejected.value, - ); - expect(color, AppColors.red10); - }); - - test('should return grey for unknown status', () { - final color = viewModel.getStatusCardColorByStatus('unknown'); - expect(color, AppColors.grey0); - }); - }); - - group('getStatusTextColorByStatus', () { - test('should return green for liked status', () { - final color = viewModel.getStatusTextColorByStatus( - TenderFeedback.liked.value, - ); - expect(color, AppColors.green30); - }); - - test('should return green for submitted status', () { - final color = viewModel.getStatusTextColorByStatus( - TenderApprovalStatus.submitted.value, - ); - expect(color, AppColors.green30); - }); - - test('should return red for disliked status', () { - final color = viewModel.getStatusTextColorByStatus( - TenderFeedback.disliked.value, - ); - expect(color, AppColors.red20); - }); - - test('should return red for rejected status', () { - final color = viewModel.getStatusTextColorByStatus( - TenderApprovalStatus.rejected.value, - ); - expect(color, AppColors.red20); - }); - - test('should return grey for unknown status', () { - final color = viewModel.getStatusTextColorByStatus('unknown'); - expect(color, AppColors.grey60); - }); - }); - - group('getStatusIconByStatus', () { - test('should return tick circle for liked status', () { - final icon = viewModel.getStatusIconByStatus( - TenderFeedback.liked.value, - ); - expect(icon, AssetsManager.tickCircle); - }); - - test('should return tick circle for submitted status', () { - final icon = viewModel.getStatusIconByStatus( - TenderApprovalStatus.submitted.value, - ); - expect(icon, AssetsManager.tickCircle); - }); - - test('should return close circle for disliked status', () { - final icon = viewModel.getStatusIconByStatus( - TenderFeedback.disliked.value, - ); - expect(icon, AssetsManager.closeCircle); - }); - - test('should return close circle for rejected status', () { - final icon = viewModel.getStatusIconByStatus( - TenderApprovalStatus.rejected.value, - ); - expect(icon, AssetsManager.closeCircle); - }); - - test('should return null for unknown status', () { - final icon = viewModel.getStatusIconByStatus('unknown'); - expect(icon, null); - }); - }); - }); -}