changes
This commit is contained in:
+76
-13
@@ -63,25 +63,88 @@ class _MyAppState extends State<MyApp> {
|
|||||||
_handleMessage(initialMessage);
|
_handleMessage(initialMessage);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (kIsWeb) {
|
// if (kIsWeb) {
|
||||||
FirebaseMessaging.onMessage.listen((event) {
|
FirebaseMessaging.onMessage.listen((event) {
|
||||||
final overlayCtx = rootNavigatorKey.currentContext;
|
final overlayCtx = rootNavigatorKey.currentContext;
|
||||||
if (overlayCtx != null) {
|
AppLogger().error('Notification received: ${event.notification?.web}');
|
||||||
if (overlayCtx.mounted) {
|
AppLogger().error('web link: ${event.notification?.title}');
|
||||||
AppToast.notification(
|
AppLogger().error('web link: ${event.notification?.web?.link}');
|
||||||
overlayCtx,
|
print('Notification received1: ${event.notification?.web.toString()}');
|
||||||
event.notification?.title ?? 'New Notification',
|
print(
|
||||||
event.notification?.body ?? 'New Notification',
|
'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);
|
FirebaseMessaging.onMessageOpenedApp.listen(_handleMessage);
|
||||||
}
|
}
|
||||||
|
|
||||||
void _handleMessage(RemoteMessage message) {
|
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(
|
appRouter.go(
|
||||||
const SplashScreenRoute(navigateToNotification: true).location,
|
const SplashScreenRoute(navigateToNotification: true).location,
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -147,7 +147,11 @@ class AuthViewModel with ChangeNotifier {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Future<void> localLogout() async {
|
Future<void> localLogout() async {
|
||||||
await _authRepository.localLogout();
|
try {
|
||||||
|
await _authRepository.localLogout();
|
||||||
|
} catch (e) {
|
||||||
|
// Handle local logout exceptions gracefully
|
||||||
|
}
|
||||||
_loggedInUser = null;
|
_loggedInUser = null;
|
||||||
notifyListeners();
|
notifyListeners();
|
||||||
}
|
}
|
||||||
@@ -160,7 +164,7 @@ class AuthViewModel with ChangeNotifier {
|
|||||||
|
|
||||||
switch (result) {
|
switch (result) {
|
||||||
case Ok<LogoutResponse>():
|
case Ok<LogoutResponse>():
|
||||||
localLogout();
|
await localLogout();
|
||||||
break;
|
break;
|
||||||
case Error<LogoutResponse>():
|
case Error<LogoutResponse>():
|
||||||
_errorMessage = result.error.toString();
|
_errorMessage = result.error.toString();
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import 'package:flutter/material.dart';
|
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/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_data/company_profile_data.dart';
|
||||||
import 'package:tm_app/data/services/model/company_profile_response/company_profile_response.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;
|
CompanyProfileData? _companyProfileData;
|
||||||
bool _loggedOut = false;
|
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 isLoading => _isLoading;
|
||||||
bool get loggedOut => _loggedOut;
|
bool get loggedOut => _loggedOut;
|
||||||
String? get errorMessage => _errorMessage;
|
String? get errorMessage => _errorMessage;
|
||||||
ProfileData? get profileData => _profileData;
|
ProfileData? get profileData => _profileData;
|
||||||
CompanyProfileData? get companyProfileData => _companyProfileData;
|
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 = <String>[];
|
||||||
|
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<void> getProfile() async {
|
Future<void> getProfile() async {
|
||||||
_isLoading = true;
|
_isLoading = true;
|
||||||
_errorMessage = null;
|
_errorMessage = null;
|
||||||
@@ -98,4 +152,28 @@ class ProfileViewModel with ChangeNotifier {
|
|||||||
_loggedOut = false;
|
_loggedOut = false;
|
||||||
notifyListeners();
|
notifyListeners();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Future<void> 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();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -28,6 +28,10 @@ class _MobileProfilePageState extends State<MobileProfilePage> {
|
|||||||
super.initState();
|
super.initState();
|
||||||
viewModel = context.read<ProfileViewModel>();
|
viewModel = context.read<ProfileViewModel>();
|
||||||
viewModel.addListener(_profileListener);
|
viewModel.addListener(_profileListener);
|
||||||
|
// Load notification info saved in SharedPreferences
|
||||||
|
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||||
|
viewModel.loadNotificationInfoFromPrefs();
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
void _profileListener() {
|
void _profileListener() {
|
||||||
@@ -75,6 +79,30 @@ class _MobileProfilePageState extends State<MobileProfilePage> {
|
|||||||
child: Column(
|
child: Column(
|
||||||
children: [
|
children: [
|
||||||
SizedBox(height: 36.0.h()),
|
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(
|
TitleDescription(
|
||||||
title: ProfileStrings.companyName,
|
title: ProfileStrings.companyName,
|
||||||
description: viewModel.companyProfileData!.name!,
|
description: viewModel.companyProfileData!.name!,
|
||||||
|
|||||||
Vendored
+283
@@ -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,
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -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<LoginResponseModel>? _loginResult;
|
||||||
|
Result<LogoutResponse>? _logoutResult;
|
||||||
|
Result<ForgotPasswordResponseModel>? _forgotPasswordResult;
|
||||||
|
Result<VerifyOtpResponseModel>? _verifyOtpResult;
|
||||||
|
Result<ResetPasswordResponse>? _resetPasswordResult;
|
||||||
|
Result<bool>? _checkIsLoggedInResult;
|
||||||
|
Exception? _localLogoutException;
|
||||||
|
|
||||||
|
void setLoginResult(Result<LoginResponseModel> result) {
|
||||||
|
_loginResult = result;
|
||||||
|
}
|
||||||
|
|
||||||
|
void setLogoutResult(Result<LogoutResponse> result) {
|
||||||
|
_logoutResult = result;
|
||||||
|
}
|
||||||
|
|
||||||
|
void setForgotPasswordResult(Result<ForgotPasswordResponseModel> result) {
|
||||||
|
_forgotPasswordResult = result;
|
||||||
|
}
|
||||||
|
|
||||||
|
void setVerifyOtpResult(Result<VerifyOtpResponseModel> result) {
|
||||||
|
_verifyOtpResult = result;
|
||||||
|
}
|
||||||
|
|
||||||
|
void setResetPasswordResult(Result<ResetPasswordResponse> result) {
|
||||||
|
_resetPasswordResult = result;
|
||||||
|
}
|
||||||
|
|
||||||
|
void setCheckIsLoggedInResult(Result<bool> result) {
|
||||||
|
_checkIsLoggedInResult = result;
|
||||||
|
}
|
||||||
|
|
||||||
|
void setLocalLogoutException(Exception exception) {
|
||||||
|
_localLogoutException = exception;
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<Result<LoginResponseModel>> login({
|
||||||
|
required String username,
|
||||||
|
required String password,
|
||||||
|
}) async {
|
||||||
|
return _loginResult ?? const Result.ok(TestFixtures.loginResponse);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<Result<LogoutResponse>> logout() async {
|
||||||
|
return _logoutResult ?? const Result.ok(TestFixtures.logoutResponse);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<Result<ResetPasswordResponse>> resetPassword({
|
||||||
|
required String newPassword,
|
||||||
|
required String token,
|
||||||
|
}) async {
|
||||||
|
return _resetPasswordResult ??
|
||||||
|
const Result.ok(TestFixtures.resetPasswordResponse);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<void> localLogout() async {
|
||||||
|
if (_localLogoutException != null) {
|
||||||
|
throw _localLogoutException!;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<Result<ForgotPasswordResponseModel>> forgotPassword({
|
||||||
|
required String email,
|
||||||
|
}) async {
|
||||||
|
return _forgotPasswordResult ??
|
||||||
|
const Result.ok(TestFixtures.forgotPasswordResponse);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<Result<VerifyOtpResponseModel>> verifyOtp({
|
||||||
|
required String code,
|
||||||
|
required String email,
|
||||||
|
}) async {
|
||||||
|
return _verifyOtpResult ?? const Result.ok(TestFixtures.verifyOtpResponse);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<Result<bool>> checkIsLoggedIn() async {
|
||||||
|
return _checkIsLoggedInResult ?? const Result.ok(true);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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<TenderApprovalsResponse>? _getTendersResult;
|
||||||
|
Result<LikedTendersResponse>? _getTendersFeedbackResult;
|
||||||
|
|
||||||
|
void setGetTendersResult(Result<TenderApprovalsResponse> result) {
|
||||||
|
_getTendersResult = result;
|
||||||
|
}
|
||||||
|
|
||||||
|
void setGetTendersFeedbackResult(Result<LikedTendersResponse> result) {
|
||||||
|
_getTendersFeedbackResult = result;
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<Result<TenderApprovalsResponse>> getTenders({
|
||||||
|
required GetTendersRequestModel requestModel,
|
||||||
|
}) async {
|
||||||
|
return _getTendersResult ??
|
||||||
|
const Result.ok(TestFixtures.tenderApprovalsResponse);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<Result<LikedTendersResponse>> getTendersFeedback({
|
||||||
|
required TenderFeedbackRequestModel requestModel,
|
||||||
|
}) async {
|
||||||
|
return _getTendersFeedbackResult ??
|
||||||
|
const Result.ok(TestFixtures.likedTendersResponse);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
@@ -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);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
@@ -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);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
@@ -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();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
@@ -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));
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
@@ -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);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
@@ -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);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
@@ -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');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
@@ -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);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
@@ -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);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
@@ -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);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
@@ -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);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user