diff --git a/test/core/comments/comments_cubit/comments_cubit_test.dart b/test/core/comments/comments_cubit/comments_cubit_test.dart index b9e9bf72..5dfba4b7 100644 --- a/test/core/comments/comments_cubit/comments_cubit_test.dart +++ b/test/core/comments/comments_cubit/comments_cubit_test.dart @@ -26,10 +26,10 @@ void main() { mockFeedRepository = MockFeedRepository(); mockCommentsRepository = MockCommentsRepository(); - testCommentModel = CommentModel( + testCommentModel = const CommentModel( data: [], - links: const Links(first: '', last: '', prev: null, next: null), - meta: const Meta( + links: Links(first: '', last: '', prev: null, next: null), + meta: Meta( currentPage: 1, from: null, lastPage: 1, diff --git a/test/features/feed/presentation/bloc/consultants_cubit/consultants_cubit_test.dart b/test/features/feed/presentation/bloc/consultants_cubit/consultants_cubit_test.dart index f1f7fc0b..a2abb430 100644 --- a/test/features/feed/presentation/bloc/consultants_cubit/consultants_cubit_test.dart +++ b/test/features/feed/presentation/bloc/consultants_cubit/consultants_cubit_test.dart @@ -31,7 +31,7 @@ void main() { test('getConsultants - успешная загрузка консультантов', () async { final testConsultants = [ - FeedConsultantModel( + const FeedConsultantModel( slug: 'consultant-1', name: 'Консультант 1', description: 'Описание консультанта 1', @@ -40,7 +40,7 @@ void main() { badges: [], ratingPlace: 1, ), - FeedConsultantModel( + const FeedConsultantModel( slug: 'consultant-2', name: 'Консультант 2', description: 'Описание консультанта 2', diff --git a/test/features/feed/repositories/feed_repository_test.dart b/test/features/feed/repositories/feed_repository_test.dart index 51932930..36ad39c0 100644 --- a/test/features/feed/repositories/feed_repository_test.dart +++ b/test/features/feed/repositories/feed_repository_test.dart @@ -28,9 +28,9 @@ void main() { }); test('getFeedBlogPosts - успешное получение постов', () async { - final expectedModel = FeedBlogPostSettingsModel( - feedBlogPostList: const [], - meta: const Meta( + const expectedModel = FeedBlogPostSettingsModel( + feedBlogPostList: [], + meta: Meta( currentPage: 1, from: null, lastPage: 1, @@ -56,9 +56,9 @@ void main() { test('getFeedBlogPosts - с запросом и страницей', () async { const query = 'тест'; const page = 2; - final expectedModel = FeedBlogPostSettingsModel( - feedBlogPostList: const [], - meta: const Meta( + const expectedModel = FeedBlogPostSettingsModel( + feedBlogPostList: [], + meta: Meta( currentPage: page, from: null, lastPage: 2, @@ -117,7 +117,7 @@ void main() { }); test('getRecipes - успешное получение рецептов', () async { - final expectedRecipes = const RecipesPagination( + const expectedRecipes = RecipesPagination( recipesList: [], ); @@ -131,10 +131,10 @@ void main() { }); test('getDiets - успешное получение диет', () async { - final expectedDiets = DietsPagination( - data: const [], - links: const Links(first: '', last: '', prev: null, next: null), - meta: const Meta( + const expectedDiets = DietsPagination( + data: [], + links: Links(first: '', last: '', prev: null, next: null), + meta: Meta( currentPage: 1, from: null, lastPage: 1, @@ -169,10 +169,10 @@ void main() { }); test('getPlans - успешное получение планов', () async { - final expectedPlans = PlanPaginationModel( - data: const [], - links: const Links(first: '', last: '', prev: null, next: null), - meta: const Meta( + const expectedPlans = PlanPaginationModel( + data: [], + links: Links(first: '', last: '', prev: null, next: null), + meta: Meta( currentPage: 1, from: null, lastPage: 1, @@ -210,10 +210,10 @@ void main() { test('getBlogPostComments - успешное получение комментариев', () async { const postId = '1'; - final expectedComments = CommentModel( + const expectedComments = CommentModel( data: [], - links: const Links(first: '', last: '', prev: null, next: null), - meta: const Meta( + links: Links(first: '', last: '', prev: null, next: null), + meta: Meta( currentPage: 1, from: null, lastPage: 1, @@ -278,10 +278,10 @@ void main() { }); test('getConsultants - успешное получение консультантов', () async { - final expectedConsultants = FeedConsultantPaginationModel( - data: const [], - links: const Links(first: '', last: '', prev: null, next: null), - meta: const Meta( + const expectedConsultants = FeedConsultantPaginationModel( + data: [], + links: Links(first: '', last: '', prev: null, next: null), + meta: Meta( currentPage: 1, from: null, lastPage: 1, diff --git a/test/features/my_finance/models/card_model_test.dart b/test/features/my_finance/models/card_model_test.dart new file mode 100644 index 00000000..36e84d94 --- /dev/null +++ b/test/features/my_finance/models/card_model_test.dart @@ -0,0 +1,85 @@ +import 'package:eatvio_app/features/my_finance/models/card_model.dart'; +import 'package:flutter_test/flutter_test.dart'; + +void main() { + group('CardModel', () { + test('fromJson - успешная десериализация', () { + final json = { + 'id': 1, + 'bank_pan': '1234567890123456', + 'is_default': true, + }; + + final card = CardModel.fromJson(json); + + expect(card.id, equals(1)); + expect(card.cardNumber, equals('1234567890123456')); + expect(card.isDefault, isTrue); + }); + + test('fromJson - is_default как int', () { + final json = { + 'id': 1, + 'bank_pan': '1234567890123456', + 'is_default': 1, + }; + + final card = CardModel.fromJson(json); + + expect(card.isDefault, isTrue); + }); + + test('fromJson - is_default как строка "1"', () { + final json = { + 'id': 1, + 'bank_pan': '1234567890123456', + 'is_default': '1', + }; + + final card = CardModel.fromJson(json); + + expect(card.isDefault, isTrue); + }); + + test('fromJson - is_default как false', () { + final json = { + 'id': 1, + 'bank_pan': '1234567890123456', + 'is_default': false, + }; + + final card = CardModel.fromJson(json); + + expect(card.isDefault, isFalse); + }); + + test('toJson - успешная сериализация', () { + final card = CardModel( + id: 1, + cardNumber: '1234567890123456', + isDefault: true, + ); + + final json = card.toJson(); + + expect(json['id'], equals(1)); + expect(json['bank_pan'], equals('1234567890123456')); + expect(json['is_default'], isTrue); + }); + + test('copyWith - создание копии с изменениями', () { + final card = CardModel( + id: 1, + cardNumber: '1234567890123456', + isDefault: false, + ); + + final updatedCard = card.copyWith(isDefault: true); + + expect(updatedCard.id, equals(1)); + expect(updatedCard.cardNumber, equals('1234567890123456')); + expect(updatedCard.isDefault, isTrue); + }); + }); +} + diff --git a/test/features/my_finance/models/payment_url_model_test.dart b/test/features/my_finance/models/payment_url_model_test.dart new file mode 100644 index 00000000..e1f3a121 --- /dev/null +++ b/test/features/my_finance/models/payment_url_model_test.dart @@ -0,0 +1,39 @@ +import 'package:eatvio_app/features/my_finance/models/payment_url_model.dart'; +import 'package:flutter_test/flutter_test.dart'; + +void main() { + group('PaymentUrlModel', () { + test('fromJson - успешная десериализация', () { + final json = { + 'paymentUrl': 'https://payment.example.com/checkout', + }; + + final model = PaymentUrlModel.fromJson(json); + + expect(model.paymentUrl, equals('https://payment.example.com/checkout')); + }); + + test('toJson - успешная сериализация', () { + final model = PaymentUrlModel( + paymentUrl: 'https://payment.example.com/checkout', + ); + + final json = model.toJson(); + + expect(json['paymentUrl'], equals('https://payment.example.com/checkout')); + }); + + test('copyWith - создание копии с изменениями', () { + final model = PaymentUrlModel( + paymentUrl: 'https://payment.example.com/checkout', + ); + + final updatedModel = model.copyWith( + paymentUrl: 'https://payment.example.com/new', + ); + + expect(updatedModel.paymentUrl, equals('https://payment.example.com/new')); + }); + }); +} + diff --git a/test/features/my_finance/presentation/cubit/user_cards_cubit/user_cards_cubit_test.dart b/test/features/my_finance/presentation/cubit/user_cards_cubit/user_cards_cubit_test.dart new file mode 100644 index 00000000..3c972d13 --- /dev/null +++ b/test/features/my_finance/presentation/cubit/user_cards_cubit/user_cards_cubit_test.dart @@ -0,0 +1,154 @@ +import 'package:eatvio_app/core/enums/base_state.dart'; +import 'package:eatvio_app/features/my_finance/models/card_model.dart'; +import 'package:eatvio_app/features/my_finance/models/payment_url_model.dart'; +import 'package:eatvio_app/features/my_finance/presentation/cubit/user_cards_cubit/user_cards_cubit.dart'; +import 'package:eatvio_app/features/my_finance/repositories/cards_repository.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:mockito/annotations.dart'; +import 'package:mockito/mockito.dart'; + +import 'user_cards_cubit_test.mocks.dart'; + +@GenerateMocks([CardsRepository]) +void main() { + group('UserCardsCubit', () { + late UserCardsCubit cubit; + late MockCardsRepository mockCardsRepository; + + setUp(() { + mockCardsRepository = MockCardsRepository(); + cubit = UserCardsCubit(cardsRepository: mockCardsRepository); + }); + + tearDown(() { + cubit.close(); + }); + + test('начальное состояние', () { + expect(cubit.state.cards, isEmpty); + expect(cubit.state.error, isNull); + expect(cubit.state.state, BaseState.initial); + }); + + test('getCards - успешная загрузка карт', () async { + final cards = [ + CardModel(id: 1, cardNumber: '1234', isDefault: true), + CardModel(id: 2, cardNumber: '5678', isDefault: false), + ]; + + when(mockCardsRepository.getCards()).thenAnswer((_) async => cards); + + await cubit.getCards(); + + expect(cubit.state.cards, equals(cards)); + expect(cubit.state.state, BaseState.success); + expect(cubit.state.error, isNull); + verify(mockCardsRepository.getCards()).called(1); + }); + + test('getCards - ошибка при загрузке', () async { + final error = Exception('Ошибка загрузки'); + when(mockCardsRepository.getCards()).thenThrow(error); + + await cubit.getCards(); + + expect(cubit.state.cards, isEmpty); + expect(cubit.state.state, BaseState.error); + expect(cubit.state.error, equals(error.toString())); + verify(mockCardsRepository.getCards()).called(1); + }); + + test('addCard - успешное добавление карты', () async { + final paymentUrl = PaymentUrlModel(paymentUrl: 'https://payment.url'); + when(mockCardsRepository.addCard()).thenAnswer((_) async => paymentUrl); + + final result = await cubit.addCard(); + + expect(result, equals(paymentUrl)); + verify(mockCardsRepository.addCard()).called(1); + }); + + test('addCard - ошибка при добавлении', () async { + when(mockCardsRepository.addCard()).thenThrow(Exception('Ошибка')); + + final result = await cubit.addCard(); + + expect(result, isNull); + verify(mockCardsRepository.addCard()).called(1); + }); + + test('markAsDefault - успешное изменение карты по умолчанию', () async { + final cards = [ + CardModel(id: 1, cardNumber: '1234', isDefault: true), + CardModel(id: 2, cardNumber: '5678', isDefault: false), + ]; + + when(mockCardsRepository.getCards()).thenAnswer((_) async => cards); + when(mockCardsRepository.markAsDefault(id: 2)).thenAnswer((_) async {}); + + await cubit.getCards(); + await cubit.markAsDefault(id: 2); + + expect(cubit.state.cards[0].isDefault, isFalse); + expect(cubit.state.cards[1].isDefault, isTrue); + + // Ждем завершения debounce + await Future.delayed(const Duration(milliseconds: 100)); + + verify(mockCardsRepository.markAsDefault(id: 2)).called(1); + }); + + test('deleteCard - успешное удаление карты', () async { + final cards = [ + CardModel(id: 1, cardNumber: '1234', isDefault: true), + CardModel(id: 2, cardNumber: '5678', isDefault: false), + ]; + + when(mockCardsRepository.getCards()).thenAnswer((_) async => cards); + when(mockCardsRepository.deleteCard(id: 2)).thenAnswer((_) async {}); + + await cubit.getCards(); + await cubit.deleteCard(id: 2); + + expect(cubit.state.cards.length, equals(1)); + expect(cubit.state.cards[0].id, equals(1)); + verify(mockCardsRepository.deleteCard(id: 2)).called(1); + }); + + test('deleteCard - ошибка при удалении', () async { + final cards = [ + CardModel(id: 1, cardNumber: '1234', isDefault: true), + ]; + + when(mockCardsRepository.getCards()).thenAnswer((_) async => cards); + when(mockCardsRepository.deleteCard(id: 1)).thenThrow(Exception('Ошибка')); + + await cubit.getCards(); + await cubit.deleteCard(id: 1); + + // Карта должна вернуться обратно + expect(cubit.state.cards.length, equals(1)); + expect(cubit.state.state, BaseState.error); + verify(mockCardsRepository.deleteCard(id: 1)).called(1); + }); + + test('collectCharge - успешный сбор платежа', () async { + when(mockCardsRepository.collectCharge(chosenPlaId: 1)).thenAnswer((_) async {}); + + final result = await cubit.collectCharge(chosenPlaId: 1); + + expect(result, isTrue); + verify(mockCardsRepository.collectCharge(chosenPlaId: 1)).called(1); + }); + + test('collectCharge - ошибка при сборе платежа', () async { + when(mockCardsRepository.collectCharge(chosenPlaId: 1)).thenThrow(Exception('Ошибка')); + + final result = await cubit.collectCharge(chosenPlaId: 1); + + expect(result, isFalse); + verify(mockCardsRepository.collectCharge(chosenPlaId: 1)).called(1); + }); + }); +} + diff --git a/test/features/my_finance/repositories/cards_repository_test.dart b/test/features/my_finance/repositories/cards_repository_test.dart new file mode 100644 index 00000000..5a657586 --- /dev/null +++ b/test/features/my_finance/repositories/cards_repository_test.dart @@ -0,0 +1,111 @@ +import 'package:eatvio_app/features/my_finance/models/card_model.dart'; +import 'package:eatvio_app/features/my_finance/models/payment_url_model.dart'; +import 'package:eatvio_app/features/my_finance/repositories/cards_repository.dart'; +import 'package:eatvio_app/features/my_finance/services/cards_service.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:mockito/annotations.dart'; +import 'package:mockito/mockito.dart'; + +import 'cards_repository_test.mocks.dart'; + +@GenerateMocks([CardsService]) +void main() { + group('CardsRepository', () { + late CardsRepository repository; + late MockCardsService mockCardsService; + + setUp(() { + mockCardsService = MockCardsService(); + repository = CardsRepository(cardsService: mockCardsService); + }); + + test('addCard - успешное добавление карты', () async { + final paymentUrl = PaymentUrlModel(paymentUrl: 'https://payment.url'); + when(mockCardsService.addCard()).thenAnswer((_) async => paymentUrl); + + final result = await repository.addCard(); + + expect(result, equals(paymentUrl)); + verify(mockCardsService.addCard()).called(1); + }); + + test('addCard - проброс ошибки', () async { + final error = Exception('Ошибка'); + when(mockCardsService.addCard()).thenThrow(error); + + expect(() => repository.addCard(), throwsA(error)); + verify(mockCardsService.addCard()).called(1); + }); + + test('getCards - успешная загрузка карт', () async { + final cards = [ + CardModel(id: 1, cardNumber: '1234', isDefault: true), + CardModel(id: 2, cardNumber: '5678', isDefault: false), + ]; + + when(mockCardsService.getCards()).thenAnswer((_) async => cards); + + final result = await repository.getCards(); + + expect(result, equals(cards)); + verify(mockCardsService.getCards()).called(1); + }); + + test('getCards - проброс ошибки', () async { + final error = Exception('Ошибка'); + when(mockCardsService.getCards()).thenThrow(error); + + expect(() => repository.getCards(), throwsA(error)); + verify(mockCardsService.getCards()).called(1); + }); + + test('markAsDefault - успешное изменение карты по умолчанию', () async { + when(mockCardsService.markAsDefault(id: 1)).thenAnswer((_) async {}); + + await repository.markAsDefault(id: 1); + + verify(mockCardsService.markAsDefault(id: 1)).called(1); + }); + + test('markAsDefault - проброс ошибки', () async { + final error = Exception('Ошибка'); + when(mockCardsService.markAsDefault(id: 1)).thenThrow(error); + + expect(() => repository.markAsDefault(id: 1), throwsA(error)); + verify(mockCardsService.markAsDefault(id: 1)).called(1); + }); + + test('deleteCard - успешное удаление карты', () async { + when(mockCardsService.deleteCard(id: 1)).thenAnswer((_) async {}); + + await repository.deleteCard(id: 1); + + verify(mockCardsService.deleteCard(id: 1)).called(1); + }); + + test('deleteCard - проброс ошибки', () async { + final error = Exception('Ошибка'); + when(mockCardsService.deleteCard(id: 1)).thenThrow(error); + + expect(() => repository.deleteCard(id: 1), throwsA(error)); + verify(mockCardsService.deleteCard(id: 1)).called(1); + }); + + test('collectCharge - успешный сбор платежа', () async { + when(mockCardsService.collectCharge(chosenPlanId: 1)).thenAnswer((_) async {}); + + await repository.collectCharge(chosenPlaId: 1); + + verify(mockCardsService.collectCharge(chosenPlanId: 1)).called(1); + }); + + test('collectCharge - проброс ошибки', () async { + final error = Exception('Ошибка'); + when(mockCardsService.collectCharge(chosenPlanId: 1)).thenThrow(error); + + expect(() => repository.collectCharge(chosenPlaId: 1), throwsA(error)); + verify(mockCardsService.collectCharge(chosenPlanId: 1)).called(1); + }); + }); +} + diff --git a/test/features/notifications/presentation/notifications_cubit/notifications_cubit_test.dart b/test/features/notifications/presentation/notifications_cubit/notifications_cubit_test.dart new file mode 100644 index 00000000..4d930b1b --- /dev/null +++ b/test/features/notifications/presentation/notifications_cubit/notifications_cubit_test.dart @@ -0,0 +1,194 @@ +import 'package:eatvio_app/core/websockets/ws_manager.dart'; +import 'package:eatvio_app/features/notifications/models/notification_model.dart'; +import 'package:eatvio_app/features/notifications/presentation/notifications_cubit/notifications_cubit.dart'; +import 'package:eatvio_app/features/notifications/presentation/notifications_cubit/notifications_state.dart'; +import 'package:eatvio_app/features/notifications/repository/notifications_repository.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:mockito/annotations.dart'; +import 'package:mockito/mockito.dart'; + +import 'notifications_cubit_test.mocks.dart'; + +@GenerateMocks([WsManager, NotificationsRepository]) +void main() { + group('NotificationsCubit', () { + late NotificationsCubit cubit; + late MockWsManager mockWsManager; + late MockNotificationsRepository mockNotificationsRepository; + + setUp(() { + mockWsManager = MockWsManager(); + mockNotificationsRepository = MockNotificationsRepository(); + + // Мокируем stream как null, чтобы избежать проблем с подписками в тестах + when(mockWsManager.newNotificationsStream).thenAnswer((_) => null); + + cubit = NotificationsCubit( + wsManager: mockWsManager, + notificationsRepository: mockNotificationsRepository, + ); + }); + + tearDown(() async { + await cubit.close(); + }); + + test('начальное состояние - empty', () { + expect(cubit.state, isA()); + }); + + test('getNotifications - успешная загрузка уведомлений', () async { + final notifications = [ + ChosenPlanStartedToClientNotification( + type: NotificationType.chosenPlanStartedToClientNotification, + planName: 'Test Plan', + planId: 1, + isRead: false, + createdAt: DateTime.now(), + ), + ]; + + final result = { + 'notifications': notifications, + 'pagination': { + 'current_page': 1, + 'last_page': 1, + }, + }; + + when(mockNotificationsRepository.getNotifications(page: 1)) + .thenAnswer((_) async => result); + + await cubit.getNotifications(); + + final state = cubit.state; + expect(state, isA()); + if (state is NotificationsStateLoaded) { + expect(state.notifications.length, 1); + expect(state.currentPage, 1); + expect(state.hasNextPage, false); + } + verify(mockNotificationsRepository.getNotifications(page: 1)).called(1); + }); + + test('getNotifications - обработка ошибки', () async { + when(mockNotificationsRepository.getNotifications(page: 1)) + .thenThrow(Exception('Ошибка загрузки')); + + await cubit.getNotifications(); + + // Состояние должно остаться empty при ошибке + expect(cubit.state, isA()); + }); + + test('loadMoreNotifications - успешная загрузка следующей страницы', () async { + final firstPageNotifications = [ + ChosenPlanStartedToClientNotification( + type: NotificationType.chosenPlanStartedToClientNotification, + planName: 'Plan 1', + planId: 1, + isRead: false, + createdAt: DateTime.now(), + ), + ]; + + final secondPageNotifications = [ + ChosenPlanStartedToClientNotification( + type: NotificationType.chosenPlanStartedToClientNotification, + planName: 'Plan 2', + planId: 2, + isRead: false, + createdAt: DateTime.now(), + ), + ]; + + when(mockNotificationsRepository.getNotifications(page: 1)) + .thenAnswer((_) async => { + 'notifications': firstPageNotifications, + 'pagination': {'current_page': 1, 'last_page': 2}, + }); + + when(mockNotificationsRepository.getNotifications(page: 2)) + .thenAnswer((_) async => { + 'notifications': secondPageNotifications, + 'pagination': {'current_page': 2, 'last_page': 2}, + }); + + await cubit.getNotifications(); + + final firstState = cubit.state; + expect(firstState, isA()); + if (firstState is NotificationsStateLoaded) { + expect(firstState.hasNextPage, true); + + await cubit.loadMoreNotifications(); + + final secondState = cubit.state; + expect(secondState, isA()); + if (secondState is NotificationsStateLoaded) { + expect(secondState.notifications.length, 2); + expect(secondState.currentPage, 2); + expect(secondState.hasNextPage, false); + } + } + }); + + test('loadMoreNotifications - не загружает, если нет следующей страницы', () async { + final notifications = [ + ChosenPlanStartedToClientNotification( + type: NotificationType.chosenPlanStartedToClientNotification, + planName: 'Plan 1', + planId: 1, + isRead: false, + createdAt: DateTime.now(), + ), + ]; + + when(mockNotificationsRepository.getNotifications(page: 1)) + .thenAnswer((_) async => { + 'notifications': notifications, + 'pagination': {'current_page': 1, 'last_page': 1}, + }); + + await cubit.getNotifications(); + await cubit.loadMoreNotifications(); + + verify(mockNotificationsRepository.getNotifications(page: 1)).called(1); + verifyNever(mockNotificationsRepository.getNotifications(page: 2)); + }); + + test('markNotificationsAsRead - успешная пометка как прочитанных', () async { + final notifications = [ + ChosenPlanStartedToClientNotification( + type: NotificationType.chosenPlanStartedToClientNotification, + planName: 'Plan 1', + planId: 1, + isRead: false, + createdAt: DateTime.now(), + ), + ]; + + when(mockNotificationsRepository.getNotifications(page: 1)) + .thenAnswer((_) async => { + 'notifications': notifications, + 'pagination': {'current_page': 1, 'last_page': 1}, + }); + + when(mockNotificationsRepository.markNotificationsAsRead()) + .thenAnswer((_) async => {}); + + await cubit.getNotifications(); + await cubit.markNotificationsAsRead(); + + verify(mockNotificationsRepository.markNotificationsAsRead()).called(1); + verify(mockNotificationsRepository.getNotifications(page: 1)).called(2); // Один раз при getNotifications, второй раз после markNotificationsAsRead + }); + + test('connectToWebSocket - подключается к WebSocket', () async { + await cubit.connectToWebSocket(); + + verify(mockWsManager.newNotificationsStream).called(1); + }); + }); +} + diff --git a/test/features/notifications/repository/notifications_repository_test.dart b/test/features/notifications/repository/notifications_repository_test.dart new file mode 100644 index 00000000..a80b3fb8 --- /dev/null +++ b/test/features/notifications/repository/notifications_repository_test.dart @@ -0,0 +1,77 @@ +import 'package:eatvio_app/features/notifications/models/notification_model.dart'; +import 'package:eatvio_app/features/notifications/repository/notifications_repository.dart'; +import 'package:eatvio_app/features/notifications/service/notifications_service.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:mockito/annotations.dart'; +import 'package:mockito/mockito.dart'; + +import 'notifications_repository_test.mocks.dart'; + +@GenerateMocks([NotificationsService]) +void main() { + group('NotificationsRepository', () { + late NotificationsRepository repository; + late MockNotificationsService mockNotificationsService; + + setUp(() { + mockNotificationsService = MockNotificationsService(); + repository = NotificationsRepository(notificationsService: mockNotificationsService); + }); + + test('getNotifications - успешная загрузка уведомлений', () async { + final notifications = [ + ChosenPlanStartedToClientNotification( + type: NotificationType.chosenPlanStartedToClientNotification, + planName: 'Test Plan', + planId: 1, + isRead: false, + createdAt: DateTime.now(), + ), + ]; + + final result = { + 'notifications': notifications, + 'pagination': { + 'current_page': 1, + 'last_page': 1, + }, + }; + + when(mockNotificationsService.getNotifications(page: 1)) + .thenAnswer((_) async => result); + + final response = await repository.getNotifications(page: 1); + + expect(response, equals(result)); + verify(mockNotificationsService.getNotifications(page: 1)).called(1); + }); + + test('getNotifications - проброс ошибки', () async { + final error = Exception('Ошибка загрузки'); + when(mockNotificationsService.getNotifications(page: 1)) + .thenThrow(error); + + expect(() => repository.getNotifications(page: 1), throwsA(error)); + verify(mockNotificationsService.getNotifications(page: 1)).called(1); + }); + + test('markNotificationsAsRead - успешная пометка как прочитанных', () async { + when(mockNotificationsService.markNotificationsAsRead()) + .thenAnswer((_) async => {}); + + await repository.markNotificationsAsRead(); + + verify(mockNotificationsService.markNotificationsAsRead()).called(1); + }); + + test('markNotificationsAsRead - проброс ошибки', () async { + final error = Exception('Ошибка'); + when(mockNotificationsService.markNotificationsAsRead()) + .thenThrow(error); + + expect(() => repository.markNotificationsAsRead(), throwsA(error)); + verify(mockNotificationsService.markNotificationsAsRead()).called(1); + }); + }); +} + diff --git a/test/features/order_info/cubit/order_info_cubit_test.dart b/test/features/order_info/cubit/order_info_cubit_test.dart new file mode 100644 index 00000000..de00ad94 --- /dev/null +++ b/test/features/order_info/cubit/order_info_cubit_test.dart @@ -0,0 +1,266 @@ +import 'package:eatvio_app/features/chat/models/payment_model.dart'; +import 'package:eatvio_app/features/chat/repositories/chat_repository.dart'; +import 'package:eatvio_app/features/order_info/cubit/order_info_cubit.dart'; +import 'package:eatvio_app/features/plans/models/chosen_plan_model.dart'; +import 'package:eatvio_app/features/plans/enums/chosen_plan_state_enum.dart'; +import 'package:eatvio_app/features/feed/models/plan_pagination_model.dart'; +import 'package:eatvio_app/features/feed/models/diets_pagination_model.dart'; +import 'package:eatvio_app/features/profile/models/profile_model.dart'; +import 'package:eatvio_app/features/profile/repositories/profile_repository.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:mockito/annotations.dart'; +import 'package:mockito/mockito.dart'; + +import 'order_info_cubit_test.mocks.dart'; + +@GenerateMocks([ChatRepository, ProfileRepository]) +void main() { + group('OrderInfoCubit', () { + late OrderInfoCubit cubit; + late MockChatRepository mockChatRepository; + late MockProfileRepository mockProfileRepository; + + setUp(() { + mockChatRepository = MockChatRepository(); + mockProfileRepository = MockProfileRepository(); + cubit = OrderInfoCubit( + chatRepository: mockChatRepository, + profileRepository: mockProfileRepository, + ); + }); + + tearDown(() { + cubit.close(); + }); + + test('начальное состояние', () { + expect(cubit.state.isLoading, isTrue); + expect(cubit.state.chosenPlan, isNull); + expect(cubit.state.customerProfile, isNull); + }); + + test('getChosenPlan - успешная загрузка плана', () async { + final chosenPlan = ChosenPlanModel( + id: 1, + optionType: 'options1', + startDate: DateTime.now(), + chosenPlanState: ChosenPlanStateEnum.stateStarted, + chatId: 'chat-1', + consultantId: 1, + plan: const PlanModel( + id: 1, + slug: 'test-plan', + name: 'Test Plan', + image: '', + duration: 30, + state: 'active', + type: PlanType(fit: false, slim: false, behavior: false, wellness: false), + details: '', + packetsCount: 1, + author: Author( + authorSlug: 'author-slug', + authorName: 'Author Name', + authorProfileIcon: '', + authorIsMe: false, + ), + totalLikes: 0, + totalViews: 0, + totalComments: 0, + isFavorite: false, + minPriceOfOptions: 0, + maxPriceOfOptions: 0, + wasLikedByMe: false, + ), + ); + + when(mockChatRepository.getChosenPlan(id: 1)) + .thenAnswer((_) async => chosenPlan); + + await cubit.getChosenPlan(id: 1); + + expect(cubit.state.isLoading, isFalse); + expect(cubit.state.chosenPlan, equals(chosenPlan)); + verify(mockChatRepository.getChosenPlan(id: 1)).called(1); + }); + + test('getChosenPlan - загрузка плана с профилем клиента', () async { + final chosenPlan = ChosenPlanModel( + id: 1, + optionType: 'options1', + startDate: DateTime.now(), + chosenPlanState: ChosenPlanStateEnum.stateStarted, + chatId: 'chat-1', + consultantId: 1, + plan: const PlanModel( + id: 1, + slug: 'test-plan', + name: 'Test Plan', + image: '', + duration: 30, + state: 'active', + type: PlanType(fit: false, slim: false, behavior: false, wellness: false), + details: '', + packetsCount: 1, + author: Author( + authorSlug: 'author-slug', + authorName: 'Author Name', + authorProfileIcon: '', + authorIsMe: false, + ), + totalLikes: 0, + totalViews: 0, + totalComments: 0, + isFavorite: false, + minPriceOfOptions: 0, + maxPriceOfOptions: 0, + wasLikedByMe: false, + ), + ); + + const customerProfile = ProfileModel( + id: 1, + name: 'Test User', + email: 'test@example.com', + phoneConfirmationState: 'confirmed', + isPublicProfile: true, + isBlockedByMe: false, + isBlockedByAuthor: false, + isBlockedByUser: false, + isEntitySubscribedByMe: false, + slug: 'test-user', + profilePhotoUrl: '', + roles: [], + badges: [], + subscriptions: [], + subscribers: [], + ); + + when(mockChatRepository.getChosenPlan(id: 1)) + .thenAnswer((_) async => chosenPlan); + + when(mockProfileRepository.getProfileBySlug('test-user')) + .thenAnswer((_) async => customerProfile); + + await cubit.getChosenPlan(id: 1, customerSlug: 'test-user'); + + expect(cubit.state.isLoading, isFalse); + expect(cubit.state.chosenPlan, equals(chosenPlan)); + expect(cubit.state.customerProfile, equals(customerProfile)); + verify(mockChatRepository.getChosenPlan(id: 1)).called(1); + verify(mockProfileRepository.getProfileBySlug('test-user')).called(1); + }); + + test('getChosenPlan - обработка ошибки', () async { + when(mockChatRepository.getChosenPlan(id: 1)) + .thenThrow(Exception('Ошибка загрузки')); + + await cubit.getChosenPlan(id: 1); + + expect(cubit.state.isLoading, isFalse); + expect(cubit.state.chosenPlan, isNull); + }); + + test('getPaymentLink - успешное получение ссылки на оплату', () async { + final chosenPlan = ChosenPlanModel( + id: 1, + optionType: 'options1', + startDate: DateTime.now(), + chosenPlanState: ChosenPlanStateEnum.stateStarted, + chatId: 'chat-1', + consultantId: 1, + plan: const PlanModel( + id: 1, + slug: 'test-plan', + name: 'Test Plan', + image: '', + duration: 30, + state: 'active', + type: PlanType(fit: false, slim: false, behavior: false, wellness: false), + details: '', + packetsCount: 1, + author: Author( + authorSlug: 'author-slug', + authorName: 'Author Name', + authorProfileIcon: '', + authorIsMe: false, + ), + totalLikes: 0, + totalViews: 0, + totalComments: 0, + isFavorite: false, + minPriceOfOptions: 0, + maxPriceOfOptions: 0, + wasLikedByMe: false, + ), + ); + final paymentModel = PaymentModel(paymentUrl: 'https://payment.url'); + + // Сначала устанавливаем план в состояние + when(mockChatRepository.getChosenPlan(id: 1)) + .thenAnswer((_) async => chosenPlan); + await cubit.getChosenPlan(id: 1); + + when(mockChatRepository.getPaymentLink(id: 1)) + .thenAnswer((_) async => paymentModel); + + final result = await cubit.getPaymentLink(); + + expect(result, equals('https://payment.url')); + verify(mockChatRepository.getPaymentLink(id: 1)).called(1); + }); + + test('getPaymentLink - возвращает null, если план не выбран', () async { + final result = await cubit.getPaymentLink(); + + expect(result, isNull); + verifyNever(mockChatRepository.getPaymentLink(id: anyNamed('id'))); + }); + + test('getPaymentLink - обработка ошибки', () async { + final chosenPlan = ChosenPlanModel( + id: 1, + optionType: 'options1', + startDate: DateTime.now(), + chosenPlanState: ChosenPlanStateEnum.stateStarted, + chatId: 'chat-1', + consultantId: 1, + plan: const PlanModel( + id: 1, + slug: 'test-plan', + name: 'Test Plan', + image: '', + duration: 30, + state: 'active', + type: PlanType(fit: false, slim: false, behavior: false, wellness: false), + details: '', + packetsCount: 1, + author: Author( + authorSlug: 'author-slug', + authorName: 'Author Name', + authorProfileIcon: '', + authorIsMe: false, + ), + totalLikes: 0, + totalViews: 0, + totalComments: 0, + isFavorite: false, + minPriceOfOptions: 0, + maxPriceOfOptions: 0, + wasLikedByMe: false, + ), + ); + + when(mockChatRepository.getChosenPlan(id: 1)) + .thenAnswer((_) async => chosenPlan); + await cubit.getChosenPlan(id: 1); + + when(mockChatRepository.getPaymentLink(id: 1)) + .thenThrow(Exception('Ошибка')); + + final result = await cubit.getPaymentLink(); + + expect(result, isNull); + }); + }); +} + diff --git a/test/features/personal_diet_suggestion/cubit/personal_diet_suggestion_cubit_test.dart b/test/features/personal_diet_suggestion/cubit/personal_diet_suggestion_cubit_test.dart new file mode 100644 index 00000000..82df287b --- /dev/null +++ b/test/features/personal_diet_suggestion/cubit/personal_diet_suggestion_cubit_test.dart @@ -0,0 +1,271 @@ +import 'package:eatvio_app/features/consultations/models/consultation_model.dart'; +import 'package:eatvio_app/features/diets/repositories/diet_repository.dart'; +import 'package:eatvio_app/features/feed/models/diets_pagination_model.dart'; +import 'package:eatvio_app/features/personal_diet_suggestion/cubit/personal_diet_suggestion_cubit.dart'; +import 'package:eatvio_app/features/personal_diet_suggestion/repository/personal_diet_suggestion_repository.dart'; +import 'package:eatvio_app/features/plans/enums/chosen_plan_state_enum.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:mockito/annotations.dart'; +import 'package:mockito/mockito.dart'; + +import 'personal_diet_suggestion_cubit_test.mocks.dart'; + +@GenerateMocks([DietRepository, PersonalDietSuggestionRepository]) +void main() { + group('PersonalDietSuggestionCubit', () { + late PersonalDietSuggestionCubit cubit; + late MockDietRepository mockDietRepository; + late MockPersonalDietSuggestionRepository mockPersonalDietSuggestionRepository; + + setUp(() { + mockDietRepository = MockDietRepository(); + mockPersonalDietSuggestionRepository = MockPersonalDietSuggestionRepository(); + cubit = PersonalDietSuggestionCubit( + dietRepository: mockDietRepository, + personalDietSuggestionService: mockPersonalDietSuggestionRepository, + ); + }); + + tearDown(() { + cubit.close(); + }); + + test('начальное состояние', () { + expect(cubit.state.consultationsWithDiet, isEmpty); + expect(cubit.state.consultationsWithoutDiet, isEmpty); + expect(cubit.state.dietName, isEmpty); + expect(cubit.state.isLoading, isFalse); + expect(cubit.state.error, isNull); + }); + + test('setDietName - успешная установка названия диеты', () async { + const dietId = 1; + const dietName = 'Test Diet'; + const diet = Diet( + id: dietId, + name: dietName, + slug: 'test-diet', + image: '', + duration: 30, + state: DietState.active, + type: DietType(light: false, veg: false, carb: false, meat: false), + details: '', + dailySchedule: {}, + dailyNutrition: [], + author: Author( + authorSlug: 'author-slug', + authorName: 'Author Name', + authorProfileIcon: '', + authorIsMe: false, + ), + totalLikes: 0, + totalViews: 0, + totalComments: 0, + isFavorite: false, + wasLikedByMe: false, + ); + + when(mockDietRepository.getDiet(dietId)) + .thenAnswer((_) async => diet); + + await cubit.setDietName(dietId); + + expect(cubit.state.dietName, equals(dietName)); + expect(cubit.state.isLoading, isFalse); + verify(mockDietRepository.getDiet(dietId)).called(1); + }); + + test('setDietName - обработка ошибки', () async { + const dietId = 1; + final error = Exception('Ошибка загрузки'); + + when(mockDietRepository.getDiet(dietId)) + .thenThrow(error); + + await cubit.setDietName(dietId); + + expect(cubit.state.isLoading, isFalse); + expect(cubit.state.error, equals(error.toString())); + }); + + test('getConsultationWithPersonalDiets - успешная загрузка консультаций', () async { + const dietId = 1; + const dietName = 'Test Diet'; + const diet = Diet( + id: dietId, + name: dietName, + slug: 'test-diet', + image: '', + duration: 30, + state: DietState.active, + type: DietType(light: false, veg: false, carb: false, meat: false), + details: '', + dailySchedule: {}, + dailyNutrition: [], + author: Author( + authorSlug: 'author-slug', + authorName: 'Author Name', + authorProfileIcon: '', + authorIsMe: false, + ), + totalLikes: 0, + totalViews: 0, + totalComments: 0, + isFavorite: false, + wasLikedByMe: false, + ); + + final consultations = [ + ConsultationModel( + id: 1, + chosenPlanState: ChosenPlanStateEnum.stateStarted, + planTitle: 'Plan 1', + userName: 'User 1', + userSlug: 'user-1', + consultantPayment: 100.0, + duration: 30, + currentDiaryDay: 1, + userProfilePhotoUrl: '', + consultationOptionPrice: 100, + startDate: DateTime.now(), + endDate: DateTime.now().add(const Duration(days: 30)), + consultantId: 1, + diaries: [], + personalDietId: 1, // С диетой + ), + ConsultationModel( + id: 2, + chosenPlanState: ChosenPlanStateEnum.stateStarted, + planTitle: 'Plan 2', + userName: 'User 2', + userSlug: 'user-2', + consultantPayment: 100.0, + duration: 30, + currentDiaryDay: 1, + userProfilePhotoUrl: '', + consultationOptionPrice: 100, + startDate: DateTime.now(), + endDate: DateTime.now().add(const Duration(days: 30)), + consultantId: 1, + diaries: [], + personalDietId: null, // Без диеты + ), + ]; + + when(mockDietRepository.getDiet(dietId)) + .thenAnswer((_) async => diet); + + when(mockPersonalDietSuggestionRepository.getConsultationWithPersonalDiets()) + .thenAnswer((_) async => consultations); + + await cubit.getConsultationWithPersonalDiets(dietId); + + expect(cubit.state.dietName, equals(dietName)); + expect(cubit.state.consultationsWithDiet.length, 1); + expect(cubit.state.consultationsWithoutDiet.length, 1); + expect(cubit.state.isLoading, isFalse); + verify(mockDietRepository.getDiet(dietId)).called(1); + verify(mockPersonalDietSuggestionRepository.getConsultationWithPersonalDiets()).called(1); + }); + + test('attachDietToConsultation - успешное прикрепление диеты', () async { + const dietId = 1; + const consultationId = 1; + + when(mockPersonalDietSuggestionRepository.attachPersonalDiet( + consultationId: consultationId, + dietId: dietId, + )).thenAnswer((_) async => {}); + + // Устанавливаем dietId через getConsultationWithPersonalDiets + const diet = Diet( + id: dietId, + name: 'Test Diet', + slug: 'test-diet', + image: '', + duration: 30, + state: DietState.active, + type: DietType(light: false, veg: false, carb: false, meat: false), + details: '', + dailySchedule: {}, + dailyNutrition: [], + author: Author( + authorSlug: 'author-slug', + authorName: 'Author Name', + authorProfileIcon: '', + authorIsMe: false, + ), + totalLikes: 0, + totalViews: 0, + totalComments: 0, + isFavorite: false, + wasLikedByMe: false, + ); + + when(mockDietRepository.getDiet(dietId)) + .thenAnswer((_) async => diet); + + when(mockPersonalDietSuggestionRepository.getConsultationWithPersonalDiets()) + .thenAnswer((_) async => []); + + await cubit.getConsultationWithPersonalDiets(dietId); + final result = await cubit.attachDietToConsultation(consultationId: consultationId); + + expect(result, isTrue); + verify(mockPersonalDietSuggestionRepository.attachPersonalDiet( + consultationId: consultationId, + dietId: dietId, + )).called(1); + }); + + test('reattachDietToConsultation - успешное переприкрепление диеты', () async { + const dietId = 1; + const consultationId = 1; + + const diet = Diet( + id: dietId, + name: 'Test Diet', + slug: 'test-diet', + image: '', + duration: 30, + state: DietState.active, + type: DietType(light: false, veg: false, carb: false, meat: false), + details: '', + dailySchedule: {}, + dailyNutrition: [], + author: Author( + authorSlug: 'author-slug', + authorName: 'Author Name', + authorProfileIcon: '', + authorIsMe: false, + ), + totalLikes: 0, + totalViews: 0, + totalComments: 0, + isFavorite: false, + wasLikedByMe: false, + ); + + when(mockDietRepository.getDiet(dietId)) + .thenAnswer((_) async => diet); + + when(mockPersonalDietSuggestionRepository.getConsultationWithPersonalDiets()) + .thenAnswer((_) async => []); + + when(mockPersonalDietSuggestionRepository.reattachPersonalDiet( + consultationId: consultationId, + dietId: dietId, + )).thenAnswer((_) async => {}); + + await cubit.getConsultationWithPersonalDiets(dietId); + final result = await cubit.reattachDietToConsultation(consultationId: consultationId); + + expect(result, isTrue); + verify(mockPersonalDietSuggestionRepository.reattachPersonalDiet( + consultationId: consultationId, + dietId: dietId, + )).called(1); + }); + }); +} + diff --git a/test/features/personal_diet_suggestion/repository/personal_diet_suggestion_repository_test.dart b/test/features/personal_diet_suggestion/repository/personal_diet_suggestion_repository_test.dart new file mode 100644 index 00000000..914732d4 --- /dev/null +++ b/test/features/personal_diet_suggestion/repository/personal_diet_suggestion_repository_test.dart @@ -0,0 +1,109 @@ +import 'package:eatvio_app/features/consultations/models/consultation_model.dart'; +import 'package:eatvio_app/features/personal_diet_suggestion/repository/personal_diet_suggestion_repository.dart'; +import 'package:eatvio_app/features/personal_diet_suggestion/services/personal_diet_suggestion_service.dart'; +import 'package:eatvio_app/features/plans/enums/chosen_plan_state_enum.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:mockito/annotations.dart'; +import 'package:mockito/mockito.dart'; + +import 'personal_diet_suggestion_repository_test.mocks.dart'; + +@GenerateMocks([PersonalDietSuggestionService]) +void main() { + group('PersonalDietSuggestionRepository', () { + late PersonalDietSuggestionRepository repository; + late MockPersonalDietSuggestionService mockService; + + setUp(() { + mockService = MockPersonalDietSuggestionService(); + repository = PersonalDietSuggestionRepository( + personalDietSuggestionService: mockService, + ); + }); + + test('getConsultationWithPersonalDiets - успешная загрузка консультаций', () async { + final consultations = [ + ConsultationModel( + id: 1, + chosenPlanState: ChosenPlanStateEnum.stateStarted, + planTitle: 'Plan 1', + userName: 'User 1', + userSlug: 'user-1', + consultantPayment: 100.0, + duration: 30, + currentDiaryDay: 1, + userProfilePhotoUrl: '', + consultationOptionPrice: 100, + startDate: DateTime.now(), + endDate: DateTime.now().add(const Duration(days: 30)), + consultantId: 1, + diaries: [], + personalDietId: 1, + ), + ]; + + final paginationModel = ConsultationPaginationModel(data: consultations); + + when(mockService.getConsultationWithPersonalDiets()) + .thenAnswer((_) async => paginationModel); + + final result = await repository.getConsultationWithPersonalDiets(); + + expect(result, equals(consultations)); + verify(mockService.getConsultationWithPersonalDiets()).called(1); + }); + + test('getConsultationWithPersonalDiets - проброс ошибки', () async { + final error = Exception('Ошибка загрузки'); + when(mockService.getConsultationWithPersonalDiets()) + .thenThrow(error); + + expect( + () => repository.getConsultationWithPersonalDiets(), + throwsA(error), + ); + verify(mockService.getConsultationWithPersonalDiets()).called(1); + }); + + test('attachPersonalDiet - успешное прикрепление диеты', () async { + const consultationId = 1; + const dietId = 2; + + when(mockService.attachPersonalDiet( + consultationId: consultationId, + dietId: dietId, + )).thenAnswer((_) async => {}); + + await repository.attachPersonalDiet( + consultationId: consultationId, + dietId: dietId, + ); + + verify(mockService.attachPersonalDiet( + consultationId: consultationId, + dietId: dietId, + )).called(1); + }); + + test('reattachPersonalDiet - успешное переприкрепление диеты', () async { + const consultationId = 1; + const dietId = 2; + + when(mockService.reattachPersonalDiet( + consultationId: consultationId, + dietId: dietId, + )).thenAnswer((_) async => {}); + + await repository.reattachPersonalDiet( + consultationId: consultationId, + dietId: dietId, + ); + + verify(mockService.reattachPersonalDiet( + consultationId: consultationId, + dietId: dietId, + )).called(1); + }); + }); +} + diff --git a/test/features/registration_v2/cubit/registration_cubit_v2_test.dart b/test/features/registration_v2/cubit/registration_cubit_v2_test.dart new file mode 100644 index 00000000..cec3aa9a --- /dev/null +++ b/test/features/registration_v2/cubit/registration_cubit_v2_test.dart @@ -0,0 +1,241 @@ +import 'package:dio/dio.dart'; +import 'package:eatvio_app/features/registration_v2/cubit/registration_cubit.dart'; +import 'package:eatvio_app/features/registration_v2/repositories/registration_repository_v2.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 'registration_cubit_v2_test.mocks.dart'; + +@GenerateMocks([RegistrationRepositoryV2]) +void main() { + TestWidgetsFlutterBinding.ensureInitialized(); + + group('RegistrationCubitV2', () { + late RegistrationCubitV2 cubit; + late MockRegistrationRepositoryV2 mockRepository; + const MethodChannel passwordGeneratorChannel = MethodChannel('flutter_password_generator'); + + setUp(() { + mockRepository = MockRegistrationRepositoryV2(); + cubit = RegistrationCubitV2(registrationRepository: mockRepository); + + // Инициализируем SharedPreferences для тестов + SharedPreferences.setMockInitialValues({}); + + // Мокируем MethodChannel для flutter_password_generator + TestDefaultBinaryMessengerBinding.instance.defaultBinaryMessenger.setMockMethodCallHandler( + passwordGeneratorChannel, + (MethodCall methodCall) async { + if (methodCall.method == 'generateSecureRandomData') { + // Возвращаем Uint8List для генерации пароля + // MethodChannel автоматически преобразует Uint8List в List + return Uint8List.fromList(List.generate(32, (i) => i % 256)); + } + return null; + }, + ); + }); + + tearDown(() { + cubit.close(); + // Очищаем моки + TestDefaultBinaryMessengerBinding.instance.defaultBinaryMessenger.setMockMethodCallHandler( + passwordGeneratorChannel, + null, + ); + }); + + test('начальное состояние', () { + expect(cubit.state.firstName, isEmpty); + expect(cubit.state.lastName, isEmpty); + expect(cubit.state.email, isEmpty); + expect(cubit.state.phone, isEmpty); + expect(cubit.state.gender, isEmpty); + expect(cubit.state.step, 0); + }); + + test('setFirstName - устанавливает имя и валидирует', () { + cubit.setFirstName('John'); + + expect(cubit.state.firstName, equals('John')); + expect(cubit.state.isFirstNameValid, isTrue); + }); + + test('setFirstName - валидация пустого имени', () { + cubit.setFirstName(''); + + expect(cubit.state.isFirstNameValid, isFalse); + }); + + test('setLastName - устанавливает фамилию и валидирует', () { + cubit.setLastName('Doe'); + + expect(cubit.state.lastName, equals('Doe')); + expect(cubit.state.isLastNameValid, isTrue); + }); + + test('setEmail - устанавливает email и валидирует', () { + cubit.setEmail('test@example.com'); + + expect(cubit.state.email, equals('test@example.com')); + expect(cubit.state.isEmailValid, isTrue); + }); + + test('setPhone - устанавливает телефон и валидирует', () { + cubit.setPhone('+1234567890'); + + expect(cubit.state.phone, equals('+1234567890')); + expect(cubit.state.isPhoneValid, isTrue); + }); + + test('setGender - устанавливает пол и валидирует', () { + cubit.setGender('male'); + + expect(cubit.state.gender, equals('male')); + expect(cubit.state.isGenderValid, isTrue); + }); + + test('setWeight - устанавливает вес и валидирует', () { + cubit.setWeight(70); + + expect(cubit.state.weight, equals(70)); + expect(cubit.state.isWeightValid, isTrue); + }); + + test('setWeight - валидация нулевого веса', () { + cubit.setWeight(0); + + expect(cubit.state.isWeightValid, isFalse); + }); + + test('setHeight - устанавливает рост и валидирует', () { + cubit.setHeight(175); + + expect(cubit.state.height, equals(175)); + expect(cubit.state.isHeightValid, isTrue); + }); + + test('setActivityCoefficient - устанавливает коэффициент активности', () { + cubit.setActivityCoefficient(1.5); + + expect(cubit.state.activityCoefficient, equals(1.5)); + expect(cubit.state.isActivityCoefficientValid, isTrue); + }); + + test('setBirthday - устанавливает дату рождения и валидирует', () { + final birthday = DateTime(1990, 1, 1); + cubit.setBirthday(birthday); + + expect(cubit.state.birthday, equals(birthday)); + expect(cubit.state.isBirthdayValid, isTrue); + }); + + test('nextStep - переходит на следующий шаг', () { + cubit.nextStep(); + + expect(cubit.state.step, equals(1)); + }); + + test('prevStep - возвращается на предыдущий шаг', () { + cubit.nextStep(); + cubit.prevStep(); + + expect(cubit.state.step, equals(0)); + }); + + test('validateAllFields - возвращает true, если все поля валидны', () { + cubit.setFirstName('John'); + cubit.setLastName('Doe'); + cubit.setEmail('test@example.com'); + cubit.setPhone('+1234567890'); + cubit.setGender('male'); + cubit.setWeight(70); + cubit.setHeight(175); + cubit.setActivityCoefficient(1.5); + cubit.setBirthday(DateTime(1990, 1, 1)); + + final result = cubit.validateAllFields(); + + expect(result, isTrue); + }); + + test('validateAllFields - возвращает false, если есть невалидные поля', () { + cubit.setFirstName(''); // Пустое имя + + final result = cubit.validateAllFields(); + + expect(result, isFalse); + }); + + test('register - успешная регистрация', () async { + cubit.setFirstName('John'); + cubit.setLastName('Doe'); + cubit.setEmail('test@example.com'); + cubit.setPhone('+1234567890'); + cubit.setGender('male'); + cubit.setWeight(70); + cubit.setHeight(175); + cubit.setActivityCoefficient(1.5); + cubit.setBirthday(DateTime(1990, 1, 1)); + + const token = 'test-token'; + when(mockRepository.register(registrationModel: anyNamed('registrationModel'))) + .thenAnswer((_) async => token); + + final result = await cubit.register(); + + expect(result.$1, isTrue); + expect(result.$2, isFalse); + verify(mockRepository.register(registrationModel: anyNamed('registrationModel'))).called(1); + }); + + test('register - возвращает false при невалидных полях', () async { + cubit.setFirstName(''); // Пустое имя + + final result = await cubit.register(); + + expect(result.$1, isFalse); + expect(result.$2, isTrue); + verifyNever(mockRepository.register(registrationModel: anyNamed('registrationModel'))); + }); + + test('register - обработка ошибки валидации email', () async { + cubit.setFirstName('John'); + cubit.setLastName('Doe'); + cubit.setEmail('test@example.com'); + cubit.setPhone('+1234567890'); + cubit.setGender('male'); + cubit.setWeight(70); + cubit.setHeight(175); + cubit.setActivityCoefficient(1.5); + cubit.setBirthday(DateTime(1990, 1, 1)); + + final dioException = DioException( + requestOptions: RequestOptions(path: '/'), + response: Response( + requestOptions: RequestOptions(path: '/'), + statusCode: 422, + data: { + 'errors': { + 'email': ['Некорректный формат email'], + }, + }, + ), + ); + + when(mockRepository.register(registrationModel: anyNamed('registrationModel'))) + .thenThrow(dioException); + + final result = await cubit.register(); + + expect(result.$1, isFalse); + expect(result.$2, isTrue); + expect(cubit.state.isEmailValid, isFalse); + expect(cubit.state.emailDisplayError, equals('Некорректный формат email')); + }); + }); +} + diff --git a/test/features/registration_v2/repositories/registration_repository_v2_test.dart b/test/features/registration_v2/repositories/registration_repository_v2_test.dart new file mode 100644 index 00000000..2d8f618b --- /dev/null +++ b/test/features/registration_v2/repositories/registration_repository_v2_test.dart @@ -0,0 +1,73 @@ +import 'package:eatvio_app/features/registration_v2/models/registration_model.dart'; +import 'package:eatvio_app/features/registration_v2/repositories/registration_repository_v2.dart'; +import 'package:eatvio_app/features/registration_v2/services/registration_service_v2.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:mockito/annotations.dart'; +import 'package:mockito/mockito.dart'; + +import 'registration_repository_v2_test.mocks.dart'; + +@GenerateMocks([RegistrationServiceV2]) +void main() { + group('RegistrationRepositoryV2', () { + late RegistrationRepositoryV2 repository; + late MockRegistrationServiceV2 mockService; + + setUp(() { + mockService = MockRegistrationServiceV2(); + repository = RegistrationRepositoryV2(registrationService: mockService); + }); + + test('register - успешная регистрация', () async { + final registrationModel = RegistrationModel( + firstName: 'John', + lastName: 'Doe', + email: 'test@example.com', + phone: '+1234567890', + gender: 'male', + password: 'password123', + confirmPassword: 'password123', + weight: 70, + height: 175, + activityCoefficient: 1.5, + birthday: '1990-01-01', + ); + + const token = 'test-token'; + when(mockService.register(model: anyNamed('model'))) + .thenAnswer((_) async => {'token': token}); + + final result = await repository.register(registrationModel: registrationModel); + + expect(result, equals(token)); + verify(mockService.register(model: anyNamed('model'))).called(1); + }); + + test('register - проброс ошибки', () async { + final registrationModel = RegistrationModel( + firstName: 'John', + lastName: 'Doe', + email: 'test@example.com', + phone: '+1234567890', + gender: 'male', + password: 'password123', + confirmPassword: 'password123', + weight: 70, + height: 175, + activityCoefficient: 1.5, + birthday: '1990-01-01', + ); + + final error = Exception('Ошибка регистрации'); + when(mockService.register(model: anyNamed('model'))) + .thenThrow(error); + + expect( + () => repository.register(registrationModel: registrationModel), + throwsA(error), + ); + verify(mockService.register(model: anyNamed('model'))).called(1); + }); + }); +} + diff --git a/test/features/user_details/presentation/cubit/user_details_cubit_test.dart b/test/features/user_details/presentation/cubit/user_details_cubit_test.dart new file mode 100644 index 00000000..e12fa8f2 --- /dev/null +++ b/test/features/user_details/presentation/cubit/user_details_cubit_test.dart @@ -0,0 +1,193 @@ +import 'package:eatvio_app/features/profile/models/circumference_model.dart'; +import 'package:eatvio_app/features/profile/models/profile_model.dart'; +import 'package:eatvio_app/features/profile/models/weight_model.dart'; +import 'package:eatvio_app/features/profile/repositories/profile_repository.dart'; +import 'package:eatvio_app/features/user_details/presentation/cubit/user_details_cubit.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:mockito/annotations.dart'; +import 'package:mockito/mockito.dart'; + +import 'user_details_cubit_test.mocks.dart'; + +@GenerateMocks([ProfileRepository]) +void main() { + group('UserDetailsCubit', () { + late UserDetailsCubit cubit; + late MockProfileRepository mockProfileRepository; + + setUp(() { + mockProfileRepository = MockProfileRepository(); + cubit = UserDetailsCubit(profileRepository: mockProfileRepository); + }); + + tearDown(() { + cubit.close(); + }); + + test('начальное состояние', () { + expect(cubit.state.isLoading, isFalse); + expect(cubit.state.error, isNull); + expect(cubit.state.profileDetails, isNull); + expect(cubit.state.weightPoints, isNull); + expect(cubit.state.circumferencePoints, isNull); + }); + + test('getUserDetails - успешная загрузка деталей пользователя', () async { + const slug = 'test-user'; + const profileDetails = ProfileDetailsModel( + firstName: 'Test', + lastName: 'User', + birthdate: '1990-01-01', + gender: 'male', + country: 'Russia', + city: 'Moscow', + lang: 'ru', + weight: 70.0, + height: 175, + chestCircumference: 100, + waistCircumference: 80, + hipCircumference: 90, + bloodPressureUp: 120, + bloodPressureDown: 80, + hasHypo: false, + hasHyper: false, + aboutAlergy: '', + aboutTakingPils: '', + aboutSportHobbies: '', + favoriteFoods: '', + unfavoriteFoods: '', + additionalInfo: '', + isMoodFillable: false, + isBedtimeFillable: false, + activityCoefficient: 1.5, + ); + + final weightPoints = [ + WeightModel( + id: 1, + userId: 1, + weight: 70, + createdAt: DateTime.now(), + updatedAt: DateTime.now(), + bmi: 22.9, + ), + ]; + + final circumferencePoints = [ + CircumferenceModel( + id: 1, + userId: 1, + chestCircumference: 100, + waistCircumference: 80, + hipCircumference: 90, + createdAt: DateTime.now(), + updatedAt: DateTime.now(), + ), + ]; + + when(mockProfileRepository.getProfileDetailsBySlug(slug)) + .thenAnswer((_) async => profileDetails); + + when(mockProfileRepository.getWeight(period: '1', slug: slug)) + .thenAnswer((_) async => weightPoints); + + when(mockProfileRepository.getCircumferences(period: '1', slug: slug)) + .thenAnswer((_) async => circumferencePoints); + + cubit.getUserDetails(slug); + + // Ждем завершения асинхронных операций + await Future.delayed(const Duration(milliseconds: 100)); + + expect(cubit.state.profileDetails, equals(profileDetails)); + expect(cubit.state.isLoading, isFalse); + verify(mockProfileRepository.getProfileDetailsBySlug(slug)).called(1); + }); + + test('getUserDetails - обработка ошибки', () async { + const slug = 'test-user'; + final error = Exception('Ошибка загрузки'); + + when(mockProfileRepository.getProfileDetailsBySlug(slug)) + .thenThrow(error); + + cubit.getUserDetails(slug); + + // Ждем завершения асинхронных операций + await Future.delayed(const Duration(milliseconds: 100)); + + expect(cubit.state.error, equals(error.toString())); + expect(cubit.state.isLoading, isFalse); + }); + + test('getChartData - успешная загрузка данных графика', () async { + const slug = 'test-user'; + const period = '1'; + + final weightPoints = [ + WeightModel( + id: 1, + userId: 1, + weight: 70, + createdAt: DateTime.now(), + updatedAt: DateTime.now(), + bmi: 22.9, + ), + WeightModel( + id: 2, + userId: 1, + weight: 71, + createdAt: DateTime.now().add(const Duration(days: 1)), + updatedAt: DateTime.now().add(const Duration(days: 1)), + bmi: 23.2, + ), + ]; + + final circumferencePoints = [ + CircumferenceModel( + id: 1, + userId: 1, + chestCircumference: 100, + waistCircumference: 80, + hipCircumference: 90, + createdAt: DateTime.now(), + updatedAt: DateTime.now(), + ), + ]; + + when(mockProfileRepository.getWeight(period: period, slug: slug)) + .thenAnswer((_) async => weightPoints); + + when(mockProfileRepository.getCircumferences(period: period, slug: slug)) + .thenAnswer((_) async => circumferencePoints); + + cubit.getChartData(period: period, slug: slug); + + // Ждем завершения асинхронных операций + await Future.delayed(const Duration(milliseconds: 100)); + + expect(cubit.state.weightPoints, equals(weightPoints)); + expect(cubit.state.circumferencePoints, equals(circumferencePoints)); + verify(mockProfileRepository.getWeight(period: period, slug: slug)).called(1); + verify(mockProfileRepository.getCircumferences(period: period, slug: slug)).called(1); + }); + + test('getChartData - обработка ошибки', () async { + const slug = 'test-user'; + const period = '1'; + final error = Exception('Ошибка загрузки'); + + when(mockProfileRepository.getWeight(period: period, slug: slug)) + .thenThrow(error); + + cubit.getChartData(period: period, slug: slug); + + // Ждем завершения асинхронных операций + await Future.delayed(const Duration(milliseconds: 100)); + + expect(cubit.state.error, equals(error.toString())); + expect(cubit.state.isLoading, isFalse); + }); + }); +} +