feat(test): repostory, cubit and widget tests for feed

This commit is contained in:
misha
2025-11-25 11:13:37 +03:00
parent b83d91e7f2
commit c6c42b0525
5 changed files with 15123 additions and 11671 deletions

View File

@@ -0,0 +1,404 @@
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;
final testBlogPost = FeedBlogPostModel(
id: 1,
title: 'Test Post',
description: 'Test description',
image: 'https://example.com/image.jpg',
state: 'published',
author: const Author(authorSlug: 'author-1', authorName: 'Author Name', authorProfileIcon: 'https://example.com/icon.jpg', authorIsMe: false),
videoUrl: 'https://example.com/video.mp4',
createdAt: DateTime(2024, 1, 15),
updatedAt: DateTime(2024, 1, 15),
totalLikes: 10,
totalViews: 100,
totalComments: 5,
wasLikedByMe: false,
);
final testBlogPost2 = FeedBlogPostModel(
id: 2,
title: 'Test Post 2',
description: 'Test description 2',
image: 'https://example.com/image2.jpg',
state: 'published',
author: const Author(authorSlug: 'author-2', authorName: 'Author Name 2', authorProfileIcon: 'https://example.com/icon2.jpg', authorIsMe: false),
videoUrl: 'https://example.com/video2.mp4',
createdAt: DateTime(2024, 1, 16),
updatedAt: DateTime(2024, 1, 16),
totalLikes: 20,
totalViews: 200,
totalComments: 10,
wasLikedByMe: true,
);
setUp(() {
mockFeedRepository = MockFeedRepository();
});
tearDown(() async {
if (feedCubit != null && !feedCubit!.isClosed) {
await Future.delayed(const Duration(milliseconds: 100));
feedCubit!.close();
feedCubit = null;
}
});
test('начальное состояние - loading', () {
feedCubit = FeedCubit(feedRepository: mockFeedRepository);
expect(feedCubit!.state, const FeedState.loading());
});
test('loadFeedBlogPostList - успешно загружает список постов', () async {
feedCubit = FeedCubit(feedRepository: mockFeedRepository);
final expectedSettings = FeedBlogPostSettingsModel(
feedBlogPostList: [testBlogPost],
meta: const Meta(currentPage: 1, lastPage: 3, total: 3, perPage: 10, from: null, links: [Link(url: null, label: null, active: null)], to: null),
);
when(mockFeedRepository.getFeedBlogPosts(query: null)).thenAnswer((_) async => expectedSettings);
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, 1);
expect(loadedState.feedBlogPostList.first.id, 1);
expect(loadedState.isLoading, false);
expect(feedCubit!.lastPage, 3);
verify(mockFeedRepository.getFeedBlogPosts(query: null)).called(1);
});
test('loadFeedBlogPostList - загружает список постов с query', () async {
feedCubit = FeedCubit(feedRepository: mockFeedRepository);
const query = 'test query';
final expectedSettings = FeedBlogPostSettingsModel(
feedBlogPostList: [testBlogPost, testBlogPost2],
meta: const Meta(currentPage: 1, lastPage: 1, total: 2, perPage: 10, from: null, links: [Link(url: null, label: null, active: null)], to: null),
);
when(mockFeedRepository.getFeedBlogPosts(query: query)).thenAnswer((_) async => expectedSettings);
await feedCubit!.loadFeedBlogPostList(query: query);
await Future.delayed(const Duration(milliseconds: 100));
expect(feedCubit!.state, isA<FeedStateLoaded>());
final loadedState = feedCubit!.state as FeedStateLoaded;
expect(loadedState.feedBlogPostList.length, 2);
verify(mockFeedRepository.getFeedBlogPosts(query: query)).called(1);
});
test('loadFeedBlogPostList - обрабатывает ошибку', () async {
feedCubit = FeedCubit(feedRepository: mockFeedRepository);
final exception = Exception('Network error');
when(mockFeedRepository.getFeedBlogPosts(query: null)).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: Network error'));
});
test('loadNextPage - загружает следующую страницу', () async {
feedCubit = FeedCubit(feedRepository: mockFeedRepository);
final firstPageSettings = FeedBlogPostSettingsModel(
feedBlogPostList: [testBlogPost],
meta: const Meta(currentPage: 1, lastPage: 3, total: 3, perPage: 10, from: null, links: [Link(url: null, label: null, active: null)], to: null),
);
final secondPageSettings = FeedBlogPostSettingsModel(
feedBlogPostList: [testBlogPost2],
meta: const Meta(currentPage: 2, lastPage: 3, total: 3, perPage: 10, from: null, links: [Link(url: null, label: null, active: null)], to: null),
);
when(mockFeedRepository.getFeedBlogPosts(query: null)).thenAnswer((_) async => firstPageSettings);
await feedCubit!.loadFeedBlogPostList();
await Future.delayed(const Duration(milliseconds: 100));
when(mockFeedRepository.getFeedBlogPosts(page: 2, query: null)).thenAnswer((_) async => secondPageSettings);
await feedCubit!.loadNextPage();
await Future.delayed(const Duration(milliseconds: 100));
expect(feedCubit!.state, isA<FeedStateLoaded>());
final loadedState = feedCubit!.state as FeedStateLoaded;
expect(loadedState.feedBlogPostList.length, 2);
expect(feedCubit!.currentPage, 2);
expect(loadedState.isLoading, false);
verify(mockFeedRepository.getFeedBlogPosts(page: 2, query: null)).called(1);
});
test('loadNextPage - не загружает если уже на последней странице', () async {
feedCubit = FeedCubit(feedRepository: mockFeedRepository);
final settings = FeedBlogPostSettingsModel(
feedBlogPostList: [testBlogPost],
meta: const Meta(currentPage: 1, lastPage: 1, total: 1, perPage: 10, from: null, links: [Link(url: null, label: null, active: null)], to: null),
);
when(mockFeedRepository.getFeedBlogPosts(query: null)).thenAnswer((_) async => settings);
await feedCubit!.loadFeedBlogPostList();
await Future.delayed(const Duration(milliseconds: 100));
await feedCubit!.loadNextPage();
await Future.delayed(const Duration(milliseconds: 100));
verifyNever(mockFeedRepository.getFeedBlogPosts(page: 2, query: anyNamed('query')));
});
test('loadNextPage - не загружает если уже загружается', () async {
feedCubit = FeedCubit(feedRepository: mockFeedRepository);
final settings = FeedBlogPostSettingsModel(
feedBlogPostList: [testBlogPost],
meta: const Meta(currentPage: 1, lastPage: 3, total: 3, perPage: 10, from: null, links: [Link(url: null, label: null, active: null)], to: null),
);
when(mockFeedRepository.getFeedBlogPosts(query: null)).thenAnswer((_) async => settings);
await feedCubit!.loadFeedBlogPostList();
await Future.delayed(const Duration(milliseconds: 100));
feedCubit!.isLoading = true;
await feedCubit!.loadNextPage();
await Future.delayed(const Duration(milliseconds: 100));
verifyNever(mockFeedRepository.getFeedBlogPosts(page: 2, query: anyNamed('query')));
});
test('loadNextPage - обрабатывает ошибку при загрузке следующей страницы', () async {
feedCubit = FeedCubit(feedRepository: mockFeedRepository);
final firstPageSettings = FeedBlogPostSettingsModel(
feedBlogPostList: [testBlogPost],
meta: const Meta(currentPage: 1, lastPage: 3, total: 3, perPage: 10, from: null, links: [Link(url: null, label: null, active: null)], to: null),
);
when(mockFeedRepository.getFeedBlogPosts(query: null)).thenAnswer((_) async => firstPageSettings);
await feedCubit!.loadFeedBlogPostList();
await Future.delayed(const Duration(milliseconds: 100));
final exception = Exception('Network error');
when(mockFeedRepository.getFeedBlogPosts(page: 2, query: null)).thenThrow(exception);
await feedCubit!.loadNextPage();
await Future.delayed(const Duration(milliseconds: 100));
expect(feedCubit!.state, isA<FeedStateError>());
final errorState = feedCubit!.state as FeedStateError;
expect(errorState.errorMessage, contains('Exception: Network error'));
});
test('loadNextPage - сохраняет query при загрузке следующей страницы', () async {
feedCubit = FeedCubit(feedRepository: mockFeedRepository);
const query = 'test query';
final firstPageSettings = FeedBlogPostSettingsModel(
feedBlogPostList: [testBlogPost],
meta: const Meta(currentPage: 1, lastPage: 3, total: 3, perPage: 10, from: null, links: [Link(url: null, label: null, active: null)], to: null),
);
final secondPageSettings = FeedBlogPostSettingsModel(
feedBlogPostList: [testBlogPost2],
meta: const Meta(currentPage: 2, lastPage: 3, total: 3, perPage: 10, from: null, links: [Link(url: null, label: null, active: null)], to: null),
);
when(mockFeedRepository.getFeedBlogPosts(query: query)).thenAnswer((_) async => firstPageSettings);
await feedCubit!.loadFeedBlogPostList(query: query);
await Future.delayed(const Duration(milliseconds: 100));
when(mockFeedRepository.getFeedBlogPosts(page: 2, query: query)).thenAnswer((_) async => secondPageSettings);
await feedCubit!.loadNextPage();
await Future.delayed(const Duration(milliseconds: 100));
verify(mockFeedRepository.getFeedBlogPosts(page: 2, query: query)).called(1);
});
test('updateFeedBlogPostList - обновляет лайк поста', () async {
feedCubit = FeedCubit(feedRepository: mockFeedRepository);
final settings = FeedBlogPostSettingsModel(
feedBlogPostList: [testBlogPost],
meta: const Meta(currentPage: 1, lastPage: 1, total: 1, perPage: 10, from: null, links: [Link(url: null, label: null, active: null)], to: null),
);
when(mockFeedRepository.getFeedBlogPosts(query: null)).thenAnswer((_) async => settings);
await feedCubit!.loadFeedBlogPostList();
await Future.delayed(const Duration(milliseconds: 100));
feedCubit!.updateFeedBlogPostList(testBlogPost, true);
await Future.delayed(const Duration(milliseconds: 100));
expect(feedCubit!.state, isA<FeedStateLoaded>());
final loadedState = feedCubit!.state as FeedStateLoaded;
expect(loadedState.feedBlogPostList.first.wasLikedByMe, true);
expect(loadedState.feedBlogPostList.first.totalLikes, 11);
});
test('updateFeedBlogPostList - убирает лайк поста', () async {
feedCubit = FeedCubit(feedRepository: mockFeedRepository);
final settings = FeedBlogPostSettingsModel(
feedBlogPostList: [testBlogPost2],
meta: const Meta(currentPage: 1, lastPage: 1, total: 1, perPage: 10, from: null, links: [Link(url: null, label: null, active: null)], to: null),
);
when(mockFeedRepository.getFeedBlogPosts(query: null)).thenAnswer((_) async => settings);
await feedCubit!.loadFeedBlogPostList();
await Future.delayed(const Duration(milliseconds: 100));
feedCubit!.updateFeedBlogPostList(testBlogPost2, false);
await Future.delayed(const Duration(milliseconds: 100));
expect(feedCubit!.state, isA<FeedStateLoaded>());
final loadedState = feedCubit!.state as FeedStateLoaded;
expect(loadedState.feedBlogPostList.first.wasLikedByMe, false);
expect(loadedState.feedBlogPostList.first.totalLikes, 19);
});
test('updateFeedBlogPostList - не обновляет если пост не найден', () async {
feedCubit = FeedCubit(feedRepository: mockFeedRepository);
final settings = FeedBlogPostSettingsModel(
feedBlogPostList: [testBlogPost],
meta: const Meta(currentPage: 1, lastPage: 1, total: 1, perPage: 10, from: null, links: [Link(url: null, label: null, active: null)], to: null),
);
when(mockFeedRepository.getFeedBlogPosts(query: null)).thenAnswer((_) async => settings);
await feedCubit!.loadFeedBlogPostList();
await Future.delayed(const Duration(milliseconds: 100));
final nonExistentPost = testBlogPost.copyWith(id: 999);
final initialState = feedCubit!.state;
feedCubit!.updateFeedBlogPostList(nonExistentPost, true);
await Future.delayed(const Duration(milliseconds: 100));
expect(feedCubit!.state, initialState);
});
test('updateFeedBlogPostList - не обновляет если state не loaded', () {
feedCubit = FeedCubit(feedRepository: mockFeedRepository);
feedCubit!.updateFeedBlogPostList(testBlogPost, true);
expect(feedCubit!.state, const FeedState.loading());
});
test('updateFeedBlogPostList - не обновляет если post null', () async {
feedCubit = FeedCubit(feedRepository: mockFeedRepository);
final settings = FeedBlogPostSettingsModel(
feedBlogPostList: [testBlogPost],
meta: const Meta(currentPage: 1, lastPage: 1, total: 1, perPage: 10, from: null, links: [Link(url: null, label: null, active: null)], to: null),
);
when(mockFeedRepository.getFeedBlogPosts(query: null)).thenAnswer((_) async => settings);
await feedCubit!.loadFeedBlogPostList();
await Future.delayed(const Duration(milliseconds: 100));
final initialState = feedCubit!.state;
feedCubit!.updateFeedBlogPostList(null, true);
await Future.delayed(const Duration(milliseconds: 100));
expect(feedCubit!.state, initialState);
});
test('likePost - лайкает пост', () async {
feedCubit = FeedCubit(feedRepository: mockFeedRepository);
const postId = 1;
when(mockFeedRepository.likeBlogPost(id: postId)).thenAnswer((_) async => Future.value());
await feedCubit!.likePost(postId);
verify(mockFeedRepository.likeBlogPost(id: postId)).called(1);
});
test('likePost - обрабатывает ошибку без выбрасывания', () async {
feedCubit = FeedCubit(feedRepository: mockFeedRepository);
const postId = 1;
final exception = Exception('Network error');
when(mockFeedRepository.likeBlogPost(id: postId)).thenThrow(exception);
await feedCubit!.likePost(postId);
verify(mockFeedRepository.likeBlogPost(id: postId)).called(1);
});
test('unlikedPost - убирает лайк с поста', () async {
feedCubit = FeedCubit(feedRepository: mockFeedRepository);
const postId = 1;
when(mockFeedRepository.unlikedBlogPost(id: postId)).thenAnswer((_) async => Future.value());
await feedCubit!.unlikedPost(postId);
verify(mockFeedRepository.unlikedBlogPost(id: postId)).called(1);
});
test('viewPost - отправляет просмотр поста', () async {
feedCubit = FeedCubit(feedRepository: mockFeedRepository);
const postId = 1;
when(mockFeedRepository.viewPost(postId)).thenAnswer((_) async => Future.value());
await feedCubit!.viewPost(postId);
verify(mockFeedRepository.viewPost(postId)).called(1);
});
test('viewRecipe - отправляет просмотр рецепта', () async {
feedCubit = FeedCubit(feedRepository: mockFeedRepository);
const slug = 'recipe-1';
when(mockFeedRepository.viewRecipe(slug)).thenAnswer((_) async => Future.value());
await feedCubit!.viewRecipe(slug);
verify(mockFeedRepository.viewRecipe(slug)).called(1);
});
test('viewDiet - отправляет просмотр диеты', () async {
feedCubit = FeedCubit(feedRepository: mockFeedRepository);
const dietId = 2;
when(mockFeedRepository.viewDiet(dietId)).thenAnswer((_) async => Future.value());
await feedCubit!.viewDiet(dietId);
verify(mockFeedRepository.viewDiet(dietId)).called(1);
});
test('viewPlan - отправляет просмотр плана', () async {
feedCubit = FeedCubit(feedRepository: mockFeedRepository);
const slug = 'plan-1';
when(mockFeedRepository.viewPlan(slug)).thenAnswer((_) async => Future.value());
await feedCubit!.viewPlan(slug);
verify(mockFeedRepository.viewPlan(slug)).called(1);
});
});
}