forked from eatvio/eatvio_app
feat(post_creation): added multiple image posts creation and validation
This commit is contained in:
@@ -11,6 +11,7 @@ import 'package:eatvio_app/features/blog_post_create/presentation/pages/blog_pos
|
||||
import 'package:eatvio_app/features/blog_post_create/repositories/blog_post_repository.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/post_feed_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:eatvio_app/generated/l10n.dart';
|
||||
@@ -19,14 +20,15 @@ import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:flutter_localizations/flutter_localizations.dart';
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:go_router/go_router.dart';
|
||||
import 'package:mockito/annotations.dart';
|
||||
import 'package:mockito/mockito.dart';
|
||||
import 'package:mocktail/mocktail.dart';
|
||||
import 'package:flutter_dotenv/flutter_dotenv.dart';
|
||||
import 'package:flutter_image_compress/flutter_image_compress.dart';
|
||||
import 'package:network_image_mock/network_image_mock.dart';
|
||||
|
||||
import 'blog_post_create_screen_test.mocks.dart';
|
||||
class MockBlogPostRepository extends Mock implements BlogPostRepository {}
|
||||
|
||||
class MockFeedCubit extends Mock implements FeedCubit {}
|
||||
|
||||
@GenerateMocks([BlogPostRepository, BlogPostCreateCubit, FeedCubit])
|
||||
void main() {
|
||||
TestWidgetsFlutterBinding.ensureInitialized();
|
||||
|
||||
@@ -37,13 +39,22 @@ void main() {
|
||||
|
||||
setUp(() async {
|
||||
await sl.reset();
|
||||
dotenv.loadFromString(envString: 'DEV_BASE_URL=http://localhost\nBASE_URL=http://localhost');
|
||||
dotenv.loadFromString(
|
||||
envString: 'DEV_BASE_URL=http://localhost\nBASE_URL=http://localhost',
|
||||
);
|
||||
AppConfigRegistration.registerAppConfig(buildType: AppBuildType.tests);
|
||||
registerFallbackValue(<XFile>[]);
|
||||
registerFallbackValue(<int>[]);
|
||||
mockBlogPostRepository = MockBlogPostRepository();
|
||||
blogPostCreateCubit = BlogPostCreateCubit(blogPostRepository: mockBlogPostRepository);
|
||||
blogPostCreateCubit = BlogPostCreateCubit(
|
||||
blogPostRepository: mockBlogPostRepository,
|
||||
);
|
||||
mockFeedCubit = MockFeedCubit();
|
||||
when(mockFeedCubit.state).thenReturn(const FeedState.loading());
|
||||
when(mockFeedCubit.stream).thenAnswer((_) => Stream.value(const FeedState.loading()));
|
||||
when(() => mockFeedCubit.state).thenReturn(const FeedState.loading());
|
||||
when(
|
||||
() => mockFeedCubit.stream,
|
||||
).thenAnswer((_) => Stream.value(const FeedState.loading()));
|
||||
when(() => mockFeedCubit.loadFeedBlogPostList()).thenAnswer((_) async {});
|
||||
sl.registerLazySingleton<FeedCubit>(() => mockFeedCubit);
|
||||
sl.registerLazySingleton<BlogPostCreateCubit>(() => blogPostCreateCubit);
|
||||
});
|
||||
@@ -52,19 +63,25 @@ void main() {
|
||||
blogPostCreateCubit.close();
|
||||
});
|
||||
|
||||
Widget createTestWidget({FeedBlogPostModel? feedBlogPostModel, bool canPop = false}) {
|
||||
Widget createTestWidget({
|
||||
FeedBlogPostModel? feedBlogPostModel,
|
||||
bool canPop = false,
|
||||
}) {
|
||||
final router = GoRouter(
|
||||
initialLocation: '/blog-post-create',
|
||||
routes: [
|
||||
GoRoute(
|
||||
path: '/home',
|
||||
builder: (context, state) => const Scaffold(body: Center(child: Text('Home'))),
|
||||
builder: (context, state) =>
|
||||
const Scaffold(body: Center(child: Text('Home'))),
|
||||
),
|
||||
GoRoute(
|
||||
path: '/blog-post-create',
|
||||
builder: (context, state) => MultiBlocProvider(
|
||||
providers: [
|
||||
BlocProvider<BlogPostCreateCubit>.value(value: blogPostCreateCubit),
|
||||
BlocProvider<BlogPostCreateCubit>.value(
|
||||
value: blogPostCreateCubit,
|
||||
),
|
||||
BlocProvider<FeedCubit>.value(value: mockFeedCubit),
|
||||
],
|
||||
child: BlogPostCreateScreen(feedBlogPostModel: feedBlogPostModel),
|
||||
@@ -87,28 +104,46 @@ void main() {
|
||||
);
|
||||
}
|
||||
|
||||
testWidgets('displays all form fields when creating new blog post', (tester) async {
|
||||
testWidgets('displays all form fields when creating new blog post', (
|
||||
tester,
|
||||
) async {
|
||||
await tester.pumpWidget(createTestWidget());
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
expect(find.text('Заголовок'), findsOneWidget);
|
||||
expect(find.text('Описание'), findsOneWidget);
|
||||
expect(find.text('Изображение поста'), findsOneWidget);
|
||||
expect(find.textContaining('Изображения'), findsOneWidget);
|
||||
expect(find.text('Ссылка на видео'), findsOneWidget);
|
||||
expect(find.byKey(AppKeys.blogPostCreateScreenTitleTextField), findsOneWidget);
|
||||
expect(find.byKey(AppKeys.blogPostCreateScreenContentTextField), findsOneWidget);
|
||||
expect(find.byKey(AppKeys.blogPostCreateScreenVideoLinkTextField), findsOneWidget);
|
||||
expect(
|
||||
find.byKey(AppKeys.blogPostCreateScreenTitleTextField),
|
||||
findsOneWidget,
|
||||
);
|
||||
expect(
|
||||
find.byKey(AppKeys.blogPostCreateScreenContentTextField),
|
||||
findsOneWidget,
|
||||
);
|
||||
expect(
|
||||
find.byKey(AppKeys.blogPostCreateScreenVideoLinkTextField),
|
||||
findsOneWidget,
|
||||
);
|
||||
});
|
||||
|
||||
testWidgets('pre-fills form fields when editing existing blog post', (tester) async {
|
||||
testWidgets('pre-fills form fields when editing existing blog post', (
|
||||
tester,
|
||||
) async {
|
||||
final existingPost = FeedBlogPostModel(
|
||||
id: 1,
|
||||
title: 'Test Title',
|
||||
description: 'Test Description',
|
||||
videoUrl: 'https://youtube.com/watch?v=test',
|
||||
image: '', // Changed: removed image so video field is visible
|
||||
videoUrl: 'https://rutube.ru/video/12345/',
|
||||
image: '',
|
||||
state: 'published',
|
||||
author: const Author(authorSlug: 'test-author', authorName: 'Test Author', authorProfileIcon: 'avatar.jpg', authorIsMe: true),
|
||||
author: const Author(
|
||||
authorSlug: 'test-author',
|
||||
authorName: 'Test Author',
|
||||
authorProfileIcon: 'avatar.jpg',
|
||||
authorIsMe: true,
|
||||
),
|
||||
createdAt: DateTime.now(),
|
||||
updatedAt: DateTime.now(),
|
||||
totalLikes: 0,
|
||||
@@ -117,24 +152,299 @@ void main() {
|
||||
wasLikedByMe: false,
|
||||
);
|
||||
|
||||
await tester.pumpWidget(createTestWidget(feedBlogPostModel: existingPost));
|
||||
await tester.pumpWidget(
|
||||
createTestWidget(feedBlogPostModel: existingPost),
|
||||
);
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
final titleField = tester.widget<CustomTextFormField>(find.byKey(AppKeys.blogPostCreateScreenTitleTextField));
|
||||
final titleField = tester.widget<CustomTextFormField>(
|
||||
find.byKey(AppKeys.blogPostCreateScreenTitleTextField),
|
||||
);
|
||||
expect(titleField.controller?.text, 'Test Title');
|
||||
|
||||
final descriptionField = tester.widget<CustomTextFormField>(find.byKey(AppKeys.blogPostCreateScreenContentTextField));
|
||||
final descriptionField = tester.widget<CustomTextFormField>(
|
||||
find.byKey(AppKeys.blogPostCreateScreenContentTextField),
|
||||
);
|
||||
expect(descriptionField.controller?.text, 'Test Description');
|
||||
|
||||
final videoFieldFinder = find.byKey(AppKeys.blogPostCreateScreenVideoLinkTextField);
|
||||
if (videoFieldFinder.evaluate().isNotEmpty) {
|
||||
final videoField = tester.widget<CustomTextFormField>(videoFieldFinder);
|
||||
expect(videoField.controller?.text, 'https://youtube.com/watch?v=test');
|
||||
}
|
||||
final videoField = tester.widget<CustomTextFormField>(
|
||||
find.byKey(AppKeys.blogPostCreateScreenVideoLinkTextField),
|
||||
);
|
||||
expect(videoField.controller?.text, 'https://rutube.ru/video/12345/');
|
||||
expect(find.text('Сохранить'), findsOneWidget);
|
||||
});
|
||||
|
||||
testWidgets('hides video field when image is present', (tester) async {
|
||||
testWidgets('shows video and gallery fields together', (tester) async {
|
||||
await mockNetworkImagesFor(() async {
|
||||
final existingPost = FeedBlogPostModel(
|
||||
id: 1,
|
||||
title: 'Test Title',
|
||||
description: 'Test Description',
|
||||
videoUrl: 'https://rutube.ru/video/12345/',
|
||||
image: 'http://localhost/storage/test_image.jpg',
|
||||
state: 'published',
|
||||
author: const Author(
|
||||
authorSlug: 'test-author',
|
||||
authorName: 'Test Author',
|
||||
authorProfileIcon: 'http://localhost/storage/avatar.jpg',
|
||||
authorIsMe: true,
|
||||
),
|
||||
createdAt: DateTime.now(),
|
||||
updatedAt: DateTime.now(),
|
||||
totalLikes: 0,
|
||||
totalViews: 0,
|
||||
totalComments: 0,
|
||||
wasLikedByMe: false,
|
||||
images: const [
|
||||
FeedGalleryImage(
|
||||
id: 10,
|
||||
url: 'http://localhost/storage/test_image.jpg',
|
||||
),
|
||||
],
|
||||
);
|
||||
|
||||
await tester.pumpWidget(
|
||||
createTestWidget(feedBlogPostModel: existingPost),
|
||||
);
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
expect(find.text('Ссылка на видео'), findsOneWidget);
|
||||
expect(
|
||||
find.byKey(AppKeys.blogPostCreateScreenVideoLinkTextField),
|
||||
findsOneWidget,
|
||||
);
|
||||
expect(find.textContaining('Изображения'), findsOneWidget);
|
||||
});
|
||||
});
|
||||
|
||||
testWidgets('allows entering text in title field', (tester) async {
|
||||
await tester.pumpWidget(createTestWidget());
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
await tester.enterText(
|
||||
find.byKey(AppKeys.blogPostCreateScreenTitleTextField),
|
||||
'New Title',
|
||||
);
|
||||
await tester.pump();
|
||||
|
||||
final titleField = tester.widget<CustomTextFormField>(
|
||||
find.byKey(AppKeys.blogPostCreateScreenTitleTextField),
|
||||
);
|
||||
expect(titleField.controller?.text, 'New Title');
|
||||
});
|
||||
|
||||
testWidgets('allows entering text in description field', (tester) async {
|
||||
await tester.pumpWidget(createTestWidget());
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
await tester.enterText(
|
||||
find.byKey(AppKeys.blogPostCreateScreenContentTextField),
|
||||
'New Description',
|
||||
);
|
||||
await tester.pump();
|
||||
|
||||
final descriptionField = tester.widget<CustomTextFormField>(
|
||||
find.byKey(AppKeys.blogPostCreateScreenContentTextField),
|
||||
);
|
||||
expect(descriptionField.controller?.text, 'New Description');
|
||||
});
|
||||
|
||||
testWidgets('allows entering text in video URL field', (tester) async {
|
||||
await tester.pumpWidget(createTestWidget());
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
await tester.enterText(
|
||||
find.byKey(AppKeys.blogPostCreateScreenVideoLinkTextField),
|
||||
'https://youtube.com/watch?v=new',
|
||||
);
|
||||
await tester.pump();
|
||||
|
||||
final videoField = tester.widget<CustomTextFormField>(
|
||||
find.byKey(AppKeys.blogPostCreateScreenVideoLinkTextField),
|
||||
);
|
||||
expect(videoField.controller?.text, 'https://youtube.com/watch?v=new');
|
||||
});
|
||||
|
||||
testWidgets('displays create button', (tester) async {
|
||||
await tester.pumpWidget(createTestWidget());
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
expect(find.text('Создать'), findsOneWidget);
|
||||
});
|
||||
|
||||
testWidgets('does not submit when form is empty', (tester) async {
|
||||
when(
|
||||
() => mockBlogPostRepository.createBlogPost(
|
||||
title: any(named: 'title'),
|
||||
videoUrl: any(named: 'videoUrl'),
|
||||
description: any(named: 'description'),
|
||||
images: any(named: 'images'),
|
||||
),
|
||||
).thenAnswer((_) async {});
|
||||
|
||||
await tester.pumpWidget(createTestWidget());
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
final createButton = find.text('Создать');
|
||||
await tester.ensureVisible(createButton);
|
||||
await tester.tap(createButton);
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
verifyNever(
|
||||
() => mockBlogPostRepository.createBlogPost(
|
||||
title: any(named: 'title'),
|
||||
videoUrl: any(named: 'videoUrl'),
|
||||
description: any(named: 'description'),
|
||||
images: any(named: 'images'),
|
||||
),
|
||||
);
|
||||
expect(
|
||||
find.text(
|
||||
'Добавьте заголовок, описание, изображение или ссылку на видео',
|
||||
),
|
||||
findsOneWidget,
|
||||
);
|
||||
});
|
||||
|
||||
testWidgets('does not submit when video url is invalid', (tester) async {
|
||||
when(
|
||||
() => mockBlogPostRepository.createBlogPost(
|
||||
title: any(named: 'title'),
|
||||
videoUrl: any(named: 'videoUrl'),
|
||||
description: any(named: 'description'),
|
||||
images: any(named: 'images'),
|
||||
),
|
||||
).thenAnswer((_) async {});
|
||||
|
||||
await tester.pumpWidget(createTestWidget());
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
await tester.enterText(
|
||||
find.byKey(AppKeys.blogPostCreateScreenTitleTextField),
|
||||
'Test Title',
|
||||
);
|
||||
await tester.enterText(
|
||||
find.byKey(AppKeys.blogPostCreateScreenVideoLinkTextField),
|
||||
'https://youtube.com/watch?v=abc',
|
||||
);
|
||||
await tester.pump();
|
||||
|
||||
final createButton = find.text('Создать');
|
||||
await tester.ensureVisible(createButton);
|
||||
await tester.tap(createButton);
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
verifyNever(
|
||||
() => mockBlogPostRepository.createBlogPost(
|
||||
title: any(named: 'title'),
|
||||
videoUrl: any(named: 'videoUrl'),
|
||||
description: any(named: 'description'),
|
||||
images: any(named: 'images'),
|
||||
),
|
||||
);
|
||||
expect(
|
||||
find.text('Поддерживаются только Rutube, VK и OK'),
|
||||
findsOneWidget,
|
||||
);
|
||||
});
|
||||
|
||||
testWidgets('shows loading state when creating blog post', (tester) async {
|
||||
when(
|
||||
() => mockBlogPostRepository.createBlogPost(
|
||||
title: any(named: 'title'),
|
||||
videoUrl: any(named: 'videoUrl'),
|
||||
description: any(named: 'description'),
|
||||
images: any(named: 'images'),
|
||||
),
|
||||
).thenAnswer((_) async {
|
||||
await Future.delayed(const Duration(milliseconds: 100));
|
||||
return;
|
||||
});
|
||||
|
||||
await tester.pumpWidget(createTestWidget());
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
await tester.enterText(
|
||||
find.byKey(AppKeys.blogPostCreateScreenTitleTextField),
|
||||
'Test Title',
|
||||
);
|
||||
await tester.enterText(
|
||||
find.byKey(AppKeys.blogPostCreateScreenContentTextField),
|
||||
'Test Description',
|
||||
);
|
||||
await tester.pump();
|
||||
|
||||
final createButton = find.text('Создать');
|
||||
await tester.ensureVisible(createButton);
|
||||
await tester.tap(createButton);
|
||||
await tester.pump();
|
||||
|
||||
expect(find.byType(Loader), findsWidgets);
|
||||
|
||||
await tester.pump(const Duration(milliseconds: 150));
|
||||
await tester.pumpAndSettle();
|
||||
});
|
||||
|
||||
testWidgets(
|
||||
'calls FeedCubit.loadFeedBlogPostList when blog post is created successfully',
|
||||
(tester) async {
|
||||
when(
|
||||
() => mockBlogPostRepository.createBlogPost(
|
||||
title: any(named: 'title'),
|
||||
videoUrl: any(named: 'videoUrl'),
|
||||
description: any(named: 'description'),
|
||||
images: any(named: 'images'),
|
||||
),
|
||||
).thenAnswer((_) async {
|
||||
return;
|
||||
});
|
||||
|
||||
await tester.pumpWidget(createTestWidget());
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
final titleField = find.byKey(
|
||||
AppKeys.blogPostCreateScreenTitleTextField,
|
||||
);
|
||||
final contentField = find.byKey(
|
||||
AppKeys.blogPostCreateScreenContentTextField,
|
||||
);
|
||||
|
||||
await tester.ensureVisible(titleField);
|
||||
await tester.ensureVisible(contentField);
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
await tester.enterText(titleField, 'Test Title');
|
||||
await tester.enterText(contentField, 'Test Description');
|
||||
await tester.pump();
|
||||
|
||||
final createButton = find.text('Создать');
|
||||
await tester.ensureVisible(createButton);
|
||||
await tester.tap(createButton, warnIfMissed: false);
|
||||
await tester.pump();
|
||||
|
||||
await tester.pump(const Duration(milliseconds: 200));
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
verify(() => mockFeedCubit.loadFeedBlogPostList()).called(1);
|
||||
},
|
||||
);
|
||||
|
||||
testWidgets('does not submit update when video url is invalid', (
|
||||
tester,
|
||||
) async {
|
||||
await mockNetworkImagesFor(() async {
|
||||
when(
|
||||
() => mockBlogPostRepository.updateBlogPost(
|
||||
id: any(named: 'id'),
|
||||
title: any(named: 'title'),
|
||||
videoUrl: any(named: 'videoUrl'),
|
||||
description: any(named: 'description'),
|
||||
existingMediaIds: any(named: 'existingMediaIds'),
|
||||
newImages: any(named: 'newImages'),
|
||||
mediaIdsToDelete: any(named: 'mediaIdsToDelete'),
|
||||
),
|
||||
).thenAnswer((_) async {});
|
||||
|
||||
final existingPost = FeedBlogPostModel(
|
||||
id: 1,
|
||||
title: 'Test Title',
|
||||
@@ -154,136 +464,140 @@ void main() {
|
||||
totalViews: 0,
|
||||
totalComments: 0,
|
||||
wasLikedByMe: false,
|
||||
images: const [
|
||||
FeedGalleryImage(
|
||||
id: 10,
|
||||
url: 'http://localhost/storage/test_image.jpg',
|
||||
),
|
||||
],
|
||||
);
|
||||
|
||||
await tester.pumpWidget(createTestWidget(feedBlogPostModel: existingPost));
|
||||
await tester.pumpWidget(
|
||||
createTestWidget(feedBlogPostModel: existingPost),
|
||||
);
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
expect(find.text('Ссылка на видео'), findsNothing);
|
||||
expect(find.byKey(AppKeys.blogPostCreateScreenVideoLinkTextField), findsNothing);
|
||||
await tester.enterText(
|
||||
find.byKey(AppKeys.blogPostCreateScreenVideoLinkTextField),
|
||||
'https://youtube.com/watch?v=abc',
|
||||
);
|
||||
await tester.pump();
|
||||
|
||||
final saveButton = find.text('Сохранить');
|
||||
await tester.ensureVisible(saveButton);
|
||||
await tester.tap(saveButton);
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
verifyNever(
|
||||
() => mockBlogPostRepository.updateBlogPost(
|
||||
id: any(named: 'id'),
|
||||
title: any(named: 'title'),
|
||||
videoUrl: any(named: 'videoUrl'),
|
||||
description: any(named: 'description'),
|
||||
existingMediaIds: any(named: 'existingMediaIds'),
|
||||
newImages: any(named: 'newImages'),
|
||||
mediaIdsToDelete: any(named: 'mediaIdsToDelete'),
|
||||
),
|
||||
);
|
||||
expect(
|
||||
find.text('Поддерживаются только Rutube, VK и OK'),
|
||||
findsOneWidget,
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
testWidgets('hides image field when video URL is present', (tester) async {
|
||||
final existingPost = FeedBlogPostModel(
|
||||
id: 1,
|
||||
title: 'Test Title',
|
||||
description: 'Test Description',
|
||||
videoUrl: 'https://youtube.com/watch?v=test',
|
||||
image: '',
|
||||
state: 'published',
|
||||
author: const Author(authorSlug: 'test-author', authorName: 'Test Author', authorProfileIcon: 'avatar.jpg', authorIsMe: true),
|
||||
createdAt: DateTime.now(),
|
||||
updatedAt: DateTime.now(),
|
||||
totalLikes: 0,
|
||||
totalViews: 0,
|
||||
totalComments: 0,
|
||||
wasLikedByMe: false,
|
||||
);
|
||||
|
||||
await tester.pumpWidget(createTestWidget(feedBlogPostModel: existingPost));
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
expect(find.text('Изображение поста'), findsNothing);
|
||||
});
|
||||
|
||||
testWidgets('allows entering text in title field', (tester) async {
|
||||
await tester.pumpWidget(createTestWidget());
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
await tester.enterText(find.byKey(AppKeys.blogPostCreateScreenTitleTextField), 'New Title');
|
||||
await tester.pump();
|
||||
|
||||
final titleField = tester.widget<CustomTextFormField>(find.byKey(AppKeys.blogPostCreateScreenTitleTextField));
|
||||
expect(titleField.controller?.text, 'New Title');
|
||||
});
|
||||
|
||||
testWidgets('allows entering text in description field', (tester) async {
|
||||
await tester.pumpWidget(createTestWidget());
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
await tester.enterText(find.byKey(AppKeys.blogPostCreateScreenContentTextField), 'New Description');
|
||||
await tester.pump();
|
||||
|
||||
final descriptionField = tester.widget<CustomTextFormField>(find.byKey(AppKeys.blogPostCreateScreenContentTextField));
|
||||
expect(descriptionField.controller?.text, 'New Description');
|
||||
});
|
||||
|
||||
testWidgets('allows entering text in video URL field', (tester) async {
|
||||
await tester.pumpWidget(createTestWidget());
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
await tester.enterText(find.byKey(AppKeys.blogPostCreateScreenVideoLinkTextField), 'https://youtube.com/watch?v=new');
|
||||
await tester.pump();
|
||||
|
||||
final videoField = tester.widget<CustomTextFormField>(find.byKey(AppKeys.blogPostCreateScreenVideoLinkTextField));
|
||||
expect(videoField.controller?.text, 'https://youtube.com/watch?v=new');
|
||||
});
|
||||
|
||||
testWidgets('displays create button', (tester) async {
|
||||
await tester.pumpWidget(createTestWidget());
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
expect(find.text('Создать'), findsOneWidget);
|
||||
});
|
||||
|
||||
testWidgets('shows loading state when creating blog post', (tester) async {
|
||||
when(mockBlogPostRepository.createBlogPost(any, any, any, any)).thenAnswer((_) async {
|
||||
await Future.delayed(const Duration(milliseconds: 100));
|
||||
return;
|
||||
});
|
||||
|
||||
await tester.pumpWidget(createTestWidget());
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
await tester.enterText(find.byKey(AppKeys.blogPostCreateScreenTitleTextField), 'Test Title');
|
||||
await tester.enterText(find.byKey(AppKeys.blogPostCreateScreenContentTextField), 'Test Description');
|
||||
await tester.pump();
|
||||
|
||||
final createButton = find.text('Создать');
|
||||
await tester.ensureVisible(createButton);
|
||||
await tester.tap(createButton);
|
||||
await tester.pump();
|
||||
|
||||
expect(find.byType(Loader), findsWidgets);
|
||||
|
||||
await tester.pump(const Duration(milliseconds: 150));
|
||||
await tester.pumpAndSettle();
|
||||
});
|
||||
|
||||
testWidgets('calls FeedCubit.loadFeedBlogPostList when blog post is created successfully', (tester) async {
|
||||
when(mockBlogPostRepository.createBlogPost(any, any, any, any)).thenAnswer((_) async {
|
||||
return;
|
||||
});
|
||||
|
||||
await tester.pumpWidget(createTestWidget());
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
final titleField = find.byKey(AppKeys.blogPostCreateScreenTitleTextField);
|
||||
final contentField = find.byKey(AppKeys.blogPostCreateScreenContentTextField);
|
||||
|
||||
await tester.ensureVisible(titleField);
|
||||
await tester.ensureVisible(contentField);
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
await tester.enterText(titleField, 'Test Title');
|
||||
await tester.enterText(contentField, 'Test Description');
|
||||
await tester.pump();
|
||||
|
||||
final createButton = find.text('Создать');
|
||||
await tester.ensureVisible(createButton);
|
||||
await tester.tap(createButton, warnIfMissed: false);
|
||||
await tester.pump();
|
||||
|
||||
await tester.pump(const Duration(milliseconds: 200));
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
verify(mockFeedCubit.loadFeedBlogPostList()).called(1);
|
||||
});
|
||||
|
||||
testWidgets('calls updateBlogPost when editing existing post', (tester) async {
|
||||
testWidgets('does not submit update when form content is cleared', (
|
||||
tester,
|
||||
) async {
|
||||
await mockNetworkImagesFor(() async {
|
||||
when(mockBlogPostRepository.updateBlogPost(any, any, any, any, any, any)).thenAnswer((_) async {
|
||||
when(
|
||||
() => mockBlogPostRepository.updateBlogPost(
|
||||
id: any(named: 'id'),
|
||||
title: any(named: 'title'),
|
||||
videoUrl: any(named: 'videoUrl'),
|
||||
description: any(named: 'description'),
|
||||
existingMediaIds: any(named: 'existingMediaIds'),
|
||||
newImages: any(named: 'newImages'),
|
||||
mediaIdsToDelete: any(named: 'mediaIdsToDelete'),
|
||||
),
|
||||
).thenAnswer((_) async {});
|
||||
|
||||
final existingPost = FeedBlogPostModel(
|
||||
id: 1,
|
||||
title: 'Test Title',
|
||||
description: 'Test Description',
|
||||
videoUrl: '',
|
||||
image: '',
|
||||
state: 'published',
|
||||
author: const Author(
|
||||
authorSlug: 'test-author',
|
||||
authorName: 'Test Author',
|
||||
authorProfileIcon: 'http://localhost/storage/avatar.jpg',
|
||||
authorIsMe: true,
|
||||
),
|
||||
createdAt: DateTime.now(),
|
||||
updatedAt: DateTime.now(),
|
||||
totalLikes: 0,
|
||||
totalViews: 0,
|
||||
totalComments: 0,
|
||||
wasLikedByMe: false,
|
||||
);
|
||||
|
||||
await tester.pumpWidget(
|
||||
createTestWidget(feedBlogPostModel: existingPost),
|
||||
);
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
await tester.enterText(
|
||||
find.byKey(AppKeys.blogPostCreateScreenTitleTextField),
|
||||
'',
|
||||
);
|
||||
await tester.enterText(
|
||||
find.byKey(AppKeys.blogPostCreateScreenContentTextField),
|
||||
'',
|
||||
);
|
||||
await tester.pump();
|
||||
|
||||
final saveButton = find.text('Сохранить');
|
||||
await tester.ensureVisible(saveButton);
|
||||
await tester.tap(saveButton);
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
verifyNever(
|
||||
() => mockBlogPostRepository.updateBlogPost(
|
||||
id: any(named: 'id'),
|
||||
title: any(named: 'title'),
|
||||
videoUrl: any(named: 'videoUrl'),
|
||||
description: any(named: 'description'),
|
||||
existingMediaIds: any(named: 'existingMediaIds'),
|
||||
newImages: any(named: 'newImages'),
|
||||
mediaIdsToDelete: any(named: 'mediaIdsToDelete'),
|
||||
),
|
||||
);
|
||||
expect(
|
||||
find.text(
|
||||
'Добавьте заголовок, описание, изображение или ссылку на видео',
|
||||
),
|
||||
findsOneWidget,
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
testWidgets('calls updateBlogPost when editing existing post', (
|
||||
tester,
|
||||
) async {
|
||||
await mockNetworkImagesFor(() async {
|
||||
when(
|
||||
() => mockBlogPostRepository.updateBlogPost(
|
||||
id: any(named: 'id'),
|
||||
title: any(named: 'title'),
|
||||
videoUrl: any(named: 'videoUrl'),
|
||||
description: any(named: 'description'),
|
||||
existingMediaIds: any(named: 'existingMediaIds'),
|
||||
newImages: any(named: 'newImages'),
|
||||
mediaIdsToDelete: any(named: 'mediaIdsToDelete'),
|
||||
),
|
||||
).thenAnswer((_) async {
|
||||
return;
|
||||
});
|
||||
|
||||
@@ -306,26 +620,46 @@ void main() {
|
||||
totalViews: 0,
|
||||
totalComments: 0,
|
||||
wasLikedByMe: false,
|
||||
images: const [
|
||||
FeedGalleryImage(
|
||||
id: 10,
|
||||
url: 'http://localhost/storage/test_image.jpg',
|
||||
),
|
||||
],
|
||||
);
|
||||
|
||||
await tester.pumpWidget(createTestWidget(feedBlogPostModel: existingPost, canPop: true));
|
||||
await tester.pumpWidget(
|
||||
createTestWidget(feedBlogPostModel: existingPost, canPop: true),
|
||||
);
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
final titleField = find.byKey(AppKeys.blogPostCreateScreenTitleTextField);
|
||||
final titleField = find.byKey(
|
||||
AppKeys.blogPostCreateScreenTitleTextField,
|
||||
);
|
||||
expect(titleField, findsOneWidget);
|
||||
|
||||
await tester.enterText(titleField, 'Updated Title');
|
||||
await tester.pump();
|
||||
|
||||
final createButton = find.text('Создать');
|
||||
await tester.ensureVisible(createButton);
|
||||
await tester.tap(createButton, warnIfMissed: false);
|
||||
final saveButton = find.text('Сохранить');
|
||||
await tester.ensureVisible(saveButton);
|
||||
await tester.tap(saveButton, warnIfMissed: false);
|
||||
await tester.pump();
|
||||
|
||||
await tester.pump(const Duration(milliseconds: 200));
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
verify(mockBlogPostRepository.updateBlogPost(any, any, any, any, any, any)).called(1);
|
||||
verify(
|
||||
() => mockBlogPostRepository.updateBlogPost(
|
||||
id: any(named: 'id'),
|
||||
title: any(named: 'title'),
|
||||
videoUrl: any(named: 'videoUrl'),
|
||||
description: any(named: 'description'),
|
||||
existingMediaIds: any(named: 'existingMediaIds'),
|
||||
newImages: any(named: 'newImages'),
|
||||
mediaIdsToDelete: any(named: 'mediaIdsToDelete'),
|
||||
),
|
||||
).called(1);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -344,8 +678,9 @@ void main() {
|
||||
});
|
||||
|
||||
testWidgets('renders error state', (tester) async {
|
||||
|
||||
blogPostCreateCubit.emit(const BlogPostCreateState.error(errorMessage: 'Error creating post'));
|
||||
blogPostCreateCubit.emit(
|
||||
const BlogPostCreateState(errorMessage: 'Error creating post'),
|
||||
);
|
||||
|
||||
await tester.pumpWidget(createTestWidget());
|
||||
await tester.pumpAndSettle();
|
||||
@@ -354,7 +689,7 @@ void main() {
|
||||
});
|
||||
|
||||
testWidgets('renders loading state', (tester) async {
|
||||
blogPostCreateCubit.emit(const BlogPostCreateState.loading());
|
||||
blogPostCreateCubit.emit(const BlogPostCreateState(isLoading: true));
|
||||
|
||||
await tester.pumpWidget(createTestWidget());
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
Reference in New Issue
Block a user