forked from eatvio/eatvio_app
feat(post_creation): added multiple image posts creation and validation
This commit is contained in:
@@ -21,10 +21,18 @@ void main() {
|
||||
|
||||
setUp(() {
|
||||
blogPostRepository = FixtureBlogPostRepository();
|
||||
blogPostCreateCubit = BlogPostCreateCubit(blogPostRepository: blogPostRepository);
|
||||
fullCreateFixture = FixtureReader.readAsMap('blog_post_create/create_request_full.json');
|
||||
updateFixture = FixtureReader.readAsMap('blog_post_create/update_request.json');
|
||||
deleteFixture = FixtureReader.readAsMap('blog_post_create/delete_request.json');
|
||||
blogPostCreateCubit = BlogPostCreateCubit(
|
||||
blogPostRepository: blogPostRepository,
|
||||
);
|
||||
fullCreateFixture = FixtureReader.readAsMap(
|
||||
'blog_post_create/create_request_full.json',
|
||||
);
|
||||
updateFixture = FixtureReader.readAsMap(
|
||||
'blog_post_create/update_request.json',
|
||||
);
|
||||
deleteFixture = FixtureReader.readAsMap(
|
||||
'blog_post_create/delete_request.json',
|
||||
);
|
||||
});
|
||||
|
||||
tearDown(() {
|
||||
@@ -41,7 +49,9 @@ void main() {
|
||||
);
|
||||
}
|
||||
|
||||
testWidgets('should handle blog post creation with all fields', (WidgetTester tester) async {
|
||||
testWidgets('should handle blog post creation with all fields', (
|
||||
WidgetTester tester,
|
||||
) async {
|
||||
// Arrange
|
||||
Widget formWidget = Column(
|
||||
children: [
|
||||
@@ -69,7 +79,6 @@ void main() {
|
||||
title: fullCreateFixture['title'] as String?,
|
||||
description: fullCreateFixture['description_md'] as String?,
|
||||
videoUrl: fullCreateFixture['video_url'] as String?,
|
||||
image: null,
|
||||
);
|
||||
},
|
||||
child: const Text('Опубликовать'),
|
||||
@@ -81,9 +90,18 @@ void main() {
|
||||
await tester.pumpWidget(createTestWidget(formWidget));
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
await tester.enterText(find.byKey(AppKeys.blogPostCreateScreenTitleTextField), fullCreateFixture['title'] as String);
|
||||
await tester.enterText(find.byKey(AppKeys.blogPostCreateScreenContentTextField), fullCreateFixture['description_md'] as String);
|
||||
await tester.enterText(find.byKey(AppKeys.blogPostCreateScreenVideoLinkTextField), fullCreateFixture['video_url'] as String);
|
||||
await tester.enterText(
|
||||
find.byKey(AppKeys.blogPostCreateScreenTitleTextField),
|
||||
fullCreateFixture['title'] as String,
|
||||
);
|
||||
await tester.enterText(
|
||||
find.byKey(AppKeys.blogPostCreateScreenContentTextField),
|
||||
fullCreateFixture['description_md'] as String,
|
||||
);
|
||||
await tester.enterText(
|
||||
find.byKey(AppKeys.blogPostCreateScreenVideoLinkTextField),
|
||||
fullCreateFixture['video_url'] as String,
|
||||
);
|
||||
await tester.pump();
|
||||
|
||||
await tester.tap(find.byKey(AppKeys.blogPostCreateScreenCreateButton));
|
||||
@@ -96,7 +114,9 @@ void main() {
|
||||
);
|
||||
});
|
||||
|
||||
testWidgets('should handle blog post creation with image', (WidgetTester tester) async {
|
||||
testWidgets('should handle blog post creation with image', (
|
||||
WidgetTester tester,
|
||||
) async {
|
||||
// Arrange
|
||||
final expectedPayload = Map<String, dynamic>.from(fullCreateFixture);
|
||||
expectedPayload['video_url'] = null;
|
||||
@@ -129,7 +149,11 @@ void main() {
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
Icon(Icons.add_photo_alternate, size: 48, color: Colors.grey),
|
||||
Icon(
|
||||
Icons.add_photo_alternate,
|
||||
size: 48,
|
||||
color: Colors.grey,
|
||||
),
|
||||
SizedBox(height: 8),
|
||||
Text('Добавить изображение'),
|
||||
],
|
||||
@@ -145,7 +169,6 @@ void main() {
|
||||
title: fullCreateFixture['title'] as String?,
|
||||
description: fullCreateFixture['description_md'] as String?,
|
||||
videoUrl: null,
|
||||
image: null, // В реальном тесте здесь был бы XFile
|
||||
);
|
||||
},
|
||||
child: const Text('Опубликовать'),
|
||||
@@ -157,15 +180,24 @@ void main() {
|
||||
await tester.pumpWidget(createTestWidget(formWidget));
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
await tester.enterText(find.byKey(AppKeys.blogPostCreateScreenTitleTextField), fullCreateFixture['title'] as String);
|
||||
await tester.enterText(find.byKey(AppKeys.blogPostCreateScreenContentTextField), fullCreateFixture['description_md'] as String);
|
||||
await tester.enterText(
|
||||
find.byKey(AppKeys.blogPostCreateScreenTitleTextField),
|
||||
fullCreateFixture['title'] as String,
|
||||
);
|
||||
await tester.enterText(
|
||||
find.byKey(AppKeys.blogPostCreateScreenContentTextField),
|
||||
fullCreateFixture['description_md'] as String,
|
||||
);
|
||||
await tester.pump();
|
||||
|
||||
await tester.tap(find.byKey(AppKeys.blogPostCreateScreenCreateButton));
|
||||
await tester.pump();
|
||||
|
||||
// Assert
|
||||
expect(blogPostRepository.createPayloads.single.toJson(), expectedPayload);
|
||||
expect(
|
||||
blogPostRepository.createPayloads.single.toJson(),
|
||||
expectedPayload,
|
||||
);
|
||||
});
|
||||
|
||||
testWidgets('should handle blog post update', (WidgetTester tester) async {
|
||||
@@ -175,7 +207,12 @@ void main() {
|
||||
state: 'published',
|
||||
image: 'original_image.jpg',
|
||||
description: 'Original Description',
|
||||
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,
|
||||
),
|
||||
videoUrl: 'https://youtube.com/watch?v=original',
|
||||
createdAt: DateTime.now(),
|
||||
updatedAt: DateTime.now(),
|
||||
@@ -216,8 +253,9 @@ void main() {
|
||||
title: updateFixture['title'] as String?,
|
||||
description: updateFixture['description_md'] as String?,
|
||||
videoUrl: updateFixture['video_url'] as String?,
|
||||
image: null,
|
||||
oldImageUrl: updateFixture['old_image_url'] as String?,
|
||||
existingMediaIds: List<int>.from(
|
||||
updateFixture['existing_media_ids'] as List,
|
||||
),
|
||||
);
|
||||
},
|
||||
child: const Text('Обновить'),
|
||||
@@ -229,9 +267,18 @@ void main() {
|
||||
await tester.pumpWidget(createTestWidget(formWidget));
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
await tester.enterText(find.byKey(AppKeys.blogPostCreateScreenTitleTextField), updateFixture['title'] as String);
|
||||
await tester.enterText(find.byKey(AppKeys.blogPostCreateScreenContentTextField), updateFixture['description_md'] as String);
|
||||
await tester.enterText(find.byKey(AppKeys.blogPostCreateScreenVideoLinkTextField), updateFixture['video_url'] as String);
|
||||
await tester.enterText(
|
||||
find.byKey(AppKeys.blogPostCreateScreenTitleTextField),
|
||||
updateFixture['title'] as String,
|
||||
);
|
||||
await tester.enterText(
|
||||
find.byKey(AppKeys.blogPostCreateScreenContentTextField),
|
||||
updateFixture['description_md'] as String,
|
||||
);
|
||||
await tester.enterText(
|
||||
find.byKey(AppKeys.blogPostCreateScreenVideoLinkTextField),
|
||||
updateFixture['video_url'] as String,
|
||||
);
|
||||
await tester.pump();
|
||||
|
||||
await tester.tap(find.byKey(AppKeys.blogPostCreateScreenCreateButton));
|
||||
@@ -241,7 +288,9 @@ void main() {
|
||||
expect(blogPostRepository.updatePayloads.single.toJson(), updateFixture);
|
||||
});
|
||||
|
||||
testWidgets('should handle blog post deletion', (WidgetTester tester) async {
|
||||
testWidgets('should handle blog post deletion', (
|
||||
WidgetTester tester,
|
||||
) async {
|
||||
// Arrange
|
||||
Widget deleteWidget = ElevatedButton(
|
||||
key: const Key('deleteBlogPostButton'),
|
||||
@@ -262,18 +311,21 @@ void main() {
|
||||
expect(blogPostRepository.deleteCalls.single, deleteFixture['id']);
|
||||
});
|
||||
|
||||
testWidgets('should handle loading state during blog post creation', (WidgetTester tester) async {
|
||||
testWidgets('should handle loading state during blog post creation', (
|
||||
WidgetTester tester,
|
||||
) async {
|
||||
// Arrange
|
||||
blogPostRepository.createDelay = 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');
|
||||
},
|
||||
);
|
||||
Widget loadingWidget =
|
||||
BlocBuilder<BlogPostCreateCubit, BlogPostCreateState>(
|
||||
builder: (context, state) {
|
||||
if (state.isLoading) {
|
||||
return const Center(child: CircularProgressIndicator());
|
||||
}
|
||||
return const Text('Not Loading');
|
||||
},
|
||||
);
|
||||
|
||||
// Act
|
||||
await tester.pumpWidget(createTestWidget(loadingWidget));
|
||||
@@ -284,7 +336,6 @@ void main() {
|
||||
title: fullCreateFixture['title'] as String?,
|
||||
videoUrl: null,
|
||||
description: fullCreateFixture['description_md'] as String?,
|
||||
image: null,
|
||||
);
|
||||
await tester.pump();
|
||||
await tester.pump(); // Need to pump twice to get the loading state
|
||||
@@ -297,18 +348,21 @@ void main() {
|
||||
await tester.pumpAndSettle();
|
||||
});
|
||||
|
||||
testWidgets('should handle error state during blog post creation', (WidgetTester tester) async {
|
||||
testWidgets('should handle error state during blog post creation', (
|
||||
WidgetTester tester,
|
||||
) async {
|
||||
// Arrange
|
||||
blogPostRepository.createError = 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');
|
||||
},
|
||||
);
|
||||
Widget errorWidget =
|
||||
BlocBuilder<BlogPostCreateCubit, BlogPostCreateState>(
|
||||
builder: (context, state) {
|
||||
if (state.errorMessage != null) {
|
||||
return Center(child: Text('Error: ${state.errorMessage}'));
|
||||
}
|
||||
return const Text('No Error');
|
||||
},
|
||||
);
|
||||
|
||||
// Act
|
||||
await tester.pumpWidget(createTestWidget(errorWidget));
|
||||
@@ -319,7 +373,6 @@ void main() {
|
||||
title: fullCreateFixture['title'] as String?,
|
||||
videoUrl: null,
|
||||
description: fullCreateFixture['description_md'] as String?,
|
||||
image: null,
|
||||
);
|
||||
await tester.pump();
|
||||
await tester.pump(); // Need to pump twice to get the error state
|
||||
@@ -328,16 +381,21 @@ void main() {
|
||||
expect(find.textContaining('Error:'), findsOneWidget);
|
||||
});
|
||||
|
||||
testWidgets('should handle success state after blog post creation', (WidgetTester tester) async {
|
||||
testWidgets('should handle success state after blog post creation', (
|
||||
WidgetTester tester,
|
||||
) async {
|
||||
// Arrange
|
||||
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');
|
||||
},
|
||||
);
|
||||
Widget successWidget =
|
||||
BlocBuilder<BlogPostCreateCubit, BlogPostCreateState>(
|
||||
builder: (context, state) {
|
||||
if (state.isSuccess) {
|
||||
return const Center(
|
||||
child: Text('Blog post created successfully!'),
|
||||
);
|
||||
}
|
||||
return const Text('Not Loaded');
|
||||
},
|
||||
);
|
||||
|
||||
// Act
|
||||
await tester.pumpWidget(createTestWidget(successWidget));
|
||||
@@ -348,7 +406,6 @@ void main() {
|
||||
title: fullCreateFixture['title'] as String?,
|
||||
videoUrl: null,
|
||||
description: fullCreateFixture['description_md'] as String?,
|
||||
image: null,
|
||||
);
|
||||
await tester.pump();
|
||||
await tester.pump(); // Need to pump twice to get the success state
|
||||
@@ -357,23 +414,35 @@ void main() {
|
||||
expect(find.text('Blog post created successfully!'), findsOneWidget);
|
||||
});
|
||||
|
||||
testWidgets('should validate form fields before submission', (WidgetTester tester) async {
|
||||
testWidgets('should validate form fields before submission', (
|
||||
WidgetTester tester,
|
||||
) async {
|
||||
// Arrange
|
||||
Widget validationWidget = Column(
|
||||
children: [
|
||||
TextFormField(
|
||||
key: AppKeys.blogPostCreateScreenTitleTextField,
|
||||
decoration: const InputDecoration(labelText: 'Заголовок', errorText: 'Заголовок не может быть пустым'),
|
||||
decoration: const InputDecoration(
|
||||
labelText: 'Заголовок',
|
||||
errorText: 'Заголовок не может быть пустым',
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
TextFormField(
|
||||
key: AppKeys.blogPostCreateScreenContentTextField,
|
||||
decoration: const InputDecoration(labelText: 'Описание', errorText: 'Описание не может быть пустым'),
|
||||
decoration: const InputDecoration(
|
||||
labelText: 'Описание',
|
||||
errorText: 'Описание не может быть пустым',
|
||||
),
|
||||
maxLines: 6,
|
||||
minLines: 4,
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
ElevatedButton(key: AppKeys.blogPostCreateScreenCreateButton, onPressed: () {}, child: const Text('Опубликовать')),
|
||||
ElevatedButton(
|
||||
key: AppKeys.blogPostCreateScreenCreateButton,
|
||||
onPressed: () {},
|
||||
child: const Text('Опубликовать'),
|
||||
),
|
||||
],
|
||||
);
|
||||
|
||||
@@ -386,7 +455,9 @@ void main() {
|
||||
expect(find.text('Описание не может быть пустым'), findsOneWidget);
|
||||
});
|
||||
|
||||
testWidgets('should handle image picker interaction', (WidgetTester tester) async {
|
||||
testWidgets('should handle image picker interaction', (
|
||||
WidgetTester tester,
|
||||
) async {
|
||||
// Arrange
|
||||
bool imagePickerCalled = false;
|
||||
Widget imagePickerWidget = GestureDetector(
|
||||
@@ -425,16 +496,25 @@ void main() {
|
||||
expect(imagePickerCalled, isTrue);
|
||||
});
|
||||
|
||||
testWidgets('should handle video URL validation', (WidgetTester tester) async {
|
||||
testWidgets('should handle video URL validation', (
|
||||
WidgetTester tester,
|
||||
) async {
|
||||
// Arrange
|
||||
Widget videoValidationWidget = Column(
|
||||
children: [
|
||||
TextFormField(
|
||||
key: AppKeys.blogPostCreateScreenVideoLinkTextField,
|
||||
decoration: const InputDecoration(labelText: 'Ссылка на видео', errorText: 'Введите корректную ссылку на видео'),
|
||||
decoration: const InputDecoration(
|
||||
labelText: 'Ссылка на видео',
|
||||
errorText: 'Введите корректную ссылку на видео',
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
ElevatedButton(key: AppKeys.blogPostCreateScreenCreateButton, onPressed: () {}, child: const Text('Опубликовать')),
|
||||
ElevatedButton(
|
||||
key: AppKeys.blogPostCreateScreenCreateButton,
|
||||
onPressed: () {},
|
||||
child: const Text('Опубликовать'),
|
||||
),
|
||||
],
|
||||
);
|
||||
|
||||
@@ -446,7 +526,9 @@ void main() {
|
||||
expect(find.text('Введите корректную ссылку на видео'), findsOneWidget);
|
||||
});
|
||||
|
||||
testWidgets('should handle form submission with empty fields', (WidgetTester tester) async {
|
||||
testWidgets('should handle form submission with empty fields', (
|
||||
WidgetTester tester,
|
||||
) async {
|
||||
// Arrange
|
||||
Widget emptyFormWidget = Column(
|
||||
children: [
|
||||
@@ -465,7 +547,11 @@ void main() {
|
||||
ElevatedButton(
|
||||
key: AppKeys.blogPostCreateScreenCreateButton,
|
||||
onPressed: () async {
|
||||
await blogPostCreateCubit.createBlogPost(title: '', videoUrl: '', description: '', image: null);
|
||||
await blogPostCreateCubit.createBlogPost(
|
||||
title: '',
|
||||
videoUrl: '',
|
||||
description: '',
|
||||
);
|
||||
},
|
||||
child: const Text('Опубликовать'),
|
||||
),
|
||||
@@ -479,14 +565,14 @@ void main() {
|
||||
await tester.tap(find.byKey(AppKeys.blogPostCreateScreenCreateButton));
|
||||
await tester.pump();
|
||||
|
||||
// Assert
|
||||
expect(
|
||||
blogPostRepository.createPayloads.single.toJson(),
|
||||
{'title': '', 'description_md': '', 'video_url': '', 'image_name': null},
|
||||
);
|
||||
// Assert — invalid empty payload must not reach the repository
|
||||
expect(blogPostRepository.createPayloads, isEmpty);
|
||||
expect(blogPostCreateCubit.state.hasValidationErrors, isTrue);
|
||||
});
|
||||
|
||||
testWidgets('should handle blog post creation with long content', (WidgetTester tester) async {
|
||||
testWidgets('should handle blog post creation with long content', (
|
||||
WidgetTester tester,
|
||||
) async {
|
||||
// Arrange
|
||||
const longContent =
|
||||
'Это очень длинное описание блог поста, которое содержит много текста и должно быть правильно обработано системой. '
|
||||
@@ -510,7 +596,11 @@ void main() {
|
||||
ElevatedButton(
|
||||
key: AppKeys.blogPostCreateScreenCreateButton,
|
||||
onPressed: () async {
|
||||
await blogPostCreateCubit.createBlogPost(title: 'Test Title', videoUrl: null, description: longContent, image: null);
|
||||
await blogPostCreateCubit.createBlogPost(
|
||||
title: 'Test Title',
|
||||
videoUrl: null,
|
||||
description: longContent,
|
||||
);
|
||||
},
|
||||
child: const Text('Опубликовать'),
|
||||
),
|
||||
@@ -521,23 +611,26 @@ void main() {
|
||||
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.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
|
||||
expect(
|
||||
blogPostRepository.createPayloads.single.toJson(),
|
||||
{
|
||||
'title': 'Test Title',
|
||||
'description_md': longContent,
|
||||
'video_url': null,
|
||||
'image_name': null,
|
||||
},
|
||||
);
|
||||
expect(blogPostRepository.createPayloads.single.toJson(), {
|
||||
'title': 'Test Title',
|
||||
'description_md': longContent,
|
||||
'video_url': null,
|
||||
'image_name': null,
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user