forked from eatvio/eatvio_app
feat(test): comments, auth, feed
This commit is contained in:
@@ -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);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
344
test/features/feed/repositories/feed_repository_test.dart
Normal file
344
test/features/feed/repositories/feed_repository_test.dart
Normal 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,
|
||||
);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user