diff --git a/test/features/auth/presentation/bloc/auth_cubit_test.dart b/test/features/auth/presentation/bloc/auth_cubit_test.dart new file mode 100644 index 00000000..5cfe36df --- /dev/null +++ b/test/features/auth/presentation/bloc/auth_cubit_test.dart @@ -0,0 +1,138 @@ +import 'package:eatvio_app/features/auth/models/token_model.dart'; +import 'package:eatvio_app/features/auth/presentation/bloc/auth_cubit/auth_cubit.dart'; +import 'package:eatvio_app/features/auth/presentation/bloc/auth_cubit/auth_state.dart'; +import 'package:eatvio_app/features/auth/repositories/auth_repository.dart'; +import 'package:flutter/services.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:mockito/annotations.dart'; +import 'package:mockito/mockito.dart'; +import 'package:shared_preferences/shared_preferences.dart'; + +import 'auth_cubit_test.mocks.dart'; + +@GenerateNiceMocks([MockSpec()]) +void main() { + TestWidgetsFlutterBinding.ensureInitialized(); + + group('AuthCubit', () { + AuthCubit? authCubit; + late MockAuthRepository mockAuthRepository; + + const testToken = TokenModel(token: 'test_token_12345'); + + setUp(() async { + mockAuthRepository = MockAuthRepository(); + SharedPreferences.setMockInitialValues({}); + + // Mock Fluttertoast plugin + TestDefaultBinaryMessengerBinding.instance.defaultBinaryMessenger + .setMockMethodCallHandler( + const MethodChannel('PonnamKarthik/fluttertoast'), + (MethodCall methodCall) async { + return null; + }, + ); + }); + + tearDown(() async { + if (authCubit != null && !authCubit!.isClosed) { + await Future.delayed(const Duration(milliseconds: 100)); + authCubit!.close(); + authCubit = null; + } + + // Clear mock handlers + TestDefaultBinaryMessengerBinding.instance.defaultBinaryMessenger + .setMockMethodCallHandler( + const MethodChannel('PonnamKarthik/fluttertoast'), + null, + ); + }); + + test('начальное состояние - AuthState', () { + authCubit = AuthCubit(authRepository: mockAuthRepository); + expect(authCubit!.state, const AuthState()); + }); + + test('getTokenByCredentials - успешная авторизация', () async { + authCubit = AuthCubit(authRepository: mockAuthRepository); + when(mockAuthRepository.getTokenByCredentials(any, any)) + .thenAnswer((_) async => testToken); + + final result = await authCubit!.getTokenByCredentials('test@example.com', 'password123'); + + expect(result, true); + await Future.delayed(const Duration(milliseconds: 100)); + expect(authCubit!.state, isA()); + final loadedState = authCubit!.state as AuthStateLoaded; + expect(loadedState.token, 'test_token_12345'); + verify(mockAuthRepository.getTokenByCredentials('test@example.com', 'password123')).called(1); + }); + + test('getTokenByCredentials - переход в состояние loading', () async { + authCubit = AuthCubit(authRepository: mockAuthRepository); + when(mockAuthRepository.getTokenByCredentials(any, any)) + .thenAnswer((_) async => Future.delayed(const Duration(milliseconds: 50)).then((_) => testToken)); + + final future = authCubit!.getTokenByCredentials('test@example.com', 'password123'); + await Future.delayed(const Duration(milliseconds: 10)); + + expect(authCubit!.state, isA()); + await future; + }); + + test('getTokenByCredentials - обработка ошибки авторизации', () async { + authCubit = AuthCubit(authRepository: mockAuthRepository); + when(mockAuthRepository.getTokenByCredentials(any, any)) + .thenThrow(Exception('Неверный адрес электронной почты или пароль')); + + final result = await authCubit!.getTokenByCredentials('wrong@example.com', 'wrongpassword'); + + expect(result, false); + await Future.delayed(const Duration(milliseconds: 100)); + expect(authCubit!.state, isA()); + final errorState = authCubit!.state as AuthStateError; + expect(errorState.errorMessage, contains('Exception')); + verify(mockAuthRepository.getTokenByCredentials('wrong@example.com', 'wrongpassword')).called(1); + }); + + test('getTokenByCredentials - обработка различных типов ошибок', () async { + authCubit = AuthCubit(authRepository: mockAuthRepository); + when(mockAuthRepository.getTokenByCredentials(any, any)) + .thenThrow(FormatException('Invalid credentials format')); + + final result = await authCubit!.getTokenByCredentials('invalid', 'pass'); + + expect(result, false); + await Future.delayed(const Duration(milliseconds: 100)); + expect(authCubit!.state, isA()); + final errorState = authCubit!.state as AuthStateError; + expect(errorState.errorMessage, contains('FormatException')); + }); + + test('getTokenByCredentials - проверка вызова с правильными параметрами', () async { + authCubit = AuthCubit(authRepository: mockAuthRepository); + when(mockAuthRepository.getTokenByCredentials(any, any)) + .thenAnswer((_) async => testToken); + + await authCubit!.getTokenByCredentials('user@test.com', 'securePassword123'); + + verify(mockAuthRepository.getTokenByCredentials('user@test.com', 'securePassword123')).called(1); + verifyNever(mockAuthRepository.getTokenByCredentials('other@test.com', 'otherPassword')); + }); + + test('getTokenByCredentials - множественные вызовы', () async { + authCubit = AuthCubit(authRepository: mockAuthRepository); + when(mockAuthRepository.getTokenByCredentials(any, any)) + .thenAnswer((_) async => testToken); + + await authCubit!.getTokenByCredentials('first@test.com', 'password1'); + await Future.delayed(const Duration(milliseconds: 100)); + await authCubit!.getTokenByCredentials('second@test.com', 'password2'); + + verify(mockAuthRepository.getTokenByCredentials('first@test.com', 'password1')).called(1); + verify(mockAuthRepository.getTokenByCredentials('second@test.com', 'password2')).called(1); + }); + }); +} + diff --git a/test/features/auth/presentation/widgets/basic_widget_test.dart b/test/features/auth/presentation/widgets/basic_widget_test.dart new file mode 100644 index 00000000..fb6c5159 --- /dev/null +++ b/test/features/auth/presentation/widgets/basic_widget_test.dart @@ -0,0 +1,413 @@ +// ignore_for_file: deprecated_member_use + +import 'package:eatvio_app/app_keys.dart'; +import 'package:eatvio_app/core/theme/theme_helper.dart'; +import 'package:eatvio_app/features/auth/presentation/bloc/auth_cubit/auth_cubit.dart'; +import 'package:eatvio_app/features/auth/repositories/auth_repository.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:mockito/annotations.dart'; + +import 'basic_widget_test.mocks.dart'; + +@GenerateMocks([AuthRepository]) +void main() { + group('Auth Basic Widget Tests', () { + late MockAuthRepository mockAuthRepository; + late AuthCubit authCubit; + + setUp(() { + mockAuthRepository = MockAuthRepository(); + authCubit = AuthCubit(authRepository: mockAuthRepository); + }); + + tearDown(() { + authCubit.close(); + }); + + Widget createTestWidget(Widget child) { + return MaterialApp( + theme: ThemeHelper().themeData(), + home: BlocProvider( + create: (context) => authCubit, + child: Scaffold(body: SingleChildScrollView(child: child)), + ), + ); + } + + testWidgets('should create AuthCubit successfully', (WidgetTester tester) async { + // Arrange & Act + final cubit = AuthCubit(authRepository: mockAuthRepository); + + // Assert + expect(cubit, isA()); + + cubit.close(); + }); + + testWidgets('should display email text field', (WidgetTester tester) async { + // Arrange + Widget emailWidget = TextFormField( + key: AppKeys.emailTextFieldAuthScreen, + decoration: const InputDecoration(labelText: 'Email', hintText: 'Введите email'), + keyboardType: TextInputType.emailAddress, + ); + + // Act + await tester.pumpWidget(createTestWidget(emailWidget)); + await tester.pumpAndSettle(); + + // Assert + expect(find.byKey(AppKeys.emailTextFieldAuthScreen), findsOneWidget); + }); + + testWidgets('should display password text field', (WidgetTester tester) async { + // Arrange + Widget passwordWidget = TextFormField( + key: AppKeys.passwordTextFieldAuthScreen, + decoration: const InputDecoration(labelText: 'Пароль', hintText: 'Введите пароль'), + obscureText: true, + ); + + // Act + await tester.pumpWidget(createTestWidget(passwordWidget)); + await tester.pumpAndSettle(); + + // Assert + expect(find.byKey(AppKeys.passwordTextFieldAuthScreen), findsOneWidget); + }); + + testWidgets('should display sign in button', (WidgetTester tester) async { + // Arrange + Widget buttonWidget = FloatingActionButton.extended( + key: AppKeys.signInButtonAuthScreen, + onPressed: () {}, + label: const Text('Войти'), + ); + + // Act + await tester.pumpWidget(createTestWidget(buttonWidget)); + await tester.pumpAndSettle(); + + // Assert + expect(find.byKey(AppKeys.signInButtonAuthScreen), findsOneWidget); + expect(find.text('Войти'), findsOneWidget); + }); + + testWidgets('should update email when user types in email field', (WidgetTester tester) async { + // Arrange + Widget emailWidget = TextFormField( + key: AppKeys.emailTextFieldAuthScreen, + decoration: const InputDecoration(labelText: 'Email', hintText: 'Введите email'), + keyboardType: TextInputType.emailAddress, + ); + + await tester.pumpWidget(createTestWidget(emailWidget)); + await tester.pumpAndSettle(); + + const testEmail = 'test@example.com'; + + // Act + await tester.enterText(find.byKey(AppKeys.emailTextFieldAuthScreen), testEmail); + await tester.pump(); + + // Assert + expect(find.text(testEmail), findsOneWidget); + }); + + testWidgets('should update password when user types in password field', (WidgetTester tester) async { + // Arrange + Widget passwordWidget = TextFormField( + key: AppKeys.passwordTextFieldAuthScreen, + decoration: const InputDecoration(labelText: 'Пароль', hintText: 'Введите пароль'), + obscureText: true, + ); + + await tester.pumpWidget(createTestWidget(passwordWidget)); + await tester.pumpAndSettle(); + + const testPassword = 'password123'; + + // Act + await tester.enterText(find.byKey(AppKeys.passwordTextFieldAuthScreen), testPassword); + await tester.pump(); + + // Assert + expect(find.text(testPassword), findsOneWidget); + }); + + testWidgets('should display complete auth form', (WidgetTester tester) async { + // Arrange + Widget completeForm = Column( + children: [ + TextFormField( + key: AppKeys.emailTextFieldAuthScreen, + decoration: const InputDecoration(labelText: 'Email', hintText: 'Введите email'), + keyboardType: TextInputType.emailAddress, + ), + const SizedBox(height: 16), + TextFormField( + key: AppKeys.passwordTextFieldAuthScreen, + decoration: const InputDecoration(labelText: 'Пароль', hintText: 'Введите пароль'), + obscureText: true, + ), + const SizedBox(height: 16), + FloatingActionButton.extended( + key: AppKeys.signInButtonAuthScreen, + onPressed: () {}, + label: const Text('Войти'), + ), + ], + ); + + // Act + await tester.pumpWidget(createTestWidget(completeForm)); + await tester.pumpAndSettle(); + + // Assert + expect(find.byKey(AppKeys.emailTextFieldAuthScreen), findsOneWidget); + expect(find.byKey(AppKeys.passwordTextFieldAuthScreen), findsOneWidget); + expect(find.byKey(AppKeys.signInButtonAuthScreen), findsOneWidget); + }); + + testWidgets('should display loading state', (WidgetTester tester) async { + // Arrange + Widget loadingWidget = const Center(child: CircularProgressIndicator()); + + // Act + await tester.pumpWidget(createTestWidget(loadingWidget)); + await tester.pump(); + + // Assert + expect(find.byType(CircularProgressIndicator), findsOneWidget); + }); + + testWidgets('should display error message', (WidgetTester tester) async { + // Arrange + Widget errorWidget = const Center(child: Text('Неверный адрес электронной почты или пароль')); + + // Act + await tester.pumpWidget(createTestWidget(errorWidget)); + await tester.pumpAndSettle(); + + // Assert + expect(find.text('Неверный адрес электронной почты или пароль'), findsOneWidget); + }); + + testWidgets('should handle sign in button tap', (WidgetTester tester) async { + // Arrange + bool buttonPressed = false; + Widget buttonWidget = FloatingActionButton.extended( + key: AppKeys.signInButtonAuthScreen, + onPressed: () { + buttonPressed = true; + }, + label: const Text('Войти'), + ); + + await tester.pumpWidget(createTestWidget(buttonWidget)); + await tester.pumpAndSettle(); + + // Act + await tester.tap(find.byKey(AppKeys.signInButtonAuthScreen)); + await tester.pump(); + + // Assert + expect(buttonPressed, isTrue); + }); + + testWidgets('should display password visibility toggle', (WidgetTester tester) async { + // Arrange + Widget passwordWidget = TextFormField( + key: AppKeys.passwordTextFieldAuthScreen, + decoration: const InputDecoration( + labelText: 'Пароль', + hintText: 'Введите пароль', + suffix: IconButton( + onPressed: null, + icon: Icon(Icons.visibility_outlined), + ), + ), + obscureText: true, + ); + + await tester.pumpWidget(createTestWidget(passwordWidget)); + await tester.pumpAndSettle(); + + // Assert + expect(find.byIcon(Icons.visibility_outlined), findsOneWidget); + }); + + testWidgets('should display "Забыли пароль?" link', (WidgetTester tester) async { + // Arrange + Widget forgotPasswordWidget = GestureDetector( + onTap: () {}, + child: const Text( + 'Забыли пароль?', + style: TextStyle(decoration: TextDecoration.underline), + ), + ); + + // Act + await tester.pumpWidget(createTestWidget(forgotPasswordWidget)); + await tester.pumpAndSettle(); + + // Assert + expect(find.text('Забыли пароль?'), findsOneWidget); + }); + + testWidgets('should display registration link', (WidgetTester tester) async { + // Arrange + Widget registrationLinkWidget = Row( + children: [ + const Text('Нет аккаунта? '), + GestureDetector( + onTap: () {}, + child: const Text( + 'Зарегистрироваться', + style: TextStyle(decoration: TextDecoration.underline), + ), + ), + ], + ); + + // Act + await tester.pumpWidget(createTestWidget(registrationLinkWidget)); + await tester.pumpAndSettle(); + + // Assert + expect(find.text('Нет аккаунта? '), findsOneWidget); + expect(find.text('Зарегистрироваться'), findsOneWidget); + }); + + testWidgets('should display social auth buttons', (WidgetTester tester) async { + // Arrange + Widget socialButtonsWidget = Column( + children: [ + const Text('Авторизация через'), + const SizedBox(height: 12), + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + InkWell( + onTap: () {}, + child: Container( + height: 48, + width: 80, + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(20), + border: Border.all(color: const Color(0xFFDBDBDB), width: 1), + ), + child: const Center(child: Text('VK')), + ), + ), + InkWell( + onTap: () {}, + child: Container( + height: 48, + width: 80, + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(20), + border: Border.all(color: const Color(0xFFDBDBDB), width: 1), + ), + child: const Center(child: Text('OK')), + ), + ), + ], + ), + ], + ); + + // Act + await tester.pumpWidget(createTestWidget(socialButtonsWidget)); + await tester.pumpAndSettle(); + + // Assert + expect(find.text('Авторизация через'), findsOneWidget); + expect(find.text('VK'), findsOneWidget); + expect(find.text('OK'), findsOneWidget); + }); + + testWidgets('should validate email format', (WidgetTester tester) async { + // Arrange + Widget emailWidget = TextFormField( + key: AppKeys.emailTextFieldAuthScreen, + decoration: const InputDecoration( + labelText: 'Email', + hintText: 'Введите email', + errorText: 'Введите корректный email', + ), + keyboardType: TextInputType.emailAddress, + ); + + // Act + await tester.pumpWidget(createTestWidget(emailWidget)); + await tester.pumpAndSettle(); + + // Assert + expect(find.text('Введите корректный email'), findsOneWidget); + }); + + testWidgets('should disable sign in button when loading', (WidgetTester tester) async { + // Arrange + Widget buttonWidget = FloatingActionButton.extended( + key: AppKeys.signInButtonAuthScreen, + onPressed: null, // Disabled + label: const Text('Войти'), + ); + + // Act + await tester.pumpWidget(createTestWidget(buttonWidget)); + await tester.pumpAndSettle(); + + // Assert + final button = tester.widget(find.byKey(AppKeys.signInButtonAuthScreen)); + expect(button, isA()); + }); + + testWidgets('should display form labels correctly', (WidgetTester tester) async { + // Arrange + Widget formWidget = const Column( + children: [ + Text('Email'), + Text('Пароль'), + ], + ); + + // Act + await tester.pumpWidget(createTestWidget(formWidget)); + await tester.pumpAndSettle(); + + // Assert + expect(find.text('Email'), findsOneWidget); + expect(find.text('Пароль'), findsOneWidget); + }); + + testWidgets('should display placeholder text in fields', (WidgetTester tester) async { + // Arrange + Widget formWidget = Column( + children: [ + TextFormField( + key: AppKeys.emailTextFieldAuthScreen, + decoration: const InputDecoration(labelText: 'Email', hintText: 'Введите email'), + ), + const SizedBox(height: 16), + TextFormField( + key: AppKeys.passwordTextFieldAuthScreen, + decoration: const InputDecoration(labelText: 'Пароль', hintText: 'Введите пароль'), + ), + ], + ); + + // Act + await tester.pumpWidget(createTestWidget(formWidget)); + await tester.pumpAndSettle(); + + // Assert + expect(find.text('Введите email'), findsOneWidget); + expect(find.text('Введите пароль'), findsOneWidget); + }); + }); +} +