diff --git a/integration_test/inspection_module_test.dart b/integration_test/inspection_module_test.dart new file mode 100644 index 0000000..0ba0c7f --- /dev/null +++ b/integration_test/inspection_module_test.dart @@ -0,0 +1,91 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:integration_test/integration_test.dart'; +import 'package:mocktail/mocktail.dart'; +import 'package:rasadyar_inspection/presentation/pages/auth/view.dart'; +import 'package:rasadyar_inspection/presentation/pages/auth/logic.dart'; +import 'package:rasadyar_core/core.dart'; + +class MockAuthLogic extends GetxController with Mock implements AuthLogic {} +class MockTokenStorageService extends Mock implements TokenStorageService {} + +void main() { + IntegrationTestWidgetsFlutterBinding.ensureInitialized(); + + group('Inspection Module Integration Tests', () { + late MockAuthLogic mockAuthLogic; + late MockTokenStorageService mockTokenService; + + setUp(() { + mockAuthLogic = MockAuthLogic(); + mockTokenService = MockTokenStorageService(); + + // Setup mock behaviors + mockAuthLogic.textAnimation = AlwaysStoppedAnimation(1.0); + mockAuthLogic.isLoading = false.obs; + mockAuthLogic.usernameController = TextEditingController().obs; + mockAuthLogic.passwordController = TextEditingController().obs; + + Get.put(mockAuthLogic); + Get.put(mockTokenService); + }); + + tearDown(() { + Get.reset(); + }); + + testWidgets('should complete authentication flow', (WidgetTester tester) async { + // Act + await tester.pumpWidget( + MaterialApp( + home: AuthPage(), + ), + ); + + // Assert - Verify auth page loads correctly + expect(find.text('به سامانه رصدیار خوش آمدید!'), findsOneWidget); + expect(find.text('سامانه رصد و پایش زنجیره تامین، تولید و توزیع کالا های اساسی'), findsOneWidget); + + // Test page navigation and UI interactions + await tester.pumpAndSettle(); + expect(find.byType(Scaffold), findsOneWidget); + expect(find.byType(FadeTransition), findsOneWidget); + }); + + testWidgets('should handle back navigation correctly', (WidgetTester tester) async { + // Act + await tester.pumpWidget( + MaterialApp( + home: AuthPage(), + ), + ); + + // Verify PopScope behavior + final popScope = tester.widget(find.byType(PopScope)); + expect(popScope.canPop, false); + + // Test that back button handling works + await tester.pump(); + expect(find.byType(AuthPage), findsOneWidget); + }); + + testWidgets('should display welcome animation correctly', (WidgetTester tester) async { + // Act + await tester.pumpWidget( + MaterialApp( + home: AuthPage(), + ), + ); + + // Assert - Check animation components + expect(find.byType(FadeTransition), findsOneWidget); + expect(find.byType(Column), findsOneWidget); + + // Verify text styling + final welcomeText = tester.widget( + find.text('به سامانه رصدیار خوش آمدید!') + ); + expect(welcomeText.style?.color, Colors.white); + }); + }); +} diff --git a/integration_test/modules_integration_test.dart b/integration_test/modules_integration_test.dart new file mode 100644 index 0000000..04ec1ed --- /dev/null +++ b/integration_test/modules_integration_test.dart @@ -0,0 +1,119 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:integration_test/integration_test.dart'; +import 'package:get/get.dart'; +import 'package:mocktail/mocktail.dart'; +import 'package:rasadyar_app/presentation/pages/modules/view.dart'; +import 'package:rasadyar_app/presentation/pages/modules/logic.dart'; +import 'package:rasadyar_core/core.dart'; + +class MockModulesLogic extends GetxController with Mock implements ModulesLogic {} +class MockTokenStorageService extends Mock implements TokenStorageService {} +class MockSliderLogic extends GetxController with Mock implements SliderLogic {} + +void main() { + IntegrationTestWidgetsFlutterBinding.ensureInitialized(); + + group('Modules Page Integration Tests', () { + late MockModulesLogic mockModulesLogic; + late MockTokenStorageService mockTokenService; + late MockSliderLogic mockUpSlider; + late MockSliderLogic mockDownSlider; + + setUp(() { + mockModulesLogic = MockModulesLogic(); + mockTokenService = MockTokenStorageService(); + mockUpSlider = MockSliderLogic(); + mockDownSlider = MockSliderLogic(); + + // Setup mock behaviors + mockModulesLogic.isLoading = false.obs; + mockModulesLogic.moduleList = [ + ModuleModel( + title: 'رصدطیور', + icon: 'test_icon.svg', + module: Module.chicken, + borderColor: Color(0xFF4665AF), + backgroundColor: Color(0xFFECEEF2), + titleColor: Color(0xFF4665AF), + ), + ModuleModel( + title: 'رصدبان', + icon: 'test_icon2.svg', + module: Module.inspection, + borderColor: Color(0xFF014856), + backgroundColor: Color(0xFFE9EDED), + titleColor: Color(0xFF014856), + ), + ]; + + Get.put(mockModulesLogic); + Get.put(mockTokenService); + Get.put(mockUpSlider, tag: "up"); + Get.put(mockDownSlider, tag: "down"); + }); + + tearDown(() { + Get.reset(); + }); + + testWidgets('should display modules grid and handle user interaction', (WidgetTester tester) async { + // Act + await tester.pumpWidget( + MaterialApp( + home: ModulesPage(), + ), + ); + await tester.pumpAndSettle(); + + // Assert - Verify modules page loads correctly + expect(find.text('سامانه جامع رصدیار'), findsOneWidget); + expect(find.byType(AppBar), findsOneWidget); + expect(find.byType(GridView), findsOneWidget); + + // Verify app bar styling + final appBar = tester.widget(find.byType(AppBar)); + expect(appBar.backgroundColor, AppColor.blueNormal); + expect(appBar.centerTitle, true); + }); + + testWidgets('should show loading state correctly', (WidgetTester tester) async { + // Arrange + mockModulesLogic.isLoading.value = true; + + // Act + await tester.pumpWidget( + MaterialApp( + home: ModulesPage(), + ), + ); + await tester.pump(); + + // Assert - Should show loading indicator + expect(find.byType(Container), findsWidgets); + + // Change to not loading + mockModulesLogic.isLoading.value = false; + await tester.pump(); + + // Should show main content + expect(find.byType(Column), findsWidgets); + }); + + testWidgets('should handle module selection', (WidgetTester tester) async { + // Act + await tester.pumpWidget( + MaterialApp( + home: ModulesPage(), + ), + ); + await tester.pumpAndSettle(); + + // Assert - Grid should be present for module selection + expect(find.byType(GridView), findsOneWidget); + + // Verify modules are accessible + expect(mockModulesLogic.moduleList.length, greaterThan(0)); + }); + }); +} diff --git a/test/unit/controllers/modules_logic_test.dart b/test/unit/controllers/modules_logic_test.dart new file mode 100644 index 0000000..2e83b0f --- /dev/null +++ b/test/unit/controllers/modules_logic_test.dart @@ -0,0 +1,121 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:get/get.dart'; +import 'package:mocktail/mocktail.dart'; +import 'package:rasadyar_app/presentation/pages/modules/logic.dart'; +import 'package:rasadyar_core/core.dart'; + +class MockTokenStorageService extends Mock implements TokenStorageService {} + +class MockSliderLogic extends GetxController with Mock implements SliderLogic {} + +void main() { + group('ModulesLogic Tests', () { + late ModulesLogic modulesLogic; + late MockTokenStorageService mockTokenService; + late MockSliderLogic mockUpSlider; + late MockSliderLogic mockDownSlider; + + setUp(() { + mockTokenService = MockTokenStorageService(); + mockUpSlider = MockSliderLogic(); + mockDownSlider = MockSliderLogic(); + + Get.put(mockTokenService); + Get.put(mockUpSlider, tag: "up"); + Get.put(mockDownSlider, tag: "down"); + + modulesLogic = ModulesLogic(); + }); + + tearDown(() { + Get.reset(); + }); + + test('should initialize with correct default values', () { + // Assert + expect(modulesLogic.isLoading.value, false); + expect(modulesLogic.latestNews.value, null); + expect(modulesLogic.moduleList, isNotEmpty); + expect(modulesLogic.moduleList.length, greaterThan(0)); + }); + + test('should have correct module list structure', () { + // Assert + expect(modulesLogic.moduleList, isA>()); + + // Check that chicken module exists + final chickenModule = modulesLogic.moduleList.firstWhere( + (module) => module.module == Module.chicken, + ); + expect(chickenModule.title, 'رصدطیور'); + expect(chickenModule.borderColor, Color(0xFF4665AF)); + + // Check that inspection module exists + final inspectionModule = modulesLogic.moduleList.firstWhere( + (module) => module.module == Module.inspection, + ); + expect(inspectionModule.title, 'رصدبان'); + expect(inspectionModule.borderColor, Color(0xFF014856)); + }); + + test('should update isLoading correctly', () { + // Act + modulesLogic.isLoading.value = true; + + // Assert + expect(modulesLogic.isLoading.value, true); + + // Act + modulesLogic.isLoading.value = false; + + // Assert + expect(modulesLogic.isLoading.value, false); + }); + + test('should update latestNews correctly', () { + // Act + modulesLogic.latestNews.value = 'Test news'; + + // Assert + expect(modulesLogic.latestNews.value, 'Test news'); + + // Act + modulesLogic.latestNews.value = null; + + // Assert + expect(modulesLogic.latestNews.value, null); + }); + + test('should have livestock module with correct properties', () { + // Assert + final livestockModule = modulesLogic.moduleList.firstWhere( + (module) => module.module == Module.liveStocks, + ); + expect(livestockModule.title, 'رصدام'); + expect(livestockModule.borderColor, Color(0xFFD7A972)); + expect(livestockModule.backgroundColor, Color(0xFFF4F1EF)); + expect(livestockModule.titleColor, Color(0xFF7F7F7F)); + }); + + test('should have all required modules in moduleList', () { + // Assert + final moduleTypes = modulesLogic.moduleList.map((m) => m.module).toList(); + + expect(moduleTypes.contains(Module.chicken), true); + expect(moduleTypes.contains(Module.liveStocks), true); + expect(moduleTypes.contains(Module.inspection), true); + }); + + test('should access slider controllers correctly', () { + // Assert + expect(modulesLogic.upSlider, isA()); + expect(modulesLogic.downSlider, isA()); + }); + + test('should access token service correctly', () { + // Assert + expect(modulesLogic.tokenService, isA()); + }); + }); +} diff --git a/test/unit/controllers/splash_logic_test.dart b/test/unit/controllers/splash_logic_test.dart new file mode 100644 index 0000000..9d8a193 --- /dev/null +++ b/test/unit/controllers/splash_logic_test.dart @@ -0,0 +1,93 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:get/get.dart'; +import 'package:mocktail/mocktail.dart'; +import 'package:rasadyar_app/presentation/pages/splash/logic.dart'; +import 'package:rasadyar_core/core.dart'; + +class MockGService extends Mock implements GService {} + +class MockTokenStorageService extends Mock implements TokenStorageService {} + +class MockTickerProvider extends Mock implements TickerProvider { + @override + Ticker createTicker(TickerCallback onTick) { + return Ticker(onTick); + } +} + +void main() { + group('SplashLogic Tests', () { + late SplashLogic splashLogic; + late MockGService mockGService; + late MockTokenStorageService mockTokenService; + + setUp(() { + mockGService = MockGService(); + mockTokenService = MockTokenStorageService(); + + Get.put(mockGService); + Get.put(mockTokenService); + + splashLogic = SplashLogic(); + }); + + tearDown(() { + Get.reset(); + }); + + test('should initialize animation controllers on init', () { + // Act + splashLogic.onInit(); + + // Assert + expect(splashLogic.scaleController, isA()); + expect(splashLogic.rotateController, isA()); + expect(splashLogic.scaleAnimation.value, isA>()); + expect(splashLogic.rotationAnimation.value, isA>()); + }); + + test('should have correct initial values for reactive variables', () { + // Assert + expect(splashLogic.hasUpdated.value, false); + expect(splashLogic.onUpdateDownload.value, false); + expect(splashLogic.percent.value, 0.0); + }); + + test('should dispose animation controllers on close', () { + // Arrange + splashLogic.onInit(); + + // Act + splashLogic.onClose(); + + // Assert - controllers should be disposed + expect(() => splashLogic.scaleController.forward(), throwsA(isA())); + expect(() => splashLogic.rotateController.forward(), throwsA(isA())); + }); + + test('should update percent value correctly', () { + // Act + splashLogic.percent.value = 0.5; + + // Assert + expect(splashLogic.percent.value, 0.5); + }); + + test('should update hasUpdated value correctly', () { + // Act + splashLogic.hasUpdated.value = true; + + // Assert + expect(splashLogic.hasUpdated.value, true); + }); + + test('should update onUpdateDownload value correctly', () { + // Act + splashLogic.onUpdateDownload.value = true; + + // Assert + expect(splashLogic.onUpdateDownload.value, true); + }); + }); +} diff --git a/test/unit/packages/core/services/g_service_test.dart b/test/unit/packages/core/services/g_service_test.dart new file mode 100644 index 0000000..fe25700 --- /dev/null +++ b/test/unit/packages/core/services/g_service_test.dart @@ -0,0 +1,122 @@ +import 'package:flutter_test/flutter_test.dart'; +import 'package:get/get.dart'; +import 'package:mocktail/mocktail.dart'; +import 'package:rasadyar_core/core.dart'; + +class MockHiveBox extends Mock implements Box {} + +void main() { + group('GService Tests', () { + late GService gService; + late MockHiveBox mockBox; + + setUp(() { + mockBox = MockHiveBox(); + gService = GService(); + + // Setup Hive mock + when(() => mockBox.get(any())).thenReturn(null); + when(() => mockBox.put(any(), any())).thenAnswer((_) async {}); + when(() => mockBox.delete(any())).thenAnswer((_) async {}); + when(() => mockBox.clear()).thenAnswer((_) async {}); + }); + + tearDown(() { + Get.reset(); + }); + + test('should initialize correctly', () async { + // Act + await gService.init(); + + // Assert + expect(gService, isA()); + }); + + test('should check first time correctly when true', () { + // Arrange + when(() => mockBox.get('isFirstTime')).thenReturn(true); + + // Act + final result = gService.isFirstTime(); + + // Assert + expect(result, isTrue); + }); + + test('should check first time correctly when false', () { + // Arrange + when(() => mockBox.get('isFirstTime')).thenReturn(false); + + // Act + final result = gService.isFirstTime(); + + // Assert + expect(result, isFalse); + }); + + test('should return true for first time when no value stored', () { + // Arrange + when(() => mockBox.get('isFirstTime')).thenReturn(null); + + // Act + final result = gService.isFirstTime(); + + // Assert + expect(result, isTrue); + }); + + test('should set is not first time', () async { + // Act + await gService.setIsNotFirstTime(); + + // Assert - Method should complete without error + expect(() => gService.setIsNotFirstTime(), returnsNormally); + }); + + test('should save and retrieve target page', () { + // Arrange + const testPage = '/test-page'; + when(() => mockBox.get('targetPage')).thenReturn(testPage); + + // Act + gService.saveTargetPage(testPage); + final retrievedPage = gService.getTargetPage(); + + // Assert + expect(retrievedPage, equals(testPage)); + }); + + test('should handle null target page', () { + // Arrange + when(() => mockBox.get('targetPage')).thenReturn(null); + + // Act + final retrievedPage = gService.getTargetPage(); + + // Assert + expect(retrievedPage, isNull); + }); + + test('should clear target page', () { + // Act + gService.clearTargetPage(); + + // Assert + expect(() => gService.clearTargetPage(), returnsNormally); + }); + + test('should save and retrieve user preferences', () { + // Arrange + const testPreference = 'dark_mode'; + when(() => mockBox.get('userPreference')).thenReturn(testPreference); + + // Act + gService.saveUserPreference(testPreference); + final retrievedPreference = gService.getUserPreference(); + + // Assert + expect(retrievedPreference, equals(testPreference)); + }); + }); +} diff --git a/test/unit/packages/core/services/token_storage_service_test.dart b/test/unit/packages/core/services/token_storage_service_test.dart new file mode 100644 index 0000000..a0b9db9 --- /dev/null +++ b/test/unit/packages/core/services/token_storage_service_test.dart @@ -0,0 +1,104 @@ +import 'package:flutter_test/flutter_test.dart'; +import 'package:get/get.dart'; +import 'package:mocktail/mocktail.dart'; +import 'package:rasadyar_core/core.dart'; + +class MockHiveBox extends Mock implements Box {} + +void main() { + group('TokenStorageService Tests', () { + late TokenStorageService tokenStorageService; + late MockHiveBox mockBox; + + setUp(() { + mockBox = MockHiveBox(); + tokenStorageService = TokenStorageService(); + + // Setup Hive mock + when(() => mockBox.get(any())).thenReturn(null); + when(() => mockBox.put(any(), any())).thenAnswer((_) async {}); + when(() => mockBox.delete(any())).thenAnswer((_) async {}); + when(() => mockBox.clear()).thenAnswer((_) async {}); + }); + + tearDown(() { + Get.reset(); + }); + + test('should initialize correctly', () async { + // Act + await tokenStorageService.init(); + + // Assert + expect(tokenStorageService, isA()); + }); + + test('should save and retrieve access token', () { + // Arrange + const testToken = 'test_access_token'; + when(() => mockBox.get('accessToken')).thenReturn(testToken); + + // Act + tokenStorageService.saveAccessToken(testToken); + final retrievedToken = tokenStorageService.getAccessToken(); + + // Assert + expect(retrievedToken, equals(testToken)); + }); + + test('should save and retrieve refresh token', () { + // Arrange + const testToken = 'test_refresh_token'; + when(() => mockBox.get('refreshToken')).thenReturn(testToken); + + // Act + tokenStorageService.saveRefreshToken(testToken); + final retrievedToken = tokenStorageService.getRefreshToken(); + + // Assert + expect(retrievedToken, equals(testToken)); + }); + + test('should clear all tokens', () { + // Act + tokenStorageService.clearTokens(); + + // Assert + expect(tokenStorageService.getAccessToken(), isNull); + expect(tokenStorageService.getRefreshToken(), isNull); + }); + + test('should check if user is authenticated', () { + // Arrange + when(() => mockBox.get('accessToken')).thenReturn('valid_token'); + + // Act + final isAuthenticated = tokenStorageService.isAuthenticated(); + + // Assert + expect(isAuthenticated, isTrue); + }); + + test('should return false when not authenticated', () { + // Arrange + when(() => mockBox.get('accessToken')).thenReturn(null); + + // Act + final isAuthenticated = tokenStorageService.isAuthenticated(); + + // Assert + expect(isAuthenticated, isFalse); + }); + + test('should handle empty token strings', () { + // Arrange + when(() => mockBox.get('accessToken')).thenReturn(''); + + // Act + final isAuthenticated = tokenStorageService.isAuthenticated(); + + // Assert + expect(isAuthenticated, isFalse); + }); + }); +} diff --git a/test/unit/packages/inspection/controllers/auth_logic_test.dart b/test/unit/packages/inspection/controllers/auth_logic_test.dart new file mode 100644 index 0000000..f9b5de9 --- /dev/null +++ b/test/unit/packages/inspection/controllers/auth_logic_test.dart @@ -0,0 +1,209 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:get/get.dart'; +import 'package:mocktail/mocktail.dart'; +import 'package:rasadyar_inspection/presentation/pages/auth/logic.dart'; +import 'package:rasadyar_inspection/data/repositories/auth/auth_repository_imp.dart'; +import 'package:rasadyar_inspection/presentation/widget/captcha/logic.dart'; +import 'package:rasadyar_core/core.dart'; + +class MockAuthRepositoryImpl extends Mock implements AuthRepositoryImpl {} + +class MockTokenStorageService extends Mock implements TokenStorageService {} + +class MockCaptchaWidgetLogic extends GetxController with Mock implements CaptchaWidgetLogic { + @override + GlobalKey formKey = GlobalKey(); +} + +class MockTickerProvider extends Mock implements TickerProvider { + @override + Ticker createTicker(TickerCallback onTick) { + return Ticker(onTick); + } +} + +void main() { + group('AuthLogic Tests', () { + late AuthLogic authLogic; + late MockAuthRepositoryImpl mockAuthRepository; + late MockTokenStorageService mockTokenService; + late MockCaptchaWidgetLogic mockCaptchaController; + + setUp(() { + mockAuthRepository = MockAuthRepositoryImpl(); + mockTokenService = MockTokenStorageService(); + mockCaptchaController = MockCaptchaWidgetLogic(); + + Get.put(mockTokenService); + Get.put(mockCaptchaController); + + // Mock the Get.arguments to return a Module + Get.testMode = true; + }); + + tearDown(() { + Get.reset(); + }); + + test('should initialize with correct default values', () { + // Arrange & Act + authLogic = AuthLogic(); + + // Assert + expect(authLogic.showCard.value, false); + expect(authLogic.rememberMe.value, false); + expect(authLogic.isLoading.value, false); + expect(authLogic.isDisabled.value, true); + expect(authLogic.authType.value, AuthType.useAndPass); + expect(authLogic.authStatus.value, AuthStatus.init); + expect(authLogic.otpStatus.value, OtpStatus.init); + expect(authLogic.secondsRemaining.value, 120); + expect(authLogic.phoneNumber.value, null); + }); + + test('should have correct controllers initialized', () { + // Arrange & Act + authLogic = AuthLogic(); + + // Assert + expect(authLogic.usernameController.value, isA()); + expect(authLogic.passwordController.value, isA()); + expect(authLogic.phoneOtpNumberController.value, isA()); + expect(authLogic.otpCodeController.value, isA()); + expect(authLogic.formKey, isA>()); + }); + + test('should start timer correctly', () { + // Arrange + authLogic = AuthLogic(); + + // Act + authLogic.startTimer(); + + // Assert + expect(authLogic.secondsRemaining.value, 120); + }); + + test('should format time correctly', () { + // Arrange + authLogic = AuthLogic(); + + // Act + authLogic.secondsRemaining.value = 90; // 1 minute 30 seconds + String formatted = authLogic.timeFormatted; + + // Assert + expect(formatted, '01:30'); + + // Act + authLogic.secondsRemaining.value = 5; // 5 seconds + formatted = authLogic.timeFormatted; + + // Assert + expect(formatted, '00:05'); + }); + + test('should stop timer correctly', () { + // Arrange + authLogic = AuthLogic(); + authLogic.startTimer(); + + // Act + authLogic.stopTimer(); + + // The timer should be cancelled, but we can't directly test this + // We can test that the method doesn't throw an error + expect(() => authLogic.stopTimer(), returnsNormally); + }); + + test('should update authType correctly', () { + // Arrange + authLogic = AuthLogic(); + + // Act + authLogic.authType.value = AuthType.otp; + + // Assert + expect(authLogic.authType.value, AuthType.otp); + }); + + test('should update otpStatus correctly', () { + // Arrange + authLogic = AuthLogic(); + + // Act + authLogic.otpStatus.value = OtpStatus.sent; + + // Assert + expect(authLogic.otpStatus.value, OtpStatus.sent); + + // Act + authLogic.otpStatus.value = OtpStatus.verified; + + // Assert + expect(authLogic.otpStatus.value, OtpStatus.verified); + }); + + test('should update phoneNumber correctly', () { + // Arrange + authLogic = AuthLogic(); + + // Act + authLogic.phoneNumber.value = '09123456789'; + + // Assert + expect(authLogic.phoneNumber.value, '09123456789'); + }); + + test('should update loading state correctly', () { + // Arrange + authLogic = AuthLogic(); + + // Act + authLogic.isLoading.value = true; + + // Assert + expect(authLogic.isLoading.value, true); + + // Act + authLogic.isLoading.value = false; + + // Assert + expect(authLogic.isLoading.value, false); + }); + + test('should update rememberMe correctly', () { + // Arrange + authLogic = AuthLogic(); + + // Act + authLogic.rememberMe.value = true; + + // Assert + expect(authLogic.rememberMe.value, true); + }); + + test('should have correct dependencies injected', () { + // Arrange & Act + authLogic = AuthLogic(); + + // Assert + expect(authLogic.tokenStorageService, isA()); + expect(authLogic.captchaController, isA()); + expect(authLogic.authRepository, isA()); + }); + + test('should dispose timer on close', () { + // Arrange + authLogic = AuthLogic(); + authLogic.startTimer(); + + // Act + authLogic.onClose(); + + // Timer should be cancelled + expect(() => authLogic.onClose(), returnsNormally); + }); + }); +} diff --git a/test/unit/packages/inspection/pages/auth_page_test.dart b/test/unit/packages/inspection/pages/auth_page_test.dart new file mode 100644 index 0000000..da64646 --- /dev/null +++ b/test/unit/packages/inspection/pages/auth_page_test.dart @@ -0,0 +1,141 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:get/get.dart'; +import 'package:mocktail/mocktail.dart'; +import 'package:rasadyar_inspection/presentation/pages/auth/logic.dart'; +import 'package:rasadyar_inspection/presentation/pages/auth/view.dart'; +import 'package:rasadyar_core/core.dart'; + +class MockAuthLogic extends GetxController with Mock implements AuthLogic { + @override + late Animation textAnimation; + + @override + RxBool isLoading = false.obs; + + @override + RxBool obscurePassword = true.obs; + + @override + TextEditingController usernameController = TextEditingController(); + + @override + TextEditingController passwordController = TextEditingController(); +} + +class MockAnimationController extends Mock implements AnimationController {} + +void main() { + group('AuthPage Tests', () { + late MockAuthLogic mockController; + late MockAnimationController mockAnimationController; + + setUp(() { + mockController = MockAuthLogic(); + mockAnimationController = MockAnimationController(); + + // Setup mock animation + mockController.textAnimation = AlwaysStoppedAnimation(1.0); + + Get.put(mockController); + }); + + tearDown(() { + Get.reset(); + }); + + testWidgets('should display welcome text in Persian', (WidgetTester tester) async { + // Act + await tester.pumpWidget( + MaterialApp( + home: AuthPage(), + ), + ); + + // Assert + expect(find.text('به سامانه رصدیار خوش آمدید!'), findsOneWidget); + expect(find.text('سامانه رصد و پایش زنجیره تامین، تولید و توزیع کالا های اساسی'), findsOneWidget); + }); + + testWidgets('should have correct scaffold structure', (WidgetTester tester) async { + // Act + await tester.pumpWidget( + MaterialApp( + home: AuthPage(), + ), + ); + + // Assert + expect(find.byType(Scaffold), findsOneWidget); + expect(find.byType(PopScope), findsOneWidget); + expect(find.byType(Stack), findsOneWidget); + expect(find.byType(FadeTransition), findsOneWidget); + }); + + testWidgets('should display welcome texts with correct styling', (WidgetTester tester) async { + // Act + await tester.pumpWidget( + MaterialApp( + home: AuthPage(), + ), + ); + + // Assert + final welcomeText = tester.widget( + find.text('به سامانه رصدیار خوش آمدید!') + ); + expect(welcomeText.style?.color, Colors.white); + + final descriptionText = tester.widget( + find.text('سامانه رصد و پایش زنجیره تامین، تولید و توزیع کالا های اساسی') + ); + expect(descriptionText.style?.color, Colors.white); + expect(descriptionText.textAlign, TextAlign.center); + }); + + testWidgets('should have background SVG asset', (WidgetTester tester) async { + // Act + await tester.pumpWidget( + MaterialApp( + home: AuthPage(), + ), + ); + + // Assert + expect(find.byType(Stack), findsOneWidget); + // The SVG background should be present in the stack + final stack = tester.widget(find.byType(Stack)); + expect(stack.fit, StackFit.expand); + expect(stack.alignment, Alignment.center); + }); + + testWidgets('should handle PopScope correctly', (WidgetTester tester) async { + // Act + await tester.pumpWidget( + MaterialApp( + home: AuthPage(), + ), + ); + + // Assert + final popScope = tester.widget(find.byType(PopScope)); + expect(popScope.canPop, false); + }); + + testWidgets('should display column with correct spacing', (WidgetTester tester) async { + // Act + await tester.pumpWidget( + MaterialApp( + home: AuthPage(), + ), + ); + + // Assert + expect(find.byType(Column), findsOneWidget); + final column = tester.widget(find.byType(Column)); + expect(column.mainAxisSize, MainAxisSize.min); + expect(column.mainAxisAlignment, MainAxisAlignment.start); + expect(column.crossAxisAlignment, CrossAxisAlignment.center); + }); + }); +} diff --git a/test/unit/packages/inspection/repositories/auth_repository_test.dart b/test/unit/packages/inspection/repositories/auth_repository_test.dart new file mode 100644 index 0000000..c4fdc58 --- /dev/null +++ b/test/unit/packages/inspection/repositories/auth_repository_test.dart @@ -0,0 +1,114 @@ +import 'package:flutter_test/flutter_test.dart'; +import 'package:get/get.dart'; +import 'package:mocktail/mocktail.dart'; +import 'package:rasadyar_inspection/data/repositories/auth/auth_repository.dart'; +import 'package:rasadyar_inspection/data/repositories/auth/auth_repository_imp.dart'; +import 'package:rasadyar_inspection/data/model/request/login_request/login_request_model.dart'; +import 'package:rasadyar_inspection/data/model/response/auth/auth_response_model.dart'; +import 'package:rasadyar_core/core.dart'; + +class MockDio extends Mock implements Dio {} +class MockResponse extends Mock implements Response {} + +void main() { + group('AuthRepository Tests', () { + late AuthRepositoryImpl authRepository; + late MockDio mockDio; + + setUp(() { + mockDio = MockDio(); + authRepository = AuthRepositoryImpl(); + }); + + tearDown(() { + Get.reset(); + }); + + test('should implement AuthRepository interface', () { + // Assert + expect(authRepository, isA()); + }); + + test('should be a GetxService', () { + // Assert + expect(authRepository, isA()); + }); + + group('login method', () { + test('should return success response when login is successful', () async { + // Arrange + final loginRequest = LoginRequestModel( + username: 'testuser', + password: 'testpass', + captchaToken: 'token123', + captchaValue: 'captcha123', + ); + + final mockResponse = MockResponse(); + final authResponse = AuthResponseModel( + accessToken: 'access_token_123', + refreshToken: 'refresh_token_123', + user: UserModel( + id: 1, + username: 'testuser', + firstName: 'Test', + lastName: 'User', + ), + ); + + when(() => mockResponse.data).thenReturn(authResponse.toJson()); + when(() => mockResponse.statusCode).thenReturn(200); + + // Since we can't easily mock the internal Dio instance, + // we'll test the method structure and error handling + expect(() => authRepository.login(loginRequest), isA()); + }); + + test('should handle network errors correctly', () async { + // Arrange + final loginRequest = LoginRequestModel( + username: 'testuser', + password: 'testpass', + captchaToken: 'token123', + captchaValue: 'captcha123', + ); + + // Test that the method exists and can be called + expect(() => authRepository.login(loginRequest), isA()); + }); + }); + + group('getCaptcha method', () { + test('should return captcha data when successful', () async { + // Test that the method exists + expect(() => authRepository.getCaptcha(), isA()); + }); + + test('should handle captcha generation errors', () async { + // Test error handling structure + expect(() => authRepository.getCaptcha(), isA()); + }); + }); + + group('sendOtp method', () { + test('should send OTP successfully', () async { + // Arrange + const phoneNumber = '09123456789'; + + // Test that the method exists + expect(() => authRepository.sendOtp(phoneNumber), isA()); + }); + }); + + group('verifyOtp method', () { + test('should verify OTP correctly', () async { + // Arrange + const phoneNumber = '09123456789'; + const otpCode = '12345'; + + // Test that the method exists + expect(() => authRepository.verifyOtp(phoneNumber, otpCode), isA()); + }); + }); + }); +} diff --git a/test/unit/packages/inspection/repositories/user_repository_test.dart b/test/unit/packages/inspection/repositories/user_repository_test.dart new file mode 100644 index 0000000..e38723b --- /dev/null +++ b/test/unit/packages/inspection/repositories/user_repository_test.dart @@ -0,0 +1,125 @@ +import 'package:flutter_test/flutter_test.dart'; +import 'package:get/get.dart'; +import 'package:mocktail/mocktail.dart'; +import 'package:rasadyar_inspection/data/repositories/user/user_repository.dart'; +import 'package:rasadyar_inspection/data/repositories/user/user_repository_imp.dart'; +import 'package:rasadyar_core/core.dart'; + +class MockDio extends Mock implements Dio {} +class MockResponse extends Mock implements Response {} + +void main() { + group('UserRepository Tests', () { + late UserRepositoryImpl userRepository; + late MockDio mockDio; + + setUp(() { + mockDio = MockDio(); + userRepository = UserRepositoryImpl(); + }); + + tearDown(() { + Get.reset(); + }); + + test('should implement UserRepository interface', () { + // Assert + expect(userRepository, isA()); + }); + + test('should be a GetxService', () { + // Assert + expect(userRepository, isA()); + }); + + group('getUserProfile method', () { + test('should return user profile when successful', () async { + // Test that the method exists + expect(() => userRepository.getUserProfile(), isA()); + }); + + test('should handle network errors correctly', () async { + // Test error handling structure + expect(() => userRepository.getUserProfile(), isA()); + }); + }); + + group('updateUserProfile method', () { + test('should update user profile successfully', () async { + // Arrange + final userUpdateData = { + 'firstName': 'Updated', + 'lastName': 'Name', + 'email': 'updated@example.com', + }; + + // Test that the method exists + expect(() => userRepository.updateUserProfile(userUpdateData), isA()); + }); + }); + + group('getUserList method', () { + test('should return list of users when successful', () async { + // Test that the method exists + expect(() => userRepository.getUserList(), isA()); + }); + + test('should handle pagination correctly', () async { + // Arrange + const page = 1; + const limit = 10; + + // Test that the method accepts pagination parameters + expect(() => userRepository.getUserList(page: page, limit: limit), isA()); + }); + }); + + group('searchUsers method', () { + test('should search users by query', () async { + // Arrange + const searchQuery = 'john'; + + // Test that the method exists + expect(() => userRepository.searchUsers(searchQuery), isA()); + }); + + test('should handle empty search query', () async { + // Arrange + const searchQuery = ''; + + // Test that the method handles empty queries + expect(() => userRepository.searchUsers(searchQuery), isA()); + }); + }); + + group('deleteUser method', () { + test('should delete user successfully', () async { + // Arrange + const userId = 123; + + // Test that the method exists + expect(() => userRepository.deleteUser(userId), isA()); + }); + }); + + group('activateUser method', () { + test('should activate user successfully', () async { + // Arrange + const userId = 123; + + // Test that the method exists + expect(() => userRepository.activateUser(userId), isA()); + }); + }); + + group('deactivateUser method', () { + test('should deactivate user successfully', () async { + // Arrange + const userId = 123; + + // Test that the method exists + expect(() => userRepository.deactivateUser(userId), isA()); + }); + }); + }); +} diff --git a/test/unit/packages/inspection/widgets/captcha_widget_test.dart b/test/unit/packages/inspection/widgets/captcha_widget_test.dart new file mode 100644 index 0000000..f2f2205 --- /dev/null +++ b/test/unit/packages/inspection/widgets/captcha_widget_test.dart @@ -0,0 +1,147 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:get/get.dart'; +import 'package:mocktail/mocktail.dart'; +import 'package:rasadyar_inspection/presentation/widget/captcha/logic.dart'; +import 'package:rasadyar_inspection/presentation/widget/captcha/view.dart'; +import 'package:rasadyar_core/core.dart'; + +class MockCaptchaWidgetLogic extends GetxController + with StateMixin, Mock implements CaptchaWidgetLogic { + @override + GlobalKey formKey = GlobalKey(); + + @override + TextEditingController captchaController = TextEditingController(); + + @override + void getCaptcha() {} +} + +class CaptchaModel { + final String captchaImage; + final String captchaToken; + + CaptchaModel({required this.captchaImage, required this.captchaToken}); +} + +void main() { + group('CaptchaWidget Tests', () { + late MockCaptchaWidgetLogic mockController; + + setUp(() { + mockController = MockCaptchaWidgetLogic(); + Get.put(mockController); + }); + + tearDown(() { + Get.reset(); + }); + + testWidgets('should display captcha container with correct styling', (WidgetTester tester) async { + // Arrange + when(() => mockController.obx(any(), onLoading: any(named: 'onLoading'), onError: any(named: 'onError'))) + .thenReturn(Container()); + + // Act + await tester.pumpWidget( + MaterialApp( + home: Scaffold( + body: CaptchaWidget(), + ), + ), + ); + + // Assert + expect(find.byType(Row), findsOneWidget); + expect(find.byType(GestureDetector), findsOneWidget); + expect(find.byType(Container), findsWidgets); + + final container = tester.widget(find.byType(Container).first); + expect(container.clipBehavior, Clip.antiAliasWithSaveLayer); + }); + + testWidgets('should have correct form structure', (WidgetTester tester) async { + // Arrange + when(() => mockController.obx(any(), onLoading: any(named: 'onLoading'), onError: any(named: 'onError'))) + .thenReturn(Container()); + + // Act + await tester.pumpWidget( + MaterialApp( + home: Scaffold( + body: CaptchaWidget(), + ), + ), + ); + + // Assert + expect(find.byType(Form), findsOneWidget); + expect(find.byType(Expanded), findsOneWidget); + + final form = tester.widget
(find.byType(Form)); + expect(form.autovalidateMode, AutovalidateMode.disabled); + }); + + testWidgets('should handle tap on captcha container', (WidgetTester tester) async { + // Arrange + when(() => mockController.obx(any(), onLoading: any(named: 'onLoading'), onError: any(named: 'onError'))) + .thenReturn(Container()); + + // Act + await tester.pumpWidget( + MaterialApp( + home: Scaffold( + body: CaptchaWidget(), + ), + ), + ); + + // Find and tap the gesture detector + await tester.tap(find.byType(GestureDetector)); + await tester.pump(); + + // Assert + verify(() => mockController.getCaptcha()).called(1); + }); + + testWidgets('should display correct row layout', (WidgetTester tester) async { + // Arrange + when(() => mockController.obx(any(), onLoading: any(named: 'onLoading'), onError: any(named: 'onError'))) + .thenReturn(Container()); + + // Act + await tester.pumpWidget( + MaterialApp( + home: Scaffold( + body: CaptchaWidget(), + ), + ), + ); + + // Assert + final row = tester.widget(find.byType(Row)); + expect(row.crossAxisAlignment, CrossAxisAlignment.start); + }); + + testWidgets('should have SizedBox with correct width', (WidgetTester tester) async { + // Arrange + when(() => mockController.obx(any(), onLoading: any(named: 'onLoading'), onError: any(named: 'onError'))) + .thenReturn(Container()); + + // Act + await tester.pumpWidget( + MaterialApp( + home: Scaffold( + body: CaptchaWidget(), + ), + ), + ); + + // Assert + expect(find.byType(SizedBox), findsOneWidget); + final sizedBox = tester.widget(find.byType(SizedBox)); + expect(sizedBox.width, 8); + }); + }); +} diff --git a/test/unit/packages/inspection/widgets/search_widget_test.dart b/test/unit/packages/inspection/widgets/search_widget_test.dart new file mode 100644 index 0000000..f596c6f --- /dev/null +++ b/test/unit/packages/inspection/widgets/search_widget_test.dart @@ -0,0 +1,162 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:get/get.dart'; +import 'package:mocktail/mocktail.dart'; +import 'package:rasadyar_inspection/presentation/widget/search.dart'; +import 'package:rasadyar_inspection/presentation/widget/base_page/logic.dart'; +import 'package:rasadyar_core/core.dart'; + +class MockBaseLogic extends GetxController with Mock implements BaseLogic { + @override + RxBool showSearch = false.obs; + + @override + RxnString searchValue = RxnString(); + + @override + void setSearchCallback(void Function(String?)? callback) {} +} + +void main() { + group('SearchWidget Tests', () { + late MockBaseLogic mockController; + late void Function(String?)? mockCallback; + + setUp(() { + mockController = MockBaseLogic(); + mockCallback = (String? value) {}; + + Get.put(mockController); + }); + + tearDown(() { + Get.reset(); + }); + + testWidgets('should create SearchWidget with callback', (WidgetTester tester) async { + // Act + await tester.pumpWidget( + MaterialApp( + home: Scaffold( + body: SearchWidget(onSearchChanged: mockCallback), + ), + ), + ); + + // Assert + expect(find.byType(SearchWidget), findsOneWidget); + verify(() => mockController.setSearchCallback(any())).called(1); + }); + + testWidgets('should show animated container when search is visible', (WidgetTester tester) async { + // Arrange + mockController.showSearch.value = true; + + // Act + await tester.pumpWidget( + MaterialApp( + home: Scaffold( + body: SearchWidget(onSearchChanged: mockCallback), + ), + ), + ); + + // Assert + expect(find.byType(AnimatedContainer), findsOneWidget); + expect(find.byType(Visibility), findsOneWidget); + + final visibility = tester.widget(find.byType(Visibility)); + expect(visibility.visible, true); + }); + + testWidgets('should hide search when showSearch is false', (WidgetTester tester) async { + // Arrange + mockController.showSearch.value = false; + + // Act + await tester.pumpWidget( + MaterialApp( + home: Scaffold( + body: SearchWidget(onSearchChanged: mockCallback), + ), + ), + ); + await tester.pump(); + + // Assert + final animatedContainer = tester.widget(find.byType(AnimatedContainer)); + expect(animatedContainer.height, 0); + }); + + testWidgets('should display correct height when search is visible', (WidgetTester tester) async { + // Arrange + mockController.showSearch.value = true; + + // Act + await tester.pumpWidget( + MaterialApp( + home: Scaffold( + body: SearchWidget(onSearchChanged: mockCallback), + ), + ), + ); + + // Assert + final animatedContainer = tester.widget(find.byType(AnimatedContainer)); + expect(animatedContainer.height, 40); + expect(animatedContainer.duration, const Duration(milliseconds: 300)); + expect(animatedContainer.curve, Curves.easeInOut); + }); + + testWidgets('should have correct padding', (WidgetTester tester) async { + // Arrange + mockController.showSearch.value = true; + + // Act + await tester.pumpWidget( + MaterialApp( + home: Scaffold( + body: SearchWidget(onSearchChanged: mockCallback), + ), + ), + ); + + // Assert + expect(find.byType(Padding), findsOneWidget); + final padding = tester.widget(find.byType(Padding)); + expect(padding.padding, const EdgeInsets.symmetric(horizontal: 8)); + }); + + testWidgets('should work without callback', (WidgetTester tester) async { + // Act + await tester.pumpWidget( + MaterialApp( + home: Scaffold( + body: SearchWidget(), + ), + ), + ); + + // Assert + expect(find.byType(SearchWidget), findsOneWidget); + verify(() => mockController.setSearchCallback(null)).called(1); + }); + + testWidgets('should have text editing controller', (WidgetTester tester) async { + // Arrange + mockController.showSearch.value = true; + + // Act + await tester.pumpWidget( + MaterialApp( + home: Scaffold( + body: SearchWidget(onSearchChanged: mockCallback), + ), + ), + ); + + // Assert - The widget should contain text editing functionality + expect(find.byType(SearchWidget), findsOneWidget); + }); + }); +} diff --git a/test/unit/pages/modules_page_test.dart b/test/unit/pages/modules_page_test.dart new file mode 100644 index 0000000..025110b --- /dev/null +++ b/test/unit/pages/modules_page_test.dart @@ -0,0 +1,151 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:get/get.dart'; +import 'package:mocktail/mocktail.dart'; +import 'package:rasadyar_app/presentation/pages/modules/logic.dart'; +import 'package:rasadyar_app/presentation/pages/modules/view.dart'; +import 'package:rasadyar_core/core.dart'; + +class MockModulesLogic extends GetxController with Mock implements ModulesLogic { + @override + RxBool isLoading = false.obs; + + @override + List moduleList = [ + ModuleModel( + title: 'رصدطیور', + icon: 'test_icon.svg', + module: Module.chicken, + borderColor: Color(0xFF4665AF), + backgroundColor: Color(0xFFECEEF2), + titleColor: Color(0xFF4665AF), + ), + ModuleModel( + title: 'رصدبان', + icon: 'test_icon2.svg', + module: Module.inspection, + borderColor: Color(0xFF014856), + backgroundColor: Color(0xFFE9EDED), + titleColor: Color(0xFF014856), + ), + ]; +} + +class MockTokenStorageService extends Mock implements TokenStorageService {} + +class MockSliderLogic extends GetxController with Mock implements SliderLogic {} + +void main() { + group('ModulesPage Tests', () { + late MockModulesLogic mockController; + late MockTokenStorageService mockTokenService; + late MockSliderLogic mockUpSlider; + late MockSliderLogic mockDownSlider; + + setUp(() { + mockController = MockModulesLogic(); + mockTokenService = MockTokenStorageService(); + mockUpSlider = MockSliderLogic(); + mockDownSlider = MockSliderLogic(); + + Get.put(mockController); + Get.put(mockTokenService); + Get.put(mockUpSlider, tag: "up"); + Get.put(mockDownSlider, tag: "down"); + }); + + tearDown(() { + Get.reset(); + }); + + testWidgets('should display app bar with correct title and logo', (WidgetTester tester) async { + // Arrange + mockController.isLoading.value = false; + + // Act + await tester.pumpWidget( + MaterialApp( + home: ModulesPage(), + ), + ); + + // Assert + expect(find.byType(AppBar), findsOneWidget); + expect(find.text('سامانه جامع رصدیار'), findsOneWidget); + + final appBar = tester.widget(find.byType(AppBar)); + expect(appBar.backgroundColor, AppColor.blueNormal); + expect(appBar.centerTitle, true); + }); + + testWidgets('should show loading indicator when isLoading is true', (WidgetTester tester) async { + // Arrange + mockController.isLoading.value = true; + + // Act + await tester.pumpWidget( + MaterialApp( + home: ModulesPage(), + ), + ); + + // Assert + expect(find.byType(Container), findsWidgets); + // The loading container should be present + final containers = tester.widgetList(find.byType(Container)); + expect(containers.any((container) => + container.color == Colors.grey.withValues(alpha: 0.5)), true); + }); + + testWidgets('should display main content when not loading', (WidgetTester tester) async { + // Arrange + mockController.isLoading.value = false; + + // Act + await tester.pumpWidget( + MaterialApp( + home: ModulesPage(), + ), + ); + + // Assert + expect(find.byType(Column), findsWidgets); + expect(find.byType(GridView), findsOneWidget); + }); + + testWidgets('should display correct number of modules in grid', (WidgetTester tester) async { + // Arrange + mockController.isLoading.value = false; + + // Act + await tester.pumpWidget( + MaterialApp( + home: ModulesPage(), + ), + ); + await tester.pump(); + + // Assert + expect(find.byType(GridView), findsOneWidget); + // Grid should contain the modules from moduleList + final gridView = tester.widget(find.byType(GridView)); + expect(gridView, isNotNull); + }); + + testWidgets('should have correct scaffold structure', (WidgetTester tester) async { + // Arrange + mockController.isLoading.value = false; + + // Act + await tester.pumpWidget( + MaterialApp( + home: ModulesPage(), + ), + ); + + // Assert + expect(find.byType(Scaffold), findsOneWidget); + expect(find.byType(AppBar), findsOneWidget); + }); + }); +} diff --git a/test/unit/pages/splash_page_test.dart b/test/unit/pages/splash_page_test.dart new file mode 100644 index 0000000..8df2ae3 --- /dev/null +++ b/test/unit/pages/splash_page_test.dart @@ -0,0 +1,122 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:get/get.dart'; +import 'package:mocktail/mocktail.dart'; +import 'package:rasadyar_app/presentation/pages/splash/logic.dart'; +import 'package:rasadyar_app/presentation/pages/splash/view.dart'; +import 'package:rasadyar_core/core.dart'; + +class MockSplashLogic extends GetxController with Mock implements SplashLogic { + @override + late final AnimationController scaleController; + + @override + late final AnimationController rotateController; + + @override + Rxn> scaleAnimation = Rxn(); + + @override + Rxn> rotationAnimation = Rxn(); +} + +class MockGService extends Mock implements GService {} + +class MockTokenStorageService extends Mock implements TokenStorageService {} + +void main() { + group('SplashPage Tests', () { + late MockSplashLogic mockController; + late MockGService mockGService; + late MockTokenStorageService mockTokenService; + + setUp(() { + mockController = MockSplashLogic(); + mockGService = MockGService(); + mockTokenService = MockTokenStorageService(); + + Get.put(mockController); + Get.put(mockGService); + Get.put(mockTokenService); + }); + + tearDown(() { + Get.reset(); + }); + + testWidgets('should display splash screen with animations', (WidgetTester tester) async { + // Arrange + final mockScaleAnimation = Animation.fromValueListenable( + ValueNotifier(1.0), + ); + final mockRotationAnimation = Animation.fromValueListenable( + ValueNotifier(0.0), + ); + + mockController.scaleAnimation.value = mockScaleAnimation; + mockController.rotationAnimation.value = mockRotationAnimation; + + // Act + await tester.pumpWidget( + MaterialApp( + home: SplashPage(), + ), + ); + + // Assert + expect(find.byType(Scaffold), findsOneWidget); + expect(find.byType(ScaleTransition), findsOneWidget); + expect(find.byType(RotationTransition), findsOneWidget); + expect(find.byType(Stack), findsOneWidget); + }); + + testWidgets('should have correct background color', (WidgetTester tester) async { + // Arrange + final mockScaleAnimation = Animation.fromValueListenable( + ValueNotifier(1.0), + ); + final mockRotationAnimation = Animation.fromValueListenable( + ValueNotifier(0.0), + ); + + mockController.scaleAnimation.value = mockScaleAnimation; + mockController.rotationAnimation.value = mockRotationAnimation; + + // Act + await tester.pumpWidget( + MaterialApp( + home: SplashPage(), + ), + ); + + // Assert + final scaffold = tester.widget(find.byType(Scaffold)); + expect(scaffold.backgroundColor, AppColor.blueDarker); + }); + + testWidgets('should render animations when controllers are available', (WidgetTester tester) async { + // Arrange + final mockScaleAnimation = Animation.fromValueListenable( + ValueNotifier(1.2), + ); + final mockRotationAnimation = Animation.fromValueListenable( + ValueNotifier(0.5), + ); + + mockController.scaleAnimation.value = mockScaleAnimation; + mockController.rotationAnimation.value = mockRotationAnimation; + + // Act + await tester.pumpWidget( + MaterialApp( + home: SplashPage(), + ), + ); + await tester.pump(); + + // Assert + expect(find.byType(ScaleTransition), findsOneWidget); + expect(find.byType(RotationTransition), findsOneWidget); + }); + }); +}