chore(version): update app version to 1.3.5+85 and improve test coverage reporting in CI

This commit is contained in:
mso7nueh
2025-12-08 11:19:08 +03:00
parent abe4985f0e
commit c2a506cdbf
148 changed files with 12395 additions and 1889 deletions

View File

@@ -0,0 +1,85 @@
import 'package:eatvio_app/features/auth/entities/auth_result_entity.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:vk_id/vk_id.dart';
void main() {
group('AuthResultEntity', () {
test('создает экземпляр с данными', () {
const entity = AuthResultEntity(
accessToken: 'token',
userId: 'user123',
clientId: 'client123',
refreshToken: 'refresh',
firstName: 'John',
lastName: 'Doe',
email: 'john@example.com',
phone: '+1234567890',
gender: GenderEnum.male,
birthDate: '1990-01-01',
);
expect(entity.accessToken, 'token');
expect(entity.userId, 'user123');
expect(entity.gender, GenderEnum.male);
});
test('создает экземпляр с минимальными данными', () {
const entity = AuthResultEntity(
accessToken: 'token',
userId: 'user123',
clientId: 'client123',
);
expect(entity.accessToken, 'token');
expect(entity.refreshToken, null);
expect(entity.gender, null);
});
});
group('GenderEnum', () {
test('fromVkGender - преобразует VkSex.male в GenderEnum.male', () {
final result = GenderEnum.fromVkGender(VkSex.male);
expect(result, GenderEnum.male);
});
test('fromVkGender - преобразует VkSex.female в GenderEnum.female', () {
final result = GenderEnum.fromVkGender(VkSex.female);
expect(result, GenderEnum.female);
});
test('fromVkGender - преобразует VkSex.notStated в GenderEnum.notSet', () {
final result = GenderEnum.fromVkGender(VkSex.notStated);
expect(result, GenderEnum.notSet);
});
test('fromVkGender - возвращает null для null', () {
final result = GenderEnum.fromVkGender(null);
expect(result, null);
});
test('fromString - преобразует "male" в GenderEnum.male', () {
final result = GenderEnum.fromString('male');
expect(result, GenderEnum.male);
});
test('fromString - преобразует "female" в GenderEnum.female', () {
final result = GenderEnum.fromString('female');
expect(result, GenderEnum.female);
});
test('fromString - преобразует "notSet" в GenderEnum.notSet', () {
final result = GenderEnum.fromString('notSet');
expect(result, GenderEnum.notSet);
});
test('fromString - возвращает null для неизвестного значения', () {
final result = GenderEnum.fromString('unknown');
expect(result, null);
});
test('fromString - возвращает null для null', () {
final result = GenderEnum.fromString(null);
expect(result, null);
});
});
}

View File

@@ -4,158 +4,169 @@ 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/repositories/feed_repository.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:mockito/annotations.dart';
import 'package:mockito/mockito.dart';
import 'package:mocktail/mocktail.dart';
import 'blog_post_cubit_test.mocks.dart';
class MockFeedRepository extends Mock implements FeedRepository {}
@GenerateNiceMocks([MockSpec<FeedRepository>()])
void main() {
group('BlogPostCubit', () {
BlogPostCubit? blogPostCubit;
late MockFeedRepository mockFeedRepository;
late BlogPostCubit cubit;
late MockFeedRepository mockRepository;
setUp(() {
mockFeedRepository = MockFeedRepository();
mockRepository = MockFeedRepository();
cubit = BlogPostCubit(mockRepository);
});
tearDown(() async {
if (blogPostCubit != null && !blogPostCubit!.isClosed) {
await blogPostCubit!.close();
blogPostCubit = null;
}
tearDown(() {
cubit.close();
});
test('начальное состояние - пустое', () {
blogPostCubit = BlogPostCubit(mockFeedRepository);
expect(blogPostCubit!.state, isA<BlogPostState>());
expect(blogPostCubit!.state.isLoading, isFalse);
expect(blogPostCubit!.state.blogPostModel, isNull);
test('начальное состояние - BlogPostState с isLoading: false', () {
expect(cubit.state, isA<BlogPostState>());
expect(cubit.state.isLoading, false);
});
test('getBlogPost - успешная загрузка поста', () async {
blogPostCubit = BlogPostCubit(mockFeedRepository);
final testPost = FeedBlogPostModel(
id: 1,
state: 'published',
image: 'https://example.com/image.jpg',
description: 'Test description',
author: const Author(
authorSlug: 'author',
authorName: 'Author Name',
authorProfileIcon: 'https://example.com/icon.jpg',
authorIsMe: false,
isEntitySubscribedByMe: null,
authorPlaceInRating: null,
),
const postId = 1;
final mockPost = FeedBlogPostModel(
id: postId,
title: 'Test Post',
state: 'active',
image: '',
description: 'Test Content',
videoUrl: '',
createdAt: DateTime.now(),
updatedAt: DateTime.now(),
title: 'Test Post',
totalLikes: 10,
totalViews: 100,
totalComments: 5,
totalLikes: 0,
totalViews: 0,
totalComments: 0,
wasLikedByMe: false,
author: const Author(authorName: 'Test Author', authorSlug: 'test-author', authorProfileIcon: '', authorIsMe: false),
);
when(mockFeedRepository.getBlogPost(id: 1))
.thenAnswer((_) async => testPost);
when(() => mockRepository.getBlogPost(id: postId)).thenAnswer((_) async => mockPost);
await blogPostCubit!.getBlogPost(1);
await cubit.getBlogPost(postId);
expect(blogPostCubit!.state.blogPostModel, isNotNull);
expect(blogPostCubit!.state.blogPostModel!.id, 1);
expect(blogPostCubit!.state.isLoading, isFalse);
expect(cubit.state.isLoading, false);
expect(cubit.state.blogPostModel, mockPost);
verify(() => mockRepository.getBlogPost(id: postId)).called(1);
});
test('getBlogPost - обработка ошибки', () async {
blogPostCubit = BlogPostCubit(mockFeedRepository);
const postId = 1;
when(mockFeedRepository.getBlogPost(id: anyNamed('id')))
.thenThrow(Exception('Ошибка загрузки'));
when(() => mockRepository.getBlogPost(id: postId)).thenThrow(Exception('Error'));
await blogPostCubit!.getBlogPost(1);
await cubit.getBlogPost(postId);
expect(blogPostCubit!.state.isLoading, isFalse);
expect(cubit.state.isLoading, false);
expect(cubit.state.blogPostModel, null);
});
test('toggleLike - лайк поста', () async {
blogPostCubit = BlogPostCubit(mockFeedRepository);
final testPost = FeedBlogPostModel(
id: 1,
state: 'published',
image: 'https://example.com/image.jpg',
description: 'Test description',
author: const Author(
authorSlug: 'author',
authorName: 'Author Name',
authorProfileIcon: 'https://example.com/icon.jpg',
authorIsMe: false,
isEntitySubscribedByMe: null,
authorPlaceInRating: null,
),
const postId = 1;
final mockPost = FeedBlogPostModel(
id: postId,
title: 'Test Post',
state: 'active',
image: '',
description: 'Test Content',
videoUrl: '',
createdAt: DateTime.now(),
updatedAt: DateTime.now(),
title: 'Test Post',
totalLikes: 10,
totalViews: 100,
totalComments: 5,
totalLikes: 5,
totalViews: 0,
totalComments: 0,
wasLikedByMe: false,
author: const Author(authorName: 'Test Author', authorSlug: 'test-author', authorProfileIcon: '', authorIsMe: false),
);
when(mockFeedRepository.getBlogPost(id: 1))
.thenAnswer((_) async => testPost);
when(mockFeedRepository.likeBlogPost(id: 1))
.thenAnswer((_) async => {});
when(() => mockRepository.likeBlogPost(id: postId)).thenAnswer((_) async => {});
await blogPostCubit!.getBlogPost(1);
await blogPostCubit!.toggleLike();
// Сначала загружаем пост
when(() => mockRepository.getBlogPost(id: postId)).thenAnswer((_) async => mockPost);
await cubit.getBlogPost(postId);
expect(blogPostCubit!.state.blogPostModel!.wasLikedByMe, isTrue);
expect(blogPostCubit!.state.blogPostModel!.totalLikes, 11);
// Затем лайкаем
await cubit.toggleLike();
expect(cubit.state.blogPostModel?.wasLikedByMe, true);
expect(cubit.state.blogPostModel?.totalLikes, 6);
verify(() => mockRepository.likeBlogPost(id: postId)).called(1);
});
test('toggleLike - убирает лайк', () async {
blogPostCubit = BlogPostCubit(mockFeedRepository);
final testPost = FeedBlogPostModel(
id: 1,
state: 'published',
image: 'https://example.com/image.jpg',
description: 'Test description',
author: const Author(
authorSlug: 'author',
authorName: 'Author Name',
authorProfileIcon: 'https://example.com/icon.jpg',
authorIsMe: false,
isEntitySubscribedByMe: null,
authorPlaceInRating: null,
),
const postId = 1;
final mockPost = FeedBlogPostModel(
id: postId,
title: 'Test Post',
state: 'active',
image: '',
description: 'Test Content',
videoUrl: '',
createdAt: DateTime.now(),
updatedAt: DateTime.now(),
title: 'Test Post',
totalLikes: 10,
totalViews: 100,
totalComments: 5,
totalLikes: 5,
totalViews: 0,
totalComments: 0,
wasLikedByMe: true,
author: const Author(authorName: 'Test Author', authorSlug: 'test-author', authorProfileIcon: '', authorIsMe: false),
);
when(mockFeedRepository.getBlogPost(id: 1))
.thenAnswer((_) async => testPost);
when(mockFeedRepository.unlikedBlogPost(id: 1))
.thenAnswer((_) async => {});
when(() => mockRepository.unlikedBlogPost(id: postId)).thenAnswer((_) async => {});
await blogPostCubit!.getBlogPost(1);
await blogPostCubit!.toggleLike();
// Сначала загружаем пост
when(() => mockRepository.getBlogPost(id: postId)).thenAnswer((_) async => mockPost);
await cubit.getBlogPost(postId);
expect(blogPostCubit!.state.blogPostModel!.wasLikedByMe, isFalse);
expect(blogPostCubit!.state.blogPostModel!.totalLikes, 9);
// Затем убираем лайк
await cubit.toggleLike();
expect(cubit.state.blogPostModel?.wasLikedByMe, false);
expect(cubit.state.blogPostModel?.totalLikes, 4);
verify(() => mockRepository.unlikedBlogPost(id: postId)).called(1);
});
test('toggleLike - откат при ошибке', () async {
const postId = 1;
final mockPost = FeedBlogPostModel(
id: postId,
title: 'Test Post',
state: 'active',
image: '',
description: 'Test Content',
videoUrl: '',
createdAt: DateTime.now(),
updatedAt: DateTime.now(),
totalLikes: 5,
totalViews: 0,
totalComments: 0,
wasLikedByMe: false,
author: const Author(authorName: 'Test Author', authorSlug: 'test-author', authorProfileIcon: '', authorIsMe: false),
);
when(() => mockRepository.likeBlogPost(id: postId)).thenThrow(Exception('Error'));
// Сначала загружаем пост
when(() => mockRepository.getBlogPost(id: postId)).thenAnswer((_) async => mockPost);
await cubit.getBlogPost(postId);
// Затем пытаемся лайкнуть (с ошибкой)
await cubit.toggleLike();
// Должен откатиться к исходному состоянию
expect(cubit.state.blogPostModel?.wasLikedByMe, false);
expect(cubit.state.blogPostModel?.totalLikes, 5);
});
test('toggleLike - ничего не делает если пост не загружен', () async {
await cubit.toggleLike();
expect(cubit.state.blogPostModel, null);
verifyNever(() => mockRepository.likeBlogPost(id: any(named: 'id')));
verifyNever(() => mockRepository.unlikedBlogPost(id: any(named: 'id')));
});
});
}

View File

@@ -0,0 +1,100 @@
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:flutter_test/flutter_test.dart';
import 'package:mocktail/mocktail.dart';
class MockBlogPostRepository extends Mock implements BlogPostRepository {}
void main() {
group('BlogPostCreateCubit', () {
late BlogPostCreateCubit cubit;
late MockBlogPostRepository mockRepository;
setUp(() {
mockRepository = MockBlogPostRepository();
cubit = BlogPostCreateCubit(blogPostRepository: mockRepository);
});
tearDown(() {
cubit.close();
});
test('начальное состояние - BlogPostCreateState', () {
expect(cubit.state, isA<BlogPostCreateState>());
});
test('createBlogPost - успешное создание', () async {
when(() => mockRepository.createBlogPost(any(), any(), any(), any()))
.thenAnswer((_) async => {});
final result = await cubit.createBlogPost();
expect(result, true);
expect(cubit.state, isA<BlogPostCreateStateLoaded>());
verify(() => mockRepository.createBlogPost(null, null, null, null)).called(1);
});
test('createBlogPost - обработка ошибки', () async {
when(() => mockRepository.createBlogPost(any(), any(), any(), any()))
.thenThrow(Exception('Error'));
final result = await cubit.createBlogPost();
expect(result, false);
expect(cubit.state, isA<BlogPostCreateStateError>());
final errorState = cubit.state as BlogPostCreateStateError;
expect(errorState.errorMessage, contains('Error'));
});
test('updateBlogPost - успешное обновление', () async {
const id = 1;
const title = 'Updated Title';
when(() => mockRepository.updateBlogPost(any(), any(), any(), any(), any(), any()))
.thenAnswer((_) async => {});
final result = await cubit.updateBlogPost(id: id, title: title);
expect(result, true);
expect(cubit.state, isA<BlogPostCreateStateLoaded>());
verify(() => mockRepository.updateBlogPost(id, title, null, null, null, null)).called(1);
});
test('updateBlogPost - обработка ошибки', () async {
const id = 1;
when(() => mockRepository.updateBlogPost(any(), any(), any(), any(), any(), any()))
.thenThrow(Exception('Error'));
final result = await cubit.updateBlogPost(id: id);
expect(result, false);
expect(cubit.state, isA<BlogPostCreateStateError>());
});
test('deleteBlogPost - успешное удаление', () async {
const id = 1;
when(() => mockRepository.deleteBlogPost(id))
.thenAnswer((_) async => {});
final result = await cubit.deleteBlogPost(id);
expect(result, true);
verify(() => mockRepository.deleteBlogPost(id)).called(1);
});
test('deleteBlogPost - обработка ошибки', () async {
const id = 1;
when(() => mockRepository.deleteBlogPost(id))
.thenThrow(Exception('Error'));
final result = await cubit.deleteBlogPost(id);
expect(result, false);
});
});
}

View File

@@ -0,0 +1,376 @@
import 'package:eatvio_app/app_keys.dart';
import 'package:eatvio_app/core/app_initializer.dart';
import 'package:eatvio_app/core/di/app_config_registration.dart';
import 'package:eatvio_app/core/locator_service.dart';
import 'package:eatvio_app/core/theme/theme_helper.dart';
import 'package:eatvio_app/core/widgets/custom_text_form_field.dart';
import 'package:eatvio_app/core/widgets/loader.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/presentation/pages/blog_post_create_screen.dart';
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/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';
import 'package:flutter/material.dart';
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:flutter_dotenv/flutter_dotenv.dart';
import 'package:network_image_mock/network_image_mock.dart';
import 'blog_post_create_screen_test.mocks.dart';
@GenerateMocks([BlogPostRepository, BlogPostCreateCubit, FeedCubit])
void main() {
TestWidgetsFlutterBinding.ensureInitialized();
group('BlogPostCreateScreen', () {
late MockBlogPostRepository mockBlogPostRepository;
late BlogPostCreateCubit blogPostCreateCubit;
late MockFeedCubit mockFeedCubit;
setUp(() async {
await sl.reset();
dotenv.loadFromString(envString: 'DEV_BASE_URL=http://localhost\nBASE_URL=http://localhost');
AppConfigRegistration.registerAppConfig(buildType: AppBuildType.tests);
mockBlogPostRepository = MockBlogPostRepository();
blogPostCreateCubit = BlogPostCreateCubit(blogPostRepository: mockBlogPostRepository);
mockFeedCubit = MockFeedCubit();
when(mockFeedCubit.state).thenReturn(const FeedState.loading());
when(mockFeedCubit.stream).thenAnswer((_) => Stream.value(const FeedState.loading()));
sl.registerLazySingleton<FeedCubit>(() => mockFeedCubit);
sl.registerLazySingleton<BlogPostCreateCubit>(() => blogPostCreateCubit);
});
tearDown(() {
blogPostCreateCubit.close();
});
Widget createTestWidget({FeedBlogPostModel? feedBlogPostModel, bool canPop = false}) {
final router = GoRouter(
initialLocation: '/blog-post-create',
routes: [
GoRoute(
path: '/home',
builder: (context, state) => Scaffold(
body: Center(child: Text('Home')),
),
),
GoRoute(
path: '/blog-post-create',
builder: (context, state) => MultiBlocProvider(
providers: [
BlocProvider<BlogPostCreateCubit>.value(
value: blogPostCreateCubit,
),
BlocProvider<FeedCubit>.value(
value: mockFeedCubit,
),
],
child: BlogPostCreateScreen(feedBlogPostModel: feedBlogPostModel),
),
),
],
);
return MaterialApp.router(
routerConfig: router,
theme: ThemeHelper().themeData(),
localizationsDelegates: [
S.delegate,
GlobalMaterialLocalizations.delegate,
GlobalWidgetsLocalizations.delegate,
GlobalCupertinoLocalizations.delegate,
],
supportedLocales: const [Locale('ru'), Locale('en')],
locale: const Locale('ru'),
);
}
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.text('Ссылка на видео'), 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 {
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
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();
final titleField = tester.widget<CustomTextFormField>(find.byKey(AppKeys.blogPostCreateScreenTitleTextField));
expect(titleField.controller?.text, 'Test Title');
final descriptionField = tester.widget<CustomTextFormField>(find.byKey(AppKeys.blogPostCreateScreenContentTextField));
expect(descriptionField.controller?.text, 'Test Description');
// Only check video field if it's visible (when image is null)
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');
}
});
testWidgets('hides video field when image is present', (tester) async {
await mockNetworkImagesFor(() async {
final existingPost = FeedBlogPostModel(
id: 1,
title: 'Test Title',
description: 'Test Description',
videoUrl: '',
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,
);
await tester.pumpWidget(createTestWidget(feedBlogPostModel: existingPost));
await tester.pumpAndSettle();
expect(find.text('Ссылка на видео'), findsNothing);
expect(find.byKey(AppKeys.blogPostCreateScreenVideoLinkTextField), findsNothing);
});
});
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();
// Tap create button - find by text and scroll if needed
final createButton = find.text('Создать');
await tester.ensureVisible(createButton);
await tester.tap(createButton);
await tester.pump();
// Check for Loader widget (not CircularProgressIndicator)
expect(find.byType(Loader), findsWidgets);
// Wait for timer to complete
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();
// Wait for async operation
await tester.pump(const Duration(milliseconds: 200));
await tester.pumpAndSettle();
verify(mockFeedCubit.loadFeedBlogPostList()).called(1);
});
testWidgets('calls updateBlogPost when editing existing post', (tester) async {
await mockNetworkImagesFor(() async {
when(mockBlogPostRepository.updateBlogPost(any, any, any, any, any, any)).thenAnswer((_) async {
return;
});
final existingPost = FeedBlogPostModel(
id: 1,
title: 'Test Title',
description: 'Test Description',
videoUrl: '',
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,
);
await tester.pumpWidget(createTestWidget(feedBlogPostModel: existingPost, canPop: true));
await tester.pumpAndSettle();
final titleField = find.byKey(AppKeys.blogPostCreateScreenTitleTextField);
expect(titleField, findsOneWidget);
await tester.enterText(titleField, 'Updated Title');
await tester.pump();
// Find and tap create button - ensure it's visible
final createButton = find.text('Создать');
await tester.ensureVisible(createButton);
await tester.tap(createButton, warnIfMissed: false);
await tester.pump();
// Wait for async operation
await tester.pump(const Duration(milliseconds: 200));
await tester.pumpAndSettle();
verify(mockBlogPostRepository.updateBlogPost(
any,
any,
any,
any,
any,
any,
)).called(1);
});
});
testWidgets('displays AppBar', (tester) async {
await tester.pumpWidget(createTestWidget());
await tester.pumpAndSettle();
expect(find.byType(AppBar), findsOneWidget);
});
testWidgets('has scrollable body', (tester) async {
await tester.pumpWidget(createTestWidget());
await tester.pumpAndSettle();
expect(find.byType(SingleChildScrollView), findsOneWidget);
});
});
}

View File

@@ -0,0 +1,67 @@
import 'package:dio/dio.dart';
import 'package:eatvio_app/features/blog_post_create/repositories/blog_post_repository.dart';
import 'package:eatvio_app/features/blog_post_create/services/blog_post_service.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:mocktail/mocktail.dart';
class MockBlogPostService extends Mock implements BlogPostService {}
class FakeFormData extends Fake implements FormData {}
void main() {
group('BlogPostRepository', () {
late BlogPostRepository repository;
late MockBlogPostService mockService;
setUpAll(() {
registerFallbackValue(FakeFormData());
});
setUp(() {
mockService = MockBlogPostService();
repository = BlogPostRepository(blogPostService: mockService);
});
test('deleteBlogPost вызывает сервис', () async {
const id = 1;
when(() => mockService.deleteBlogPost(id: id)).thenAnswer((_) async => {});
await repository.deleteBlogPost(id);
verify(() => mockService.deleteBlogPost(id: id)).called(1);
});
test('createBlogPost вызывает сервис с FormData', () async {
const title = 'Test Title';
const description = 'Test Description';
when(() => mockService.createBlogPost(formData: any(named: 'formData'))).thenAnswer((_) async => {});
await repository.createBlogPost(title, null, description, null);
verify(() => mockService.createBlogPost(formData: any(named: 'formData'))).called(1);
});
test('updateBlogPost вызывает сервис с FormData', () async {
const id = 1;
const title = 'Updated Title';
when(
() => mockService.updateBlogPost(
id: id,
formData: any(named: 'formData'),
),
).thenAnswer((_) async => {});
await repository.updateBlogPost(id, title, null, null, null, null);
verify(
() => mockService.updateBlogPost(
id: id,
formData: any(named: 'formData'),
),
).called(1);
});
});
}

View File

@@ -4,39 +4,36 @@ import 'package:flutter_test/flutter_test.dart';
void main() {
group('NewMessagesCubit', () {
late NewMessagesCubit newMessagesCubit;
late NewMessagesCubit cubit;
setUp(() {
newMessagesCubit = NewMessagesCubit();
cubit = NewMessagesCubit();
});
tearDown(() async {
if (!newMessagesCubit.isClosed) {
await newMessagesCubit.close();
}
tearDown(() {
cubit.close();
});
test('начальное состояние - NewMessagesState.empty', () {
expect(newMessagesCubit.state, const NewMessagesState.empty());
test('начальное состояние - empty', () {
expect(cubit.state, isA<NewMessagesStateEmpty>());
});
test('incrementCounter - увеличивает счётчик сообщений', () {
newMessagesCubit.incrementCounter();
test('incrementCounter увеличивает счетчик и эмитит loaded состояние', () {
cubit.incrementCounter();
expect(newMessagesCubit.state, isA<NewMessagesStateLoaded>());
final loadedState = newMessagesCubit.state as NewMessagesStateLoaded;
expect(cubit.state, isA<NewMessagesStateLoaded>());
final loadedState = cubit.state as NewMessagesStateLoaded;
expect(loadedState.messagesCount, 1);
});
test('incrementCounter - увеличивает счётчик несколько раз', () {
newMessagesCubit.incrementCounter();
newMessagesCubit.incrementCounter();
newMessagesCubit.incrementCounter();
test('incrementCounter увеличивает счетчик несколько раз', () {
cubit.incrementCounter();
cubit.incrementCounter();
cubit.incrementCounter();
expect(newMessagesCubit.state, isA<NewMessagesStateLoaded>());
final loadedState = newMessagesCubit.state as NewMessagesStateLoaded;
expect(cubit.state, isA<NewMessagesStateLoaded>());
final loadedState = cubit.state as NewMessagesStateLoaded;
expect(loadedState.messagesCount, 3);
});
});
}

View File

@@ -3,30 +3,42 @@ import 'package:flutter_test/flutter_test.dart';
void main() {
group('CalendarModel', () {
test('создает CalendarModel с правильными значениями', () {
test('создает экземпляр с данными', () {
const model = CalendarModel(
date: '2024-01-01',
date: '2024-01-15',
isCurrentDayIncludedInTheChosenDietProgression: true,
diary: null,
);
expect(model.date, '2024-01-01');
expect(model.isCurrentDayIncludedInTheChosenDietProgression, isTrue);
expect(model.diary, isNull);
expect(model.date, '2024-01-15');
expect(model.isCurrentDayIncludedInTheChosenDietProgression, true);
expect(model.diary, null);
});
test('fromJson создает CalendarModel из JSON', () {
test('fromJson создает модель из JSON', () {
final json = {
'date': '2024-01-01',
'date': '2024-01-15',
'is_current_day_included_in_the_chosen_diet_progression': true,
'diary': null,
};
final model = CalendarModel.fromJson(json);
expect(model.date, '2024-01-01');
expect(model.isCurrentDayIncludedInTheChosenDietProgression, isTrue);
expect(model.date, '2024-01-15');
expect(model.isCurrentDayIncludedInTheChosenDietProgression, true);
});
test('toJson преобразует модель в JSON', () {
const model = CalendarModel(
date: '2024-01-15',
isCurrentDayIncludedInTheChosenDietProgression: true,
diary: null,
);
final json = model.toJson();
expect(json['date'], '2024-01-15');
expect(json['is_current_day_included_in_the_chosen_diet_progression'], true);
});
});
}

View File

@@ -3,62 +3,51 @@ import 'package:eatvio_app/features/calendar/presentation/bloc/calendar_cubit.da
import 'package:eatvio_app/features/calendar/presentation/bloc/calendar_state.dart';
import 'package:eatvio_app/features/calendar/repositories/calendar_repository.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:mockito/annotations.dart';
import 'package:mockito/mockito.dart';
import 'package:mocktail/mocktail.dart';
import 'calendar_cubit_test.mocks.dart';
class MockCalendarRepository extends Mock implements CalendarRepository {}
@GenerateNiceMocks([MockSpec<CalendarRepository>()])
void main() {
group('CalendarCubit', () {
CalendarCubit? cubit;
late CalendarCubit cubit;
late MockCalendarRepository mockRepository;
setUp(() {
mockRepository = MockCalendarRepository();
cubit = CalendarCubit(calendarRepository: mockRepository);
});
tearDown(() async {
if (cubit != null && !cubit!.isClosed) {
await cubit!.close();
cubit = null;
}
tearDown(() {
cubit.close();
});
test('начальное состояние - loading', () {
cubit = CalendarCubit(calendarRepository: mockRepository);
expect(cubit!.state, isA<CalendarStateLoading>());
expect(cubit.state, isA<CalendarStateLoading>());
});
test('loadCalendarList - успешная загрузка календаря', () async {
cubit = CalendarCubit(calendarRepository: mockRepository);
test('loadCalendarList - успешная загрузка', () async {
final mockCalendarList = <CalendarModel>[];
const testCalendar = CalendarModel(
date: '2024-01-01',
isCurrentDayIncludedInTheChosenDietProgression: true,
);
when(() => mockRepository.getCalendar())
.thenAnswer((_) async => mockCalendarList);
when(mockRepository.getCalendar())
.thenAnswer((_) async => [testCalendar]);
await cubit.loadCalendarList();
await cubit!.loadCalendarList();
expect(cubit!.state, isA<CalendarStateLoaded>());
final loadedState = cubit!.state as CalendarStateLoaded;
expect(loadedState.calendarList.length, 1);
expect(cubit.state, isA<CalendarStateLoaded>());
final loadedState = cubit.state as CalendarStateLoaded;
expect(loadedState.calendarList, mockCalendarList);
verify(() => mockRepository.getCalendar()).called(1);
});
test('loadCalendarList - обработка ошибки', () async {
cubit = CalendarCubit(calendarRepository: mockRepository);
when(() => mockRepository.getCalendar())
.thenThrow(Exception('Error'));
when(mockRepository.getCalendar())
.thenThrow(Exception('Ошибка загрузки'));
await cubit.loadCalendarList();
await cubit!.loadCalendarList();
expect(cubit!.state, isA<CalendarStateError>());
expect(cubit.state, isA<CalendarStateError>());
final errorState = cubit.state as CalendarStateError;
expect(errorState.errorMessage, isNotEmpty);
});
});
}

View File

@@ -0,0 +1,93 @@
import 'package:eatvio_app/core/app_initializer.dart';
import 'package:eatvio_app/core/di/app_config_registration.dart';
import 'package:eatvio_app/core/locator_service.dart';
import 'package:eatvio_app/core/theme/theme_helper.dart';
import 'package:eatvio_app/features/cancel_plan_bottom_sheet/cancel_plan_bottom_sheet.dart';
import 'package:eatvio_app/features/cancel_plan_bottom_sheet/cubit/cancel_plan_cubit.dart';
import 'package:eatvio_app/generated/l10n.dart';
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:flutter_localizations/flutter_localizations.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:mockito/annotations.dart';
import 'package:mockito/mockito.dart';
import 'cancel_plan_bottom_sheet_test.mocks.dart';
@GenerateMocks([CancelPlanCubit])
void main() {
TestWidgetsFlutterBinding.ensureInitialized();
group('CancelPlanBottomSheet', () {
late MockCancelPlanCubit mockCancelPlanCubit;
setUp(() async {
await sl.reset();
AppConfigRegistration.registerAppConfig(buildType: AppBuildType.tests);
mockCancelPlanCubit = MockCancelPlanCubit();
});
Widget createTestWidget({
bool enableForm = false,
required int planId,
}) {
return MaterialApp(
theme: ThemeHelper().themeData(),
localizationsDelegates: [
S.delegate,
GlobalMaterialLocalizations.delegate,
GlobalWidgetsLocalizations.delegate,
GlobalCupertinoLocalizations.delegate,
],
supportedLocales: const [Locale('ru'), Locale('en')],
locale: const Locale('ru'),
home: BlocProvider<CancelPlanCubit>.value(
value: mockCancelPlanCubit,
child: Scaffold(
body: CancelPlanBottomSheet(
enableForm: enableForm,
planId: planId,
),
),
),
);
}
testWidgets('displays cancel plan bottom sheet without form', (tester) async {
when(mockCancelPlanCubit.cancelChosenPlan(id: anyNamed('id')))
.thenAnswer((_) async => true);
await tester.pumpWidget(createTestWidget(planId: 1));
await tester.pumpAndSettle();
expect(find.byType(CancelPlanBottomSheet), findsOneWidget);
});
testWidgets('displays cancel plan bottom sheet with form', (tester) async {
when(mockCancelPlanCubit.cancelChosenPlan(
id: anyNamed('id'),
reason: anyNamed('reason'),
price: anyNamed('price'),
)).thenAnswer((_) async => true);
await tester.pumpWidget(createTestWidget(enableForm: true, planId: 1));
await tester.pumpAndSettle();
expect(find.byType(CancelPlanBottomSheet), findsOneWidget);
});
testWidgets('displays form fields when enableForm is true', (tester) async {
when(mockCancelPlanCubit.cancelChosenPlan(
id: anyNamed('id'),
reason: anyNamed('reason'),
price: anyNamed('price'),
)).thenAnswer((_) async => true);
await tester.pumpWidget(createTestWidget(enableForm: true, planId: 1));
await tester.pumpAndSettle();
expect(find.byType(CancelPlanBottomSheet), findsOneWidget);
});
});
}

View File

@@ -1,84 +1,81 @@
import 'package:eatvio_app/features/cancel_plan_bottom_sheet/cubit/cancel_plan_cubit.dart';
import 'package:eatvio_app/features/cancel_plan_bottom_sheet/cubit/cancel_plan_state.dart';
import 'package:eatvio_app/features/cancel_plan_bottom_sheet/models/cancelation_model.dart';
import 'package:eatvio_app/features/cancel_plan_bottom_sheet/repository/cancel_plan_repository.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:mockito/annotations.dart';
import 'package:mockito/mockito.dart';
import 'package:mocktail/mocktail.dart';
import 'cancel_plan_cubit_test.mocks.dart';
class MockCancelPlanRepository extends Mock implements CancelPlanRepository {}
@GenerateNiceMocks([MockSpec<CancelPlanRepository>()])
void main() {
group('CancelPlanCubit', () {
CancelPlanCubit? cubit;
late CancelPlanCubit cubit;
late MockCancelPlanRepository mockRepository;
setUp(() {
mockRepository = MockCancelPlanRepository();
});
tearDown(() async {
if (cubit != null && !cubit!.isClosed) {
await cubit!.close();
cubit = null;
}
});
test('начальное состояние - пустое', () {
cubit = CancelPlanCubit(cancelPlanRepository: mockRepository);
expect(cubit!.state, isA<CancelPlanState>());
});
test('cancelChosenPlan - успешная отмена плана без причины', () async {
cubit = CancelPlanCubit(cancelPlanRepository: mockRepository);
when(mockRepository.cancelChosenPlan(
id: 1,
body: anyNamed('body'),
)).thenAnswer((_) async => {});
final result = await cubit!.cancelChosenPlan(id: 1);
expect(result, isTrue);
verify(mockRepository.cancelChosenPlan(id: 1, body: null)).called(1);
tearDown(() {
cubit.close();
});
test('cancelChosenPlan - успешная отмена плана с причиной', () async {
cubit = CancelPlanCubit(cancelPlanRepository: mockRepository);
when(mockRepository.cancelChosenPlan(
id: anyNamed('id'),
body: anyNamed('body'),
)).thenAnswer((_) async => {});
final result = await cubit!.cancelChosenPlan(
id: 1,
reason: 'Test reason',
price: 1000,
);
expect(result, isTrue);
verify(mockRepository.cancelChosenPlan(
id: 1,
body: anyNamed('body'),
)).called(1);
test('начальное состояние - CancelPlanState', () {
expect(cubit.state, isA<CancelPlanState>());
});
test('cancelChosenPlan - обработка ошибки', () async {
cubit = CancelPlanCubit(cancelPlanRepository: mockRepository);
test('cancelChosenPlan - успешная отмена без причины', () async {
const planId = 1;
when(mockRepository.cancelChosenPlan(
id: anyNamed('id'),
body: anyNamed('body'),
)).thenAnswer((_) async {
throw Exception('Ошибка отмены');
});
when(() => mockRepository.cancelChosenPlan(id: planId, body: null))
.thenAnswer((_) async => {});
final result = await cubit!.cancelChosenPlan(id: 1);
final result = await cubit.cancelChosenPlan(id: planId);
expect(result, isFalse);
expect(result, true);
verify(() => mockRepository.cancelChosenPlan(id: planId, body: null)).called(1);
});
test('cancelChosenPlan - успешная отмена с причиной и ценой', () async {
const planId = 1;
const reason = 'Test reason';
const price = 1000;
final body = CancelationModel(description: reason, price: price);
when(() => mockRepository.cancelChosenPlan(id: planId, body: body))
.thenAnswer((_) async => {});
final result = await cubit.cancelChosenPlan(id: planId, reason: reason, price: price);
expect(result, true);
verify(() => mockRepository.cancelChosenPlan(id: planId, body: body)).called(1);
});
test('cancelChosenPlan - возвращает false при ошибке', () async {
const planId = 1;
when(() => mockRepository.cancelChosenPlan(id: planId, body: null))
.thenThrow(Exception('Error'));
final result = await cubit.cancelChosenPlan(id: planId);
expect(result, false);
});
test('cancelChosenPlan - не создает body если reason или price null', () async {
const planId = 1;
when(() => mockRepository.cancelChosenPlan(id: planId, body: null))
.thenAnswer((_) async => {});
// Только reason
await cubit.cancelChosenPlan(id: planId, reason: 'reason');
verify(() => mockRepository.cancelChosenPlan(id: planId, body: null)).called(1);
// Только price
await cubit.cancelChosenPlan(id: planId, price: 100);
verify(() => mockRepository.cancelChosenPlan(id: planId, body: null)).called(1);
});
});
}

View File

@@ -3,27 +3,28 @@ import 'package:flutter_test/flutter_test.dart';
void main() {
group('CancelationModel', () {
test('создает CancelationModel с правильными значениями', () {
final model = CancelationModel(
description: 'Test reason',
price: 1000,
);
test('создает экземпляр с description и price', () {
final model = CancelationModel(description: 'Test reason', price: 1000);
expect(model.description, 'Test reason');
expect(model.price, 1000);
});
test('fromJson создает CancelationModel из JSON', () {
final json = {
'description': 'Test reason',
'price': 1000,
};
test('fromJson создает модель из JSON', () {
final json = {'description': 'Test reason', 'price': 1000};
final model = CancelationModel.fromJson(json);
expect(model.description, 'Test reason');
expect(model.price, 1000);
});
test('toJson преобразует модель в JSON', () {
final model = CancelationModel(description: 'Test reason', price: 1000);
final json = model.toJson();
expect(json['description'], 'Test reason');
expect(json['price'], 1000);
});
});
}

View File

@@ -0,0 +1,258 @@
import 'package:eatvio_app/core/theme/theme_helper.dart';
import 'package:eatvio_app/features/chat/models/chat_message_model.dart';
import 'package:eatvio_app/features/chat/models/chat_room_pagination_model.dart';
import 'package:eatvio_app/features/chat/presentation/widgets/chat_entry_widget.dart';
import 'package:eatvio_app/generated/l10n.dart';
import 'package:flutter/material.dart';
import 'package:flutter_localizations/flutter_localizations.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:go_router/go_router.dart';
import 'package:network_image_mock/network_image_mock.dart';
void main() {
TestWidgetsFlutterBinding.ensureInitialized();
group('ChatEntryWidget', () {
Widget createTestWidget(ChatRoom chatRoom) {
return MaterialApp.router(
theme: ThemeHelper().themeData(),
localizationsDelegates: [
S.delegate,
GlobalMaterialLocalizations.delegate,
GlobalWidgetsLocalizations.delegate,
GlobalCupertinoLocalizations.delegate,
],
supportedLocales: const [Locale('ru'), Locale('en')],
locale: const Locale('ru'),
routerConfig: GoRouter(
initialLocation: '/',
routes: [
GoRoute(
path: '/',
builder: (context, state) => Scaffold(
body: ChatEntryWidget(chatRoom: chatRoom),
),
),
GoRoute(
path: '/chatScreen',
name: 'chatScreen',
builder: (context, state) => Scaffold(
appBar: AppBar(title: const Text('Chat')),
body: Center(child: Text('Chat: ${state.extra}')),
),
),
],
),
);
}
testWidgets('displays chat room with user name', (tester) async {
final chatRoom = ChatRoom(
id: 'test-id',
users: Users(
fromChatUser: ChatUser(
id: 1,
name: 'From User',
profilePhotoPath: null,
profilePhotoUrl: 'http://localhost/photo.jpg',
),
toChatUser: ChatUser(
id: 2,
name: 'Test User',
profilePhotoPath: null,
profilePhotoUrl: 'http://localhost/photo.jpg',
),
),
unreadMessagesCount: 0,
lastMessage: null,
chosenPlan: null,
);
mockNetworkImagesFor(() async {
await tester.pumpWidget(createTestWidget(chatRoom));
await tester.pumpAndSettle();
expect(find.text('Test User'), findsOneWidget);
});
});
testWidgets('displays chat room with last message', (tester) async {
final chatRoom = ChatRoom(
id: 'test-id',
users: Users(
fromChatUser: ChatUser(
id: 1,
name: 'From User',
profilePhotoPath: null,
profilePhotoUrl: 'http://localhost/photo.jpg',
),
toChatUser: ChatUser(
id: 2,
name: 'Test User',
profilePhotoPath: null,
profilePhotoUrl: 'http://localhost/photo.jpg',
),
),
unreadMessagesCount: 0,
lastMessage: ChatMessageModel(
id: 1,
data: 'Test message',
updatedAt: DateTime.now(),
isCurrentUserAuthor: false,
mediaData: null,
authorId: 1,
isRead: false,
roomId: 'test-id',
),
chosenPlan: null,
);
mockNetworkImagesFor(() async {
await tester.pumpWidget(createTestWidget(chatRoom));
await tester.pumpAndSettle();
expect(find.text('Test message'), findsOneWidget);
});
});
testWidgets('displays chat room with unread messages count', (tester) async {
final chatRoom = ChatRoom(
id: 'test-id',
users: Users(
fromChatUser: ChatUser(
id: 1,
name: 'From User',
profilePhotoPath: null,
profilePhotoUrl: 'http://localhost/photo.jpg',
),
toChatUser: ChatUser(
id: 2,
name: 'Test User',
profilePhotoPath: null,
profilePhotoUrl: 'http://localhost/photo.jpg',
),
),
unreadMessagesCount: 5,
lastMessage: null,
chosenPlan: null,
);
mockNetworkImagesFor(() async {
await tester.pumpWidget(createTestWidget(chatRoom));
await tester.pumpAndSettle();
expect(find.text('5'), findsOneWidget);
});
});
testWidgets('displays message from current user', (tester) async {
final chatRoom = ChatRoom(
id: 'test-id',
users: Users(
fromChatUser: ChatUser(
id: 1,
name: 'From User',
profilePhotoPath: null,
profilePhotoUrl: 'http://localhost/photo.jpg',
),
toChatUser: ChatUser(
id: 2,
name: 'Test User',
profilePhotoPath: null,
profilePhotoUrl: 'http://localhost/photo.jpg',
),
),
unreadMessagesCount: 0,
lastMessage: ChatMessageModel(
id: 1,
data: 'Test message',
updatedAt: DateTime.now(),
isCurrentUserAuthor: true,
mediaData: null,
authorId: 1,
isRead: false,
roomId: 'test-id',
),
chosenPlan: null,
);
mockNetworkImagesFor(() async {
await tester.pumpWidget(createTestWidget(chatRoom));
await tester.pumpAndSettle();
expect(find.textContaining('Вы:'), findsOneWidget);
});
});
testWidgets('displays media attachment message', (tester) async {
final chatRoom = ChatRoom(
id: 'test-id',
users: Users(
fromChatUser: ChatUser(
id: 1,
name: 'From User',
profilePhotoPath: null,
profilePhotoUrl: 'http://localhost/photo.jpg',
),
toChatUser: ChatUser(
id: 2,
name: 'Test User',
profilePhotoPath: null,
profilePhotoUrl: 'http://localhost/photo.jpg',
),
),
unreadMessagesCount: 0,
lastMessage: ChatMessageModel(
id: 1,
data: 'Test message',
updatedAt: DateTime.now(),
isCurrentUserAuthor: false,
mediaData: [{'type': 'image', 'url': 'https://example.com/image.jpg'}],
authorId: 1,
isRead: false,
roomId: 'test-id',
),
chosenPlan: null,
);
mockNetworkImagesFor(() async {
await tester.pumpWidget(createTestWidget(chatRoom));
await tester.pumpAndSettle();
expect(find.textContaining('📎 Вложение'), findsOneWidget);
});
});
testWidgets('truncates long user names', (tester) async {
final longName = 'A' * 30;
final chatRoom = ChatRoom(
id: 'test-id',
users: Users(
fromChatUser: ChatUser(
id: 1,
name: 'From User',
profilePhotoPath: null,
profilePhotoUrl: 'http://localhost/photo.jpg',
),
toChatUser: ChatUser(
id: 2,
name: longName,
profilePhotoPath: null,
profilePhotoUrl: 'http://localhost/photo.jpg',
),
),
unreadMessagesCount: 0,
lastMessage: null,
chosenPlan: null,
);
mockNetworkImagesFor(() async {
await tester.pumpWidget(createTestWidget(chatRoom));
await tester.pumpAndSettle();
expect(find.textContaining('...'), findsOneWidget);
});
});
});
}

View File

@@ -1,117 +1,108 @@
import 'package:eatvio_app/core/theme/theme_helper.dart';
import 'package:eatvio_app/features/chat/presentation/widgets/chat_search_bar_widget.dart';
import 'package:eatvio_app/generated/l10n.dart';
import 'package:flutter/material.dart';
import 'package:flutter_localizations/flutter_localizations.dart';
import 'package:flutter_test/flutter_test.dart';
void main() {
group('ChatSearchBarWidget Tests', () {
Widget createTestWidget(Widget child) {
return MaterialApp(
theme: ThemeHelper().themeData(),
localizationsDelegates: const [
S.delegate,
GlobalMaterialLocalizations.delegate,
GlobalWidgetsLocalizations.delegate,
GlobalCupertinoLocalizations.delegate,
],
supportedLocales: const [Locale('ru')],
home: Scaffold(body: child),
group('ChatSearchBarWidget', () {
testWidgets('displays search bar with default icon', (tester) async {
await tester.pumpWidget(
MaterialApp(
theme: ThemeHelper().themeData(),
home: Scaffold(
body: ChatSearchBarWidget(
onSuffixIconTap: () {},
),
),
),
);
}
testWidgets('ChatSearchBarWidget - отображается с дефолтными значениями', (WidgetTester tester) async {
final widget = ChatSearchBarWidget(onSuffixIconTap: () {});
await tester.pumpWidget(createTestWidget(widget));
await tester.pumpAndSettle();
expect(find.byType(TextFormField), findsOneWidget);
expect(find.byType(ChatSearchBarWidget), findsOneWidget);
expect(find.byIcon(Icons.search), findsOneWidget);
});
testWidgets('ChatSearchBarWidget - вызывает onSuffixIconTap при нажатии на иконку очистки', (WidgetTester tester) async {
testWidgets('displays custom hint text', (tester) async {
await tester.pumpWidget(
MaterialApp(
theme: ThemeHelper().themeData(),
home: Scaffold(
body: ChatSearchBarWidget(
hintText: 'Search messages',
onSuffixIconTap: () {},
),
),
),
);
expect(find.byType(ChatSearchBarWidget), findsOneWidget);
});
testWidgets('calls onSuffixIconTap when clear button is tapped', (tester) async {
bool suffixTapped = false;
final controller = TextEditingController(text: 'test');
final widget = ChatSearchBarWidget(
controller: controller,
onSuffixIconTap: () {
suffixTapped = true;
},
await tester.pumpWidget(
MaterialApp(
theme: ThemeHelper().themeData(),
home: Scaffold(
body: ChatSearchBarWidget(
controller: controller,
onSuffixIconTap: () {
suffixTapped = true;
},
),
),
),
);
await tester.pumpWidget(createTestWidget(widget));
await tester.pumpAndSettle();
// Находим иконку очистки и нажимаем на неё
final clearIcon = find.byIcon(Icons.clear);
expect(clearIcon, findsOneWidget);
await tester.tap(clearIcon);
await tester.pumpAndSettle();
await tester.tap(find.byIcon(Icons.clear));
await tester.pump();
expect(suffixTapped, isTrue);
expect(controller.text, isEmpty);
});
testWidgets('ChatSearchBarWidget - вызывает onChanged при вводе текста', (WidgetTester tester) async {
testWidgets('clears controller when clear button is tapped', (tester) async {
final controller = TextEditingController(text: 'test');
await tester.pumpWidget(
MaterialApp(
theme: ThemeHelper().themeData(),
home: Scaffold(
body: ChatSearchBarWidget(
controller: controller,
onSuffixIconTap: () {},
),
),
),
);
expect(controller.text, 'test');
await tester.tap(find.byIcon(Icons.clear));
await tester.pump();
expect(controller.text, '');
});
testWidgets('calls onChanged when text is entered', (tester) async {
String? changedValue;
final controller = TextEditingController();
final widget = ChatSearchBarWidget(
controller: controller,
onChanged: (value) {
changedValue = value;
},
onSuffixIconTap: () {},
await tester.pumpWidget(
MaterialApp(
theme: ThemeHelper().themeData(),
home: Scaffold(
body: ChatSearchBarWidget(
controller: controller,
onChanged: (value) {
changedValue = value;
},
onSuffixIconTap: () {},
),
),
),
);
await tester.pumpWidget(createTestWidget(widget));
await tester.pumpAndSettle();
await tester.enterText(find.byType(TextFormField), 'test');
await tester.pump();
await tester.enterText(find.byType(TextFormField), 'test query');
await tester.pumpAndSettle();
expect(changedValue, equals('test query'));
});
testWidgets('ChatSearchBarWidget - отображает кастомный hintText', (WidgetTester tester) async {
const hintText = 'Search chats...';
final widget = ChatSearchBarWidget(hintText: hintText, onSuffixIconTap: () {});
await tester.pumpWidget(createTestWidget(widget));
await tester.pumpAndSettle();
expect(find.text(hintText), findsOneWidget);
});
testWidgets('ChatSearchBarWidget - использует кастомный prefix', (WidgetTester tester) async {
const customPrefix = Icon(Icons.filter_list);
final widget = ChatSearchBarWidget(prefix: customPrefix, onSuffixIconTap: () {});
await tester.pumpWidget(createTestWidget(widget));
await tester.pumpAndSettle();
expect(find.byIcon(Icons.filter_list), findsOneWidget);
expect(find.byIcon(Icons.search), findsNothing);
});
testWidgets('ChatSearchBarWidget - использует кастомный suffix', (WidgetTester tester) async {
bool suffixTapped = false;
final customSuffix = IconButton(
icon: const Icon(Icons.close),
onPressed: () {
suffixTapped = true;
},
);
final widget = ChatSearchBarWidget(suffix: customSuffix, onSuffixIconTap: () {});
await tester.pumpWidget(createTestWidget(widget));
await tester.pumpAndSettle();
await tester.tap(find.byIcon(Icons.close));
await tester.pumpAndSettle();
expect(suffixTapped, isTrue);
expect(changedValue, 'test');
});
});
}

View File

@@ -3,34 +3,45 @@ import 'package:flutter_test/flutter_test.dart';
void main() {
group('ComplainModel', () {
test('создает ComplainModel с дефолтными значениями', () {
test('создает экземпляр с дефолтными значениями', () {
const model = ComplainModel();
expect(model.description, isNull);
expect(model.tags, isEmpty);
expect(model.description, null);
expect(model.tags, []);
});
test('создает ComplainModel с описанием и тегами', () {
test('создает экземпляр с description и tags', () {
const model = ComplainModel(
description: 'Test complaint',
description: 'Test description',
tags: ['tag1', 'tag2'],
);
expect(model.description, 'Test complaint');
expect(model.description, 'Test description');
expect(model.tags, ['tag1', 'tag2']);
});
test('fromJson создает ComplainModel из JSON', () {
test('fromJson создает модель из JSON', () {
final json = {
'description': 'Test complaint',
'description': 'Test description',
'tags': ['tag1', 'tag2'],
};
final model = ComplainModel.fromJson(json);
expect(model.description, 'Test complaint');
expect(model.description, 'Test description');
expect(model.tags, ['tag1', 'tag2']);
});
test('toJson преобразует модель в JSON', () {
const model = ComplainModel(
description: 'Test description',
tags: ['tag1', 'tag2'],
);
final json = model.toJson();
expect(json['description'], 'Test description');
expect(json['tags'], ['tag1', 'tag2']);
});
});
}

View File

@@ -1,143 +1,124 @@
import 'package:eatvio_app/core/enums/complained_content.dart';
import 'package:eatvio_app/features/complaining/models/complain_model.dart';
import 'package:eatvio_app/features/complaining/presentation/complaining_bottom_sheet.dart';
import 'package:eatvio_app/features/complaining/presentation/cubit/complaining_cubit.dart';
import 'package:eatvio_app/features/complaining/presentation/cubit/complaining_state.dart';
import 'package:eatvio_app/features/complaining/repositories/complaining_repository.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:mockito/annotations.dart';
import 'package:mockito/mockito.dart';
import 'package:mocktail/mocktail.dart';
import 'complaining_cubit_test.mocks.dart';
class MockComplainingRepository extends Mock implements ComplainingRepository {}
@GenerateNiceMocks([MockSpec<ComplainingRepository>()])
void main() {
group('ComplainingCubit', () {
ComplainingCubit? complainingCubit;
late ComplainingCubit cubit;
late MockComplainingRepository mockRepository;
setUpAll(() {
registerFallbackValue(const ComplainModel());
});
setUp(() {
mockRepository = MockComplainingRepository();
cubit = ComplainingCubit(repository: mockRepository);
});
tearDown(() async {
if (complainingCubit != null && !complainingCubit!.isClosed) {
await complainingCubit!.close();
complainingCubit = null;
}
tearDown(() {
cubit.close();
});
test('начальное состояние - пустое', () {
complainingCubit = ComplainingCubit(repository: mockRepository);
expect(complainingCubit!.state.contentType, isNull);
expect(complainingCubit!.state.category, isNull);
expect(complainingCubit!.state.wasSentSuccessfully, isNull);
test('начальное состояние - ComplainingState', () {
expect(cubit.state, isA<ComplainingState>());
});
test('setContentTypeAndIdentifier - устанавливает тип контента и идентификатор', () {
complainingCubit = ComplainingCubit(repository: mockRepository);
complainingCubit!.setContentTypeAndIdentifier(
test('setContentTypeAndIdentifier устанавливает тип и идентификатор', () {
cubit.setContentTypeAndIdentifier(
contentType: ComplainedContent.diet,
id: 1,
);
expect(complainingCubit!.state.contentType, ComplainedContent.diet);
expect(complainingCubit!.state.contentId, 1);
expect(cubit.state.contentType, ComplainedContent.diet);
expect(cubit.state.contentId, 1);
});
test('setCategory - устанавливает категорию жалобы', () {
complainingCubit = ComplainingCubit(repository: mockRepository);
complainingCubit!.setCategory(ComplainType.spam);
expect(complainingCubit!.state.category, ComplainType.spam);
});
test('setTags - устанавливает теги', () {
complainingCubit = ComplainingCubit(repository: mockRepository);
complainingCubit!.setTags(['tag1', 'tag2']);
expect(complainingCubit!.state.complainInfo.tags, ['tag1', 'tag2']);
});
test('toggleDescription - переключает описание', () {
complainingCubit = ComplainingCubit(repository: mockRepository);
complainingCubit!.toggleDescription();
expect(complainingCubit!.state.complainInfo.description, '');
complainingCubit!.toggleDescription();
expect(complainingCubit!.state.complainInfo.description, isNull);
});
test('setDescription - устанавливает описание', () {
complainingCubit = ComplainingCubit(repository: mockRepository);
complainingCubit!.setDescription('Test description');
expect(complainingCubit!.state.complainInfo.description, 'Test description');
});
test('complain - отправляет жалобу на диету', () async {
complainingCubit = ComplainingCubit(repository: mockRepository);
complainingCubit!.setContentTypeAndIdentifier(
contentType: ComplainedContent.diet,
id: 1,
);
complainingCubit!.setCategory(ComplainType.spam);
when(mockRepository.sendDietComplain(
id: 1,
category: anyNamed('category'),
body: anyNamed('body'),
)).thenAnswer((_) async => {});
await complainingCubit!.complain();
expect(complainingCubit!.state.wasSentSuccessfully, isTrue);
});
test('complain - отправляет жалобу на план', () async {
complainingCubit = ComplainingCubit(repository: mockRepository);
complainingCubit!.setContentTypeAndIdentifier(
test('setContentTypeAndIdentifier устанавливает slug', () {
cubit.setContentTypeAndIdentifier(
contentType: ComplainedContent.plan,
slug: 'test-plan',
slug: 'test-slug',
);
complainingCubit!.setCategory(ComplainType.spam);
when(mockRepository.sendPlanComplain(
slug: 'test-plan',
category: anyNamed('category'),
body: anyNamed('body'),
)).thenAnswer((_) async => {});
await complainingCubit!.complain();
expect(complainingCubit!.state.wasSentSuccessfully, isTrue);
expect(cubit.state.contentType, ComplainedContent.plan);
expect(cubit.state.contentSlug, 'test-slug');
});
test('complain - обработка ошибки', () async {
complainingCubit = ComplainingCubit(repository: mockRepository);
test('setCategory устанавливает категорию', () {
cubit.setCategory(ComplainType.spam);
complainingCubit!.setContentTypeAndIdentifier(
contentType: ComplainedContent.diet,
id: 1,
);
complainingCubit!.setCategory(ComplainType.spam);
expect(cubit.state.category, ComplainType.spam);
});
when(mockRepository.sendDietComplain(
id: anyNamed('id'),
category: anyNamed('category'),
body: anyNamed('body'),
)).thenThrow(Exception('Ошибка отправки'));
test('setTags устанавливает теги', () {
final tags = ['tag1', 'tag2'];
cubit.setTags(tags);
await complainingCubit!.complain();
expect(cubit.state.complainInfo.tags, tags);
});
expect(complainingCubit!.state.wasSentSuccessfully, isFalse);
test('toggleDescription переключает описание', () {
// Включает описание
cubit.toggleDescription();
expect(cubit.state.complainInfo.description, '');
// Выключает описание
cubit.toggleDescription();
expect(cubit.state.complainInfo.description, null);
});
test('setDescription устанавливает описание', () {
const description = 'Test description';
cubit.setDescription(description);
expect(cubit.state.complainInfo.description, description);
});
test('complain - отправляет жалобу на diet', () async {
const id = 1;
when(() => mockRepository.sendDietComplain(
id: id,
category: any(named: 'category'),
body: any(named: 'body'),
)).thenAnswer((_) async => {});
cubit.setContentTypeAndIdentifier(contentType: ComplainedContent.diet, id: id);
cubit.setCategory(ComplainType.spam);
cubit.setTags(['tag1']);
await cubit.complain();
expect(cubit.state.wasSentSuccessfully, true);
verify(() => mockRepository.sendDietComplain(
id: id,
category: any(named: 'category'),
body: any(named: 'body'),
)).called(1);
});
test('complain - возвращает false при ошибке', () async {
const id = 1;
when(() => mockRepository.sendDietComplain(
id: id,
category: any(named: 'category'),
body: any(named: 'body'),
)).thenThrow(Exception('Error'));
cubit.setContentTypeAndIdentifier(contentType: ComplainedContent.diet, id: id);
cubit.setCategory(ComplainType.spam);
await cubit.complain();
expect(cubit.state.wasSentSuccessfully, false);
});
});
}

View File

@@ -3,12 +3,10 @@ import 'package:eatvio_app/features/consultant_rating/models/statistics_model.da
import 'package:eatvio_app/features/consultant_rating/repositories/consultant_rating_repository.dart';
import 'package:eatvio_app/features/consultant_rating/services/consultant_rating_service.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:mockito/annotations.dart';
import 'package:mockito/mockito.dart';
import 'package:mocktail/mocktail.dart';
import 'consultant_rating_repository_test.mocks.dart';
class MockConsultantRatingService extends Mock implements ConsultantRatingService {}
@GenerateNiceMocks([MockSpec<ConsultantRatingService>()])
void main() {
group('ConsultantRatingRepository', () {
late ConsultantRatingRepository repository;
@@ -19,47 +17,49 @@ void main() {
repository = ConsultantRatingRepository(mockService);
});
test('getConsultantStatistics - успешное получение статистики', () async {
final expectedStats = StatisticsModel(
countReferal: 10,
blogPostsCount: 5,
recipesCount: 3,
dietsCount: 2,
consultationsCount: 15,
qwe: 1,
refLink: 'https://example.com/ref',
);
test('getConsultantStatistics - успешный запрос', () async {
final mockStatistics = StatisticsModel();
when(mockService.getConsultantStatistics(date: null))
.thenAnswer((_) async => expectedStats);
when(() => mockService.getConsultantStatistics(date: any(named: 'date')))
.thenAnswer((_) async => mockStatistics);
final result = await repository.getConsultantStatistics();
expect(result, equals(expectedStats));
expect(result.countReferal, 10);
verify(mockService.getConsultantStatistics(date: null)).called(1);
expect(result, mockStatistics);
verify(() => mockService.getConsultantStatistics(date: null)).called(1);
});
test('getScoreLogs - успешное получение логов', () async {
final expectedLogs = [
RatingModel(
date: DateTime(2024, 1, 1),
type: RatingEnum.addedRecipe,
amount: 10,
),
];
test('getConsultantStatistics - с датой', () async {
const date = '2024-01-01';
final mockStatistics = StatisticsModel();
final pagination = RatingPaginationModel(data: expectedLogs);
when(() => mockService.getConsultantStatistics(date: date))
.thenAnswer((_) async => mockStatistics);
when(mockService.getScoreLogs(date: null))
.thenAnswer((_) async => pagination);
final result = await repository.getConsultantStatistics(date: date);
expect(result, mockStatistics);
verify(() => mockService.getConsultantStatistics(date: date)).called(1);
});
test('getScoreLogs - успешный запрос', () async {
final mockRatings = <RatingModel>[];
final mockPagination = RatingPaginationModel(data: mockRatings);
when(() => mockService.getScoreLogs(date: any(named: 'date')))
.thenAnswer((_) async => mockPagination);
final result = await repository.getScoreLogs();
expect(result.length, 1);
expect(result.first.amount, 10);
verify(mockService.getScoreLogs(date: null)).called(1);
expect(result, mockRatings);
verify(() => mockService.getScoreLogs(date: null)).called(1);
});
test('getScoreLogs - пробрасывает ошибку', () async {
when(() => mockService.getScoreLogs(date: any(named: 'date')))
.thenThrow(Exception('Error'));
expect(() => repository.getScoreLogs(), throwsException);
});
});
}

View File

@@ -1,80 +1,51 @@
import 'package:eatvio_app/core/enums/base_state.dart';
import 'package:eatvio_app/features/consultations/models/consultation_model.dart';
import 'package:eatvio_app/features/consultations/presentation/cubit/consultations_cubit.dart';
import 'package:eatvio_app/features/consultations/presentation/cubit/consultations_state.dart';
import 'package:eatvio_app/features/consultations/repositories/consultations_repository.dart';
import 'package:eatvio_app/features/plans/enums/chosen_plan_state_enum.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:mockito/annotations.dart';
import 'package:mockito/mockito.dart';
import 'package:mocktail/mocktail.dart';
import 'consultations_cubit_test.mocks.dart';
class MockConsultationsRepository extends Mock implements ConsultationsRepository {}
@GenerateNiceMocks([MockSpec<ConsultationsRepository>()])
void main() {
group('ConsultationsCubit', () {
ConsultationsCubit? cubit;
late ConsultationsCubit cubit;
late MockConsultationsRepository mockRepository;
setUp(() {
mockRepository = MockConsultationsRepository();
});
tearDown(() async {
if (cubit != null && !cubit!.isClosed) {
await cubit!.close();
cubit = null;
}
});
test('начальное состояние - initial', () {
cubit = ConsultationsCubit(consultationsRepository: mockRepository);
expect(cubit!.state.state, BaseState.initial);
expect(cubit!.state.consultations, isEmpty);
});
test('loadConsultations - успешная загрузка консультаций', () async {
cubit = ConsultationsCubit(consultationsRepository: mockRepository);
tearDown(() {
cubit.close();
});
final testConsultations = [
ConsultationModel(
id: 1,
chosenPlanState: ChosenPlanStateEnum.stateInProgress,
planTitle: 'Test Plan',
userName: 'Test User',
userSlug: 'test-user',
consultantPayment: 1000.0,
duration: 30,
currentDiaryDay: 1,
userProfilePhotoUrl: 'https://example.com/photo.jpg',
consultationOptionPrice: 5000,
startDate: DateTime(2024, 1, 1),
endDate: DateTime(2024, 1, 31),
consultantId: 1,
diaries: [],
personalDietId: null,
),
];
test('начальное состояние - ComplaintsState', () {
expect(cubit.state, isA<ConsultationsState>());
});
when(mockRepository.getConsultations())
.thenAnswer((_) async => testConsultations);
test('loadConsultations - успешная загрузка', () async {
final mockConsultations = <ConsultationModel>[];
await cubit!.loadConsultations();
when(() => mockRepository.getConsultations())
.thenAnswer((_) async => mockConsultations);
expect(cubit!.state.state, BaseState.success);
expect(cubit!.state.consultations.length, 1);
await cubit.loadConsultations();
expect(cubit.state.state, BaseState.success);
expect(cubit.state.consultations, mockConsultations);
verify(() => mockRepository.getConsultations()).called(1);
});
test('loadConsultations - обработка ошибки', () async {
cubit = ConsultationsCubit(consultationsRepository: mockRepository);
when(() => mockRepository.getConsultations())
.thenThrow(Exception('Error'));
when(mockRepository.getConsultations())
.thenThrow(Exception('Ошибка загрузки'));
await cubit.loadConsultations();
await cubit!.loadConsultations();
expect(cubit!.state.state, BaseState.error);
expect(cubit.state.state, BaseState.error);
});
});
}

View File

@@ -0,0 +1,113 @@
import 'package:eatvio_app/features/consultations/models/consultation_model.dart';
import 'package:eatvio_app/features/consultations/repositories/consultations_repository.dart';
import 'package:eatvio_app/features/consultations/services/consultation_service.dart';
import 'package:eatvio_app/features/finances_and_consultations_archive/models/consultant_finance_model.dart';
import 'package:eatvio_app/features/finances_and_consultations_archive/models/statistic_model.dart';
import 'package:eatvio_app/features/finances_and_consultations_archive/models/user_purchases_model.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:mocktail/mocktail.dart';
class MockConsultationService extends Mock implements ConsultationService {}
class FakeStatisticModel extends Fake implements StatisticModel {}
void main() {
group('ConsultationsRepository', () {
late ConsultationsRepository repository;
late MockConsultationService mockService;
setUpAll(() {
registerFallbackValue(FakeStatisticModel());
});
setUp(() {
mockService = MockConsultationService();
repository = ConsultationsRepository(consultationService: mockService);
});
test('getConsultations - успешный запрос', () async {
final mockConsultations = <ConsultationModel>[];
final mockPagination = ConsultationPaginationModel(data: mockConsultations);
when(() => mockService.getConsultations())
.thenAnswer((_) async => mockPagination);
final result = await repository.getConsultations();
expect(result, mockConsultations);
verify(() => mockService.getConsultations()).called(1);
});
test('getConsultations - пробрасывает ошибку', () async {
when(() => mockService.getConsultations())
.thenThrow(Exception('Error'));
expect(() => repository.getConsultations(), throwsException);
});
test('getFilteredConsultations - успешный запрос', () async {
final mockConsultations = <ConsultationModel>[];
final mockPagination = ConsultationPaginationModel(data: mockConsultations);
when(() => mockService.getFilteredConsultations(
state: any(named: 'state'),
date: any(named: 'date'),
stateComparison: any(named: 'stateComparison'),
dateComparison: any(named: 'dateComparison'),
))
.thenAnswer((_) async => mockPagination);
final result = await repository.getFilteredConsultations();
expect(result, mockConsultations);
verify(() => mockService.getFilteredConsultations(
state: null,
date: null,
stateComparison: null,
dateComparison: null,
)).called(1);
});
test('getStatistic - успешный запрос', () async {
final mockStatistic = StatisticModel(
total: 10,
inProgress: 5,
finished: 3,
canceled: 2,
);
when(() => mockService.getStatistics())
.thenAnswer((_) async => mockStatistic);
final result = await repository.getStatistic();
expect(result, mockStatistic);
verify(() => mockService.getStatistics()).called(1);
});
test('getConsultantFinances - успешный запрос', () async {
final mockFinances = <ConsultantFinanceModel>[];
when(() => mockService.getConsultantFinances(any(), any()))
.thenAnswer((_) async => mockFinances);
final result = await repository.getConsultantFinances(null, null);
expect(result, mockFinances);
verify(() => mockService.getConsultantFinances(null, null)).called(1);
});
test('getUserPurchases - успешный запрос', () async {
final mockPurchases = <UserPurchasesModel>[];
when(() => mockService.getUserPurchases(any(), any()))
.thenAnswer((_) async => mockPurchases);
final result = await repository.getUserPurchases(null, null);
expect(result, mockPurchases);
verify(() => mockService.getUserPurchases(null, null)).called(1);
});
});
}

View File

@@ -0,0 +1,64 @@
import 'package:eatvio_app/core/theme/theme_helper.dart';
import 'package:eatvio_app/features/create_diet/presentation/widgets/error_widget.dart';
import 'package:flutter/material.dart';
import 'package:flutter_test/flutter_test.dart';
void main() {
group('ValidationErrorWidget', () {
testWidgets('displays error message', (tester) async {
await tester.pumpWidget(
MaterialApp(
theme: ThemeHelper().themeData(),
home: Scaffold(
body: ValidationErrorWidget(error: 'Test error message'),
),
),
);
expect(find.byType(ValidationErrorWidget), findsOneWidget);
expect(find.text('Test error message'), findsOneWidget);
});
testWidgets('displays error icon', (tester) async {
await tester.pumpWidget(
MaterialApp(
theme: ThemeHelper().themeData(),
home: Scaffold(
body: ValidationErrorWidget(error: 'Test error'),
),
),
);
expect(find.byType(ValidationErrorWidget), findsOneWidget);
});
testWidgets('handles long error messages', (tester) async {
final longError = 'A' * 100;
await tester.pumpWidget(
MaterialApp(
theme: ThemeHelper().themeData(),
home: Scaffold(
body: ValidationErrorWidget(error: longError),
),
),
);
expect(find.byType(ValidationErrorWidget), findsOneWidget);
expect(find.text(longError), findsOneWidget);
});
testWidgets('displays empty error message', (tester) async {
await tester.pumpWidget(
MaterialApp(
theme: ThemeHelper().themeData(),
home: Scaffold(
body: ValidationErrorWidget(error: ''),
),
),
);
expect(find.byType(ValidationErrorWidget), findsOneWidget);
});
});
}

View File

@@ -0,0 +1,81 @@
import 'package:eatvio_app/core/theme/theme_helper.dart';
import 'package:eatvio_app/features/create_diet/presentation/cubit/create_diet_cubit.dart';
import 'package:eatvio_app/features/create_diet/presentation/cubit/create_diet_state.dart';
import 'package:eatvio_app/features/create_diet/presentation/widgets/status_drop_down_widget.dart';
import 'package:eatvio_app/features/feed/models/diets_pagination_model.dart';
import 'package:eatvio_app/generated/l10n.dart';
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:flutter_localizations/flutter_localizations.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:mockito/annotations.dart';
import 'package:mockito/mockito.dart';
import 'status_drop_down_widget_test.mocks.dart';
@GenerateMocks([CreateDietCubit])
void main() {
group('StatusDropDownWidget', () {
late MockCreateDietCubit mockCubit;
setUp(() {
mockCubit = MockCreateDietCubit();
});
Widget createTestWidget({DietState? dietState}) {
return MaterialApp(
theme: ThemeHelper().themeData(),
localizationsDelegates: const [
S.delegate,
GlobalMaterialLocalizations.delegate,
GlobalWidgetsLocalizations.delegate,
GlobalCupertinoLocalizations.delegate,
],
supportedLocales: const [Locale('ru')],
home: BlocProvider<CreateDietCubit>.value(
value: mockCubit,
child: Scaffold(
body: StatusDropDownWidget(dietState: dietState),
),
),
);
}
testWidgets('displays status dropdown widget', (tester) async {
when(mockCubit.state).thenReturn(
CreateDietState.initial(),
);
when(mockCubit.stream).thenAnswer((_) => Stream.value(
CreateDietState.initial(),
));
// ignore: return_of_invalid_type
when(mockCubit.setDietState(any)).thenAnswer((_) => () {});
await tester.pumpWidget(createTestWidget());
await tester.pumpAndSettle();
expect(find.byType(StatusDropDownWidget), findsOneWidget);
});
testWidgets('displays error when dietStateError is present', (tester) async {
when(mockCubit.state).thenReturn(
CreateDietState.initial().copyWith(
dietStateError: 'Error message',
),
);
when(mockCubit.stream).thenAnswer((_) => Stream.value(
CreateDietState.initial().copyWith(
dietStateError: 'Error message',
),
));
// ignore: return_of_invalid_type
when(mockCubit.setDietState(any)).thenAnswer((_) => () {});
await tester.pumpWidget(createTestWidget());
await tester.pumpAndSettle();
expect(find.byType(StatusDropDownWidget), findsOneWidget);
});
});
}

View File

@@ -0,0 +1,88 @@
import 'package:eatvio_app/core/theme/theme_helper.dart';
import 'package:eatvio_app/features/create_plan/presentation/cubit/create_plan_cubit.dart';
import 'package:eatvio_app/features/create_plan/presentation/cubit/create_plan_state.dart';
import 'package:eatvio_app/features/create_plan/presentation/widgets/select_exact_option_widget.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 'select_exact_option_widget_test.mocks.dart';
@GenerateMocks([CreatePlanCubit])
void main() {
group('SelectExactOptionWidget', () {
late MockCreatePlanCubit mockCubit;
setUp(() {
mockCubit = MockCreatePlanCubit();
});
Widget createTestWidget() {
return MaterialApp(
theme: ThemeHelper().themeData(),
home: BlocProvider<CreatePlanCubit>.value(
value: mockCubit,
child: Scaffold(
body: SelectExactOptionWidget(),
),
),
);
}
testWidgets('displays select exact option widget', (tester) async {
when(mockCubit.state).thenReturn(
CreatePlanState.initial(),
);
when(mockCubit.stream).thenAnswer((_) => Stream.value(
CreatePlanState.initial(),
));
// ignore: return_of_invalid_type
when(mockCubit.setSelectedOptionIndex(any)).thenAnswer((_) => () {});
await tester.pumpWidget(createTestWidget());
await tester.pumpAndSettle();
expect(find.byType(SelectExactOptionWidget), findsOneWidget);
});
testWidgets('displays option buttons 1, 2, 3', (tester) async {
when(mockCubit.state).thenReturn(
CreatePlanState.initial(),
);
when(mockCubit.stream).thenAnswer((_) => Stream.value(
CreatePlanState.initial(),
));
// ignore: return_of_invalid_type
when(mockCubit.setSelectedOptionIndex(any)).thenAnswer((_) => () {});
await tester.pumpWidget(createTestWidget());
await tester.pumpAndSettle();
expect(find.text('1'), findsOneWidget);
expect(find.text('2'), findsOneWidget);
expect(find.text('3'), findsOneWidget);
});
testWidgets('calls setSelectedOptionIndex when option is tapped', (tester) async {
when(mockCubit.state).thenReturn(
CreatePlanState.initial(),
);
when(mockCubit.stream).thenAnswer((_) => Stream.value(
CreatePlanState.initial(),
));
// ignore: return_of_invalid_type
when(mockCubit.setSelectedOptionIndex(any)).thenAnswer((_) => () {});
await tester.pumpWidget(createTestWidget());
await tester.pumpAndSettle();
await tester.tap(find.text('1'));
await tester.pump();
verify(mockCubit.setSelectedOptionIndex(0)).called(1);
});
});
}

View File

@@ -0,0 +1,113 @@
import 'package:eatvio_app/core/theme/theme_helper.dart';
import 'package:eatvio_app/features/create_recipe/entities/recipe_create_entity.dart';
import 'package:eatvio_app/features/create_recipe/presentation/bloc/create_recipe_cubit/create_recipe_cubit.dart';
import 'package:eatvio_app/features/create_recipe/presentation/bloc/create_recipe_cubit/create_recipe_state.dart';
import 'package:eatvio_app/features/create_recipe/presentation/widgets/tags_widget.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 'tags_widget_test.mocks.dart';
@GenerateMocks([CreateRecipeCubit])
void main() {
group('TagsWidget', () {
late MockCreateRecipeCubit mockCubit;
setUp(() {
mockCubit = MockCreateRecipeCubit();
});
Widget createTestWidget() {
return MaterialApp(
theme: ThemeHelper().themeData(),
home: BlocProvider<CreateRecipeCubit>.value(
value: mockCubit,
child: Scaffold(
body: TagsWidget(),
),
),
);
}
testWidgets('displays tags widget', (tester) async {
final initialState = CreateRecipeState(
entity: RecipeCreateEntity(
ingredients: [],
steps: [],
tags: [],
),
loading: false,
);
when(mockCubit.state).thenReturn(initialState);
when(mockCubit.stream).thenAnswer((_) => Stream.value(initialState));
// ignore: return_of_invalid_type
when(mockCubit.addTag(any)).thenAnswer((_) => () {});
// ignore: return_of_invalid_type
when(mockCubit.removeTag(any)).thenAnswer((_) => () {});
await tester.pumpWidget(createTestWidget());
await tester.pumpAndSettle();
expect(find.byType(TagsWidget), findsOneWidget);
});
testWidgets('displays existing tags', (tester) async {
final stateWithTags = CreateRecipeState(
entity: RecipeCreateEntity(
ingredients: [],
steps: [],
tags: ['tag1', 'tag2'],
),
loading: false,
);
when(mockCubit.state).thenReturn(stateWithTags);
when(mockCubit.stream).thenAnswer((_) => Stream.value(stateWithTags));
// ignore: return_of_invalid_type
when(mockCubit.addTag(any)).thenAnswer((_) => () {});
// ignore: return_of_invalid_type
when(mockCubit.removeTag(any)).thenAnswer((_) => () {});
await tester.pumpWidget(createTestWidget());
await tester.pumpAndSettle();
expect(find.text('tag1'), findsOneWidget);
expect(find.text('tag2'), findsOneWidget);
});
testWidgets('calls addTag when add button is tapped', (tester) async {
final initialState = CreateRecipeState(
entity: RecipeCreateEntity(
ingredients: [],
steps: [],
tags: [],
),
loading: false,
);
when(mockCubit.state).thenReturn(initialState);
when(mockCubit.stream).thenAnswer((_) => Stream.value(initialState));
// ignore: return_of_invalid_type
when(mockCubit.addTag(any)).thenAnswer((_) => () {});
// ignore: return_of_invalid_type
when(mockCubit.removeTag(any)).thenAnswer((_) => () {});
await tester.pumpWidget(createTestWidget());
await tester.pumpAndSettle();
// Find text field and enter tag
final textField = find.byType(TextField);
await tester.enterText(textField, 'new tag');
await tester.pump();
// Find and tap add button
final addButton = find.byIcon(Icons.done);
await tester.tap(addButton);
await tester.pump();
verify(mockCubit.addTag('new tag')).called(1);
});
});
}

View File

@@ -0,0 +1,46 @@
import 'package:eatvio_app/core/theme/theme_helper.dart';
import 'package:eatvio_app/features/diary/presentation/bloc/diary_cubit.dart';
import 'package:eatvio_app/features/diary/presentation/bloc/diary_state.dart';
import 'package:eatvio_app/features/diary/presentation/widgets/add_product_button_widget.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 'add_product_button_widget_test.mocks.dart';
@GenerateMocks([DiaryCubit])
void main() {
group('AddProductButtonWidget', () {
late MockDiaryCubit mockCubit;
setUp(() {
mockCubit = MockDiaryCubit();
});
Widget createTestWidget() {
return MaterialApp(
theme: ThemeHelper().themeData(),
home: BlocProvider<DiaryCubit>.value(
value: mockCubit,
child: Scaffold(
body: AddProductButtonWidget(),
),
),
);
}
testWidgets('displays add product button widget', (tester) async {
final loadingState = DiaryState.loading();
when(mockCubit.state).thenReturn(loadingState);
when(mockCubit.stream).thenAnswer((_) => Stream.value(loadingState));
await tester.pumpWidget(createTestWidget());
await tester.pumpAndSettle();
expect(find.byType(AddProductButtonWidget), findsOneWidget);
});
});
}

View File

@@ -0,0 +1,73 @@
import 'package:eatvio_app/core/theme/theme_helper.dart';
import 'package:eatvio_app/features/diary/presentation/widgets/chosen_diet_widget.dart';
import 'package:eatvio_app/generated/l10n.dart';
import 'package:flutter/material.dart';
import 'package:flutter_localizations/flutter_localizations.dart';
import 'package:flutter_test/flutter_test.dart';
void main() {
group('ChosenDietWidgetContent', () {
Widget createTestWidget(Widget child) {
return MaterialApp(
theme: ThemeHelper().themeData(),
localizationsDelegates: [
S.delegate,
GlobalMaterialLocalizations.delegate,
GlobalWidgetsLocalizations.delegate,
GlobalCupertinoLocalizations.delegate,
],
supportedLocales: S.delegate.supportedLocales,
home: Scaffold(
body: child,
),
);
}
testWidgets('displays chosen diet widget content', (tester) async {
await tester.pumpWidget(
createTestWidget(
ChosenDietWidgetContent(
currentDietDay: 5,
dietName: 'Test Diet',
dietDuration: 14,
dietId: 1,
),
),
);
expect(find.byType(ChosenDietWidgetContent), findsOneWidget);
expect(find.text('Test Diet'), findsOneWidget);
});
testWidgets('displays current day and duration', (tester) async {
await tester.pumpWidget(
createTestWidget(
ChosenDietWidgetContent(
currentDietDay: 7,
dietName: 'Test Diet',
dietDuration: 21,
dietId: 1,
),
),
);
expect(find.byType(ChosenDietWidgetContent), findsOneWidget);
});
testWidgets('displays diet name with underline', (tester) async {
await tester.pumpWidget(
createTestWidget(
ChosenDietWidgetContent(
currentDietDay: 1,
dietName: 'My Diet',
dietDuration: 7,
dietId: 1,
),
),
);
expect(find.text('My Diet'), findsOneWidget);
});
});
}

View File

@@ -0,0 +1,69 @@
import 'package:eatvio_app/core/theme/theme_helper.dart';
import 'package:eatvio_app/features/diary/presentation/widgets/chosen_plan_widget.dart';
import 'package:eatvio_app/generated/l10n.dart';
import 'package:flutter/material.dart';
import 'package:flutter_localizations/flutter_localizations.dart';
import 'package:flutter_test/flutter_test.dart';
void main() {
group('ChosenPlanWidgetContent', () {
Widget createTestWidget({
required int currentPlanDay,
required String planName,
required int planDuration,
}) {
return MaterialApp(
theme: ThemeHelper().themeData(),
localizationsDelegates: const [
S.delegate,
GlobalMaterialLocalizations.delegate,
GlobalWidgetsLocalizations.delegate,
GlobalCupertinoLocalizations.delegate,
],
supportedLocales: const [Locale('ru')],
home: Scaffold(
body: ChosenPlanWidgetContent(
currentPlanDay: currentPlanDay,
planName: planName,
planDuration: planDuration,
),
),
);
}
testWidgets('displays plan name', (tester) async {
await tester.pumpWidget(createTestWidget(
currentPlanDay: 5,
planName: 'Test Plan',
planDuration: 14,
));
expect(find.byType(ChosenPlanWidgetContent), findsOneWidget);
expect(find.text('Test Plan'), findsOneWidget);
});
testWidgets('displays day information', (tester) async {
await tester.pumpWidget(createTestWidget(
currentPlanDay: 3,
planName: 'My Plan',
planDuration: 7,
));
expect(find.byType(ChosenPlanWidgetContent), findsOneWidget);
expect(find.textContaining('3'), findsOneWidget);
expect(find.textContaining('7'), findsOneWidget);
});
testWidgets('displays correct day and duration', (tester) async {
await tester.pumpWidget(createTestWidget(
currentPlanDay: 10,
planName: 'Long Plan',
planDuration: 30,
));
expect(find.byType(ChosenPlanWidgetContent), findsOneWidget);
expect(find.text('Long Plan'), findsOneWidget);
});
});
}

View File

@@ -0,0 +1,51 @@
import 'package:eatvio_app/core/theme/theme_helper.dart';
import 'package:eatvio_app/features/diary/presentation/widgets/comment_container.dart';
import 'package:flutter/material.dart';
import 'package:flutter_test/flutter_test.dart';
void main() {
group('CommentContainer', () {
testWidgets('displays comment text', (tester) async {
await tester.pumpWidget(
MaterialApp(
theme: ThemeHelper().themeData(),
home: Scaffold(
body: CommentContainer(comment: 'Test comment'),
),
),
);
expect(find.byType(CommentContainer), findsOneWidget);
expect(find.text('Test comment'), findsOneWidget);
});
testWidgets('displays empty comment', (tester) async {
await tester.pumpWidget(
MaterialApp(
theme: ThemeHelper().themeData(),
home: Scaffold(
body: CommentContainer(comment: ''),
),
),
);
expect(find.byType(CommentContainer), findsOneWidget);
});
testWidgets('displays long comment', (tester) async {
const longComment = 'This is a very long comment that should be displayed properly in the container';
await tester.pumpWidget(
MaterialApp(
theme: ThemeHelper().themeData(),
home: Scaffold(
body: CommentContainer(comment: longComment),
),
),
);
expect(find.byType(CommentContainer), findsOneWidget);
expect(find.text(longComment), findsOneWidget);
});
});
}

View File

@@ -0,0 +1,46 @@
import 'package:eatvio_app/core/theme/theme_helper.dart';
import 'package:eatvio_app/features/diary/presentation/widgets/finish_day_button_widget.dart';
import 'package:flutter/material.dart';
import 'package:flutter_test/flutter_test.dart';
void main() {
group('FinishDayButtonWidget', () {
testWidgets('displays finish day button', (tester) async {
await tester.pumpWidget(
MaterialApp(
theme: ThemeHelper().themeData(),
home: Scaffold(
body: FinishDayButtonWidget(
onPressed: () async {},
),
),
),
);
expect(find.byType(FinishDayButtonWidget), findsOneWidget);
expect(find.text('Закончить день'), findsOneWidget);
});
testWidgets('calls onPressed when tapped', (tester) async {
bool wasCalled = false;
await tester.pumpWidget(
MaterialApp(
theme: ThemeHelper().themeData(),
home: Scaffold(
body: FinishDayButtonWidget(
onPressed: () async {
wasCalled = true;
},
),
),
),
);
await tester.tap(find.text('Закончить день'));
await tester.pump();
expect(wasCalled, isTrue);
});
});
}

View File

@@ -0,0 +1,83 @@
import 'package:eatvio_app/features/diary/models/diary_day_model.dart';
import 'package:eatvio_app/features/diary/presentation/widgets/meal_summary_widget.dart';
import 'package:flutter/material.dart';
import 'package:flutter_test/flutter_test.dart';
void main() {
group('MealSummaryWidget', () {
testWidgets('displays meal summary with totals', (tester) async {
final portions = <PortionModel>[];
await tester.pumpWidget(
MaterialApp(
home: Scaffold(
body: MealSummaryWidget(
portions: portions,
mealNumber: 1,
),
),
),
);
expect(find.byType(MealSummaryWidget), findsOneWidget);
});
testWidgets('hides when all totals are zero', (tester) async {
final portions = <PortionModel>[];
await tester.pumpWidget(
MaterialApp(
home: Scaffold(
body: MealSummaryWidget(
portions: portions,
mealNumber: 1,
),
),
),
);
await tester.pumpAndSettle();
// Widget should be hidden (SizedBox.shrink) when totals are zero
expect(find.byType(MealSummaryWidget), findsOneWidget);
});
testWidgets('displays meal number', (tester) async {
final portions = <PortionModel>[];
await tester.pumpWidget(
MaterialApp(
home: Scaffold(
body: MealSummaryWidget(
portions: portions,
mealNumber: 3,
),
),
),
);
expect(find.byType(MealSummaryWidget), findsOneWidget);
});
});
group('MealSummaryWidget.calculateMealTotals', () {
test('returns zero totals for empty portions list', () {
final result = MealSummaryWidget.calculateMealTotals([]);
expect(result['calories'], 0.0);
expect(result['proteins'], 0.0);
expect(result['fats'], 0.0);
expect(result['carbohydrates'], 0.0);
});
test('returns correct map structure', () {
final result = MealSummaryWidget.calculateMealTotals([]);
expect(result.containsKey('calories'), isTrue);
expect(result.containsKey('proteins'), isTrue);
expect(result.containsKey('fats'), isTrue);
expect(result.containsKey('carbohydrates'), isTrue);
});
});
}

View File

@@ -0,0 +1,35 @@
import 'package:eatvio_app/features/diary/presentation/widgets/product_loader_widget.dart';
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:flutter_test/flutter_test.dart';
void main() {
group('ProductLoaderWidget', () {
testWidgets('displays product loader widget', (tester) async {
await tester.pumpWidget(
MaterialApp(
home: Scaffold(
body: ProductLoaderWidget(),
),
),
);
expect(find.byType(ProductLoaderWidget), findsOneWidget);
expect(find.byType(CupertinoActivityIndicator), findsOneWidget);
});
testWidgets('displays ListTile with activity indicator', (tester) async {
await tester.pumpWidget(
MaterialApp(
home: Scaffold(
body: ProductLoaderWidget(),
),
),
);
expect(find.byType(ListTile), findsOneWidget);
expect(find.byType(CupertinoActivityIndicator), findsOneWidget);
});
});
}

View File

@@ -0,0 +1,101 @@
import 'package:eatvio_app/core/theme/theme_helper.dart';
import 'package:eatvio_app/features/diets/presentation/widgets/info_bouble.dart';
import 'package:flutter/material.dart';
import 'package:flutter_test/flutter_test.dart';
void main() {
group('InfoBouble', () {
testWidgets('displays info bouble with children', (tester) async {
await tester.pumpWidget(
MaterialApp(
theme: ThemeHelper().themeData(),
home: Scaffold(
body: InfoBouble(
children: [
Text('Test'),
Icon(Icons.star),
],
),
),
),
);
expect(find.byType(InfoBouble), findsOneWidget);
expect(find.text('Test'), findsOneWidget);
expect(find.byIcon(Icons.star), findsOneWidget);
});
testWidgets('displays with custom background color', (tester) async {
await tester.pumpWidget(
MaterialApp(
theme: ThemeHelper().themeData(),
home: Scaffold(
body: InfoBouble(
backgroundColor: Colors.blue,
children: [
Text('Test'),
],
),
),
),
);
expect(find.byType(InfoBouble), findsOneWidget);
});
testWidgets('displays with custom radius', (tester) async {
await tester.pumpWidget(
MaterialApp(
theme: ThemeHelper().themeData(),
home: Scaffold(
body: InfoBouble(
radius: 20,
children: [
Text('Test'),
],
),
),
),
);
expect(find.byType(InfoBouble), findsOneWidget);
});
testWidgets('displays with custom padding', (tester) async {
await tester.pumpWidget(
MaterialApp(
theme: ThemeHelper().themeData(),
home: Scaffold(
body: InfoBouble(
padding: EdgeInsets.all(16),
children: [
Text('Test'),
],
),
),
),
);
expect(find.byType(InfoBouble), findsOneWidget);
});
testWidgets('displays with custom mainAxisAlignment', (tester) async {
await tester.pumpWidget(
MaterialApp(
theme: ThemeHelper().themeData(),
home: Scaffold(
body: InfoBouble(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text('Test'),
],
),
),
),
);
expect(find.byType(InfoBouble), findsOneWidget);
});
});
}

View File

@@ -0,0 +1,92 @@
import 'package:eatvio_app/core/app_initializer.dart';
import 'package:eatvio_app/core/di/app_config_registration.dart';
import 'package:eatvio_app/core/enums/complained_content.dart';
import 'package:eatvio_app/core/locator_service.dart';
import 'package:eatvio_app/core/theme/theme_helper.dart';
import 'package:eatvio_app/features/diets/presentation/widgets/more_options.dart';
import 'package:eatvio_app/generated/l10n.dart';
import 'package:flutter/material.dart';
import 'package:flutter_localizations/flutter_localizations.dart';
import 'package:flutter_test/flutter_test.dart';
void main() {
TestWidgetsFlutterBinding.ensureInitialized();
group('MoreOptions', () {
setUp(() async {
await sl.reset();
AppConfigRegistration.registerAppConfig(buildType: AppBuildType.tests);
});
Widget createTestWidget({
String path = 'test/path',
ComplainedContent? complainedContentType,
String? slug,
int? id,
bool? disableActions,
}) {
return MaterialApp(
theme: ThemeHelper().themeData(),
localizationsDelegates: [
S.delegate,
GlobalMaterialLocalizations.delegate,
GlobalWidgetsLocalizations.delegate,
GlobalCupertinoLocalizations.delegate,
],
supportedLocales: const [Locale('ru'), Locale('en')],
locale: const Locale('ru'),
home: Scaffold(
body: MoreOptions(
path: path,
complainedContentType: complainedContentType,
slug: slug,
id: id,
disableActions: disableActions,
),
),
);
}
testWidgets('displays copy link option', (tester) async {
await tester.pumpWidget(createTestWidget());
await tester.pumpAndSettle();
expect(find.byType(MoreOptions), findsOneWidget);
});
testWidgets('displays share option', (tester) async {
await tester.pumpWidget(createTestWidget());
await tester.pumpAndSettle();
expect(find.byType(MoreOptions), findsOneWidget);
});
testWidgets('displays complain option when complainedContentType is provided', (tester) async {
await tester.pumpWidget(
createTestWidget(
complainedContentType: ComplainedContent.diet,
id: 1,
slug: 'test-slug',
),
);
await tester.pumpAndSettle();
expect(find.byType(MoreOptions), findsOneWidget);
});
testWidgets('disables actions when disableActions is true', (tester) async {
await tester.pumpWidget(createTestWidget(disableActions: true));
await tester.pumpAndSettle();
expect(find.byType(MoreOptions), findsOneWidget);
});
testWidgets('enables actions when disableActions is false', (tester) async {
await tester.pumpWidget(createTestWidget(disableActions: false));
await tester.pumpAndSettle();
expect(find.byType(MoreOptions), findsOneWidget);
});
});
}

View File

@@ -4,62 +4,96 @@ import 'package:flutter_test/flutter_test.dart';
void main() {
group('CommentModel', () {
test('создает CommentModel с правильными значениями', () {
test('создает экземпляр с данными', () {
final comment = Comment(
id: 1,
authorName: 'Author Name',
authorSlug: 'author',
authorProfileIcon: 'https://example.com/icon.jpg',
authorName: 'Test Author',
authorSlug: 'test-author',
authorProfileIcon: 'icon.png',
body: 'Test comment',
childrenComments: [],
createdAt: DateTime(2024, 1, 1),
createdAt: DateTime.now(),
);
final model = CommentModel(
data: [comment],
links: const Links(
first: 'https://example.com?page=1',
last: 'https://example.com?page=1',
prev: null,
next: null,
),
meta: const Meta(
currentPage: 1,
from: 1,
lastPage: 1,
links: [],
perPage: 10,
to: 1,
total: 1,
),
links: const Links(first: '', last: '', prev: null, next: null),
meta: const Meta(currentPage: 1, from: null, lastPage: 1, links: [], perPage: 10, to: null, total: 0),
);
expect(model.data.length, 1);
expect(model.data.first.id, 1);
expect(model.data.first.body, 'Test comment');
});
test('fromJson создает модель из JSON', () {
final json = {
'data': [
{
'id': 1,
'author_name': 'Test Author',
'author_slug': 'test-author',
'author_profile_icon': 'icon.png',
'body': 'Test comment',
'children_comments': [],
'created_at': '2024-01-01T00:00:00Z',
}
],
'links': {'first': '', 'last': '', 'prev': null, 'next': null},
'meta': {
'current_page': 1,
'from': null,
'last_page': 1,
'links': [],
'per_page': 10,
'to': null,
'total': 0,
},
};
final model = CommentModel.fromJson(json);
expect(model.data.length, 1);
expect(model.data.first.id, 1);
});
});
group('Comment', () {
test('создает Comment с вложенными комментариями', () {
test('создает экземпляр с данными', () {
final comment = Comment(
id: 1,
authorName: 'Test Author',
authorSlug: 'test-author',
authorProfileIcon: 'icon.png',
body: 'Test comment',
childrenComments: [],
createdAt: DateTime.now(),
);
expect(comment.id, 1);
expect(comment.authorName, 'Test Author');
expect(comment.body, 'Test comment');
});
test('поддерживает вложенные комментарии', () {
final childComment = Comment(
id: 2,
authorName: 'Child Author',
authorSlug: 'child-author',
authorProfileIcon: 'https://example.com/icon2.jpg',
authorProfileIcon: 'child.png',
body: 'Child comment',
childrenComments: [],
createdAt: DateTime(2024, 1, 2),
createdAt: DateTime.now(),
);
final parentComment = Comment(
id: 1,
authorName: 'Author Name',
authorSlug: 'author',
authorProfileIcon: 'https://example.com/icon.jpg',
authorName: 'Parent Author',
authorSlug: 'parent-author',
authorProfileIcon: 'parent.png',
body: 'Parent comment',
childrenComments: [childComment],
createdAt: DateTime(2024, 1, 1),
createdAt: DateTime.now(),
);
expect(parentComment.childrenComments.length, 1);

View File

@@ -0,0 +1,87 @@
import 'package:eatvio_app/core/theme/theme_helper.dart';
import 'package:eatvio_app/features/feed/presentation/widgets/chip_group.dart';
import 'package:flutter/material.dart';
import 'package:flutter_test/flutter_test.dart';
void main() {
group('ChipGroup', () {
testWidgets('displays chip group widget', (tester) async {
await tester.pumpWidget(
MaterialApp(
theme: ThemeHelper().themeData(),
home: Scaffold(
body: ChipGroup(
selectedChipIndex: 0,
chipsLabelList: ['All', 'Recipes', 'Diets'],
setSelectedChipIndex: (index) {},
),
),
),
);
expect(find.byType(ChipGroup), findsOneWidget);
});
testWidgets('displays all chips from list', (tester) async {
await tester.pumpWidget(
MaterialApp(
theme: ThemeHelper().themeData(),
home: Scaffold(
body: ChipGroup(
selectedChipIndex: 0,
chipsLabelList: ['All', 'Recipes', 'Diets', 'Plans'],
setSelectedChipIndex: (index) {},
),
),
),
);
expect(find.byType(ChipGroup), findsOneWidget);
expect(find.text('All'), findsOneWidget);
expect(find.text('Recipes'), findsOneWidget);
expect(find.text('Diets'), findsOneWidget);
expect(find.text('Plans'), findsOneWidget);
});
testWidgets('calls setSelectedChipIndex when chip is tapped', (tester) async {
int? selectedIndex;
await tester.pumpWidget(
MaterialApp(
theme: ThemeHelper().themeData(),
home: Scaffold(
body: ChipGroup(
selectedChipIndex: 0,
chipsLabelList: ['All', 'Recipes', 'Diets'],
setSelectedChipIndex: (index) {
selectedIndex = index;
},
),
),
),
);
await tester.tap(find.text('Recipes'));
await tester.pump();
expect(selectedIndex, 1);
});
testWidgets('displays empty chip group when list is empty', (tester) async {
await tester.pumpWidget(
MaterialApp(
theme: ThemeHelper().themeData(),
home: Scaffold(
body: ChipGroup(
selectedChipIndex: 0,
chipsLabelList: [],
setSelectedChipIndex: (index) {},
),
),
),
);
expect(find.byType(ChipGroup), findsOneWidget);
});
});
}

View File

@@ -0,0 +1,131 @@
import 'package:eatvio_app/core/theme/theme_helper.dart';
import 'package:eatvio_app/features/feed/presentation/widgets/choice_chip_widget.dart';
import 'package:flutter/material.dart';
import 'package:flutter_test/flutter_test.dart';
void main() {
group('ChoiceChipWidget', () {
testWidgets('displays chip with label', (tester) async {
await tester.pumpWidget(
MaterialApp(
theme: ThemeHelper().themeData(),
home: Scaffold(
body: ChoiceChipWidget(
chipsLabelList: ['Option 1', 'Option 2'],
index: 0,
selectedChipIndex: 0,
onSelected: (selected) {},
),
),
),
);
expect(find.byType(ChoiceChipWidget), findsOneWidget);
expect(find.text('Option 1'), findsOneWidget);
});
testWidgets('shows selected state when index matches selectedChipIndex', (tester) async {
await tester.pumpWidget(
MaterialApp(
theme: ThemeHelper().themeData(),
home: Scaffold(
body: ChoiceChipWidget(
chipsLabelList: ['Option 1', 'Option 2'],
index: 0,
selectedChipIndex: 0,
onSelected: (selected) {},
),
),
),
);
expect(find.byType(ChoiceChipWidget), findsOneWidget);
final chip = tester.widget<ChoiceChip>(find.byType(ChoiceChip));
expect(chip.selected, isTrue);
});
testWidgets('shows unselected state when index does not match', (tester) async {
await tester.pumpWidget(
MaterialApp(
theme: ThemeHelper().themeData(),
home: Scaffold(
body: ChoiceChipWidget(
chipsLabelList: ['Option 1', 'Option 2'],
index: 1,
selectedChipIndex: 0,
onSelected: (selected) {},
),
),
),
);
expect(find.byType(ChoiceChipWidget), findsOneWidget);
final chip = tester.widget<ChoiceChip>(find.byType(ChoiceChip));
expect(chip.selected, isFalse);
});
testWidgets('applies custom padding', (tester) async {
await tester.pumpWidget(
MaterialApp(
theme: ThemeHelper().themeData(),
home: Scaffold(
body: ChoiceChipWidget(
chipsLabelList: ['Option 1'],
index: 0,
selectedChipIndex: 0,
padding: EdgeInsets.all(16),
onSelected: (selected) {},
),
),
),
);
expect(find.byType(ChoiceChipWidget), findsOneWidget);
});
testWidgets('applies custom colors', (tester) async {
await tester.pumpWidget(
MaterialApp(
theme: ThemeHelper().themeData(),
home: Scaffold(
body: ChoiceChipWidget(
chipsLabelList: ['Option 1'],
index: 0,
selectedChipIndex: 0,
activeColor: Colors.blue,
inactiveColor: Colors.grey,
onSelected: (selected) {},
),
),
),
);
expect(find.byType(ChoiceChipWidget), findsOneWidget);
});
testWidgets('calls onSelected when tapped', (tester) async {
bool wasCalled = false;
await tester.pumpWidget(
MaterialApp(
theme: ThemeHelper().themeData(),
home: Scaffold(
body: ChoiceChipWidget(
chipsLabelList: ['Option 1'],
index: 0,
selectedChipIndex: 0,
onSelected: (selected) {
wasCalled = true;
},
),
),
),
);
await tester.tap(find.byType(ChoiceChip));
await tester.pump();
expect(wasCalled, isTrue);
});
});
}

View File

@@ -0,0 +1,145 @@
import 'package:eatvio_app/core/app_initializer.dart';
import 'package:eatvio_app/core/di/app_config_registration.dart';
import 'package:eatvio_app/core/models/pagination_model.dart';
import 'package:eatvio_app/features/bottom_navigation_bar/presentation/bloc/profile_avatar_cubit/profile_avatar_cubit.dart';
import 'package:eatvio_app/features/bottom_navigation_bar/presentation/bloc/profile_avatar_cubit/profile_avatar_state.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/profile/models/profile_model.dart';
import 'package:eatvio_app/generated/l10n.dart';
import 'package:flutter/material.dart';
import 'package:flutter_dotenv/flutter_dotenv.dart';
import 'package:flutter_localizations/flutter_localizations.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:get_it/get_it.dart';
import 'package:mocktail/mocktail.dart';
import 'package:network_image_mock/network_image_mock.dart';
class MockProfileAvatarCubit extends Mock implements ProfileAvatarCubit {}
void main() {
late MockProfileAvatarCubit mockProfileAvatarCubit;
setUpAll(() async {
GetIt.instance.reset();
// Инициализируем dotenv для AppConfig
dotenv.loadFromString(envString: 'DEV_BASE_URL=http://localhost\nBASE_URL=http://localhost');
});
setUp(() {
mockProfileAvatarCubit = MockProfileAvatarCubit();
GetIt.instance.registerSingleton<ProfileAvatarCubit>(mockProfileAvatarCubit);
// Регистрируем AppConfig для тестов
AppConfigRegistration.registerAppConfig(buildType: AppBuildType.tests);
});
tearDown(() {
GetIt.instance.reset();
});
group('CommentsWidget', () {
testWidgets('отображается как виджет', (WidgetTester tester) async {
const testProfile = ProfileModel(
id: 1,
slug: 'test-user',
name: 'Test User',
email: 'test@example.com',
phoneConfirmationState: 'confirmed',
isPublicProfile: true,
isBlockedByMe: false,
isBlockedByAuthor: false,
isBlockedByUser: false,
isEntitySubscribedByMe: false,
profilePhotoUrl: '',
roles: [],
badges: [],
subscriptions: [],
subscribers: [],
);
when(() => mockProfileAvatarCubit.state).thenReturn(const ProfileAvatarState.loaded(profile: testProfile));
const commentModel = CommentModel(
data: [],
links: Links(first: '', last: '', prev: null, next: null),
meta: Meta(currentPage: 1, from: null, lastPage: 1, links: [], perPage: 10, to: null, total: 0),
);
await tester.pumpWidget(
MaterialApp(
localizationsDelegates: const [
S.delegate,
GlobalMaterialLocalizations.delegate,
GlobalWidgetsLocalizations.delegate,
GlobalCupertinoLocalizations.delegate,
],
supportedLocales: S.delegate.supportedLocales,
home: const Scaffold(
body: CommentsWidget(id: '1', commentSourceType: CommentSourceType.blogPost, comment: commentModel),
),
),
);
expect(find.byType(CommentsWidget), findsOneWidget);
});
testWidgets('отображает комментарии', (WidgetTester tester) async {
const testProfile = ProfileModel(
id: 1,
slug: 'test-author',
name: 'Test Author',
email: 'test@example.com',
phoneConfirmationState: 'confirmed',
isPublicProfile: true,
isBlockedByMe: false,
isBlockedByAuthor: false,
isBlockedByUser: false,
isEntitySubscribedByMe: false,
profilePhotoUrl: '',
roles: [],
badges: [],
subscriptions: [],
subscribers: [],
);
when(() => mockProfileAvatarCubit.state).thenReturn(const ProfileAvatarState.loaded(profile: testProfile));
final comment = Comment(
id: 1,
authorName: 'Test Author',
authorSlug: 'test-author',
authorProfileIcon: '',
body: 'Test comment',
childrenComments: [],
createdAt: DateTime.now(),
);
final commentModel = CommentModel(
data: [comment],
links: const Links(first: '', last: '', prev: null, next: null),
meta: const Meta(currentPage: 1, from: null, lastPage: 1, links: [], perPage: 10, to: null, total: 0),
);
// Используем mockNetworkImages для обработки NetworkImage в тестах
await mockNetworkImagesFor(() async {
await tester.pumpWidget(
MaterialApp(
localizationsDelegates: const [
S.delegate,
GlobalMaterialLocalizations.delegate,
GlobalWidgetsLocalizations.delegate,
GlobalCupertinoLocalizations.delegate,
],
supportedLocales: S.delegate.supportedLocales,
home: Scaffold(
body: CommentsWidget(id: '1', commentSourceType: CommentSourceType.blogPost, comment: commentModel),
),
),
);
await tester.pumpAndSettle();
expect(find.byType(CommentsWidget), findsOneWidget);
});
});
});
}

View File

@@ -0,0 +1,169 @@
import 'package:eatvio_app/core/app_config.dart';
import 'package:eatvio_app/core/app_initializer.dart';
import 'package:eatvio_app/core/di/app_config_registration.dart';
import 'package:eatvio_app/core/locator_service.dart';
import 'package:eatvio_app/core/theme/theme_helper.dart';
import 'package:eatvio_app/features/feed/models/feed_consultant_model.dart';
import 'package:eatvio_app/features/feed/presentation/widgets/consultant_widget.dart';
import 'package:eatvio_app/features/profile/models/badge_model.dart';
import 'package:eatvio_app/generated/l10n.dart';
import 'package:flutter/material.dart';
import 'package:flutter_localizations/flutter_localizations.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:go_router/go_router.dart';
import 'package:flutter_dotenv/flutter_dotenv.dart';
import 'package:network_image_mock/network_image_mock.dart';
void main() {
TestWidgetsFlutterBinding.ensureInitialized();
setUpAll(() async {
// Initialize dotenv for tests
dotenv.loadFromString(envString: 'DEV_BASE_URL=http://localhost\nBASE_URL=http://localhost');
});
group('ConsultantWidget', () {
setUp(() async {
await sl.reset();
dotenv.loadFromString(envString: 'DEV_BASE_URL=http://localhost\nBASE_URL=http://localhost');
AppConfigRegistration.registerAppConfig(buildType: AppBuildType.tests);
// Ensure AppConfig has the correct baseUrl for image URL construction
sl<AppConfig>().setBaseUrl('http://localhost');
});
Widget createTestWidget(FeedConsultantModel consultant) {
return MaterialApp.router(
theme: ThemeHelper().themeData(),
localizationsDelegates: [
S.delegate,
GlobalMaterialLocalizations.delegate,
GlobalWidgetsLocalizations.delegate,
GlobalCupertinoLocalizations.delegate,
],
supportedLocales: const [Locale('ru'), Locale('en')],
locale: const Locale('ru'),
routerConfig: GoRouter(
initialLocation: '/',
routes: [
GoRoute(
path: '/',
builder: (context, state) => Scaffold(
body: ConsultantWidget(consultant: consultant),
),
),
GoRoute(
path: '/profileScreen',
name: 'profileScreen',
builder: (context, state) => Scaffold(
appBar: AppBar(title: const Text('Profile')),
body: Center(child: Text('Profile: ${state.extra}')),
),
),
],
),
);
}
testWidgets('displays consultant information', (tester) async {
await mockNetworkImagesFor(() async {
final consultant = FeedConsultantModel(
slug: 'test-consultant',
name: 'Test Consultant',
photo: 'https://example.com/photo.jpg',
description: 'Test description',
totalScores: 100,
ratingPlace: 1,
badges: [],
);
await tester.pumpWidget(createTestWidget(consultant));
await tester.pumpAndSettle();
expect(find.text('Test Consultant'), findsOneWidget);
expect(find.text('Рейтинг: 100'), findsOneWidget);
expect(find.text('Test description'), findsOneWidget);
});
});
testWidgets('displays consultant with badges', (tester) async {
await mockNetworkImagesFor(() async {
final consultant = FeedConsultantModel(
slug: 'test-consultant',
name: 'Test Consultant',
photo: 'https://example.com/photo.jpg',
description: 'Test description',
totalScores: 100,
ratingPlace: 1,
badges: [
BadgeModel(id: 1, title: 'Badge 1', image: 'badge1.png', description: 'Description 1', type: 'test'),
BadgeModel(id: 2, title: 'Badge 2', image: 'badge2.png', description: 'Description 2', type: 'test'),
],
);
await tester.pumpWidget(createTestWidget(consultant));
await tester.pumpAndSettle();
expect(find.text('Test Consultant'), findsOneWidget);
expect(find.byType(ConsultantWidget), findsOneWidget);
});
});
testWidgets('displays rating place', (tester) async {
await mockNetworkImagesFor(() async {
final consultant = FeedConsultantModel(
slug: 'test-consultant',
name: 'Test Consultant',
photo: 'https://example.com/photo.jpg',
description: 'Test description',
totalScores: 100,
ratingPlace: 5,
badges: [],
);
await tester.pumpWidget(createTestWidget(consultant));
await tester.pumpAndSettle();
expect(find.text('5'), findsOneWidget);
});
});
testWidgets('handles photo with https URL', (tester) async {
await mockNetworkImagesFor(() async {
final consultant = FeedConsultantModel(
slug: 'test-consultant',
name: 'Test Consultant',
photo: 'https://example.com/photo.jpg',
description: 'Test description',
totalScores: 100,
ratingPlace: 1,
badges: [],
);
await tester.pumpWidget(createTestWidget(consultant));
await tester.pumpAndSettle();
expect(find.byType(CircleAvatar), findsOneWidget);
});
});
testWidgets('handles photo without https URL', (tester) async {
await mockNetworkImagesFor(() async {
final consultant = FeedConsultantModel(
slug: 'test-consultant',
name: 'Test Consultant',
photo: '/storage/photo.jpg',
description: 'Test description',
totalScores: 100,
ratingPlace: 1,
badges: [],
);
await tester.pumpWidget(createTestWidget(consultant));
await tester.pumpAndSettle();
expect(find.byType(CircleAvatar), findsOneWidget);
});
});
});
}

View File

@@ -0,0 +1,125 @@
import 'package:eatvio_app/core/theme/theme_helper.dart';
import 'package:eatvio_app/features/feed/presentation/bloc/diets_cubit/diets_cubit.dart';
import 'package:eatvio_app/features/feed/presentation/bloc/diets_cubit/diets_state.dart';
import 'package:eatvio_app/features/feed/presentation/bloc/plans_cubit/plans_cubit.dart';
import 'package:eatvio_app/features/feed/presentation/bloc/plans_cubit/plans_state.dart';
import 'package:eatvio_app/features/feed/presentation/widgets/duration_choice_filter_widget.dart';
import 'package:eatvio_app/features/my_diets/presentation/cubit/my_diets_cubit.dart';
import 'package:eatvio_app/features/my_diets/presentation/cubit/my_diets_state.dart';
import 'package:eatvio_app/features/my_plans/presentation/cubit/my_plans_cubit.dart';
import 'package:eatvio_app/features/my_plans/presentation/cubit/my_plans_state.dart';
import 'package:eatvio_app/generated/l10n.dart';
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:flutter_localizations/flutter_localizations.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:mockito/annotations.dart';
import 'package:mockito/mockito.dart';
import 'duration_choice_filter_widget_test.mocks.dart';
@GenerateNiceMocks([
MockSpec<PlansCubit>(),
MockSpec<DietsCubit>(),
MockSpec<MyPlansCubit>(),
MockSpec<MyDietsCubit>(),
])
void main() {
group('DurationChoiceFilterWidget', () {
late MockPlansCubit mockPlansCubit;
late MockDietsCubit mockDietsCubit;
late MockMyPlansCubit mockMyPlansCubit;
late MockMyDietsCubit mockMyDietsCubit;
setUp(() {
mockPlansCubit = MockPlansCubit();
mockDietsCubit = MockDietsCubit();
mockMyPlansCubit = MockMyPlansCubit();
mockMyDietsCubit = MockMyDietsCubit();
});
Widget createTestWidget(Widget child) {
return MaterialApp(
theme: ThemeHelper().themeData(),
localizationsDelegates: [
S.delegate,
GlobalMaterialLocalizations.delegate,
GlobalWidgetsLocalizations.delegate,
GlobalCupertinoLocalizations.delegate,
],
supportedLocales: const [Locale('ru'), Locale('en')],
locale: const Locale('ru'),
home: Scaffold(body: child),
);
}
testWidgets('displays duration filter for DietsCubit', (tester) async {
when(mockDietsCubit.state).thenReturn(DietsState.initial());
when(mockDietsCubit.stream).thenAnswer((_) => Stream.value(DietsState.initial()));
await tester.pumpWidget(
createTestWidget(
BlocProvider<DietsCubit>.value(
value: mockDietsCubit,
child: DurationChoiceFilterWidget(dietCubit: mockDietsCubit),
),
),
);
await tester.pumpAndSettle();
expect(find.byType(DurationChoiceFilterWidget), findsOneWidget);
});
testWidgets('displays duration filter for PlansCubit', (tester) async {
when(mockPlansCubit.state).thenReturn(PlansState.initial());
when(mockPlansCubit.stream).thenAnswer((_) => Stream.value(PlansState.initial()));
await tester.pumpWidget(
createTestWidget(
BlocProvider<PlansCubit>.value(
value: mockPlansCubit,
child: DurationChoiceFilterWidget(plansCubit: mockPlansCubit),
),
),
);
await tester.pumpAndSettle();
expect(find.byType(DurationChoiceFilterWidget), findsOneWidget);
});
testWidgets('displays duration filter for MyPlansCubit', (tester) async {
when(mockMyPlansCubit.state).thenReturn(MyPlansState());
when(mockMyPlansCubit.stream).thenAnswer((_) => Stream.value(MyPlansState()));
await tester.pumpWidget(
createTestWidget(
BlocProvider<MyPlansCubit>.value(
value: mockMyPlansCubit,
child: DurationChoiceFilterWidget(myPlansCubit: mockMyPlansCubit),
),
),
);
await tester.pumpAndSettle();
expect(find.byType(DurationChoiceFilterWidget), findsOneWidget);
});
testWidgets('displays duration filter for MyDietsCubit', (tester) async {
when(mockMyDietsCubit.state).thenReturn(MyDietsState());
when(mockMyDietsCubit.stream).thenAnswer((_) => Stream.value(MyDietsState()));
await tester.pumpWidget(
createTestWidget(
BlocProvider<MyDietsCubit>.value(
value: mockMyDietsCubit,
child: DurationChoiceFilterWidget(myDietsCubit: mockMyDietsCubit),
),
),
);
await tester.pumpAndSettle();
expect(find.byType(DurationChoiceFilterWidget), findsOneWidget);
});
});
}

View File

@@ -0,0 +1,47 @@
import 'package:eatvio_app/core/theme/theme_helper.dart';
import 'package:eatvio_app/features/feed/presentation/widgets/filter_badge.dart';
import 'package:flutter/material.dart';
import 'package:flutter_test/flutter_test.dart';
void main() {
group('FilterBadge', () {
testWidgets('displays filter badge widget', (tester) async {
await tester.pumpWidget(
MaterialApp(
theme: ThemeHelper().themeData(),
home: Scaffold(
body: FilterBadge(
filterBottomSheet: Container(
child: Text('Filter Bottom Sheet'),
),
),
),
),
);
expect(find.byType(FilterBadge), findsOneWidget);
expect(find.byType(GestureDetector), findsOneWidget);
});
testWidgets('opens bottom sheet when tapped', (tester) async {
await tester.pumpWidget(
MaterialApp(
theme: ThemeHelper().themeData(),
home: Scaffold(
body: FilterBadge(
filterBottomSheet: Container(
child: Text('Filter Bottom Sheet'),
),
),
),
),
);
await tester.tap(find.byType(FilterBadge));
await tester.pumpAndSettle();
expect(find.text('Filter Bottom Sheet'), findsOneWidget);
});
});
}

View File

@@ -0,0 +1,81 @@
import 'package:eatvio_app/core/theme/theme_helper.dart';
import 'package:eatvio_app/features/feed/presentation/bloc/diets_cubit/diets_cubit.dart';
import 'package:eatvio_app/features/feed/presentation/bloc/diets_cubit/diets_state.dart';
import 'package:eatvio_app/features/feed/presentation/widgets/sorting_drop_down_filter.dart';
import 'package:eatvio_app/features/my_diets/presentation/cubit/my_diets_cubit.dart';
import 'package:eatvio_app/features/my_diets/presentation/cubit/my_diets_state.dart';
import 'package:eatvio_app/generated/l10n.dart';
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:flutter_localizations/flutter_localizations.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:mockito/annotations.dart';
import 'package:mockito/mockito.dart';
import 'sorting_drop_down_filter_test.mocks.dart';
@GenerateNiceMocks([
MockSpec<DietsCubit>(),
MockSpec<MyDietsCubit>(),
])
void main() {
group('SortingDropDownFilter', () {
late MockDietsCubit mockDietsCubit;
late MockMyDietsCubit mockMyDietsCubit;
setUp(() {
mockDietsCubit = MockDietsCubit();
mockMyDietsCubit = MockMyDietsCubit();
});
Widget createTestWidget(Widget child) {
return MaterialApp(
theme: ThemeHelper().themeData(),
localizationsDelegates: [
S.delegate,
GlobalMaterialLocalizations.delegate,
GlobalWidgetsLocalizations.delegate,
GlobalCupertinoLocalizations.delegate,
],
supportedLocales: const [Locale('ru'), Locale('en')],
locale: const Locale('ru'),
home: Scaffold(body: child),
);
}
testWidgets('displays sorting dropdown for DietsCubit', (tester) async {
when(mockDietsCubit.state).thenReturn(DietsState.initial());
when(mockDietsCubit.stream).thenAnswer((_) => Stream.value(DietsState.initial()));
await tester.pumpWidget(
createTestWidget(
BlocProvider<DietsCubit>.value(
value: mockDietsCubit,
child: SortingDropDownFilter(dietsCubit: mockDietsCubit),
),
),
);
await tester.pumpAndSettle();
expect(find.byType(SortingDropDownFilter), findsOneWidget);
});
testWidgets('displays sorting dropdown for MyDietsCubit', (tester) async {
when(mockMyDietsCubit.state).thenReturn(MyDietsState());
when(mockMyDietsCubit.stream).thenAnswer((_) => Stream.value(MyDietsState()));
await tester.pumpWidget(
createTestWidget(
BlocProvider<MyDietsCubit>.value(
value: mockMyDietsCubit,
child: SortingDropDownFilter(myDietsCubit: mockMyDietsCubit),
),
),
);
await tester.pumpAndSettle();
expect(find.byType(SortingDropDownFilter), findsOneWidget);
});
});
}

View File

@@ -0,0 +1,66 @@
import 'package:eatvio_app/core/app_initializer.dart';
import 'package:eatvio_app/core/di/app_config_registration.dart';
import 'package:eatvio_app/core/locator_service.dart';
import 'package:eatvio_app/core/theme/theme_helper.dart';
import 'package:eatvio_app/features/feed/presentation/widgets/subscribe_button_widget.dart';
import 'package:eatvio_app/features/profile/presentation/bloc/sub_cubit/sub_cubit.dart';
import 'package:eatvio_app/features/profile/presentation/bloc/sub_cubit/sub_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 'subscribe_button_widget_test.mocks.dart';
@GenerateNiceMocks([MockSpec<SubCubit>()])
void main() {
TestWidgetsFlutterBinding.ensureInitialized();
group('SubscribeButtonWidget', () {
late MockSubCubit mockSubCubit;
setUp(() async {
await sl.reset();
AppConfigRegistration.registerAppConfig(buildType: AppBuildType.tests);
mockSubCubit = MockSubCubit();
sl.registerLazySingleton<SubCubit>(() => mockSubCubit);
});
Widget createTestWidget(String slug) {
return MaterialApp(
theme: ThemeHelper().themeData(),
home: BlocProvider<SubCubit>.value(
value: mockSubCubit,
child: Scaffold(
body: SubscribeButtonWidget(slug: slug),
),
),
);
}
testWidgets('displays subscribe button', (tester) async {
when(mockSubCubit.stream).thenAnswer((_) => Stream.value(const SubState()));
await tester.pumpWidget(createTestWidget('test-slug'));
await tester.pumpAndSettle();
expect(find.byType(SubscribeButtonWidget), findsOneWidget);
expect(find.text('Подписаться'), findsOneWidget);
});
testWidgets('calls subscribe when tapped', (tester) async {
when(mockSubCubit.stream).thenAnswer((_) => Stream.value(const SubState()));
when(mockSubCubit.subscribe(any)).thenAnswer((_) async {});
await tester.pumpWidget(createTestWidget('test-slug'));
await tester.pumpAndSettle();
await tester.tap(find.text('Подписаться'));
await tester.pump();
verify(mockSubCubit.subscribe('test-slug')).called(1);
});
});
}

View File

@@ -0,0 +1,119 @@
import 'package:eatvio_app/core/theme/theme_helper.dart';
import 'package:eatvio_app/features/create_recipe/models/recipe_tag_model.dart';
import 'package:eatvio_app/features/feed/presentation/bloc/recipes_cubit/recipes_cubit.dart';
import 'package:eatvio_app/features/feed/presentation/bloc/recipes_cubit/recipes_state.dart';
import 'package:eatvio_app/features/feed/presentation/widgets/tags_modal_widget.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 'tags_modal_widget_test.mocks.dart';
@GenerateNiceMocks([MockSpec<RecipesCubit>()])
void main() {
group('TagsModalWidget', () {
late MockRecipesCubit mockRecipesCubit;
setUp(() {
mockRecipesCubit = MockRecipesCubit();
});
Widget createTestWidget() {
return MaterialApp(
theme: ThemeHelper().themeData(),
home: BlocProvider<RecipesCubit>.value(
value: mockRecipesCubit,
child: Scaffold(
body: TagsModalWidget(recipesCubit: mockRecipesCubit),
),
),
);
}
testWidgets('displays tags modal widget', (tester) async {
final state = RecipesState(
recipesList: [],
recipesTags: [
RecipeTag(
id: 1,
name: 'Tag 1',
recipesCount: 5,
createdAt: DateTime.now(),
updatedAt: DateTime.now(),
),
RecipeTag(
id: 2,
name: 'Tag 2',
recipesCount: 10,
createdAt: DateTime.now(),
updatedAt: DateTime.now(),
),
],
selectedTags: {},
selectedTagsCounter: 0,
isLoading: false,
);
when(mockRecipesCubit.state).thenReturn(state);
when(mockRecipesCubit.stream).thenAnswer((_) => Stream.value(state));
await tester.pumpWidget(createTestWidget());
await tester.pumpAndSettle();
expect(find.byType(TagsModalWidget), findsOneWidget);
});
testWidgets('displays tags with counts', (tester) async {
final state = RecipesState(
recipesList: [],
recipesTags: [
RecipeTag(
id: 1,
name: 'Tag 1',
recipesCount: 5,
createdAt: DateTime.now(),
updatedAt: DateTime.now(),
),
],
selectedTags: {},
selectedTagsCounter: 0,
isLoading: false,
);
when(mockRecipesCubit.state).thenReturn(state);
when(mockRecipesCubit.stream).thenAnswer((_) => Stream.value(state));
await tester.pumpWidget(createTestWidget());
await tester.pumpAndSettle();
expect(find.textContaining('Tag 1'), findsOneWidget);
expect(find.textContaining('5'), findsOneWidget);
});
testWidgets('highlights selected tags', (tester) async {
final state = RecipesState(
recipesList: [],
recipesTags: [
RecipeTag(
id: 1,
name: 'Tag 1',
recipesCount: 5,
createdAt: DateTime.now(),
updatedAt: DateTime.now(),
),
],
selectedTags: {1},
selectedTagsCounter: 1,
isLoading: false,
);
when(mockRecipesCubit.state).thenReturn(state);
when(mockRecipesCubit.stream).thenAnswer((_) => Stream.value(state));
await tester.pumpWidget(createTestWidget());
await tester.pumpAndSettle();
expect(find.byType(TagsModalWidget), findsOneWidget);
});
});
}

View File

@@ -0,0 +1,125 @@
import 'package:eatvio_app/core/theme/theme_helper.dart';
import 'package:eatvio_app/features/feed/presentation/bloc/diets_cubit/diets_cubit.dart';
import 'package:eatvio_app/features/feed/presentation/bloc/diets_cubit/diets_state.dart';
import 'package:eatvio_app/features/feed/presentation/bloc/plans_cubit/plans_cubit.dart';
import 'package:eatvio_app/features/feed/presentation/bloc/plans_cubit/plans_state.dart';
import 'package:eatvio_app/features/feed/presentation/widgets/type_choice_filter_widget.dart';
import 'package:eatvio_app/features/my_diets/presentation/cubit/my_diets_cubit.dart';
import 'package:eatvio_app/features/my_diets/presentation/cubit/my_diets_state.dart';
import 'package:eatvio_app/features/my_plans/presentation/cubit/my_plans_cubit.dart';
import 'package:eatvio_app/features/my_plans/presentation/cubit/my_plans_state.dart';
import 'package:eatvio_app/generated/l10n.dart';
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:flutter_localizations/flutter_localizations.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:mockito/annotations.dart';
import 'package:mockito/mockito.dart';
import 'type_choice_filter_widget_test.mocks.dart';
@GenerateNiceMocks([
MockSpec<PlansCubit>(),
MockSpec<DietsCubit>(),
MockSpec<MyPlansCubit>(),
MockSpec<MyDietsCubit>(),
])
void main() {
group('TypeChoiceFilterWidget', () {
late MockPlansCubit mockPlansCubit;
late MockDietsCubit mockDietsCubit;
late MockMyPlansCubit mockMyPlansCubit;
late MockMyDietsCubit mockMyDietsCubit;
setUp(() {
mockPlansCubit = MockPlansCubit();
mockDietsCubit = MockDietsCubit();
mockMyPlansCubit = MockMyPlansCubit();
mockMyDietsCubit = MockMyDietsCubit();
});
Widget createTestWidget(Widget child) {
return MaterialApp(
theme: ThemeHelper().themeData(),
localizationsDelegates: [
S.delegate,
GlobalMaterialLocalizations.delegate,
GlobalWidgetsLocalizations.delegate,
GlobalCupertinoLocalizations.delegate,
],
supportedLocales: const [Locale('ru'), Locale('en')],
locale: const Locale('ru'),
home: Scaffold(body: child),
);
}
testWidgets('displays type filter for PlansCubit', (tester) async {
when(mockPlansCubit.state).thenReturn(PlansState.initial());
when(mockPlansCubit.stream).thenAnswer((_) => Stream.value(PlansState.initial()));
await tester.pumpWidget(
createTestWidget(
BlocProvider<PlansCubit>.value(
value: mockPlansCubit,
child: TypeChoiceFilterWidget(plansCubit: mockPlansCubit),
),
),
);
await tester.pumpAndSettle();
expect(find.byType(TypeChoiceFilterWidget), findsOneWidget);
});
testWidgets('displays type filter for DietsCubit', (tester) async {
when(mockDietsCubit.state).thenReturn(DietsState.initial());
when(mockDietsCubit.stream).thenAnswer((_) => Stream.value(DietsState.initial()));
await tester.pumpWidget(
createTestWidget(
BlocProvider<DietsCubit>.value(
value: mockDietsCubit,
child: TypeChoiceFilterWidget(dietsCubit: mockDietsCubit),
),
),
);
await tester.pumpAndSettle();
expect(find.byType(TypeChoiceFilterWidget), findsOneWidget);
});
testWidgets('displays type filter for MyPlansCubit', (tester) async {
when(mockMyPlansCubit.state).thenReturn(MyPlansState());
when(mockMyPlansCubit.stream).thenAnswer((_) => Stream.value(MyPlansState()));
await tester.pumpWidget(
createTestWidget(
BlocProvider<MyPlansCubit>.value(
value: mockMyPlansCubit,
child: TypeChoiceFilterWidget(myPlansCubit: mockMyPlansCubit),
),
),
);
await tester.pumpAndSettle();
expect(find.byType(TypeChoiceFilterWidget), findsOneWidget);
});
testWidgets('displays type filter for MyDietsCubit', (tester) async {
when(mockMyDietsCubit.state).thenReturn(MyDietsState());
when(mockMyDietsCubit.stream).thenAnswer((_) => Stream.value(MyDietsState()));
await tester.pumpWidget(
createTestWidget(
BlocProvider<MyDietsCubit>.value(
value: mockMyDietsCubit,
child: TypeChoiceFilterWidget(myDietsCubit: mockMyDietsCubit),
),
),
);
await tester.pumpAndSettle();
expect(find.byType(TypeChoiceFilterWidget), findsOneWidget);
});
});
}

View File

@@ -0,0 +1,51 @@
import 'package:eatvio_app/core/theme/theme_helper.dart';
import 'package:eatvio_app/features/finances_and_consultations_archive/presentation/widgets/status_widget.dart';
import 'package:flutter/material.dart';
import 'package:flutter_test/flutter_test.dart';
void main() {
group('StatusWidget', () {
testWidgets('displays canceled status', (tester) async {
await tester.pumpWidget(
MaterialApp(
theme: ThemeHelper().themeData(),
home: Scaffold(
body: StatusWidget(status: Status.canceled),
),
),
);
expect(find.byType(StatusWidget), findsOneWidget);
expect(find.text('Отменён'), findsOneWidget);
});
testWidgets('displays success status', (tester) async {
await tester.pumpWidget(
MaterialApp(
theme: ThemeHelper().themeData(),
home: Scaffold(
body: StatusWidget(status: Status.success),
),
),
);
expect(find.byType(StatusWidget), findsOneWidget);
expect(find.text('Успешно'), findsOneWidget);
});
testWidgets('displays inProgress status', (tester) async {
await tester.pumpWidget(
MaterialApp(
theme: ThemeHelper().themeData(),
home: Scaffold(
body: StatusWidget(status: Status.inProgress),
),
),
);
expect(find.byType(StatusWidget), findsOneWidget);
expect(find.text('В процессе'), findsOneWidget);
});
});
}

View File

@@ -0,0 +1,44 @@
import 'package:eatvio_app/features/glossary/models/glossary_model.dart';
import 'package:eatvio_app/features/glossary/repositories/glossary_repository.dart';
import 'package:eatvio_app/features/glossary/services/glossary_service.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:mocktail/mocktail.dart';
class MockGlossaryService extends Mock implements GlossaryService {}
void main() {
group('GlossaryRepository', () {
late GlossaryRepository repository;
late MockGlossaryService mockService;
setUp(() {
mockService = MockGlossaryService();
repository = GlossaryRepository(glossaryService: mockService);
});
test('getGlossaryListByQuery - успешный запрос', () async {
const query = 'test';
final mockGlossaries = <GlossaryModel>[];
final mockPagination = GlossaryPaginationModel(glossariesList: mockGlossaries);
when(() => mockService.getGlossaryByQuery(query: query))
.thenAnswer((_) async => mockPagination);
final result = await repository.getGlossaryListByQuery(query);
expect(result, mockGlossaries);
verify(() => mockService.getGlossaryByQuery(query: query)).called(1);
});
test('getGlossaryListByQuery - пробрасывает ошибку', () async {
const query = 'test';
when(() => mockService.getGlossaryByQuery(query: query))
.thenThrow(Exception('Error'));
expect(() => repository.getGlossaryListByQuery(query), throwsException);
});
});
}

View File

@@ -0,0 +1,135 @@
import 'package:eatvio_app/core/theme/theme_helper.dart';
import 'package:eatvio_app/features/my_finance/models/payment_url_model.dart';
import 'package:eatvio_app/features/my_finance/presentation/cubit/user_cards_cubit/user_cards_cubit.dart';
import 'package:eatvio_app/features/my_finance/presentation/cubit/user_cards_cubit/user_cards_state.dart';
import 'package:eatvio_app/features/my_finance/presentation/widgets/card_widget.dart';
import 'package:eatvio_app/generated/l10n.dart';
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:flutter_localizations/flutter_localizations.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:mockito/annotations.dart';
import 'package:mockito/mockito.dart';
import 'card_widget_test.mocks.dart';
@GenerateNiceMocks([MockSpec<UserCardsCubit>()])
void main() {
group('CardWidget', () {
late MockUserCardsCubit mockCubit;
setUp(() {
mockCubit = MockUserCardsCubit();
});
Widget createTestWidget({
bool isSelected = false,
bool enabledAddCard = false,
int cardId = 0,
String cardNumber = '',
}) {
return MaterialApp(
theme: ThemeHelper().themeData(),
localizationsDelegates: const [
S.delegate,
GlobalMaterialLocalizations.delegate,
GlobalWidgetsLocalizations.delegate,
GlobalCupertinoLocalizations.delegate,
],
supportedLocales: const [Locale('ru'), Locale('en')],
locale: const Locale('ru'),
home: BlocProvider<UserCardsCubit>.value(
value: mockCubit,
child: Scaffold(
body: CardWidget(
isSelected: isSelected,
enabledAddCard: enabledAddCard,
cardId: cardId,
cardNumber: cardNumber,
),
),
),
);
}
testWidgets('displays card widget with card number', (tester) async {
when(mockCubit.stream).thenAnswer((_) => Stream.value(UserCardsState()));
await tester.pumpWidget(createTestWidget(
cardId: 1,
cardNumber: '1234 5678 9012 3456',
));
await tester.pumpAndSettle();
expect(find.byType(CardWidget), findsOneWidget);
expect(find.text('1234 5678 9012 3456'), findsOneWidget);
});
testWidgets('displays add card widget when enabledAddCard is true', (tester) async {
when(mockCubit.stream).thenAnswer((_) => Stream.value(UserCardsState()));
await tester.pumpWidget(createTestWidget(
enabledAddCard: true,
));
await tester.pumpAndSettle();
expect(find.byType(CardWidget), findsOneWidget);
});
testWidgets('calls markAsDefault when card is tapped', (tester) async {
when(mockCubit.state).thenReturn(UserCardsState());
when(mockCubit.stream).thenAnswer((_) => Stream.value(UserCardsState()));
when(mockCubit.markAsDefault(id: anyNamed('id'))).thenAnswer((_) async {});
await tester.pumpWidget(createTestWidget(
cardId: 1,
cardNumber: '1234 5678 9012 3456',
));
await tester.pumpAndSettle();
await tester.tap(find.byType(CardWidget));
await tester.pump();
verify(mockCubit.markAsDefault(id: 1)).called(1);
});
testWidgets('calls addCard when add card widget is tapped', (tester) async {
when(mockCubit.state).thenReturn(UserCardsState());
when(mockCubit.stream).thenAnswer((_) => Stream.value(UserCardsState()));
when(mockCubit.addCard()).thenAnswer((_) async => PaymentUrlModel(paymentUrl: 'https://example.com'));
await tester.pumpWidget(createTestWidget(
enabledAddCard: true,
));
await tester.pumpAndSettle();
await tester.tap(find.byType(CardWidget));
await tester.pump();
verify(mockCubit.addCard()).called(1);
});
testWidgets('calls deleteCard when delete button is tapped', (tester) async {
when(mockCubit.state).thenReturn(UserCardsState());
when(mockCubit.stream).thenAnswer((_) => Stream.value(UserCardsState()));
when(mockCubit.markAsDefault(id: anyNamed('id'))).thenAnswer((_) async {});
when(mockCubit.deleteCard(id: anyNamed('id'))).thenAnswer((_) async {});
await tester.pumpWidget(createTestWidget(
cardId: 1,
cardNumber: '1234 5678 9012 3456',
));
await tester.pumpAndSettle();
// Find delete button (close icon)
final deleteButton = find.byType(GestureDetector);
if (deleteButton.evaluate().length > 1) {
await tester.tap(deleteButton.last);
await tester.pump();
verify(mockCubit.deleteCard(id: 1)).called(1);
}
});
});
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,169 @@
import 'package:dio/dio.dart';
import 'package:eatvio_app/features/notifications/models/notification_model.dart';
import 'package:eatvio_app/features/notifications/service/notifications_service.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:mockito/annotations.dart';
import 'package:mockito/mockito.dart';
import 'notifications_service_test.mocks.dart';
@GenerateMocks([Dio])
void main() {
group('NotificationsService', () {
late NotificationsService service;
late MockDio mockDio;
setUp(() {
mockDio = MockDio();
service = NotificationsService(mockDio);
});
test('getNotifications returns null when status code is not 200', () async {
when(mockDio.get(any)).thenAnswer((_) async => Response(
requestOptions: RequestOptions(path: '/api/profile/notifications'),
statusCode: 404,
data: {},
));
final result = await service.getNotifications(page: 1);
expect(result, isNull);
});
test('getNotifications parses notifications correctly', () async {
when(mockDio.get(any)).thenAnswer((_) async => Response(
requestOptions: RequestOptions(path: '/api/profile/notifications'),
statusCode: 200,
data: {
'data': [
{
'type': 'diary-reminder-notification',
'read_at': null,
'created_at': '2024-01-15T10:30:00Z',
'data': {},
}
],
'current_page': 1,
'last_page': 1,
'per_page': 10,
'total': 1,
},
));
final result = await service.getNotifications(page: 1);
expect(result, isNotNull);
expect(result!['notifications'], isA<List>());
expect(result['pagination'], isA<Map>());
expect(result['pagination']['current_page'], 1);
});
test('getNotifications handles unknown notification type', () async {
when(mockDio.get(any)).thenAnswer((_) async => Response(
requestOptions: RequestOptions(path: '/api/profile/notifications'),
statusCode: 200,
data: {
'data': [
{
'type': 'unknown-type',
'read_at': null,
'created_at': '2024-01-15T10:30:00Z',
'data': {},
}
],
'current_page': 1,
'last_page': 1,
'per_page': 10,
'total': 1,
},
));
final result = await service.getNotifications(page: 1);
expect(result, isNotNull);
expect(result!['notifications'], isA<List>());
final notifications = result['notifications'] as List;
expect(notifications.first, isA<UnknownNotification>());
});
test('getNotifications handles notification_class_name field', () async {
when(mockDio.get(any)).thenAnswer((_) async => Response(
requestOptions: RequestOptions(path: '/api/profile/notifications'),
statusCode: 200,
data: {
'data': [
{
'notification_class_name': 'App\\Notifications\\DiaryReminderNotification',
'read_at': null,
'created_at': '2024-01-15T10:30:00Z',
'data': {},
}
],
'current_page': 1,
'last_page': 1,
'per_page': 10,
'total': 1,
},
));
final result = await service.getNotifications(page: 1);
expect(result, isNotNull);
expect(result!['notifications'], isA<List>());
});
test('getNotifications handles read_at correctly', () async {
when(mockDio.get(any)).thenAnswer((_) async => Response(
requestOptions: RequestOptions(path: '/api/profile/notifications'),
statusCode: 200,
data: {
'data': [
{
'type': 'diary-reminder-notification',
'read_at': '2024-01-15T10:30:00Z',
'created_at': '2024-01-15T10:30:00Z',
'data': {},
}
],
'current_page': 1,
'last_page': 1,
'per_page': 10,
'total': 1,
},
));
final result = await service.getNotifications(page: 1);
expect(result, isNotNull);
expect(result!['notifications'], isA<List>());
});
test('getNotifications rethrows exceptions', () async {
when(mockDio.get(any)).thenThrow(DioException(
requestOptions: RequestOptions(path: '/api/profile/notifications'),
));
expect(() => service.getNotifications(page: 1), throwsA(isA<DioException>()));
});
test('markNotificationsAsRead calls correct endpoint', () async {
when(mockDio.post(any)).thenAnswer((_) async => Response(
requestOptions: RequestOptions(path: '/api/profile/mark_notifications_as_readed'),
statusCode: 200,
));
await service.markNotificationsAsRead();
verify(mockDio.post('/api/profile/mark_notifications_as_readed')).called(1);
});
test('markNotificationsAsRead rethrows exceptions', () async {
when(mockDio.post(any)).thenThrow(DioException(
requestOptions: RequestOptions(path: '/api/profile/mark_notifications_as_readed'),
));
expect(() => service.markNotificationsAsRead(), throwsA(isA<DioException>()));
});
});
}

View File

@@ -0,0 +1,90 @@
import 'package:eatvio_app/core/app_initializer.dart';
import 'package:eatvio_app/core/di/app_config_registration.dart';
import 'package:eatvio_app/core/locator_service.dart';
import 'package:eatvio_app/core/theme/theme_helper.dart';
import 'package:eatvio_app/features/profile/presentation/bloc/profile_cubit/profile_cubit.dart';
import 'package:eatvio_app/features/profile/presentation/bloc/sub_cubit/sub_cubit.dart';
import 'package:eatvio_app/features/profile/presentation/bloc/sub_cubit/sub_state.dart';
import 'package:eatvio_app/features/profile/presentation/widgets/sub_action_button_widget.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 'sub_action_button_widget_test.mocks.dart';
@GenerateNiceMocks([
MockSpec<SubCubit>(),
MockSpec<ProfileCubit>(),
])
void main() {
TestWidgetsFlutterBinding.ensureInitialized();
group('SubActionButtonWidget', () {
late MockSubCubit mockSubCubit;
late MockProfileCubit mockProfileCubit;
setUp(() async {
await sl.reset();
AppConfigRegistration.registerAppConfig(buildType: AppBuildType.tests);
mockSubCubit = MockSubCubit();
mockProfileCubit = MockProfileCubit();
sl.registerLazySingleton<SubCubit>(() => mockSubCubit);
sl.registerLazySingleton<ProfileCubit>(() => mockProfileCubit);
});
Widget createTestWidget(SubActionType actionType) {
return MaterialApp(
theme: ThemeHelper().themeData(),
home: MultiBlocProvider(
providers: [
BlocProvider<SubCubit>.value(value: mockSubCubit),
BlocProvider<ProfileCubit>.value(value: mockProfileCubit),
],
child: Scaffold(
body: SubActionButtonWidget(
subActionType: actionType,
slug: 'test-slug',
name: 'Test User',
porfileIcon: 'https://example.com/icon.jpg',
),
),
),
);
}
testWidgets('displays subscribe button when action type is subscribe', (tester) async {
await tester.pumpWidget(createTestWidget(SubActionType.subscribe));
await tester.pumpAndSettle();
expect(find.byType(SubActionButtonWidget), findsOneWidget);
expect(find.text('Подписаться'), findsOneWidget);
});
testWidgets('displays message button when action type is sendMessage', (tester) async {
await tester.pumpWidget(createTestWidget(SubActionType.sendMessage));
await tester.pumpAndSettle();
expect(find.byType(SubActionButtonWidget), findsOneWidget);
expect(find.text('Сообщение'), findsOneWidget);
});
testWidgets('calls subscribe when subscribe button is tapped', (tester) async {
when(mockSubCubit.state).thenReturn(const SubState());
when(mockSubCubit.stream).thenAnswer((_) => Stream.value(const SubState()));
when(mockSubCubit.subscribe(any)).thenAnswer((_) async {});
await tester.pumpWidget(createTestWidget(SubActionType.subscribe));
await tester.pumpAndSettle();
await tester.tap(find.text('Подписаться'));
await tester.pump();
verify(mockSubCubit.subscribe('test-slug')).called(1);
});
});
}

View File

@@ -0,0 +1,165 @@
import 'package:eatvio_app/core/prefs.dart';
import 'package:eatvio_app/core/theme/theme_helper.dart';
import 'package:eatvio_app/features/registration/models/user_registration_model.dart';
import 'package:eatvio_app/features/registration/presentation/bloc/registration_cubit/registration_cubit.dart';
import 'package:eatvio_app/features/registration/presentation/pages/blood_pressure_screen.dart';
import 'package:eatvio_app/features/registration/repositories/registration_repository.dart';
import 'package:eatvio_app/generated/l10n.dart';
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:flutter_localizations/flutter_localizations.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:mockito/annotations.dart';
import 'blood_pressure_screen_test.mocks.dart';
@GenerateMocks([RegistrationRepository, Prefs])
void main() {
group('BloodPressureScreen', () {
late MockRegistrationRepository mockRegistrationRepository;
late MockPrefs mockPrefs;
late RegistrationCubit registrationCubit;
setUp(() {
mockRegistrationRepository = MockRegistrationRepository();
mockPrefs = MockPrefs();
registrationCubit = RegistrationCubit(
registrationRepository: mockRegistrationRepository,
prefs: mockPrefs,
);
});
tearDown(() {
registrationCubit.close();
});
Widget createTestWidget(UserRegistrationSheet sheet) {
return MaterialApp(
theme: ThemeHelper().themeData(),
localizationsDelegates: [
S.delegate,
GlobalMaterialLocalizations.delegate,
GlobalWidgetsLocalizations.delegate,
GlobalCupertinoLocalizations.delegate,
],
supportedLocales: const [Locale('ru'), Locale('en')],
locale: const Locale('ru'),
home: BlocProvider<RegistrationCubit>.value(
value: registrationCubit,
child: Scaffold(
body: BloodPressureScreen(sheet: sheet),
),
),
);
}
testWidgets('displays blood pressure screen', (tester) async {
final sheet = UserRegistrationSheet(
firstName: 'Test',
lastName: 'User',
email: 'test@example.com',
password: 'password',
passwordConfirmation: 'password',
birthdate: '',
gender: 'male',
location: '',
responseBody: '{}',
lang: 'ru',
weight: 70,
height: 175,
phone: '',
chestCircumference: 90,
waistCircumference: 80,
hipCircumference: 95,
bloodPressureUp: 0,
bloodPressureDown: 0,
hasHypo: 0,
hasHyper: 0,
aboutAllergy: '',
aboutTakingPils: '',
aboutSportHobbies: '',
favoriteFoods: '',
unfavoriteFoods: '',
additionalInfo: '',
);
await tester.pumpWidget(createTestWidget(sheet));
await tester.pumpAndSettle();
expect(find.byType(BloodPressureScreen), findsOneWidget);
});
testWidgets('displays existing blood pressure values', (tester) async {
final sheet = UserRegistrationSheet(
firstName: 'Test',
lastName: 'User',
email: 'test@example.com',
password: 'password',
passwordConfirmation: 'password',
birthdate: '',
gender: 'male',
location: '',
responseBody: '{}',
lang: 'ru',
weight: 70,
height: 175,
phone: '',
chestCircumference: 90,
waistCircumference: 80,
hipCircumference: 95,
bloodPressureUp: 120,
bloodPressureDown: 80,
hasHypo: 0,
hasHyper: 0,
aboutAllergy: '',
aboutTakingPils: '',
aboutSportHobbies: '',
favoriteFoods: '',
unfavoriteFoods: '',
additionalInfo: '',
);
await tester.pumpWidget(createTestWidget(sheet));
await tester.pumpAndSettle();
expect(find.byType(BloodPressureScreen), findsOneWidget);
});
testWidgets('displays dropdowns for hypo and hyper', (tester) async {
final sheet = UserRegistrationSheet(
firstName: 'Test',
lastName: 'User',
email: 'test@example.com',
password: 'password',
passwordConfirmation: 'password',
birthdate: '',
gender: 'male',
location: '',
responseBody: '{}',
lang: 'ru',
weight: 70,
height: 175,
phone: '',
chestCircumference: 90,
waistCircumference: 80,
hipCircumference: 95,
bloodPressureUp: 0,
bloodPressureDown: 0,
hasHypo: 1,
hasHyper: 1,
aboutAllergy: '',
aboutTakingPils: '',
aboutSportHobbies: '',
favoriteFoods: '',
unfavoriteFoods: '',
additionalInfo: '',
);
await tester.pumpWidget(createTestWidget(sheet));
await tester.pumpAndSettle();
expect(find.byType(BloodPressureScreen), findsOneWidget);
});
});
}

View File

@@ -0,0 +1,165 @@
import 'package:eatvio_app/core/prefs.dart';
import 'package:eatvio_app/core/theme/theme_helper.dart';
import 'package:eatvio_app/features/registration/models/user_registration_model.dart';
import 'package:eatvio_app/features/registration/presentation/bloc/registration_cubit/registration_cubit.dart';
import 'package:eatvio_app/features/registration/presentation/pages/credentials_screen.dart';
import 'package:eatvio_app/features/registration/repositories/registration_repository.dart';
import 'package:eatvio_app/generated/l10n.dart';
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:flutter_localizations/flutter_localizations.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:mockito/annotations.dart';
import 'credentials_screen_test.mocks.dart';
@GenerateMocks([RegistrationRepository, Prefs])
void main() {
group('CredentialsScreen', () {
late MockRegistrationRepository mockRegistrationRepository;
late MockPrefs mockPrefs;
late RegistrationCubit registrationCubit;
setUp(() {
mockRegistrationRepository = MockRegistrationRepository();
mockPrefs = MockPrefs();
registrationCubit = RegistrationCubit(
registrationRepository: mockRegistrationRepository,
prefs: mockPrefs,
);
});
tearDown(() {
registrationCubit.close();
});
Widget createTestWidget(UserRegistrationSheet sheet) {
return MaterialApp(
theme: ThemeHelper().themeData(),
localizationsDelegates: [
S.delegate,
GlobalMaterialLocalizations.delegate,
GlobalWidgetsLocalizations.delegate,
GlobalCupertinoLocalizations.delegate,
],
supportedLocales: const [Locale('ru'), Locale('en')],
locale: const Locale('ru'),
home: BlocProvider<RegistrationCubit>.value(
value: registrationCubit,
child: Scaffold(
body: CredentialsScreen(sheet: sheet),
),
),
);
}
testWidgets('displays email field', (tester) async {
final sheet = UserRegistrationSheet(
firstName: 'Test',
lastName: 'User',
email: '',
password: '',
passwordConfirmation: '',
birthdate: '',
gender: 'male',
location: '',
responseBody: '{}',
lang: 'ru',
weight: 70,
height: 175,
phone: '',
chestCircumference: 90,
waistCircumference: 80,
hipCircumference: 95,
bloodPressureUp: 0,
bloodPressureDown: 0,
hasHypo: 0,
hasHyper: 0,
aboutAllergy: '',
aboutTakingPils: '',
aboutSportHobbies: '',
favoriteFoods: '',
unfavoriteFoods: '',
additionalInfo: '',
);
await tester.pumpWidget(createTestWidget(sheet));
await tester.pumpAndSettle();
expect(find.byType(CredentialsScreen), findsOneWidget);
});
testWidgets('displays password fields', (tester) async {
final sheet = UserRegistrationSheet(
firstName: 'Test',
lastName: 'User',
email: 'test@example.com',
password: '',
passwordConfirmation: '',
birthdate: '',
gender: 'male',
location: '',
responseBody: '{}',
lang: 'ru',
weight: 70,
height: 175,
phone: '',
chestCircumference: 90,
waistCircumference: 80,
hipCircumference: 95,
bloodPressureUp: 0,
bloodPressureDown: 0,
hasHypo: 0,
hasHyper: 0,
aboutAllergy: '',
aboutTakingPils: '',
aboutSportHobbies: '',
favoriteFoods: '',
unfavoriteFoods: '',
additionalInfo: '',
);
await tester.pumpWidget(createTestWidget(sheet));
await tester.pumpAndSettle();
expect(find.byType(CredentialsScreen), findsOneWidget);
});
testWidgets('initializes with existing values', (tester) async {
final sheet = UserRegistrationSheet(
firstName: 'Test',
lastName: 'User',
email: 'test@example.com',
password: 'password123',
passwordConfirmation: 'password123',
birthdate: '',
gender: 'male',
location: '',
responseBody: '{}',
lang: 'ru',
weight: 70,
height: 175,
phone: '',
chestCircumference: 90,
waistCircumference: 80,
hipCircumference: 95,
bloodPressureUp: 0,
bloodPressureDown: 0,
hasHypo: 0,
hasHyper: 0,
aboutAllergy: '',
aboutTakingPils: '',
aboutSportHobbies: '',
favoriteFoods: '',
unfavoriteFoods: '',
additionalInfo: '',
);
await tester.pumpWidget(createTestWidget(sheet));
await tester.pumpAndSettle();
expect(find.byType(CredentialsScreen), findsOneWidget);
});
});
}

View File

@@ -0,0 +1,165 @@
import 'package:eatvio_app/core/prefs.dart';
import 'package:eatvio_app/core/theme/theme_helper.dart';
import 'package:eatvio_app/features/registration/models/user_registration_model.dart';
import 'package:eatvio_app/features/registration/presentation/bloc/registration_cubit/registration_cubit.dart';
import 'package:eatvio_app/features/registration/presentation/pages/date_of_birth_screen.dart';
import 'package:eatvio_app/features/registration/repositories/registration_repository.dart';
import 'package:eatvio_app/generated/l10n.dart';
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:flutter_localizations/flutter_localizations.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:mockito/annotations.dart';
import 'date_of_birth_screen_test.mocks.dart';
@GenerateMocks([RegistrationRepository, Prefs])
void main() {
group('DateOfBirthScreen', () {
late MockRegistrationRepository mockRegistrationRepository;
late MockPrefs mockPrefs;
late RegistrationCubit registrationCubit;
setUp(() {
mockRegistrationRepository = MockRegistrationRepository();
mockPrefs = MockPrefs();
registrationCubit = RegistrationCubit(
registrationRepository: mockRegistrationRepository,
prefs: mockPrefs,
);
});
tearDown(() {
registrationCubit.close();
});
Widget createTestWidget(UserRegistrationSheet sheet) {
return MaterialApp(
theme: ThemeHelper().themeData(),
localizationsDelegates: [
S.delegate,
GlobalMaterialLocalizations.delegate,
GlobalWidgetsLocalizations.delegate,
GlobalCupertinoLocalizations.delegate,
],
supportedLocales: const [Locale('ru'), Locale('en')],
locale: const Locale('ru'),
home: BlocProvider<RegistrationCubit>.value(
value: registrationCubit,
child: Scaffold(
body: DateOfBirthScreen(sheet: sheet),
),
),
);
}
testWidgets('displays date of birth screen', (tester) async {
final sheet = UserRegistrationSheet(
firstName: 'Test',
lastName: 'User',
email: 'test@example.com',
password: 'password',
passwordConfirmation: 'password',
birthdate: '',
gender: 'male',
location: '',
responseBody: '{}',
lang: 'ru',
weight: 70,
height: 175,
phone: '',
chestCircumference: 90,
waistCircumference: 80,
hipCircumference: 95,
bloodPressureUp: 0,
bloodPressureDown: 0,
hasHypo: 0,
hasHyper: 0,
aboutAllergy: '',
aboutTakingPils: '',
aboutSportHobbies: '',
favoriteFoods: '',
unfavoriteFoods: '',
additionalInfo: '',
);
await tester.pumpWidget(createTestWidget(sheet));
await tester.pumpAndSettle();
expect(find.byType(DateOfBirthScreen), findsOneWidget);
});
testWidgets('displays existing birthdate when provided', (tester) async {
final sheet = UserRegistrationSheet(
firstName: 'Test',
lastName: 'User',
email: 'test@example.com',
password: 'password',
passwordConfirmation: 'password',
birthdate: '2000-01-01',
gender: 'male',
location: '',
responseBody: '{}',
lang: 'ru',
weight: 70,
height: 175,
phone: '',
chestCircumference: 90,
waistCircumference: 80,
hipCircumference: 95,
bloodPressureUp: 0,
bloodPressureDown: 0,
hasHypo: 0,
hasHyper: 0,
aboutAllergy: '',
aboutTakingPils: '',
aboutSportHobbies: '',
favoriteFoods: '',
unfavoriteFoods: '',
additionalInfo: '',
);
await tester.pumpWidget(createTestWidget(sheet));
await tester.pumpAndSettle();
expect(find.byType(DateOfBirthScreen), findsOneWidget);
});
testWidgets('displays calendar icon', (tester) async {
final sheet = UserRegistrationSheet(
firstName: 'Test',
lastName: 'User',
email: 'test@example.com',
password: 'password',
passwordConfirmation: 'password',
birthdate: '',
gender: 'male',
location: '',
responseBody: '{}',
lang: 'ru',
weight: 70,
height: 175,
phone: '',
chestCircumference: 90,
waistCircumference: 80,
hipCircumference: 95,
bloodPressureUp: 0,
bloodPressureDown: 0,
hasHypo: 0,
hasHyper: 0,
aboutAllergy: '',
aboutTakingPils: '',
aboutSportHobbies: '',
favoriteFoods: '',
unfavoriteFoods: '',
additionalInfo: '',
);
await tester.pumpWidget(createTestWidget(sheet));
await tester.pumpAndSettle();
expect(find.byIcon(Icons.calendar_month_rounded), findsOneWidget);
});
});
}

View File

@@ -0,0 +1,118 @@
import 'package:eatvio_app/core/theme/theme_helper.dart';
import 'package:eatvio_app/features/registration_v2/cubit/registration_cubit.dart';
import 'package:eatvio_app/features/registration_v2/cubit/registration_state.dart';
import 'package:eatvio_app/features/registration_v2/presentation/widgets/sex_widget.dart';
import 'package:eatvio_app/generated/l10n.dart';
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:flutter_localizations/flutter_localizations.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:mockito/annotations.dart';
import 'package:mockito/mockito.dart';
import 'sex_widget_test.mocks.dart';
@GenerateNiceMocks([MockSpec<RegistrationCubitV2>()])
void main() {
group('SexWidget', () {
late MockRegistrationCubitV2 mockCubit;
setUp(() {
mockCubit = MockRegistrationCubitV2();
});
Widget createTestWidget() {
return MaterialApp(
theme: ThemeHelper().themeData(),
localizationsDelegates: const [
S.delegate,
GlobalMaterialLocalizations.delegate,
GlobalWidgetsLocalizations.delegate,
GlobalCupertinoLocalizations.delegate,
],
supportedLocales: const [Locale('ru')],
home: BlocProvider<RegistrationCubitV2>.value(
value: mockCubit,
child: Scaffold(
body: SexWidget(),
),
),
);
}
testWidgets('displays sex widget', (tester) async {
when(mockCubit.state).thenReturn(
RegistrationStateV2(
step: 0,
gender: '',
isGenderValid: true,
),
);
when(mockCubit.stream).thenAnswer((_) => Stream.value(
RegistrationStateV2(
step: 0,
gender: '',
isGenderValid: true,
),
));
await tester.pumpWidget(createTestWidget());
await tester.pumpAndSettle();
expect(find.byType(SexWidget), findsOneWidget);
});
testWidgets('calls setGender when male is tapped', (tester) async {
when(mockCubit.state).thenReturn(
RegistrationStateV2(
step: 0,
gender: '',
isGenderValid: true,
),
);
when(mockCubit.stream).thenAnswer((_) => Stream.value(
RegistrationStateV2(
step: 0,
gender: '',
isGenderValid: true,
),
));
when(mockCubit.setGender(any)).thenAnswer((_) async {});
await tester.pumpWidget(createTestWidget());
await tester.pumpAndSettle();
// Find and tap male button - try to find by InkWell or text
final inkWells = find.byType(InkWell);
if (inkWells.evaluate().isNotEmpty) {
await tester.tap(inkWells.first);
await tester.pump();
verify(mockCubit.setGender('male')).called(1);
}
});
testWidgets('shows error when gender is invalid', (tester) async {
when(mockCubit.state).thenReturn(
RegistrationStateV2(
step: 0,
gender: '',
isGenderValid: false,
),
);
when(mockCubit.stream).thenAnswer((_) => Stream.value(
RegistrationStateV2(
step: 0,
gender: '',
isGenderValid: false,
),
));
await tester.pumpWidget(createTestWidget());
await tester.pumpAndSettle();
expect(find.byType(SexWidget), findsOneWidget);
});
});
}

View File

@@ -0,0 +1,69 @@
import 'package:eatvio_app/core/theme/theme_helper.dart';
import 'package:eatvio_app/features/registration_v2/cubit/registration_cubit.dart';
import 'package:eatvio_app/features/registration_v2/cubit/registration_state.dart';
import 'package:eatvio_app/features/registration_v2/presentation/widgets/step_widget.dart';
import 'package:eatvio_app/generated/l10n.dart';
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:flutter_localizations/flutter_localizations.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:mockito/annotations.dart';
import 'package:mockito/mockito.dart';
import 'step_widget_test.mocks.dart';
@GenerateMocks([RegistrationCubitV2])
void main() {
group('StepWidget', () {
late MockRegistrationCubitV2 mockCubit;
setUp(() {
mockCubit = MockRegistrationCubitV2();
});
Widget createTestWidget() {
return MaterialApp(
theme: ThemeHelper().themeData(),
localizationsDelegates: const [
S.delegate,
GlobalMaterialLocalizations.delegate,
GlobalWidgetsLocalizations.delegate,
GlobalCupertinoLocalizations.delegate,
],
supportedLocales: const [Locale('ru'), Locale('en')],
locale: const Locale('ru'),
home: BlocProvider<RegistrationCubitV2>.value(
value: mockCubit,
child: Scaffold(
body: StepWidget(),
),
),
);
}
testWidgets('displays step widget', (tester) async {
when(mockCubit.state).thenReturn(
RegistrationStateV2(step: 0),
);
when(mockCubit.stream).thenAnswer((_) => Stream.value(RegistrationStateV2(step: 0)));
await tester.pumpWidget(createTestWidget());
await tester.pumpAndSettle();
expect(find.byType(StepWidget), findsOneWidget);
});
testWidgets('displays correct step number', (tester) async {
when(mockCubit.state).thenReturn(
RegistrationStateV2(step: 2),
);
when(mockCubit.stream).thenAnswer((_) => Stream.value(RegistrationStateV2(step: 2)));
await tester.pumpWidget(createTestWidget());
await tester.pumpAndSettle();
expect(find.byType(StepWidget), findsOneWidget);
});
});
}

View File

@@ -2,12 +2,10 @@ import 'package:eatvio_app/features/registration_v2/models/registration_model.da
import 'package:eatvio_app/features/registration_v2/repositories/registration_repository_v2.dart';
import 'package:eatvio_app/features/registration_v2/services/registration_service_v2.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:mockito/annotations.dart';
import 'package:mockito/mockito.dart';
import 'package:mocktail/mocktail.dart';
import 'registration_repository_v2_test.mocks.dart';
class MockRegistrationServiceV2 extends Mock implements RegistrationServiceV2 {}
@GenerateMocks([RegistrationServiceV2])
void main() {
group('RegistrationRepositoryV2', () {
late RegistrationRepositoryV2 repository;
@@ -18,10 +16,11 @@ void main() {
repository = RegistrationRepositoryV2(registrationService: mockService);
});
test('register - успешная регистрация', () async {
test('register - успешная регистрация возвращает токен', () async {
const token = 'test-token-123';
final registrationModel = RegistrationModel(
firstName: 'John',
lastName: 'Doe',
firstName: 'Test',
lastName: 'User',
email: 'test@example.com',
phone: '+1234567890',
gender: 'male',
@@ -33,20 +32,19 @@ void main() {
birthday: '1990-01-01',
);
const token = 'test-token';
when(mockService.register(model: anyNamed('model')))
when(() => mockService.register(model: any(named: 'model')))
.thenAnswer((_) async => {'token': token});
final result = await repository.register(registrationModel: registrationModel);
expect(result, equals(token));
verify(mockService.register(model: anyNamed('model'))).called(1);
expect(result, token);
verify(() => mockService.register(model: any(named: 'model'))).called(1);
});
test('register - проброс ошибки', () async {
test('register - возвращает null если токена нет', () async {
final registrationModel = RegistrationModel(
firstName: 'John',
lastName: 'Doe',
firstName: 'Test',
lastName: 'User',
email: 'test@example.com',
phone: '+1234567890',
gender: 'male',
@@ -58,16 +56,33 @@ void main() {
birthday: '1990-01-01',
);
final error = Exception('Ошибка регистрации');
when(mockService.register(model: anyNamed('model')))
.thenThrow(error);
when(() => mockService.register(model: any(named: 'model')))
.thenAnswer((_) async => {});
expect(
() => repository.register(registrationModel: registrationModel),
throwsA(error),
final result = await repository.register(registrationModel: registrationModel);
expect(result, null);
});
test('register - пробрасывает ошибку', () async {
final registrationModel = RegistrationModel(
firstName: 'Test',
lastName: 'User',
email: 'test@example.com',
phone: '+1234567890',
gender: 'male',
password: 'password123',
confirmPassword: 'password123',
weight: 70,
height: 175,
activityCoefficient: 1.5,
birthday: '1990-01-01',
);
verify(mockService.register(model: anyNamed('model'))).called(1);
when(() => mockService.register(model: any(named: 'model')))
.thenThrow(Exception('Error'));
expect(() => repository.register(registrationModel: registrationModel), throwsException);
});
});
}