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,419 @@
import 'package:eatvio_app/core/comments/comments_cubit/comments_cubit.dart';
import 'package:eatvio_app/core/comments/comments_cubit/comments_state.dart';
import 'package:eatvio_app/core/comments/repositories/comments_repository.dart';
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/presentation/widgets/comments_widget.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 'comments_cubit_test.mocks.dart';
@GenerateNiceMocks([
MockSpec<FeedRepository>(),
MockSpec<CommentsRepository>(),
])
void main() {
group('CommentsCubit', () {
CommentsCubit? commentsCubit;
late MockFeedRepository mockFeedRepository;
late MockCommentsRepository mockCommentsRepository;
late CommentModel testCommentModel;
setUp(() {
mockFeedRepository = MockFeedRepository();
mockCommentsRepository = MockCommentsRepository();
testCommentModel = 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,
),
);
});
tearDown(() async {
if (commentsCubit != null && !commentsCubit!.isClosed) {
await Future.delayed(const Duration(milliseconds: 100));
commentsCubit!.close();
commentsCubit = null;
}
});
test('начальное состояние - CommentsState', () {
commentsCubit = CommentsCubit(
feedRepository: mockFeedRepository,
commentsRepository: mockCommentsRepository,
);
expect(commentsCubit!.state, const CommentsState());
});
test('getComments - успешная загрузка комментариев для blogPost', () async {
commentsCubit = CommentsCubit(
feedRepository: mockFeedRepository,
commentsRepository: mockCommentsRepository,
);
const postId = '1';
when(mockFeedRepository.getBlogPostComments(id: postId))
.thenAnswer((_) async => testCommentModel);
await commentsCubit!.getComments(postId, CommentSourceType.blogPost, true);
await Future.delayed(const Duration(milliseconds: 100));
expect(commentsCubit!.state, isA<CommentsStateLoaded>());
final loadedState = commentsCubit!.state as CommentsStateLoaded;
expect(loadedState.comment, testCommentModel);
verify(mockFeedRepository.getBlogPostComments(id: postId)).called(1);
});
test('getComments - успешная загрузка комментариев для diet', () async {
commentsCubit = CommentsCubit(
feedRepository: mockFeedRepository,
commentsRepository: mockCommentsRepository,
);
const dietId = '1';
when(mockFeedRepository.getDietsComments(id: dietId))
.thenAnswer((_) async => testCommentModel);
await commentsCubit!.getComments(dietId, CommentSourceType.diet, true);
await Future.delayed(const Duration(milliseconds: 100));
expect(commentsCubit!.state, isA<CommentsStateLoaded>());
verify(mockFeedRepository.getDietsComments(id: dietId)).called(1);
});
test('getComments - успешная загрузка комментариев для recipe', () async {
commentsCubit = CommentsCubit(
feedRepository: mockFeedRepository,
commentsRepository: mockCommentsRepository,
);
const recipeId = 'recipe-slug';
when(mockFeedRepository.getRecipesComments(id: recipeId))
.thenAnswer((_) async => testCommentModel);
await commentsCubit!.getComments(recipeId, CommentSourceType.recipe, true);
await Future.delayed(const Duration(milliseconds: 100));
expect(commentsCubit!.state, isA<CommentsStateLoaded>());
verify(mockFeedRepository.getRecipesComments(id: recipeId)).called(1);
});
test('getComments - успешная загрузка комментариев для plan', () async {
commentsCubit = CommentsCubit(
feedRepository: mockFeedRepository,
commentsRepository: mockCommentsRepository,
);
const planSlug = 'plan-slug';
when(mockFeedRepository.getPlansComments(slug: planSlug))
.thenAnswer((_) async => testCommentModel);
await commentsCubit!.getComments(planSlug, CommentSourceType.plan, true);
await Future.delayed(const Duration(milliseconds: 100));
expect(commentsCubit!.state, isA<CommentsStateLoaded>());
verify(mockFeedRepository.getPlansComments(slug: planSlug)).called(1);
});
test('getComments - переход в состояние loading при первом запросе', () async {
commentsCubit = CommentsCubit(
feedRepository: mockFeedRepository,
commentsRepository: mockCommentsRepository,
);
const postId = '1';
when(mockFeedRepository.getBlogPostComments(id: postId))
.thenAnswer((_) async {
await Future.delayed(const Duration(milliseconds: 50));
return testCommentModel;
});
final future = commentsCubit!.getComments(postId, CommentSourceType.blogPost, true);
await Future.delayed(const Duration(milliseconds: 10));
expect(commentsCubit!.state, const CommentsState.loading());
await future;
});
test('getComments - обработка ошибки', () async {
commentsCubit = CommentsCubit(
feedRepository: mockFeedRepository,
commentsRepository: mockCommentsRepository,
);
const postId = '1';
when(mockFeedRepository.getBlogPostComments(id: postId))
.thenThrow(Exception('Ошибка загрузки'));
await commentsCubit!.getComments(postId, CommentSourceType.blogPost, true);
await Future.delayed(const Duration(milliseconds: 100));
expect(commentsCubit!.state, isA<CommentsStateError>());
final errorState = commentsCubit!.state as CommentsStateError;
expect(errorState.errorMessage, contains('Exception'));
});
test('sendComment - успешная отправка комментария для blogPost', () async {
commentsCubit = CommentsCubit(
feedRepository: mockFeedRepository,
commentsRepository: mockCommentsRepository,
);
const postId = '1';
const commentText = 'Тестовый комментарий';
// Устанавливаем начальное состояние
commentsCubit!.emit(CommentsState.loaded(comment: testCommentModel));
when(mockFeedRepository.sendBlogPostComment(
id: postId,
commentText: commentText,
parentCommentId: null,
)).thenAnswer((_) async => Future.value());
when(mockFeedRepository.getBlogPostComments(id: postId))
.thenAnswer((_) async => testCommentModel);
await commentsCubit!.sendComment(
id: postId,
commentText: commentText,
commentSourceType: CommentSourceType.blogPost,
);
verify(mockFeedRepository.sendBlogPostComment(
id: postId,
commentText: commentText,
parentCommentId: null,
)).called(1);
verify(mockFeedRepository.getBlogPostComments(id: postId)).called(1);
});
test('sendComment - обработка ошибки при отправке', () async {
commentsCubit = CommentsCubit(
feedRepository: mockFeedRepository,
commentsRepository: mockCommentsRepository,
);
const postId = '1';
const commentText = 'Тестовый комментарий';
commentsCubit!.emit(CommentsState.loaded(comment: testCommentModel));
when(mockFeedRepository.sendBlogPostComment(
id: postId,
commentText: commentText,
parentCommentId: null,
)).thenThrow(Exception('Ошибка отправки'));
await commentsCubit!.sendComment(
id: postId,
commentText: commentText,
commentSourceType: CommentSourceType.blogPost,
);
await Future.delayed(const Duration(milliseconds: 100));
expect(commentsCubit!.state, isA<CommentsStateError>());
});
test('deleteComment - успешное удаление комментария', () async {
commentsCubit = CommentsCubit(
feedRepository: mockFeedRepository,
commentsRepository: mockCommentsRepository,
);
const commentId = 1;
final commentWithData = testCommentModel.copyWith(
data: [
Comment(
id: commentId,
body: 'Комментарий для удаления',
authorName: 'Автор',
authorSlug: 'author-slug',
authorProfileIcon: 'https://example.com/photo.jpg',
createdAt: DateTime.now(),
childrenComments: [],
),
],
);
commentsCubit!.emit(CommentsState.loaded(comment: commentWithData));
when(mockCommentsRepository.deleteComment(id: commentId))
.thenAnswer((_) async => Future.value());
final result = await commentsCubit!.deleteComment(id: commentId);
expect(result, true);
expect(commentsCubit!.state, isA<CommentsStateLoaded>());
final loadedState = commentsCubit!.state as CommentsStateLoaded;
expect(loadedState.comment.data, isEmpty);
verify(mockCommentsRepository.deleteComment(id: commentId)).called(1);
});
test('deleteComment - возвращает false если состояние не loaded', () async {
commentsCubit = CommentsCubit(
feedRepository: mockFeedRepository,
commentsRepository: mockCommentsRepository,
);
const commentId = 1;
commentsCubit!.emit(const CommentsState());
final result = await commentsCubit!.deleteComment(id: commentId);
expect(result, false);
verifyNever(mockCommentsRepository.deleteComment(id: commentId));
});
test('deleteComment - обработка ошибки при удалении', () async {
commentsCubit = CommentsCubit(
feedRepository: mockFeedRepository,
commentsRepository: mockCommentsRepository,
);
const commentId = 1;
final commentWithData = testCommentModel.copyWith(
data: [
Comment(
id: commentId,
body: 'Комментарий',
authorName: 'Автор',
authorSlug: 'author-slug',
authorProfileIcon: 'https://example.com/photo.jpg',
createdAt: DateTime.now(),
childrenComments: [],
),
],
);
commentsCubit!.emit(CommentsState.loaded(comment: commentWithData));
when(mockCommentsRepository.deleteComment(id: commentId))
.thenThrow(Exception('Ошибка удаления'));
final result = await commentsCubit!.deleteComment(id: commentId);
expect(result, false);
// Состояние должно вернуться к исходному
final loadedState = commentsCubit!.state as CommentsStateLoaded;
expect(loadedState.comment.data.length, 1);
});
test('updateComment - успешное обновление комментария', () async {
commentsCubit = CommentsCubit(
feedRepository: mockFeedRepository,
commentsRepository: mockCommentsRepository,
);
const commentId = 1;
const newBody = 'Обновлённый комментарий';
final commentWithData = testCommentModel.copyWith(
data: [
Comment(
id: commentId,
body: 'Старый комментарий',
authorName: 'Автор',
authorSlug: 'author-slug',
authorProfileIcon: 'https://example.com/photo.jpg',
createdAt: DateTime.now(),
childrenComments: [],
),
],
);
commentsCubit!.emit(CommentsState.loaded(comment: commentWithData));
when(mockCommentsRepository.updateComment(id: commentId, body: newBody))
.thenAnswer((_) async => Future.value());
final result = await commentsCubit!.updateComment(id: commentId, body: newBody);
expect(result, true);
expect(commentsCubit!.state, isA<CommentsStateLoaded>());
final loadedState = commentsCubit!.state as CommentsStateLoaded;
expect(loadedState.comment.data.first.body, newBody);
verify(mockCommentsRepository.updateComment(id: commentId, body: newBody)).called(1);
});
test('updateComment - обработка ошибки при обновлении', () async {
commentsCubit = CommentsCubit(
feedRepository: mockFeedRepository,
commentsRepository: mockCommentsRepository,
);
const commentId = 1;
const newBody = 'Обновлённый комментарий';
const oldBody = 'Старый комментарий';
final commentWithData = testCommentModel.copyWith(
data: [
Comment(
id: commentId,
body: oldBody,
authorName: 'Автор',
authorSlug: 'author-slug',
authorProfileIcon: 'https://example.com/photo.jpg',
createdAt: DateTime.now(),
childrenComments: [],
),
],
);
commentsCubit!.emit(CommentsState.loaded(comment: commentWithData));
when(mockCommentsRepository.updateComment(id: commentId, body: newBody))
.thenThrow(Exception('Ошибка обновления'));
final result = await commentsCubit!.updateComment(id: commentId, body: newBody);
expect(result, false);
// Состояние должно вернуться к исходному
final loadedState = commentsCubit!.state as CommentsStateLoaded;
expect(loadedState.comment.data.first.body, oldBody);
});
test('setReplyId - устанавливает ID ответа', () {
commentsCubit = CommentsCubit(
feedRepository: mockFeedRepository,
commentsRepository: mockCommentsRepository,
);
const replyId = 5;
commentsCubit!.emit(CommentsState.loaded(comment: testCommentModel));
commentsCubit!.setReplyId(replyId);
expect(commentsCubit!.state, isA<CommentsStateLoaded>());
final loadedState = commentsCubit!.state as CommentsStateLoaded;
expect(loadedState.replyId, replyId);
});
test('setReplyId - сбрасывает ID ответа', () {
commentsCubit = CommentsCubit(
feedRepository: mockFeedRepository,
commentsRepository: mockCommentsRepository,
);
commentsCubit!.emit(CommentsState.loaded(comment: testCommentModel, replyId: 5));
commentsCubit!.setReplyId(null);
expect(commentsCubit!.state, isA<CommentsStateLoaded>());
final loadedState = commentsCubit!.state as CommentsStateLoaded;
expect(loadedState.replyId, isNull);
});
});
}