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: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: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 'integration_widget_test.mocks.dart'; @GenerateMocks([BlogPostRepository, FeedCubit]) void main() { group('Blog Post Creation Integration Widget Tests', () { late MockBlogPostRepository mockBlogPostRepository; late MockFeedCubit mockFeedCubit; late BlogPostCreateCubit blogPostCreateCubit; setUp(() { mockBlogPostRepository = MockBlogPostRepository(); mockFeedCubit = MockFeedCubit(); blogPostCreateCubit = BlogPostCreateCubit(blogPostRepository: mockBlogPostRepository); // Setup mock stubs for FeedCubit when(mockFeedCubit.stream).thenAnswer((_) => Stream.value(const FeedState.loading())); when(mockFeedCubit.close()).thenAnswer((_) async {}); }); tearDown(() { blogPostCreateCubit.close(); }); Widget createTestWidget(Widget child) { return MaterialApp( theme: ThemeHelper().themeData(), home: MultiBlocProvider( providers: [ BlocProvider(create: (context) => blogPostCreateCubit), BlocProvider(create: (context) => mockFeedCubit), ], child: Scaffold(body: SingleChildScrollView(child: child)), ), ); } testWidgets('should complete full blog post creation flow', (WidgetTester tester) async { // Arrange when(mockBlogPostRepository.createBlogPost(any, any, any, any)).thenAnswer((_) async {}); when(mockFeedCubit.loadFeedBlogPostList()).thenAnswer((_) async {}); Widget fullFlowWidget = BlocListener( listener: (context, state) { if (state is BlogPostCreateStateLoaded) { context.read().loadFeedBlogPostList(); } }, child: 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), 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: 'Integration Test Title', videoUrl: 'https://youtube.com/watch?v=integration', description: 'Integration Test Description', image: null, ); }, child: const Text('Опубликовать'), ), ], ), ); // Act await tester.pumpWidget(createTestWidget(fullFlowWidget)); await tester.pumpAndSettle(); await tester.enterText(find.byKey(AppKeys.blogPostCreateScreenTitleTextField), 'Integration Test Title'); await tester.enterText(find.byKey(AppKeys.blogPostCreateScreenContentTextField), 'Integration Test Description'); await tester.enterText(find.byKey(AppKeys.blogPostCreateScreenVideoLinkTextField), 'https://youtube.com/watch?v=integration'); await tester.pump(); await tester.tap(find.byKey(AppKeys.blogPostCreateScreenCreateButton)); await tester.pump(); // Assert verify( mockBlogPostRepository.createBlogPost('Integration Test Title', 'https://youtube.com/watch?v=integration', 'Integration Test Description', null), ).called(1); verify(mockFeedCubit.loadFeedBlogPostList()).called(1); }); testWidgets('should handle blog post update flow', (WidgetTester tester) async { // Arrange when(mockBlogPostRepository.updateBlogPost(any, any, any, any, any, any)).thenAnswer((_) async {}); when(mockFeedCubit.loadFeedBlogPostList()).thenAnswer((_) async {}); final existingBlogPost = 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: 5, totalViews: 10, totalComments: 3, wasLikedByMe: false, ); Widget updateFlowWidget = BlocListener( listener: (context, state) { if (state is BlogPostCreateStateLoaded) { context.read().loadFeedBlogPostList(); } }, child: Column( children: [ TextFormField( key: AppKeys.blogPostCreateScreenTitleTextField, decoration: const InputDecoration(labelText: 'Заголовок'), controller: TextEditingController(text: existingBlogPost.title), ), const SizedBox(height: 16), TextFormField( key: AppKeys.blogPostCreateScreenContentTextField, decoration: const InputDecoration(labelText: 'Описание'), maxLines: 6, minLines: 4, controller: TextEditingController(text: existingBlogPost.description), ), const SizedBox(height: 16), TextFormField( key: AppKeys.blogPostCreateScreenVideoLinkTextField, decoration: const InputDecoration(labelText: 'Ссылка на видео'), controller: TextEditingController(text: existingBlogPost.videoUrl), ), const SizedBox(height: 16), ElevatedButton( key: AppKeys.blogPostCreateScreenCreateButton, onPressed: () async { await blogPostCreateCubit.updateBlogPost( id: existingBlogPost.id, title: 'Updated Title', videoUrl: 'https://youtube.com/watch?v=updated', description: 'Updated Description', image: null, oldImageUrl: existingBlogPost.image, ); }, child: const Text('Обновить'), ), ], ), ); // Act await tester.pumpWidget(createTestWidget(updateFlowWidget)); 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); verify(mockFeedCubit.loadFeedBlogPostList()).called(1); }); testWidgets('should handle blog post deletion flow', (WidgetTester tester) async { // Arrange when(mockBlogPostRepository.deleteBlogPost(any)).thenAnswer((_) async {}); when(mockFeedCubit.loadFeedBlogPostList()).thenAnswer((_) async {}); Widget deleteFlowWidget = Column( children: [ const Text('Удалить блог пост?'), const SizedBox(height: 16), ElevatedButton( key: const Key('deleteBlogPostButton'), onPressed: () async { await blogPostCreateCubit.deleteBlogPost(1); mockFeedCubit.loadFeedBlogPostList(); }, child: const Text('Удалить'), ), ], ); // Act await tester.pumpWidget(createTestWidget(deleteFlowWidget)); await tester.pumpAndSettle(); await tester.tap(find.byKey(const Key('deleteBlogPostButton'))); await tester.pump(); // Assert verify(mockBlogPostRepository.deleteBlogPost(1)).called(1); verify(mockFeedCubit.loadFeedBlogPostList()).called(1); }); testWidgets('should handle error flow during blog post creation', (WidgetTester tester) async { // Arrange when(mockBlogPostRepository.createBlogPost(any, any, any, any)).thenThrow(Exception('Network error')); Widget errorFlowWidget = BlocBuilder( builder: (context, state) { if (state is BlogPostCreateStateError) { return Column( children: [ Text('Error: ${state.errorMessage}'), const SizedBox(height: 16), ElevatedButton( key: const Key('retryButton'), onPressed: () async { await blogPostCreateCubit.createBlogPost(title: 'Retry Title', videoUrl: null, description: 'Retry Description', image: null); }, child: const Text('Повторить'), ), ], ); } return const Text('No Error'); }, ); // Act await tester.pumpWidget(createTestWidget(errorFlowWidget)); await tester.pumpAndSettle(); // Trigger error await blogPostCreateCubit.createBlogPost(title: 'Test Title', videoUrl: null, description: 'Test Description', image: null); await tester.pump(); // Assert expect(find.textContaining('Error:'), findsOneWidget); expect(find.byKey(const Key('retryButton')), findsOneWidget); }); 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 loadingFlowWidget = BlocBuilder( builder: (context, state) { if (state is BlogPostCreateStateLoading) { return const Column(children: [CircularProgressIndicator(), SizedBox(height: 16), Text('Создание блог поста...')]); } return const Text('Not Loading'); }, ); // Act await tester.pumpWidget(createTestWidget(loadingFlowWidget)); await tester.pumpAndSettle(); // Trigger loading 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); expect(find.text('Создание блог поста...'), findsOneWidget); // Wait for the async operation to complete await tester.pump(const Duration(milliseconds: 100)); await tester.pumpAndSettle(); }); testWidgets('should handle success state after blog post creation', (WidgetTester tester) async { // Arrange when(mockBlogPostRepository.createBlogPost(any, any, any, any)).thenAnswer((_) async {}); when(mockFeedCubit.loadFeedBlogPostList()).thenAnswer((_) async {}); Widget successFlowWidget = BlocListener( listener: (context, state) { if (state is BlogPostCreateStateLoaded) { context.read().loadFeedBlogPostList(); } }, child: BlocBuilder( builder: (context, state) { if (state is BlogPostCreateStateLoaded) { return const Column( children: [ Icon(Icons.check_circle, color: Colors.green, size: 48), SizedBox(height: 16), Text('Блог пост успешно создан!'), ], ); } return const Text('Not Loaded'); }, ), ); // Act await tester.pumpWidget(createTestWidget(successFlowWidget)); await tester.pumpAndSettle(); // Trigger success await blogPostCreateCubit.createBlogPost(title: 'Test Title', videoUrl: null, description: 'Test Description', image: null); await tester.pump(); // Assert expect(find.byIcon(Icons.check_circle), findsOneWidget); expect(find.text('Блог пост успешно создан!'), findsOneWidget); verify(mockFeedCubit.loadFeedBlogPostList()).called(1); }); testWidgets('should handle form validation flow', (WidgetTester tester) async { // Arrange Widget validationFlowWidget = 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), 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(validationFlowWidget)); await tester.pumpAndSettle(); // Assert expect(find.text('Заголовок обязателен'), findsOneWidget); expect(find.text('Описание обязательно'), findsOneWidget); expect(find.text('Введите корректную ссылку'), findsOneWidget); }); testWidgets('should handle image upload flow', (WidgetTester tester) async { // Arrange bool imageUploadInitiated = false; Widget imageUploadWidget = Column( children: [ const Text('Изображение поста'), const SizedBox(height: 8), GestureDetector( key: AppKeys.imageWidget, onTap: () { imageUploadInitiated = 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('Добавить изображение'), ], ), ), ), ), const SizedBox(height: 16), ElevatedButton( key: AppKeys.blogPostCreateScreenCreateButton, onPressed: () async { await blogPostCreateCubit.createBlogPost( title: 'Test Title', videoUrl: null, description: 'Test Description', image: null, // В реальном тесте здесь был бы XFile ); }, child: const Text('Опубликовать'), ), ], ); // Act await tester.pumpWidget(createTestWidget(imageUploadWidget)); await tester.pumpAndSettle(); await tester.tap(find.byKey(AppKeys.imageWidget)); await tester.pump(); // Assert expect(imageUploadInitiated, isTrue); }); testWidgets('should handle complete blog post creation with all features', (WidgetTester tester) async { // Arrange when(mockBlogPostRepository.createBlogPost(any, any, any, any)).thenAnswer((_) async {}); when(mockFeedCubit.loadFeedBlogPostList()).thenAnswer((_) async {}); Widget completeFlowWidget = BlocListener( listener: (context, state) { if (state is BlogPostCreateStateLoaded) { context.read().loadFeedBlogPostList(); } }, child: Column( children: [ const Text('Заголовок'), const SizedBox(height: 8), TextFormField( key: AppKeys.blogPostCreateScreenTitleTextField, decoration: const InputDecoration(hintText: 'Введите заголовок поста'), ), const SizedBox(height: 16), const Text('Описание'), const SizedBox(height: 8), TextFormField(key: AppKeys.blogPostCreateScreenContentTextField, decoration: const InputDecoration(), maxLines: 6, minLines: 4), const SizedBox(height: 16), const Text('Изображение поста'), const SizedBox(height: 8), 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), const Text('Ссылка на видео'), const SizedBox(height: 8), TextFormField(key: AppKeys.blogPostCreateScreenVideoLinkTextField, decoration: const InputDecoration()), const SizedBox(height: 32), ElevatedButton( key: AppKeys.blogPostCreateScreenCreateButton, onPressed: () async { await blogPostCreateCubit.createBlogPost( title: 'Complete Test Title', videoUrl: 'https://youtube.com/watch?v=complete', description: 'Complete Test Description', image: null, ); }, child: const Text('Опубликовать'), ), ], ), ); // Act await tester.pumpWidget(createTestWidget(completeFlowWidget)); await tester.pumpAndSettle(); await tester.enterText(find.byKey(AppKeys.blogPostCreateScreenTitleTextField), 'Complete Test Title'); await tester.enterText(find.byKey(AppKeys.blogPostCreateScreenContentTextField), 'Complete Test Description'); await tester.enterText(find.byKey(AppKeys.blogPostCreateScreenVideoLinkTextField), 'https://youtube.com/watch?v=complete'); await tester.pump(); // Scroll to make sure the button is visible await tester.drag(find.byType(SingleChildScrollView), const Offset(0, -200)); await tester.pump(); await tester.tap(find.byKey(AppKeys.blogPostCreateScreenCreateButton)); await tester.pump(); // Assert verify(mockBlogPostRepository.createBlogPost('Complete Test Title', 'https://youtube.com/watch?v=complete', 'Complete Test Description', null)).called(1); verify(mockFeedCubit.loadFeedBlogPostList()).called(1); }); }); }