feat(test): comments, auth, feed

This commit is contained in:
mso7nueh
2025-11-28 14:13:05 +03:00
parent 7df32dabd2
commit 2a25be5aa4
12 changed files with 1774 additions and 1 deletions

View File

@@ -0,0 +1,223 @@
import 'package:eatvio_app/core/models/pagination_model.dart';
import 'package:eatvio_app/features/feed/models/diets_pagination_model.dart';
import 'package:eatvio_app/features/feed/models/feed_blog_post_model.dart';
import 'package:eatvio_app/features/feed/models/feed_blog_post_settings_model.dart';
import 'package:eatvio_app/features/feed/presentation/bloc/feed_cubit/feed_cubit.dart';
import 'package:eatvio_app/features/feed/presentation/bloc/feed_cubit/feed_state.dart';
import 'package:eatvio_app/features/feed/repositories/feed_repository.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:mockito/annotations.dart';
import 'package:mockito/mockito.dart';
import 'feed_cubit_test.mocks.dart';
@GenerateNiceMocks([MockSpec<FeedRepository>()])
void main() {
group('FeedCubit', () {
FeedCubit? feedCubit;
late MockFeedRepository mockFeedRepository;
late FeedBlogPostSettingsModel feedBlogPostsFixture;
setUp(() {
mockFeedRepository = MockFeedRepository();
// Создаём тестовые данные
final testPosts = [
FeedBlogPostModel(
id: 1,
state: 'published',
image: 'https://example.com/image1.jpg',
description: 'Описание поста 1',
author: const Author(
authorSlug: 'author-1',
authorName: 'Автор 1',
authorProfileIcon: 'https://example.com/photo1.jpg',
authorIsMe: false,
isEntitySubscribedByMe: null,
authorPlaceInRating: null,
),
videoUrl: '',
createdAt: DateTime.now(),
updatedAt: DateTime.now(),
title: 'Тестовый пост 1',
totalLikes: 10,
totalViews: 100,
totalComments: 5,
wasLikedByMe: false,
),
FeedBlogPostModel(
id: 2,
state: 'published',
image: 'https://example.com/image2.jpg',
description: 'Описание поста 2',
author: const Author(
authorSlug: 'author-2',
authorName: 'Автор 2',
authorProfileIcon: 'https://example.com/photo2.jpg',
authorIsMe: false,
isEntitySubscribedByMe: null,
authorPlaceInRating: null,
),
videoUrl: '',
createdAt: DateTime.now(),
updatedAt: DateTime.now(),
title: 'Тестовый пост 2',
totalLikes: 20,
totalViews: 200,
totalComments: 10,
wasLikedByMe: true,
),
];
feedBlogPostsFixture = FeedBlogPostSettingsModel(
feedBlogPostList: testPosts,
meta: const Meta(
currentPage: 1,
from: null,
lastPage: 3,
links: [],
path: null,
perPage: 10,
to: null,
total: 25,
totalMinPrice: null,
totalMaxPrice: null,
),
);
});
tearDown(() async {
if (feedCubit != null && !feedCubit!.isClosed) {
await Future.delayed(const Duration(milliseconds: 100));
feedCubit!.close();
feedCubit = null;
}
});
test('начальное состояние - FeedStateLoading', () {
feedCubit = FeedCubit(feedRepository: mockFeedRepository);
expect(feedCubit!.state, const FeedState.loading());
});
test('loadFeedBlogPostList - успешная загрузка постов', () async {
feedCubit = FeedCubit(feedRepository: mockFeedRepository);
when(mockFeedRepository.getFeedBlogPosts(query: null))
.thenAnswer((_) async => feedBlogPostsFixture);
await feedCubit!.loadFeedBlogPostList();
await Future.delayed(const Duration(milliseconds: 100));
expect(feedCubit!.state, isA<FeedStateLoaded>());
final loadedState = feedCubit!.state as FeedStateLoaded;
expect(loadedState.feedBlogPostList.length, 2);
expect(loadedState.isLoading, false);
expect(feedCubit!.lastPage, 3);
});
test('loadFeedBlogPostList - загрузка с запросом', () async {
feedCubit = FeedCubit(feedRepository: mockFeedRepository);
const query = 'тест';
when(mockFeedRepository.getFeedBlogPosts(query: query))
.thenAnswer((_) async => feedBlogPostsFixture);
await feedCubit!.loadFeedBlogPostList(query: query);
await Future.delayed(const Duration(milliseconds: 100));
expect(feedCubit!.state, isA<FeedStateLoaded>());
verify(mockFeedRepository.getFeedBlogPosts(query: query)).called(1);
});
test('loadFeedBlogPostList - обработка ошибки', () async {
feedCubit = FeedCubit(feedRepository: mockFeedRepository);
when(mockFeedRepository.getFeedBlogPosts(query: anyNamed('query')))
.thenThrow(Exception('Ошибка загрузки'));
await feedCubit!.loadFeedBlogPostList();
await Future.delayed(const Duration(milliseconds: 100));
expect(feedCubit!.state, isA<FeedStateError>());
final errorState = feedCubit!.state as FeedStateError;
expect(errorState.errorMessage, contains('Exception'));
});
test('updateFeedBlogPostList - обновление лайка поста', () {
feedCubit = FeedCubit(feedRepository: mockFeedRepository);
// Сначала загружаем посты
when(mockFeedRepository.getFeedBlogPosts(query: anyNamed('query')))
.thenAnswer((_) async => feedBlogPostsFixture);
// Устанавливаем состояние loaded вручную для теста
feedCubit!.emit(FeedState.loaded(
feedBlogPostList: feedBlogPostsFixture.feedBlogPostList,
isLoading: false,
));
feedCubit!.lastPage = 3;
final postToUpdate = feedBlogPostsFixture.feedBlogPostList.first;
feedCubit!.updateFeedBlogPostList(postToUpdate, true);
expect(feedCubit!.state, isA<FeedStateLoaded>());
final loadedState = feedCubit!.state as FeedStateLoaded;
final updatedPost = loadedState.feedBlogPostList.firstWhere((p) => p.id == postToUpdate.id);
expect(updatedPost.wasLikedByMe, true);
expect(updatedPost.totalLikes, postToUpdate.totalLikes + 1);
});
test('updateFeedBlogPostList - снятие лайка', () {
feedCubit = FeedCubit(feedRepository: mockFeedRepository);
feedCubit!.emit(FeedState.loaded(
feedBlogPostList: feedBlogPostsFixture.feedBlogPostList,
isLoading: false,
));
feedCubit!.lastPage = 3;
final postToUpdate = feedBlogPostsFixture.feedBlogPostList[1]; // У этого wasLikedByMe = true
final initialLikes = postToUpdate.totalLikes;
feedCubit!.updateFeedBlogPostList(postToUpdate, false);
expect(feedCubit!.state, isA<FeedStateLoaded>());
final loadedState = feedCubit!.state as FeedStateLoaded;
final updatedPost = loadedState.feedBlogPostList.firstWhere((p) => p.id == postToUpdate.id);
expect(updatedPost.wasLikedByMe, false);
expect(updatedPost.totalLikes, initialLikes - 1);
});
test('updateFeedBlogPostList - игнорирует обновление если состояние не loaded', () {
feedCubit = FeedCubit(feedRepository: mockFeedRepository);
// Состояние loading
feedCubit!.emit(const FeedState.loading());
final postToUpdate = feedBlogPostsFixture.feedBlogPostList.first;
feedCubit!.updateFeedBlogPostList(postToUpdate, true);
// Состояние не должно измениться
expect(feedCubit!.state, const FeedState.loading());
});
test('updateFeedBlogPostList - игнорирует обновление если пост не найден', () {
feedCubit = FeedCubit(feedRepository: mockFeedRepository);
feedCubit!.emit(FeedState.loaded(
feedBlogPostList: feedBlogPostsFixture.feedBlogPostList,
isLoading: false,
));
feedCubit!.lastPage = 3;
// Создаём пост с несуществующим ID
final nonExistentPost = feedBlogPostsFixture.feedBlogPostList.first.copyWith(id: 999);
feedCubit!.updateFeedBlogPostList(nonExistentPost, true);
// Список не должен измениться
final loadedState = feedCubit!.state as FeedStateLoaded;
expect(loadedState.feedBlogPostList.length, 2);
});
});
}

View File

@@ -0,0 +1,344 @@
import 'package:eatvio_app/core/models/pagination_model.dart';
import 'package:eatvio_app/features/feed/models/comment_model.dart';
import 'package:eatvio_app/features/feed/models/diets_pagination_model.dart';
import 'package:eatvio_app/features/feed/models/feed_blog_post_model.dart';
import 'package:eatvio_app/features/feed/models/feed_blog_post_settings_model.dart';
import 'package:eatvio_app/features/feed/models/feed_consultant_model.dart';
import 'package:eatvio_app/features/feed/models/plan_pagination_model.dart';
import 'package:eatvio_app/features/feed/models/recipes_model.dart';
import 'package:eatvio_app/features/feed/models/recipes_pagination_model.dart';
import 'package:eatvio_app/features/feed/models/view_model.dart';
import 'package:eatvio_app/features/feed/repositories/feed_repository.dart';
import 'package:eatvio_app/features/feed/services/feed_service.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:mockito/annotations.dart';
import 'package:mockito/mockito.dart';
import 'feed_repository_test.mocks.dart';
@GenerateNiceMocks([MockSpec<FeedService>()])
void main() {
group('FeedRepository', () {
late FeedRepository feedRepository;
late MockFeedService mockFeedService;
setUp(() {
mockFeedService = MockFeedService();
feedRepository = FeedRepository(feedService: mockFeedService);
});
test('getFeedBlogPosts - успешное получение постов', () async {
final expectedModel = FeedBlogPostSettingsModel(
feedBlogPostList: const [],
meta: const Meta(
currentPage: 1,
from: null,
lastPage: 1,
links: [],
path: null,
perPage: 10,
to: null,
total: 0,
totalMinPrice: null,
totalMaxPrice: null,
),
);
when(mockFeedService.getBlogPostList(page: 1, query: null))
.thenAnswer((_) async => expectedModel);
final result = await feedRepository.getFeedBlogPosts();
expect(result, isA<FeedBlogPostSettingsModel>());
verify(mockFeedService.getBlogPostList(page: 1, query: null)).called(1);
});
test('getFeedBlogPosts - с запросом и страницей', () async {
const query = 'тест';
const page = 2;
final expectedModel = FeedBlogPostSettingsModel(
feedBlogPostList: const [],
meta: const Meta(
currentPage: page,
from: null,
lastPage: 2,
links: [],
path: null,
perPage: 10,
to: null,
total: 15,
totalMinPrice: null,
totalMaxPrice: null,
),
);
when(mockFeedService.getBlogPostList(page: page, query: query))
.thenAnswer((_) async => expectedModel);
final result = await feedRepository.getFeedBlogPosts(query: query, page: page);
expect(result, isA<FeedBlogPostSettingsModel>());
verify(mockFeedService.getBlogPostList(page: page, query: query)).called(1);
});
test('getBlogPost - успешное получение поста', () async {
const postId = 1;
final expectedPost = FeedBlogPostModel(
id: postId,
state: 'published',
image: 'https://example.com/image.jpg',
description: 'Описание',
author: const Author(
authorSlug: 'author-slug',
authorName: 'Автор',
authorProfileIcon: 'https://example.com/photo.jpg',
authorIsMe: false,
isEntitySubscribedByMe: null,
authorPlaceInRating: null,
),
videoUrl: '',
createdAt: DateTime.now(),
updatedAt: DateTime.now(),
title: 'Заголовок',
totalLikes: 10,
totalViews: 100,
totalComments: 5,
wasLikedByMe: false,
);
when(mockFeedService.getBlogPost(postId: postId))
.thenAnswer((_) async => expectedPost);
final result = await feedRepository.getBlogPost(id: postId);
expect(result, isA<FeedBlogPostModel>());
expect(result.id, postId);
verify(mockFeedService.getBlogPost(postId: postId)).called(1);
});
test('getRecipes - успешное получение рецептов', () async {
final expectedRecipes = const RecipesPagination(
recipesList: [],
);
when(mockFeedService.getRecipes(query: null, tags: null, page: 1))
.thenAnswer((_) async => expectedRecipes);
final result = await feedRepository.getRecipes();
expect(result, isA<List<Recipe>>());
verify(mockFeedService.getRecipes(query: null, tags: null, page: 1)).called(1);
});
test('getDiets - успешное получение диет', () async {
final expectedDiets = DietsPagination(
data: const [],
links: const Links(first: '', last: '', prev: null, next: null),
meta: const Meta(
currentPage: 1,
from: null,
lastPage: 1,
links: [],
path: null,
perPage: 10,
to: null,
total: 0,
totalMinPrice: null,
totalMaxPrice: null,
),
);
when(mockFeedService.getDiets(
page: 1,
query: null,
types: null,
duration: null,
sorting: null,
)).thenAnswer((_) async => expectedDiets);
final result = await feedRepository.getDiets();
expect(result, isA<DietsPagination>());
verify(mockFeedService.getDiets(
page: 1,
query: null,
types: null,
duration: null,
sorting: null,
)).called(1);
});
test('getPlans - успешное получение планов', () async {
final expectedPlans = PlanPaginationModel(
data: const [],
links: const Links(first: '', last: '', prev: null, next: null),
meta: const Meta(
currentPage: 1,
from: null,
lastPage: 1,
links: [],
path: null,
perPage: 10,
to: null,
total: 0,
totalMinPrice: null,
totalMaxPrice: null,
),
);
when(mockFeedService.getPlans(
page: 1,
query: null,
types: null,
duration: null,
minPrice: null,
maxPrice: null,
)).thenAnswer((_) async => expectedPlans);
final result = await feedRepository.getPlans();
expect(result, isA<PlanPaginationModel>());
verify(mockFeedService.getPlans(
page: 1,
query: null,
types: null,
duration: null,
minPrice: null,
maxPrice: null,
)).called(1);
});
test('getBlogPostComments - успешное получение комментариев', () async {
const postId = '1';
final expectedComments = CommentModel(
data: [],
links: const Links(first: '', last: '', prev: null, next: null),
meta: const Meta(
currentPage: 1,
from: null,
lastPage: 1,
links: [],
path: null,
perPage: 10,
to: null,
total: 0,
totalMinPrice: null,
totalMaxPrice: null,
),
);
when(mockFeedService.getBlogPostComments(id: postId))
.thenAnswer((_) async => expectedComments);
final result = await feedRepository.getBlogPostComments(id: postId);
expect(result, isA<CommentModel>());
verify(mockFeedService.getBlogPostComments(id: postId)).called(1);
});
test('sendBlogPostComment - успешная отправка комментария', () async {
const postId = '1';
const commentText = 'Тестовый комментарий';
when(mockFeedService.sendBlogPostComment(
id: postId,
body: commentText,
parentCommentId: null,
)).thenAnswer((_) async => Future.value());
await feedRepository.sendBlogPostComment(id: postId, commentText: commentText);
verify(mockFeedService.sendBlogPostComment(
id: postId,
body: commentText,
parentCommentId: null,
)).called(1);
});
test('likeBlogPost - успешный лайк поста', () async {
const postId = 1;
when(mockFeedService.likeBlogPost(id: postId))
.thenAnswer((_) async => Future.value());
await feedRepository.likeBlogPost(id: postId);
verify(mockFeedService.likeBlogPost(id: postId)).called(1);
});
test('unlikedBlogPost - успешное снятие лайка', () async {
const postId = 1;
when(mockFeedService.unlikedBlogPost(id: postId))
.thenAnswer((_) async => Future.value());
await feedRepository.unlikedBlogPost(id: postId);
verify(mockFeedService.unlikedBlogPost(id: postId)).called(1);
});
test('getConsultants - успешное получение консультантов', () async {
final expectedConsultants = FeedConsultantPaginationModel(
data: const [],
links: const Links(first: '', last: '', prev: null, next: null),
meta: const Meta(
currentPage: 1,
from: null,
lastPage: 1,
links: [],
path: null,
perPage: 10,
to: null,
total: 0,
totalMinPrice: null,
totalMaxPrice: null,
),
);
when(mockFeedService.getConsultants(name: null))
.thenAnswer((_) async => expectedConsultants);
final result = await feedRepository.getConsultants();
expect(result, isA<List<FeedConsultantModel>>());
verify(mockFeedService.getConsultants(name: null)).called(1);
});
test('viewPost - успешное просмотр поста', () async {
const postId = 1;
when(mockFeedService.viewPost(id: postId))
.thenAnswer((_) async => Future.value());
await feedRepository.viewPost(postId);
verify(mockFeedService.viewPost(id: postId)).called(1);
});
test('viewContent - успешный просмотр контента', () async {
final viewModel = ViewModel(
type: 'blog_post',
ids: [1],
slugs: null,
);
when(mockFeedService.viewContent(viewModel: viewModel))
.thenAnswer((_) async => Future.value());
await feedRepository.viewContent(viewModel: viewModel);
verify(mockFeedService.viewContent(viewModel: viewModel)).called(1);
});
test('getFeedBlogPosts - пробрасывает исключение', () async {
when(mockFeedService.getBlogPostList(page: 1, query: null))
.thenThrow(Exception('Ошибка сети'));
expect(
() => feedRepository.getFeedBlogPosts(),
throwsException,
);
});
});
}