forked from eatvio/eatvio_app
525 lines
20 KiB
Dart
525 lines
20 KiB
Dart
import 'package:eatvio_app/app_keys.dart';
|
||
import 'package:eatvio_app/core/theme/theme_helper.dart';
|
||
import 'package:eatvio_app/features/blog_post_create/bloc/blog_post_create_cubit/blog_post_create_cubit.dart';
|
||
import 'package:eatvio_app/features/blog_post_create/bloc/blog_post_create_cubit/blog_post_create_state.dart';
|
||
import 'package:eatvio_app/features/blog_post_create/repositories/blog_post_repository.dart';
|
||
import 'package:eatvio_app/features/feed/models/feed_blog_post_model.dart';
|
||
import 'package:eatvio_app/features/feed/models/diets_pagination_model.dart';
|
||
import 'package:flutter/material.dart';
|
||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||
import 'package:flutter_test/flutter_test.dart';
|
||
import 'package:mockito/annotations.dart';
|
||
import 'package:mockito/mockito.dart';
|
||
|
||
import 'advanced_widget_test.mocks.dart';
|
||
|
||
@GenerateMocks([BlogPostRepository])
|
||
void main() {
|
||
group('Blog Post Creation Advanced Widget Tests', () {
|
||
late MockBlogPostRepository mockBlogPostRepository;
|
||
late BlogPostCreateCubit blogPostCreateCubit;
|
||
|
||
setUp(() {
|
||
mockBlogPostRepository = MockBlogPostRepository();
|
||
blogPostCreateCubit = BlogPostCreateCubit(blogPostRepository: mockBlogPostRepository);
|
||
});
|
||
|
||
tearDown(() {
|
||
blogPostCreateCubit.close();
|
||
});
|
||
|
||
Widget createTestWidget(Widget child) {
|
||
return MaterialApp(
|
||
theme: ThemeHelper().themeData(),
|
||
home: BlocProvider<BlogPostCreateCubit>(
|
||
create: (context) => blogPostCreateCubit,
|
||
child: Scaffold(body: SingleChildScrollView(child: child)),
|
||
),
|
||
);
|
||
}
|
||
|
||
testWidgets('should handle blog post creation with all fields', (WidgetTester tester) async {
|
||
// Arrange
|
||
when(mockBlogPostRepository.createBlogPost(any, any, any, any)).thenAnswer((_) async {});
|
||
|
||
Widget formWidget = Column(
|
||
children: [
|
||
TextFormField(
|
||
key: AppKeys.blogPostCreateScreenTitleTextField,
|
||
decoration: const InputDecoration(labelText: 'Заголовок'),
|
||
),
|
||
const SizedBox(height: 16),
|
||
TextFormField(
|
||
key: AppKeys.blogPostCreateScreenContentTextField,
|
||
decoration: const InputDecoration(labelText: 'Описание'),
|
||
maxLines: 6,
|
||
minLines: 4,
|
||
),
|
||
const SizedBox(height: 16),
|
||
TextFormField(
|
||
key: AppKeys.blogPostCreateScreenVideoLinkTextField,
|
||
decoration: const InputDecoration(labelText: 'Ссылка на видео'),
|
||
),
|
||
const SizedBox(height: 16),
|
||
ElevatedButton(
|
||
key: AppKeys.blogPostCreateScreenCreateButton,
|
||
onPressed: () async {
|
||
await blogPostCreateCubit.createBlogPost(
|
||
title: 'Test Title',
|
||
description: 'Test Description',
|
||
videoUrl: 'https://youtube.com/watch?v=test',
|
||
image: null,
|
||
);
|
||
},
|
||
child: const Text('Опубликовать'),
|
||
),
|
||
],
|
||
);
|
||
|
||
// Act
|
||
await tester.pumpWidget(createTestWidget(formWidget));
|
||
await tester.pumpAndSettle();
|
||
|
||
await tester.enterText(find.byKey(AppKeys.blogPostCreateScreenTitleTextField), 'Test Title');
|
||
await tester.enterText(find.byKey(AppKeys.blogPostCreateScreenContentTextField), 'Test Description');
|
||
await tester.enterText(find.byKey(AppKeys.blogPostCreateScreenVideoLinkTextField), 'https://youtube.com/watch?v=test');
|
||
await tester.pump();
|
||
|
||
await tester.tap(find.byKey(AppKeys.blogPostCreateScreenCreateButton));
|
||
await tester.pump();
|
||
|
||
// Assert
|
||
verify(mockBlogPostRepository.createBlogPost('Test Title', 'https://youtube.com/watch?v=test', 'Test Description', null)).called(1);
|
||
});
|
||
|
||
testWidgets('should handle blog post creation with image', (WidgetTester tester) async {
|
||
// Arrange
|
||
when(mockBlogPostRepository.createBlogPost(any, any, any, any)).thenAnswer((_) async {});
|
||
|
||
Widget formWidget = Column(
|
||
children: [
|
||
TextFormField(
|
||
key: AppKeys.blogPostCreateScreenTitleTextField,
|
||
decoration: const InputDecoration(labelText: 'Заголовок'),
|
||
),
|
||
const SizedBox(height: 16),
|
||
TextFormField(
|
||
key: AppKeys.blogPostCreateScreenContentTextField,
|
||
decoration: const InputDecoration(labelText: 'Описание'),
|
||
maxLines: 6,
|
||
minLines: 4,
|
||
),
|
||
const SizedBox(height: 16),
|
||
GestureDetector(
|
||
key: AppKeys.imageWidget,
|
||
onTap: () {},
|
||
child: Container(
|
||
height: 200,
|
||
width: double.infinity,
|
||
decoration: BoxDecoration(
|
||
border: Border.all(color: Colors.grey),
|
||
borderRadius: BorderRadius.circular(8),
|
||
),
|
||
child: const Center(
|
||
child: Column(
|
||
mainAxisAlignment: MainAxisAlignment.center,
|
||
children: [
|
||
Icon(Icons.add_photo_alternate, size: 48, color: Colors.grey),
|
||
SizedBox(height: 8),
|
||
Text('Добавить изображение'),
|
||
],
|
||
),
|
||
),
|
||
),
|
||
),
|
||
const SizedBox(height: 16),
|
||
ElevatedButton(
|
||
key: AppKeys.blogPostCreateScreenCreateButton,
|
||
onPressed: () async {
|
||
await blogPostCreateCubit.createBlogPost(
|
||
title: 'Test Title',
|
||
description: 'Test Description',
|
||
videoUrl: null,
|
||
image: null, // В реальном тесте здесь был бы XFile
|
||
);
|
||
},
|
||
child: const Text('Опубликовать'),
|
||
),
|
||
],
|
||
);
|
||
|
||
// Act
|
||
await tester.pumpWidget(createTestWidget(formWidget));
|
||
await tester.pumpAndSettle();
|
||
|
||
await tester.enterText(find.byKey(AppKeys.blogPostCreateScreenTitleTextField), 'Test Title');
|
||
await tester.enterText(find.byKey(AppKeys.blogPostCreateScreenContentTextField), 'Test Description');
|
||
await tester.pump();
|
||
|
||
await tester.tap(find.byKey(AppKeys.blogPostCreateScreenCreateButton));
|
||
await tester.pump();
|
||
|
||
// Assert
|
||
verify(mockBlogPostRepository.createBlogPost('Test Title', null, 'Test Description', null)).called(1);
|
||
});
|
||
|
||
testWidgets('should handle blog post update', (WidgetTester tester) async {
|
||
// Arrange
|
||
when(mockBlogPostRepository.updateBlogPost(any, any, any, any, any, any)).thenAnswer((_) async {});
|
||
|
||
final testBlogPost = FeedBlogPostModel(
|
||
id: 1,
|
||
state: 'published',
|
||
image: 'original_image.jpg',
|
||
description: 'Original Description',
|
||
author: const Author(authorSlug: 'test-author', authorName: 'Test Author', authorProfileIcon: 'avatar.jpg', authorIsMe: true),
|
||
videoUrl: 'https://youtube.com/watch?v=original',
|
||
createdAt: DateTime.now(),
|
||
updatedAt: DateTime.now(),
|
||
title: 'Original Title',
|
||
totalLikes: 0,
|
||
totalViews: 0,
|
||
totalComments: 0,
|
||
wasLikedByMe: false,
|
||
);
|
||
|
||
Widget formWidget = Column(
|
||
children: [
|
||
TextFormField(
|
||
key: AppKeys.blogPostCreateScreenTitleTextField,
|
||
decoration: const InputDecoration(labelText: 'Заголовок'),
|
||
controller: TextEditingController(text: testBlogPost.title),
|
||
),
|
||
const SizedBox(height: 16),
|
||
TextFormField(
|
||
key: AppKeys.blogPostCreateScreenContentTextField,
|
||
decoration: const InputDecoration(labelText: 'Описание'),
|
||
maxLines: 6,
|
||
minLines: 4,
|
||
controller: TextEditingController(text: testBlogPost.description),
|
||
),
|
||
const SizedBox(height: 16),
|
||
TextFormField(
|
||
key: AppKeys.blogPostCreateScreenVideoLinkTextField,
|
||
decoration: const InputDecoration(labelText: 'Ссылка на видео'),
|
||
controller: TextEditingController(text: testBlogPost.videoUrl),
|
||
),
|
||
const SizedBox(height: 16),
|
||
ElevatedButton(
|
||
key: AppKeys.blogPostCreateScreenCreateButton,
|
||
onPressed: () async {
|
||
await blogPostCreateCubit.updateBlogPost(
|
||
id: testBlogPost.id,
|
||
title: 'Updated Title',
|
||
description: 'Updated Description',
|
||
videoUrl: 'https://youtube.com/watch?v=updated',
|
||
image: null,
|
||
oldImageUrl: testBlogPost.image,
|
||
);
|
||
},
|
||
child: const Text('Обновить'),
|
||
),
|
||
],
|
||
);
|
||
|
||
// Act
|
||
await tester.pumpWidget(createTestWidget(formWidget));
|
||
await tester.pumpAndSettle();
|
||
|
||
await tester.enterText(find.byKey(AppKeys.blogPostCreateScreenTitleTextField), 'Updated Title');
|
||
await tester.enterText(find.byKey(AppKeys.blogPostCreateScreenContentTextField), 'Updated Description');
|
||
await tester.enterText(find.byKey(AppKeys.blogPostCreateScreenVideoLinkTextField), 'https://youtube.com/watch?v=updated');
|
||
await tester.pump();
|
||
|
||
await tester.tap(find.byKey(AppKeys.blogPostCreateScreenCreateButton));
|
||
await tester.pump();
|
||
|
||
// Assert
|
||
verify(
|
||
mockBlogPostRepository.updateBlogPost(1, 'Updated Title', 'https://youtube.com/watch?v=updated', 'Updated Description', null, 'original_image.jpg'),
|
||
).called(1);
|
||
});
|
||
|
||
testWidgets('should handle blog post deletion', (WidgetTester tester) async {
|
||
// Arrange
|
||
when(mockBlogPostRepository.deleteBlogPost(any)).thenAnswer((_) async {});
|
||
|
||
Widget deleteWidget = ElevatedButton(
|
||
key: const Key('deleteBlogPostButton'),
|
||
onPressed: () async {
|
||
await blogPostCreateCubit.deleteBlogPost(1);
|
||
},
|
||
child: const Text('Удалить пост'),
|
||
);
|
||
|
||
// Act
|
||
await tester.pumpWidget(createTestWidget(deleteWidget));
|
||
await tester.pumpAndSettle();
|
||
|
||
await tester.tap(find.byKey(const Key('deleteBlogPostButton')));
|
||
await tester.pump();
|
||
|
||
// Assert
|
||
verify(mockBlogPostRepository.deleteBlogPost(1)).called(1);
|
||
});
|
||
|
||
testWidgets('should handle loading state during blog post creation', (WidgetTester tester) async {
|
||
// Arrange
|
||
when(mockBlogPostRepository.createBlogPost(any, any, any, any)).thenAnswer((_) async {
|
||
await Future.delayed(const Duration(milliseconds: 50));
|
||
});
|
||
|
||
Widget loadingWidget = BlocBuilder<BlogPostCreateCubit, BlogPostCreateState>(
|
||
builder: (context, state) {
|
||
if (state is BlogPostCreateStateLoading) {
|
||
return const Center(child: CircularProgressIndicator());
|
||
}
|
||
return const Text('Not Loading');
|
||
},
|
||
);
|
||
|
||
// Act
|
||
await tester.pumpWidget(createTestWidget(loadingWidget));
|
||
await tester.pumpAndSettle();
|
||
|
||
// Trigger loading state
|
||
blogPostCreateCubit.createBlogPost(title: 'Test Title', videoUrl: null, description: 'Test Description', image: null);
|
||
await tester.pump();
|
||
await tester.pump(); // Need to pump twice to get the loading state
|
||
|
||
// Assert
|
||
expect(find.byType(CircularProgressIndicator), findsOneWidget);
|
||
|
||
// Wait for the async operation to complete
|
||
await tester.pump(const Duration(milliseconds: 100));
|
||
await tester.pumpAndSettle();
|
||
});
|
||
|
||
testWidgets('should handle error state during blog post creation', (WidgetTester tester) async {
|
||
// Arrange
|
||
when(mockBlogPostRepository.createBlogPost(any, any, any, any)).thenThrow(Exception('Network error'));
|
||
|
||
Widget errorWidget = BlocBuilder<BlogPostCreateCubit, BlogPostCreateState>(
|
||
builder: (context, state) {
|
||
if (state is BlogPostCreateStateError) {
|
||
return Center(child: Text('Error: ${state.errorMessage}'));
|
||
}
|
||
return const Text('No Error');
|
||
},
|
||
);
|
||
|
||
// Act
|
||
await tester.pumpWidget(createTestWidget(errorWidget));
|
||
await tester.pumpAndSettle();
|
||
|
||
// Trigger error state
|
||
blogPostCreateCubit.createBlogPost(title: 'Test Title', videoUrl: null, description: 'Test Description', image: null);
|
||
await tester.pump();
|
||
await tester.pump(); // Need to pump twice to get the error state
|
||
|
||
// Assert
|
||
expect(find.textContaining('Error:'), findsOneWidget);
|
||
});
|
||
|
||
testWidgets('should handle success state after blog post creation', (WidgetTester tester) async {
|
||
// Arrange
|
||
when(mockBlogPostRepository.createBlogPost(any, any, any, any)).thenAnswer((_) async {});
|
||
|
||
Widget successWidget = BlocBuilder<BlogPostCreateCubit, BlogPostCreateState>(
|
||
builder: (context, state) {
|
||
if (state is BlogPostCreateStateLoaded) {
|
||
return const Center(child: Text('Blog post created successfully!'));
|
||
}
|
||
return const Text('Not Loaded');
|
||
},
|
||
);
|
||
|
||
// Act
|
||
await tester.pumpWidget(createTestWidget(successWidget));
|
||
await tester.pumpAndSettle();
|
||
|
||
// Trigger success state
|
||
blogPostCreateCubit.createBlogPost(title: 'Test Title', videoUrl: null, description: 'Test Description', image: null);
|
||
await tester.pump();
|
||
await tester.pump(); // Need to pump twice to get the success state
|
||
|
||
// Assert
|
||
expect(find.text('Blog post created successfully!'), findsOneWidget);
|
||
});
|
||
|
||
testWidgets('should validate form fields before submission', (WidgetTester tester) async {
|
||
// Arrange
|
||
Widget validationWidget = Column(
|
||
children: [
|
||
TextFormField(
|
||
key: AppKeys.blogPostCreateScreenTitleTextField,
|
||
decoration: const InputDecoration(labelText: 'Заголовок', errorText: 'Заголовок не может быть пустым'),
|
||
),
|
||
const SizedBox(height: 16),
|
||
TextFormField(
|
||
key: AppKeys.blogPostCreateScreenContentTextField,
|
||
decoration: const InputDecoration(labelText: 'Описание', errorText: 'Описание не может быть пустым'),
|
||
maxLines: 6,
|
||
minLines: 4,
|
||
),
|
||
const SizedBox(height: 16),
|
||
ElevatedButton(key: AppKeys.blogPostCreateScreenCreateButton, onPressed: () {}, child: const Text('Опубликовать')),
|
||
],
|
||
);
|
||
|
||
// Act
|
||
await tester.pumpWidget(createTestWidget(validationWidget));
|
||
await tester.pumpAndSettle();
|
||
|
||
// Assert
|
||
expect(find.text('Заголовок не может быть пустым'), findsOneWidget);
|
||
expect(find.text('Описание не может быть пустым'), findsOneWidget);
|
||
});
|
||
|
||
testWidgets('should handle image picker interaction', (WidgetTester tester) async {
|
||
// Arrange
|
||
bool imagePickerCalled = false;
|
||
Widget imagePickerWidget = GestureDetector(
|
||
key: AppKeys.imageWidget,
|
||
onTap: () {
|
||
imagePickerCalled = true;
|
||
},
|
||
child: Container(
|
||
height: 200,
|
||
width: double.infinity,
|
||
decoration: BoxDecoration(
|
||
border: Border.all(color: Colors.grey),
|
||
borderRadius: BorderRadius.circular(8),
|
||
),
|
||
child: const Center(
|
||
child: Column(
|
||
mainAxisAlignment: MainAxisAlignment.center,
|
||
children: [
|
||
Icon(Icons.add_photo_alternate, size: 48, color: Colors.grey),
|
||
SizedBox(height: 8),
|
||
Text('Добавить изображение'),
|
||
],
|
||
),
|
||
),
|
||
),
|
||
);
|
||
|
||
// Act
|
||
await tester.pumpWidget(createTestWidget(imagePickerWidget));
|
||
await tester.pumpAndSettle();
|
||
|
||
await tester.tap(find.byKey(AppKeys.imageWidget));
|
||
await tester.pump();
|
||
|
||
// Assert
|
||
expect(imagePickerCalled, isTrue);
|
||
});
|
||
|
||
testWidgets('should handle video URL validation', (WidgetTester tester) async {
|
||
// Arrange
|
||
Widget videoValidationWidget = Column(
|
||
children: [
|
||
TextFormField(
|
||
key: AppKeys.blogPostCreateScreenVideoLinkTextField,
|
||
decoration: const InputDecoration(labelText: 'Ссылка на видео', errorText: 'Введите корректную ссылку на видео'),
|
||
),
|
||
const SizedBox(height: 16),
|
||
ElevatedButton(key: AppKeys.blogPostCreateScreenCreateButton, onPressed: () {}, child: const Text('Опубликовать')),
|
||
],
|
||
);
|
||
|
||
// Act
|
||
await tester.pumpWidget(createTestWidget(videoValidationWidget));
|
||
await tester.pumpAndSettle();
|
||
|
||
// Assert
|
||
expect(find.text('Введите корректную ссылку на видео'), findsOneWidget);
|
||
});
|
||
|
||
testWidgets('should handle form submission with empty fields', (WidgetTester tester) async {
|
||
// Arrange
|
||
Widget emptyFormWidget = Column(
|
||
children: [
|
||
TextFormField(
|
||
key: AppKeys.blogPostCreateScreenTitleTextField,
|
||
decoration: const InputDecoration(labelText: 'Заголовок'),
|
||
),
|
||
const SizedBox(height: 16),
|
||
TextFormField(
|
||
key: AppKeys.blogPostCreateScreenContentTextField,
|
||
decoration: const InputDecoration(labelText: 'Описание'),
|
||
maxLines: 6,
|
||
minLines: 4,
|
||
),
|
||
const SizedBox(height: 16),
|
||
ElevatedButton(
|
||
key: AppKeys.blogPostCreateScreenCreateButton,
|
||
onPressed: () async {
|
||
await blogPostCreateCubit.createBlogPost(title: '', videoUrl: '', description: '', image: null);
|
||
},
|
||
child: const Text('Опубликовать'),
|
||
),
|
||
],
|
||
);
|
||
|
||
// Act
|
||
await tester.pumpWidget(createTestWidget(emptyFormWidget));
|
||
await tester.pumpAndSettle();
|
||
|
||
await tester.tap(find.byKey(AppKeys.blogPostCreateScreenCreateButton));
|
||
await tester.pump();
|
||
|
||
// Assert
|
||
verify(mockBlogPostRepository.createBlogPost('', '', '', null)).called(1);
|
||
});
|
||
|
||
testWidgets('should handle blog post creation with long content', (WidgetTester tester) async {
|
||
// Arrange
|
||
const longContent =
|
||
'Это очень длинное описание блог поста, которое содержит много текста и должно быть правильно обработано системой. '
|
||
'Оно может содержать несколько абзацев и различные форматирования. '
|
||
'Пользователи должны иметь возможность создавать подробные и информативные посты.';
|
||
|
||
when(mockBlogPostRepository.createBlogPost(any, any, any, any)).thenAnswer((_) async {});
|
||
|
||
Widget longContentWidget = Column(
|
||
children: [
|
||
TextFormField(
|
||
key: AppKeys.blogPostCreateScreenTitleTextField,
|
||
decoration: const InputDecoration(labelText: 'Заголовок'),
|
||
),
|
||
const SizedBox(height: 16),
|
||
TextFormField(
|
||
key: AppKeys.blogPostCreateScreenContentTextField,
|
||
decoration: const InputDecoration(labelText: 'Описание'),
|
||
maxLines: 6,
|
||
minLines: 4,
|
||
),
|
||
const SizedBox(height: 16),
|
||
ElevatedButton(
|
||
key: AppKeys.blogPostCreateScreenCreateButton,
|
||
onPressed: () async {
|
||
await blogPostCreateCubit.createBlogPost(title: 'Test Title', videoUrl: null, description: longContent, image: null);
|
||
},
|
||
child: const Text('Опубликовать'),
|
||
),
|
||
],
|
||
);
|
||
|
||
// Act
|
||
await tester.pumpWidget(createTestWidget(longContentWidget));
|
||
await tester.pumpAndSettle();
|
||
|
||
await tester.enterText(find.byKey(AppKeys.blogPostCreateScreenTitleTextField), 'Test Title');
|
||
await tester.enterText(find.byKey(AppKeys.blogPostCreateScreenContentTextField), longContent);
|
||
await tester.pump();
|
||
|
||
await tester.tap(find.byKey(AppKeys.blogPostCreateScreenCreateButton));
|
||
await tester.pump();
|
||
|
||
// Assert
|
||
verify(mockBlogPostRepository.createBlogPost('Test Title', null, longContent, null)).called(1);
|
||
});
|
||
});
|
||
}
|