forked from eatvio/eatvio_app
merge upstream
This commit is contained in:
@@ -0,0 +1,59 @@
|
||||
import 'package:eatvio_app/core/app_config.dart';
|
||||
import 'package:eatvio_app/core/app_initializer.dart';
|
||||
import 'package:eatvio_app/core/locator_service.dart';
|
||||
import 'package:eatvio_app/core/theme/theme_helper.dart';
|
||||
import 'package:eatvio_app/features/auth/presentation/bloc/auth_cubit/auth_cubit.dart';
|
||||
import 'package:eatvio_app/features/auth/presentation/bloc/auth_cubit/auth_state.dart';
|
||||
import 'package:eatvio_app/features/auth/presentation/pages/auth_screen.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:mockito/annotations.dart';
|
||||
import 'package:mockito/mockito.dart';
|
||||
|
||||
import 'auth_screen_smoke_test.mocks.dart';
|
||||
|
||||
@GenerateNiceMocks([MockSpec<AuthCubit>()])
|
||||
void main() {
|
||||
group('AuthScreen smoke', () {
|
||||
setUpAll(() async {
|
||||
dotenv.loadFromString(envString: 'DEV_BASE_URL=http://localhost\nBASE_URL=http://localhost');
|
||||
});
|
||||
|
||||
setUp(() async {
|
||||
await sl.reset();
|
||||
sl.registerLazySingleton<AppConfig>(() => AppConfig(buildType: AppBuildType.tests)..setBaseUrl('http://localhost'));
|
||||
});
|
||||
|
||||
Widget wrap(Widget child) {
|
||||
return MaterialApp(
|
||||
theme: ThemeHelper().themeData(),
|
||||
localizationsDelegates: const [
|
||||
S.delegate,
|
||||
GlobalMaterialLocalizations.delegate,
|
||||
GlobalWidgetsLocalizations.delegate,
|
||||
GlobalCupertinoLocalizations.delegate,
|
||||
],
|
||||
supportedLocales: const [Locale('ru')],
|
||||
home: child,
|
||||
);
|
||||
}
|
||||
|
||||
testWidgets('renders base auth UI', (tester) async {
|
||||
final cubit = MockAuthCubit();
|
||||
when(cubit.state).thenReturn(const AuthState());
|
||||
when(cubit.stream).thenAnswer((_) => const Stream<AuthState>.empty());
|
||||
when(cubit.close()).thenAnswer((_) async {});
|
||||
sl.registerFactory<AuthCubit>(() => cubit);
|
||||
|
||||
await tester.pumpWidget(wrap(const AuthScreen()));
|
||||
await tester.pump();
|
||||
|
||||
expect(find.byType(AuthScreen), findsOneWidget);
|
||||
// Minimal/stable assertions.
|
||||
expect(find.text(S.current.authentificate), findsOneWidget);
|
||||
});
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,65 @@
|
||||
import 'package:eatvio_app/core/app_config.dart';
|
||||
import 'package:eatvio_app/core/app_initializer.dart';
|
||||
import 'package:eatvio_app/core/locator_service.dart';
|
||||
import 'package:eatvio_app/core/theme/theme_helper.dart';
|
||||
import 'package:eatvio_app/features/auth/presentation/bloc/reset_password_cubit/reset_password_cubit.dart';
|
||||
import 'package:eatvio_app/features/auth/presentation/bloc/reset_password_cubit/reset_password_state.dart';
|
||||
import 'package:eatvio_app/features/auth/presentation/pages/forget_password_screen.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_dotenv/flutter_dotenv.dart';
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:mockito/annotations.dart';
|
||||
import 'package:mockito/mockito.dart';
|
||||
|
||||
import 'forget_password_screen_smoke_test.mocks.dart';
|
||||
|
||||
@GenerateNiceMocks([MockSpec<ResetPasswordCubit>()])
|
||||
void main() {
|
||||
group('ForgetPasswordScreen smoke', () {
|
||||
setUpAll(() async {
|
||||
dotenv.loadFromString(envString: 'DEV_BASE_URL=http://localhost\nBASE_URL=http://localhost');
|
||||
});
|
||||
|
||||
setUp(() async {
|
||||
await sl.reset();
|
||||
sl.registerLazySingleton<AppConfig>(() => AppConfig(buildType: AppBuildType.tests)..setBaseUrl('http://localhost'));
|
||||
});
|
||||
|
||||
Widget wrap(Widget child) {
|
||||
return MaterialApp(theme: ThemeHelper().themeData(), home: child);
|
||||
}
|
||||
|
||||
testWidgets('renders initial email step (isAuth=false)', (tester) async {
|
||||
final cubit = MockResetPasswordCubit();
|
||||
when(cubit.state).thenReturn(const ResetPasswordState.empty());
|
||||
when(cubit.stream).thenAnswer((_) => const Stream<ResetPasswordState>.empty());
|
||||
when(cubit.close()).thenAnswer((_) async {});
|
||||
sl.registerFactory<ResetPasswordCubit>(() => cubit);
|
||||
|
||||
await tester.pumpWidget(wrap(const ForgetPasswordScreen(isAuth: false)));
|
||||
await tester.pump();
|
||||
|
||||
expect(find.byType(ForgetPasswordScreen), findsOneWidget);
|
||||
expect(find.text('Восстановление пароля'), findsOneWidget);
|
||||
expect(find.text('E-mail'), findsOneWidget);
|
||||
expect(find.text('Сбросить пароль'), findsOneWidget);
|
||||
});
|
||||
|
||||
testWidgets('renders code + new password step', (tester) async {
|
||||
final cubit = MockResetPasswordCubit();
|
||||
when(cubit.state).thenReturn(const ResetPasswordState.codeSendSuccess());
|
||||
when(cubit.stream).thenAnswer((_) => const Stream<ResetPasswordState>.empty());
|
||||
when(cubit.close()).thenAnswer((_) async {});
|
||||
sl.registerFactory<ResetPasswordCubit>(() => cubit);
|
||||
|
||||
await tester.pumpWidget(wrap(const ForgetPasswordScreen(isAuth: true)));
|
||||
await tester.pump();
|
||||
|
||||
expect(find.text('Смена пароля'), findsOneWidget);
|
||||
expect(find.text('Код'), findsOneWidget);
|
||||
expect(find.text('Пароль'), findsOneWidget);
|
||||
expect(find.text('Подтвердите пароль'), findsOneWidget);
|
||||
expect(find.text('Сменить пароль'), findsOneWidget);
|
||||
});
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,66 @@
|
||||
import 'package:eatvio_app/core/app_config.dart';
|
||||
import 'package:eatvio_app/core/app_initializer.dart';
|
||||
import 'package:eatvio_app/core/di/cubit_registrations.dart';
|
||||
import 'package:eatvio_app/core/locator_service.dart';
|
||||
import 'package:eatvio_app/core/theme/theme_helper.dart';
|
||||
import 'package:eatvio_app/features/auth/entities/auth_result_entity.dart';
|
||||
import 'package:eatvio_app/features/auth/presentation/bloc/oauth_cubit/oauth_cubit.dart';
|
||||
import 'package:eatvio_app/features/auth/presentation/bloc/oauth_cubit/oauth_state.dart';
|
||||
import 'package:eatvio_app/features/auth/presentation/pages/register_via_social_screen.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_dotenv/flutter_dotenv.dart';
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:mockito/annotations.dart';
|
||||
import 'package:mockito/mockito.dart';
|
||||
|
||||
import 'register_via_social_screen_smoke_test.mocks.dart';
|
||||
|
||||
@GenerateNiceMocks([MockSpec<OAuthCubit>()])
|
||||
void main() {
|
||||
group('RegisterViaSocialScreen smoke', () {
|
||||
setUpAll(() async {
|
||||
dotenv.loadFromString(envString: 'DEV_BASE_URL=http://localhost\nBASE_URL=http://localhost');
|
||||
});
|
||||
|
||||
setUp(() async {
|
||||
await sl.reset();
|
||||
|
||||
await CubitRegistrations.sl.reset();
|
||||
sl.registerLazySingleton<AppConfig>(() => AppConfig(buildType: AppBuildType.tests)..setBaseUrl('http://localhost'));
|
||||
});
|
||||
|
||||
Widget wrap(Widget child) {
|
||||
return MaterialApp(theme: ThemeHelper().themeData(), home: child);
|
||||
}
|
||||
|
||||
testWidgets('renders and pre-fills fields from AuthResultEntity', (tester) async {
|
||||
final cubit = MockOAuthCubit();
|
||||
when(cubit.state).thenReturn(const OAuthState());
|
||||
when(cubit.stream).thenAnswer((_) => const Stream<OAuthState>.empty());
|
||||
when(cubit.close()).thenAnswer((_) async {});
|
||||
CubitRegistrations.sl.registerFactory<OAuthCubit>(() => cubit);
|
||||
|
||||
const authResult = AuthResultEntity(
|
||||
accessToken: 'token',
|
||||
userId: '1',
|
||||
clientId: 'client',
|
||||
refreshToken: 'refresh',
|
||||
firstName: 'Ivan',
|
||||
lastName: 'Petrov',
|
||||
email: 'ivan@example.com',
|
||||
phone: '+7 (999) 111 22 33',
|
||||
birthDate: '1990-01-01',
|
||||
gender: GenderEnum.male,
|
||||
);
|
||||
|
||||
await tester.pumpWidget(wrap(const RegisterViaSocialScreen(authResult: authResult, provider: 'vk')));
|
||||
await tester.pump();
|
||||
|
||||
expect(find.byType(RegisterViaSocialScreen), findsOneWidget);
|
||||
expect(find.text('Регистрация'), findsOneWidget);
|
||||
expect(find.text('Ivan'), findsOneWidget);
|
||||
expect(find.text('Petrov'), findsOneWidget);
|
||||
expect(find.text('ivan@example.com'), findsOneWidget);
|
||||
});
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,109 @@
|
||||
import 'package:eatvio_app/core/app_config.dart';
|
||||
import 'package:eatvio_app/core/app_initializer.dart';
|
||||
import 'package:eatvio_app/core/comments/comments_cubit/comments_cubit.dart';
|
||||
import 'package:eatvio_app/core/comments/comments_cubit/comments_state.dart';
|
||||
import 'package:eatvio_app/core/locator_service.dart';
|
||||
import 'package:eatvio_app/core/theme/theme_helper.dart';
|
||||
import 'package:eatvio_app/features/blog_post/presentation/cubit/blog_post_cubit.dart';
|
||||
import 'package:eatvio_app/features/blog_post/presentation/cubit/blog_post_state.dart';
|
||||
import 'package:eatvio_app/features/blog_post/presentation/pages/blog_post_screen.dart';
|
||||
import 'package:eatvio_app/features/feed/presentation/bloc/feed_cubit/feed_cubit.dart';
|
||||
import 'package:eatvio_app/generated/l10n.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:flutter_dotenv/flutter_dotenv.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 'blog_post_screen_smoke_test.mocks.dart';
|
||||
|
||||
@GenerateNiceMocks([MockSpec<BlogPostCubit>(), MockSpec<FeedCubit>(), MockSpec<CommentsCubit>()])
|
||||
void main() {
|
||||
group('BlogPostScreen smoke', () {
|
||||
setUpAll(() async {
|
||||
dotenv.loadFromString(envString: 'DEV_BASE_URL=http://localhost\nBASE_URL=http://localhost');
|
||||
});
|
||||
|
||||
setUp(() {
|
||||
FlutterError.onError = (FlutterErrorDetails details) {
|
||||
if (details.exception is FlutterError && details.exception.toString().contains('overflowed')) {
|
||||
return;
|
||||
}
|
||||
FlutterError.presentError(details);
|
||||
};
|
||||
});
|
||||
|
||||
setUp(() async {
|
||||
await sl.reset();
|
||||
sl.registerLazySingleton<AppConfig>(() => AppConfig(buildType: AppBuildType.tests)..setBaseUrl('http://localhost'));
|
||||
});
|
||||
|
||||
void registerMocks(BlogPostCubit blogPostCubit, CommentsCubit commentsCubit) {
|
||||
sl.registerLazySingleton<BlogPostCubit>(() => blogPostCubit);
|
||||
sl.registerLazySingleton<CommentsCubit>(() => commentsCubit);
|
||||
}
|
||||
|
||||
Widget wrap(BlogPostCubit blogPostCubit, FeedCubit feedCubit, CommentsCubit commentsCubit, Widget child) {
|
||||
final router = GoRouter(
|
||||
routes: [
|
||||
GoRoute(
|
||||
path: '/',
|
||||
builder: (context, state) => MediaQuery(
|
||||
data: const MediaQueryData(size: Size(2000, 1200)),
|
||||
child: MultiBlocProvider(
|
||||
providers: [
|
||||
BlocProvider<BlogPostCubit>.value(value: blogPostCubit),
|
||||
BlocProvider<FeedCubit>.value(value: feedCubit),
|
||||
BlocProvider<CommentsCubit>.value(value: commentsCubit),
|
||||
],
|
||||
child: child,
|
||||
),
|
||||
),
|
||||
),
|
||||
GoRoute(path: '/feed-screen', builder: (_, _) => const SizedBox.shrink()),
|
||||
],
|
||||
);
|
||||
|
||||
return MaterialApp.router(
|
||||
theme: ThemeHelper().themeData(),
|
||||
localizationsDelegates: const [
|
||||
S.delegate,
|
||||
GlobalMaterialLocalizations.delegate,
|
||||
GlobalWidgetsLocalizations.delegate,
|
||||
GlobalCupertinoLocalizations.delegate,
|
||||
],
|
||||
supportedLocales: const [Locale('ru')],
|
||||
routerConfig: router,
|
||||
);
|
||||
}
|
||||
|
||||
testWidgets('renders loading state', (tester) async {
|
||||
final blogPostCubit = MockBlogPostCubit();
|
||||
final feedCubit = MockFeedCubit();
|
||||
final commentsCubit = MockCommentsCubit();
|
||||
final state = BlogPostState(isLoading: true, blogPostModel: null, error: null);
|
||||
|
||||
when(blogPostCubit.state).thenReturn(state);
|
||||
when(blogPostCubit.stream).thenAnswer((_) => const Stream<BlogPostState>.empty());
|
||||
when(blogPostCubit.close()).thenAnswer((_) async => Future<void>.value());
|
||||
when(feedCubit.viewPost(any)).thenAnswer((_) async => Future<void>.value());
|
||||
when(feedCubit.stream).thenAnswer((_) => const Stream.empty());
|
||||
when(feedCubit.close()).thenAnswer((_) async => Future<void>.value());
|
||||
when(commentsCubit.state).thenReturn(const CommentsState());
|
||||
when(commentsCubit.stream).thenAnswer((_) => const Stream<CommentsState>.empty());
|
||||
when(commentsCubit.close()).thenAnswer((_) async => Future<void>.value());
|
||||
when(commentsCubit.getComments(any, any, any)).thenAnswer((_) async => Future<void>.value());
|
||||
|
||||
registerMocks(blogPostCubit, commentsCubit);
|
||||
|
||||
await tester.pumpWidget(wrap(blogPostCubit, feedCubit, commentsCubit, const BlogPostScreen(id: 1)));
|
||||
await tester.pump();
|
||||
|
||||
expect(find.byType(BlogPostScreen), findsOneWidget);
|
||||
});
|
||||
|
||||
});
|
||||
}
|
||||
@@ -6,6 +6,7 @@ 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/bloc/blog_post_create_cubit/blog_post_create_state.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';
|
||||
@@ -57,27 +58,21 @@ void main() {
|
||||
routes: [
|
||||
GoRoute(
|
||||
path: '/home',
|
||||
builder: (context, state) => const Scaffold(
|
||||
body: Center(child: Text('Home')),
|
||||
),
|
||||
builder: (context, state) => const Scaffold(body: Center(child: Text('Home'))),
|
||||
),
|
||||
GoRoute(
|
||||
path: '/blog-post-create',
|
||||
builder: (context, state) => MultiBlocProvider(
|
||||
providers: [
|
||||
BlocProvider<BlogPostCreateCubit>.value(
|
||||
value: blogPostCreateCubit,
|
||||
),
|
||||
BlocProvider<FeedCubit>.value(
|
||||
value: mockFeedCubit,
|
||||
),
|
||||
BlocProvider<BlogPostCreateCubit>.value(value: blogPostCreateCubit),
|
||||
BlocProvider<FeedCubit>.value(value: mockFeedCubit),
|
||||
],
|
||||
child: BlogPostCreateScreen(feedBlogPostModel: feedBlogPostModel),
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
|
||||
|
||||
return MaterialApp.router(
|
||||
routerConfig: router,
|
||||
theme: ThemeHelper().themeData(),
|
||||
@@ -113,12 +108,7 @@ void main() {
|
||||
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,
|
||||
),
|
||||
author: const Author(authorSlug: 'test-author', authorName: 'Test Author', authorProfileIcon: 'avatar.jpg', authorIsMe: true),
|
||||
createdAt: DateTime.now(),
|
||||
updatedAt: DateTime.now(),
|
||||
totalLikes: 0,
|
||||
@@ -136,7 +126,6 @@ void main() {
|
||||
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);
|
||||
@@ -183,12 +172,7 @@ void main() {
|
||||
videoUrl: 'https://youtube.com/watch?v=test',
|
||||
image: '',
|
||||
state: 'published',
|
||||
author: const Author(
|
||||
authorSlug: 'test-author',
|
||||
authorName: 'Test Author',
|
||||
authorProfileIcon: 'avatar.jpg',
|
||||
authorIsMe: true,
|
||||
),
|
||||
author: const Author(authorSlug: 'test-author', authorName: 'Test Author', authorProfileIcon: 'avatar.jpg', authorIsMe: true),
|
||||
createdAt: DateTime.now(),
|
||||
updatedAt: DateTime.now(),
|
||||
totalLikes: 0,
|
||||
@@ -256,16 +240,13 @@ void main() {
|
||||
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();
|
||||
});
|
||||
@@ -280,11 +261,11 @@ void main() {
|
||||
|
||||
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();
|
||||
@@ -294,7 +275,6 @@ void main() {
|
||||
await tester.tap(createButton, warnIfMissed: false);
|
||||
await tester.pump();
|
||||
|
||||
// Wait for async operation
|
||||
await tester.pump(const Duration(milliseconds: 200));
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
@@ -333,28 +313,19 @@ void main() {
|
||||
|
||||
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);
|
||||
verify(mockBlogPostRepository.updateBlogPost(any, any, any, any, any, any)).called(1);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -371,6 +342,24 @@ void main() {
|
||||
|
||||
expect(find.byType(SingleChildScrollView), findsOneWidget);
|
||||
});
|
||||
|
||||
testWidgets('renders error state', (tester) async {
|
||||
|
||||
blogPostCreateCubit.emit(const BlogPostCreateState.error(errorMessage: 'Error creating post'));
|
||||
|
||||
await tester.pumpWidget(createTestWidget());
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
expect(find.byType(BlogPostCreateScreen), findsOneWidget);
|
||||
});
|
||||
|
||||
testWidgets('renders loading state', (tester) async {
|
||||
blogPostCreateCubit.emit(const BlogPostCreateState.loading());
|
||||
|
||||
await tester.pumpWidget(createTestWidget());
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
expect(find.byType(BlogPostCreateScreen), findsOneWidget);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,147 @@
|
||||
import 'package:eatvio_app/core/app_config.dart';
|
||||
import 'package:eatvio_app/core/app_initializer.dart';
|
||||
import 'package:eatvio_app/core/locator_service.dart';
|
||||
import 'package:eatvio_app/core/theme/theme_helper.dart';
|
||||
import 'package:eatvio_app/features/bottom_navigation_bar/presentation/widgets/bottom_navigation_bar_widget.dart';
|
||||
import 'package:eatvio_app/features/chat/presentation/bloc/chats_cubit/chats_cubit.dart';
|
||||
import 'package:eatvio_app/features/chat/presentation/bloc/chats_cubit/chats_state.dart';
|
||||
import 'package:eatvio_app/features/notifications/presentation/notifications_cubit/notifications_cubit.dart';
|
||||
import 'package:eatvio_app/features/notifications/presentation/notifications_cubit/notifications_state.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_bloc/flutter_bloc.dart';
|
||||
import 'package:flutter_dotenv/flutter_dotenv.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 'package:network_image_mock/network_image_mock.dart';
|
||||
|
||||
import 'bottom_navigation_bar_widget_test.mocks.dart';
|
||||
|
||||
@GenerateNiceMocks([MockSpec<NotificationsCubit>(), MockSpec<ChatsCubit>()])
|
||||
void main() {
|
||||
group('BottomNavigationBarWidget smoke', () {
|
||||
setUpAll(() async {
|
||||
dotenv.loadFromString(envString: 'DEV_BASE_URL=http://localhost\nBASE_URL=http://localhost');
|
||||
});
|
||||
|
||||
setUp(() async {
|
||||
await sl.reset();
|
||||
sl.registerLazySingleton<AppConfig>(() => AppConfig(buildType: AppBuildType.tests)..setBaseUrl('http://localhost'));
|
||||
});
|
||||
|
||||
const mockUserProfile = ProfileModel(
|
||||
id: 1,
|
||||
name: 'Test User',
|
||||
email: 'test@example.com',
|
||||
phoneConfirmationState: 'confirmed',
|
||||
isPublicProfile: true,
|
||||
isBlockedByMe: false,
|
||||
isBlockedByAuthor: false,
|
||||
isBlockedByUser: false,
|
||||
isEntitySubscribedByMe: false,
|
||||
slug: 'test-user',
|
||||
profilePhotoUrl: 'https://example.com/avatar.jpg',
|
||||
roles: ['user'],
|
||||
badges: [],
|
||||
subscriptions: [],
|
||||
subscribers: [],
|
||||
);
|
||||
|
||||
const mockConsultantProfile = ProfileModel(
|
||||
id: 1,
|
||||
name: 'Test Consultant',
|
||||
email: 'consultant@example.com',
|
||||
phoneConfirmationState: 'confirmed',
|
||||
isPublicProfile: true,
|
||||
isBlockedByMe: false,
|
||||
isBlockedByAuthor: false,
|
||||
isBlockedByUser: false,
|
||||
isEntitySubscribedByMe: false,
|
||||
slug: 'test-consultant',
|
||||
profilePhotoUrl: 'https://example.com/avatar.jpg',
|
||||
roles: ['consultant'],
|
||||
badges: [],
|
||||
subscriptions: [],
|
||||
subscribers: [],
|
||||
);
|
||||
|
||||
Widget wrap(Widget child) {
|
||||
return MaterialApp(
|
||||
theme: ThemeHelper().themeData(),
|
||||
localizationsDelegates: const [
|
||||
S.delegate,
|
||||
GlobalMaterialLocalizations.delegate,
|
||||
GlobalWidgetsLocalizations.delegate,
|
||||
GlobalCupertinoLocalizations.delegate,
|
||||
],
|
||||
supportedLocales: const [Locale('ru')],
|
||||
home: child,
|
||||
);
|
||||
}
|
||||
|
||||
testWidgets('renders bottom navigation bar for user', (tester) async {
|
||||
mockNetworkImagesFor(() async {
|
||||
final notificationsCubit = MockNotificationsCubit();
|
||||
final chatsCubit = MockChatsCubit();
|
||||
|
||||
when(notificationsCubit.state).thenReturn(const NotificationsState());
|
||||
when(notificationsCubit.stream).thenAnswer((_) => const Stream.empty());
|
||||
when(notificationsCubit.close()).thenAnswer((_) async => Future<void>.value());
|
||||
when(chatsCubit.state).thenReturn(ChatsState());
|
||||
when(chatsCubit.stream).thenAnswer((_) => const Stream.empty());
|
||||
when(chatsCubit.close()).thenAnswer((_) async => Future<void>.value());
|
||||
|
||||
await tester.pumpWidget(
|
||||
MultiBlocProvider(
|
||||
providers: [
|
||||
BlocProvider<NotificationsCubit>.value(value: notificationsCubit),
|
||||
BlocProvider<ChatsCubit>.value(value: chatsCubit),
|
||||
],
|
||||
child: wrap(
|
||||
const Scaffold(
|
||||
body: BottomNavigationBarWidget(profileModel: mockUserProfile, child: Text('Test Content')),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
expect(find.byType(BottomNavigationBarWidget), findsOneWidget);
|
||||
});
|
||||
});
|
||||
|
||||
testWidgets('renders bottom navigation bar for consultant', (tester) async {
|
||||
mockNetworkImagesFor(() async {
|
||||
final notificationsCubit = MockNotificationsCubit();
|
||||
final chatsCubit = MockChatsCubit();
|
||||
|
||||
when(notificationsCubit.state).thenReturn(const NotificationsState());
|
||||
when(notificationsCubit.stream).thenAnswer((_) => const Stream.empty());
|
||||
when(notificationsCubit.close()).thenAnswer((_) async => Future<void>.value());
|
||||
when(chatsCubit.state).thenReturn(ChatsState());
|
||||
when(chatsCubit.stream).thenAnswer((_) => const Stream.empty());
|
||||
when(chatsCubit.close()).thenAnswer((_) async => Future<void>.value());
|
||||
|
||||
await tester.pumpWidget(
|
||||
MultiBlocProvider(
|
||||
providers: [
|
||||
BlocProvider<NotificationsCubit>.value(value: notificationsCubit),
|
||||
BlocProvider<ChatsCubit>.value(value: chatsCubit),
|
||||
],
|
||||
child: wrap(
|
||||
const Scaffold(
|
||||
body: BottomNavigationBarWidget(profileModel: mockConsultantProfile, child: Text('Test Content')),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
expect(find.byType(BottomNavigationBarWidget), findsOneWidget);
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,85 @@
|
||||
import 'package:eatvio_app/core/app_config.dart';
|
||||
import 'package:eatvio_app/core/app_initializer.dart';
|
||||
import 'package:eatvio_app/core/locator_service.dart';
|
||||
import 'package:eatvio_app/core/theme/theme_helper.dart';
|
||||
import 'package:eatvio_app/features/calendar/models/calendar_model.dart';
|
||||
import 'package:eatvio_app/features/calendar/presentation/bloc/calendar_cubit.dart';
|
||||
import 'package:eatvio_app/features/calendar/presentation/pages/calendar_screen.dart';
|
||||
import 'package:eatvio_app/features/calendar/repositories/calendar_repository.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_dotenv/flutter_dotenv.dart';
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:intl/date_symbol_data_local.dart';
|
||||
import 'package:mockito/annotations.dart';
|
||||
import 'package:mockito/mockito.dart';
|
||||
|
||||
import 'calendar_screen_smoke_test.mocks.dart';
|
||||
|
||||
@GenerateNiceMocks([MockSpec<CalendarRepository>()])
|
||||
void main() {
|
||||
group('CalendarScreen smoke', () {
|
||||
setUpAll(() async {
|
||||
dotenv.loadFromString(envString: 'DEV_BASE_URL=http://localhost\nBASE_URL=http://localhost');
|
||||
await initializeDateFormatting('ru', null);
|
||||
});
|
||||
|
||||
setUp(() async {
|
||||
await sl.reset();
|
||||
sl.registerLazySingleton<AppConfig>(() => AppConfig(buildType: AppBuildType.tests)..setBaseUrl('http://localhost'));
|
||||
});
|
||||
|
||||
final mockCalendarList = [
|
||||
const CalendarModel(date: '2025-01-01', isCurrentDayIncludedInTheChosenDietProgression: true, diary: <String, dynamic>{'id': 1, 'state': 'finished'}),
|
||||
];
|
||||
|
||||
Widget wrap(Widget child) {
|
||||
return MaterialApp(theme: ThemeHelper().themeData(), home: child);
|
||||
}
|
||||
|
||||
testWidgets('renders loading state', (tester) async {
|
||||
final calendarRepository = MockCalendarRepository();
|
||||
final cubit = CalendarCubit(calendarRepository: calendarRepository);
|
||||
|
||||
when(calendarRepository.getCalendar()).thenAnswer((_) async => mockCalendarList);
|
||||
|
||||
sl.registerLazySingleton<CalendarRepository>(() => calendarRepository);
|
||||
sl.registerLazySingleton<CalendarCubit>(() => cubit);
|
||||
|
||||
await tester.pumpWidget(wrap(const CalendarScreen()));
|
||||
await tester.pump();
|
||||
await tester.pump(const Duration(milliseconds: 100));
|
||||
|
||||
expect(find.byType(CalendarScreen), findsOneWidget);
|
||||
});
|
||||
|
||||
testWidgets('renders loaded state with calendar list', (tester) async {
|
||||
final calendarRepository = MockCalendarRepository();
|
||||
final cubit = CalendarCubit(calendarRepository: calendarRepository);
|
||||
|
||||
when(calendarRepository.getCalendar()).thenAnswer((_) async => mockCalendarList);
|
||||
|
||||
sl.registerLazySingleton<CalendarRepository>(() => calendarRepository);
|
||||
sl.registerLazySingleton<CalendarCubit>(() => cubit);
|
||||
|
||||
await tester.pumpWidget(wrap(const CalendarScreen()));
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
expect(find.text('Календарь'), findsOneWidget);
|
||||
});
|
||||
|
||||
testWidgets('renders error state', (tester) async {
|
||||
final calendarRepository = MockCalendarRepository();
|
||||
final cubit = CalendarCubit(calendarRepository: calendarRepository);
|
||||
|
||||
when(calendarRepository.getCalendar()).thenThrow(Exception('Error loading calendar'));
|
||||
|
||||
sl.registerLazySingleton<CalendarRepository>(() => calendarRepository);
|
||||
sl.registerLazySingleton<CalendarCubit>(() => cubit);
|
||||
|
||||
await tester.pumpWidget(wrap(const CalendarScreen()));
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
expect(find.textContaining('Error loading calendar'), findsOneWidget);
|
||||
});
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,199 @@
|
||||
import 'package:eatvio_app/core/app_config.dart';
|
||||
import 'package:eatvio_app/core/app_initializer.dart';
|
||||
import 'package:eatvio_app/core/locator_service.dart';
|
||||
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_model.dart';
|
||||
import 'package:eatvio_app/features/chat/models/chat_room_settings_model.dart';
|
||||
import 'package:eatvio_app/features/chat/models/chat_user_model.dart';
|
||||
import 'package:eatvio_app/features/chat/presentation/bloc/chat_room_cubit/chat_room_cubit.dart';
|
||||
import 'package:eatvio_app/features/chat/presentation/bloc/chat_room_cubit/chat_room_state.dart';
|
||||
import 'package:eatvio_app/features/chat/presentation/bloc/chats_cubit/chats_cubit.dart';
|
||||
import 'package:eatvio_app/features/chat/presentation/bloc/chats_cubit/chats_state.dart';
|
||||
import 'package:eatvio_app/features/chat/presentation/pages/chat_screen.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:flutter_dotenv/flutter_dotenv.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:intl/date_symbol_data_local.dart';
|
||||
import 'package:mockito/annotations.dart';
|
||||
import 'package:mockito/mockito.dart';
|
||||
import 'package:visibility_detector/visibility_detector.dart';
|
||||
|
||||
import 'chat_screen_loaded_smoke_test.mocks.dart';
|
||||
|
||||
@GenerateNiceMocks([MockSpec<ChatRoomCubit>(), MockSpec<ChatsCubit>()])
|
||||
void main() {
|
||||
group('ChatScreen loaded smoke', () {
|
||||
setUpAll(() async {
|
||||
dotenv.loadFromString(envString: 'DEV_BASE_URL=http://localhost\nBASE_URL=http://localhost');
|
||||
await initializeDateFormatting('ru', null);
|
||||
await initializeDateFormatting('ru_RU', null);
|
||||
await initializeDateFormatting('RU', null);
|
||||
VisibilityDetectorController.instance.updateInterval = Duration.zero;
|
||||
});
|
||||
|
||||
setUp(() async {
|
||||
await sl.reset();
|
||||
sl.registerLazySingleton<AppConfig>(() => AppConfig(buildType: AppBuildType.tests)..setBaseUrl('http://localhost'));
|
||||
});
|
||||
|
||||
const fakeSvg = '''
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="10" height="10" viewBox="0 0 10 10">
|
||||
<rect width="10" height="10" fill="red"/>
|
||||
</svg>
|
||||
''';
|
||||
|
||||
final fakeBundle = _FakeAssetBundle({'assets/icons/error_icon.svg': fakeSvg});
|
||||
|
||||
Widget wrap(Widget child, ChatsCubit chatsCubit) {
|
||||
return DefaultAssetBundle(
|
||||
bundle: fakeBundle,
|
||||
child: MaterialApp(
|
||||
theme: ThemeHelper().themeData(),
|
||||
home: BlocProvider<ChatsCubit>.value(value: chatsCubit, child: child),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
testWidgets('renders loaded header (chosenPlan null)', (tester) async {
|
||||
final chatRoomCubit = MockChatRoomCubit();
|
||||
final chatsCubit = MockChatsCubit();
|
||||
|
||||
final now = DateTime(2025, 1, 2, 10, 30);
|
||||
final yesterday = DateTime(2025, 1, 1, 9, 15);
|
||||
|
||||
const chatRoom = ChatRoomModel(
|
||||
id: '1',
|
||||
chatRoomSettings: ChatRoomSettingsModel(name: 'room', state: 'active'),
|
||||
users: Users(
|
||||
sender: ChatUserModel(id: 1, name: 'Me', slug: 'me', profilePhotoUrl: null),
|
||||
receiver: ChatUserModel(id: 2, name: 'User Two', slug: 'user-two', profilePhotoUrl: null),
|
||||
),
|
||||
chosenPlanId: null,
|
||||
chosenPlan: null,
|
||||
);
|
||||
|
||||
final state = ChatRoomState.loaded(
|
||||
chatRoom: chatRoom,
|
||||
chatMessages: [
|
||||
ChatMessageModel(
|
||||
id: 2,
|
||||
data: 'hello',
|
||||
mediaData: const [],
|
||||
authorId: 1, // current user (sender)
|
||||
updatedAt: now,
|
||||
isRead: true,
|
||||
isCurrentUserAuthor: true,
|
||||
roomId: '1',
|
||||
messageState: MessageState.sent,
|
||||
uuid: 'u2',
|
||||
),
|
||||
ChatMessageModel(
|
||||
id: 4,
|
||||
data: '',
|
||||
mediaData: const ['chat_files/file.pdf'],
|
||||
authorId: 1, // current user
|
||||
updatedAt: now.subtract(const Duration(minutes: 1)),
|
||||
isRead: true,
|
||||
isCurrentUserAuthor: true,
|
||||
roomId: '1',
|
||||
messageState: MessageState.sent,
|
||||
uuid: 'u4',
|
||||
),
|
||||
ChatMessageModel(
|
||||
id: 3,
|
||||
data: 'sending...',
|
||||
mediaData: const [],
|
||||
authorId: 1, // current user
|
||||
updatedAt: now.subtract(const Duration(minutes: 2)),
|
||||
isRead: true,
|
||||
isCurrentUserAuthor: true,
|
||||
roomId: '1',
|
||||
messageState: MessageState.loading,
|
||||
uuid: 'u3',
|
||||
),
|
||||
ChatMessageModel(
|
||||
id: 5,
|
||||
data: 'tap to retry',
|
||||
mediaData: const [],
|
||||
authorId: 1, // current user
|
||||
updatedAt: now.subtract(const Duration(minutes: 3)),
|
||||
isRead: true,
|
||||
isCurrentUserAuthor: true,
|
||||
roomId: '1',
|
||||
messageState: MessageState.error,
|
||||
uuid: 'u5',
|
||||
),
|
||||
ChatMessageModel(
|
||||
id: 1,
|
||||
data: 'hi',
|
||||
mediaData: const [],
|
||||
authorId: 2, // other user (receiver)
|
||||
updatedAt: yesterday,
|
||||
isRead: true,
|
||||
isCurrentUserAuthor: false,
|
||||
roomId: '1',
|
||||
messageState: MessageState.sent,
|
||||
uuid: 'u1',
|
||||
),
|
||||
],
|
||||
hasCard: false,
|
||||
isTyping: false,
|
||||
isLoading: false,
|
||||
);
|
||||
|
||||
when(chatRoomCubit.state).thenReturn(state);
|
||||
when(chatRoomCubit.stream).thenAnswer((_) => const Stream<ChatRoomState>.empty());
|
||||
when(chatRoomCubit.close()).thenAnswer((_) async => Future<void>.value());
|
||||
when(chatRoomCubit.initChat(any, sendInitialMessage: anyNamed('sendInitialMessage'))).thenAnswer((_) async => Future<void>.value());
|
||||
when(chatRoomCubit.setTimer()).thenReturn(null);
|
||||
|
||||
|
||||
sl.registerFactory<ChatRoomCubit>(() => chatRoomCubit);
|
||||
|
||||
when(chatsCubit.state).thenReturn(ChatsState());
|
||||
when(chatsCubit.stream).thenAnswer((_) => const Stream.empty());
|
||||
when(chatsCubit.close()).thenAnswer((_) async => Future<void>.value());
|
||||
|
||||
await tester.pumpWidget(wrap(const ChatScreen(uuid: 'uuid', sendInitialMessage: false), chatsCubit));
|
||||
await tester.pump();
|
||||
await tester.pump(const Duration(milliseconds: 200));
|
||||
|
||||
expect(find.byType(ChatScreen), findsOneWidget);
|
||||
expect(find.text('User Two'), findsOneWidget);
|
||||
expect(find.text('hello'), findsOneWidget);
|
||||
expect(find.text('hi'), findsOneWidget);
|
||||
expect(find.text('sending...'), findsOneWidget);
|
||||
expect(find.text('tap to retry'), findsOneWidget);
|
||||
expect(find.text('file.pdf'), findsOneWidget);
|
||||
VisibilityDetectorController.instance.notifyNow();
|
||||
await tester.pump();
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
class _FakeAssetBundle extends CachingAssetBundle {
|
||||
_FakeAssetBundle(this.assets);
|
||||
final Map<String, String> assets;
|
||||
|
||||
@override
|
||||
Future<String> loadString(String key, {bool cache = true}) async {
|
||||
final value = assets[key];
|
||||
if (value == null) {
|
||||
throw FlutterError('Asset not found: $key');
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
@override
|
||||
Future<ByteData> load(String key) async {
|
||||
final value = assets[key];
|
||||
if (value == null) {
|
||||
throw FlutterError('Asset not found: $key');
|
||||
}
|
||||
final bytes = Uint8List.fromList(value.codeUnits);
|
||||
return ByteData.sublistView(bytes);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,47 @@
|
||||
import 'package:eatvio_app/core/app_config.dart';
|
||||
import 'package:eatvio_app/core/app_initializer.dart';
|
||||
import 'package:eatvio_app/core/locator_service.dart';
|
||||
import 'package:eatvio_app/core/theme/theme_helper.dart';
|
||||
import 'package:eatvio_app/core/widgets/loader.dart';
|
||||
import 'package:eatvio_app/features/chat/presentation/bloc/chat_room_cubit/chat_room_cubit.dart';
|
||||
import 'package:eatvio_app/features/chat/presentation/bloc/chat_room_cubit/chat_room_state.dart';
|
||||
import 'package:eatvio_app/features/chat/presentation/pages/chat_screen.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_dotenv/flutter_dotenv.dart';
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:mockito/annotations.dart';
|
||||
import 'package:mockito/mockito.dart';
|
||||
|
||||
import 'chat_screen_loading_smoke_test.mocks.dart';
|
||||
|
||||
@GenerateNiceMocks([MockSpec<ChatRoomCubit>()])
|
||||
void main() {
|
||||
group('ChatScreen smoke', () {
|
||||
setUpAll(() async {
|
||||
dotenv.loadFromString(envString: 'DEV_BASE_URL=http://localhost\nBASE_URL=http://localhost');
|
||||
});
|
||||
|
||||
setUp(() async {
|
||||
await sl.reset();
|
||||
sl.registerLazySingleton<AppConfig>(() => AppConfig(buildType: AppBuildType.tests)..setBaseUrl('http://localhost'));
|
||||
});
|
||||
|
||||
Widget wrap(Widget child) => MaterialApp(theme: ThemeHelper().themeData(), home: child);
|
||||
|
||||
testWidgets('renders loading scaffold', (tester) async {
|
||||
final cubit = MockChatRoomCubit();
|
||||
when(cubit.state).thenReturn(const ChatRoomState.loading());
|
||||
when(cubit.stream).thenAnswer((_) => const Stream<ChatRoomState>.empty());
|
||||
when(cubit.close()).thenAnswer((_) async => Future<void>.value());
|
||||
when(cubit.initChat(any, sendInitialMessage: anyNamed('sendInitialMessage'))).thenAnswer((_) async => Future<void>.value());
|
||||
when(cubit.setTimer()).thenReturn(null);
|
||||
sl.registerFactory<ChatRoomCubit>(() => cubit);
|
||||
|
||||
await tester.pumpWidget(wrap(const ChatScreen(uuid: 'uuid', sendInitialMessage: false)));
|
||||
await tester.pump();
|
||||
|
||||
expect(find.byType(ChatScreen), findsOneWidget);
|
||||
expect(find.byType(Loader), findsOneWidget);
|
||||
});
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,53 @@
|
||||
import 'package:eatvio_app/core/enums/base_state.dart';
|
||||
import 'package:eatvio_app/core/theme/theme_helper.dart';
|
||||
import 'package:eatvio_app/features/chat/presentation/bloc/chats_cubit/chats_cubit.dart';
|
||||
import 'package:eatvio_app/features/chat/presentation/bloc/chats_cubit/chats_state.dart';
|
||||
import 'package:eatvio_app/features/chat/presentation/pages/chats_screen.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 'chats_screen_smoke_test.mocks.dart';
|
||||
|
||||
@GenerateNiceMocks([MockSpec<ChatsCubit>()])
|
||||
void main() {
|
||||
group('ChatsScreen smoke', () {
|
||||
Widget wrap(Widget child, ChatsCubit cubit) {
|
||||
return MaterialApp(
|
||||
theme: ThemeHelper().themeData(),
|
||||
localizationsDelegates: const [
|
||||
S.delegate,
|
||||
GlobalMaterialLocalizations.delegate,
|
||||
GlobalWidgetsLocalizations.delegate,
|
||||
GlobalCupertinoLocalizations.delegate,
|
||||
],
|
||||
supportedLocales: const [Locale('ru')],
|
||||
home: BlocProvider<ChatsCubit>.value(value: cubit, child: child),
|
||||
);
|
||||
}
|
||||
|
||||
testWidgets('renders success state (empty list)', (tester) async {
|
||||
final cubit = MockChatsCubit();
|
||||
when(cubit.state).thenReturn(ChatsState(state: BaseState.success, chatRooms: const []));
|
||||
when(cubit.stream).thenAnswer((_) => const Stream<ChatsState>.empty());
|
||||
when(cubit.close()).thenAnswer((_) async => Future<void>.value());
|
||||
await tester.pumpWidget(wrap(const ChatsScreen(), cubit));
|
||||
await tester.pump();
|
||||
expect(find.byType(ChatsScreen), findsOneWidget);
|
||||
});
|
||||
|
||||
testWidgets('renders error state', (tester) async {
|
||||
final cubit = MockChatsCubit();
|
||||
when(cubit.state).thenReturn(ChatsState(state: BaseState.error, errorMessage: 'err'));
|
||||
when(cubit.stream).thenAnswer((_) => const Stream<ChatsState>.empty());
|
||||
when(cubit.close()).thenAnswer((_) async => Future<void>.value());
|
||||
await tester.pumpWidget(wrap(const ChatsScreen(), cubit));
|
||||
await tester.pump();
|
||||
expect(find.byType(ChatsScreen), findsOneWidget);
|
||||
});
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
import 'package:eatvio_app/core/theme/theme_helper.dart';
|
||||
import 'package:eatvio_app/features/chat/presentation/pages/empty_chat_screen.dart';
|
||||
import 'package:eatvio_app/features/profile/models/profile_model.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:network_image_mock/network_image_mock.dart';
|
||||
|
||||
void main() {
|
||||
group('EmptyChatScreen smoke', () {
|
||||
Widget wrap(Widget child) => MaterialApp(theme: ThemeHelper().themeData(), home: child);
|
||||
|
||||
testWidgets('renders', (tester) async {
|
||||
const user = ProfileModel(
|
||||
id: 1,
|
||||
name: 'User',
|
||||
email: 'u@e.com',
|
||||
phoneConfirmationState: 'confirmed',
|
||||
slug: 'user',
|
||||
isPublicProfile: true,
|
||||
isBlockedByMe: false,
|
||||
isBlockedByAuthor: false,
|
||||
isBlockedByUser: false,
|
||||
isEntitySubscribedByMe: false,
|
||||
profilePhotoUrl: 'http://localhost/avatar.png',
|
||||
roles: [],
|
||||
badges: [],
|
||||
subscriptions: [],
|
||||
subscribers: [],
|
||||
);
|
||||
|
||||
await mockNetworkImagesFor(() async {
|
||||
await tester.pumpWidget(wrap(const EmptyChatScreen(user: user)));
|
||||
await tester.pump();
|
||||
});
|
||||
|
||||
expect(find.byType(EmptyChatScreen), findsOneWidget);
|
||||
expect(find.text('Сообщение'), findsOneWidget);
|
||||
});
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,152 @@
|
||||
import 'dart:async';
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:eatvio_app/core/app_config.dart';
|
||||
import 'package:eatvio_app/core/app_initializer.dart';
|
||||
import 'package:eatvio_app/core/locator_service.dart';
|
||||
import 'package:eatvio_app/core/theme/theme_helper.dart';
|
||||
import 'package:eatvio_app/features/chat/presentation/pages/photo_view_screen.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_dotenv/flutter_dotenv.dart';
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
|
||||
class _FakeHttpClient implements HttpClient {
|
||||
bool _autoUncompress = true;
|
||||
|
||||
@override
|
||||
bool get autoUncompress => _autoUncompress;
|
||||
|
||||
@override
|
||||
set autoUncompress(bool value) {
|
||||
_autoUncompress = value;
|
||||
}
|
||||
|
||||
@override
|
||||
Future<HttpClientRequest> getUrl(Uri url) async => _FakeHttpClientRequest();
|
||||
|
||||
@override
|
||||
dynamic noSuchMethod(Invocation invocation) => super.noSuchMethod(invocation);
|
||||
}
|
||||
|
||||
class _FakeHttpClientRequest implements HttpClientRequest {
|
||||
@override
|
||||
Future<HttpClientResponse> close() async => _FakeHttpClientResponse();
|
||||
|
||||
@override
|
||||
dynamic noSuchMethod(Invocation invocation) => super.noSuchMethod(invocation);
|
||||
}
|
||||
|
||||
class _FakeHttpClientResponse extends Stream<List<int>> implements HttpClientResponse {
|
||||
static final List<int> _png = <int>[
|
||||
0x89,
|
||||
0x50,
|
||||
0x4E,
|
||||
0x47,
|
||||
0x0D,
|
||||
0x0A,
|
||||
0x1A,
|
||||
0x0A,
|
||||
0x00,
|
||||
0x00,
|
||||
0x00,
|
||||
0x0D,
|
||||
0x49,
|
||||
0x48,
|
||||
0x44,
|
||||
0x52,
|
||||
0x00,
|
||||
0x00,
|
||||
0x00,
|
||||
0x01,
|
||||
0x00,
|
||||
0x00,
|
||||
0x00,
|
||||
0x01,
|
||||
0x08,
|
||||
0x06,
|
||||
0x00,
|
||||
0x00,
|
||||
0x00,
|
||||
0x1F,
|
||||
0x15,
|
||||
0xC4,
|
||||
0x89,
|
||||
0x00,
|
||||
0x00,
|
||||
0x00,
|
||||
0x0A,
|
||||
0x49,
|
||||
0x44,
|
||||
0x41,
|
||||
0x54,
|
||||
0x78,
|
||||
0x9C,
|
||||
0x63,
|
||||
0x00,
|
||||
0x01,
|
||||
0x00,
|
||||
0x00,
|
||||
0x05,
|
||||
0x00,
|
||||
0x01,
|
||||
0x0D,
|
||||
0x0A,
|
||||
0x2D,
|
||||
0xB4,
|
||||
0x00,
|
||||
0x00,
|
||||
0x00,
|
||||
0x00,
|
||||
0x49,
|
||||
0x45,
|
||||
0x4E,
|
||||
0x44,
|
||||
0xAE,
|
||||
0x42,
|
||||
0x60,
|
||||
0x82,
|
||||
];
|
||||
|
||||
@override
|
||||
int get statusCode => 200;
|
||||
|
||||
@override
|
||||
int get contentLength => _png.length;
|
||||
|
||||
@override
|
||||
HttpClientResponseCompressionState get compressionState => HttpClientResponseCompressionState.notCompressed;
|
||||
|
||||
@override
|
||||
StreamSubscription<List<int>> listen(void Function(List<int>)? onData, {Function? onError, void Function()? onDone, bool? cancelOnError}) {
|
||||
return Stream<List<int>>.fromIterable([_png]).listen(onData, onError: onError, onDone: onDone, cancelOnError: cancelOnError);
|
||||
}
|
||||
|
||||
@override
|
||||
dynamic noSuchMethod(Invocation invocation) => super.noSuchMethod(invocation);
|
||||
}
|
||||
|
||||
void main() {
|
||||
group('PhotoViewScreen smoke', () {
|
||||
setUpAll(() async {
|
||||
dotenv.loadFromString(envString: 'DEV_BASE_URL=http://localhost\nBASE_URL=http://localhost');
|
||||
});
|
||||
|
||||
setUp(() async {
|
||||
await sl.reset();
|
||||
sl.registerLazySingleton<AppConfig>(() => AppConfig(buildType: AppBuildType.tests)..setBaseUrl('http://localhost'));
|
||||
});
|
||||
|
||||
testWidgets('renders with a network image url', (tester) async {
|
||||
await HttpOverrides.runZoned(() async {
|
||||
await tester.pumpWidget(
|
||||
MaterialApp(
|
||||
theme: ThemeHelper().themeData(),
|
||||
home: const PhotoViewScreen(imageUrls: ['https://example.com/a.png'], index: 0),
|
||||
),
|
||||
);
|
||||
await tester.pumpAndSettle(const Duration(milliseconds: 200));
|
||||
expect(find.byType(PhotoViewScreen), findsOneWidget);
|
||||
}, createHttpClient: (context) => _FakeHttpClient());
|
||||
});
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,205 @@
|
||||
|
||||
import 'package:eatvio_app/core/theme/theme_helper.dart';
|
||||
import 'package:eatvio_app/features/chat/presentation/bloc/chat_room_cubit/chat_room_cubit.dart';
|
||||
import 'package:eatvio_app/features/chat/presentation/bloc/chat_room_cubit/chat_room_state.dart';
|
||||
import 'package:eatvio_app/features/chat/presentation/widgets/chose_plan_date_widget.dart';
|
||||
import 'package:eatvio_app/features/chat/presentation/widgets/complete_plan_bottom_sheet.dart';
|
||||
import 'package:eatvio_app/features/chat/presentation/widgets/plan_complain_bottom_sheet.dart';
|
||||
import 'package:eatvio_app/generated/l10n.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:flutter_localizations/flutter_localizations.dart';
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:flutter_portal/flutter_portal.dart';
|
||||
import 'package:go_router/go_router.dart';
|
||||
|
||||
class FakeChatRoomCubit extends Cubit<ChatRoomState> implements ChatRoomCubit {
|
||||
FakeChatRoomCubit(super.initialState);
|
||||
|
||||
bool finishResult = true;
|
||||
bool complainResult = true;
|
||||
|
||||
@override
|
||||
Future<bool> choseStartPlanDate(DateTime date) async => true;
|
||||
|
||||
@override
|
||||
Future<bool> finishChosenPlan() async => finishResult;
|
||||
|
||||
@override
|
||||
Future<bool> complainAboutConsultation({required String description}) async => complainResult;
|
||||
|
||||
@override
|
||||
dynamic noSuchMethod(Invocation invocation) => super.noSuchMethod(invocation);
|
||||
}
|
||||
|
||||
class _FakeAssetBundle extends CachingAssetBundle {
|
||||
_FakeAssetBundle(this.assets);
|
||||
final Map<String, String> assets;
|
||||
|
||||
@override
|
||||
Future<String> loadString(String key, {bool cache = true}) async {
|
||||
final value = assets[key];
|
||||
if (value == null) throw FlutterError('Asset not found: $key');
|
||||
return value;
|
||||
}
|
||||
|
||||
@override
|
||||
Future<ByteData> load(String key) async {
|
||||
final value = assets[key];
|
||||
if (value == null) throw FlutterError('Asset not found: $key');
|
||||
return ByteData.sublistView(Uint8List.fromList(value.codeUnits));
|
||||
}
|
||||
}
|
||||
|
||||
void main() {
|
||||
group('Chat bottom sheets smoke', () {
|
||||
const fakeSvg = '''
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="10" height="10" viewBox="0 0 10 10">
|
||||
<rect width="10" height="10" fill="red"/>
|
||||
</svg>
|
||||
''';
|
||||
|
||||
final bundle = _FakeAssetBundle({'assets/icons/error_icon.svg': fakeSvg, 'assets/icons/question_mark.svg': fakeSvg});
|
||||
|
||||
Widget wrap(Widget child) {
|
||||
final router = GoRouter(
|
||||
routes: [
|
||||
GoRoute(
|
||||
path: '/',
|
||||
builder: (_, _) => Scaffold(body: SingleChildScrollView(child: child)),
|
||||
),
|
||||
],
|
||||
);
|
||||
return DefaultAssetBundle(
|
||||
bundle: bundle,
|
||||
child: Portal(
|
||||
child: MaterialApp.router(
|
||||
theme: ThemeHelper().themeData(),
|
||||
localizationsDelegates: const [
|
||||
S.delegate,
|
||||
GlobalMaterialLocalizations.delegate,
|
||||
GlobalWidgetsLocalizations.delegate,
|
||||
GlobalCupertinoLocalizations.delegate,
|
||||
],
|
||||
supportedLocales: const [Locale('ru')],
|
||||
routerConfig: router,
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
testWidgets('ChosePlanDateWidget renders', (tester) async {
|
||||
final cubit = FakeChatRoomCubit(const ChatRoomState.loading());
|
||||
await tester.pumpWidget(wrap(ChosePlanDateWidget(chatCubit: cubit, hasPersonalDiet: false)));
|
||||
await tester.pump();
|
||||
expect(find.text(S.current.confirm), findsOneWidget);
|
||||
});
|
||||
|
||||
testWidgets('ChosePlanDateWidget toggles tooltip when hasPersonalDiet', (tester) async {
|
||||
final cubit = FakeChatRoomCubit(const ChatRoomState.loading());
|
||||
await tester.pumpWidget(wrap(ChosePlanDateWidget(chatCubit: cubit, hasPersonalDiet: true)));
|
||||
await tester.pump();
|
||||
|
||||
|
||||
expect(find.text(S.current.chosenPlanStartDateDescr), findsOneWidget);
|
||||
await tester.tap(find.byType(GestureDetector).first);
|
||||
await tester.pump();
|
||||
expect(find.text(S.current.chosenPlanStartDateDescr), findsOneWidget);
|
||||
});
|
||||
|
||||
testWidgets('CompletePlanBottomSheet shows failure UI when finish fails', (tester) async {
|
||||
final cubit = FakeChatRoomCubit(const ChatRoomState.loading())..finishResult = false;
|
||||
await tester.pumpWidget(wrap(CompletePlanBottomSheet(cubit: cubit)));
|
||||
await tester.pump();
|
||||
await tester.tap(find.text(S.current.confirm));
|
||||
await tester.pump();
|
||||
expect(find.text('Произошла ошибка, попробуйте позже'), findsOneWidget);
|
||||
expect(find.text(S.current.ok), findsOneWidget);
|
||||
});
|
||||
|
||||
testWidgets('CompletePlanBottomSheet closes on success', (tester) async {
|
||||
final cubit = FakeChatRoomCubit(const ChatRoomState.loading())..finishResult = true;
|
||||
final router = GoRouter(
|
||||
routes: [
|
||||
GoRoute(
|
||||
path: '/',
|
||||
builder: (context, state) => Scaffold(
|
||||
body: Center(
|
||||
child: TextButton(onPressed: () => context.push('/sheet'), child: const Text('open')),
|
||||
),
|
||||
),
|
||||
),
|
||||
GoRoute(
|
||||
path: '/sheet',
|
||||
builder: (_, _) => Scaffold(
|
||||
body: SingleChildScrollView(child: CompletePlanBottomSheet(cubit: cubit)),
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
await tester.pumpWidget(
|
||||
DefaultAssetBundle(
|
||||
bundle: bundle,
|
||||
child: Portal(
|
||||
child: MaterialApp.router(
|
||||
theme: ThemeHelper().themeData(),
|
||||
localizationsDelegates: const [
|
||||
S.delegate,
|
||||
GlobalMaterialLocalizations.delegate,
|
||||
GlobalWidgetsLocalizations.delegate,
|
||||
GlobalCupertinoLocalizations.delegate,
|
||||
],
|
||||
supportedLocales: const [Locale('ru')],
|
||||
routerConfig: router,
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
await tester.pump();
|
||||
await tester.tap(find.text('open'));
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
expect(find.byType(CompletePlanBottomSheet), findsOneWidget);
|
||||
await tester.tap(find.text(S.current.confirm));
|
||||
await tester.pumpAndSettle();
|
||||
expect(find.byType(CompletePlanBottomSheet), findsNothing);
|
||||
});
|
||||
|
||||
testWidgets('PlanComplainBottomSheet validates text length', (tester) async {
|
||||
final cubit = FakeChatRoomCubit(const ChatRoomState.loading());
|
||||
await tester.pumpWidget(wrap(PlanComplainBottomSheet(cubit: cubit)));
|
||||
await tester.pump();
|
||||
await tester.tap(find.text(S.current.confirm));
|
||||
await tester.pump();
|
||||
|
||||
await tester.enterText(find.byType(TextFormField), 'a');
|
||||
await tester.pump();
|
||||
expect(find.text(S.current.atLeast3Symbols), findsOneWidget);
|
||||
});
|
||||
|
||||
testWidgets('PlanComplainBottomSheet shows success UI when submit succeeds', (tester) async {
|
||||
final cubit = FakeChatRoomCubit(const ChatRoomState.loading())..complainResult = true;
|
||||
await tester.pumpWidget(wrap(PlanComplainBottomSheet(cubit: cubit)));
|
||||
await tester.pump();
|
||||
await tester.enterText(find.byType(TextFormField), 'abc');
|
||||
await tester.pump();
|
||||
await tester.tap(find.text(S.current.confirm));
|
||||
await tester.pump();
|
||||
expect(find.text('Жалоба отправлена на рассмотрение. Обычно это занимает до трёх рабочих дней.'), findsOneWidget);
|
||||
expect(find.text(S.current.ok), findsOneWidget);
|
||||
});
|
||||
|
||||
testWidgets('PlanComplainBottomSheet shows failure UI when submit fails', (tester) async {
|
||||
final cubit = FakeChatRoomCubit(const ChatRoomState.loading())..complainResult = false;
|
||||
await tester.pumpWidget(wrap(PlanComplainBottomSheet(cubit: cubit)));
|
||||
await tester.pump();
|
||||
await tester.enterText(find.byType(TextFormField), 'abc');
|
||||
await tester.pump();
|
||||
await tester.tap(find.text(S.current.confirm));
|
||||
await tester.pump();
|
||||
expect(find.text('Произошла ошибка, попробуйте позже'), findsOneWidget);
|
||||
expect(find.text(S.current.ok), findsOneWidget);
|
||||
});
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,219 @@
|
||||
import 'dart:async';
|
||||
import 'dart:io';
|
||||
|
||||
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/presentation/widgets/chat_bubble_widget.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
|
||||
class _FakeHttpClient implements HttpClient {
|
||||
bool _autoUncompress = true;
|
||||
|
||||
@override
|
||||
bool get autoUncompress => _autoUncompress;
|
||||
|
||||
@override
|
||||
set autoUncompress(bool value) {
|
||||
_autoUncompress = value;
|
||||
}
|
||||
|
||||
@override
|
||||
Future<HttpClientRequest> getUrl(Uri url) async => _FakeHttpClientRequest();
|
||||
|
||||
@override
|
||||
dynamic noSuchMethod(Invocation invocation) => super.noSuchMethod(invocation);
|
||||
}
|
||||
|
||||
class _FakeHttpClientRequest implements HttpClientRequest {
|
||||
@override
|
||||
Future<HttpClientResponse> close() async => _FakeHttpClientResponse();
|
||||
|
||||
@override
|
||||
dynamic noSuchMethod(Invocation invocation) => super.noSuchMethod(invocation);
|
||||
}
|
||||
|
||||
class _FakeHttpClientResponse extends Stream<List<int>> implements HttpClientResponse {
|
||||
|
||||
static final List<int> _png = <int>[
|
||||
0x89,
|
||||
0x50,
|
||||
0x4E,
|
||||
0x47,
|
||||
0x0D,
|
||||
0x0A,
|
||||
0x1A,
|
||||
0x0A,
|
||||
0x00,
|
||||
0x00,
|
||||
0x00,
|
||||
0x0D,
|
||||
0x49,
|
||||
0x48,
|
||||
0x44,
|
||||
0x52,
|
||||
0x00,
|
||||
0x00,
|
||||
0x00,
|
||||
0x01,
|
||||
0x00,
|
||||
0x00,
|
||||
0x00,
|
||||
0x01,
|
||||
0x08,
|
||||
0x06,
|
||||
0x00,
|
||||
0x00,
|
||||
0x00,
|
||||
0x1F,
|
||||
0x15,
|
||||
0xC4,
|
||||
0x89,
|
||||
0x00,
|
||||
0x00,
|
||||
0x00,
|
||||
0x0A,
|
||||
0x49,
|
||||
0x44,
|
||||
0x41,
|
||||
0x54,
|
||||
0x78,
|
||||
0x9C,
|
||||
0x63,
|
||||
0x00,
|
||||
0x01,
|
||||
0x00,
|
||||
0x00,
|
||||
0x05,
|
||||
0x00,
|
||||
0x01,
|
||||
0x0D,
|
||||
0x0A,
|
||||
0x2D,
|
||||
0xB4,
|
||||
0x00,
|
||||
0x00,
|
||||
0x00,
|
||||
0x00,
|
||||
0x49,
|
||||
0x45,
|
||||
0x4E,
|
||||
0x44,
|
||||
0xAE,
|
||||
0x42,
|
||||
0x60,
|
||||
0x82,
|
||||
];
|
||||
|
||||
@override
|
||||
int get statusCode => 200;
|
||||
|
||||
@override
|
||||
int get contentLength => _png.length;
|
||||
|
||||
@override
|
||||
HttpClientResponseCompressionState get compressionState => HttpClientResponseCompressionState.notCompressed;
|
||||
|
||||
@override
|
||||
StreamSubscription<List<int>> listen(void Function(List<int>)? onData, {Function? onError, void Function()? onDone, bool? cancelOnError}) {
|
||||
return Stream<List<int>>.fromIterable([_png]).listen(onData, onError: onError, onDone: onDone, cancelOnError: cancelOnError);
|
||||
}
|
||||
|
||||
@override
|
||||
dynamic noSuchMethod(Invocation invocation) => super.noSuchMethod(invocation);
|
||||
}
|
||||
|
||||
class _FakeAssetBundle extends CachingAssetBundle {
|
||||
_FakeAssetBundle(this.assets);
|
||||
final Map<String, String> assets;
|
||||
|
||||
@override
|
||||
Future<String> loadString(String key, {bool cache = true}) async {
|
||||
final value = assets[key];
|
||||
if (value == null) throw FlutterError('Asset not found: $key');
|
||||
return value;
|
||||
}
|
||||
|
||||
@override
|
||||
Future<ByteData> load(String key) async {
|
||||
final value = assets[key];
|
||||
if (value == null) throw FlutterError('Asset not found: $key');
|
||||
return ByteData.sublistView(Uint8List.fromList(value.codeUnits));
|
||||
}
|
||||
}
|
||||
|
||||
void main() {
|
||||
group('ChatBubbleWidget smoke', () {
|
||||
const fakeSvg = '''
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="10" height="10" viewBox="0 0 10 10">
|
||||
<rect width="10" height="10" fill="red"/>
|
||||
</svg>
|
||||
''';
|
||||
|
||||
final bundle = _FakeAssetBundle({'assets/icons/error_icon.svg': fakeSvg});
|
||||
|
||||
Widget wrap(Widget child) {
|
||||
return DefaultAssetBundle(
|
||||
bundle: bundle,
|
||||
child: MaterialApp(
|
||||
theme: ThemeHelper().themeData(),
|
||||
home: MediaQuery(
|
||||
data: const MediaQueryData(size: Size(1200, 800)),
|
||||
child: Scaffold(body: child),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
testWidgets('renders with network image attachment', (tester) async {
|
||||
tester.view.devicePixelRatio = 1.0;
|
||||
tester.view.physicalSize = const Size(2000, 1200);
|
||||
addTearDown(tester.view.reset);
|
||||
|
||||
await HttpOverrides.runZoned(() async {
|
||||
await tester.pumpWidget(
|
||||
wrap(
|
||||
ChatBubbleWidget(
|
||||
sendTime: DateTime(2025, 1, 1, 10, 0),
|
||||
text: 'text',
|
||||
isCurrentUser: true,
|
||||
isRead: true,
|
||||
mediaData: const ['https://example.com/a.png'],
|
||||
messageState: MessageState.sent,
|
||||
messageUuid: 'u',
|
||||
userSlug: 'slug',
|
||||
),
|
||||
),
|
||||
);
|
||||
await tester.pump(const Duration(milliseconds: 200));
|
||||
expect(find.byType(ChatBubbleWidget), findsOneWidget);
|
||||
expect(find.text('text'), findsOneWidget);
|
||||
}, createHttpClient: (_) => _FakeHttpClient());
|
||||
});
|
||||
|
||||
testWidgets('renders error state icon for current user', (tester) async {
|
||||
tester.view.devicePixelRatio = 1.0;
|
||||
tester.view.physicalSize = const Size(2000, 1200);
|
||||
addTearDown(tester.view.reset);
|
||||
|
||||
await tester.pumpWidget(
|
||||
wrap(
|
||||
ChatBubbleWidget(
|
||||
sendTime: DateTime(2025, 1, 1, 10, 0),
|
||||
text: 'tap to retry',
|
||||
isCurrentUser: true,
|
||||
isRead: true,
|
||||
mediaData: const [],
|
||||
messageState: MessageState.error,
|
||||
messageUuid: 'u',
|
||||
userSlug: 'slug',
|
||||
),
|
||||
),
|
||||
);
|
||||
await tester.pump();
|
||||
expect(find.byType(ChatBubbleWidget), findsOneWidget);
|
||||
expect(find.text('tap to retry'), findsOneWidget);
|
||||
});
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,95 @@
|
||||
import 'package:eatvio_app/core/app_config.dart';
|
||||
import 'package:eatvio_app/core/app_initializer.dart';
|
||||
import 'package:eatvio_app/core/locator_service.dart';
|
||||
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/features/chat/presentation/widgets/chat_search_bar_widget.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:go_router/go_router.dart';
|
||||
import 'package:network_image_mock/network_image_mock.dart';
|
||||
|
||||
void main() {
|
||||
group('Chat list widgets smoke', () {
|
||||
setUpAll(() async {
|
||||
dotenv.loadFromString(envString: 'DEV_BASE_URL=http://localhost\nBASE_URL=http://localhost');
|
||||
});
|
||||
|
||||
setUp(() async {
|
||||
await sl.reset();
|
||||
sl.registerLazySingleton<AppConfig>(() => AppConfig(buildType: AppBuildType.tests)..setBaseUrl('http://localhost'));
|
||||
});
|
||||
|
||||
Widget wrap(Widget child) {
|
||||
final router = GoRouter(
|
||||
routes: [
|
||||
GoRoute(
|
||||
path: '/',
|
||||
builder: (_, _) => Scaffold(body: child),
|
||||
),
|
||||
GoRoute(path: '/chat', name: 'chatScreen', builder: (_, _) => const SizedBox.shrink()),
|
||||
],
|
||||
);
|
||||
return MaterialApp.router(
|
||||
theme: ThemeHelper().themeData(),
|
||||
localizationsDelegates: const [
|
||||
S.delegate,
|
||||
GlobalMaterialLocalizations.delegate,
|
||||
GlobalWidgetsLocalizations.delegate,
|
||||
GlobalCupertinoLocalizations.delegate,
|
||||
],
|
||||
supportedLocales: const [Locale('ru')],
|
||||
routerConfig: router,
|
||||
);
|
||||
}
|
||||
|
||||
testWidgets('ChatSearchBarWidget clears text and calls onSuffixIconTap', (tester) async {
|
||||
final controller = TextEditingController(text: 'abc');
|
||||
var called = 0;
|
||||
|
||||
await tester.pumpWidget(wrap(ChatSearchBarWidget(controller: controller, hintText: 'Search', onSuffixIconTap: () => called++)));
|
||||
await tester.pump();
|
||||
|
||||
expect(find.byType(TextFormField), findsOneWidget);
|
||||
await tester.tap(find.byIcon(Icons.clear));
|
||||
await tester.pump();
|
||||
expect(controller.text, '');
|
||||
expect(called, 1);
|
||||
});
|
||||
|
||||
testWidgets('ChatEntryWidget renders unread badge and attachment preview', (tester) async {
|
||||
final chatRoom = ChatRoom(
|
||||
id: '1',
|
||||
lastMessage: ChatMessageModel(
|
||||
id: 1,
|
||||
data: 'ignored',
|
||||
mediaData: const ['chat_files/file.pdf'],
|
||||
authorId: 2,
|
||||
updatedAt: DateTime(2025, 1, 1),
|
||||
isRead: false,
|
||||
isCurrentUserAuthor: false,
|
||||
roomId: '1',
|
||||
),
|
||||
users: const Users(
|
||||
fromChatUser: ChatUser(id: 1, name: 'Me', profilePhotoPath: null, profilePhotoUrl: 'http://localhost/a.png'),
|
||||
toChatUser: ChatUser(id: 2, name: 'Other User', profilePhotoPath: null, profilePhotoUrl: 'http://localhost/b.png'),
|
||||
),
|
||||
unreadMessagesCount: 3,
|
||||
chosenPlan: null,
|
||||
);
|
||||
|
||||
await mockNetworkImagesFor(() async {
|
||||
await tester.pumpWidget(wrap(ChatEntryWidget(chatRoom: chatRoom)));
|
||||
await tester.pump(const Duration(milliseconds: 200));
|
||||
expect(find.text('Other User'), findsOneWidget);
|
||||
expect(find.text('3'), findsOneWidget);
|
||||
expect(find.text('📎 Вложение'), findsOneWidget);
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,105 @@
|
||||
|
||||
import 'package:eatvio_app/core/theme/theme_helper.dart';
|
||||
import 'package:eatvio_app/features/chat/presentation/widgets/about_order_button.dart';
|
||||
import 'package:eatvio_app/features/chat/presentation/widgets/message_action_bottom_sheet.dart';
|
||||
import 'package:eatvio_app/features/chat/presentation/widgets/notification_banner.dart';
|
||||
import 'package:eatvio_app/generated/l10n.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:flutter_localizations/flutter_localizations.dart';
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
|
||||
class _FakeAssetBundle extends CachingAssetBundle {
|
||||
_FakeAssetBundle(this.assets);
|
||||
final Map<String, String> assets;
|
||||
|
||||
@override
|
||||
Future<String> loadString(String key, {bool cache = true}) async {
|
||||
final value = assets[key];
|
||||
if (value == null) throw FlutterError('Asset not found: $key');
|
||||
return value;
|
||||
}
|
||||
|
||||
@override
|
||||
Future<ByteData> load(String key) async {
|
||||
final value = assets[key];
|
||||
if (value == null) throw FlutterError('Asset not found: $key');
|
||||
return ByteData.sublistView(Uint8List.fromList(value.codeUnits));
|
||||
}
|
||||
}
|
||||
|
||||
void main() {
|
||||
group('Chat misc widgets smoke', () {
|
||||
const fakeSvg = '''
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="10" height="10" viewBox="0 0 10 10">
|
||||
<rect width="10" height="10" fill="red"/>
|
||||
</svg>
|
||||
''';
|
||||
|
||||
final bundle = _FakeAssetBundle({
|
||||
'assets/icons/right_arrow.svg': fakeSvg,
|
||||
'assets/icons/resend.svg': fakeSvg,
|
||||
'assets/icons/trash_can.svg': fakeSvg,
|
||||
'assets/icons/reversed_exclamation_mark.svg': fakeSvg,
|
||||
'assets/icons/star.svg': fakeSvg,
|
||||
});
|
||||
|
||||
Widget wrap(Widget child) {
|
||||
return DefaultAssetBundle(
|
||||
bundle: bundle,
|
||||
child: MaterialApp(
|
||||
theme: ThemeHelper().themeData(),
|
||||
localizationsDelegates: const [
|
||||
S.delegate,
|
||||
GlobalMaterialLocalizations.delegate,
|
||||
GlobalWidgetsLocalizations.delegate,
|
||||
GlobalCupertinoLocalizations.delegate,
|
||||
],
|
||||
supportedLocales: const [Locale('ru')],
|
||||
home: MediaQuery(
|
||||
data: const MediaQueryData(size: Size(500, 900)),
|
||||
child: Scaffold(body: child),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
testWidgets('AboutOrderButton renders and taps', (tester) async {
|
||||
var tapped = false;
|
||||
await tester.pumpWidget(wrap(AboutOrderButton(onTap: () => tapped = true)));
|
||||
await tester.pump();
|
||||
expect(find.byType(AboutOrderButton), findsOneWidget);
|
||||
await tester.tap(find.byType(AboutOrderButton));
|
||||
await tester.pump();
|
||||
expect(tapped, isTrue);
|
||||
});
|
||||
|
||||
testWidgets('MessageActionsBottomSheet renders', (tester) async {
|
||||
await tester.pumpWidget(wrap(MessageActionsBottomSheet(resend: () {}, delete: () {})));
|
||||
await tester.pump();
|
||||
expect(find.byType(MessageActionsBottomSheet), findsOneWidget);
|
||||
expect(find.text(S.current.resend), findsOneWidget);
|
||||
expect(find.text(S.current.delete), findsOneWidget);
|
||||
});
|
||||
|
||||
testWidgets('NotificationBanner renders for multiple banner types', (tester) async {
|
||||
await tester.pumpWidget(
|
||||
wrap(
|
||||
const SingleChildScrollView(
|
||||
child: Column(
|
||||
children: [
|
||||
NotificationBanner(bannerType: BannerType.date, hasPersonalDiet: false),
|
||||
NotificationBanner(bannerType: BannerType.card),
|
||||
NotificationBanner(bannerType: BannerType.collectCharge, planName: 'Plan', planPrice: 100, chosenPlanId: 1),
|
||||
NotificationBanner(bannerType: BannerType.finishPlan, planName: 'Plan', planPrice: 100, chosenPlanId: 1),
|
||||
NotificationBanner(bannerType: BannerType.setPersonalDiet, hasPersonalDiet: true),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
await tester.pump();
|
||||
expect(find.byType(NotificationBanner), findsWidgets);
|
||||
});
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,204 @@
|
||||
import 'dart:async';
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:eatvio_app/core/app_config.dart';
|
||||
import 'package:eatvio_app/core/app_initializer.dart';
|
||||
import 'package:eatvio_app/core/locator_service.dart';
|
||||
import 'package:eatvio_app/core/theme/theme_helper.dart';
|
||||
import 'package:eatvio_app/features/chat/presentation/widgets/order_card.dart';
|
||||
import 'package:eatvio_app/features/feed/models/diets_pagination_model.dart';
|
||||
import 'package:eatvio_app/features/feed/models/plan_pagination_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';
|
||||
|
||||
class _FakeHttpClient implements HttpClient {
|
||||
bool _autoUncompress = true;
|
||||
|
||||
@override
|
||||
bool get autoUncompress => _autoUncompress;
|
||||
|
||||
@override
|
||||
set autoUncompress(bool value) {
|
||||
_autoUncompress = value;
|
||||
}
|
||||
|
||||
@override
|
||||
Future<HttpClientRequest> getUrl(Uri url) async => _FakeHttpClientRequest();
|
||||
|
||||
@override
|
||||
dynamic noSuchMethod(Invocation invocation) => super.noSuchMethod(invocation);
|
||||
}
|
||||
|
||||
class _FakeHttpClientRequest implements HttpClientRequest {
|
||||
@override
|
||||
Future<HttpClientResponse> close() async => _FakeHttpClientResponse();
|
||||
|
||||
@override
|
||||
dynamic noSuchMethod(Invocation invocation) => super.noSuchMethod(invocation);
|
||||
}
|
||||
|
||||
class _FakeHttpClientResponse extends Stream<List<int>> implements HttpClientResponse {
|
||||
static final List<int> _png = <int>[
|
||||
0x89,
|
||||
0x50,
|
||||
0x4E,
|
||||
0x47,
|
||||
0x0D,
|
||||
0x0A,
|
||||
0x1A,
|
||||
0x0A,
|
||||
0x00,
|
||||
0x00,
|
||||
0x00,
|
||||
0x0D,
|
||||
0x49,
|
||||
0x48,
|
||||
0x44,
|
||||
0x52,
|
||||
0x00,
|
||||
0x00,
|
||||
0x00,
|
||||
0x01,
|
||||
0x00,
|
||||
0x00,
|
||||
0x00,
|
||||
0x01,
|
||||
0x08,
|
||||
0x06,
|
||||
0x00,
|
||||
0x00,
|
||||
0x00,
|
||||
0x1F,
|
||||
0x15,
|
||||
0xC4,
|
||||
0x89,
|
||||
0x00,
|
||||
0x00,
|
||||
0x00,
|
||||
0x0A,
|
||||
0x49,
|
||||
0x44,
|
||||
0x41,
|
||||
0x54,
|
||||
0x78,
|
||||
0x9C,
|
||||
0x63,
|
||||
0x00,
|
||||
0x01,
|
||||
0x00,
|
||||
0x00,
|
||||
0x05,
|
||||
0x00,
|
||||
0x01,
|
||||
0x0D,
|
||||
0x0A,
|
||||
0x2D,
|
||||
0xB4,
|
||||
0x00,
|
||||
0x00,
|
||||
0x00,
|
||||
0x00,
|
||||
0x49,
|
||||
0x45,
|
||||
0x4E,
|
||||
0x44,
|
||||
0xAE,
|
||||
0x42,
|
||||
0x60,
|
||||
0x82,
|
||||
];
|
||||
|
||||
@override
|
||||
int get statusCode => 200;
|
||||
|
||||
@override
|
||||
int get contentLength => _png.length;
|
||||
|
||||
@override
|
||||
HttpClientResponseCompressionState get compressionState => HttpClientResponseCompressionState.notCompressed;
|
||||
|
||||
@override
|
||||
StreamSubscription<List<int>> listen(void Function(List<int>)? onData, {Function? onError, void Function()? onDone, bool? cancelOnError}) {
|
||||
return Stream<List<int>>.fromIterable([_png]).listen(onData, onError: onError, onDone: onDone, cancelOnError: cancelOnError);
|
||||
}
|
||||
|
||||
@override
|
||||
dynamic noSuchMethod(Invocation invocation) => super.noSuchMethod(invocation);
|
||||
}
|
||||
|
||||
void main() {
|
||||
group('OrderCard smoke', () {
|
||||
setUpAll(() async {
|
||||
dotenv.loadFromString(envString: 'DEV_BASE_URL=http://localhost\nBASE_URL=http://localhost');
|
||||
});
|
||||
|
||||
setUp(() async {
|
||||
await sl.reset();
|
||||
sl.registerLazySingleton<AppConfig>(() => AppConfig(buildType: AppBuildType.tests)..setBaseUrl('http://localhost'));
|
||||
});
|
||||
|
||||
Widget wrap(Widget child) {
|
||||
return MaterialApp(
|
||||
theme: ThemeHelper().themeData(),
|
||||
localizationsDelegates: const [
|
||||
S.delegate,
|
||||
GlobalMaterialLocalizations.delegate,
|
||||
GlobalWidgetsLocalizations.delegate,
|
||||
GlobalCupertinoLocalizations.delegate,
|
||||
],
|
||||
supportedLocales: const [Locale('ru')],
|
||||
home: Scaffold(body: SingleChildScrollView(child: child)),
|
||||
);
|
||||
}
|
||||
|
||||
const author = Author(
|
||||
authorSlug: 'a',
|
||||
authorName: 'Author',
|
||||
authorProfileIcon: 'http://localhost/a.png',
|
||||
authorIsMe: false,
|
||||
isEntitySubscribedByMe: false,
|
||||
authorPlaceInRating: 1,
|
||||
);
|
||||
|
||||
const plan = PlanModel(
|
||||
id: 1,
|
||||
slug: 'slug',
|
||||
name: 'Plan',
|
||||
image: 'img.png',
|
||||
duration: 7,
|
||||
state: 'active',
|
||||
type: PlanType(fit: false, slim: false, behavior: false, wellness: false),
|
||||
details: '',
|
||||
packetsCount: 1,
|
||||
packet1Options: PacketOptions(items: ['a'], price: 100, short: 'S', description: '', hasPersonalDiet: false),
|
||||
packet2Options: null,
|
||||
packet3Options: null,
|
||||
author: author,
|
||||
totalLikes: 0,
|
||||
totalViews: 0,
|
||||
totalComments: 0,
|
||||
isFavorite: false,
|
||||
minPriceOfOptions: 100,
|
||||
maxPriceOfOptions: 100,
|
||||
wasLikedByMe: false,
|
||||
);
|
||||
|
||||
testWidgets('renders and runs cancel action', (tester) async {
|
||||
var cancelled = 0;
|
||||
await HttpOverrides.runZoned(() async {
|
||||
await tester.pumpWidget(wrap(OrderCard(plan: plan, isCustomerView: true, optionType: 'options1', onCancel: () async => cancelled++)));
|
||||
await tester.pump(const Duration(milliseconds: 200));
|
||||
expect(find.text('Plan'), findsOneWidget);
|
||||
expect(find.text(S.current.cancel_order), findsOneWidget);
|
||||
|
||||
await tester.tap(find.text(S.current.cancel_order));
|
||||
await tester.pump();
|
||||
await tester.pump(const Duration(milliseconds: 50));
|
||||
expect(cancelled, 1);
|
||||
}, createHttpClient: (_) => _FakeHttpClient());
|
||||
});
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,66 @@
|
||||
import 'package:eatvio_app/core/app_config.dart';
|
||||
import 'package:eatvio_app/core/app_initializer.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/complaining/presentation/complaining_bottom_sheet.dart';
|
||||
import 'package:eatvio_app/features/complaining/presentation/cubit/complaining_cubit.dart';
|
||||
import 'package:eatvio_app/features/complaining/repositories/complaining_repository.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:mockito/annotations.dart';
|
||||
|
||||
import 'complaining_bottom_sheet_smoke_test.mocks.dart';
|
||||
|
||||
@GenerateNiceMocks([MockSpec<ComplainingRepository>()])
|
||||
void main() {
|
||||
group('ComplainingBottomSheet smoke', () {
|
||||
late MockComplainingRepository repository;
|
||||
|
||||
setUpAll(() async {
|
||||
dotenv.loadFromString(envString: 'DEV_BASE_URL=http://localhost\nBASE_URL=http://localhost');
|
||||
});
|
||||
|
||||
setUp(() async {
|
||||
await sl.reset();
|
||||
sl.registerLazySingleton<AppConfig>(() => AppConfig(buildType: AppBuildType.tests)..setBaseUrl('http://localhost'));
|
||||
|
||||
repository = MockComplainingRepository();
|
||||
sl.registerLazySingleton<ComplainingRepository>(() => repository);
|
||||
sl.registerFactory<ComplainingCubit>(() => ComplainingCubit(repository: sl()));
|
||||
});
|
||||
|
||||
Widget wrap(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),
|
||||
);
|
||||
}
|
||||
|
||||
testWidgets('renders category selection', (tester) async {
|
||||
await tester.pumpWidget(
|
||||
wrap(
|
||||
const ComplainingBottomSheet(
|
||||
complainedContentType: ComplainedContent.plan,
|
||||
slug: 'test-slug',
|
||||
),
|
||||
),
|
||||
);
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
expect(find.byType(ComplainingBottomSheet), findsOneWidget);
|
||||
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
@@ -0,0 +1,86 @@
|
||||
import 'package:eatvio_app/core/app_config.dart';
|
||||
import 'package:eatvio_app/core/app_initializer.dart';
|
||||
import 'package:eatvio_app/core/locator_service.dart';
|
||||
import 'package:eatvio_app/core/theme/theme_helper.dart';
|
||||
import 'package:eatvio_app/features/consultant/become_consultant/models/consultant_profile_model.dart';
|
||||
import 'package:eatvio_app/features/consultant/become_consultant/presentation/bloc/register_consultant_cubit/register_consultant_cubit.dart';
|
||||
import 'package:eatvio_app/features/consultant/become_consultant/presentation/bloc/register_consultant_cubit/register_consultant_state.dart';
|
||||
import 'package:eatvio_app/features/consultant/become_consultant/presentation/pages/certificates_and_diplomas_screen.dart';
|
||||
import 'package:eatvio_app/generated/l10n.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:flutter_dotenv/flutter_dotenv.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 'certificates_and_diplomas_screen_smoke_test.mocks.dart';
|
||||
|
||||
@GenerateNiceMocks([MockSpec<RegisterConsultantCubit>()])
|
||||
void main() {
|
||||
group('CertificatesAndDiplomasScreen smoke', () {
|
||||
setUpAll(() async {
|
||||
dotenv.loadFromString(envString: 'DEV_BASE_URL=http://localhost\nBASE_URL=http://localhost');
|
||||
});
|
||||
|
||||
setUp(() async {
|
||||
await sl.reset();
|
||||
sl.registerLazySingleton<AppConfig>(() => AppConfig(buildType: AppBuildType.tests)..setBaseUrl('http://localhost'));
|
||||
});
|
||||
|
||||
Widget wrap(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),
|
||||
);
|
||||
}
|
||||
|
||||
testWidgets('renders certificates and diplomas screen', (tester) async {
|
||||
final cubit = MockRegisterConsultantCubit();
|
||||
const consultant = ConsultantProfileModel(hasMedicalEducation: null, socialLinks: null, photos: null, certificates: null);
|
||||
const state = RegisterConsultantState(consultant: consultant, acceptOffer: false, loading: false);
|
||||
|
||||
when(cubit.state).thenReturn(state);
|
||||
when(cubit.stream).thenAnswer((_) => Stream.value(state));
|
||||
when(cubit.close()).thenAnswer((_) async => Future<void>.value());
|
||||
when(cubit.updateConsultantProfileModel(any)).thenReturn(null);
|
||||
|
||||
await tester.pumpWidget(BlocProvider<RegisterConsultantCubit>.value(value: cubit, child: wrap(const CertificatesAndDiplomasScreen())));
|
||||
await tester.pump();
|
||||
await tester.pump(const Duration(milliseconds: 100));
|
||||
|
||||
expect(find.byType(CertificatesAndDiplomasScreen), findsOneWidget);
|
||||
expect(find.text('Добавьте дипломы\nи сертификаты'), findsOneWidget);
|
||||
});
|
||||
|
||||
testWidgets('renders with existing certificates', (tester) async {
|
||||
final cubit = MockRegisterConsultantCubit();
|
||||
const consultant = ConsultantProfileModel(
|
||||
hasMedicalEducation: null,
|
||||
socialLinks: null,
|
||||
photos: null,
|
||||
certificates: [CertificatesModel(title: 'Test Certificate', date: '2025-01-01')],
|
||||
);
|
||||
const state = RegisterConsultantState(consultant: consultant, acceptOffer: false, loading: false);
|
||||
|
||||
when(cubit.state).thenReturn(state);
|
||||
when(cubit.stream).thenAnswer((_) => Stream.value(state));
|
||||
when(cubit.close()).thenAnswer((_) async => Future<void>.value());
|
||||
when(cubit.updateConsultantProfileModel(any)).thenReturn(null);
|
||||
|
||||
await tester.pumpWidget(BlocProvider<RegisterConsultantCubit>.value(value: cubit, child: wrap(const CertificatesAndDiplomasScreen())));
|
||||
await tester.pump();
|
||||
await tester.pump(const Duration(milliseconds: 100));
|
||||
|
||||
expect(find.byType(CertificatesAndDiplomasScreen), findsOneWidget);
|
||||
});
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,90 @@
|
||||
import 'package:eatvio_app/core/app_config.dart';
|
||||
import 'package:eatvio_app/core/app_initializer.dart';
|
||||
import 'package:eatvio_app/core/locator_service.dart';
|
||||
import 'package:eatvio_app/core/theme/theme_helper.dart';
|
||||
import 'package:eatvio_app/features/consultant/become_consultant/models/consultant_profile_model.dart';
|
||||
import 'package:eatvio_app/features/consultant/become_consultant/presentation/bloc/register_consultant_cubit/register_consultant_cubit.dart';
|
||||
import 'package:eatvio_app/features/consultant/become_consultant/presentation/bloc/register_consultant_cubit/register_consultant_state.dart';
|
||||
import 'package:eatvio_app/features/consultant/become_consultant/presentation/pages/social_networks_screen.dart';
|
||||
import 'package:eatvio_app/generated/l10n.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:flutter_dotenv/flutter_dotenv.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 'social_networks_screen_smoke_test.mocks.dart';
|
||||
|
||||
@GenerateNiceMocks([MockSpec<RegisterConsultantCubit>()])
|
||||
void main() {
|
||||
group('SocialNetworksScreen smoke', () {
|
||||
setUpAll(() async {
|
||||
dotenv.loadFromString(envString: 'DEV_BASE_URL=http://localhost\nBASE_URL=http://localhost');
|
||||
});
|
||||
|
||||
setUp(() async {
|
||||
await sl.reset();
|
||||
sl.registerLazySingleton<AppConfig>(() => AppConfig(buildType: AppBuildType.tests)..setBaseUrl('http://localhost'));
|
||||
});
|
||||
|
||||
Widget wrap(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),
|
||||
);
|
||||
}
|
||||
|
||||
testWidgets('renders social networks screen', (tester) async {
|
||||
final cubit = MockRegisterConsultantCubit();
|
||||
const consultant = ConsultantProfileModel(
|
||||
hasMedicalEducation: null,
|
||||
socialLinks: SocialLinksModel(vkLink: null, tgLink: null, fbLink: null, instaLink: null, personalSiteLink: null),
|
||||
photos: null,
|
||||
certificates: null,
|
||||
);
|
||||
const state = RegisterConsultantState(consultant: consultant, acceptOffer: false, loading: false);
|
||||
|
||||
when(cubit.state).thenReturn(state);
|
||||
when(cubit.stream).thenAnswer((_) => Stream.value(state));
|
||||
when(cubit.close()).thenAnswer((_) async => Future<void>.value());
|
||||
when(cubit.updateConsultantProfileModel(any)).thenReturn(null);
|
||||
|
||||
await tester.pumpWidget(BlocProvider<RegisterConsultantCubit>.value(value: cubit, child: wrap(const SocialNetworksScreen())));
|
||||
await tester.pump();
|
||||
await tester.pump(const Duration(milliseconds: 100));
|
||||
|
||||
expect(find.byType(SocialNetworksScreen), findsOneWidget);
|
||||
});
|
||||
|
||||
testWidgets('renders with existing social links', (tester) async {
|
||||
final cubit = MockRegisterConsultantCubit();
|
||||
const consultant = ConsultantProfileModel(
|
||||
hasMedicalEducation: null,
|
||||
socialLinks: SocialLinksModel(vkLink: 'https://vk.com/test', tgLink: 'https://t.me/test', fbLink: null, instaLink: null, personalSiteLink: null),
|
||||
photos: null,
|
||||
certificates: null,
|
||||
);
|
||||
const state = RegisterConsultantState(consultant: consultant, acceptOffer: false, loading: false);
|
||||
|
||||
when(cubit.state).thenReturn(state);
|
||||
when(cubit.stream).thenAnswer((_) => Stream.value(state));
|
||||
when(cubit.close()).thenAnswer((_) async => Future<void>.value());
|
||||
when(cubit.updateConsultantProfileModel(any)).thenReturn(null);
|
||||
|
||||
await tester.pumpWidget(BlocProvider<RegisterConsultantCubit>.value(value: cubit, child: wrap(const SocialNetworksScreen())));
|
||||
await tester.pump();
|
||||
await tester.pump(const Duration(milliseconds: 100));
|
||||
|
||||
expect(find.byType(SocialNetworksScreen), findsOneWidget);
|
||||
});
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,103 @@
|
||||
import 'package:eatvio_app/core/app_config.dart';
|
||||
import 'package:eatvio_app/core/app_initializer.dart';
|
||||
import 'package:eatvio_app/core/locator_service.dart';
|
||||
import 'package:eatvio_app/core/theme/theme_helper.dart';
|
||||
import 'package:eatvio_app/features/consultations/models/consultation_model.dart';
|
||||
import 'package:eatvio_app/features/consultations/presentation/widgets/consultation_card_widget.dart';
|
||||
import 'package:eatvio_app/features/plans/enums/chosen_plan_state_enum.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:go_router/go_router.dart';
|
||||
import 'package:network_image_mock/network_image_mock.dart';
|
||||
|
||||
void main() {
|
||||
group('ConsultationCardWidget smoke', () {
|
||||
setUpAll(() async {
|
||||
dotenv.loadFromString(envString: 'DEV_BASE_URL=http://localhost\nBASE_URL=http://localhost');
|
||||
});
|
||||
|
||||
setUp(() async {
|
||||
await sl.reset();
|
||||
sl.registerLazySingleton<AppConfig>(() => AppConfig(buildType: AppBuildType.tests)..setBaseUrl('http://localhost'));
|
||||
});
|
||||
|
||||
final mockConsultation = ConsultationModel(
|
||||
id: 1,
|
||||
chosenPlanState: ChosenPlanStateEnum.stateInProgress,
|
||||
planTitle: 'Test Plan',
|
||||
userName: 'John Doe',
|
||||
userSlug: 'john-doe',
|
||||
consultantPayment: 1000.0,
|
||||
duration: 7,
|
||||
currentDiaryDay: 3,
|
||||
userProfilePhotoUrl: 'https://example.com/avatar.jpg',
|
||||
consultationOptionPrice: 500,
|
||||
startDate: DateTime(2025, 1, 1),
|
||||
endDate: DateTime(2025, 1, 8),
|
||||
consultantId: 1,
|
||||
diaries: [
|
||||
DiaryModel(id: 1, state: DiaryStateEnum.finished, date: DateTime(2025, 1, 1)),
|
||||
DiaryModel(id: 2, state: DiaryStateEnum.commented, date: DateTime(2025, 1, 2)),
|
||||
DiaryModel(id: 3, state: DiaryStateEnum.init, date: DateTime(2025, 1, 3)),
|
||||
],
|
||||
personalDietId: null,
|
||||
);
|
||||
|
||||
Widget wrap(Widget child) {
|
||||
return MaterialApp.router(
|
||||
theme: ThemeHelper().themeData(),
|
||||
localizationsDelegates: const [
|
||||
S.delegate,
|
||||
GlobalMaterialLocalizations.delegate,
|
||||
GlobalWidgetsLocalizations.delegate,
|
||||
GlobalCupertinoLocalizations.delegate,
|
||||
],
|
||||
supportedLocales: const [Locale('ru')],
|
||||
routerConfig: GoRouter(
|
||||
initialLocation: '/',
|
||||
routes: [
|
||||
GoRoute(path: '/', builder: (context, state) => child),
|
||||
GoRoute(
|
||||
path: '/profile-screen',
|
||||
name: 'profileScreen',
|
||||
builder: (context, state) => const Scaffold(body: Text('Profile')),
|
||||
),
|
||||
GoRoute(
|
||||
path: '/user-details-screen',
|
||||
name: 'userDetailsScreen',
|
||||
builder: (context, state) => const Scaffold(body: Text('User Details')),
|
||||
),
|
||||
GoRoute(
|
||||
path: '/diary-screen',
|
||||
name: 'diaryScreen',
|
||||
builder: (context, state) => const Scaffold(body: Text('Diary')),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
testWidgets('renders consultation card with user info', (tester) async {
|
||||
await mockNetworkImagesFor(() async {
|
||||
await tester.pumpWidget(wrap(ConsultationCardWidget(consultationsModel: mockConsultation)));
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
expect(find.text('John Doe'), findsOneWidget);
|
||||
expect(find.text('Test Plan'), findsOneWidget);
|
||||
});
|
||||
});
|
||||
|
||||
testWidgets('renders diary days', (tester) async {
|
||||
await mockNetworkImagesFor(() async {
|
||||
await tester.pumpWidget(wrap(ConsultationCardWidget(consultationsModel: mockConsultation)));
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
|
||||
expect(find.byType(InkWell), findsWidgets);
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,135 @@
|
||||
import 'package:eatvio_app/features/create_diet/presentation/cubit/create_diet_cubit.dart';
|
||||
import 'package:eatvio_app/features/create_diet/repositories/create_diet_repository.dart';
|
||||
import 'package:eatvio_app/features/feed/models/diets_pagination_model.dart';
|
||||
import 'package:eatvio_app/features/glossary/repositories/glossary_repository.dart';
|
||||
import 'package:eatvio_app/features/profile/repositories/profile_repository.dart';
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:mockito/annotations.dart';
|
||||
|
||||
import 'create_diet_cubit_test.mocks.dart';
|
||||
|
||||
@GenerateNiceMocks([MockSpec<CreateDietRepository>(), MockSpec<ProfileRepository>(), MockSpec<GlossaryRepository>()])
|
||||
void main() {
|
||||
group('CreateDietCubit unit', () {
|
||||
late CreateDietCubit cubit;
|
||||
late MockCreateDietRepository dietRepository;
|
||||
late MockProfileRepository profileRepository;
|
||||
late MockGlossaryRepository glossaryRepository;
|
||||
|
||||
setUp(() {
|
||||
dietRepository = MockCreateDietRepository();
|
||||
profileRepository = MockProfileRepository();
|
||||
glossaryRepository = MockGlossaryRepository();
|
||||
cubit = CreateDietCubit(createDietRepository: dietRepository, profileRepository: profileRepository, glossaryRepository: glossaryRepository);
|
||||
});
|
||||
|
||||
tearDown(() {
|
||||
cubit.close();
|
||||
});
|
||||
|
||||
test('initial state is correct', () {
|
||||
expect(cubit.state.isLoading, false);
|
||||
expect(cubit.state.isDietBeingEdited, false);
|
||||
expect(cubit.state.dietData.name, null);
|
||||
expect(cubit.state.dietData.dailySchedule, null);
|
||||
});
|
||||
|
||||
test('setName updates name and clears error', () {
|
||||
cubit.setName('Test Diet');
|
||||
expect(cubit.state.dietData.name, 'Test Diet');
|
||||
expect(cubit.state.dietNameError, null);
|
||||
});
|
||||
|
||||
test('setDetails updates details and clears error', () {
|
||||
cubit.setDetails('Test description');
|
||||
expect(cubit.state.dietData.details, 'Test description');
|
||||
expect(cubit.state.dietDescriptionError, null);
|
||||
});
|
||||
|
||||
test('setDuration updates duration and creates daily schedule', () {
|
||||
cubit.setDuration(0);
|
||||
expect(cubit.state.durationIndex, 0);
|
||||
expect(cubit.state.dietData.duration, 7);
|
||||
expect(cubit.state.dietData.dailySchedule?.length, 7);
|
||||
expect(cubit.state.dietDurationError, null);
|
||||
});
|
||||
|
||||
test('setDuration adjusts daily schedule when changing duration', () {
|
||||
cubit.setDuration(0); // 7 days
|
||||
expect(cubit.state.dietData.dailySchedule?.length, 7);
|
||||
|
||||
cubit.setDuration(1); // 14 days
|
||||
expect(cubit.state.dietData.dailySchedule?.length, 14);
|
||||
|
||||
cubit.setDuration(0); // back to 7 days
|
||||
expect(cubit.state.dietData.dailySchedule?.length, 7);
|
||||
});
|
||||
|
||||
test('setDietState updates state and clears error', () {
|
||||
cubit.setDietState(DietState.active);
|
||||
expect(cubit.state.dietState, DietState.active);
|
||||
expect(cubit.state.dietData.state, DietState.active);
|
||||
expect(cubit.state.dietStateError, null);
|
||||
});
|
||||
|
||||
test('setDietType updates type', () {
|
||||
cubit.setDietType(key: 'veg', value: true);
|
||||
expect(cubit.state.dietData.type['veg'], true);
|
||||
|
||||
cubit.setDietType(key: 'veg', value: false);
|
||||
expect(cubit.state.dietData.type['veg'], false);
|
||||
});
|
||||
|
||||
test('addPortion adds portion to daily schedule', () {
|
||||
cubit.setDuration(0); // Create 7 days schedule
|
||||
const portion = DailySchedule(name: 'Apple', portion: Portion.grams, quantity: 100, dailyNutrition: null, productNutritionalValueId: null);
|
||||
|
||||
cubit.addPortion(dayIndex: 0, mealIndex: 0, portion: portion);
|
||||
expect(cubit.state.dietData.dailySchedule?[0][0].length, 1);
|
||||
expect(cubit.state.dietData.dailySchedule?[0][0][0].name, 'Apple');
|
||||
});
|
||||
|
||||
test('removePortion removes portion from daily schedule', () {
|
||||
cubit.setDuration(0);
|
||||
const portion = DailySchedule(name: 'Apple', portion: Portion.grams, quantity: 100, dailyNutrition: null, productNutritionalValueId: null);
|
||||
|
||||
cubit.addPortion(dayIndex: 0, mealIndex: 0, portion: portion);
|
||||
expect(cubit.state.dietData.dailySchedule?[0][0].length, 1);
|
||||
|
||||
cubit.removePortion(day: 0, meal: 0, portionIndex: 0);
|
||||
expect(cubit.state.dietData.dailySchedule?[0][0].length, 0);
|
||||
});
|
||||
|
||||
test('updatePortion updates portion in daily schedule', () {
|
||||
cubit.setDuration(0);
|
||||
const portion1 = DailySchedule(name: 'Apple', portion: Portion.grams, quantity: 100, dailyNutrition: null, productNutritionalValueId: null);
|
||||
const portion2 = DailySchedule(name: 'Banana', portion: Portion.grams, quantity: 150, dailyNutrition: null, productNutritionalValueId: null);
|
||||
|
||||
cubit.addPortion(dayIndex: 0, mealIndex: 0, portion: portion1);
|
||||
expect(cubit.state.dietData.dailySchedule?[0][0][0].name, 'Apple');
|
||||
|
||||
cubit.updatePortion(dayIndex: 0, mealIndex: 0, portionIndex: 0, portion: portion2);
|
||||
expect(cubit.state.dietData.dailySchedule?[0][0][0].name, 'Banana');
|
||||
});
|
||||
|
||||
test('copyDay and pasteCopiedDay copy and paste day', () {
|
||||
cubit.setDuration(0);
|
||||
const portion = DailySchedule(name: 'Apple', portion: Portion.grams, quantity: 100, dailyNutrition: null, productNutritionalValueId: null);
|
||||
|
||||
cubit.addPortion(dayIndex: 0, mealIndex: 0, portion: portion);
|
||||
cubit.copyDay(0);
|
||||
expect(cubit.state.copiedDay, isNotNull);
|
||||
|
||||
cubit.pasteCopiedDay(1);
|
||||
expect(cubit.state.dietData.dailySchedule?[1][0].length, 1);
|
||||
expect(cubit.state.dietData.dailySchedule?[1][0][0].name, 'Apple');
|
||||
});
|
||||
|
||||
test('setDailySchedule updates daily schedule', () {
|
||||
final schedule = List.generate(7, (day) => List.generate(5, (meal) => <DailySchedule>[]));
|
||||
cubit.setDailySchedule(schedule);
|
||||
expect(cubit.state.dietData.dailySchedule, schedule);
|
||||
expect(cubit.state.dietDurationError, null);
|
||||
});
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,151 @@
|
||||
import 'package:eatvio_app/features/create_plan/presentation/cubit/create_plan_cubit.dart';
|
||||
import 'package:eatvio_app/features/create_plan/repositories/create_plan_repository.dart';
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:mockito/annotations.dart';
|
||||
|
||||
import 'create_plan_cubit_test.mocks.dart';
|
||||
|
||||
@GenerateNiceMocks([MockSpec<CreatePlanRepository>()])
|
||||
void main() {
|
||||
group('CreatePlanCubit unit', () {
|
||||
late CreatePlanCubit cubit;
|
||||
late MockCreatePlanRepository repository;
|
||||
|
||||
setUp(() {
|
||||
repository = MockCreatePlanRepository();
|
||||
cubit = CreatePlanCubit(createPlanRepository: repository);
|
||||
});
|
||||
|
||||
tearDown(() {
|
||||
cubit.close();
|
||||
});
|
||||
|
||||
test('initial state is correct', () {
|
||||
expect(cubit.state.isLoading, false);
|
||||
expect(cubit.state.planCreated, false);
|
||||
expect(cubit.state.errorMessage, null);
|
||||
expect(cubit.state.planData.name, null);
|
||||
expect(cubit.state.planData.options.length, 3);
|
||||
});
|
||||
|
||||
test('setName updates name and validates', () {
|
||||
cubit.setName('Test Plan');
|
||||
expect(cubit.state.planData.name, 'Test Plan');
|
||||
expect(cubit.state.validationState.titleHasError, false);
|
||||
|
||||
cubit.setName('AB');
|
||||
expect(cubit.state.validationState.titleHasError, true);
|
||||
});
|
||||
|
||||
test('setDetails updates details and validates', () {
|
||||
cubit.setDetails('Test description');
|
||||
expect(cubit.state.planData.details, 'Test description');
|
||||
expect(cubit.state.validationState.descriptionHasError, false);
|
||||
|
||||
cubit.setDetails('AB');
|
||||
expect(cubit.state.validationState.descriptionHasError, true);
|
||||
});
|
||||
|
||||
test('setDuration updates duration and validates', () {
|
||||
cubit.setDuration(0);
|
||||
expect(cubit.state.durationIndex, 0);
|
||||
expect(cubit.state.planData.duration, 7);
|
||||
expect(cubit.state.validationState.durationHasError, false);
|
||||
});
|
||||
|
||||
test('setPlanType updates type and validates', () {
|
||||
cubit.setPlanType([true, false, false, false]);
|
||||
expect(cubit.state.planData.type, [true, false, false, false]);
|
||||
expect(cubit.state.validationState.typeHasError, false);
|
||||
|
||||
cubit.setPlanType([false, false, false, false]);
|
||||
expect(cubit.state.planData.type, [false, false, false, false]);
|
||||
});
|
||||
|
||||
test('setSelectedOptionIndex updates selected index', () {
|
||||
cubit.setSelectedOptionIndex(1);
|
||||
expect(cubit.state.selectedOptionIndex, 1);
|
||||
|
||||
cubit.setSelectedOptionIndex(null);
|
||||
expect(cubit.state.selectedOptionIndex, null);
|
||||
});
|
||||
|
||||
test('setOptionTitle updates option title and validates', () {
|
||||
cubit.setSelectedOptionIndex(0);
|
||||
cubit.setOptionTitle('Option 1');
|
||||
expect(cubit.state.planData.options[0].short, 'Option 1');
|
||||
});
|
||||
|
||||
test('setPrice updates price and validates', () {
|
||||
cubit.setSelectedOptionIndex(0);
|
||||
cubit.setPrice(1500.0);
|
||||
expect(cubit.state.planData.options[0].price, 1500.0);
|
||||
expect(cubit.state.validationState.optionsValidationState[0].priceHasError, false);
|
||||
|
||||
cubit.setPrice(1000.0);
|
||||
expect(cubit.state.validationState.optionsValidationState[0].priceHasError, true);
|
||||
});
|
||||
|
||||
test('setOptionDescription updates description and validates', () {
|
||||
cubit.setSelectedOptionIndex(0);
|
||||
cubit.setOptionDescription('Test description');
|
||||
expect(cubit.state.planData.options[0].description, 'Test description');
|
||||
});
|
||||
|
||||
test('setOptionContent adds and updates items', () {
|
||||
cubit.setSelectedOptionIndex(0);
|
||||
cubit.setOptionContent('Item 1', 0);
|
||||
expect(cubit.state.planData.options[0].items.length, 1);
|
||||
expect(cubit.state.planData.options[0].items[0], 'Item 1');
|
||||
|
||||
cubit.setOptionContent('Item 1 Updated', 0);
|
||||
expect(cubit.state.planData.options[0].items[0], 'Item 1 Updated');
|
||||
});
|
||||
|
||||
test('removeOptionContentItem removes item', () {
|
||||
cubit.setSelectedOptionIndex(0);
|
||||
cubit.setOptionContent('Item 1', 0);
|
||||
cubit.setOptionContent('Item 2', 1);
|
||||
expect(cubit.state.planData.options[0].items.length, 2);
|
||||
|
||||
cubit.removeOptionContentItem(0);
|
||||
expect(cubit.state.planData.options[0].items.length, 1);
|
||||
expect(cubit.state.planData.options[0].items[0], 'Item 2');
|
||||
});
|
||||
|
||||
test('togglePersonalizedDietOptionState toggles hasPersonalDiet', () {
|
||||
cubit.setSelectedOptionIndex(0);
|
||||
final initialValue = cubit.state.planData.options[0].hasPersonalDiet;
|
||||
|
||||
cubit.togglePersonalizedDietOptionState();
|
||||
expect(cubit.state.planData.options[0].hasPersonalDiet, !initialValue);
|
||||
|
||||
cubit.togglePersonalizedDietOptionState();
|
||||
expect(cubit.state.planData.options[0].hasPersonalDiet, initialValue);
|
||||
});
|
||||
|
||||
test('validateSelectedOptionItems validates items', () {
|
||||
cubit.setSelectedOptionIndex(0);
|
||||
expect(cubit.validateSelectedOptionItems(), false);
|
||||
expect(cubit.state.validationState.optionsValidationState[0].isItemsEmpty, true);
|
||||
|
||||
cubit.setOptionContent('Valid item', 0);
|
||||
expect(cubit.validateSelectedOptionItems(), true);
|
||||
expect(cubit.state.validationState.optionsValidationState[0].isItemsEmpty, false);
|
||||
|
||||
cubit.setOptionContent('AB', 1);
|
||||
expect(cubit.validateSelectedOptionItems(), false);
|
||||
});
|
||||
|
||||
test('validatePriceOfAllFilledOptions validates all options', () {
|
||||
// Set prices for all options
|
||||
cubit.setSelectedOptionIndex(0);
|
||||
cubit.setPrice(1500.0);
|
||||
cubit.setSelectedOptionIndex(1);
|
||||
cubit.setPrice(1500.0);
|
||||
cubit.setSelectedOptionIndex(2);
|
||||
cubit.setPrice(1500.0);
|
||||
expect(cubit.validatePriceOfAllFilledOptions(), true);
|
||||
});
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,238 @@
|
||||
import 'package:eatvio_app/core/app_config.dart';
|
||||
import 'package:eatvio_app/core/app_initializer.dart';
|
||||
import 'package:eatvio_app/core/locator_service.dart';
|
||||
import 'package:eatvio_app/core/theme/theme_helper.dart';
|
||||
import 'package:eatvio_app/features/diary/presentation/bloc/consultant_comments_cubit/consultant_comments_cubit.dart';
|
||||
import 'package:eatvio_app/features/diary/presentation/bloc/consultant_comments_cubit/consultant_comments_state.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/pages/diary_screen.dart';
|
||||
import 'package:eatvio_app/features/diary/models/consultant_comment_model.dart';
|
||||
import 'package:eatvio_app/features/diary/entities/diary_day_entity.dart';
|
||||
import 'package:eatvio_app/features/diary/entities/diary_future_diet_day_entity.dart';
|
||||
import 'package:eatvio_app/features/diary/models/consultant_review_model.dart';
|
||||
import 'package:eatvio_app/features/diary/models/diary_day_model.dart';
|
||||
import 'package:eatvio_app/features/diary/models/nutrition_model.dart';
|
||||
import 'package:eatvio_app/core/widgets/data_error_widget.dart';
|
||||
import 'package:eatvio_app/features/diary/presentation/widgets/meal_widget.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:mockito/annotations.dart';
|
||||
import 'package:mockito/mockito.dart';
|
||||
|
||||
import 'diary_screen_smoke_test.mocks.dart';
|
||||
|
||||
@GenerateNiceMocks([MockSpec<DiaryCubit>(), MockSpec<ConsultantCommentsCubit>()])
|
||||
void main() {
|
||||
group('DiaryScreen smoke', () {
|
||||
setUpAll(() async {
|
||||
dotenv.loadFromString(envString: 'DEV_BASE_URL=http://localhost\nBASE_URL=http://localhost');
|
||||
});
|
||||
|
||||
setUp(() async {
|
||||
await sl.reset();
|
||||
sl.registerLazySingleton<AppConfig>(() => AppConfig(buildType: AppBuildType.tests)..setBaseUrl('http://localhost'));
|
||||
});
|
||||
|
||||
Widget wrap(Widget child) {
|
||||
return MaterialApp(
|
||||
theme: ThemeHelper().themeData(),
|
||||
localizationsDelegates: const [
|
||||
S.delegate,
|
||||
GlobalMaterialLocalizations.delegate,
|
||||
GlobalWidgetsLocalizations.delegate,
|
||||
GlobalCupertinoLocalizations.delegate,
|
||||
],
|
||||
supportedLocales: const [Locale('ru')],
|
||||
home: child,
|
||||
);
|
||||
}
|
||||
|
||||
DiaryStateLoaded loadedState({required bool hasChosenPlan}) {
|
||||
final diary = DiaryDayEntity(
|
||||
id: 1,
|
||||
date: '2025-01-01',
|
||||
wakeup: '07:00',
|
||||
retire: '23:00',
|
||||
closed: 0,
|
||||
comment: '',
|
||||
typeOfDiary: TypeOfDiaryModel(hasChosenDiet: false, hasChosenPlan: hasChosenPlan, diaryState: null),
|
||||
meals: const [MealModel(id: 1, time: '08:00', typeNumber: 1, satiety: 0, mood: 0, portions: [], photos: [])],
|
||||
chosenPlanId: hasChosenPlan ? 10 : null,
|
||||
dayOfChosenDiet: null,
|
||||
diet: null,
|
||||
);
|
||||
const nutrition = NutritionDataModel(
|
||||
dailyNutritionIntake: NutritionModel(energy: 0, proteins: 0, fats: 0, carbohydrates: 0),
|
||||
dailyNutritionRecommendation: NutritionModel(energy: 0, proteins: 0, fats: 0, carbohydrates: 0),
|
||||
);
|
||||
return DiaryState.loaded(
|
||||
diary: diary,
|
||||
nutrition: nutrition,
|
||||
selectedDay: DateTime(2025, 1, 1),
|
||||
selectedMeal: 1,
|
||||
isConsultantView: false,
|
||||
isDiaryFinished: false,
|
||||
isDiaryCommented: false,
|
||||
isWakeUpAndRetireEnabled: true,
|
||||
isMoodEnabled: true,
|
||||
)
|
||||
as DiaryStateLoaded;
|
||||
}
|
||||
|
||||
testWidgets('renders loading state', (tester) async {
|
||||
final diaryCubit = MockDiaryCubit();
|
||||
when(diaryCubit.state).thenReturn(const DiaryState.loading());
|
||||
when(diaryCubit.stream).thenAnswer((_) => const Stream<DiaryState>.empty());
|
||||
when(diaryCubit.close()).thenAnswer((_) async => Future<void>.value());
|
||||
when(diaryCubit.fetchDiaryV2(needLoading: anyNamed('needLoading'), diaryId: anyNamed('diaryId'))).thenAnswer((_) async => true);
|
||||
|
||||
final commentsCubit = MockConsultantCommentsCubit();
|
||||
when(commentsCubit.state).thenReturn(ConsultantCommentsState(commentsModel: ConsultantCommentModel(comment: '')));
|
||||
when(commentsCubit.stream).thenAnswer((_) => const Stream<ConsultantCommentsState>.empty());
|
||||
when(commentsCubit.close()).thenAnswer((_) async => Future<void>.value());
|
||||
|
||||
sl.registerFactory<DiaryCubit>(() => diaryCubit);
|
||||
sl.registerFactory<ConsultantCommentsCubit>(() => commentsCubit);
|
||||
|
||||
await tester.pumpWidget(wrap(const DiaryScreen()));
|
||||
await tester.pump();
|
||||
|
||||
expect(find.byType(DiaryScreen), findsOneWidget);
|
||||
});
|
||||
|
||||
testWidgets('renders loaded state (chosen plan)', (tester) async {
|
||||
final diaryCubit = MockDiaryCubit();
|
||||
when(diaryCubit.state).thenReturn(loadedState(hasChosenPlan: true));
|
||||
when(diaryCubit.stream).thenAnswer((_) => const Stream<DiaryState>.empty());
|
||||
when(diaryCubit.close()).thenAnswer((_) async => Future<void>.value());
|
||||
when(diaryCubit.fetchDiaryV2(needLoading: anyNamed('needLoading'), diaryId: anyNamed('diaryId'))).thenAnswer((_) async => true);
|
||||
|
||||
final commentsCubit = MockConsultantCommentsCubit();
|
||||
when(commentsCubit.state).thenReturn(ConsultantCommentsState(commentsModel: ConsultantCommentModel(comment: ''), dayCommentNeedsToBeFilled: false));
|
||||
when(commentsCubit.stream).thenAnswer((_) => const Stream<ConsultantCommentsState>.empty());
|
||||
when(commentsCubit.close()).thenAnswer((_) async => Future<void>.value());
|
||||
|
||||
sl.registerFactory<DiaryCubit>(() => diaryCubit);
|
||||
sl.registerFactory<ConsultantCommentsCubit>(() => commentsCubit);
|
||||
|
||||
await tester.pumpWidget(wrap(const DiaryScreen()));
|
||||
await tester.pumpAndSettle(const Duration(milliseconds: 300));
|
||||
|
||||
expect(find.byType(DiaryScreen), findsOneWidget);
|
||||
|
||||
expect(find.text('Дополнительная информация для консультанта'), findsOneWidget);
|
||||
});
|
||||
|
||||
testWidgets('renders error state and reload button', (tester) async {
|
||||
final diaryCubit = MockDiaryCubit();
|
||||
when(diaryCubit.state).thenReturn(const DiaryState.error(errorMessage: 'err'));
|
||||
when(diaryCubit.stream).thenAnswer((_) => const Stream<DiaryState>.empty());
|
||||
when(diaryCubit.close()).thenAnswer((_) async => Future<void>.value());
|
||||
when(diaryCubit.fetchDiaryV2(needLoading: anyNamed('needLoading'), diaryId: anyNamed('diaryId'))).thenAnswer((_) async => true);
|
||||
|
||||
final commentsCubit = MockConsultantCommentsCubit();
|
||||
when(commentsCubit.state).thenReturn(ConsultantCommentsState(commentsModel: ConsultantCommentModel(comment: '')));
|
||||
when(commentsCubit.stream).thenAnswer((_) => const Stream<ConsultantCommentsState>.empty());
|
||||
when(commentsCubit.close()).thenAnswer((_) async => Future<void>.value());
|
||||
|
||||
sl.registerFactory<DiaryCubit>(() => diaryCubit);
|
||||
sl.registerFactory<ConsultantCommentsCubit>(() => commentsCubit);
|
||||
|
||||
await tester.pumpWidget(wrap(const DiaryScreen()));
|
||||
await tester.pump();
|
||||
|
||||
expect(find.byType(DiaryScreen), findsOneWidget);
|
||||
expect(find.byType(DataErrorWidget), findsOneWidget);
|
||||
});
|
||||
|
||||
testWidgets('renders tomorrow-with-diet state', (tester) async {
|
||||
final diaryCubit = MockDiaryCubit();
|
||||
const futureDiary = DiaryFutureDietDayEntity(
|
||||
date: '2025-01-02',
|
||||
typeOfDiary: TypeOfDiaryModel(hasChosenDiet: true, hasChosenPlan: false, diaryState: null),
|
||||
meals: [MealModel(id: 1, time: '08:00', typeNumber: 1, satiety: 0, mood: 0, portions: [], photos: [])],
|
||||
diet: null,
|
||||
dayOfChosenDiet: 1,
|
||||
);
|
||||
when(diaryCubit.state).thenReturn(DiaryState.loadedTomorrowWithDiet(diary: futureDiary, selectedDay: DateTime(2025, 1, 2), selectedMeal: 1));
|
||||
when(diaryCubit.stream).thenAnswer((_) => const Stream<DiaryState>.empty());
|
||||
when(diaryCubit.close()).thenAnswer((_) async => Future<void>.value());
|
||||
when(diaryCubit.fetchDiaryV2(needLoading: anyNamed('needLoading'), diaryId: anyNamed('diaryId'))).thenAnswer((_) async => true);
|
||||
|
||||
final commentsCubit = MockConsultantCommentsCubit();
|
||||
when(commentsCubit.state).thenReturn(ConsultantCommentsState(commentsModel: ConsultantCommentModel(comment: '')));
|
||||
when(commentsCubit.stream).thenAnswer((_) => const Stream<ConsultantCommentsState>.empty());
|
||||
when(commentsCubit.close()).thenAnswer((_) async => Future<void>.value());
|
||||
|
||||
sl.registerFactory<DiaryCubit>(() => diaryCubit);
|
||||
sl.registerFactory<ConsultantCommentsCubit>(() => commentsCubit);
|
||||
|
||||
await tester.pumpWidget(wrap(const DiaryScreen()));
|
||||
await tester.pump(const Duration(milliseconds: 200));
|
||||
|
||||
expect(find.byType(DiaryScreen), findsOneWidget);
|
||||
expect(find.byType(MealWidget), findsOneWidget);
|
||||
});
|
||||
|
||||
testWidgets('renders consultant view blocks', (tester) async {
|
||||
final diaryCubit = MockDiaryCubit();
|
||||
final state = (loadedState(hasChosenPlan: true)).copyWith(
|
||||
isConsultantView: true,
|
||||
isDiaryFinished: true,
|
||||
consultantReview: ConsultantReviewModel(
|
||||
planName: 'Plan',
|
||||
planDuration: 30,
|
||||
currentPlanDay: 1,
|
||||
consultantComments: ConsultantComments(dayComment: 'Nice', mealsComments: const [], portionsComments: const []),
|
||||
),
|
||||
);
|
||||
when(diaryCubit.state).thenReturn(state);
|
||||
when(diaryCubit.stream).thenAnswer((_) => const Stream<DiaryState>.empty());
|
||||
when(diaryCubit.close()).thenAnswer((_) async => Future<void>.value());
|
||||
when(diaryCubit.fetchDiaryV2(needLoading: anyNamed('needLoading'), diaryId: anyNamed('diaryId'))).thenAnswer((_) async => true);
|
||||
|
||||
final commentsCubit = MockConsultantCommentsCubit();
|
||||
when(commentsCubit.state).thenReturn(ConsultantCommentsState(commentsModel: ConsultantCommentModel(comment: ''), dayCommentNeedsToBeFilled: false));
|
||||
when(commentsCubit.stream).thenAnswer((_) => const Stream<ConsultantCommentsState>.empty());
|
||||
when(commentsCubit.close()).thenAnswer((_) async => Future<void>.value());
|
||||
when(commentsCubit.submitComments(any)).thenAnswer((_) async => true);
|
||||
|
||||
sl.registerFactory<DiaryCubit>(() => diaryCubit);
|
||||
sl.registerFactory<ConsultantCommentsCubit>(() => commentsCubit);
|
||||
|
||||
await tester.pumpWidget(wrap(const DiaryScreen()));
|
||||
await tester.pump(const Duration(milliseconds: 200));
|
||||
|
||||
expect(find.text('Nice'), findsOneWidget);
|
||||
expect(find.text(S.current.send), findsOneWidget);
|
||||
});
|
||||
|
||||
testWidgets('renders user diary screen app bar title', (tester) async {
|
||||
final diaryCubit = MockDiaryCubit();
|
||||
final state = loadedState(hasChosenPlan: false);
|
||||
when(diaryCubit.state).thenReturn(state);
|
||||
when(diaryCubit.stream).thenAnswer((_) => const Stream<DiaryState>.empty());
|
||||
when(diaryCubit.close()).thenAnswer((_) async => Future<void>.value());
|
||||
when(diaryCubit.fetchDiaryV2(needLoading: anyNamed('needLoading'), diaryId: anyNamed('diaryId'))).thenAnswer((_) async => true);
|
||||
|
||||
final commentsCubit = MockConsultantCommentsCubit();
|
||||
when(commentsCubit.state).thenReturn(ConsultantCommentsState(commentsModel: ConsultantCommentModel(comment: '')));
|
||||
when(commentsCubit.stream).thenAnswer((_) => const Stream<ConsultantCommentsState>.empty());
|
||||
when(commentsCubit.close()).thenAnswer((_) async => Future<void>.value());
|
||||
|
||||
sl.registerFactory<DiaryCubit>(() => diaryCubit);
|
||||
sl.registerFactory<ConsultantCommentsCubit>(() => commentsCubit);
|
||||
|
||||
await tester.pumpWidget(wrap(const UserDiaryScreen(diaryId: 1, currentPlanDay: 1)));
|
||||
await tester.pump(const Duration(milliseconds: 200));
|
||||
|
||||
expect(find.byType(AppBar), findsWidgets);
|
||||
|
||||
expect(find.byType(Text), findsWidgets);
|
||||
});
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,107 @@
|
||||
import 'package:eatvio_app/core/theme/theme_helper.dart';
|
||||
import 'package:eatvio_app/features/diary/entities/diary_day_entity.dart';
|
||||
import 'package:eatvio_app/features/diary/models/diary_day_model.dart';
|
||||
import 'package:eatvio_app/features/diary/models/nutrition_model.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_modal.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
|
||||
class FakeDiaryCubit extends Cubit<DiaryState> implements DiaryCubit {
|
||||
FakeDiaryCubit(super.initialState);
|
||||
|
||||
@override
|
||||
dynamic noSuchMethod(Invocation invocation) => super.noSuchMethod(invocation);
|
||||
}
|
||||
|
||||
void main() {
|
||||
group('AddProductModal smoke', () {
|
||||
DiaryState makeLoadedState({required bool withPortion}) {
|
||||
const nutrition = NutritionDataModel(
|
||||
dailyNutritionIntake: NutritionModel(energy: 0, proteins: 0, fats: 0, carbohydrates: 0),
|
||||
dailyNutritionRecommendation: NutritionModel(energy: 1, proteins: 1, fats: 1, carbohydrates: 1),
|
||||
);
|
||||
|
||||
final diary = DiaryDayEntity(
|
||||
id: 1,
|
||||
date: '2025-01-01',
|
||||
wakeup: '06:30:00',
|
||||
retire: '22:30:00',
|
||||
closed: 0,
|
||||
comment: '',
|
||||
typeOfDiary: const TypeOfDiaryModel(hasChosenDiet: false, hasChosenPlan: false, diaryState: null),
|
||||
meals: [
|
||||
MealModel(
|
||||
id: 1,
|
||||
time: '08:00',
|
||||
typeNumber: 1,
|
||||
satiety: 0,
|
||||
mood: 0,
|
||||
portions: withPortion
|
||||
? [
|
||||
const PortionModel(
|
||||
id: 10,
|
||||
productNutritionalValueId: null,
|
||||
productCaloriesInfo: null,
|
||||
productName: 'Apple',
|
||||
quantity: 100,
|
||||
portion: 'gr',
|
||||
fromDiet: false,
|
||||
portionState: PortionState.eaten,
|
||||
),
|
||||
]
|
||||
: const [],
|
||||
photos: const [],
|
||||
),
|
||||
],
|
||||
chosenPlanId: null,
|
||||
dayOfChosenDiet: null,
|
||||
diet: null,
|
||||
);
|
||||
|
||||
return DiaryState.loaded(diary: diary, nutrition: nutrition, selectedDay: DateTime(2025, 1, 1), selectedMeal: 1);
|
||||
}
|
||||
|
||||
Widget wrap(Widget child, DiaryCubit cubit) {
|
||||
return MaterialApp(
|
||||
theme: ThemeHelper().themeData(),
|
||||
home: BlocProvider<DiaryCubit>.value(
|
||||
value: cubit,
|
||||
child: Scaffold(body: child),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
testWidgets('renders for adding a product (portion=null)', (tester) async {
|
||||
final state = makeLoadedState(withPortion: false);
|
||||
final cubit = FakeDiaryCubit(state);
|
||||
|
||||
await tester.pumpWidget(wrap(AddProductModal(state: state as DiaryStateLoaded, diaryCubit: cubit), cubit));
|
||||
await tester.pump();
|
||||
|
||||
expect(find.text('Добавление блюда'), findsOneWidget);
|
||||
expect(find.text('Добавить'), findsOneWidget);
|
||||
|
||||
await cubit.close();
|
||||
});
|
||||
|
||||
testWidgets('renders for editing a product (portion!=null)', (tester) async {
|
||||
final state = makeLoadedState(withPortion: true);
|
||||
final cubit = FakeDiaryCubit(state);
|
||||
|
||||
final loaded = state as DiaryStateLoaded;
|
||||
final portion = loaded.diary.meals.first.portions.first;
|
||||
|
||||
await tester.pumpWidget(wrap(AddProductModal(state: loaded, diaryCubit: cubit, portion: portion), cubit));
|
||||
await tester.pump();
|
||||
|
||||
expect(find.text('Добавление блюда'), findsOneWidget);
|
||||
expect(find.text('Сохранить'), findsOneWidget);
|
||||
expect(find.byIcon(Icons.delete_outline_rounded), findsOneWidget);
|
||||
|
||||
await cubit.close();
|
||||
});
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
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/calendar_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';
|
||||
|
||||
class FakeDiaryCubit extends Cubit<DiaryState> implements DiaryCubit {
|
||||
FakeDiaryCubit(super.initialState);
|
||||
@override
|
||||
dynamic noSuchMethod(Invocation invocation) => super.noSuchMethod(invocation);
|
||||
}
|
||||
|
||||
void main() {
|
||||
group('CalendarWidget smoke', () {
|
||||
Widget wrap(Widget child, DiaryCubit cubit) {
|
||||
return MaterialApp(
|
||||
theme: ThemeHelper().themeData(),
|
||||
localizationsDelegates: const [
|
||||
S.delegate,
|
||||
GlobalMaterialLocalizations.delegate,
|
||||
GlobalWidgetsLocalizations.delegate,
|
||||
GlobalCupertinoLocalizations.delegate,
|
||||
],
|
||||
supportedLocales: const [Locale('ru')],
|
||||
home: BlocProvider<DiaryCubit>.value(
|
||||
value: cubit,
|
||||
child: Scaffold(body: child),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
testWidgets('renders', (tester) async {
|
||||
final cubit = FakeDiaryCubit(const DiaryState.loading());
|
||||
await tester.pumpWidget(wrap(const CalendarWidget(), cubit));
|
||||
await tester.pump();
|
||||
// Just make sure it builds.
|
||||
expect(find.byType(CalendarWidget), findsOneWidget);
|
||||
await cubit.close();
|
||||
});
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,69 @@
|
||||
import 'package:eatvio_app/core/theme/theme_helper.dart';
|
||||
import 'package:eatvio_app/features/diary/entities/diary_day_entity.dart';
|
||||
import 'package:eatvio_app/features/diary/models/diary_day_model.dart';
|
||||
import 'package:eatvio_app/features/diary/models/nutrition_model.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/calories_chart_widget.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
|
||||
class FakeDiaryCubit extends Cubit<DiaryState> implements DiaryCubit {
|
||||
FakeDiaryCubit(super.initialState);
|
||||
@override
|
||||
dynamic noSuchMethod(Invocation invocation) => super.noSuchMethod(invocation);
|
||||
}
|
||||
|
||||
void main() {
|
||||
group('CaloriesChartWidget smoke', () {
|
||||
Widget wrap(Widget child, DiaryCubit cubit) {
|
||||
return MaterialApp(
|
||||
theme: ThemeHelper().themeData(),
|
||||
home: BlocProvider<DiaryCubit>.value(
|
||||
value: cubit,
|
||||
child: Scaffold(body: child),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
testWidgets('renders chart and opens help dialog', (tester) async {
|
||||
final diary = DiaryDayEntity(
|
||||
id: 1,
|
||||
date: '2025-01-01',
|
||||
wakeup: '06:30:00',
|
||||
retire: '22:30:00',
|
||||
closed: 0,
|
||||
comment: '',
|
||||
typeOfDiary: const TypeOfDiaryModel(hasChosenDiet: false, hasChosenPlan: false, diaryState: null),
|
||||
meals: const [MealModel(id: 1, time: '08:00', typeNumber: 1, satiety: 0, mood: 0, portions: [], photos: [])],
|
||||
chosenPlanId: null,
|
||||
dayOfChosenDiet: null,
|
||||
diet: null,
|
||||
);
|
||||
|
||||
const nutrition = NutritionDataModel(
|
||||
dailyNutritionIntake: NutritionModel(energy: 1200, proteins: 50, fats: 30, carbohydrates: 150),
|
||||
dailyNutritionRecommendation: NutritionModel(energy: 2000, proteins: 100, fats: 70, carbohydrates: 250),
|
||||
);
|
||||
|
||||
final state = DiaryState.loaded(diary: diary, nutrition: nutrition, selectedDay: DateTime(2025, 1, 1), selectedMeal: 1);
|
||||
final cubit = FakeDiaryCubit(state);
|
||||
|
||||
await tester.pumpWidget(wrap(const CaloriesChartWidget(), cubit));
|
||||
|
||||
await tester.pumpAndSettle(const Duration(milliseconds: 200));
|
||||
|
||||
expect(find.text('Калории'), findsWidgets);
|
||||
expect(find.text('Белки'), findsWidgets);
|
||||
expect(find.text('Жиры'), findsWidgets);
|
||||
expect(find.text('Углеводы'), findsWidgets);
|
||||
|
||||
await tester.tap(find.byIcon(Icons.help_outline_rounded));
|
||||
await tester.pumpAndSettle();
|
||||
expect(find.byType(AlertDialog), findsOneWidget);
|
||||
|
||||
await cubit.close();
|
||||
});
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,65 @@
|
||||
import 'package:eatvio_app/core/theme/theme_helper.dart';
|
||||
import 'package:eatvio_app/features/diary/models/consultant_comment_model.dart';
|
||||
import 'package:eatvio_app/features/diary/presentation/bloc/consultant_comments_cubit/consultant_comments_cubit.dart';
|
||||
import 'package:eatvio_app/features/diary/presentation/bloc/consultant_comments_cubit/consultant_comments_state.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/comment_for_consultant_widget.dart';
|
||||
import 'package:eatvio_app/features/diary/presentation/widgets/day_comment_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';
|
||||
|
||||
class FakeDiaryCubit extends Cubit<DiaryState> implements DiaryCubit {
|
||||
FakeDiaryCubit() : super(const DiaryState.loading());
|
||||
@override
|
||||
dynamic noSuchMethod(Invocation invocation) => super.noSuchMethod(invocation);
|
||||
}
|
||||
|
||||
class FakeConsultantCommentsCubit extends Cubit<ConsultantCommentsState> implements ConsultantCommentsCubit {
|
||||
FakeConsultantCommentsCubit(super.initialState);
|
||||
@override
|
||||
dynamic noSuchMethod(Invocation invocation) => super.noSuchMethod(invocation);
|
||||
}
|
||||
|
||||
void main() {
|
||||
group('Diary comment widgets smoke', () {
|
||||
Widget wrap(Widget child, {DiaryCubit? diaryCubit, ConsultantCommentsCubit? commentsCubit}) {
|
||||
return MaterialApp(
|
||||
theme: ThemeHelper().themeData(),
|
||||
localizationsDelegates: const [
|
||||
S.delegate,
|
||||
GlobalMaterialLocalizations.delegate,
|
||||
GlobalWidgetsLocalizations.delegate,
|
||||
GlobalCupertinoLocalizations.delegate,
|
||||
],
|
||||
supportedLocales: const [Locale('ru')],
|
||||
home: MultiBlocProvider(
|
||||
providers: [
|
||||
if (diaryCubit != null) BlocProvider<DiaryCubit>.value(value: diaryCubit),
|
||||
if (commentsCubit != null) BlocProvider<ConsultantCommentsCubit>.value(value: commentsCubit),
|
||||
],
|
||||
child: Scaffold(body: child),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
testWidgets('CommentForConsultantWidget renders and accepts initial comment', (tester) async {
|
||||
final cubit = FakeDiaryCubit();
|
||||
await tester.pumpWidget(wrap(const CommentForConsultantWidget(comment: 'Hi'), diaryCubit: cubit));
|
||||
await tester.pump();
|
||||
expect(find.text('Дополнительная информация для консультанта'), findsOneWidget);
|
||||
expect(find.text('Hi'), findsOneWidget);
|
||||
});
|
||||
|
||||
testWidgets('DayCommentWidget shows validation error state', (tester) async {
|
||||
final cubit = FakeConsultantCommentsCubit(ConsultantCommentsState(commentsModel: ConsultantCommentModel(comment: ''), dayCommentNeedsToBeFilled: true));
|
||||
await tester.pumpWidget(wrap(const DayCommentWidget(), commentsCubit: cubit));
|
||||
await tester.pump();
|
||||
expect(find.byType(DayCommentWidget), findsOneWidget);
|
||||
expect(find.text('Минимум 3 символа'), findsOneWidget);
|
||||
});
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,112 @@
|
||||
import 'package:eatvio_app/core/theme/theme_helper.dart';
|
||||
import 'package:eatvio_app/features/consultations/models/consultation_model.dart';
|
||||
import 'package:eatvio_app/features/diary/entities/diary_day_entity.dart';
|
||||
import 'package:eatvio_app/features/diary/entities/diary_future_diet_day_entity.dart';
|
||||
import 'package:eatvio_app/features/diary/models/diary_day_model.dart';
|
||||
import 'package:eatvio_app/features/diary/models/nutrition_model.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:eatvio_app/features/diary/presentation/widgets/chosen_diet_widget.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_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';
|
||||
|
||||
class FakeDiaryCubit extends Cubit<DiaryState> implements DiaryCubit {
|
||||
FakeDiaryCubit(super.initialState);
|
||||
@override
|
||||
dynamic noSuchMethod(Invocation invocation) => super.noSuchMethod(invocation);
|
||||
}
|
||||
|
||||
void main() {
|
||||
group('Diary chosen widgets smoke', () {
|
||||
Widget wrap(Widget child, DiaryCubit cubit) {
|
||||
final router = GoRouter(
|
||||
routes: [
|
||||
GoRoute(
|
||||
path: '/',
|
||||
builder: (_, _) => BlocProvider<DiaryCubit>.value(
|
||||
value: cubit,
|
||||
child: Scaffold(body: child),
|
||||
),
|
||||
),
|
||||
GoRoute(path: '/about-diet', builder: (_, _) => const SizedBox.shrink()),
|
||||
],
|
||||
);
|
||||
return MaterialApp.router(
|
||||
theme: ThemeHelper().themeData(),
|
||||
localizationsDelegates: const [
|
||||
S.delegate,
|
||||
GlobalMaterialLocalizations.delegate,
|
||||
GlobalWidgetsLocalizations.delegate,
|
||||
GlobalCupertinoLocalizations.delegate,
|
||||
],
|
||||
supportedLocales: const [Locale('ru')],
|
||||
routerConfig: router,
|
||||
);
|
||||
}
|
||||
|
||||
DiaryStateLoaded loadedState({required bool hasDiet, required bool hasPlan}) {
|
||||
final diary = DiaryDayEntity(
|
||||
id: 1,
|
||||
date: '2025-01-01',
|
||||
wakeup: '07:00',
|
||||
retire: '23:00',
|
||||
closed: 0,
|
||||
comment: '',
|
||||
typeOfDiary: TypeOfDiaryModel(hasChosenDiet: hasDiet, hasChosenPlan: hasPlan, diaryState: DiaryStateEnum.started),
|
||||
meals: const [MealModel(id: 1, time: '08:00', typeNumber: 1, satiety: 0, mood: 0, portions: [], photos: [])],
|
||||
chosenPlanId: hasPlan ? 1 : null,
|
||||
dayOfChosenDiet: hasDiet ? 2 : null,
|
||||
diet: hasDiet ? const DiaryDietModel(id: 10, duration: 7, name: 'Diet') : null,
|
||||
);
|
||||
const nutrition = NutritionDataModel(
|
||||
dailyNutritionIntake: NutritionModel(energy: 0, proteins: 0, fats: 0, carbohydrates: 0),
|
||||
dailyNutritionRecommendation: NutritionModel(energy: 0, proteins: 0, fats: 0, carbohydrates: 0),
|
||||
);
|
||||
return DiaryState.loaded(
|
||||
diary: diary,
|
||||
nutrition: nutrition,
|
||||
selectedDay: DateTime(2025, 1, 1),
|
||||
selectedMeal: 1,
|
||||
chosenPlanCurrentDay: hasPlan ? 3 : null,
|
||||
chosenPlanName: hasPlan ? 'Plan' : null,
|
||||
chosenPlanDuration: hasPlan ? 30 : null,
|
||||
)
|
||||
as DiaryStateLoaded;
|
||||
}
|
||||
|
||||
testWidgets('ChosenPlanWidget renders when plan selected', (tester) async {
|
||||
final cubit = FakeDiaryCubit(loadedState(hasDiet: false, hasPlan: true));
|
||||
await tester.pumpWidget(wrap(const ChosenPlanWidget(), cubit));
|
||||
await tester.pump();
|
||||
expect(find.text('Plan'), findsOneWidget);
|
||||
expect(find.textContaining('из 30'), findsOneWidget);
|
||||
});
|
||||
|
||||
testWidgets('ChosenDietWidget renders and is tappable in loadedTomorrowWithDiet', (tester) async {
|
||||
const futureDiary = DiaryFutureDietDayEntity(
|
||||
date: '2025-01-02',
|
||||
typeOfDiary: TypeOfDiaryModel(hasChosenDiet: true, hasChosenPlan: false, diaryState: null),
|
||||
meals: [MealModel(id: 1, time: '08:00', typeNumber: 1, satiety: 0, mood: 0, portions: [], photos: [])],
|
||||
diet: DiaryDietModel(id: 10, duration: 7, name: 'Diet'),
|
||||
dayOfChosenDiet: 2,
|
||||
);
|
||||
final cubit = FakeDiaryCubit(DiaryState.loadedTomorrowWithDiet(diary: futureDiary, selectedDay: DateTime(2025, 1, 2), selectedMeal: 1));
|
||||
await tester.pumpWidget(wrap(const ChosenDietWidget(), cubit));
|
||||
await tester.pump();
|
||||
expect(find.text('Diet'), findsOneWidget);
|
||||
});
|
||||
|
||||
testWidgets('AddProductButtonWidget renders for loaded state', (tester) async {
|
||||
final cubit = FakeDiaryCubit(loadedState(hasDiet: false, hasPlan: false));
|
||||
await tester.pumpWidget(wrap(const AddProductButtonWidget(), cubit));
|
||||
await tester.pump();
|
||||
expect(find.text('Добавить'), findsOneWidget);
|
||||
});
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,224 @@
|
||||
|
||||
import 'package:eatvio_app/core/theme/theme_helper.dart';
|
||||
import 'package:eatvio_app/features/consultations/models/consultation_model.dart';
|
||||
import 'package:eatvio_app/features/diary/entities/diary_day_entity.dart';
|
||||
import 'package:eatvio_app/features/diary/models/consultant_comment_model.dart';
|
||||
import 'package:eatvio_app/features/diary/models/consultant_review_model.dart';
|
||||
import 'package:eatvio_app/features/diary/models/diary_day_model.dart';
|
||||
import 'package:eatvio_app/features/diary/models/nutrition_model.dart';
|
||||
import 'package:eatvio_app/features/diary/presentation/bloc/consultant_comments_cubit/consultant_comments_cubit.dart';
|
||||
import 'package:eatvio_app/features/diary/presentation/bloc/consultant_comments_cubit/consultant_comments_state.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/meal_comment_widget.dart';
|
||||
import 'package:eatvio_app/features/diary/presentation/widgets/mood_rating_widget.dart';
|
||||
import 'package:eatvio_app/features/diary/presentation/widgets/satiety_rating_widget.dart';
|
||||
import 'package:eatvio_app/features/diary/presentation/widgets/time_of_meal_picker_widget.dart';
|
||||
import 'package:eatvio_app/generated/l10n.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:flutter_localizations/flutter_localizations.dart';
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
|
||||
class FakeDiaryCubit extends Cubit<DiaryState> implements DiaryCubit {
|
||||
FakeDiaryCubit(super.initialState);
|
||||
|
||||
int? fillTimeDiaryId;
|
||||
String? fillTimeValue;
|
||||
int? fillMoodValue;
|
||||
int? fillSatietyValue;
|
||||
|
||||
@override
|
||||
Future<void> fillTime({required int diaryId, required String time}) async {
|
||||
fillTimeDiaryId = diaryId;
|
||||
fillTimeValue = time;
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> fillMood({required int diaryId, required int mood}) async {
|
||||
fillMoodValue = mood;
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> fillSatiety({required int diaryId, required int satiety}) async {
|
||||
fillSatietyValue = satiety;
|
||||
}
|
||||
|
||||
@override
|
||||
dynamic noSuchMethod(Invocation invocation) => super.noSuchMethod(invocation);
|
||||
}
|
||||
|
||||
class FakeConsultantCommentsCubit extends Cubit<ConsultantCommentsState> implements ConsultantCommentsCubit {
|
||||
FakeConsultantCommentsCubit(super.initialState);
|
||||
|
||||
int? lastMealNumber;
|
||||
String? lastMealComment;
|
||||
|
||||
@override
|
||||
void setMealComment({required int mealNumber, required String comment}) {
|
||||
lastMealNumber = mealNumber;
|
||||
lastMealComment = comment;
|
||||
}
|
||||
|
||||
@override
|
||||
dynamic noSuchMethod(Invocation invocation) => super.noSuchMethod(invocation);
|
||||
}
|
||||
|
||||
class _FakeAssetBundle extends CachingAssetBundle {
|
||||
_FakeAssetBundle(this.assets);
|
||||
final Map<String, String> assets;
|
||||
|
||||
@override
|
||||
Future<String> loadString(String key, {bool cache = true}) async {
|
||||
final value = assets[key];
|
||||
if (value == null) throw FlutterError('Asset not found: $key');
|
||||
return value;
|
||||
}
|
||||
|
||||
@override
|
||||
Future<ByteData> load(String key) async {
|
||||
final value = assets[key];
|
||||
if (value == null) throw FlutterError('Asset not found: $key');
|
||||
return ByteData.sublistView(Uint8List.fromList(value.codeUnits));
|
||||
}
|
||||
}
|
||||
|
||||
void main() {
|
||||
group('Diary rating/time widgets smoke', () {
|
||||
const fakeSvg = '''
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="10" height="10" viewBox="0 0 10 10">
|
||||
<rect width="10" height="10" fill="red"/>
|
||||
</svg>
|
||||
''';
|
||||
|
||||
final bundle = _FakeAssetBundle({
|
||||
'assets/icons/satiety_emoji_left.svg': fakeSvg,
|
||||
'assets/icons/satiety_emoji_right.svg': fakeSvg,
|
||||
'assets/icons/mood_1.svg': fakeSvg,
|
||||
'assets/icons/mood_2.svg': fakeSvg,
|
||||
'assets/icons/error_icon.svg': fakeSvg,
|
||||
});
|
||||
|
||||
Widget wrap({required DiaryCubit diaryCubit, required ConsultantCommentsCubit commentsCubit, required Widget child}) {
|
||||
return DefaultAssetBundle(
|
||||
bundle: bundle,
|
||||
child: MaterialApp(
|
||||
theme: ThemeHelper().themeData(),
|
||||
localizationsDelegates: const [
|
||||
S.delegate,
|
||||
GlobalMaterialLocalizations.delegate,
|
||||
GlobalWidgetsLocalizations.delegate,
|
||||
GlobalCupertinoLocalizations.delegate,
|
||||
],
|
||||
supportedLocales: const [Locale('ru')],
|
||||
home: MediaQuery(
|
||||
data: const MediaQueryData(size: Size(500, 900)),
|
||||
child: MultiBlocProvider(
|
||||
providers: [
|
||||
BlocProvider<DiaryCubit>.value(value: diaryCubit),
|
||||
BlocProvider<ConsultantCommentsCubit>.value(value: commentsCubit),
|
||||
],
|
||||
child: Scaffold(body: child),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
DiaryStateLoaded loadedState({
|
||||
required int satiety,
|
||||
required int mood,
|
||||
bool consultantView = false,
|
||||
bool diaryFinished = false,
|
||||
bool diaryCommented = false,
|
||||
}) {
|
||||
final diary = DiaryDayEntity(
|
||||
id: 1,
|
||||
date: '2025-01-01',
|
||||
wakeup: '07:00',
|
||||
retire: '23:00',
|
||||
closed: 0,
|
||||
comment: '',
|
||||
typeOfDiary: const TypeOfDiaryModel(hasChosenDiet: false, hasChosenPlan: true, diaryState: DiaryStateEnum.started),
|
||||
meals: [MealModel(id: 1, time: '08:00:00', typeNumber: 1, satiety: satiety, mood: mood, portions: const [], photos: const [])],
|
||||
chosenPlanId: 1,
|
||||
dayOfChosenDiet: null,
|
||||
diet: null,
|
||||
);
|
||||
const nutrition = NutritionDataModel(
|
||||
dailyNutritionIntake: NutritionModel(energy: 0, proteins: 0, fats: 0, carbohydrates: 0),
|
||||
dailyNutritionRecommendation: NutritionModel(energy: 0, proteins: 0, fats: 0, carbohydrates: 0),
|
||||
);
|
||||
return DiaryState.loaded(
|
||||
diary: diary,
|
||||
nutrition: nutrition,
|
||||
selectedDay: DateTime(2025, 1, 1),
|
||||
selectedMeal: 1,
|
||||
isConsultantView: consultantView,
|
||||
isDiaryFinished: diaryFinished,
|
||||
isDiaryCommented: diaryCommented,
|
||||
consultantReview: ConsultantReviewModel(
|
||||
planName: 'Plan',
|
||||
planDuration: 10,
|
||||
currentPlanDay: 1,
|
||||
consultantComments: ConsultantComments(dayComment: null, mealsComments: const [], portionsComments: const []),
|
||||
),
|
||||
)
|
||||
as DiaryStateLoaded;
|
||||
}
|
||||
|
||||
testWidgets('TimeOfMealPickerWidget renders time and opens picker', (tester) async {
|
||||
final diaryCubit = FakeDiaryCubit(loadedState(satiety: 0, mood: 0));
|
||||
final commentsCubit = FakeConsultantCommentsCubit(ConsultantCommentsState(commentsModel: ConsultantCommentModel(comment: '')));
|
||||
|
||||
await tester.pumpWidget(wrap(diaryCubit: diaryCubit, commentsCubit: commentsCubit, child: const TimeOfMealPickerWidget()));
|
||||
await tester.pump();
|
||||
|
||||
expect(find.text('Время приема пищи'), findsOneWidget);
|
||||
expect(find.text('08:00'), findsOneWidget);
|
||||
|
||||
await tester.tap(find.text('08:00'));
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
expect(find.text('Сохранить'), findsOneWidget);
|
||||
});
|
||||
|
||||
testWidgets('SatietyRatingWidget renders and is disabled for consultant', (tester) async {
|
||||
final diaryCubit = FakeDiaryCubit(loadedState(satiety: 2, mood: 0, consultantView: true));
|
||||
final commentsCubit = FakeConsultantCommentsCubit(ConsultantCommentsState(commentsModel: ConsultantCommentModel(comment: '')));
|
||||
|
||||
await tester.pumpWidget(wrap(diaryCubit: diaryCubit, commentsCubit: commentsCubit, child: const SatietyRatingWidget()));
|
||||
await tester.pump();
|
||||
|
||||
expect(find.text('Сытость пользователя после приема пищи'), findsOneWidget);
|
||||
});
|
||||
|
||||
testWidgets('MoodRatingWidget renders', (tester) async {
|
||||
final diaryCubit = FakeDiaryCubit(loadedState(satiety: 0, mood: 3));
|
||||
final commentsCubit = FakeConsultantCommentsCubit(ConsultantCommentsState(commentsModel: ConsultantCommentModel(comment: '')));
|
||||
|
||||
await tester.pumpWidget(wrap(diaryCubit: diaryCubit, commentsCubit: commentsCubit, child: const MoodRatingWidget()));
|
||||
await tester.pump();
|
||||
|
||||
expect(find.text('Ваше настроение после приёма пищи'), findsOneWidget);
|
||||
});
|
||||
|
||||
testWidgets('MealCommentWidget shows validation error when comment too short', (tester) async {
|
||||
final diaryCubit = FakeDiaryCubit(loadedState(satiety: 0, mood: 0));
|
||||
final commentsCubit = FakeConsultantCommentsCubit(
|
||||
ConsultantCommentsState(
|
||||
commentsModel: ConsultantCommentModel(
|
||||
comment: '',
|
||||
mealsComments: {'1': MealCommentModel(comment: 'a', typeNumber: 1)},
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
await tester.pumpWidget(wrap(diaryCubit: diaryCubit, commentsCubit: commentsCubit, child: const MealCommentWidget(selectedMeal: 1)));
|
||||
await tester.pump();
|
||||
|
||||
expect(find.text('Минимум 3 символа'), findsOneWidget);
|
||||
});
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,207 @@
|
||||
|
||||
import 'package:eatvio_app/core/theme/theme_helper.dart';
|
||||
import 'package:eatvio_app/features/consultations/models/consultation_model.dart';
|
||||
import 'package:eatvio_app/features/diary/entities/diary_day_entity.dart';
|
||||
import 'package:eatvio_app/features/diary/models/consultant_comment_model.dart';
|
||||
import 'package:eatvio_app/features/diary/models/consultant_review_model.dart';
|
||||
import 'package:eatvio_app/features/diary/models/diary_day_model.dart';
|
||||
import 'package:eatvio_app/features/diary/models/nutrition_model.dart';
|
||||
import 'package:eatvio_app/features/diary/presentation/bloc/consultant_comments_cubit/consultant_comments_cubit.dart';
|
||||
import 'package:eatvio_app/features/diary/presentation/bloc/consultant_comments_cubit/consultant_comments_state.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/mood_rating_widget.dart';
|
||||
import 'package:eatvio_app/features/diary/presentation/widgets/satiety_rating_widget.dart';
|
||||
import 'package:eatvio_app/features/diary/presentation/widgets/time_of_meal_picker_widget.dart';
|
||||
import 'package:eatvio_app/generated/l10n.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:flutter_localizations/flutter_localizations.dart';
|
||||
import 'package:flutter_rating_bar/flutter_rating_bar.dart';
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
|
||||
class FakeDiaryCubit extends Cubit<DiaryState> implements DiaryCubit {
|
||||
FakeDiaryCubit(super.initialState);
|
||||
|
||||
int? lastDiaryId;
|
||||
String? lastTime;
|
||||
int? lastSatiety;
|
||||
int? lastMood;
|
||||
|
||||
@override
|
||||
Future<void> fillTime({required int diaryId, required String time}) async {
|
||||
lastDiaryId = diaryId;
|
||||
lastTime = time;
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> fillSatiety({required int diaryId, required int satiety}) async {
|
||||
lastDiaryId = diaryId;
|
||||
lastSatiety = satiety;
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> fillMood({required int diaryId, required int mood}) async {
|
||||
lastDiaryId = diaryId;
|
||||
lastMood = mood;
|
||||
}
|
||||
|
||||
@override
|
||||
dynamic noSuchMethod(Invocation invocation) => super.noSuchMethod(invocation);
|
||||
}
|
||||
|
||||
class FakeConsultantCommentsCubit extends Cubit<ConsultantCommentsState> implements ConsultantCommentsCubit {
|
||||
FakeConsultantCommentsCubit(super.initialState);
|
||||
@override
|
||||
dynamic noSuchMethod(Invocation invocation) => super.noSuchMethod(invocation);
|
||||
}
|
||||
|
||||
class _FakeAssetBundle extends CachingAssetBundle {
|
||||
_FakeAssetBundle(this.assets);
|
||||
final Map<String, String> assets;
|
||||
|
||||
@override
|
||||
Future<String> loadString(String key, {bool cache = true}) async {
|
||||
final value = assets[key];
|
||||
if (value == null) throw FlutterError('Asset not found: $key');
|
||||
return value;
|
||||
}
|
||||
|
||||
@override
|
||||
Future<ByteData> load(String key) async {
|
||||
final value = assets[key];
|
||||
if (value == null) throw FlutterError('Asset not found: $key');
|
||||
return ByteData.sublistView(Uint8List.fromList(value.codeUnits));
|
||||
}
|
||||
}
|
||||
|
||||
void main() {
|
||||
group('Diary time & rating interactions', () {
|
||||
const fakeSvg = '''
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="10" height="10" viewBox="0 0 10 10">
|
||||
<rect width="10" height="10" fill="red"/>
|
||||
</svg>
|
||||
''';
|
||||
|
||||
final bundle = _FakeAssetBundle({
|
||||
'assets/icons/satiety_emoji_left.svg': fakeSvg,
|
||||
'assets/icons/satiety_emoji_right.svg': fakeSvg,
|
||||
'assets/icons/mood_1.svg': fakeSvg,
|
||||
'assets/icons/mood_2.svg': fakeSvg,
|
||||
});
|
||||
|
||||
Widget wrap({required DiaryCubit diaryCubit, required ConsultantCommentsCubit commentsCubit, required Widget child}) {
|
||||
return DefaultAssetBundle(
|
||||
bundle: bundle,
|
||||
child: MaterialApp(
|
||||
theme: ThemeHelper().themeData(),
|
||||
localizationsDelegates: const [
|
||||
S.delegate,
|
||||
GlobalMaterialLocalizations.delegate,
|
||||
GlobalWidgetsLocalizations.delegate,
|
||||
GlobalCupertinoLocalizations.delegate,
|
||||
],
|
||||
supportedLocales: const [Locale('ru')],
|
||||
home: MediaQuery(
|
||||
data: const MediaQueryData(size: Size(500, 900)),
|
||||
child: MultiBlocProvider(
|
||||
providers: [
|
||||
BlocProvider<DiaryCubit>.value(value: diaryCubit),
|
||||
BlocProvider<ConsultantCommentsCubit>.value(value: commentsCubit),
|
||||
],
|
||||
child: Scaffold(body: child),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
DiaryStateLoaded loadedState({required int satiety, required int mood}) {
|
||||
final diary = DiaryDayEntity(
|
||||
id: 1,
|
||||
date: '2025-01-01',
|
||||
wakeup: '07:00',
|
||||
retire: '23:00',
|
||||
closed: 0,
|
||||
comment: '',
|
||||
typeOfDiary: const TypeOfDiaryModel(hasChosenDiet: false, hasChosenPlan: true, diaryState: DiaryStateEnum.started),
|
||||
meals: [MealModel(id: 1, time: '08:00:00', typeNumber: 1, satiety: satiety, mood: mood, portions: const [], photos: const [])],
|
||||
chosenPlanId: 1,
|
||||
dayOfChosenDiet: null,
|
||||
diet: null,
|
||||
);
|
||||
const nutrition = NutritionDataModel(
|
||||
dailyNutritionIntake: NutritionModel(energy: 0, proteins: 0, fats: 0, carbohydrates: 0),
|
||||
dailyNutritionRecommendation: NutritionModel(energy: 0, proteins: 0, fats: 0, carbohydrates: 0),
|
||||
);
|
||||
return DiaryState.loaded(
|
||||
diary: diary,
|
||||
nutrition: nutrition,
|
||||
selectedDay: DateTime(2025, 1, 1),
|
||||
selectedMeal: 1,
|
||||
isConsultantView: false,
|
||||
isDiaryFinished: false,
|
||||
isDiaryCommented: false,
|
||||
consultantReview: ConsultantReviewModel(
|
||||
planName: 'Plan',
|
||||
planDuration: 10,
|
||||
currentPlanDay: 1,
|
||||
consultantComments: ConsultantComments(dayComment: null, mealsComments: const [], portionsComments: const []),
|
||||
),
|
||||
)
|
||||
as DiaryStateLoaded;
|
||||
}
|
||||
|
||||
testWidgets('TimeOfMealPickerWidget calls fillTime on save', (tester) async {
|
||||
final diaryCubit = FakeDiaryCubit(loadedState(satiety: 0, mood: 0));
|
||||
final commentsCubit = FakeConsultantCommentsCubit(ConsultantCommentsState(commentsModel: ConsultantCommentModel(comment: '')));
|
||||
|
||||
await tester.pumpWidget(wrap(diaryCubit: diaryCubit, commentsCubit: commentsCubit, child: const TimeOfMealPickerWidget()));
|
||||
await tester.pump();
|
||||
|
||||
// Open picker.
|
||||
await tester.tap(find.text('08:00'));
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
await tester.tap(find.text('Сохранить'));
|
||||
await tester.pump();
|
||||
|
||||
expect(diaryCubit.lastDiaryId, 1);
|
||||
expect(diaryCubit.lastTime, isNotNull);
|
||||
});
|
||||
|
||||
testWidgets('SatietyRatingWidget calls fillSatiety after rating change', (tester) async {
|
||||
final diaryCubit = FakeDiaryCubit(loadedState(satiety: 0, mood: 0));
|
||||
final commentsCubit = FakeConsultantCommentsCubit(ConsultantCommentsState(commentsModel: ConsultantCommentModel(comment: '')));
|
||||
|
||||
await tester.pumpWidget(wrap(diaryCubit: diaryCubit, commentsCubit: commentsCubit, child: const SatietyRatingWidget()));
|
||||
await tester.pump();
|
||||
|
||||
final bar = find.byType(RatingBar);
|
||||
expect(bar, findsOneWidget);
|
||||
final box = tester.renderObject<RenderBox>(bar);
|
||||
|
||||
await tester.tapAt(box.localToGlobal(box.size.center(Offset.zero)));
|
||||
await tester.pump(const Duration(milliseconds: 300)); // allow EasyDebounce to fire
|
||||
|
||||
expect(diaryCubit.lastSatiety, isNotNull);
|
||||
});
|
||||
|
||||
testWidgets('MoodRatingWidget calls fillMood after rating change', (tester) async {
|
||||
final diaryCubit = FakeDiaryCubit(loadedState(satiety: 0, mood: 0));
|
||||
final commentsCubit = FakeConsultantCommentsCubit(ConsultantCommentsState(commentsModel: ConsultantCommentModel(comment: '')));
|
||||
|
||||
await tester.pumpWidget(wrap(diaryCubit: diaryCubit, commentsCubit: commentsCubit, child: const MoodRatingWidget()));
|
||||
await tester.pump();
|
||||
|
||||
final bar = find.byType(RatingBar);
|
||||
expect(bar, findsOneWidget);
|
||||
final box = tester.renderObject<RenderBox>(bar);
|
||||
await tester.tapAt(box.localToGlobal(box.size.center(Offset.zero)));
|
||||
await tester.pump(const Duration(milliseconds: 300));
|
||||
|
||||
expect(diaryCubit.lastMood, isNotNull);
|
||||
});
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,82 @@
|
||||
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/widgets/glossary_moderation_request_modal_widget.dart';
|
||||
import 'package:eatvio_app/features/glossary/models/glossary_model.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:mockito/annotations.dart';
|
||||
import 'package:mockito/mockito.dart';
|
||||
|
||||
import 'glossary_moderation_request_modal_widget_test.mocks.dart';
|
||||
|
||||
@GenerateNiceMocks([MockSpec<DiaryCubit>()])
|
||||
void main() {
|
||||
group('GlossaryModerationRequestModalWidget', () {
|
||||
Widget wrap(Widget child) => MaterialApp(
|
||||
theme: ThemeHelper().themeData(),
|
||||
home: Scaffold(body: child),
|
||||
);
|
||||
|
||||
testWidgets('renders alert state and switches to addNewProduct', (tester) async {
|
||||
final cubit = MockDiaryCubit();
|
||||
|
||||
await tester.pumpWidget(wrap(GlossaryModerationRequestModalWidget(productName: 'Apple', diaryCubit: cubit)));
|
||||
await tester.pump();
|
||||
|
||||
expect(find.text('Добавление нового продукта'), findsOneWidget);
|
||||
expect(find.text('Записать'), findsOneWidget);
|
||||
|
||||
await tester.tap(find.text('Записать'));
|
||||
await tester.pump();
|
||||
|
||||
|
||||
expect(find.text('Сохранить'), findsOneWidget);
|
||||
});
|
||||
|
||||
testWidgets('alert -> refuse triggers sendProductToModeration and success', (tester) async {
|
||||
final cubit = MockDiaryCubit();
|
||||
when(
|
||||
cubit.sendProductToModeration(
|
||||
name: anyNamed('name'),
|
||||
energy: anyNamed('energy'),
|
||||
proteins: anyNamed('proteins'),
|
||||
fats: anyNamed('fats'),
|
||||
carbohydrates: anyNamed('carbohydrates'),
|
||||
),
|
||||
).thenAnswer(
|
||||
(_) async => const GlossaryModerationModel(
|
||||
glossary: GlossaryModel(
|
||||
id: 1,
|
||||
name: 'Apple',
|
||||
grInPortion: 0,
|
||||
energyOn100Gr: 52,
|
||||
proteinsOn100Gr: 0,
|
||||
fatsOn100Gr: 0,
|
||||
carbohydratesOn100Gr: 14,
|
||||
energyOnPortion: 0,
|
||||
proteinsOnPortion: 0,
|
||||
fatsOnPortion: 0,
|
||||
carbohydratesOnPortion: 0,
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
await tester.pumpWidget(wrap(GlossaryModerationRequestModalWidget(productName: 'Apple', diaryCubit: cubit)));
|
||||
await tester.pump();
|
||||
|
||||
await tester.tap(find.text('Отказаться'));
|
||||
await tester.pump();
|
||||
|
||||
expect(find.textContaining('Продукт успешно отправлен'), findsOneWidget);
|
||||
verify(
|
||||
cubit.sendProductToModeration(
|
||||
name: anyNamed('name'),
|
||||
energy: anyNamed('energy'),
|
||||
proteins: anyNamed('proteins'),
|
||||
fats: anyNamed('fats'),
|
||||
carbohydrates: anyNamed('carbohydrates'),
|
||||
),
|
||||
).called(1);
|
||||
});
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,216 @@
|
||||
import 'package:eatvio_app/core/theme/theme_helper.dart';
|
||||
import 'package:eatvio_app/features/consultations/models/consultation_model.dart';
|
||||
import 'package:eatvio_app/features/diary/entities/diary_day_entity.dart';
|
||||
import 'package:eatvio_app/features/diary/entities/diary_future_diet_day_entity.dart';
|
||||
import 'package:eatvio_app/features/diary/models/consultant_review_model.dart';
|
||||
import 'package:eatvio_app/features/diary/models/diary_day_model.dart';
|
||||
import 'package:eatvio_app/features/diary/models/nutrition_model.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/meal_button_widget.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
|
||||
class FakeDiaryCubit extends Cubit<DiaryState> implements DiaryCubit {
|
||||
FakeDiaryCubit(super.initialState);
|
||||
|
||||
int? lastSelectedMeal;
|
||||
|
||||
@override
|
||||
void setSelectedMeal(int meal) {
|
||||
lastSelectedMeal = meal;
|
||||
}
|
||||
|
||||
@override
|
||||
dynamic noSuchMethod(Invocation invocation) => super.noSuchMethod(invocation);
|
||||
}
|
||||
|
||||
void main() {
|
||||
group('MealButtonWidget', () {
|
||||
Widget wrap(DiaryCubit cubit, Widget child) {
|
||||
return MaterialApp(
|
||||
theme: ThemeHelper().themeData(),
|
||||
home: MediaQuery(
|
||||
data: const MediaQueryData(size: Size(500, 900)),
|
||||
child: BlocProvider<DiaryCubit>.value(
|
||||
value: cubit,
|
||||
child: Scaffold(body: child),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
DiaryStateLoaded loadedState({required int selectedMeal, required List<List<PortionModel>> mealsPortions, bool withConsultantComment = false}) {
|
||||
const caloriesInfo = ProductCaloriesInfoModel(
|
||||
id: 1,
|
||||
productName: 'P',
|
||||
calories: 100,
|
||||
proteins: 10,
|
||||
fats: 10,
|
||||
carbohydrates: 10,
|
||||
energyOnPortion: 100,
|
||||
proteinsOnPortion: 10,
|
||||
fatsOnPortion: 10,
|
||||
carbohydratesOnPortion: 10,
|
||||
);
|
||||
|
||||
final meals = List<MealModel>.generate(
|
||||
5,
|
||||
(i) => MealModel(
|
||||
id: i + 1,
|
||||
time: '08:00',
|
||||
typeNumber: i + 1,
|
||||
satiety: 0,
|
||||
mood: 0,
|
||||
portions: mealsPortions.length > i
|
||||
? mealsPortions[i]
|
||||
: const [
|
||||
PortionModel(
|
||||
id: 1,
|
||||
productNutritionalValueId: 1,
|
||||
productCaloriesInfo: caloriesInfo,
|
||||
productName: 'P',
|
||||
quantity: 100,
|
||||
portion: 'gr',
|
||||
fromDiet: false,
|
||||
portionState: PortionState.eaten,
|
||||
),
|
||||
],
|
||||
photos: const [],
|
||||
),
|
||||
);
|
||||
|
||||
final diary = DiaryDayEntity(
|
||||
id: 1,
|
||||
date: '2025-01-01',
|
||||
wakeup: '07:00',
|
||||
retire: '23:00',
|
||||
closed: 0,
|
||||
comment: '',
|
||||
typeOfDiary: const TypeOfDiaryModel(hasChosenDiet: false, hasChosenPlan: true, diaryState: DiaryStateEnum.started),
|
||||
meals: meals,
|
||||
chosenPlanId: 1,
|
||||
dayOfChosenDiet: null,
|
||||
diet: null,
|
||||
);
|
||||
|
||||
const nutrition = NutritionDataModel(
|
||||
dailyNutritionIntake: NutritionModel(energy: 0, proteins: 0, fats: 0, carbohydrates: 0),
|
||||
dailyNutritionRecommendation: NutritionModel(energy: 0, proteins: 0, fats: 0, carbohydrates: 0),
|
||||
);
|
||||
|
||||
return DiaryState.loaded(
|
||||
diary: diary,
|
||||
nutrition: nutrition,
|
||||
selectedDay: DateTime(2025, 1, 1),
|
||||
selectedMeal: selectedMeal,
|
||||
isConsultantView: false,
|
||||
isDiaryFinished: false,
|
||||
isDiaryCommented: false,
|
||||
consultantReview: ConsultantReviewModel(
|
||||
planName: 'Plan',
|
||||
planDuration: 10,
|
||||
currentPlanDay: 1,
|
||||
consultantComments: ConsultantComments(
|
||||
dayComment: null,
|
||||
mealsComments: withConsultantComment ? [MealComment(comment: 'c', typeNo: 2)] : const [],
|
||||
portionsComments: const [],
|
||||
),
|
||||
),
|
||||
)
|
||||
as DiaryStateLoaded;
|
||||
}
|
||||
|
||||
testWidgets('shows calories for loaded state and calls setSelectedMeal on tap', (tester) async {
|
||||
const caloriesInfo = ProductCaloriesInfoModel(
|
||||
id: 1,
|
||||
productName: 'P',
|
||||
calories: 100,
|
||||
proteins: 10,
|
||||
fats: 10,
|
||||
carbohydrates: 10,
|
||||
energyOnPortion: 100,
|
||||
proteinsOnPortion: 10,
|
||||
fatsOnPortion: 10,
|
||||
carbohydratesOnPortion: 10,
|
||||
);
|
||||
const portion = PortionModel(
|
||||
id: 1,
|
||||
productNutritionalValueId: 1,
|
||||
productCaloriesInfo: caloriesInfo,
|
||||
productName: 'P',
|
||||
quantity: 100,
|
||||
portion: 'gr',
|
||||
fromDiet: false,
|
||||
portionState: PortionState.eaten,
|
||||
);
|
||||
|
||||
final cubit = FakeDiaryCubit(
|
||||
loadedState(
|
||||
selectedMeal: 1,
|
||||
mealsPortions: const [
|
||||
[portion],
|
||||
[],
|
||||
[],
|
||||
[],
|
||||
[],
|
||||
],
|
||||
),
|
||||
);
|
||||
await tester.pumpWidget(wrap(cubit, const MealButtonWidget(mealNumber: 1)));
|
||||
await tester.pump();
|
||||
|
||||
expect(find.text('100 ккал'), findsOneWidget);
|
||||
await tester.tap(find.text('1'));
|
||||
await tester.pump();
|
||||
expect(cubit.lastSelectedMeal, 1);
|
||||
});
|
||||
|
||||
testWidgets('shows dash calories when meal empty', (tester) async {
|
||||
final cubit = FakeDiaryCubit(loadedState(selectedMeal: 1, mealsPortions: const [[], [], [], [], []]));
|
||||
await tester.pumpWidget(wrap(cubit, const MealButtonWidget(mealNumber: 3)));
|
||||
await tester.pump();
|
||||
expect(find.text('— ккал'), findsOneWidget);
|
||||
});
|
||||
|
||||
testWidgets('works for tomorrow-with-diet state', (tester) async {
|
||||
const caloriesInfo = ProductCaloriesInfoModel(
|
||||
id: 1,
|
||||
productName: 'P',
|
||||
calories: 100,
|
||||
proteins: 10,
|
||||
fats: 10,
|
||||
carbohydrates: 10,
|
||||
energyOnPortion: 100,
|
||||
proteinsOnPortion: 10,
|
||||
fatsOnPortion: 10,
|
||||
carbohydratesOnPortion: 10,
|
||||
);
|
||||
const portion = PortionModel(
|
||||
id: 1,
|
||||
productNutritionalValueId: 1,
|
||||
productCaloriesInfo: caloriesInfo,
|
||||
productName: 'P',
|
||||
quantity: 100,
|
||||
portion: 'gr',
|
||||
fromDiet: true,
|
||||
portionState: PortionState.eatenFromDiet,
|
||||
);
|
||||
const diary = DiaryFutureDietDayEntity(
|
||||
date: '2025-01-02',
|
||||
typeOfDiary: TypeOfDiaryModel(hasChosenDiet: true, hasChosenPlan: false, diaryState: null),
|
||||
meals: [
|
||||
MealModel(id: 1, time: '08:00', typeNumber: 1, satiety: 0, mood: 0, portions: [portion], photos: []),
|
||||
],
|
||||
diet: null,
|
||||
dayOfChosenDiet: 1,
|
||||
);
|
||||
final cubit = FakeDiaryCubit(DiaryState.loadedTomorrowWithDiet(diary: diary, selectedDay: DateTime(2025, 1, 2), selectedMeal: 1));
|
||||
|
||||
await tester.pumpWidget(wrap(cubit, const MealButtonWidget(mealNumber: 1)));
|
||||
await tester.pump();
|
||||
expect(find.text('100 ккал'), findsOneWidget);
|
||||
});
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,107 @@
|
||||
|
||||
import 'package:eatvio_app/core/theme/theme_helper.dart';
|
||||
import 'package:eatvio_app/features/diary/models/consultant_comment_model.dart';
|
||||
import 'package:eatvio_app/features/diary/presentation/bloc/consultant_comments_cubit/consultant_comments_cubit.dart';
|
||||
import 'package:eatvio_app/features/diary/presentation/bloc/consultant_comments_cubit/consultant_comments_state.dart';
|
||||
import 'package:eatvio_app/features/diary/presentation/widgets/meal_comment_widget.dart';
|
||||
import 'package:eatvio_app/generated/l10n.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:flutter_localizations/flutter_localizations.dart';
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
|
||||
class FakeConsultantCommentsCubit extends Cubit<ConsultantCommentsState> implements ConsultantCommentsCubit {
|
||||
FakeConsultantCommentsCubit(super.initialState);
|
||||
|
||||
int? lastMealNumber;
|
||||
String? lastMealComment;
|
||||
|
||||
@override
|
||||
void setMealComment({required int mealNumber, required String comment}) {
|
||||
lastMealNumber = mealNumber;
|
||||
lastMealComment = comment;
|
||||
}
|
||||
|
||||
@override
|
||||
dynamic noSuchMethod(Invocation invocation) => super.noSuchMethod(invocation);
|
||||
}
|
||||
|
||||
class _FakeAssetBundle extends CachingAssetBundle {
|
||||
_FakeAssetBundle(this.assets);
|
||||
final Map<String, String> assets;
|
||||
|
||||
@override
|
||||
Future<String> loadString(String key, {bool cache = true}) async {
|
||||
final value = assets[key];
|
||||
if (value == null) throw FlutterError('Asset not found: $key');
|
||||
return value;
|
||||
}
|
||||
|
||||
@override
|
||||
Future<ByteData> load(String key) async {
|
||||
final value = assets[key];
|
||||
if (value == null) throw FlutterError('Asset not found: $key');
|
||||
return ByteData.sublistView(Uint8List.fromList(value.codeUnits));
|
||||
}
|
||||
}
|
||||
|
||||
void main() {
|
||||
group('MealCommentWidget', () {
|
||||
const fakeSvg = '''
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="10" height="10" viewBox="0 0 10 10">
|
||||
<rect width="10" height="10" fill="red"/>
|
||||
</svg>
|
||||
''';
|
||||
|
||||
final bundle = _FakeAssetBundle({'assets/icons/error_icon.svg': fakeSvg});
|
||||
|
||||
Widget wrap(ConsultantCommentsCubit cubit, Widget child) {
|
||||
return DefaultAssetBundle(
|
||||
bundle: bundle,
|
||||
child: MaterialApp(
|
||||
theme: ThemeHelper().themeData(),
|
||||
localizationsDelegates: const [
|
||||
S.delegate,
|
||||
GlobalMaterialLocalizations.delegate,
|
||||
GlobalWidgetsLocalizations.delegate,
|
||||
GlobalCupertinoLocalizations.delegate,
|
||||
],
|
||||
supportedLocales: const [Locale('ru')],
|
||||
home: BlocProvider<ConsultantCommentsCubit>.value(
|
||||
value: cubit,
|
||||
child: Scaffold(body: child),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
testWidgets('shows validation error when comment too short', (tester) async {
|
||||
final cubit = FakeConsultantCommentsCubit(
|
||||
ConsultantCommentsState(
|
||||
commentsModel: ConsultantCommentModel(
|
||||
comment: '',
|
||||
mealsComments: {'1': MealCommentModel(comment: 'a', typeNumber: 1)},
|
||||
),
|
||||
),
|
||||
);
|
||||
await tester.pumpWidget(wrap(cubit, const MealCommentWidget(selectedMeal: 1)));
|
||||
await tester.pump();
|
||||
expect(find.text('Минимум 3 символа'), findsOneWidget);
|
||||
});
|
||||
|
||||
testWidgets('renders and shows comment title', (tester) async {
|
||||
final cubit = FakeConsultantCommentsCubit(
|
||||
ConsultantCommentsState(
|
||||
commentsModel: ConsultantCommentModel(
|
||||
comment: '',
|
||||
mealsComments: {'1': MealCommentModel(comment: 'ok', typeNumber: 1)},
|
||||
),
|
||||
),
|
||||
);
|
||||
await tester.pumpWidget(wrap(cubit, const MealCommentWidget(selectedMeal: 1)));
|
||||
await tester.pump();
|
||||
expect(find.textContaining(S.current.commentForMeal), findsOneWidget);
|
||||
});
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,173 @@
|
||||
|
||||
import 'package:eatvio_app/core/theme/theme_helper.dart';
|
||||
import 'package:eatvio_app/features/consultations/models/consultation_model.dart';
|
||||
import 'package:eatvio_app/features/diary/entities/diary_day_entity.dart';
|
||||
import 'package:eatvio_app/features/diary/models/consultant_comment_model.dart';
|
||||
import 'package:eatvio_app/features/diary/models/consultant_review_model.dart';
|
||||
import 'package:eatvio_app/features/diary/models/diary_day_model.dart';
|
||||
import 'package:eatvio_app/features/diary/models/nutrition_model.dart';
|
||||
import 'package:eatvio_app/features/diary/presentation/bloc/consultant_comments_cubit/consultant_comments_cubit.dart';
|
||||
import 'package:eatvio_app/features/diary/presentation/bloc/consultant_comments_cubit/consultant_comments_state.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/meal_widget.dart';
|
||||
import 'package:eatvio_app/generated/l10n.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:flutter_localizations/flutter_localizations.dart';
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
|
||||
class FakeDiaryCubit extends Cubit<DiaryState> implements DiaryCubit {
|
||||
FakeDiaryCubit(super.initialState);
|
||||
@override
|
||||
dynamic noSuchMethod(Invocation invocation) => super.noSuchMethod(invocation);
|
||||
}
|
||||
|
||||
class FakeConsultantCommentsCubit extends Cubit<ConsultantCommentsState> implements ConsultantCommentsCubit {
|
||||
FakeConsultantCommentsCubit(super.initialState);
|
||||
@override
|
||||
dynamic noSuchMethod(Invocation invocation) => super.noSuchMethod(invocation);
|
||||
}
|
||||
|
||||
class _FakeAssetBundle extends CachingAssetBundle {
|
||||
_FakeAssetBundle(this.assets);
|
||||
final Map<String, String> assets;
|
||||
|
||||
@override
|
||||
Future<String> loadString(String key, {bool cache = true}) async {
|
||||
final value = assets[key];
|
||||
if (value == null) throw FlutterError('Asset not found: $key');
|
||||
return value;
|
||||
}
|
||||
|
||||
@override
|
||||
Future<ByteData> load(String key) async {
|
||||
final value = assets[key];
|
||||
if (value == null) throw FlutterError('Asset not found: $key');
|
||||
return ByteData.sublistView(Uint8List.fromList(value.codeUnits));
|
||||
}
|
||||
}
|
||||
|
||||
void main() {
|
||||
group('MealWidget smoke', () {
|
||||
const fakeSvg = '''
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="10" height="10" viewBox="0 0 10 10">
|
||||
<rect width="10" height="10" fill="red"/>
|
||||
</svg>
|
||||
''';
|
||||
|
||||
final bundle = _FakeAssetBundle({
|
||||
'assets/icons/satiety_emoji_left.svg': fakeSvg,
|
||||
'assets/icons/satiety_emoji_right.svg': fakeSvg,
|
||||
'assets/icons/mood_1.svg': fakeSvg,
|
||||
'assets/icons/mood_2.svg': fakeSvg,
|
||||
'assets/icons/error_icon.svg': fakeSvg,
|
||||
'assets/no_one_image_was_added.svg': fakeSvg,
|
||||
});
|
||||
|
||||
Widget wrap({required DiaryCubit diaryCubit, required ConsultantCommentsCubit commentsCubit, required Widget child}) {
|
||||
return DefaultAssetBundle(
|
||||
bundle: bundle,
|
||||
child: MaterialApp(
|
||||
theme: ThemeHelper().themeData(),
|
||||
localizationsDelegates: const [
|
||||
S.delegate,
|
||||
GlobalMaterialLocalizations.delegate,
|
||||
GlobalWidgetsLocalizations.delegate,
|
||||
GlobalCupertinoLocalizations.delegate,
|
||||
],
|
||||
supportedLocales: const [Locale('ru')],
|
||||
home: MediaQuery(
|
||||
data: const MediaQueryData(size: Size(500, 900)),
|
||||
child: MultiBlocProvider(
|
||||
providers: [
|
||||
BlocProvider<DiaryCubit>.value(value: diaryCubit),
|
||||
BlocProvider<ConsultantCommentsCubit>.value(value: commentsCubit),
|
||||
],
|
||||
child: Scaffold(body: SingleChildScrollView(child: child)),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
DiaryStateLoaded loadedState({required bool consultantView, required bool showMealCommentFromReview}) {
|
||||
const meals = [
|
||||
MealModel(id: 1, time: '08:00:00', typeNumber: 1, satiety: 0, mood: 0, portions: [], photos: []),
|
||||
MealModel(id: 2, time: '09:00:00', typeNumber: 2, satiety: 0, mood: 0, portions: [], photos: []),
|
||||
MealModel(id: 3, time: '10:00:00', typeNumber: 3, satiety: 0, mood: 0, portions: [], photos: []),
|
||||
MealModel(id: 4, time: '11:00:00', typeNumber: 4, satiety: 0, mood: 0, portions: [], photos: []),
|
||||
MealModel(id: 5, time: '12:00:00', typeNumber: 5, satiety: 0, mood: 0, portions: [], photos: []),
|
||||
];
|
||||
|
||||
final diary = DiaryDayEntity(
|
||||
id: 1,
|
||||
date: '2025-01-01',
|
||||
wakeup: '07:00',
|
||||
retire: '23:00',
|
||||
closed: 0,
|
||||
comment: '',
|
||||
typeOfDiary: const TypeOfDiaryModel(hasChosenDiet: false, hasChosenPlan: false, diaryState: DiaryStateEnum.started),
|
||||
meals: meals,
|
||||
chosenPlanId: null,
|
||||
dayOfChosenDiet: null,
|
||||
diet: null,
|
||||
);
|
||||
const nutrition = NutritionDataModel(
|
||||
dailyNutritionIntake: NutritionModel(energy: 0, proteins: 0, fats: 0, carbohydrates: 0),
|
||||
dailyNutritionRecommendation: NutritionModel(energy: 0, proteins: 0, fats: 0, carbohydrates: 0),
|
||||
);
|
||||
|
||||
final review = ConsultantReviewModel(
|
||||
planName: 'Plan',
|
||||
planDuration: 10,
|
||||
currentPlanDay: 1,
|
||||
consultantComments: ConsultantComments(
|
||||
dayComment: null,
|
||||
mealsComments: showMealCommentFromReview ? [MealComment(comment: 'Comment', typeNo: 1)] : const [],
|
||||
portionsComments: const [],
|
||||
),
|
||||
);
|
||||
|
||||
return DiaryState.loaded(
|
||||
diary: diary,
|
||||
nutrition: nutrition,
|
||||
selectedDay: DateTime(2025, 1, 1),
|
||||
selectedMeal: 1,
|
||||
isConsultantView: consultantView,
|
||||
isDiaryFinished: false,
|
||||
isDiaryCommented: false,
|
||||
consultantReview: review,
|
||||
)
|
||||
as DiaryStateLoaded;
|
||||
}
|
||||
|
||||
testWidgets('renders base meal widget content', (tester) async {
|
||||
final diaryCubit = FakeDiaryCubit(loadedState(consultantView: false, showMealCommentFromReview: false));
|
||||
final commentsCubit = FakeConsultantCommentsCubit(ConsultantCommentsState(commentsModel: ConsultantCommentModel(comment: '')));
|
||||
|
||||
await tester.pumpWidget(wrap(diaryCubit: diaryCubit, commentsCubit: commentsCubit, child: const MealWidget(showMood: true, showAddButton: true)));
|
||||
await tester.pump();
|
||||
|
||||
expect(find.text('Прием пищи'), findsOneWidget);
|
||||
expect(find.byType(MealWidget), findsOneWidget);
|
||||
});
|
||||
|
||||
testWidgets('renders consultant meal comment widgets', (tester) async {
|
||||
final diaryCubit = FakeDiaryCubit(loadedState(consultantView: true, showMealCommentFromReview: true));
|
||||
final commentsCubit = FakeConsultantCommentsCubit(
|
||||
ConsultantCommentsState(
|
||||
commentsModel: ConsultantCommentModel(comment: '', mealsComments: const {}),
|
||||
),
|
||||
);
|
||||
|
||||
await tester.pumpWidget(wrap(diaryCubit: diaryCubit, commentsCubit: commentsCubit, child: const MealWidget(showMood: false, showAddButton: false)));
|
||||
await tester.pump();
|
||||
|
||||
|
||||
expect(find.textContaining(S.current.mealComment), findsOneWidget);
|
||||
expect(find.text('Comment'), findsOneWidget);
|
||||
});
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,233 @@
|
||||
import 'dart:async';
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:eatvio_app/core/app_config.dart';
|
||||
import 'package:eatvio_app/core/app_initializer.dart';
|
||||
import 'package:eatvio_app/core/locator_service.dart';
|
||||
import 'package:eatvio_app/core/theme/theme_helper.dart';
|
||||
import 'package:eatvio_app/features/diary/entities/diary_day_entity.dart';
|
||||
import 'package:eatvio_app/features/diary/models/diary_day_model.dart';
|
||||
import 'package:eatvio_app/features/diary/models/nutrition_model.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/photo_gallery_widget.dart';
|
||||
import 'package:eatvio_app/generated/l10n.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:flutter_dotenv/flutter_dotenv.dart';
|
||||
import 'package:flutter_localizations/flutter_localizations.dart';
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:flutter_svg/flutter_svg.dart';
|
||||
|
||||
class FakeDiaryCubit extends Cubit<DiaryState> implements DiaryCubit {
|
||||
FakeDiaryCubit(super.initialState);
|
||||
|
||||
@override
|
||||
dynamic noSuchMethod(Invocation invocation) => super.noSuchMethod(invocation);
|
||||
}
|
||||
|
||||
class _FakeHttpClient implements HttpClient {
|
||||
bool _autoUncompress = true;
|
||||
|
||||
@override
|
||||
bool get autoUncompress => _autoUncompress;
|
||||
|
||||
@override
|
||||
set autoUncompress(bool value) {
|
||||
_autoUncompress = value;
|
||||
}
|
||||
|
||||
@override
|
||||
Future<HttpClientRequest> getUrl(Uri url) async => _FakeHttpClientRequest();
|
||||
|
||||
@override
|
||||
dynamic noSuchMethod(Invocation invocation) => super.noSuchMethod(invocation);
|
||||
}
|
||||
|
||||
class _FakeHttpClientRequest implements HttpClientRequest {
|
||||
@override
|
||||
Future<HttpClientResponse> close() async => _FakeHttpClientResponse();
|
||||
|
||||
@override
|
||||
dynamic noSuchMethod(Invocation invocation) => super.noSuchMethod(invocation);
|
||||
}
|
||||
|
||||
class _FakeHttpClientResponse extends Stream<List<int>> implements HttpClientResponse {
|
||||
// 1x1 transparent PNG.
|
||||
static final List<int> _png = <int>[
|
||||
0x89,
|
||||
0x50,
|
||||
0x4E,
|
||||
0x47,
|
||||
0x0D,
|
||||
0x0A,
|
||||
0x1A,
|
||||
0x0A,
|
||||
0x00,
|
||||
0x00,
|
||||
0x00,
|
||||
0x0D,
|
||||
0x49,
|
||||
0x48,
|
||||
0x44,
|
||||
0x52,
|
||||
0x00,
|
||||
0x00,
|
||||
0x00,
|
||||
0x01,
|
||||
0x00,
|
||||
0x00,
|
||||
0x00,
|
||||
0x01,
|
||||
0x08,
|
||||
0x06,
|
||||
0x00,
|
||||
0x00,
|
||||
0x00,
|
||||
0x1F,
|
||||
0x15,
|
||||
0xC4,
|
||||
0x89,
|
||||
0x00,
|
||||
0x00,
|
||||
0x00,
|
||||
0x0A,
|
||||
0x49,
|
||||
0x44,
|
||||
0x41,
|
||||
0x54,
|
||||
0x78,
|
||||
0x9C,
|
||||
0x63,
|
||||
0x00,
|
||||
0x01,
|
||||
0x00,
|
||||
0x00,
|
||||
0x05,
|
||||
0x00,
|
||||
0x01,
|
||||
0x0D,
|
||||
0x0A,
|
||||
0x2D,
|
||||
0xB4,
|
||||
0x00,
|
||||
0x00,
|
||||
0x00,
|
||||
0x00,
|
||||
0x49,
|
||||
0x45,
|
||||
0x4E,
|
||||
0x44,
|
||||
0xAE,
|
||||
0x42,
|
||||
0x60,
|
||||
0x82,
|
||||
];
|
||||
|
||||
@override
|
||||
int get statusCode => 200;
|
||||
|
||||
@override
|
||||
int get contentLength => _png.length;
|
||||
|
||||
@override
|
||||
HttpClientResponseCompressionState get compressionState => HttpClientResponseCompressionState.notCompressed;
|
||||
|
||||
@override
|
||||
StreamSubscription<List<int>> listen(void Function(List<int>)? onData, {Function? onError, void Function()? onDone, bool? cancelOnError}) {
|
||||
return Stream<List<int>>.fromIterable([_png]).listen(onData, onError: onError, onDone: onDone, cancelOnError: cancelOnError);
|
||||
}
|
||||
|
||||
@override
|
||||
dynamic noSuchMethod(Invocation invocation) => super.noSuchMethod(invocation);
|
||||
}
|
||||
|
||||
void main() {
|
||||
group('PhotoGalleryWidget smoke', () {
|
||||
setUpAll(() async {
|
||||
dotenv.loadFromString(envString: 'DEV_BASE_URL=http://localhost\nBASE_URL=http://localhost');
|
||||
});
|
||||
|
||||
setUp(() async {
|
||||
await sl.reset();
|
||||
sl.registerLazySingleton<AppConfig>(() => AppConfig(buildType: AppBuildType.tests)..setBaseUrl('http://localhost'));
|
||||
});
|
||||
|
||||
Widget wrap(DiaryCubit cubit) {
|
||||
return MaterialApp(
|
||||
theme: ThemeHelper().themeData(),
|
||||
localizationsDelegates: const [
|
||||
S.delegate,
|
||||
GlobalMaterialLocalizations.delegate,
|
||||
GlobalWidgetsLocalizations.delegate,
|
||||
GlobalCupertinoLocalizations.delegate,
|
||||
],
|
||||
supportedLocales: const [Locale('ru')],
|
||||
home: BlocProvider<DiaryCubit>.value(
|
||||
value: cubit,
|
||||
child: const Scaffold(body: PhotoGalleryWidget()),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
DiaryStateLoaded loadedState({required List<DiaryPhotoModel> photos, bool isConsultantView = false}) {
|
||||
final diary = DiaryDayEntity(
|
||||
id: 1,
|
||||
date: '2025-01-01',
|
||||
wakeup: '06:30:00',
|
||||
retire: '22:30:00',
|
||||
closed: 0,
|
||||
comment: '',
|
||||
typeOfDiary: const TypeOfDiaryModel(hasChosenDiet: false, hasChosenPlan: false, diaryState: null),
|
||||
meals: [MealModel(id: 1, time: '08:00', typeNumber: 1, satiety: 0, mood: 0, portions: const [], photos: photos)],
|
||||
chosenPlanId: null,
|
||||
dayOfChosenDiet: null,
|
||||
diet: null,
|
||||
);
|
||||
|
||||
const nutrition = NutritionDataModel(
|
||||
dailyNutritionIntake: NutritionModel(energy: 0, proteins: 0, fats: 0, carbohydrates: 0),
|
||||
dailyNutritionRecommendation: NutritionModel(energy: 1, proteins: 1, fats: 1, carbohydrates: 1),
|
||||
);
|
||||
|
||||
return DiaryState.loaded(
|
||||
diary: diary,
|
||||
nutrition: nutrition,
|
||||
selectedDay: DateTime(2025, 1, 1),
|
||||
selectedMeal: 1,
|
||||
isConsultantView: isConsultantView,
|
||||
isDiaryFinished: false,
|
||||
isDiaryCommented: false,
|
||||
)
|
||||
as DiaryStateLoaded;
|
||||
}
|
||||
|
||||
testWidgets('renders empty placeholder when no photos', (tester) async {
|
||||
final state = loadedState(photos: const []);
|
||||
final cubit = FakeDiaryCubit(state);
|
||||
|
||||
await tester.pumpWidget(wrap(cubit));
|
||||
await tester.pump();
|
||||
|
||||
expect(find.byType(PhotoGalleryWidget), findsOneWidget);
|
||||
|
||||
expect(find.byType(SvgPicture), findsOneWidget);
|
||||
|
||||
await cubit.close();
|
||||
});
|
||||
|
||||
testWidgets('renders gallery when photos exist', (tester) async {
|
||||
final photos = [const DiaryPhotoModel(id: 1, originalName: 'a.png', path: 'chat_files/a.png', size: 1)];
|
||||
final state = loadedState(photos: photos);
|
||||
final cubit = FakeDiaryCubit(state);
|
||||
|
||||
await HttpOverrides.runZoned(() async {
|
||||
await tester.pumpWidget(wrap(cubit));
|
||||
await tester.pumpAndSettle(const Duration(milliseconds: 200));
|
||||
expect(find.byType(PhotoGalleryWidget), findsOneWidget);
|
||||
}, createHttpClient: (_) => _FakeHttpClient());
|
||||
|
||||
await cubit.close();
|
||||
});
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,112 @@
|
||||
|
||||
import 'package:eatvio_app/core/theme/theme_helper.dart';
|
||||
import 'package:eatvio_app/features/diary/models/consultant_comment_model.dart';
|
||||
import 'package:eatvio_app/features/diary/presentation/bloc/consultant_comments_cubit/consultant_comments_cubit.dart';
|
||||
import 'package:eatvio_app/features/diary/presentation/bloc/consultant_comments_cubit/consultant_comments_state.dart';
|
||||
import 'package:eatvio_app/features/diary/presentation/widgets/portion_comment_widget.dart';
|
||||
import 'package:eatvio_app/generated/l10n.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:flutter_localizations/flutter_localizations.dart';
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
|
||||
class FakeConsultantCommentsCubit extends Cubit<ConsultantCommentsState> implements ConsultantCommentsCubit {
|
||||
FakeConsultantCommentsCubit(super.initialState);
|
||||
@override
|
||||
dynamic noSuchMethod(Invocation invocation) => super.noSuchMethod(invocation);
|
||||
}
|
||||
|
||||
class _FakeAssetBundle extends CachingAssetBundle {
|
||||
_FakeAssetBundle(this.assets);
|
||||
final Map<String, String> assets;
|
||||
|
||||
@override
|
||||
Future<String> loadString(String key, {bool cache = true}) async {
|
||||
final value = assets[key];
|
||||
if (value == null) throw FlutterError('Asset not found: $key');
|
||||
return value;
|
||||
}
|
||||
|
||||
@override
|
||||
Future<ByteData> load(String key) async {
|
||||
final value = assets[key];
|
||||
if (value == null) throw FlutterError('Asset not found: $key');
|
||||
return ByteData.sublistView(Uint8List.fromList(value.codeUnits));
|
||||
}
|
||||
}
|
||||
|
||||
void main() {
|
||||
group('PortionCommentWidget', () {
|
||||
const fakeSvg = '''
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="10" height="10" viewBox="0 0 10 10">
|
||||
<rect width="10" height="10" fill="red"/>
|
||||
</svg>
|
||||
''';
|
||||
|
||||
final bundle = _FakeAssetBundle({'assets/icons/error_icon.svg': fakeSvg});
|
||||
|
||||
Widget wrap(ConsultantCommentsCubit cubit, Widget child) {
|
||||
return DefaultAssetBundle(
|
||||
bundle: bundle,
|
||||
child: MaterialApp(
|
||||
theme: ThemeHelper().themeData(),
|
||||
localizationsDelegates: const [
|
||||
S.delegate,
|
||||
GlobalMaterialLocalizations.delegate,
|
||||
GlobalWidgetsLocalizations.delegate,
|
||||
GlobalCupertinoLocalizations.delegate,
|
||||
],
|
||||
supportedLocales: const [Locale('ru')],
|
||||
home: BlocProvider<ConsultantCommentsCubit>.value(
|
||||
value: cubit,
|
||||
child: Scaffold(body: child),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
testWidgets('shows CommentContainer when disabled and comment provided', (tester) async {
|
||||
final cubit = FakeConsultantCommentsCubit(ConsultantCommentsState(commentsModel: ConsultantCommentModel(comment: '')));
|
||||
await tester.pumpWidget(wrap(cubit, const PortionCommentWidget(enabled: false, portionId: 1, comment: 'ok')));
|
||||
await tester.pump();
|
||||
expect(find.text('ok'), findsOneWidget);
|
||||
});
|
||||
|
||||
testWidgets('shows validation error when enabled and comment too short', (tester) async {
|
||||
final cubit = FakeConsultantCommentsCubit(
|
||||
ConsultantCommentsState(
|
||||
commentsModel: ConsultantCommentModel(
|
||||
comment: '',
|
||||
portions: [PortionCommentModel(id: 1, comment: 'a')],
|
||||
),
|
||||
),
|
||||
);
|
||||
await tester.pumpWidget(wrap(cubit, const PortionCommentWidget(enabled: true, portionId: 1)));
|
||||
await tester.pump();
|
||||
expect(find.text('Минимум 3 символа'), findsOneWidget);
|
||||
});
|
||||
|
||||
testWidgets('updates controller text on portionId change', (tester) async {
|
||||
final cubit = FakeConsultantCommentsCubit(
|
||||
ConsultantCommentsState(
|
||||
commentsModel: ConsultantCommentModel(
|
||||
comment: '',
|
||||
portions: [
|
||||
PortionCommentModel(id: 1, comment: 'first'),
|
||||
PortionCommentModel(id: 2, comment: 'second'),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
await tester.pumpWidget(wrap(cubit, const PortionCommentWidget(enabled: true, portionId: 1)));
|
||||
await tester.pump();
|
||||
expect(find.text('first'), findsOneWidget);
|
||||
|
||||
await tester.pumpWidget(wrap(cubit, const PortionCommentWidget(enabled: true, portionId: 2)));
|
||||
await tester.pump();
|
||||
expect(find.text('second'), findsOneWidget);
|
||||
});
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,244 @@
|
||||
|
||||
import 'package:eatvio_app/core/theme/theme_helper.dart';
|
||||
import 'package:eatvio_app/features/consultations/models/consultation_model.dart';
|
||||
import 'package:eatvio_app/features/diary/entities/diary_day_entity.dart';
|
||||
import 'package:eatvio_app/features/diary/entities/diary_future_diet_day_entity.dart';
|
||||
import 'package:eatvio_app/features/diary/models/consultant_comment_model.dart';
|
||||
import 'package:eatvio_app/features/diary/models/consultant_review_model.dart';
|
||||
import 'package:eatvio_app/features/diary/models/diary_day_model.dart';
|
||||
import 'package:eatvio_app/features/diary/models/nutrition_model.dart';
|
||||
import 'package:eatvio_app/features/diary/presentation/bloc/consultant_comments_cubit/consultant_comments_cubit.dart';
|
||||
import 'package:eatvio_app/features/diary/presentation/bloc/consultant_comments_cubit/consultant_comments_state.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/comment_container.dart';
|
||||
import 'package:eatvio_app/features/diary/presentation/widgets/portion_comment_widget.dart';
|
||||
import 'package:eatvio_app/features/diary/presentation/widgets/product_widget.dart';
|
||||
import 'package:eatvio_app/generated/l10n.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:flutter_localizations/flutter_localizations.dart';
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
|
||||
class FakeDiaryCubit extends Cubit<DiaryState> implements DiaryCubit {
|
||||
FakeDiaryCubit(super.initialState);
|
||||
@override
|
||||
dynamic noSuchMethod(Invocation invocation) => super.noSuchMethod(invocation);
|
||||
}
|
||||
|
||||
class FakeConsultantCommentsCubit extends Cubit<ConsultantCommentsState> implements ConsultantCommentsCubit {
|
||||
FakeConsultantCommentsCubit(super.initialState);
|
||||
@override
|
||||
dynamic noSuchMethod(Invocation invocation) => super.noSuchMethod(invocation);
|
||||
}
|
||||
|
||||
class _FakeAssetBundle extends CachingAssetBundle {
|
||||
_FakeAssetBundle(this.assets);
|
||||
final Map<String, String> assets;
|
||||
|
||||
@override
|
||||
Future<String> loadString(String key, {bool cache = true}) async {
|
||||
final value = assets[key];
|
||||
if (value == null) throw FlutterError('Asset not found: $key');
|
||||
return value;
|
||||
}
|
||||
|
||||
@override
|
||||
Future<ByteData> load(String key) async {
|
||||
final value = assets[key];
|
||||
if (value == null) throw FlutterError('Asset not found: $key');
|
||||
return ByteData.sublistView(Uint8List.fromList(value.codeUnits));
|
||||
}
|
||||
}
|
||||
|
||||
void main() {
|
||||
group('ProductWidget smoke', () {
|
||||
const fakeSvg = '''
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="10" height="10" viewBox="0 0 10 10">
|
||||
<rect width="10" height="10" fill="red"/>
|
||||
</svg>
|
||||
''';
|
||||
|
||||
final bundle = _FakeAssetBundle({'assets/icons/revert_product.svg': fakeSvg, 'assets/icons/edit.svg': fakeSvg});
|
||||
|
||||
Widget wrap({required DiaryCubit diaryCubit, required ConsultantCommentsCubit consultantCommentsCubit, required Widget child}) {
|
||||
return DefaultAssetBundle(
|
||||
bundle: bundle,
|
||||
child: MaterialApp(
|
||||
theme: ThemeHelper().themeData(),
|
||||
localizationsDelegates: const [
|
||||
S.delegate,
|
||||
GlobalMaterialLocalizations.delegate,
|
||||
GlobalWidgetsLocalizations.delegate,
|
||||
GlobalCupertinoLocalizations.delegate,
|
||||
],
|
||||
supportedLocales: const [Locale('ru')],
|
||||
home: MultiBlocProvider(
|
||||
providers: [
|
||||
BlocProvider<DiaryCubit>.value(value: diaryCubit),
|
||||
BlocProvider<ConsultantCommentsCubit>.value(value: consultantCommentsCubit),
|
||||
],
|
||||
child: Scaffold(body: child),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
DiaryStateLoaded loadedState({required PortionModel portion, required bool consultantView}) {
|
||||
final diary = DiaryDayEntity(
|
||||
id: 1,
|
||||
date: '2025-01-01',
|
||||
wakeup: '07:00',
|
||||
retire: '23:00',
|
||||
closed: 0,
|
||||
comment: '',
|
||||
typeOfDiary: const TypeOfDiaryModel(hasChosenDiet: false, hasChosenPlan: true, diaryState: DiaryStateEnum.started),
|
||||
meals: [
|
||||
MealModel(id: 1, time: '08:00', typeNumber: 1, satiety: 0, mood: 0, portions: [portion], photos: const []),
|
||||
],
|
||||
chosenPlanId: 1,
|
||||
dayOfChosenDiet: null,
|
||||
diet: null,
|
||||
);
|
||||
|
||||
const nutrition = NutritionDataModel(
|
||||
dailyNutritionIntake: NutritionModel(energy: 0, proteins: 0, fats: 0, carbohydrates: 0),
|
||||
dailyNutritionRecommendation: NutritionModel(energy: 0, proteins: 0, fats: 0, carbohydrates: 0),
|
||||
);
|
||||
|
||||
return DiaryState.loaded(
|
||||
diary: diary,
|
||||
nutrition: nutrition,
|
||||
selectedDay: DateTime(2025, 1, 1),
|
||||
selectedMeal: 1,
|
||||
isConsultantView: consultantView,
|
||||
isDiaryFinished: false,
|
||||
isDiaryCommented: false,
|
||||
consultantReview: ConsultantReviewModel(
|
||||
planName: 'Plan',
|
||||
planDuration: 10,
|
||||
currentPlanDay: 1,
|
||||
consultantComments: ConsultantComments(dayComment: null, mealsComments: const [], portionsComments: const []),
|
||||
),
|
||||
)
|
||||
as DiaryStateLoaded;
|
||||
}
|
||||
|
||||
testWidgets('renders tomorrow-with-diet tile', (tester) async {
|
||||
const portion = PortionModel(
|
||||
id: 1,
|
||||
productNutritionalValueId: null,
|
||||
productCaloriesInfo: null,
|
||||
productName: 'Apple',
|
||||
quantity: 1,
|
||||
portion: 'piece',
|
||||
fromDiet: true,
|
||||
portionState: PortionState.filledDiet,
|
||||
);
|
||||
final diaryCubit = FakeDiaryCubit(
|
||||
DiaryState.loadedTomorrowWithDiet(
|
||||
diary: const DiaryFutureDietDayEntity(
|
||||
date: '2025-01-02',
|
||||
typeOfDiary: TypeOfDiaryModel(hasChosenDiet: true, hasChosenPlan: false, diaryState: null),
|
||||
meals: [
|
||||
MealModel(id: 1, time: '08:00', typeNumber: 1, satiety: 0, mood: 0, portions: [portion], photos: []),
|
||||
],
|
||||
diet: null,
|
||||
dayOfChosenDiet: 1,
|
||||
),
|
||||
selectedDay: DateTime(2025, 1, 2),
|
||||
selectedMeal: 1,
|
||||
),
|
||||
);
|
||||
final commentsCubit = FakeConsultantCommentsCubit(ConsultantCommentsState(commentsModel: ConsultantCommentModel(comment: '')));
|
||||
|
||||
await tester.pumpWidget(
|
||||
wrap(
|
||||
diaryCubit: diaryCubit,
|
||||
consultantCommentsCubit: commentsCubit,
|
||||
child: const ProductWidget(portion: portion),
|
||||
),
|
||||
);
|
||||
await tester.pump();
|
||||
|
||||
expect(find.text('Apple'), findsOneWidget);
|
||||
expect(find.byType(ListTile), findsOneWidget);
|
||||
});
|
||||
|
||||
testWidgets('renders eatenDeleted state with revert icon', (tester) async {
|
||||
const portion = PortionModel(
|
||||
id: 1,
|
||||
productNutritionalValueId: null,
|
||||
productCaloriesInfo: null,
|
||||
productName: 'Deleted',
|
||||
quantity: 100,
|
||||
portion: 'gr',
|
||||
fromDiet: false,
|
||||
portionState: PortionState.eatenDeleted,
|
||||
);
|
||||
final diaryCubit = FakeDiaryCubit(loadedState(portion: portion, consultantView: false));
|
||||
final commentsCubit = FakeConsultantCommentsCubit(ConsultantCommentsState(commentsModel: ConsultantCommentModel(comment: '')));
|
||||
|
||||
await tester.pumpWidget(
|
||||
wrap(
|
||||
diaryCubit: diaryCubit,
|
||||
consultantCommentsCubit: commentsCubit,
|
||||
child: const ProductWidget(portion: portion),
|
||||
),
|
||||
);
|
||||
await tester.pump();
|
||||
|
||||
expect(find.text('Deleted'), findsOneWidget);
|
||||
|
||||
expect(find.byType(ProductWidget), findsOneWidget);
|
||||
});
|
||||
|
||||
testWidgets('consultant view toggles comment widget and shows existing comment container', (tester) async {
|
||||
const portion = PortionModel(
|
||||
id: 1,
|
||||
productNutritionalValueId: null,
|
||||
productCaloriesInfo: null,
|
||||
productName: 'Portion',
|
||||
quantity: 100,
|
||||
portion: 'gr',
|
||||
fromDiet: false,
|
||||
portionState: PortionState.filledDiet,
|
||||
);
|
||||
final state = loadedState(portion: portion, consultantView: true).copyWith(
|
||||
consultantReview: ConsultantReviewModel(
|
||||
planName: 'Plan',
|
||||
planDuration: 10,
|
||||
currentPlanDay: 1,
|
||||
consultantComments: ConsultantComments(
|
||||
dayComment: null,
|
||||
mealsComments: const [],
|
||||
portionsComments: [PortionComment(portionId: 1, comment: 'Looks good')],
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
final diaryCubit = FakeDiaryCubit(state);
|
||||
final commentsCubit = FakeConsultantCommentsCubit(ConsultantCommentsState(commentsModel: ConsultantCommentModel(comment: '')));
|
||||
|
||||
await tester.pumpWidget(
|
||||
wrap(
|
||||
diaryCubit: diaryCubit,
|
||||
consultantCommentsCubit: commentsCubit,
|
||||
child: const ProductWidget(portion: portion),
|
||||
),
|
||||
);
|
||||
await tester.pump();
|
||||
|
||||
|
||||
expect(find.byType(CommentContainer), findsOneWidget);
|
||||
expect(find.text('Looks good'), findsOneWidget);
|
||||
|
||||
// Tap edit icon to enable PortionCommentWidget.
|
||||
await tester.tap(find.byType(GestureDetector).first);
|
||||
await tester.pump();
|
||||
|
||||
expect(find.byType(PortionCommentWidget), findsOneWidget);
|
||||
});
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,131 @@
|
||||
import 'package:eatvio_app/core/theme/theme_helper.dart';
|
||||
import 'package:eatvio_app/features/diary/entities/diary_future_diet_day_entity.dart';
|
||||
import 'package:eatvio_app/features/diary/models/diary_day_model.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/meal_summary_widget.dart';
|
||||
import 'package:eatvio_app/features/diary/presentation/widgets/products_list_widget.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
|
||||
class FakeDiaryCubit extends Cubit<DiaryState> implements DiaryCubit {
|
||||
FakeDiaryCubit(super.initialState);
|
||||
|
||||
@override
|
||||
dynamic noSuchMethod(Invocation invocation) => super.noSuchMethod(invocation);
|
||||
}
|
||||
|
||||
void main() {
|
||||
group('ProductsListWidget smoke', () {
|
||||
Widget wrap(DiaryCubit cubit, Widget child) {
|
||||
return MaterialApp(
|
||||
theme: ThemeHelper().themeData(),
|
||||
home: BlocProvider<DiaryCubit>.value(
|
||||
value: cubit,
|
||||
child: Scaffold(body: child),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
testWidgets('renders tomorrow-with-diet list with summary', (tester) async {
|
||||
const caloriesInfo = ProductCaloriesInfoModel(
|
||||
id: 1,
|
||||
productName: 'Apple',
|
||||
calories: 52,
|
||||
proteins: 0.3,
|
||||
fats: 0.2,
|
||||
carbohydrates: 14,
|
||||
energyOnPortion: 52,
|
||||
proteinsOnPortion: 0.3,
|
||||
fatsOnPortion: 0.2,
|
||||
carbohydratesOnPortion: 14,
|
||||
);
|
||||
const portion = PortionModel(
|
||||
id: 1,
|
||||
productNutritionalValueId: 1,
|
||||
productCaloriesInfo: caloriesInfo,
|
||||
productName: 'Apple',
|
||||
quantity: 100,
|
||||
portion: 'gr',
|
||||
fromDiet: true,
|
||||
portionState: PortionState.eatenFromDiet,
|
||||
);
|
||||
|
||||
const futureDiary = DiaryFutureDietDayEntity(
|
||||
date: '2025-01-02',
|
||||
typeOfDiary: TypeOfDiaryModel(hasChosenDiet: true, hasChosenPlan: false, diaryState: null),
|
||||
meals: [
|
||||
MealModel(id: 1, time: '08:00', typeNumber: 1, satiety: 0, mood: 0, portions: [portion], photos: []),
|
||||
],
|
||||
diet: null,
|
||||
dayOfChosenDiet: 1,
|
||||
);
|
||||
|
||||
final state = DiaryState.loadedTomorrowWithDiet(diary: futureDiary, selectedDay: DateTime(2025, 1, 2), selectedMeal: 1);
|
||||
final cubit = FakeDiaryCubit(state);
|
||||
|
||||
await tester.pumpWidget(wrap(cubit, const ProductsListWidget()));
|
||||
await tester.pump();
|
||||
|
||||
expect(find.byType(ProductsListWidget), findsOneWidget);
|
||||
expect(find.text('Apple'), findsOneWidget);
|
||||
expect(find.byType(MealSummaryWidget), findsOneWidget);
|
||||
|
||||
await cubit.close();
|
||||
});
|
||||
|
||||
test('MealSummaryWidget.calculateMealTotals ignores deleted and non-eaten diet items', () {
|
||||
const caloriesInfo = ProductCaloriesInfoModel(
|
||||
id: 1,
|
||||
productName: 'P',
|
||||
calories: 100,
|
||||
proteins: 10,
|
||||
fats: 10,
|
||||
carbohydrates: 10,
|
||||
energyOnPortion: 100,
|
||||
proteinsOnPortion: 10,
|
||||
fatsOnPortion: 10,
|
||||
carbohydratesOnPortion: 10,
|
||||
);
|
||||
const portions = [
|
||||
PortionModel(
|
||||
id: 1,
|
||||
productNutritionalValueId: 1,
|
||||
productCaloriesInfo: caloriesInfo,
|
||||
productName: 'A',
|
||||
quantity: 100,
|
||||
portion: 'gr',
|
||||
fromDiet: true,
|
||||
portionState: PortionState.eatenFromDiet,
|
||||
),
|
||||
PortionModel(
|
||||
id: 2,
|
||||
productNutritionalValueId: 1,
|
||||
productCaloriesInfo: caloriesInfo,
|
||||
productName: 'B',
|
||||
quantity: 100,
|
||||
portion: 'gr',
|
||||
fromDiet: true,
|
||||
portionState: PortionState.filledDiet, // not eaten -> ignored
|
||||
),
|
||||
PortionModel(
|
||||
id: 3,
|
||||
productNutritionalValueId: 1,
|
||||
productCaloriesInfo: caloriesInfo,
|
||||
productName: 'C',
|
||||
quantity: 100,
|
||||
portion: 'gr',
|
||||
fromDiet: false,
|
||||
portionState: PortionState.eatenDeleted, // deleted -> ignored
|
||||
),
|
||||
];
|
||||
|
||||
final totals = MealSummaryWidget.calculateMealTotals(portions);
|
||||
expect(totals['calories']!.round(), 100);
|
||||
expect(totals['proteins']!.round(), 10);
|
||||
expect(totals['fats']!.round(), 10);
|
||||
expect(totals['carbohydrates']!.round(), 10);
|
||||
});
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,72 @@
|
||||
import 'package:eatvio_app/core/theme/theme_helper.dart';
|
||||
import 'package:eatvio_app/features/diary/entities/diary_day_entity.dart';
|
||||
import 'package:eatvio_app/features/diary/models/diary_day_model.dart';
|
||||
import 'package:eatvio_app/features/diary/models/nutrition_model.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/wakeup_and_retire_widget.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
|
||||
class FakeDiaryCubit extends Cubit<DiaryState> implements DiaryCubit {
|
||||
FakeDiaryCubit(super.initialState);
|
||||
@override
|
||||
dynamic noSuchMethod(Invocation invocation) => super.noSuchMethod(invocation);
|
||||
}
|
||||
|
||||
void main() {
|
||||
group('WakeupAndRetireWidget', () {
|
||||
Widget wrap(Widget child, DiaryCubit cubit) {
|
||||
return MaterialApp(
|
||||
theme: ThemeHelper().themeData(),
|
||||
home: BlocProvider<DiaryCubit>.value(
|
||||
value: cubit,
|
||||
child: Scaffold(body: child),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
testWidgets('hidden when isEnabled=false', (tester) async {
|
||||
final cubit = FakeDiaryCubit(const DiaryState.loading());
|
||||
await tester.pumpWidget(wrap(const WakeupAndRetireWidget(isEnabled: false), cubit));
|
||||
await tester.pump();
|
||||
expect(find.text('Подъём'), findsNothing);
|
||||
await cubit.close();
|
||||
});
|
||||
|
||||
testWidgets('renders wakeup/retire when state loaded', (tester) async {
|
||||
final diary = DiaryDayEntity(
|
||||
id: 1,
|
||||
date: '2025-01-01',
|
||||
wakeup: '06:30:00',
|
||||
retire: '22:30:00',
|
||||
closed: 0,
|
||||
comment: '',
|
||||
typeOfDiary: const TypeOfDiaryModel(hasChosenDiet: false, hasChosenPlan: false, diaryState: null),
|
||||
meals: const [MealModel(id: 1, time: '08:00', typeNumber: 1, satiety: 0, mood: 0, portions: [], photos: [])],
|
||||
chosenPlanId: null,
|
||||
dayOfChosenDiet: null,
|
||||
diet: null,
|
||||
);
|
||||
|
||||
const nutrition = NutritionDataModel(
|
||||
dailyNutritionIntake: NutritionModel(energy: 0, proteins: 0, fats: 0, carbohydrates: 0),
|
||||
dailyNutritionRecommendation: NutritionModel(energy: 1, proteins: 1, fats: 1, carbohydrates: 1),
|
||||
);
|
||||
|
||||
final state = DiaryState.loaded(diary: diary, nutrition: nutrition, selectedDay: DateTime(2025, 1, 1), selectedMeal: 1);
|
||||
final cubit = FakeDiaryCubit(state);
|
||||
|
||||
await tester.pumpWidget(wrap(const WakeupAndRetireWidget(isEnabled: true), cubit));
|
||||
await tester.pump();
|
||||
|
||||
expect(find.text('Подъём'), findsOneWidget);
|
||||
expect(find.text('Уход ко сну'), findsOneWidget);
|
||||
expect(find.textContaining('06:30'), findsOneWidget);
|
||||
expect(find.textContaining('22:30'), findsOneWidget);
|
||||
|
||||
await cubit.close();
|
||||
});
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,192 @@
|
||||
import 'package:eatvio_app/core/app_config.dart';
|
||||
import 'package:eatvio_app/core/app_initializer.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/cubit/about_diet_cubit.dart';
|
||||
import 'package:eatvio_app/features/diets/presentation/cubit/about_diet_state.dart';
|
||||
import 'package:eatvio_app/features/diets/presentation/pages/about_diet_screen.dart';
|
||||
import 'package:eatvio_app/features/feed/models/diets_pagination_model.dart';
|
||||
import 'package:eatvio_app/core/comments/comments_cubit/comments_cubit.dart';
|
||||
import 'package:eatvio_app/core/comments/comments_cubit/comments_state.dart';
|
||||
import 'package:eatvio_app/features/feed/presentation/bloc/feed_cubit/feed_cubit.dart';
|
||||
import 'package:eatvio_app/generated/l10n.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:flutter_dotenv/flutter_dotenv.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 'package:network_image_mock/network_image_mock.dart';
|
||||
|
||||
import 'about_diet_screen_smoke_test.mocks.dart';
|
||||
|
||||
@GenerateNiceMocks([MockSpec<AboutDietCubit>(), MockSpec<FeedCubit>(), MockSpec<CommentsCubit>()])
|
||||
void main() {
|
||||
group('AboutDietScreen smoke', () {
|
||||
setUpAll(() async {
|
||||
dotenv.loadFromString(envString: 'DEV_BASE_URL=http://localhost\nBASE_URL=http://localhost');
|
||||
});
|
||||
|
||||
setUp(() async {
|
||||
await sl.reset();
|
||||
sl.registerLazySingleton<AppConfig>(() => AppConfig(buildType: AppBuildType.tests)..setBaseUrl('http://localhost'));
|
||||
});
|
||||
|
||||
void registerMocks(AboutDietCubit dietCubit, CommentsCubit commentsCubit) {
|
||||
sl.registerLazySingleton<AboutDietCubit>(() => dietCubit);
|
||||
sl.registerLazySingleton<CommentsCubit>(() => commentsCubit);
|
||||
}
|
||||
|
||||
const testDiet = Diet(
|
||||
id: 1,
|
||||
slug: 'test-diet',
|
||||
name: 'Test Diet',
|
||||
image: 'https://example.com/image.jpg',
|
||||
duration: 7,
|
||||
state: DietState.active,
|
||||
type: DietType(veg: true, light: false, carb: false, meat: false),
|
||||
details: 'Test details',
|
||||
dailySchedule: {},
|
||||
author: Author(
|
||||
authorSlug: 'author',
|
||||
authorName: 'Author Name',
|
||||
authorProfileIcon: 'https://example.com/icon.jpg',
|
||||
authorIsMe: false,
|
||||
isEntitySubscribedByMe: null,
|
||||
authorPlaceInRating: null,
|
||||
),
|
||||
totalLikes: 10,
|
||||
totalViews: 100,
|
||||
totalComments: 5,
|
||||
wasLikedByMe: false,
|
||||
isFavorite: false,
|
||||
dailyNutrition: [],
|
||||
);
|
||||
|
||||
Widget wrap(AboutDietCubit cubit, FeedCubit feedCubit, CommentsCubit commentsCubit, Widget child) {
|
||||
return MaterialApp(
|
||||
theme: ThemeHelper().themeData(),
|
||||
localizationsDelegates: const [
|
||||
S.delegate,
|
||||
GlobalMaterialLocalizations.delegate,
|
||||
GlobalWidgetsLocalizations.delegate,
|
||||
GlobalCupertinoLocalizations.delegate,
|
||||
],
|
||||
supportedLocales: const [Locale('ru')],
|
||||
home: MultiBlocProvider(
|
||||
providers: [
|
||||
BlocProvider<AboutDietCubit>.value(value: cubit),
|
||||
BlocProvider<FeedCubit>.value(value: feedCubit),
|
||||
BlocProvider<CommentsCubit>.value(value: commentsCubit),
|
||||
],
|
||||
child: child,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
testWidgets('renders loading state', (tester) async {
|
||||
final cubit = MockAboutDietCubit();
|
||||
final feedCubit = MockFeedCubit();
|
||||
final commentsCubit = MockCommentsCubit();
|
||||
when(cubit.state).thenReturn(const AboutDietState.loading());
|
||||
when(cubit.stream).thenAnswer((_) => const Stream<AboutDietState>.empty());
|
||||
when(cubit.close()).thenAnswer((_) async => Future<void>.value());
|
||||
when(feedCubit.stream).thenAnswer((_) => const Stream.empty());
|
||||
when(feedCubit.close()).thenAnswer((_) async => Future<void>.value());
|
||||
when(commentsCubit.state).thenReturn(const CommentsState());
|
||||
when(commentsCubit.stream).thenAnswer((_) => const Stream<CommentsState>.empty());
|
||||
when(commentsCubit.close()).thenAnswer((_) async => Future<void>.value());
|
||||
when(commentsCubit.getComments(any, any, any)).thenAnswer((_) async => Future<void>.value());
|
||||
|
||||
registerMocks(cubit, commentsCubit);
|
||||
|
||||
await tester.pumpWidget(wrap(cubit, feedCubit, commentsCubit, const AboutDietScreen(slug: 'test', id: null, index: null, navigateToComments: false)));
|
||||
await tester.pump();
|
||||
|
||||
expect(find.byType(AboutDietScreen), findsOneWidget);
|
||||
});
|
||||
|
||||
testWidgets('renders loaded state with diet not started', (tester) async {
|
||||
final cubit = MockAboutDietCubit();
|
||||
final feedCubit = MockFeedCubit();
|
||||
final commentsCubit = MockCommentsCubit();
|
||||
const state = AboutDietState.loaded(diet: testDiet, isStarted: false, hasStartedDiet: false, selectedIndexes: []);
|
||||
|
||||
when(cubit.state).thenReturn(state);
|
||||
when(cubit.stream).thenAnswer((_) => const Stream<AboutDietState>.empty());
|
||||
when(cubit.close()).thenAnswer((_) async => Future<void>.value());
|
||||
when(feedCubit.viewDiet(any)).thenAnswer((_) async => Future<void>.value());
|
||||
when(feedCubit.stream).thenAnswer((_) => const Stream.empty());
|
||||
when(feedCubit.close()).thenAnswer((_) async => Future<void>.value());
|
||||
when(commentsCubit.state).thenReturn(const CommentsState());
|
||||
when(commentsCubit.stream).thenAnswer((_) => const Stream<CommentsState>.empty());
|
||||
when(commentsCubit.close()).thenAnswer((_) async => Future<void>.value());
|
||||
when(commentsCubit.getComments(any, any, any)).thenAnswer((_) async => Future<void>.value());
|
||||
|
||||
registerMocks(cubit, commentsCubit);
|
||||
|
||||
await mockNetworkImagesFor(() async {
|
||||
await tester.pumpWidget(wrap(cubit, feedCubit, commentsCubit, const AboutDietScreen(slug: 'test', id: null, index: null, navigateToComments: false)));
|
||||
await tester.pump();
|
||||
await tester.pump(const Duration(milliseconds: 600));
|
||||
});
|
||||
|
||||
expect(find.byType(AboutDietScreen), findsOneWidget);
|
||||
expect(find.text('Test Diet'), findsOneWidget);
|
||||
expect(find.text(S.current.startDiet), findsOneWidget);
|
||||
});
|
||||
|
||||
testWidgets('renders loaded state with diet started', (tester) async {
|
||||
final cubit = MockAboutDietCubit();
|
||||
final feedCubit = MockFeedCubit();
|
||||
final commentsCubit = MockCommentsCubit();
|
||||
const state = AboutDietState.loaded(diet: testDiet, isStarted: true, hasStartedDiet: true, selectedIndexes: []);
|
||||
|
||||
when(cubit.state).thenReturn(state);
|
||||
when(cubit.stream).thenAnswer((_) => const Stream<AboutDietState>.empty());
|
||||
when(cubit.close()).thenAnswer((_) async => Future<void>.value());
|
||||
when(feedCubit.viewDiet(any)).thenAnswer((_) async => Future<void>.value());
|
||||
when(feedCubit.stream).thenAnswer((_) => const Stream.empty());
|
||||
when(feedCubit.close()).thenAnswer((_) async => Future<void>.value());
|
||||
when(commentsCubit.state).thenReturn(const CommentsState());
|
||||
when(commentsCubit.stream).thenAnswer((_) => const Stream<CommentsState>.empty());
|
||||
when(commentsCubit.close()).thenAnswer((_) async => Future<void>.value());
|
||||
when(commentsCubit.getComments(any, any, any)).thenAnswer((_) async => Future<void>.value());
|
||||
|
||||
registerMocks(cubit, commentsCubit);
|
||||
|
||||
await mockNetworkImagesFor(() async {
|
||||
await tester.pumpWidget(wrap(cubit, feedCubit, commentsCubit, const AboutDietScreen(slug: 'test', id: null, index: null, navigateToComments: false)));
|
||||
await tester.pump();
|
||||
await tester.pump(const Duration(milliseconds: 600));
|
||||
});
|
||||
|
||||
expect(find.byType(AboutDietScreen), findsOneWidget);
|
||||
expect(find.text('Отменить диету'), findsOneWidget);
|
||||
});
|
||||
|
||||
testWidgets('renders error state', (tester) async {
|
||||
final cubit = MockAboutDietCubit();
|
||||
final feedCubit = MockFeedCubit();
|
||||
final commentsCubit = MockCommentsCubit();
|
||||
const state = AboutDietState.error(message: 'Error loading diet');
|
||||
|
||||
when(cubit.state).thenReturn(state);
|
||||
when(cubit.stream).thenAnswer((_) => const Stream<AboutDietState>.empty());
|
||||
when(cubit.close()).thenAnswer((_) async => Future<void>.value());
|
||||
when(feedCubit.stream).thenAnswer((_) => const Stream.empty());
|
||||
when(feedCubit.close()).thenAnswer((_) async => Future<void>.value());
|
||||
when(commentsCubit.state).thenReturn(const CommentsState());
|
||||
when(commentsCubit.stream).thenAnswer((_) => const Stream<CommentsState>.empty());
|
||||
when(commentsCubit.close()).thenAnswer((_) async => Future<void>.value());
|
||||
|
||||
registerMocks(cubit, commentsCubit);
|
||||
|
||||
await tester.pumpWidget(wrap(cubit, feedCubit, commentsCubit, const AboutDietScreen(slug: 'test', id: null, index: null, navigateToComments: false)));
|
||||
await tester.pump();
|
||||
|
||||
expect(find.byType(AboutDietScreen), findsOneWidget);
|
||||
});
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,139 @@
|
||||
import 'package:eatvio_app/core/app_config.dart';
|
||||
import 'package:eatvio_app/core/app_initializer.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/bloc/consultants_cubit/consultants_cubit.dart';
|
||||
import 'package:eatvio_app/features/feed/presentation/bloc/diets_cubit/diets_cubit.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/features/feed/presentation/bloc/consultants_cubit/consultants_state.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/bloc/profile_cubit/profile_cubit.dart';
|
||||
import 'package:eatvio_app/features/feed/presentation/bloc/profile_cubit/profile_state.dart';
|
||||
import 'package:eatvio_app/features/feed/presentation/bloc/recipes_cubit/recipes_cubit.dart';
|
||||
import 'package:eatvio_app/core/cubit/marketing/marketing_cubit.dart';
|
||||
import 'package:eatvio_app/core/views/cubit/views_cubit.dart';
|
||||
import 'package:eatvio_app/core/views/repository/views_repository.dart';
|
||||
import 'package:eatvio_app/core/views/service/views_service.dart';
|
||||
import 'package:eatvio_app/features/feed/presentation/bloc/recipes_cubit/recipes_state.dart';
|
||||
import 'package:eatvio_app/features/feed/presentation/pages/feed_screen.dart';
|
||||
import 'package:eatvio_app/generated/l10n.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:flutter_dotenv/flutter_dotenv.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 'feed_screen_smoke_test.mocks.dart';
|
||||
|
||||
@GenerateNiceMocks([
|
||||
MockSpec<RecipesCubit>(),
|
||||
MockSpec<FeedProfileCubit>(),
|
||||
MockSpec<DietsCubit>(),
|
||||
MockSpec<PlansCubit>(),
|
||||
MockSpec<ConsultantsCubit>(),
|
||||
MockSpec<FeedCubit>(),
|
||||
MockSpec<ViewsService>(),
|
||||
MockSpec<MarketingCubit>(),
|
||||
])
|
||||
void main() {
|
||||
group('FeedScreen smoke', () {
|
||||
setUpAll(() async {
|
||||
dotenv.loadFromString(envString: 'DEV_BASE_URL=http://localhost\nBASE_URL=http://localhost');
|
||||
});
|
||||
|
||||
setUp(() async {
|
||||
await sl.reset();
|
||||
sl.registerLazySingleton<AppConfig>(() => AppConfig(buildType: AppBuildType.tests)..setBaseUrl('http://localhost'));
|
||||
});
|
||||
|
||||
Widget wrap(FeedCubit feedCubit, MarketingCubit marketingCubit, Widget child) {
|
||||
final viewsService = MockViewsService();
|
||||
final viewsRepository = ViewsRepository(viewsService: viewsService);
|
||||
final viewsCubit = ViewsCubit(viewsRepository: viewsRepository);
|
||||
|
||||
return MultiBlocProvider(
|
||||
providers: [
|
||||
BlocProvider<ViewsCubit>.value(value: viewsCubit),
|
||||
BlocProvider<MarketingCubit>.value(value: marketingCubit),
|
||||
BlocProvider<FeedCubit>.value(value: feedCubit),
|
||||
],
|
||||
child: MaterialApp(
|
||||
theme: ThemeHelper().themeData(),
|
||||
localizationsDelegates: const [
|
||||
S.delegate,
|
||||
GlobalMaterialLocalizations.delegate,
|
||||
GlobalWidgetsLocalizations.delegate,
|
||||
GlobalCupertinoLocalizations.delegate,
|
||||
],
|
||||
supportedLocales: const [Locale('ru')],
|
||||
home: child,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
testWidgets('renders FeedScreen with all tabs', (tester) async {
|
||||
final recipesCubit = MockRecipesCubit();
|
||||
final profileCubit = MockFeedProfileCubit();
|
||||
final dietsCubit = MockDietsCubit();
|
||||
final plansCubit = MockPlansCubit();
|
||||
final consultantsCubit = MockConsultantsCubit();
|
||||
final feedCubit = MockFeedCubit();
|
||||
final viewsService = MockViewsService();
|
||||
final viewsRepository = ViewsRepository(viewsService: viewsService);
|
||||
final viewsCubit = ViewsCubit(viewsRepository: viewsRepository);
|
||||
final marketingCubit = MockMarketingCubit();
|
||||
|
||||
when(recipesCubit.state).thenReturn(RecipesState.initial());
|
||||
when(recipesCubit.stream).thenAnswer((_) => const Stream.empty());
|
||||
when(recipesCubit.close()).thenAnswer((_) async => Future<void>.value());
|
||||
when(profileCubit.state).thenReturn(ProfileState(profile: null));
|
||||
when(profileCubit.stream).thenAnswer((_) => const Stream.empty());
|
||||
when(profileCubit.close()).thenAnswer((_) async => Future<void>.value());
|
||||
when(dietsCubit.state).thenReturn(DietsState.initial());
|
||||
when(dietsCubit.stream).thenAnswer((_) => const Stream.empty());
|
||||
when(dietsCubit.close()).thenAnswer((_) async => Future<void>.value());
|
||||
when(plansCubit.state).thenReturn(PlansState.initial());
|
||||
when(plansCubit.stream).thenAnswer((_) => const Stream.empty());
|
||||
when(plansCubit.close()).thenAnswer((_) async => Future<void>.value());
|
||||
when(consultantsCubit.state).thenReturn(const ConsultantsState());
|
||||
when(consultantsCubit.stream).thenAnswer((_) => const Stream.empty());
|
||||
when(consultantsCubit.close()).thenAnswer((_) async => Future<void>.value());
|
||||
when(feedCubit.state).thenReturn(const FeedState.loading());
|
||||
when(feedCubit.stream).thenAnswer((_) => const Stream.empty());
|
||||
when(feedCubit.close()).thenAnswer((_) async => Future<void>.value());
|
||||
when(feedCubit.loadFeedBlogPostList()).thenAnswer((_) async => Future<void>.value());
|
||||
when(marketingCubit.checkRegistrationDate()).thenAnswer((_) async => false);
|
||||
when(marketingCubit.stream).thenAnswer((_) => const Stream.empty());
|
||||
when(marketingCubit.close()).thenAnswer((_) async => Future<void>.value());
|
||||
|
||||
sl.registerLazySingleton<RecipesCubit>(() => recipesCubit);
|
||||
sl.registerLazySingleton<FeedProfileCubit>(() => profileCubit);
|
||||
sl.registerLazySingleton<DietsCubit>(() => dietsCubit);
|
||||
sl.registerLazySingleton<PlansCubit>(() => plansCubit);
|
||||
sl.registerLazySingleton<ConsultantsCubit>(() => consultantsCubit);
|
||||
sl.registerLazySingleton<FeedCubit>(() => feedCubit);
|
||||
if (!sl.isRegistered<ViewsRepository>()) {
|
||||
sl.registerLazySingleton<ViewsRepository>(() => viewsRepository);
|
||||
} else {
|
||||
sl.unregister<ViewsRepository>();
|
||||
sl.registerLazySingleton<ViewsRepository>(() => viewsRepository);
|
||||
}
|
||||
if (!sl.isRegistered<ViewsCubit>()) {
|
||||
sl.registerLazySingleton<ViewsCubit>(() => viewsCubit);
|
||||
} else {
|
||||
sl.unregister<ViewsCubit>();
|
||||
sl.registerLazySingleton<ViewsCubit>(() => viewsCubit);
|
||||
}
|
||||
|
||||
await tester.pumpWidget(wrap(feedCubit, marketingCubit, const FeedScreen()));
|
||||
await tester.pump();
|
||||
|
||||
expect(find.byType(FeedScreen), findsOneWidget);
|
||||
});
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,57 @@
|
||||
import 'package:eatvio_app/core/theme/theme_helper.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/price_choice_filter_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 'price_choice_filter_widget_test.mocks.dart';
|
||||
|
||||
@GenerateNiceMocks([MockSpec<PlansCubit>()])
|
||||
void main() {
|
||||
group('PriceChoiceFilterWidget', () {
|
||||
Widget wrap(PlansCubit cubit, Widget child) {
|
||||
return MaterialApp(
|
||||
theme: ThemeHelper().themeData(),
|
||||
localizationsDelegates: const [
|
||||
S.delegate,
|
||||
GlobalMaterialLocalizations.delegate,
|
||||
GlobalWidgetsLocalizations.delegate,
|
||||
GlobalCupertinoLocalizations.delegate,
|
||||
],
|
||||
supportedLocales: const [Locale('ru')],
|
||||
home: BlocProvider<PlansCubit>.value(
|
||||
value: cubit,
|
||||
child: Scaffold(body: child),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
testWidgets('renders with PlansCubit', (tester) async {
|
||||
final cubit = MockPlansCubit();
|
||||
const state = PlansState(
|
||||
isLoading: false,
|
||||
plans: [],
|
||||
errorMessage: null,
|
||||
filterState: PlansFilterState(priceFilter: (null, null), durationIndex: 0, typeIndex: 0),
|
||||
minPlanPrice: 100,
|
||||
maxPlanPrice: 1000,
|
||||
);
|
||||
|
||||
when(cubit.state).thenReturn(state);
|
||||
when(cubit.stream).thenAnswer((_) => const Stream<PlansState>.empty());
|
||||
when(cubit.close()).thenAnswer((_) async => Future<void>.value());
|
||||
when(cubit.changePriceFilter(any, any, disableDebounce: anyNamed('disableDebounce'))).thenReturn(null);
|
||||
|
||||
await tester.pumpWidget(wrap(cubit, PriceChoiceFilterWidget(plansCubit: cubit)));
|
||||
await tester.pump();
|
||||
|
||||
expect(find.byType(PriceChoiceFilterWidget), findsOneWidget);
|
||||
});
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,121 @@
|
||||
import 'package:eatvio_app/core/app_config.dart';
|
||||
import 'package:eatvio_app/core/app_initializer.dart';
|
||||
import 'package:eatvio_app/core/locator_service.dart';
|
||||
import 'package:eatvio_app/core/theme/theme_helper.dart';
|
||||
import 'package:eatvio_app/features/consultations/repositories/consultations_repository.dart';
|
||||
import 'package:eatvio_app/features/finances_and_consultations_archive/cubit/consultant_finances/consultant_finances_cubit.dart';
|
||||
import 'package:eatvio_app/features/finances_and_consultations_archive/models/consultant_finance_model.dart';
|
||||
import 'package:eatvio_app/features/finances_and_consultations_archive/presentation/consultant_finances_screen.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:go_router/go_router.dart';
|
||||
import 'package:mockito/annotations.dart';
|
||||
import 'package:mockito/mockito.dart';
|
||||
|
||||
import 'consultant_finances_screen_smoke_test.mocks.dart';
|
||||
|
||||
@GenerateNiceMocks([MockSpec<ConsultationsRepository>()])
|
||||
void main() {
|
||||
group('ConsultantFinancesScreen smoke', () {
|
||||
setUpAll(() async {
|
||||
dotenv.loadFromString(envString: 'DEV_BASE_URL=http://localhost\nBASE_URL=http://localhost');
|
||||
});
|
||||
|
||||
setUp(() async {
|
||||
await sl.reset();
|
||||
sl.registerLazySingleton<AppConfig>(() => AppConfig(buildType: AppBuildType.tests)..setBaseUrl('http://localhost'));
|
||||
});
|
||||
|
||||
final mockFinances = [
|
||||
ConsultantFinanceModel(
|
||||
startDate: DateTime(2025, 1, 1),
|
||||
bankId: '1234567890123456',
|
||||
title: 'Test Plan',
|
||||
clientName: 'Test Client',
|
||||
duration: 7,
|
||||
paymentPrice: '1000.00',
|
||||
paymentStatus: PaymentStatus.completed,
|
||||
),
|
||||
];
|
||||
|
||||
Widget wrap(Widget child) {
|
||||
return MaterialApp.router(
|
||||
theme: ThemeHelper().themeData(),
|
||||
localizationsDelegates: const [
|
||||
S.delegate,
|
||||
GlobalMaterialLocalizations.delegate,
|
||||
GlobalWidgetsLocalizations.delegate,
|
||||
GlobalCupertinoLocalizations.delegate,
|
||||
],
|
||||
supportedLocales: const [Locale('ru')],
|
||||
routerConfig: GoRouter(
|
||||
initialLocation: '/',
|
||||
routes: [
|
||||
GoRoute(path: '/', builder: (context, state) => child),
|
||||
GoRoute(
|
||||
path: '/card-settings-screen',
|
||||
name: 'cardSettingsScreen',
|
||||
builder: (context, state) => const Scaffold(body: Text('Card Settings')),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
testWidgets('renders loading state', (tester) async {
|
||||
final repository = MockConsultationsRepository();
|
||||
final cubit = ConsultantFinancesCubit(consultationsRepository: repository);
|
||||
|
||||
when(repository.getConsultantFinances(any, any)).thenAnswer((_) async => mockFinances);
|
||||
|
||||
sl.registerLazySingleton<ConsultationsRepository>(() => repository);
|
||||
sl.registerLazySingleton<ConsultantFinancesCubit>(() => cubit);
|
||||
|
||||
await tester.pumpWidget(wrap(const ConsultantFinancesScreen()));
|
||||
await tester.pump();
|
||||
await tester.pump(const Duration(milliseconds: 100));
|
||||
|
||||
expect(find.byType(ConsultantFinancesScreen), findsOneWidget);
|
||||
});
|
||||
|
||||
testWidgets('renders loaded state with finances', (tester) async {
|
||||
final repository = MockConsultationsRepository();
|
||||
final cubit = ConsultantFinancesCubit(consultationsRepository: repository);
|
||||
|
||||
when(repository.getConsultantFinances(any, any)).thenAnswer((_) async => mockFinances);
|
||||
|
||||
sl.registerLazySingleton<ConsultationsRepository>(() => repository);
|
||||
sl.registerLazySingleton<ConsultantFinancesCubit>(() => cubit);
|
||||
|
||||
await tester.pumpWidget(wrap(const ConsultantFinancesScreen()));
|
||||
await tester.pump();
|
||||
await tester.pump(const Duration(milliseconds: 100));
|
||||
|
||||
expect(find.text('Финансы'), findsOneWidget);
|
||||
expect(find.text('Управление картами'), findsOneWidget);
|
||||
});
|
||||
|
||||
testWidgets('renders empty state', (tester) async {
|
||||
tester.view.physicalSize = const Size(800, 1200);
|
||||
tester.view.devicePixelRatio = 1.0;
|
||||
addTearDown(() => tester.view.resetPhysicalSize());
|
||||
|
||||
final repository = MockConsultationsRepository();
|
||||
final cubit = ConsultantFinancesCubit(consultationsRepository: repository);
|
||||
|
||||
when(repository.getConsultantFinances(any, any)).thenAnswer((_) async => []);
|
||||
|
||||
sl.registerLazySingleton<ConsultationsRepository>(() => repository);
|
||||
sl.registerLazySingleton<ConsultantFinancesCubit>(() => cubit);
|
||||
|
||||
await tester.pumpWidget(wrap(const ConsultantFinancesScreen()));
|
||||
await tester.pump();
|
||||
await tester.pump(const Duration(milliseconds: 100));
|
||||
|
||||
expect(find.text('У вас нет таких консультаций'), findsOneWidget);
|
||||
});
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,141 @@
|
||||
import 'package:eatvio_app/core/app_config.dart';
|
||||
import 'package:eatvio_app/core/app_initializer.dart';
|
||||
import 'package:eatvio_app/core/locator_service.dart';
|
||||
import 'package:eatvio_app/core/theme/theme_helper.dart';
|
||||
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/finances_and_consultations_archive/cubit/archive/consultations_archive_cubit.dart';
|
||||
import 'package:eatvio_app/features/finances_and_consultations_archive/models/statistic_model.dart';
|
||||
import 'package:eatvio_app/features/finances_and_consultations_archive/presentation/consultations_archive_screen.dart';
|
||||
import 'package:eatvio_app/features/plans/enums/chosen_plan_state_enum.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:mockito/annotations.dart';
|
||||
import 'package:mockito/mockito.dart';
|
||||
|
||||
import 'consultations_archive_screen_smoke_test.mocks.dart';
|
||||
|
||||
@GenerateNiceMocks([MockSpec<ConsultationsRepository>()])
|
||||
void main() {
|
||||
group('ConsultationsArchiveScreen smoke', () {
|
||||
setUpAll(() async {
|
||||
dotenv.loadFromString(envString: 'DEV_BASE_URL=http://localhost\nBASE_URL=http://localhost');
|
||||
});
|
||||
|
||||
setUp(() async {
|
||||
await sl.reset();
|
||||
sl.registerLazySingleton<AppConfig>(() => AppConfig(buildType: AppBuildType.tests)..setBaseUrl('http://localhost'));
|
||||
});
|
||||
|
||||
final mockStatistic = StatisticModel(total: 10, canceled: 2, inProgress: 3, finished: 5);
|
||||
|
||||
final mockConsultations = [
|
||||
ConsultationModel(
|
||||
id: 1,
|
||||
chosenPlanState: ChosenPlanStateEnum.stateDone,
|
||||
planTitle: 'Test Plan',
|
||||
userName: 'Test User',
|
||||
userSlug: 'test-user',
|
||||
consultantPayment: 1000.0,
|
||||
duration: 7,
|
||||
currentDiaryDay: 3,
|
||||
userProfilePhotoUrl: 'https://example.com/avatar.jpg',
|
||||
consultationOptionPrice: 500,
|
||||
startDate: DateTime(2025, 1, 1),
|
||||
endDate: DateTime(2025, 1, 8),
|
||||
consultantId: 1,
|
||||
diaries: [],
|
||||
personalDietId: null,
|
||||
),
|
||||
];
|
||||
|
||||
Widget wrap(Widget child) {
|
||||
return MaterialApp(
|
||||
theme: ThemeHelper().themeData(),
|
||||
localizationsDelegates: const [
|
||||
S.delegate,
|
||||
GlobalMaterialLocalizations.delegate,
|
||||
GlobalWidgetsLocalizations.delegate,
|
||||
GlobalCupertinoLocalizations.delegate,
|
||||
],
|
||||
supportedLocales: const [Locale('ru')],
|
||||
home: child,
|
||||
);
|
||||
}
|
||||
|
||||
testWidgets('renders loading state', (tester) async {
|
||||
final repository = MockConsultationsRepository();
|
||||
final cubit = ConsultationsArchiveCubit(consultationsRepository: repository);
|
||||
|
||||
when(
|
||||
repository.getFilteredConsultations(
|
||||
date: anyNamed('date'),
|
||||
state: anyNamed('state'),
|
||||
dateComparison: anyNamed('dateComparison'),
|
||||
stateComparison: anyNamed('stateComparison'),
|
||||
),
|
||||
).thenAnswer((_) async => mockConsultations);
|
||||
when(repository.getStatistic()).thenAnswer((_) async => mockStatistic);
|
||||
|
||||
sl.registerLazySingleton<ConsultationsRepository>(() => repository);
|
||||
sl.registerLazySingleton<ConsultationsArchiveCubit>(() => cubit);
|
||||
|
||||
await tester.pumpWidget(wrap(const ConsultationsArchiveScreen()));
|
||||
await tester.pump();
|
||||
await tester.pump(const Duration(milliseconds: 100));
|
||||
|
||||
expect(find.byType(ConsultationsArchiveScreen), findsOneWidget);
|
||||
});
|
||||
|
||||
testWidgets('renders loaded state with consultations', (tester) async {
|
||||
final repository = MockConsultationsRepository();
|
||||
final cubit = ConsultationsArchiveCubit(consultationsRepository: repository);
|
||||
|
||||
when(
|
||||
repository.getFilteredConsultations(
|
||||
date: anyNamed('date'),
|
||||
state: anyNamed('state'),
|
||||
dateComparison: anyNamed('dateComparison'),
|
||||
stateComparison: anyNamed('stateComparison'),
|
||||
),
|
||||
).thenAnswer((_) async => mockConsultations);
|
||||
when(repository.getStatistic()).thenAnswer((_) async => mockStatistic);
|
||||
|
||||
sl.registerLazySingleton<ConsultationsRepository>(() => repository);
|
||||
sl.registerLazySingleton<ConsultationsArchiveCubit>(() => cubit);
|
||||
|
||||
await tester.pumpWidget(wrap(const ConsultationsArchiveScreen()));
|
||||
await tester.pump();
|
||||
await tester.pump(const Duration(milliseconds: 100));
|
||||
|
||||
expect(find.text('Архив консультаций'), findsOneWidget);
|
||||
});
|
||||
|
||||
testWidgets('renders error state', (tester) async {
|
||||
final repository = MockConsultationsRepository();
|
||||
final cubit = ConsultationsArchiveCubit(consultationsRepository: repository);
|
||||
|
||||
when(
|
||||
repository.getFilteredConsultations(
|
||||
date: anyNamed('date'),
|
||||
state: anyNamed('state'),
|
||||
dateComparison: anyNamed('dateComparison'),
|
||||
stateComparison: anyNamed('stateComparison'),
|
||||
),
|
||||
).thenThrow(Exception('Error loading consultations'));
|
||||
when(repository.getStatistic()).thenAnswer((_) async => mockStatistic);
|
||||
|
||||
sl.registerLazySingleton<ConsultationsRepository>(() => repository);
|
||||
sl.registerLazySingleton<ConsultationsArchiveCubit>(() => cubit);
|
||||
|
||||
await tester.pumpWidget(wrap(const ConsultationsArchiveScreen()));
|
||||
await tester.pump();
|
||||
await tester.pump(const Duration(milliseconds: 100));
|
||||
|
||||
expect(find.byType(ConsultationsArchiveScreen), findsOneWidget);
|
||||
});
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,110 @@
|
||||
import 'package:eatvio_app/core/app_config.dart';
|
||||
import 'package:eatvio_app/core/app_initializer.dart';
|
||||
import 'package:eatvio_app/core/locator_service.dart';
|
||||
import 'package:eatvio_app/core/theme/theme_helper.dart';
|
||||
import 'package:eatvio_app/features/consultations/repositories/consultations_repository.dart';
|
||||
import 'package:eatvio_app/features/finances_and_consultations_archive/cubit/user_purchases/user_purchases_cubit.dart';
|
||||
import 'package:eatvio_app/features/finances_and_consultations_archive/models/user_purchases_model.dart';
|
||||
import 'package:eatvio_app/features/finances_and_consultations_archive/presentation/user_purchases_screen.dart';
|
||||
import 'package:eatvio_app/features/plans/enums/chosen_plan_state_enum.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:mockito/annotations.dart';
|
||||
import 'package:mockito/mockito.dart';
|
||||
|
||||
import 'user_purchases_screen_smoke_test.mocks.dart';
|
||||
|
||||
@GenerateNiceMocks([MockSpec<ConsultationsRepository>()])
|
||||
void main() {
|
||||
group('UserPurchasesScreen smoke', () {
|
||||
setUpAll(() async {
|
||||
dotenv.loadFromString(envString: 'DEV_BASE_URL=http://localhost\nBASE_URL=http://localhost');
|
||||
});
|
||||
|
||||
setUp(() async {
|
||||
await sl.reset();
|
||||
sl.registerLazySingleton<AppConfig>(() => AppConfig(buildType: AppBuildType.tests)..setBaseUrl('http://localhost'));
|
||||
});
|
||||
|
||||
final mockPurchases = [
|
||||
UserPurchasesModel(
|
||||
startDate: DateTime(2025, 1, 1),
|
||||
bankId: '1234567890123456',
|
||||
title: 'Test Plan',
|
||||
consultantName: 'Test Consultant',
|
||||
duration: 7,
|
||||
paymentPrice: 1000,
|
||||
chosenPlanState: ChosenPlanStateEnum.stateChargeWasCollected,
|
||||
),
|
||||
];
|
||||
|
||||
Widget wrap(Widget child) {
|
||||
return MaterialApp(
|
||||
theme: ThemeHelper().themeData(),
|
||||
localizationsDelegates: const [
|
||||
S.delegate,
|
||||
GlobalMaterialLocalizations.delegate,
|
||||
GlobalWidgetsLocalizations.delegate,
|
||||
GlobalCupertinoLocalizations.delegate,
|
||||
],
|
||||
supportedLocales: const [Locale('ru')],
|
||||
home: child,
|
||||
);
|
||||
}
|
||||
|
||||
testWidgets('renders loading state', (tester) async {
|
||||
final repository = MockConsultationsRepository();
|
||||
final cubit = UserPurchasesCubit(consultationsRepository: repository);
|
||||
|
||||
when(repository.getUserPurchases(any, any)).thenAnswer((_) async => mockPurchases);
|
||||
|
||||
sl.registerLazySingleton<ConsultationsRepository>(() => repository);
|
||||
sl.registerLazySingleton<UserPurchasesCubit>(() => cubit);
|
||||
|
||||
await tester.pumpWidget(wrap(const UserPurchasesScreen()));
|
||||
await tester.pump();
|
||||
await tester.pump(const Duration(milliseconds: 100));
|
||||
|
||||
expect(find.byType(UserPurchasesScreen), findsOneWidget);
|
||||
});
|
||||
|
||||
testWidgets('renders loaded state with purchases', (tester) async {
|
||||
final repository = MockConsultationsRepository();
|
||||
final cubit = UserPurchasesCubit(consultationsRepository: repository);
|
||||
|
||||
when(repository.getUserPurchases(any, any)).thenAnswer((_) async => mockPurchases);
|
||||
|
||||
sl.registerLazySingleton<ConsultationsRepository>(() => repository);
|
||||
sl.registerLazySingleton<UserPurchasesCubit>(() => cubit);
|
||||
|
||||
await tester.pumpWidget(wrap(const UserPurchasesScreen()));
|
||||
await tester.pump();
|
||||
await tester.pump(const Duration(milliseconds: 100));
|
||||
|
||||
expect(find.text('Финансы'), findsOneWidget);
|
||||
});
|
||||
|
||||
testWidgets('renders empty state', (tester) async {
|
||||
tester.view.physicalSize = const Size(800, 1200);
|
||||
tester.view.devicePixelRatio = 1.0;
|
||||
addTearDown(() => tester.view.resetPhysicalSize());
|
||||
|
||||
final repository = MockConsultationsRepository();
|
||||
final cubit = UserPurchasesCubit(consultationsRepository: repository);
|
||||
|
||||
when(repository.getUserPurchases(any, any)).thenAnswer((_) async => []);
|
||||
|
||||
sl.registerLazySingleton<ConsultationsRepository>(() => repository);
|
||||
sl.registerLazySingleton<UserPurchasesCubit>(() => cubit);
|
||||
|
||||
await tester.pumpWidget(wrap(const UserPurchasesScreen()));
|
||||
await tester.pump();
|
||||
await tester.pump(const Duration(milliseconds: 100));
|
||||
|
||||
expect(find.text('У вас нет таких консультаций'), findsOneWidget);
|
||||
});
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,173 @@
|
||||
import 'package:eatvio_app/core/app_config.dart';
|
||||
import 'package:eatvio_app/core/app_initializer.dart';
|
||||
import 'package:eatvio_app/core/locator_service.dart';
|
||||
import 'package:eatvio_app/core/theme/theme_helper.dart';
|
||||
import 'package:eatvio_app/features/menu/presentation/bloc/menu_cubit/menu_cubit.dart';
|
||||
import 'package:eatvio_app/features/menu/presentation/pages/menu_screen.dart';
|
||||
import 'package:eatvio_app/features/profile/models/profile_model.dart';
|
||||
import 'package:eatvio_app/features/profile/repositories/profile_repository.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:go_router/go_router.dart';
|
||||
import 'package:mockito/annotations.dart';
|
||||
import 'package:mockito/mockito.dart';
|
||||
|
||||
import 'menu_screen_smoke_test.mocks.dart';
|
||||
|
||||
@GenerateNiceMocks([MockSpec<ProfileRepository>()])
|
||||
void main() {
|
||||
group('MenuScreen smoke', () {
|
||||
setUpAll(() async {
|
||||
dotenv.loadFromString(envString: 'DEV_BASE_URL=http://localhost\nBASE_URL=http://localhost');
|
||||
});
|
||||
|
||||
setUp(() async {
|
||||
await sl.reset();
|
||||
sl.registerLazySingleton<AppConfig>(() => AppConfig(buildType: AppBuildType.tests)..setBaseUrl('http://localhost'));
|
||||
});
|
||||
|
||||
const mockUserProfile = ProfileModel(
|
||||
id: 1,
|
||||
name: 'Test User',
|
||||
email: 'test@example.com',
|
||||
phoneConfirmationState: 'confirmed',
|
||||
isPublicProfile: true,
|
||||
isBlockedByMe: false,
|
||||
isBlockedByAuthor: false,
|
||||
isBlockedByUser: false,
|
||||
isEntitySubscribedByMe: false,
|
||||
slug: 'test-user',
|
||||
profilePhotoUrl: 'https://example.com/avatar.jpg',
|
||||
roles: ['user'],
|
||||
badges: [],
|
||||
subscriptions: [],
|
||||
subscribers: [],
|
||||
);
|
||||
|
||||
const mockConsultantProfile = ProfileModel(
|
||||
id: 1,
|
||||
name: 'Test Consultant',
|
||||
email: 'consultant@example.com',
|
||||
phoneConfirmationState: 'confirmed',
|
||||
isPublicProfile: true,
|
||||
isBlockedByMe: false,
|
||||
isBlockedByAuthor: false,
|
||||
isBlockedByUser: false,
|
||||
isEntitySubscribedByMe: false,
|
||||
slug: 'test-consultant',
|
||||
profilePhotoUrl: 'https://example.com/avatar.jpg',
|
||||
roles: ['consultant'],
|
||||
badges: [],
|
||||
subscriptions: [],
|
||||
subscribers: [],
|
||||
);
|
||||
|
||||
Widget wrap(Widget child) {
|
||||
return MaterialApp.router(
|
||||
theme: ThemeHelper().themeData(),
|
||||
localizationsDelegates: const [
|
||||
S.delegate,
|
||||
GlobalMaterialLocalizations.delegate,
|
||||
GlobalWidgetsLocalizations.delegate,
|
||||
GlobalCupertinoLocalizations.delegate,
|
||||
],
|
||||
supportedLocales: const [Locale('ru')],
|
||||
routerConfig: GoRouter(
|
||||
initialLocation: '/',
|
||||
routes: [
|
||||
GoRoute(path: '/', builder: (context, state) => child),
|
||||
GoRoute(
|
||||
path: '/settings-screen',
|
||||
builder: (context, state) => const Scaffold(body: Text('Settings')),
|
||||
),
|
||||
GoRoute(
|
||||
path: '/glossary-screen',
|
||||
builder: (context, state) => const Scaffold(body: Text('Glossary')),
|
||||
),
|
||||
GoRoute(
|
||||
path: '/support-screen',
|
||||
builder: (context, state) => const Scaffold(body: Text('Support')),
|
||||
),
|
||||
GoRoute(
|
||||
path: '/my-recipes-screen',
|
||||
name: 'myRecipesScreen',
|
||||
builder: (context, state) => const Scaffold(body: Text('My Recipes')),
|
||||
),
|
||||
GoRoute(
|
||||
path: '/my-diets-screen',
|
||||
name: 'myDietsScreen',
|
||||
builder: (context, state) => const Scaffold(body: Text('My Diets')),
|
||||
),
|
||||
GoRoute(
|
||||
path: '/my-plans-screen',
|
||||
name: 'myPlansScreen',
|
||||
builder: (context, state) => const Scaffold(body: Text('My Plans')),
|
||||
),
|
||||
GoRoute(
|
||||
path: '/consultant-finances-screen',
|
||||
name: 'consultantFinancesScreen',
|
||||
builder: (context, state) => const Scaffold(body: Text('Consultant Finances')),
|
||||
),
|
||||
GoRoute(
|
||||
path: '/user-purchases-screen',
|
||||
name: 'userPurchasesScreen',
|
||||
builder: (context, state) => const Scaffold(body: Text('User Purchases')),
|
||||
),
|
||||
GoRoute(
|
||||
path: '/consultant-rating-screen',
|
||||
name: 'consultantRatingScreen',
|
||||
builder: (context, state) => const Scaffold(body: Text('Consultant Rating')),
|
||||
),
|
||||
GoRoute(
|
||||
path: '/notifications-settings-screen',
|
||||
name: 'notificationsSettingsScreen',
|
||||
builder: (context, state) => const Scaffold(body: Text('Notifications Settings')),
|
||||
),
|
||||
GoRoute(
|
||||
path: '/blocked-users-screen',
|
||||
name: 'blockedUsersScreen',
|
||||
builder: (context, state) => const Scaffold(body: Text('Blocked Users')),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
testWidgets('renders menu screen for user', (tester) async {
|
||||
final repository = MockProfileRepository();
|
||||
final cubit = MenuCubit(repository);
|
||||
|
||||
when(repository.getPassKey(routeName: anyNamed('routeName'), routeParams: anyNamed('routeParams'))).thenAnswer((_) async => 'test-passkey');
|
||||
|
||||
sl.registerLazySingleton<ProfileRepository>(() => repository);
|
||||
sl.registerLazySingleton<MenuCubit>(() => cubit);
|
||||
|
||||
await tester.pumpWidget(wrap(const MenuScreen(profileModel: mockUserProfile)));
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
expect(find.text('Меню'), findsOneWidget);
|
||||
expect(find.text('Настройки профиля'), findsOneWidget);
|
||||
});
|
||||
|
||||
testWidgets('renders menu screen for consultant', (tester) async {
|
||||
final repository = MockProfileRepository();
|
||||
final cubit = MenuCubit(repository);
|
||||
|
||||
when(repository.getPassKey(routeName: anyNamed('routeName'), routeParams: anyNamed('routeParams'))).thenAnswer((_) async => 'test-passkey');
|
||||
|
||||
sl.registerLazySingleton<ProfileRepository>(() => repository);
|
||||
sl.registerLazySingleton<MenuCubit>(() => cubit);
|
||||
|
||||
await tester.pumpWidget(wrap(const MenuScreen(profileModel: mockConsultantProfile)));
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
expect(find.text('Меню'), findsOneWidget);
|
||||
expect(find.text('Мои рецепты'), findsOneWidget);
|
||||
expect(find.text('Мои диеты'), findsOneWidget);
|
||||
expect(find.text('Мои планы'), findsOneWidget);
|
||||
});
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,115 @@
|
||||
import 'package:eatvio_app/core/app_config.dart';
|
||||
import 'package:eatvio_app/core/app_initializer.dart';
|
||||
import 'package:eatvio_app/core/locator_service.dart';
|
||||
import 'package:eatvio_app/core/theme/theme_helper.dart';
|
||||
import 'package:eatvio_app/features/menu/models/notification_settings_model.dart';
|
||||
import 'package:eatvio_app/features/menu/presentation/bloc/notification_settings_cubit/notification_settings_cubit.dart';
|
||||
import 'package:eatvio_app/features/menu/presentation/pages/notifications_settings_screen.dart';
|
||||
import 'package:eatvio_app/features/menu/repositories/notification_settings_repository.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:mockito/annotations.dart';
|
||||
import 'package:mockito/mockito.dart';
|
||||
|
||||
import 'notifications_settings_screen_smoke_test.mocks.dart';
|
||||
|
||||
@GenerateNiceMocks([MockSpec<NotificationSettingsRepository>()])
|
||||
void main() {
|
||||
group('NotificationsSettingsScreen smoke', () {
|
||||
setUpAll(() async {
|
||||
dotenv.loadFromString(envString: 'DEV_BASE_URL=http://localhost\nBASE_URL=http://localhost');
|
||||
});
|
||||
|
||||
setUp(() async {
|
||||
await sl.reset();
|
||||
sl.registerLazySingleton<AppConfig>(() => AppConfig(buildType: AppBuildType.tests)..setBaseUrl('http://localhost'));
|
||||
});
|
||||
|
||||
const mockNotificationSettings = NotificationSettingsModel(
|
||||
newUpdatesNotification: true,
|
||||
weightMeasurementsNotification: true,
|
||||
isEmailNotificationsEnabled: true,
|
||||
isPushNotificationsEnabled: true,
|
||||
diaryReminderNotifications: DiaryReminderNotifications(
|
||||
firstReminder: ReminderSettings(enabled: true, localTimeHours: 9, localTimeMins: 0),
|
||||
secondReminder: ReminderSettings(enabled: true, localTimeHours: 13, localTimeMins: 0),
|
||||
thirdReminder: ReminderSettings(enabled: false, localTimeHours: 18, localTimeMins: 0),
|
||||
),
|
||||
);
|
||||
|
||||
Widget wrap(Widget child) {
|
||||
return MaterialApp(
|
||||
theme: ThemeHelper().themeData(),
|
||||
localizationsDelegates: const [
|
||||
S.delegate,
|
||||
GlobalMaterialLocalizations.delegate,
|
||||
GlobalWidgetsLocalizations.delegate,
|
||||
GlobalCupertinoLocalizations.delegate,
|
||||
],
|
||||
supportedLocales: const [Locale('ru')],
|
||||
home: child,
|
||||
);
|
||||
}
|
||||
|
||||
testWidgets('renders loading state', (tester) async {
|
||||
tester.view.physicalSize = const Size(800, 1200);
|
||||
tester.view.devicePixelRatio = 1.0;
|
||||
addTearDown(() => tester.view.resetPhysicalSize());
|
||||
|
||||
final repository = MockNotificationSettingsRepository();
|
||||
final cubit = NotificationSettingsCubit(notificationSettingsRepository: repository);
|
||||
|
||||
when(repository.getNotificationSettings()).thenAnswer((_) async => mockNotificationSettings);
|
||||
|
||||
sl.registerLazySingleton<NotificationSettingsRepository>(() => repository);
|
||||
sl.registerLazySingleton<NotificationSettingsCubit>(() => cubit);
|
||||
|
||||
await tester.pumpWidget(wrap(const NotificationsSettingsScreen()));
|
||||
await tester.pump();
|
||||
|
||||
|
||||
expect(find.byType(NotificationsSettingsScreen), findsOneWidget);
|
||||
});
|
||||
|
||||
testWidgets('renders loaded state with settings', (tester) async {
|
||||
tester.view.physicalSize = const Size(800, 1200);
|
||||
tester.view.devicePixelRatio = 1.0;
|
||||
addTearDown(() => tester.view.resetPhysicalSize());
|
||||
|
||||
final repository = MockNotificationSettingsRepository();
|
||||
final cubit = NotificationSettingsCubit(notificationSettingsRepository: repository);
|
||||
|
||||
when(repository.getNotificationSettings()).thenAnswer((_) async => mockNotificationSettings);
|
||||
|
||||
sl.registerLazySingleton<NotificationSettingsRepository>(() => repository);
|
||||
sl.registerLazySingleton<NotificationSettingsCubit>(() => cubit);
|
||||
|
||||
await tester.pumpWidget(wrap(const NotificationsSettingsScreen()));
|
||||
await tester.pump();
|
||||
await tester.pump(const Duration(milliseconds: 100));
|
||||
|
||||
|
||||
expect(find.byType(NotificationsSettingsScreen), findsOneWidget);
|
||||
expect(find.text('Новые обновления'), findsOneWidget);
|
||||
});
|
||||
|
||||
testWidgets('renders error state', (tester) async {
|
||||
final repository = MockNotificationSettingsRepository();
|
||||
final cubit = NotificationSettingsCubit(notificationSettingsRepository: repository);
|
||||
|
||||
when(repository.getNotificationSettings()).thenThrow(Exception('Error loading settings'));
|
||||
|
||||
sl.registerLazySingleton<NotificationSettingsRepository>(() => repository);
|
||||
sl.registerLazySingleton<NotificationSettingsCubit>(() => cubit);
|
||||
|
||||
await tester.pumpWidget(wrap(const NotificationsSettingsScreen()));
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
expect(find.textContaining('Ошибка'), findsOneWidget);
|
||||
expect(find.text('Повторить'), findsOneWidget);
|
||||
});
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,110 @@
|
||||
import 'package:eatvio_app/core/app_config.dart';
|
||||
import 'package:eatvio_app/core/app_initializer.dart';
|
||||
import 'package:eatvio_app/core/locator_service.dart';
|
||||
import 'package:eatvio_app/core/models/pagination_model.dart';
|
||||
import 'package:eatvio_app/core/theme/theme_helper.dart';
|
||||
import 'package:eatvio_app/features/create_diet/repositories/create_diet_repository.dart';
|
||||
import 'package:eatvio_app/features/feed/models/diets_pagination_model.dart';
|
||||
import 'package:eatvio_app/features/my_diets/presentation/cubit/my_diets_cubit.dart';
|
||||
import 'package:eatvio_app/features/my_diets/presentation/my_diets_screen.dart';
|
||||
import 'package:eatvio_app/features/my_diets/repositories/my_diets_repository.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:mockito/annotations.dart';
|
||||
import 'package:mockito/mockito.dart';
|
||||
|
||||
import 'my_diets_screen_smoke_test.mocks.dart';
|
||||
|
||||
@GenerateNiceMocks([MockSpec<MyDietsRepository>(), MockSpec<CreateDietRepository>()])
|
||||
void main() {
|
||||
group('MyDietsScreen smoke', () {
|
||||
setUpAll(() async {
|
||||
dotenv.loadFromString(envString: 'DEV_BASE_URL=http://localhost\nBASE_URL=http://localhost');
|
||||
});
|
||||
|
||||
setUp(() async {
|
||||
await sl.reset();
|
||||
sl.registerLazySingleton<AppConfig>(() => AppConfig(buildType: AppBuildType.tests)..setBaseUrl('http://localhost'));
|
||||
});
|
||||
|
||||
const mockDietsPagination = DietsPagination(
|
||||
data: [],
|
||||
links: Links(first: 'http://localhost/api?page=1', last: 'http://localhost/api?page=1', prev: null, next: null),
|
||||
meta: Meta(currentPage: 1, from: null, lastPage: 1, links: [], perPage: 10, to: null, total: 0),
|
||||
);
|
||||
|
||||
Widget wrap(Widget child) {
|
||||
return MaterialApp(
|
||||
theme: ThemeHelper().themeData(),
|
||||
localizationsDelegates: const [
|
||||
S.delegate,
|
||||
GlobalMaterialLocalizations.delegate,
|
||||
GlobalWidgetsLocalizations.delegate,
|
||||
GlobalCupertinoLocalizations.delegate,
|
||||
],
|
||||
supportedLocales: const [Locale('ru')],
|
||||
home: child,
|
||||
);
|
||||
}
|
||||
|
||||
testWidgets('renders loading state', (tester) async {
|
||||
final myDietsRepository = MockMyDietsRepository();
|
||||
final createDietRepository = MockCreateDietRepository();
|
||||
final cubit = MyDietsCubit(myDietsRepository: myDietsRepository, createDietRepository: createDietRepository);
|
||||
|
||||
when(myDietsRepository.getDiets(page: 1, state: 'active')).thenAnswer((_) async => mockDietsPagination);
|
||||
when(myDietsRepository.getDiets(page: 1, state: 'draft')).thenAnswer((_) async => mockDietsPagination);
|
||||
when(myDietsRepository.getDiets(page: 1, state: 'personal')).thenAnswer((_) async => mockDietsPagination);
|
||||
|
||||
sl.registerLazySingleton<MyDietsRepository>(() => myDietsRepository);
|
||||
sl.registerLazySingleton<CreateDietRepository>(() => createDietRepository);
|
||||
sl.registerLazySingleton<MyDietsCubit>(() => cubit);
|
||||
|
||||
await tester.pumpWidget(wrap(const MyDietsScreen()));
|
||||
await tester.pump();
|
||||
|
||||
expect(find.byType(MyDietsScreen), findsOneWidget);
|
||||
});
|
||||
|
||||
testWidgets('renders loaded state', (tester) async {
|
||||
final myDietsRepository = MockMyDietsRepository();
|
||||
final createDietRepository = MockCreateDietRepository();
|
||||
final cubit = MyDietsCubit(myDietsRepository: myDietsRepository, createDietRepository: createDietRepository);
|
||||
|
||||
when(myDietsRepository.getDiets(page: 1, state: 'active')).thenAnswer((_) async => mockDietsPagination);
|
||||
when(myDietsRepository.getDiets(page: 1, state: 'draft')).thenAnswer((_) async => mockDietsPagination);
|
||||
when(myDietsRepository.getDiets(page: 1, state: 'personal')).thenAnswer((_) async => mockDietsPagination);
|
||||
|
||||
sl.registerLazySingleton<MyDietsRepository>(() => myDietsRepository);
|
||||
sl.registerLazySingleton<CreateDietRepository>(() => createDietRepository);
|
||||
sl.registerLazySingleton<MyDietsCubit>(() => cubit);
|
||||
|
||||
await tester.pumpWidget(wrap(const MyDietsScreen()));
|
||||
await tester.pump();
|
||||
await tester.pump(const Duration(milliseconds: 100));
|
||||
|
||||
expect(find.byType(MyDietsScreen), findsOneWidget);
|
||||
});
|
||||
|
||||
testWidgets('renders error state', (tester) async {
|
||||
final myDietsRepository = MockMyDietsRepository();
|
||||
final createDietRepository = MockCreateDietRepository();
|
||||
final cubit = MyDietsCubit(myDietsRepository: myDietsRepository, createDietRepository: createDietRepository);
|
||||
|
||||
when(myDietsRepository.getDiets(page: 1, state: 'active')).thenThrow(Exception('Error loading diets'));
|
||||
|
||||
sl.registerLazySingleton<MyDietsRepository>(() => myDietsRepository);
|
||||
sl.registerLazySingleton<CreateDietRepository>(() => createDietRepository);
|
||||
sl.registerLazySingleton<MyDietsCubit>(() => cubit);
|
||||
|
||||
await tester.pumpWidget(wrap(const MyDietsScreen()));
|
||||
await tester.pump();
|
||||
await tester.pump(const Duration(milliseconds: 100));
|
||||
|
||||
expect(find.byType(MyDietsScreen), findsOneWidget);
|
||||
});
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,92 @@
|
||||
import 'package:eatvio_app/core/app_config.dart';
|
||||
import 'package:eatvio_app/core/app_initializer.dart';
|
||||
import 'package:eatvio_app/core/locator_service.dart';
|
||||
import 'package:eatvio_app/core/theme/theme_helper.dart';
|
||||
import 'package:eatvio_app/core/enums/base_state.dart';
|
||||
import 'package:eatvio_app/features/my_finance/models/card_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/pages/card_settings.dart';
|
||||
import 'package:eatvio_app/generated/l10n.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:flutter_dotenv/flutter_dotenv.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_settings_smoke_test.mocks.dart';
|
||||
|
||||
@GenerateNiceMocks([MockSpec<UserCardsCubit>()])
|
||||
void main() {
|
||||
group('CardSettingsScreen smoke', () {
|
||||
setUpAll(() async {
|
||||
dotenv.loadFromString(envString: 'DEV_BASE_URL=http://localhost\nBASE_URL=http://localhost');
|
||||
});
|
||||
|
||||
setUp(() async {
|
||||
await sl.reset();
|
||||
sl.registerLazySingleton<AppConfig>(() => AppConfig(buildType: AppBuildType.tests)..setBaseUrl('http://localhost'));
|
||||
});
|
||||
|
||||
void registerMock(UserCardsCubit cubit) {
|
||||
sl.registerLazySingleton<UserCardsCubit>(() => cubit);
|
||||
}
|
||||
|
||||
Widget wrap(UserCardsCubit cubit, Widget child) {
|
||||
return MaterialApp(
|
||||
theme: ThemeHelper().themeData(),
|
||||
localizationsDelegates: const [
|
||||
S.delegate,
|
||||
GlobalMaterialLocalizations.delegate,
|
||||
GlobalWidgetsLocalizations.delegate,
|
||||
GlobalCupertinoLocalizations.delegate,
|
||||
],
|
||||
supportedLocales: const [Locale('ru')],
|
||||
home: BlocProvider<UserCardsCubit>.value(
|
||||
value: cubit,
|
||||
child: child,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
testWidgets('renders with loading state', (tester) async {
|
||||
final cubit = MockUserCardsCubit();
|
||||
final state = UserCardsState(state: BaseState.initial, cards: const [], error: null);
|
||||
|
||||
when(cubit.state).thenReturn(state);
|
||||
when(cubit.stream).thenAnswer((_) => const Stream<UserCardsState>.empty());
|
||||
when(cubit.close()).thenAnswer((_) async => Future<void>.value());
|
||||
when(cubit.getCards()).thenAnswer((_) async => Future<void>.value());
|
||||
|
||||
registerMock(cubit);
|
||||
|
||||
await tester.pumpWidget(wrap(cubit, const CardSettingsScreen()));
|
||||
await tester.pump();
|
||||
|
||||
expect(find.byType(CardSettingsScreen), findsOneWidget);
|
||||
});
|
||||
|
||||
testWidgets('renders with cards', (tester) async {
|
||||
final cubit = MockUserCardsCubit();
|
||||
final state = UserCardsState(
|
||||
state: BaseState.success,
|
||||
cards: [CardModel(id: 1, cardNumber: '1234567890123456', isDefault: true)],
|
||||
error: null,
|
||||
);
|
||||
|
||||
when(cubit.state).thenReturn(state);
|
||||
when(cubit.stream).thenAnswer((_) => const Stream<UserCardsState>.empty());
|
||||
when(cubit.close()).thenAnswer((_) async => Future<void>.value());
|
||||
when(cubit.getCards()).thenAnswer((_) async => Future<void>.value());
|
||||
|
||||
registerMock(cubit);
|
||||
|
||||
await tester.pumpWidget(wrap(cubit, const CardSettingsScreen()));
|
||||
await tester.pump();
|
||||
|
||||
expect(find.byType(CardSettingsScreen), findsOneWidget);
|
||||
});
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,111 @@
|
||||
import 'package:eatvio_app/core/app_config.dart';
|
||||
import 'package:eatvio_app/core/app_initializer.dart';
|
||||
import 'package:eatvio_app/core/locator_service.dart';
|
||||
import 'package:eatvio_app/core/theme/theme_helper.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/my_finance/presentation/pages/user_finance_screen.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_bloc/flutter_bloc.dart';
|
||||
import 'package:flutter_dotenv/flutter_dotenv.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 'user_finance_screen_smoke_test.mocks.dart';
|
||||
|
||||
@GenerateNiceMocks([MockSpec<ProfileAvatarCubit>()])
|
||||
void main() {
|
||||
group('UserFinanceScreen smoke', () {
|
||||
setUpAll(() async {
|
||||
dotenv.loadFromString(envString: 'DEV_BASE_URL=http://localhost\nBASE_URL=http://localhost');
|
||||
});
|
||||
|
||||
setUp(() async {
|
||||
await sl.reset();
|
||||
sl.registerLazySingleton<AppConfig>(() => AppConfig(buildType: AppBuildType.tests)..setBaseUrl('http://localhost'));
|
||||
});
|
||||
|
||||
const mockProfile = ProfileModel(
|
||||
id: 1,
|
||||
name: 'Test User',
|
||||
email: 'test@example.com',
|
||||
phoneConfirmationState: 'confirmed',
|
||||
isPublicProfile: true,
|
||||
isBlockedByMe: false,
|
||||
isBlockedByAuthor: false,
|
||||
isBlockedByUser: false,
|
||||
isEntitySubscribedByMe: false,
|
||||
slug: 'test-user',
|
||||
profilePhotoUrl: 'https://example.com/avatar.jpg',
|
||||
roles: ['user'],
|
||||
badges: [],
|
||||
subscriptions: [],
|
||||
subscribers: [],
|
||||
);
|
||||
|
||||
Widget wrap(Widget child) {
|
||||
return MaterialApp(
|
||||
theme: ThemeHelper().themeData(),
|
||||
localizationsDelegates: const [
|
||||
S.delegate,
|
||||
GlobalMaterialLocalizations.delegate,
|
||||
GlobalWidgetsLocalizations.delegate,
|
||||
GlobalCupertinoLocalizations.delegate,
|
||||
],
|
||||
supportedLocales: const [Locale('ru')],
|
||||
home: child,
|
||||
);
|
||||
}
|
||||
|
||||
testWidgets('renders user finance screen', (tester) async {
|
||||
final cubit = MockProfileAvatarCubit();
|
||||
const state = ProfileAvatarStateLoaded(profile: mockProfile);
|
||||
|
||||
when(cubit.state).thenReturn(state);
|
||||
when(cubit.stream).thenAnswer((_) => const Stream.empty());
|
||||
when(cubit.close()).thenAnswer((_) async => Future<void>.value());
|
||||
|
||||
await tester.pumpWidget(BlocProvider<ProfileAvatarCubit>.value(value: cubit, child: wrap(const UserFinanceScreen())));
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
expect(find.text('Финансы'), findsOneWidget);
|
||||
});
|
||||
|
||||
testWidgets('renders consultant finance screen with card management', (tester) async {
|
||||
const consultantProfile = ProfileModel(
|
||||
id: 1,
|
||||
name: 'Test Consultant',
|
||||
email: 'consultant@example.com',
|
||||
phoneConfirmationState: 'confirmed',
|
||||
isPublicProfile: true,
|
||||
isBlockedByMe: false,
|
||||
isBlockedByAuthor: false,
|
||||
isBlockedByUser: false,
|
||||
isEntitySubscribedByMe: false,
|
||||
slug: 'test-consultant',
|
||||
profilePhotoUrl: 'https://example.com/avatar.jpg',
|
||||
roles: ['consultant'],
|
||||
badges: [],
|
||||
subscriptions: [],
|
||||
subscribers: [],
|
||||
);
|
||||
|
||||
final cubit = MockProfileAvatarCubit();
|
||||
const state = ProfileAvatarStateLoaded(profile: consultantProfile);
|
||||
|
||||
when(cubit.state).thenReturn(state);
|
||||
when(cubit.stream).thenAnswer((_) => const Stream.empty());
|
||||
when(cubit.close()).thenAnswer((_) async => Future<void>.value());
|
||||
|
||||
await tester.pumpWidget(BlocProvider<ProfileAvatarCubit>.value(value: cubit, child: wrap(const UserFinanceScreen())));
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
expect(find.text('Финансы'), findsOneWidget);
|
||||
expect(find.text('Управление картами'), findsOneWidget);
|
||||
});
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,99 @@
|
||||
import 'package:eatvio_app/core/app_config.dart';
|
||||
import 'package:eatvio_app/core/app_initializer.dart';
|
||||
import 'package:eatvio_app/core/locator_service.dart';
|
||||
import 'package:eatvio_app/core/models/pagination_model.dart';
|
||||
import 'package:eatvio_app/core/theme/theme_helper.dart';
|
||||
import 'package:eatvio_app/features/feed/models/plan_pagination_model.dart';
|
||||
import 'package:eatvio_app/features/my_plans/presentation/cubit/my_plans_cubit.dart';
|
||||
import 'package:eatvio_app/features/my_plans/presentation/my_plans_screen.dart';
|
||||
import 'package:eatvio_app/features/my_plans/repositories/my_diets_repository.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:mockito/annotations.dart';
|
||||
import 'package:mockito/mockito.dart';
|
||||
|
||||
import 'my_plans_screen_smoke_test.mocks.dart';
|
||||
|
||||
@GenerateNiceMocks([MockSpec<MyPlansRepository>()])
|
||||
void main() {
|
||||
group('MyPlansScreen smoke', () {
|
||||
setUpAll(() async {
|
||||
dotenv.loadFromString(envString: 'DEV_BASE_URL=http://localhost\nBASE_URL=http://localhost');
|
||||
});
|
||||
|
||||
setUp(() async {
|
||||
await sl.reset();
|
||||
sl.registerLazySingleton<AppConfig>(() => AppConfig(buildType: AppBuildType.tests)..setBaseUrl('http://localhost'));
|
||||
});
|
||||
|
||||
const mockPlansPagination = PlanPaginationModel(
|
||||
data: [],
|
||||
links: Links(first: 'http://localhost/api?page=1', last: 'http://localhost/api?page=1', prev: null, next: null),
|
||||
meta: Meta(currentPage: 1, from: null, lastPage: 1, links: [], perPage: 10, to: null, total: 0),
|
||||
);
|
||||
|
||||
Widget wrap(Widget child) {
|
||||
return MaterialApp(
|
||||
theme: ThemeHelper().themeData(),
|
||||
localizationsDelegates: const [
|
||||
S.delegate,
|
||||
GlobalMaterialLocalizations.delegate,
|
||||
GlobalWidgetsLocalizations.delegate,
|
||||
GlobalCupertinoLocalizations.delegate,
|
||||
],
|
||||
supportedLocales: const [Locale('ru')],
|
||||
home: child,
|
||||
);
|
||||
}
|
||||
|
||||
testWidgets('renders loading state', (tester) async {
|
||||
final repository = MockMyPlansRepository();
|
||||
final cubit = MyPlansCubit(myPlansRepository: repository);
|
||||
|
||||
when(repository.getPlans(page: 1)).thenAnswer((_) async => mockPlansPagination);
|
||||
|
||||
sl.registerLazySingleton<MyPlansRepository>(() => repository);
|
||||
sl.registerLazySingleton<MyPlansCubit>(() => cubit);
|
||||
|
||||
await tester.pumpWidget(wrap(const MyPlansScreen()));
|
||||
await tester.pump();
|
||||
|
||||
expect(find.byType(MyPlansScreen), findsOneWidget);
|
||||
});
|
||||
|
||||
testWidgets('renders loaded state', (tester) async {
|
||||
final repository = MockMyPlansRepository();
|
||||
final cubit = MyPlansCubit(myPlansRepository: repository);
|
||||
|
||||
when(repository.getPlans(page: 1)).thenAnswer((_) async => mockPlansPagination);
|
||||
|
||||
sl.registerLazySingleton<MyPlansRepository>(() => repository);
|
||||
sl.registerLazySingleton<MyPlansCubit>(() => cubit);
|
||||
|
||||
await tester.pumpWidget(wrap(const MyPlansScreen()));
|
||||
await tester.pump();
|
||||
await tester.pump(const Duration(milliseconds: 100));
|
||||
|
||||
expect(find.byType(MyPlansScreen), findsOneWidget);
|
||||
});
|
||||
|
||||
testWidgets('renders error state', (tester) async {
|
||||
final repository = MockMyPlansRepository();
|
||||
final cubit = MyPlansCubit(myPlansRepository: repository);
|
||||
|
||||
when(repository.getPlans(page: 1)).thenThrow(Exception('Error loading plans'));
|
||||
|
||||
sl.registerLazySingleton<MyPlansRepository>(() => repository);
|
||||
sl.registerLazySingleton<MyPlansCubit>(() => cubit);
|
||||
|
||||
await tester.pumpWidget(wrap(const MyPlansScreen()));
|
||||
await tester.pump();
|
||||
await tester.pump(const Duration(milliseconds: 100));
|
||||
|
||||
expect(find.byType(MyPlansScreen), findsOneWidget);
|
||||
});
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,70 @@
|
||||
import 'package:eatvio_app/core/app_config.dart';
|
||||
import 'package:eatvio_app/core/app_initializer.dart';
|
||||
import 'package:eatvio_app/core/locator_service.dart';
|
||||
import 'package:eatvio_app/core/theme/theme_helper.dart';
|
||||
import 'package:eatvio_app/features/notifications/models/notification_model.dart';
|
||||
import 'package:eatvio_app/features/notifications/presentation/notifications_cubit/notifications_cubit.dart';
|
||||
import 'package:eatvio_app/features/notifications/presentation/notifications_cubit/notifications_state.dart';
|
||||
import 'package:eatvio_app/features/notifications/presentation/notifications_screen.dart';
|
||||
import 'package:eatvio_app/generated/l10n.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:flutter_dotenv/flutter_dotenv.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 'notifications_screen_smoke_test.mocks.dart';
|
||||
|
||||
@GenerateNiceMocks([MockSpec<NotificationsCubit>()])
|
||||
void main() {
|
||||
group('NotificationsScreen smoke', () {
|
||||
setUpAll(() async {
|
||||
dotenv.loadFromString(envString: 'DEV_BASE_URL=http://localhost\nBASE_URL=http://localhost');
|
||||
});
|
||||
|
||||
setUp(() async {
|
||||
await sl.reset();
|
||||
sl.registerLazySingleton<AppConfig>(() => AppConfig(buildType: AppBuildType.tests)..setBaseUrl('http://localhost'));
|
||||
});
|
||||
|
||||
Widget wrap(NotificationsCubit cubit) {
|
||||
return MaterialApp(
|
||||
theme: ThemeHelper().themeData(),
|
||||
localizationsDelegates: const [
|
||||
S.delegate,
|
||||
GlobalMaterialLocalizations.delegate,
|
||||
GlobalWidgetsLocalizations.delegate,
|
||||
GlobalCupertinoLocalizations.delegate,
|
||||
],
|
||||
supportedLocales: const [Locale('ru')],
|
||||
home: BlocProvider<NotificationsCubit>.value(value: cubit, child: const NotificationsScreen()),
|
||||
);
|
||||
}
|
||||
|
||||
testWidgets('renders loaded state with notifications list', (tester) async {
|
||||
final cubit = MockNotificationsCubit();
|
||||
final state = NotificationsState.loaded(
|
||||
notifications: [DiaryReminderNotification(type: NotificationType.diaryReminderNotification, isRead: false, createdAt: DateTime(2025, 1, 1))],
|
||||
notificationsCount: 1,
|
||||
isLoading: false,
|
||||
hasUnreadNotifications: true,
|
||||
currentPage: 1,
|
||||
hasNextPage: false,
|
||||
isLoadingMore: false,
|
||||
);
|
||||
|
||||
when(cubit.state).thenReturn(state);
|
||||
when(cubit.stream).thenAnswer((_) => const Stream<NotificationsState>.empty());
|
||||
when(cubit.close()).thenAnswer((_) async => Future<void>.value());
|
||||
when(cubit.markNotificationsAsRead()).thenAnswer((_) async => Future<void>.value());
|
||||
|
||||
await tester.pumpWidget(wrap(cubit));
|
||||
await tester.pump();
|
||||
|
||||
expect(find.text('Уведомления'), findsOneWidget);
|
||||
expect(find.text(S.current.markAsRead), findsOneWidget);
|
||||
});
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,79 @@
|
||||
import 'package:eatvio_app/core/app_config.dart';
|
||||
import 'package:eatvio_app/core/app_initializer.dart';
|
||||
import 'package:eatvio_app/core/locator_service.dart';
|
||||
import 'package:eatvio_app/core/theme/theme_helper.dart';
|
||||
import 'package:eatvio_app/features/order_info/cubit/order_info_cubit.dart';
|
||||
import 'package:eatvio_app/features/order_info/cubit/order_info_state.dart';
|
||||
import 'package:eatvio_app/features/order_info/presentation/info_by_order_screen.dart';
|
||||
import 'package:eatvio_app/generated/l10n.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:flutter_dotenv/flutter_dotenv.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 'info_by_order_screen_smoke_test.mocks.dart';
|
||||
|
||||
@GenerateNiceMocks([MockSpec<OrderInfoCubit>()])
|
||||
void main() {
|
||||
group('InfoByOrderScreen smoke', () {
|
||||
setUpAll(() async {
|
||||
dotenv.loadFromString(envString: 'DEV_BASE_URL=http://localhost\nBASE_URL=http://localhost');
|
||||
});
|
||||
|
||||
setUp(() async {
|
||||
await sl.reset();
|
||||
sl.registerLazySingleton<AppConfig>(() => AppConfig(buildType: AppBuildType.tests)..setBaseUrl('http://localhost'));
|
||||
});
|
||||
|
||||
void registerMock(OrderInfoCubit cubit) {
|
||||
sl.registerLazySingleton<OrderInfoCubit>(() => cubit);
|
||||
}
|
||||
|
||||
Widget wrap(OrderInfoCubit cubit, Widget child) {
|
||||
final router = GoRouter(
|
||||
routes: [
|
||||
GoRoute(
|
||||
path: '/',
|
||||
builder: (context, state) => BlocProvider<OrderInfoCubit>.value(
|
||||
value: cubit,
|
||||
child: child,
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
|
||||
return MaterialApp.router(
|
||||
theme: ThemeHelper().themeData(),
|
||||
localizationsDelegates: const [
|
||||
S.delegate,
|
||||
GlobalMaterialLocalizations.delegate,
|
||||
GlobalWidgetsLocalizations.delegate,
|
||||
GlobalCupertinoLocalizations.delegate,
|
||||
],
|
||||
supportedLocales: const [Locale('ru')],
|
||||
routerConfig: router,
|
||||
);
|
||||
}
|
||||
|
||||
testWidgets('renders loading state', (tester) async {
|
||||
final cubit = MockOrderInfoCubit();
|
||||
final state = OrderInfoState(isLoading: true, chosenPlan: null);
|
||||
|
||||
when(cubit.state).thenReturn(state);
|
||||
when(cubit.stream).thenAnswer((_) => const Stream<OrderInfoState>.empty());
|
||||
when(cubit.close()).thenAnswer((_) async => Future<void>.value());
|
||||
when(cubit.getChosenPlan(id: anyNamed('id'), customerSlug: anyNamed('customerSlug'))).thenAnswer((_) async => Future<void>.value());
|
||||
|
||||
registerMock(cubit);
|
||||
|
||||
await tester.pumpWidget(wrap(cubit, const InfoByOrderScreen(planId: 1)));
|
||||
await tester.pump();
|
||||
|
||||
expect(find.byType(InfoByOrderScreen), findsOneWidget);
|
||||
});
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,186 @@
|
||||
import 'package:eatvio_app/core/app_config.dart';
|
||||
import 'package:eatvio_app/core/app_initializer.dart';
|
||||
import 'package:eatvio_app/core/comments/comments_cubit/comments_cubit.dart';
|
||||
import 'package:eatvio_app/core/comments/comments_cubit/comments_state.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/diets_pagination_model.dart';
|
||||
import 'package:eatvio_app/features/feed/models/plan_pagination_model.dart';
|
||||
import 'package:eatvio_app/features/feed/presentation/bloc/feed_cubit/feed_cubit.dart';
|
||||
import 'package:eatvio_app/features/plans/presentation/cubit/about_plan_cubit.dart';
|
||||
import 'package:eatvio_app/features/plans/presentation/cubit/about_plan_state.dart';
|
||||
import 'package:eatvio_app/features/plans/presentation/pages/about_plan_screen.dart';
|
||||
import 'package:eatvio_app/generated/l10n.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:flutter_dotenv/flutter_dotenv.dart';
|
||||
import 'package:flutter_localizations/flutter_localizations.dart';
|
||||
import 'package:flutter_portal/flutter_portal.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:network_image_mock/network_image_mock.dart';
|
||||
|
||||
import 'about_plan_screen_smoke_test.mocks.dart';
|
||||
|
||||
@GenerateNiceMocks([MockSpec<AboutPlanCubit>(), MockSpec<FeedCubit>(), MockSpec<CommentsCubit>()])
|
||||
void main() {
|
||||
group('AboutPlanScreen smoke', () {
|
||||
setUpAll(() async {
|
||||
dotenv.loadFromString(envString: 'DEV_BASE_URL=http://localhost\nBASE_URL=http://localhost');
|
||||
});
|
||||
|
||||
setUp(() async {
|
||||
await sl.reset();
|
||||
sl.registerLazySingleton<AppConfig>(() => AppConfig(buildType: AppBuildType.tests)..setBaseUrl('http://localhost'));
|
||||
});
|
||||
|
||||
void registerMocks(AboutPlanCubit planCubit, CommentsCubit commentsCubit) {
|
||||
sl.registerLazySingleton<AboutPlanCubit>(() => planCubit);
|
||||
sl.registerLazySingleton<CommentsCubit>(() => commentsCubit);
|
||||
}
|
||||
|
||||
const testPlan = PlanModel(
|
||||
id: 1,
|
||||
slug: 'test-plan',
|
||||
name: 'Test Plan',
|
||||
image: 'https://example.com/image.jpg',
|
||||
duration: 7,
|
||||
state: 'active',
|
||||
type: PlanType(fit: true, slim: false, behavior: false, wellness: false),
|
||||
details: 'Test details',
|
||||
packetsCount: 1,
|
||||
packet1Options: null,
|
||||
packet2Options: null,
|
||||
packet3Options: null,
|
||||
author: Author(
|
||||
authorSlug: 'author',
|
||||
authorName: 'Author Name',
|
||||
authorProfileIcon: 'https://example.com/icon.jpg',
|
||||
authorIsMe: false,
|
||||
isEntitySubscribedByMe: null,
|
||||
authorPlaceInRating: null,
|
||||
),
|
||||
totalLikes: 10,
|
||||
totalViews: 100,
|
||||
totalComments: 5,
|
||||
isFavorite: false,
|
||||
minPriceOfOptions: 100.0,
|
||||
maxPriceOfOptions: 200.0,
|
||||
wasLikedByMe: false,
|
||||
);
|
||||
|
||||
Widget wrap(AboutPlanCubit planCubit, FeedCubit feedCubit, CommentsCubit commentsCubit, Widget child) {
|
||||
final router = GoRouter(
|
||||
routes: [
|
||||
GoRoute(
|
||||
path: '/',
|
||||
builder: (context, state) => MultiBlocProvider(
|
||||
providers: [
|
||||
BlocProvider<AboutPlanCubit>.value(value: planCubit),
|
||||
BlocProvider<FeedCubit>.value(value: feedCubit),
|
||||
BlocProvider<CommentsCubit>.value(value: commentsCubit),
|
||||
],
|
||||
child: child,
|
||||
),
|
||||
),
|
||||
GoRoute(path: '/feed-screen', builder: (_, _) => const SizedBox.shrink()),
|
||||
],
|
||||
);
|
||||
|
||||
return Portal(
|
||||
child: MaterialApp.router(
|
||||
theme: ThemeHelper().themeData(),
|
||||
localizationsDelegates: const [
|
||||
S.delegate,
|
||||
GlobalMaterialLocalizations.delegate,
|
||||
GlobalWidgetsLocalizations.delegate,
|
||||
GlobalCupertinoLocalizations.delegate,
|
||||
],
|
||||
supportedLocales: const [Locale('ru')],
|
||||
routerConfig: router,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
testWidgets('renders loading state', (tester) async {
|
||||
final planCubit = MockAboutPlanCubit();
|
||||
final feedCubit = MockFeedCubit();
|
||||
final commentsCubit = MockCommentsCubit();
|
||||
const state = AboutPlanState(isLoading: true, error: null, planModel: null, chosenOptionIndex: 0);
|
||||
|
||||
when(planCubit.state).thenReturn(state);
|
||||
when(planCubit.stream).thenAnswer((_) => const Stream<AboutPlanState>.empty());
|
||||
when(planCubit.close()).thenAnswer((_) async => Future<void>.value());
|
||||
when(feedCubit.viewPlan(any)).thenAnswer((_) async => Future<void>.value());
|
||||
when(feedCubit.stream).thenAnswer((_) => const Stream.empty());
|
||||
when(feedCubit.close()).thenAnswer((_) async => Future<void>.value());
|
||||
when(commentsCubit.state).thenReturn(const CommentsState());
|
||||
when(commentsCubit.stream).thenAnswer((_) => const Stream<CommentsState>.empty());
|
||||
when(commentsCubit.close()).thenAnswer((_) async => Future<void>.value());
|
||||
when(commentsCubit.getComments(any, any, any)).thenAnswer((_) async => Future<void>.value());
|
||||
|
||||
registerMocks(planCubit, commentsCubit);
|
||||
|
||||
await tester.pumpWidget(wrap(planCubit, feedCubit, commentsCubit, const AboutPlanScreen(slug: 'test', navigateToComments: false, index: null)));
|
||||
await tester.pump();
|
||||
|
||||
expect(find.byType(AboutPlanScreen), findsOneWidget);
|
||||
});
|
||||
|
||||
testWidgets('renders loaded state with plan', (tester) async {
|
||||
final planCubit = MockAboutPlanCubit();
|
||||
final feedCubit = MockFeedCubit();
|
||||
final commentsCubit = MockCommentsCubit();
|
||||
const state = AboutPlanState(isLoading: false, planModel: testPlan, chosenOptionIndex: 0, error: null);
|
||||
|
||||
when(planCubit.state).thenReturn(state);
|
||||
when(planCubit.stream).thenAnswer((_) => const Stream<AboutPlanState>.empty());
|
||||
when(planCubit.close()).thenAnswer((_) async => Future<void>.value());
|
||||
when(feedCubit.viewPlan(any)).thenAnswer((_) async => Future<void>.value());
|
||||
when(feedCubit.stream).thenAnswer((_) => const Stream.empty());
|
||||
when(feedCubit.close()).thenAnswer((_) async => Future<void>.value());
|
||||
when(commentsCubit.state).thenReturn(const CommentsState());
|
||||
when(commentsCubit.stream).thenAnswer((_) => const Stream<CommentsState>.empty());
|
||||
when(commentsCubit.close()).thenAnswer((_) async => Future<void>.value());
|
||||
when(commentsCubit.getComments(any, any, any)).thenAnswer((_) async => Future<void>.value());
|
||||
|
||||
registerMocks(planCubit, commentsCubit);
|
||||
|
||||
await mockNetworkImagesFor(() async {
|
||||
await tester.pumpWidget(wrap(planCubit, feedCubit, commentsCubit, const AboutPlanScreen(slug: 'test', navigateToComments: false, index: null)));
|
||||
await tester.pump();
|
||||
await tester.pump();
|
||||
});
|
||||
|
||||
expect(find.byType(AboutPlanScreen), findsOneWidget);
|
||||
expect(find.text('Test Plan'), findsOneWidget);
|
||||
});
|
||||
|
||||
testWidgets('renders error state', (tester) async {
|
||||
final planCubit = MockAboutPlanCubit();
|
||||
final feedCubit = MockFeedCubit();
|
||||
final commentsCubit = MockCommentsCubit();
|
||||
const state = AboutPlanState(isLoading: false, error: 'Error loading plan', planModel: testPlan, chosenOptionIndex: 0);
|
||||
|
||||
when(planCubit.state).thenReturn(state);
|
||||
when(planCubit.stream).thenAnswer((_) => const Stream<AboutPlanState>.empty());
|
||||
when(planCubit.close()).thenAnswer((_) async => Future<void>.value());
|
||||
when(planCubit.getPlan(any)).thenAnswer((_) async => Future<void>.value());
|
||||
when(feedCubit.viewPlan(any)).thenAnswer((_) async => Future<void>.value());
|
||||
when(feedCubit.stream).thenAnswer((_) => const Stream.empty());
|
||||
when(feedCubit.close()).thenAnswer((_) async => Future<void>.value());
|
||||
when(commentsCubit.state).thenReturn(const CommentsState());
|
||||
when(commentsCubit.stream).thenAnswer((_) => const Stream<CommentsState>.empty());
|
||||
when(commentsCubit.close()).thenAnswer((_) async => Future<void>.value());
|
||||
|
||||
registerMocks(planCubit, commentsCubit);
|
||||
|
||||
await tester.pumpWidget(wrap(planCubit, feedCubit, commentsCubit, const AboutPlanScreen(slug: 'test', navigateToComments: false, index: null)));
|
||||
await tester.pump();
|
||||
|
||||
expect(find.byType(AboutPlanScreen), findsOneWidget);
|
||||
});
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,205 @@
|
||||
import 'package:eatvio_app/core/app_config.dart';
|
||||
import 'package:eatvio_app/core/app_initializer.dart';
|
||||
import 'package:eatvio_app/core/locator_service.dart';
|
||||
import 'package:eatvio_app/core/theme/theme_helper.dart';
|
||||
import 'package:eatvio_app/features/chat/repositories/chat_repository.dart';
|
||||
import 'package:eatvio_app/features/consultant/become_consultant/repositories/consultant_repository.dart';
|
||||
import 'package:eatvio_app/features/profile/models/circumference_model.dart';
|
||||
import 'package:eatvio_app/features/profile/models/profile_model.dart';
|
||||
import 'package:eatvio_app/features/profile/models/weight_model.dart';
|
||||
import 'package:eatvio_app/features/profile/presentation/bloc/plans_cubit/plans_cubit.dart' as profile_plans;
|
||||
import 'package:eatvio_app/features/profile/presentation/bloc/profile_cubit/profile_cubit.dart';
|
||||
import 'package:eatvio_app/features/profile/presentation/pages/profile_screen.dart';
|
||||
import 'package:eatvio_app/features/profile/repositories/profile_repository.dart';
|
||||
import 'package:eatvio_app/features/plans/repositories/plans_repository.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:flutter_dotenv/flutter_dotenv.dart';
|
||||
import 'package:mockito/annotations.dart';
|
||||
import 'package:mockito/mockito.dart';
|
||||
import 'package:network_image_mock/network_image_mock.dart';
|
||||
|
||||
import 'profile_screen_smoke_test.mocks.dart';
|
||||
|
||||
@GenerateNiceMocks([MockSpec<ProfileRepository>(), MockSpec<ChatRepository>(), MockSpec<ConsultantRepository>(), MockSpec<PlansRepository>()])
|
||||
void main() {
|
||||
group('ProfileScreen smoke', () {
|
||||
late MockProfileRepository profileRepository;
|
||||
late MockChatRepository chatRepository;
|
||||
late MockConsultantRepository consultantRepository;
|
||||
late MockPlansRepository plansRepository;
|
||||
|
||||
setUpAll(() async {
|
||||
dotenv.loadFromString(envString: 'DEV_BASE_URL=http://localhost\nBASE_URL=http://localhost');
|
||||
});
|
||||
|
||||
setUp(() async {
|
||||
await sl.reset();
|
||||
sl.registerLazySingleton<AppConfig>(() => AppConfig(buildType: AppBuildType.tests)..setBaseUrl('http://localhost'));
|
||||
|
||||
profileRepository = MockProfileRepository();
|
||||
chatRepository = MockChatRepository();
|
||||
consultantRepository = MockConsultantRepository();
|
||||
plansRepository = MockPlansRepository();
|
||||
|
||||
sl.registerLazySingleton<ProfileRepository>(() => profileRepository);
|
||||
sl.registerLazySingleton<ChatRepository>(() => chatRepository);
|
||||
sl.registerLazySingleton<ConsultantRepository>(() => consultantRepository);
|
||||
sl.registerLazySingleton<PlansRepository>(() => plansRepository);
|
||||
|
||||
sl.registerFactory<ProfileCubit>(() => ProfileCubit(profileRepository: sl(), chatRepository: sl(), consultantRepository: sl()));
|
||||
sl.registerFactory<profile_plans.PlansCubit>(() => profile_plans.PlansCubit(plansRepository: sl()));
|
||||
});
|
||||
|
||||
Widget wrap(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),
|
||||
);
|
||||
}
|
||||
|
||||
testWidgets('renders my profile loaded state', (tester) async {
|
||||
const profile = ProfileModel(
|
||||
id: 1,
|
||||
name: 'Test User',
|
||||
email: 'test@example.com',
|
||||
phoneConfirmationState: 'confirmed',
|
||||
slug: 'test-user',
|
||||
isPublicProfile: true,
|
||||
isBlockedByMe: false,
|
||||
isBlockedByAuthor: false,
|
||||
isBlockedByUser: false,
|
||||
isEntitySubscribedByMe: false,
|
||||
profilePhotoUrl: '',
|
||||
roles: [],
|
||||
badges: [],
|
||||
subscriptions: [],
|
||||
subscribers: [],
|
||||
);
|
||||
|
||||
const profileDetails = ProfileDetailsModel(
|
||||
firstName: 'Test',
|
||||
lastName: 'User',
|
||||
birthdate: '1990-01-01',
|
||||
gender: 'male',
|
||||
country: 'Russia',
|
||||
city: 'Moscow',
|
||||
lang: 'ru',
|
||||
weight: 70.0,
|
||||
height: 175,
|
||||
chestCircumference: 100,
|
||||
waistCircumference: 80,
|
||||
hipCircumference: 90,
|
||||
bloodPressureUp: 120,
|
||||
bloodPressureDown: 80,
|
||||
hasHypo: false,
|
||||
hasHyper: false,
|
||||
aboutAlergy: '',
|
||||
aboutTakingPils: '',
|
||||
aboutSportHobbies: '',
|
||||
favoriteFoods: '',
|
||||
unfavoriteFoods: '',
|
||||
additionalInfo: '',
|
||||
isMoodFillable: true,
|
||||
isBedtimeFillable: true,
|
||||
activityCoefficient: 1.5,
|
||||
);
|
||||
|
||||
when(profileRepository.getMyProfile()).thenAnswer((_) async => profile);
|
||||
when(profileRepository.getMyProfileDetails()).thenAnswer((_) async => profileDetails);
|
||||
when(profileRepository.getBlogPostsByAuthorSlug(slug: anyNamed('slug'))).thenAnswer((_) async => []);
|
||||
when(profileRepository.getWeight(period: anyNamed('period'))).thenAnswer((_) async => <WeightModel>[]);
|
||||
when(profileRepository.getCircumferences(period: anyNamed('period'))).thenAnswer((_) async => <CircumferenceModel>[]);
|
||||
|
||||
await mockNetworkImagesFor(() async {
|
||||
await tester.pumpWidget(wrap(const ProfileScreen()));
|
||||
await tester.pumpAndSettle();
|
||||
});
|
||||
|
||||
expect(find.byType(ProfileScreen), findsOneWidget);
|
||||
});
|
||||
|
||||
testWidgets('renders error state', (tester) async {
|
||||
when(profileRepository.getMyProfile()).thenThrow(Exception('Error loading profile'));
|
||||
when(profileRepository.getMyProfileDetails()).thenThrow(Exception('Error'));
|
||||
|
||||
await tester.pumpWidget(wrap(const ProfileScreen()));
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
expect(find.byType(ProfileScreen), findsOneWidget);
|
||||
});
|
||||
|
||||
testWidgets('renders blocked user state', (tester) async {
|
||||
const profile = ProfileModel(
|
||||
id: 1,
|
||||
name: 'Test User',
|
||||
email: 'test@example.com',
|
||||
phoneConfirmationState: 'confirmed',
|
||||
slug: 'test-user',
|
||||
isPublicProfile: true,
|
||||
isBlockedByMe: false,
|
||||
isBlockedByAuthor: false,
|
||||
isBlockedByUser: true,
|
||||
isEntitySubscribedByMe: false,
|
||||
profilePhotoUrl: '',
|
||||
roles: [],
|
||||
badges: [],
|
||||
subscriptions: [],
|
||||
subscribers: [],
|
||||
);
|
||||
|
||||
const profileDetails = ProfileDetailsModel(
|
||||
firstName: 'Test',
|
||||
lastName: 'User',
|
||||
birthdate: '1990-01-01',
|
||||
gender: 'male',
|
||||
country: 'Russia',
|
||||
city: 'Moscow',
|
||||
lang: 'ru',
|
||||
weight: 70.0,
|
||||
height: 175,
|
||||
chestCircumference: 100,
|
||||
waistCircumference: 80,
|
||||
hipCircumference: 90,
|
||||
bloodPressureUp: 120,
|
||||
bloodPressureDown: 80,
|
||||
hasHypo: false,
|
||||
hasHyper: false,
|
||||
aboutAlergy: '',
|
||||
aboutTakingPils: '',
|
||||
aboutSportHobbies: '',
|
||||
favoriteFoods: '',
|
||||
unfavoriteFoods: '',
|
||||
additionalInfo: '',
|
||||
isMoodFillable: true,
|
||||
isBedtimeFillable: true,
|
||||
activityCoefficient: 1.5,
|
||||
);
|
||||
|
||||
when(profileRepository.getProfileBySlug(any)).thenAnswer((_) async => profile);
|
||||
when(profileRepository.getMyProfile()).thenAnswer((_) async => profile);
|
||||
when(profileRepository.getMyProfileDetails()).thenAnswer((_) async => profileDetails);
|
||||
when(profileRepository.getProfileDetailsBySlug(any)).thenAnswer((_) async => profileDetails);
|
||||
when(profileRepository.getBlogPostsByAuthorSlug(slug: anyNamed('slug'))).thenAnswer((_) async => []);
|
||||
when(profileRepository.getWeight(period: anyNamed('period'))).thenAnswer((_) async => <WeightModel>[]);
|
||||
when(profileRepository.getCircumferences(period: anyNamed('period'))).thenAnswer((_) async => <CircumferenceModel>[]);
|
||||
|
||||
await mockNetworkImagesFor(() async {
|
||||
await tester.pumpWidget(wrap(const ProfileScreen(slug: 'test-user')));
|
||||
await tester.pumpAndSettle();
|
||||
});
|
||||
|
||||
expect(find.byType(ProfileScreen), findsOneWidget);
|
||||
expect(find.text('Пользователь ограничил доступ к своей странице'), findsOneWidget);
|
||||
});
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,96 @@
|
||||
import 'package:eatvio_app/core/app_config.dart';
|
||||
import 'package:eatvio_app/core/app_initializer.dart';
|
||||
import 'package:eatvio_app/core/locator_service.dart';
|
||||
import 'package:eatvio_app/core/theme/theme_helper.dart';
|
||||
import 'package:eatvio_app/features/profile/models/sub_model.dart';
|
||||
import 'package:eatvio_app/features/profile/presentation/bloc/sub_cubit/sub_cubit.dart';
|
||||
import 'package:eatvio_app/features/profile/presentation/pages/subs_screen.dart';
|
||||
import 'package:eatvio_app/features/profile/repositories/profile_repository.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_dotenv/flutter_dotenv.dart';
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:mockito/annotations.dart';
|
||||
import 'package:mockito/mockito.dart';
|
||||
import 'package:network_image_mock/network_image_mock.dart';
|
||||
|
||||
import 'subs_screen_smoke_test.mocks.dart';
|
||||
|
||||
@GenerateNiceMocks([MockSpec<ProfileRepository>()])
|
||||
void main() {
|
||||
group('SubsScreen smoke', () {
|
||||
setUpAll(() async {
|
||||
dotenv.loadFromString(envString: 'DEV_BASE_URL=http://localhost\nBASE_URL=http://localhost');
|
||||
});
|
||||
|
||||
setUp(() async {
|
||||
await sl.reset();
|
||||
sl.registerLazySingleton<AppConfig>(() => AppConfig(buildType: AppBuildType.tests)..setBaseUrl('http://localhost'));
|
||||
});
|
||||
|
||||
Widget wrap(Widget child) {
|
||||
return MaterialApp(theme: ThemeHelper().themeData(), home: child);
|
||||
}
|
||||
|
||||
testWidgets('renders subscriptions list', (tester) async {
|
||||
final repo = MockProfileRepository();
|
||||
|
||||
when(repo.getSubscriptionsBySlug(any, any)).thenAnswer(
|
||||
(_) async => [
|
||||
SubModel(
|
||||
slug: 'user-1',
|
||||
name: 'User 1',
|
||||
city: 'Moscow',
|
||||
birthdate: DateTime(1990, 1, 1),
|
||||
isEntitySubscribedByMe: false,
|
||||
profilePhotoUrl: '/photo.jpg',
|
||||
roles: const [],
|
||||
),
|
||||
],
|
||||
);
|
||||
|
||||
when(repo.getSubscribersBySlug(any, any)).thenAnswer((_) async => []);
|
||||
|
||||
sl.registerLazySingleton<ProfileRepository>(() => repo);
|
||||
sl.registerFactory<SubCubit>(() => SubCubit(profileRepository: sl()));
|
||||
|
||||
await mockNetworkImagesFor(() async {
|
||||
await tester.pumpWidget(wrap(const SubsScreen(slug: 'test', subType: SubType.subscriptions)));
|
||||
await tester.pumpAndSettle();
|
||||
});
|
||||
|
||||
expect(find.text('Подписки'), findsOneWidget);
|
||||
expect(find.text('User 1'), findsOneWidget);
|
||||
});
|
||||
|
||||
testWidgets('renders subscribers list', (tester) async {
|
||||
final repo = MockProfileRepository();
|
||||
|
||||
when(repo.getSubscribersBySlug(any, any)).thenAnswer(
|
||||
(_) async => [
|
||||
SubModel(
|
||||
slug: 'user-2',
|
||||
name: 'User 2',
|
||||
city: 'London',
|
||||
birthdate: DateTime(1995, 5, 15),
|
||||
isEntitySubscribedByMe: true,
|
||||
profilePhotoUrl: '/photo2.jpg',
|
||||
roles: const [],
|
||||
),
|
||||
],
|
||||
);
|
||||
|
||||
when(repo.getSubscriptionsBySlug(any, any)).thenAnswer((_) async => []);
|
||||
|
||||
sl.registerLazySingleton<ProfileRepository>(() => repo);
|
||||
sl.registerFactory<SubCubit>(() => SubCubit(profileRepository: sl()));
|
||||
|
||||
await mockNetworkImagesFor(() async {
|
||||
await tester.pumpWidget(wrap(const SubsScreen(slug: 'test', subType: SubType.subscribers)));
|
||||
await tester.pumpAndSettle();
|
||||
});
|
||||
|
||||
expect(find.text('Подписчики'), findsOneWidget);
|
||||
expect(find.text('User 2'), findsOneWidget);
|
||||
});
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,250 @@
|
||||
import 'dart:async';
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:eatvio_app/core/app_config.dart';
|
||||
import 'package:eatvio_app/core/app_initializer.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_blog_post_model.dart';
|
||||
import 'package:eatvio_app/features/feed/models/diets_pagination_model.dart';
|
||||
import 'package:eatvio_app/features/profile/models/profile_model.dart';
|
||||
import 'package:eatvio_app/features/profile/presentation/bloc/profile_cubit/profile_cubit.dart';
|
||||
import 'package:eatvio_app/features/profile/presentation/bloc/profile_cubit/profile_state.dart';
|
||||
import 'package:eatvio_app/features/profile/presentation/widgets/profile_blog_post_widget.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:mockito/annotations.dart';
|
||||
import 'package:mockito/mockito.dart';
|
||||
import 'package:eatvio_app/generated/l10n.dart';
|
||||
|
||||
import 'profile_blog_post_widget_smoke_test.mocks.dart';
|
||||
|
||||
class _FakeHttpClient implements HttpClient {
|
||||
bool _autoUncompress = true;
|
||||
|
||||
@override
|
||||
bool get autoUncompress => _autoUncompress;
|
||||
|
||||
@override
|
||||
set autoUncompress(bool value) {
|
||||
_autoUncompress = value;
|
||||
}
|
||||
|
||||
@override
|
||||
Future<HttpClientRequest> getUrl(Uri url) async => _FakeHttpClientRequest();
|
||||
|
||||
|
||||
@override
|
||||
dynamic noSuchMethod(Invocation invocation) => super.noSuchMethod(invocation);
|
||||
}
|
||||
|
||||
class _FakeHttpClientRequest implements HttpClientRequest {
|
||||
@override
|
||||
Future<HttpClientResponse> close() async => _FakeHttpClientResponse();
|
||||
|
||||
@override
|
||||
dynamic noSuchMethod(Invocation invocation) => super.noSuchMethod(invocation);
|
||||
}
|
||||
|
||||
class _FakeHttpClientResponse extends Stream<List<int>> implements HttpClientResponse {
|
||||
// 1x1 transparent PNG.
|
||||
static final List<int> _png = <int>[
|
||||
0x89,
|
||||
0x50,
|
||||
0x4E,
|
||||
0x47,
|
||||
0x0D,
|
||||
0x0A,
|
||||
0x1A,
|
||||
0x0A,
|
||||
0x00,
|
||||
0x00,
|
||||
0x00,
|
||||
0x0D,
|
||||
0x49,
|
||||
0x48,
|
||||
0x44,
|
||||
0x52,
|
||||
0x00,
|
||||
0x00,
|
||||
0x00,
|
||||
0x01,
|
||||
0x00,
|
||||
0x00,
|
||||
0x00,
|
||||
0x01,
|
||||
0x08,
|
||||
0x06,
|
||||
0x00,
|
||||
0x00,
|
||||
0x00,
|
||||
0x1F,
|
||||
0x15,
|
||||
0xC4,
|
||||
0x89,
|
||||
0x00,
|
||||
0x00,
|
||||
0x00,
|
||||
0x0A,
|
||||
0x49,
|
||||
0x44,
|
||||
0x41,
|
||||
0x54,
|
||||
0x78,
|
||||
0x9C,
|
||||
0x63,
|
||||
0x00,
|
||||
0x01,
|
||||
0x00,
|
||||
0x00,
|
||||
0x05,
|
||||
0x00,
|
||||
0x01,
|
||||
0x0D,
|
||||
0x0A,
|
||||
0x2D,
|
||||
0xB4,
|
||||
0x00,
|
||||
0x00,
|
||||
0x00,
|
||||
0x00,
|
||||
0x49,
|
||||
0x45,
|
||||
0x4E,
|
||||
0x44,
|
||||
0xAE,
|
||||
0x42,
|
||||
0x60,
|
||||
0x82,
|
||||
];
|
||||
|
||||
@override
|
||||
int get statusCode => 200;
|
||||
|
||||
@override
|
||||
int get contentLength => _png.length;
|
||||
|
||||
@override
|
||||
HttpClientResponseCompressionState get compressionState => HttpClientResponseCompressionState.notCompressed;
|
||||
|
||||
@override
|
||||
StreamSubscription<List<int>> listen(void Function(List<int>)? onData, {Function? onError, void Function()? onDone, bool? cancelOnError}) {
|
||||
return Stream<List<int>>.fromIterable([_png]).listen(onData, onError: onError, onDone: onDone, cancelOnError: cancelOnError);
|
||||
}
|
||||
|
||||
@override
|
||||
dynamic noSuchMethod(Invocation invocation) => super.noSuchMethod(invocation);
|
||||
}
|
||||
|
||||
@GenerateNiceMocks([MockSpec<ProfileCubit>()])
|
||||
void main() {
|
||||
group('ProfileBlogPostWidget smoke', () {
|
||||
setUpAll(() async {
|
||||
dotenv.loadFromString(envString: 'DEV_BASE_URL=http://localhost\nBASE_URL=http://localhost');
|
||||
});
|
||||
|
||||
setUp(() async {
|
||||
await sl.reset();
|
||||
sl.registerLazySingleton<AppConfig>(() => AppConfig(buildType: AppBuildType.tests)..setBaseUrl('http://localhost'));
|
||||
});
|
||||
|
||||
testWidgets('renders blog post card', (tester) async {
|
||||
final post = FeedBlogPostModel(
|
||||
id: 1,
|
||||
state: 'published',
|
||||
image: 'test.png',
|
||||
description: 'Hello',
|
||||
author: const Author(
|
||||
authorSlug: 'author',
|
||||
authorName: 'Author',
|
||||
authorProfileIcon: 'https://example.com/icon.png',
|
||||
authorIsMe: false,
|
||||
isEntitySubscribedByMe: false,
|
||||
),
|
||||
videoUrl: '',
|
||||
createdAt: DateTime(2025, 1, 1),
|
||||
updatedAt: DateTime(2025, 1, 1),
|
||||
title: 'Title',
|
||||
totalLikes: 0,
|
||||
totalViews: 0,
|
||||
totalComments: 0,
|
||||
wasLikedByMe: false,
|
||||
);
|
||||
|
||||
const profile = ProfileModel(
|
||||
id: 1,
|
||||
name: 'User',
|
||||
email: 'u@e.com',
|
||||
phoneConfirmationState: 'confirmed',
|
||||
slug: 'user',
|
||||
isPublicProfile: true,
|
||||
isBlockedByMe: false,
|
||||
isBlockedByAuthor: false,
|
||||
isBlockedByUser: false,
|
||||
isEntitySubscribedByMe: false,
|
||||
profilePhotoUrl: '',
|
||||
roles: [],
|
||||
badges: [],
|
||||
subscriptions: [],
|
||||
subscribers: [],
|
||||
);
|
||||
|
||||
const details = ProfileDetailsModel(
|
||||
firstName: 'Test',
|
||||
lastName: 'User',
|
||||
birthdate: '1990-01-01',
|
||||
gender: 'male',
|
||||
country: 'Russia',
|
||||
city: 'Moscow',
|
||||
lang: 'ru',
|
||||
weight: 70.0,
|
||||
height: 175,
|
||||
chestCircumference: 100,
|
||||
waistCircumference: 80,
|
||||
hipCircumference: 90,
|
||||
bloodPressureUp: 120,
|
||||
bloodPressureDown: 80,
|
||||
hasHypo: false,
|
||||
hasHyper: false,
|
||||
aboutAlergy: '',
|
||||
aboutTakingPils: '',
|
||||
aboutSportHobbies: '',
|
||||
favoriteFoods: '',
|
||||
unfavoriteFoods: '',
|
||||
additionalInfo: '',
|
||||
isMoodFillable: true,
|
||||
isBedtimeFillable: true,
|
||||
activityCoefficient: 1.5,
|
||||
);
|
||||
|
||||
final loadedState = ProfileState.loaded(profile: profile, profileDetails: details, blogPosts: [post], isMyProfile: true);
|
||||
final cubit = MockProfileCubit();
|
||||
when(cubit.state).thenReturn(loadedState);
|
||||
when(cubit.stream).thenAnswer((_) => const Stream<ProfileState>.empty());
|
||||
when(cubit.close()).thenAnswer((_) async => Future<void>.value());
|
||||
|
||||
await HttpOverrides.runZoned(() async {
|
||||
await tester.pumpWidget(
|
||||
MaterialApp(
|
||||
theme: ThemeHelper().themeData(),
|
||||
localizationsDelegates: const [
|
||||
S.delegate,
|
||||
GlobalMaterialLocalizations.delegate,
|
||||
GlobalWidgetsLocalizations.delegate,
|
||||
GlobalCupertinoLocalizations.delegate,
|
||||
],
|
||||
supportedLocales: const [Locale('ru')],
|
||||
home: Scaffold(body: ProfileBlogPostWidget(index: 0, profileCubit: cubit)),
|
||||
),
|
||||
);
|
||||
await tester.pumpAndSettle(const Duration(milliseconds: 200));
|
||||
}, createHttpClient: (context) => _FakeHttpClient());
|
||||
|
||||
expect(find.text('Регистрация'), findsNothing);
|
||||
expect(find.text('Title'), findsOneWidget);
|
||||
expect(find.text('Author'), findsOneWidget);
|
||||
});
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,64 @@
|
||||
import 'package:eatvio_app/core/theme/theme_helper.dart';
|
||||
import 'package:eatvio_app/features/profile/models/circumference_model.dart';
|
||||
import 'package:eatvio_app/features/profile/models/weight_model.dart';
|
||||
import 'package:eatvio_app/features/profile/presentation/widgets/circumference_chart_widget.dart';
|
||||
import 'package:eatvio_app/features/profile/presentation/widgets/weight_chart_widget.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:intl/date_symbol_data_local.dart';
|
||||
import 'package:intl/intl.dart';
|
||||
|
||||
void main() {
|
||||
group('Profile chart widgets smoke', () {
|
||||
setUpAll(() async {
|
||||
Intl.defaultLocale = 'ru';
|
||||
await initializeDateFormatting('ru');
|
||||
});
|
||||
|
||||
Widget wrap(Widget child) {
|
||||
return MaterialApp(
|
||||
theme: ThemeHelper().themeData(),
|
||||
home: Scaffold(body: SingleChildScrollView(child: child)),
|
||||
);
|
||||
}
|
||||
|
||||
testWidgets('WeightChartWidget renders empty state', (tester) async {
|
||||
await tester.pumpWidget(wrap(const WeightChartWidget(weightPoints: [])));
|
||||
await tester.pumpAndSettle(const Duration(milliseconds: 200));
|
||||
expect(find.text('Добавьте Ваш вес'), findsOneWidget);
|
||||
});
|
||||
|
||||
testWidgets('WeightChartWidget renders with one point', (tester) async {
|
||||
final points = [WeightModel(id: 1, userId: 1, weight: 70, createdAt: DateTime(2025, 1, 1), updatedAt: DateTime(2025, 1, 1), bmi: 22.0)];
|
||||
await tester.pumpWidget(wrap(WeightChartWidget(weightPoints: points)));
|
||||
await tester.pumpAndSettle(const Duration(milliseconds: 200));
|
||||
expect(find.textContaining('кг'), findsWidgets);
|
||||
});
|
||||
|
||||
testWidgets('CircumferenceChartWidget renders empty state', (tester) async {
|
||||
await tester.pumpWidget(wrap(const CircumferenceChartWidget(circumferencePoints: [])));
|
||||
await tester.pumpAndSettle(const Duration(milliseconds: 200));
|
||||
expect(find.text('Добавьте Ваши замеры'), findsOneWidget);
|
||||
});
|
||||
|
||||
testWidgets('CircumferenceChartWidget renders with one point', (tester) async {
|
||||
final points = [
|
||||
CircumferenceModel(
|
||||
id: 1,
|
||||
userId: 1,
|
||||
chestCircumference: 100,
|
||||
waistCircumference: 80,
|
||||
hipCircumference: 90,
|
||||
createdAt: DateTime(2025, 1, 1),
|
||||
updatedAt: DateTime(2025, 1, 1),
|
||||
),
|
||||
];
|
||||
await tester.pumpWidget(wrap(CircumferenceChartWidget(circumferencePoints: points)));
|
||||
await tester.pumpAndSettle(const Duration(milliseconds: 200));
|
||||
|
||||
expect(find.text('100'), findsOneWidget);
|
||||
expect(find.text('80'), findsOneWidget);
|
||||
expect(find.text('90'), findsOneWidget);
|
||||
});
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,197 @@
|
||||
import 'package:eatvio_app/core/app_config.dart';
|
||||
import 'package:eatvio_app/core/app_initializer.dart';
|
||||
import 'package:eatvio_app/core/comments/comments_cubit/comments_cubit.dart';
|
||||
import 'package:eatvio_app/core/comments/comments_cubit/comments_state.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/diets_pagination_model.dart';
|
||||
import 'package:eatvio_app/features/feed/models/recipes_model.dart';
|
||||
import 'package:eatvio_app/features/feed/presentation/bloc/feed_cubit/feed_cubit.dart';
|
||||
import 'package:eatvio_app/features/recipe/presentation/cubit/about_recipe_cubit.dart';
|
||||
import 'package:eatvio_app/features/recipe/presentation/cubit/about_recipe_state.dart';
|
||||
import 'package:eatvio_app/features/recipe/presentation/pages/about_recipe_screen.dart';
|
||||
import 'package:eatvio_app/generated/l10n.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:flutter_dotenv/flutter_dotenv.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:network_image_mock/network_image_mock.dart';
|
||||
|
||||
import 'about_recipe_screen_smoke_test.mocks.dart';
|
||||
|
||||
@GenerateNiceMocks([MockSpec<AboutRecipeCubit>(), MockSpec<FeedCubit>(), MockSpec<CommentsCubit>()])
|
||||
void main() {
|
||||
group('AboutRecipeScreen smoke', () {
|
||||
setUpAll(() async {
|
||||
dotenv.loadFromString(envString: 'DEV_BASE_URL=http://localhost\nBASE_URL=http://localhost');
|
||||
});
|
||||
|
||||
setUp(() async {
|
||||
await sl.reset();
|
||||
sl.registerLazySingleton<AppConfig>(() => AppConfig(buildType: AppBuildType.tests)..setBaseUrl('http://localhost'));
|
||||
FlutterError.onError = (FlutterErrorDetails details) {
|
||||
if (details.exception is FlutterError && details.exception.toString().contains('overflowed')) {
|
||||
return;
|
||||
}
|
||||
FlutterError.presentError(details);
|
||||
};
|
||||
});
|
||||
|
||||
void registerMocks(AboutRecipeCubit recipeCubit, CommentsCubit commentsCubit) {
|
||||
sl.registerLazySingleton<AboutRecipeCubit>(() => recipeCubit);
|
||||
sl.registerLazySingleton<CommentsCubit>(() => commentsCubit);
|
||||
}
|
||||
|
||||
final testRecipe = Recipe(
|
||||
id: 1,
|
||||
slug: 'test-recipe',
|
||||
title: 'Test Recipe',
|
||||
image: 'https://example.com/image.jpg',
|
||||
description: 'Test description',
|
||||
state: 'active',
|
||||
cookingTime: 30,
|
||||
activeCookingTime: 20,
|
||||
ingredients: const [],
|
||||
steps: const [],
|
||||
videoUrl: null,
|
||||
createdAt: DateTime(2025, 1, 1),
|
||||
updatedAt: DateTime(2025, 1, 1),
|
||||
type: 'main',
|
||||
commentsCount: 5,
|
||||
wasLikedByMe: false,
|
||||
isFavorite: false,
|
||||
calories: 200.0,
|
||||
servings: 4,
|
||||
likesCount: 10,
|
||||
viewsCount: 100,
|
||||
tags: const [],
|
||||
author: const Author(
|
||||
authorSlug: 'author',
|
||||
authorName: 'Author Name',
|
||||
authorProfileIcon: 'https://example.com/icon.jpg',
|
||||
authorIsMe: false,
|
||||
isEntitySubscribedByMe: null,
|
||||
authorPlaceInRating: null,
|
||||
),
|
||||
);
|
||||
|
||||
Widget wrap(AboutRecipeCubit recipeCubit, FeedCubit feedCubit, CommentsCubit commentsCubit, Widget child) {
|
||||
final router = GoRouter(
|
||||
routes: [
|
||||
GoRoute(
|
||||
path: '/',
|
||||
builder: (context, state) => MediaQuery(
|
||||
data: const MediaQueryData(size: Size(2000, 1200)),
|
||||
child: MultiBlocProvider(
|
||||
providers: [
|
||||
BlocProvider<AboutRecipeCubit>.value(value: recipeCubit),
|
||||
BlocProvider<FeedCubit>.value(value: feedCubit),
|
||||
BlocProvider<CommentsCubit>.value(value: commentsCubit),
|
||||
],
|
||||
child: child,
|
||||
),
|
||||
),
|
||||
),
|
||||
GoRoute(path: '/feed-screen', builder: (_, _) => const SizedBox.shrink()),
|
||||
],
|
||||
);
|
||||
|
||||
return MaterialApp.router(
|
||||
theme: ThemeHelper().themeData(),
|
||||
localizationsDelegates: const [
|
||||
S.delegate,
|
||||
GlobalMaterialLocalizations.delegate,
|
||||
GlobalWidgetsLocalizations.delegate,
|
||||
GlobalCupertinoLocalizations.delegate,
|
||||
],
|
||||
supportedLocales: const [Locale('ru')],
|
||||
routerConfig: router,
|
||||
);
|
||||
}
|
||||
|
||||
testWidgets('renders loading state', (tester) async {
|
||||
final recipeCubit = MockAboutRecipeCubit();
|
||||
final feedCubit = MockFeedCubit();
|
||||
final commentsCubit = MockCommentsCubit();
|
||||
const state = AboutRecipeState.loading();
|
||||
|
||||
when(recipeCubit.state).thenReturn(state);
|
||||
when(recipeCubit.stream).thenAnswer((_) => const Stream<AboutRecipeState>.empty());
|
||||
when(recipeCubit.close()).thenAnswer((_) async => Future<void>.value());
|
||||
when(feedCubit.viewRecipe(any)).thenAnswer((_) async => Future<void>.value());
|
||||
when(feedCubit.stream).thenAnswer((_) => const Stream.empty());
|
||||
when(feedCubit.close()).thenAnswer((_) async => Future<void>.value());
|
||||
when(commentsCubit.state).thenReturn(const CommentsState());
|
||||
when(commentsCubit.stream).thenAnswer((_) => const Stream<CommentsState>.empty());
|
||||
when(commentsCubit.close()).thenAnswer((_) async => Future<void>.value());
|
||||
when(commentsCubit.getComments(any, any, any)).thenAnswer((_) async => Future<void>.value());
|
||||
|
||||
registerMocks(recipeCubit, commentsCubit);
|
||||
|
||||
await tester.pumpWidget(wrap(recipeCubit, feedCubit, commentsCubit, const AboutRecipeScreen(recipeSlug: 'test', index: null, navigateToComments: false)));
|
||||
await tester.pump();
|
||||
|
||||
expect(find.byType(AboutRecipeScreen), findsOneWidget);
|
||||
});
|
||||
|
||||
testWidgets('renders loaded state with recipe', (tester) async {
|
||||
final recipeCubit = MockAboutRecipeCubit();
|
||||
final feedCubit = MockFeedCubit();
|
||||
final commentsCubit = MockCommentsCubit();
|
||||
final state = AboutRecipeState.loaded(recipe: testRecipe);
|
||||
|
||||
when(recipeCubit.state).thenReturn(state);
|
||||
when(recipeCubit.stream).thenAnswer((_) => const Stream<AboutRecipeState>.empty());
|
||||
when(recipeCubit.close()).thenAnswer((_) async => Future<void>.value());
|
||||
when(recipeCubit.getRecipe(any, index: anyNamed('index'))).thenAnswer((_) async => Future<void>.value());
|
||||
when(feedCubit.viewRecipe(any)).thenAnswer((_) async => Future<void>.value());
|
||||
when(feedCubit.stream).thenAnswer((_) => const Stream.empty());
|
||||
when(feedCubit.close()).thenAnswer((_) async => Future<void>.value());
|
||||
when(commentsCubit.state).thenReturn(const CommentsState());
|
||||
when(commentsCubit.stream).thenAnswer((_) => const Stream<CommentsState>.empty());
|
||||
when(commentsCubit.close()).thenAnswer((_) async => Future<void>.value());
|
||||
when(commentsCubit.getComments(any, any, any)).thenAnswer((_) async => Future<void>.value());
|
||||
when(commentsCubit.setReplyId(any)).thenReturn(null);
|
||||
|
||||
registerMocks(recipeCubit, commentsCubit);
|
||||
|
||||
await mockNetworkImagesFor(() async {
|
||||
await tester.pumpWidget(
|
||||
wrap(recipeCubit, feedCubit, commentsCubit, const AboutRecipeScreen(recipeSlug: 'test', index: null, navigateToComments: false)),
|
||||
);
|
||||
await tester.pump();
|
||||
await tester.pump(const Duration(milliseconds: 600));
|
||||
});
|
||||
|
||||
expect(find.byType(AboutRecipeScreen), findsOneWidget);
|
||||
});
|
||||
|
||||
testWidgets('renders error state', (tester) async {
|
||||
final recipeCubit = MockAboutRecipeCubit();
|
||||
final feedCubit = MockFeedCubit();
|
||||
final commentsCubit = MockCommentsCubit();
|
||||
const state = AboutRecipeState.error(message: 'Error loading recipe');
|
||||
|
||||
when(recipeCubit.state).thenReturn(state);
|
||||
when(recipeCubit.stream).thenAnswer((_) => const Stream<AboutRecipeState>.empty());
|
||||
when(recipeCubit.close()).thenAnswer((_) async => Future<void>.value());
|
||||
when(feedCubit.viewRecipe(any)).thenAnswer((_) async => Future<void>.value());
|
||||
when(feedCubit.stream).thenAnswer((_) => const Stream.empty());
|
||||
when(feedCubit.close()).thenAnswer((_) async => Future<void>.value());
|
||||
when(commentsCubit.state).thenReturn(const CommentsState());
|
||||
when(commentsCubit.stream).thenAnswer((_) => const Stream<CommentsState>.empty());
|
||||
when(commentsCubit.close()).thenAnswer((_) async => Future<void>.value());
|
||||
|
||||
registerMocks(recipeCubit, commentsCubit);
|
||||
|
||||
await tester.pumpWidget(wrap(recipeCubit, feedCubit, commentsCubit, const AboutRecipeScreen(recipeSlug: 'test', index: null, navigateToComments: false)));
|
||||
await tester.pump();
|
||||
|
||||
expect(find.byType(AboutRecipeScreen), findsOneWidget);
|
||||
});
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,112 @@
|
||||
import 'package:eatvio_app/core/app_config.dart';
|
||||
import 'package:eatvio_app/core/app_initializer.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/bloc/recipes_cubit/recipes_cubit.dart';
|
||||
import 'package:eatvio_app/features/feed/presentation/bloc/recipes_cubit/recipes_state.dart';
|
||||
import 'package:eatvio_app/features/recipe/models/my_recipe_model.dart';
|
||||
import 'package:eatvio_app/features/recipe/presentation/cubit/my_recipes_cubit/my_recieps_cubit.dart';
|
||||
import 'package:eatvio_app/features/recipe/presentation/cubit/my_recipes_cubit/my_recipes_state.dart';
|
||||
import 'package:eatvio_app/features/recipe/presentation/widgets/my_recipe_widget.dart';
|
||||
import 'package:eatvio_app/generated/l10n.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:flutter_dotenv/flutter_dotenv.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:network_image_mock/network_image_mock.dart';
|
||||
|
||||
import 'my_recipe_widget_test.mocks.dart';
|
||||
|
||||
@GenerateNiceMocks([MockSpec<RecipesCubit>(), MockSpec<MyRecipesCubit>()])
|
||||
void main() {
|
||||
group('MyRecipeWidget smoke', () {
|
||||
setUpAll(() async {
|
||||
dotenv.loadFromString(envString: 'DEV_BASE_URL=http://localhost\nBASE_URL=http://localhost');
|
||||
});
|
||||
|
||||
setUp(() async {
|
||||
await sl.reset();
|
||||
sl.registerLazySingleton<AppConfig>(() => AppConfig(buildType: AppBuildType.tests)..setBaseUrl('http://localhost'));
|
||||
});
|
||||
|
||||
const mockRecipe = MyRecipeModel(
|
||||
id: 1,
|
||||
image: 'https://example.com/recipe.jpg',
|
||||
state: 'published',
|
||||
cookingTime: 60,
|
||||
activeCookingTime: 30,
|
||||
description: '# Test Recipe\nThis is a test recipe.',
|
||||
steps: null,
|
||||
videoUrl: null,
|
||||
title: 'Test Recipe',
|
||||
slug: 'test-recipe',
|
||||
calories: 300.0,
|
||||
likesCount: 10,
|
||||
viewsCount: 100,
|
||||
);
|
||||
|
||||
Widget wrap(Widget child) {
|
||||
return MaterialApp.router(
|
||||
theme: ThemeHelper().themeData(),
|
||||
localizationsDelegates: const [
|
||||
S.delegate,
|
||||
GlobalMaterialLocalizations.delegate,
|
||||
GlobalWidgetsLocalizations.delegate,
|
||||
GlobalCupertinoLocalizations.delegate,
|
||||
],
|
||||
supportedLocales: const [Locale('ru')],
|
||||
routerConfig: GoRouter(
|
||||
initialLocation: '/',
|
||||
routes: [
|
||||
GoRoute(path: '/', builder: (context, state) => child),
|
||||
GoRoute(
|
||||
path: '/about-recipes-screen',
|
||||
name: 'aboutRecipesScreen',
|
||||
builder: (context, state) => const Scaffold(body: Text('About Recipe')),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
testWidgets('renders my recipe widget', (tester) async {
|
||||
tester.view.physicalSize = const Size(800, 2000);
|
||||
tester.view.devicePixelRatio = 1.0;
|
||||
addTearDown(() => tester.view.resetPhysicalSize());
|
||||
|
||||
mockNetworkImagesFor(() async {
|
||||
final recipesCubit = MockRecipesCubit();
|
||||
final myRecipesCubit = MockMyRecipesCubit();
|
||||
|
||||
when(recipesCubit.state).thenReturn(RecipesState.initial());
|
||||
when(recipesCubit.stream).thenAnswer((_) => const Stream.empty());
|
||||
when(recipesCubit.close()).thenAnswer((_) async => Future<void>.value());
|
||||
when(myRecipesCubit.state).thenReturn(const MyRecipesState());
|
||||
when(myRecipesCubit.stream).thenAnswer((_) => const Stream.empty());
|
||||
when(myRecipesCubit.close()).thenAnswer((_) async => Future<void>.value());
|
||||
|
||||
await tester.pumpWidget(
|
||||
MultiBlocProvider(
|
||||
providers: [
|
||||
BlocProvider<RecipesCubit>.value(value: recipesCubit),
|
||||
BlocProvider<MyRecipesCubit>.value(value: myRecipesCubit),
|
||||
],
|
||||
child: wrap(
|
||||
const Scaffold(
|
||||
body: SingleChildScrollView(child: MyRecipeWidget(recipe: mockRecipe)),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
await tester.pump();
|
||||
await tester.pump(const Duration(milliseconds: 100));
|
||||
|
||||
expect(find.byType(MyRecipeWidget), findsOneWidget);
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,75 @@
|
||||
import 'package:eatvio_app/core/app_config.dart';
|
||||
import 'package:eatvio_app/core/app_initializer.dart';
|
||||
import 'package:eatvio_app/core/locator_service.dart';
|
||||
import 'package:eatvio_app/core/prefs.dart';
|
||||
import 'package:eatvio_app/core/theme/theme_helper.dart';
|
||||
import 'package:eatvio_app/features/registration/presentation/bloc/registration_cubit/registration_cubit.dart';
|
||||
import 'package:eatvio_app/features/registration/presentation/pages/registration_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_dotenv/flutter_dotenv.dart';
|
||||
import 'package:flutter_localizations/flutter_localizations.dart';
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:mockito/annotations.dart';
|
||||
|
||||
import 'registration_screen_smoke_test.mocks.dart';
|
||||
|
||||
@GenerateNiceMocks([MockSpec<RegistrationRepository>(), MockSpec<Prefs>()])
|
||||
void main() {
|
||||
group('RegistrationScreen smoke', () {
|
||||
setUpAll(() async {
|
||||
dotenv.loadFromString(envString: 'DEV_BASE_URL=http://localhost\nBASE_URL=http://localhost');
|
||||
});
|
||||
|
||||
setUp(() async {
|
||||
await sl.reset();
|
||||
sl.registerLazySingleton<AppConfig>(() => AppConfig(buildType: AppBuildType.tests)..setBaseUrl('http://localhost'));
|
||||
});
|
||||
|
||||
Widget wrap(Widget child) {
|
||||
return MaterialApp(
|
||||
theme: ThemeHelper().themeData(),
|
||||
localizationsDelegates: const [
|
||||
S.delegate,
|
||||
GlobalMaterialLocalizations.delegate,
|
||||
GlobalWidgetsLocalizations.delegate,
|
||||
GlobalCupertinoLocalizations.delegate,
|
||||
],
|
||||
supportedLocales: const [Locale('ru')],
|
||||
home: child,
|
||||
);
|
||||
}
|
||||
|
||||
testWidgets('renders registration screen with initial step', (tester) async {
|
||||
final repository = MockRegistrationRepository();
|
||||
final prefs = MockPrefs();
|
||||
final cubit = RegistrationCubit(registrationRepository: repository, prefs: prefs);
|
||||
|
||||
sl.registerLazySingleton<RegistrationRepository>(() => repository);
|
||||
sl.registerLazySingleton<Prefs>(() => prefs);
|
||||
sl.registerLazySingleton<RegistrationCubit>(() => cubit);
|
||||
|
||||
await tester.pumpWidget(wrap(const RegistrationScreen()));
|
||||
await tester.pump();
|
||||
|
||||
expect(find.byType(RegistrationScreen), findsOneWidget);
|
||||
});
|
||||
|
||||
testWidgets('renders phone screen as first step', (tester) async {
|
||||
final repository = MockRegistrationRepository();
|
||||
final prefs = MockPrefs();
|
||||
final cubit = RegistrationCubit(registrationRepository: repository, prefs: prefs);
|
||||
|
||||
sl.registerLazySingleton<RegistrationRepository>(() => repository);
|
||||
sl.registerLazySingleton<Prefs>(() => prefs);
|
||||
sl.registerLazySingleton<RegistrationCubit>(() => cubit);
|
||||
|
||||
await tester.pumpWidget(wrap(const RegistrationScreen()));
|
||||
await tester.pump();
|
||||
|
||||
|
||||
expect(find.byType(PageView), findsOneWidget);
|
||||
});
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,72 @@
|
||||
import 'package:eatvio_app/core/app_config.dart';
|
||||
import 'package:eatvio_app/core/app_initializer.dart';
|
||||
import 'package:eatvio_app/core/locator_service.dart';
|
||||
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/first_step_content.dart';
|
||||
import 'package:eatvio_app/generated/l10n.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:flutter_dotenv/flutter_dotenv.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 'first_step_content_test.mocks.dart';
|
||||
|
||||
@GenerateNiceMocks([MockSpec<RegistrationCubitV2>()])
|
||||
void main() {
|
||||
group('FirstStepContent smoke', () {
|
||||
setUpAll(() async {
|
||||
dotenv.loadFromString(envString: 'DEV_BASE_URL=http://localhost\nBASE_URL=http://localhost');
|
||||
});
|
||||
|
||||
setUp(() async {
|
||||
await sl.reset();
|
||||
sl.registerLazySingleton<AppConfig>(() => AppConfig(buildType: AppBuildType.tests)..setBaseUrl('http://localhost'));
|
||||
});
|
||||
|
||||
Widget wrap(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),
|
||||
);
|
||||
}
|
||||
|
||||
testWidgets('renders first step content', (tester) async {
|
||||
final cubit = MockRegistrationCubitV2();
|
||||
final state = RegistrationStateV2(
|
||||
step: 0,
|
||||
firstName: 'John',
|
||||
lastName: 'Doe',
|
||||
email: 'test@example.com',
|
||||
phone: '+7 (999) 123-45-67',
|
||||
birthday: DateTime(1990, 1, 1),
|
||||
isFirstNameValid: true,
|
||||
isLastNameValid: true,
|
||||
isEmailValid: true,
|
||||
isPhoneValid: true,
|
||||
isBirthdayValid: true,
|
||||
);
|
||||
|
||||
when(cubit.state).thenReturn(state);
|
||||
when(cubit.stream).thenAnswer((_) => Stream.value(state));
|
||||
when(cubit.close()).thenAnswer((_) async => Future<void>.value());
|
||||
|
||||
await tester.pumpWidget(BlocProvider<RegistrationCubitV2>.value(value: cubit, child: wrap(const FirstStepContent())));
|
||||
await tester.pump();
|
||||
await tester.pump(const Duration(milliseconds: 100));
|
||||
|
||||
expect(find.byType(FirstStepContent), findsOneWidget);
|
||||
});
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,81 @@
|
||||
import 'package:eatvio_app/core/app_config.dart';
|
||||
import 'package:eatvio_app/core/app_initializer.dart';
|
||||
import 'package:eatvio_app/core/locator_service.dart';
|
||||
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/second_step_content.dart';
|
||||
import 'package:eatvio_app/generated/l10n.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:flutter_dotenv/flutter_dotenv.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 'second_step_content_test.mocks.dart';
|
||||
|
||||
@GenerateNiceMocks([MockSpec<RegistrationCubitV2>()])
|
||||
void main() {
|
||||
group('SecondStepContent smoke', () {
|
||||
setUpAll(() async {
|
||||
dotenv.loadFromString(envString: 'DEV_BASE_URL=http://localhost\nBASE_URL=http://localhost');
|
||||
});
|
||||
|
||||
setUp(() async {
|
||||
await sl.reset();
|
||||
sl.registerLazySingleton<AppConfig>(() => AppConfig(buildType: AppBuildType.tests)..setBaseUrl('http://localhost'));
|
||||
});
|
||||
|
||||
Widget wrap(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),
|
||||
);
|
||||
}
|
||||
|
||||
testWidgets('renders second step content', (tester) async {
|
||||
final cubit = MockRegistrationCubitV2();
|
||||
final state = RegistrationStateV2(
|
||||
step: 1,
|
||||
firstName: 'John',
|
||||
lastName: 'Doe',
|
||||
email: 'test@example.com',
|
||||
phone: '+7 (999) 123-45-67',
|
||||
gender: 'male',
|
||||
password: '',
|
||||
weight: 70,
|
||||
height: 180,
|
||||
activityCoefficient: 1.2,
|
||||
birthday: DateTime(1990, 1, 1),
|
||||
isFirstNameValid: true,
|
||||
isLastNameValid: true,
|
||||
isEmailValid: true,
|
||||
isPhoneValid: true,
|
||||
isGenderValid: true,
|
||||
isWeightValid: true,
|
||||
isHeightValid: true,
|
||||
isActivityCoefficientValid: true,
|
||||
isBirthdayValid: true,
|
||||
);
|
||||
|
||||
when(cubit.state).thenReturn(state);
|
||||
when(cubit.stream).thenAnswer((_) => Stream.value(state));
|
||||
when(cubit.close()).thenAnswer((_) async => Future<void>.value());
|
||||
|
||||
await tester.pumpWidget(BlocProvider<RegistrationCubitV2>.value(value: cubit, child: wrap(const SecondStepContent())));
|
||||
await tester.pump();
|
||||
await tester.pump(const Duration(milliseconds: 100));
|
||||
|
||||
expect(find.byType(SecondStepContent), findsOneWidget);
|
||||
});
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,199 @@
|
||||
import 'package:eatvio_app/core/app_config.dart';
|
||||
import 'package:eatvio_app/core/app_initializer.dart';
|
||||
import 'package:eatvio_app/core/locator_service.dart';
|
||||
import 'package:eatvio_app/core/theme/theme_helper.dart';
|
||||
import 'package:eatvio_app/features/profile/models/profile_model.dart';
|
||||
import 'package:eatvio_app/features/settings/entities/settings_entity.dart';
|
||||
import 'package:eatvio_app/features/settings/models/profile_settings_model.dart';
|
||||
import 'package:eatvio_app/features/settings/presentation/bloc/settings_cubit/settings_cubit.dart';
|
||||
import 'package:eatvio_app/features/settings/presentation/bloc/settings_cubit/settings_state.dart';
|
||||
import 'package:eatvio_app/features/settings/presentation/pages/general_info_screen.dart';
|
||||
import 'package:eatvio_app/features/settings/presentation/pages/other_info_screen.dart';
|
||||
import 'package:eatvio_app/features/settings/presentation/pages/physical_activity_screen.dart';
|
||||
import 'package:eatvio_app/features/settings/presentation/pages/physical_form_screen.dart';
|
||||
import 'package:eatvio_app/features/settings/presentation/pages/pressure_screen.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:mockito/annotations.dart';
|
||||
import 'package:mockito/mockito.dart';
|
||||
|
||||
import 'settings_additional_info_screens_smoke_test.mocks.dart';
|
||||
|
||||
@GenerateMocks([SettingsCubit])
|
||||
void main() {
|
||||
group('Settings additional info screens smoke', () {
|
||||
setUpAll(() async {
|
||||
dotenv.loadFromString(envString: 'DEV_BASE_URL=http://localhost\nBASE_URL=http://localhost');
|
||||
});
|
||||
|
||||
setUp(() async {
|
||||
await sl.reset();
|
||||
sl.registerLazySingleton<AppConfig>(() => AppConfig(buildType: AppBuildType.tests)..setBaseUrl('http://localhost'));
|
||||
});
|
||||
|
||||
SettingsEntity entity(MockSettingsCubit cubit) {
|
||||
return SettingsEntity(
|
||||
profileSettingsModel: const ProfileSettingsModel(
|
||||
newUpdatesNotifications: true,
|
||||
diaryReminderNotifications: true,
|
||||
isMoodFillable: true,
|
||||
isBedtimeFillable: true,
|
||||
isPublicProfile: true,
|
||||
),
|
||||
profileDetailsModel: const ProfileDetailsModel(
|
||||
firstName: 'John',
|
||||
lastName: 'Doe',
|
||||
birthdate: '1990-01-01',
|
||||
gender: 'male',
|
||||
country: 'Russia',
|
||||
city: 'Moscow',
|
||||
lang: 'ru',
|
||||
weight: 75.0,
|
||||
height: 180,
|
||||
chestCircumference: 100,
|
||||
waistCircumference: 80,
|
||||
hipCircumference: 90,
|
||||
bloodPressureUp: 120,
|
||||
bloodPressureDown: 80,
|
||||
hasHypo: false,
|
||||
hasHyper: false,
|
||||
aboutAlergy: 'None',
|
||||
aboutTakingPils: 'None',
|
||||
aboutSportHobbies: 'Running',
|
||||
favoriteFoods: 'Pizza',
|
||||
unfavoriteFoods: 'Broccoli',
|
||||
additionalInfo: 'No additional info',
|
||||
isMoodFillable: true,
|
||||
isBedtimeFillable: true,
|
||||
activityCoefficient: 1.5,
|
||||
),
|
||||
settingsCubit: cubit,
|
||||
);
|
||||
}
|
||||
|
||||
void stubCubit(MockSettingsCubit cubit) {
|
||||
const profile = ProfileModel(
|
||||
id: 1,
|
||||
name: 'Test User',
|
||||
email: 'test@example.com',
|
||||
phoneConfirmationState: 'confirmed',
|
||||
slug: 'test-user',
|
||||
isPublicProfile: true,
|
||||
isBlockedByMe: false,
|
||||
isBlockedByAuthor: false,
|
||||
isBlockedByUser: false,
|
||||
isEntitySubscribedByMe: false,
|
||||
profilePhotoUrl: '',
|
||||
roles: [],
|
||||
badges: [],
|
||||
subscriptions: [],
|
||||
subscribers: [],
|
||||
);
|
||||
|
||||
const state = SettingsState.loaded(
|
||||
profileSettings: ProfileSettingsModel(
|
||||
newUpdatesNotifications: true,
|
||||
diaryReminderNotifications: true,
|
||||
isMoodFillable: true,
|
||||
isBedtimeFillable: true,
|
||||
isPublicProfile: true,
|
||||
),
|
||||
profileDetails: ProfileDetailsModel(
|
||||
firstName: 'John',
|
||||
lastName: 'Doe',
|
||||
birthdate: '1990-01-01',
|
||||
gender: 'male',
|
||||
country: 'Russia',
|
||||
city: 'Moscow',
|
||||
lang: 'ru',
|
||||
weight: 75.0,
|
||||
height: 180,
|
||||
chestCircumference: 100,
|
||||
waistCircumference: 80,
|
||||
hipCircumference: 90,
|
||||
bloodPressureUp: 120,
|
||||
bloodPressureDown: 80,
|
||||
hasHypo: false,
|
||||
hasHyper: false,
|
||||
aboutAlergy: 'None',
|
||||
aboutTakingPils: 'None',
|
||||
aboutSportHobbies: 'Running',
|
||||
favoriteFoods: 'Pizza',
|
||||
unfavoriteFoods: 'Broccoli',
|
||||
additionalInfo: 'No additional info',
|
||||
isMoodFillable: true,
|
||||
isBedtimeFillable: true,
|
||||
activityCoefficient: 1.5,
|
||||
),
|
||||
profile: profile,
|
||||
hasActiveConsultation: false,
|
||||
);
|
||||
|
||||
when(cubit.state).thenReturn(state);
|
||||
when(cubit.stream).thenAnswer((_) => const Stream.empty());
|
||||
when(cubit.close()).thenAnswer((_) async {});
|
||||
}
|
||||
|
||||
Widget wrap(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),
|
||||
);
|
||||
}
|
||||
|
||||
testWidgets('GeneralInfoScreen renders', (tester) async {
|
||||
final mockCubit = MockSettingsCubit();
|
||||
stubCubit(mockCubit);
|
||||
await tester.pumpWidget(wrap(GeneralInfoScreen(settingsEntity: entity(mockCubit))));
|
||||
await tester.pump();
|
||||
expect(find.byType(GeneralInfoScreen), findsOneWidget);
|
||||
expect(find.text('Общая информация'), findsOneWidget);
|
||||
});
|
||||
|
||||
testWidgets('PhysicalFormScreen renders', (tester) async {
|
||||
final mockCubit = MockSettingsCubit();
|
||||
stubCubit(mockCubit);
|
||||
await tester.pumpWidget(wrap(PhysicalFormScreen(settingsEntity: entity(mockCubit))));
|
||||
await tester.pump();
|
||||
expect(find.byType(PhysicalFormScreen), findsOneWidget);
|
||||
expect(find.text('Физическая форма'), findsOneWidget);
|
||||
});
|
||||
|
||||
testWidgets('PressureScreen renders', (tester) async {
|
||||
final mockCubit = MockSettingsCubit();
|
||||
stubCubit(mockCubit);
|
||||
await tester.pumpWidget(wrap(PressureScreen(settingsEntity: entity(mockCubit))));
|
||||
await tester.pump();
|
||||
expect(find.byType(PressureScreen), findsOneWidget);
|
||||
expect(find.text('Давление'), findsOneWidget);
|
||||
});
|
||||
|
||||
testWidgets('OtherInfoScreen renders', (tester) async {
|
||||
final mockCubit = MockSettingsCubit();
|
||||
stubCubit(mockCubit);
|
||||
await tester.pumpWidget(wrap(OtherInfoScreen(settingsEntity: entity(mockCubit))));
|
||||
await tester.pump();
|
||||
expect(find.byType(OtherInfoScreen), findsOneWidget);
|
||||
expect(find.text('Прочая информаиця'), findsOneWidget);
|
||||
});
|
||||
|
||||
testWidgets('PhysicalActivityScreen renders', (tester) async {
|
||||
final mockCubit = MockSettingsCubit();
|
||||
stubCubit(mockCubit);
|
||||
await tester.pumpWidget(wrap(PhysicalActivityScreen(settingsEntity: entity(mockCubit))));
|
||||
await tester.pump();
|
||||
expect(find.byType(PhysicalActivityScreen), findsOneWidget);
|
||||
expect(find.text('Физическая нагрузка'), findsOneWidget);
|
||||
});
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,149 @@
|
||||
import 'package:eatvio_app/core/app_config.dart';
|
||||
import 'package:eatvio_app/core/app_initializer.dart';
|
||||
import 'package:eatvio_app/core/locator_service.dart';
|
||||
import 'package:eatvio_app/core/theme/theme_helper.dart';
|
||||
import 'package:eatvio_app/features/consultant/become_consultant/repositories/consultant_repository.dart';
|
||||
import 'package:eatvio_app/features/consultations/repositories/consultations_repository.dart';
|
||||
import 'package:eatvio_app/features/profile/models/profile_model.dart';
|
||||
import 'package:eatvio_app/features/profile/repositories/profile_repository.dart';
|
||||
import 'package:eatvio_app/features/settings/models/profile_settings_model.dart';
|
||||
import 'package:eatvio_app/features/settings/presentation/bloc/settings_cubit/settings_cubit.dart';
|
||||
import 'package:eatvio_app/features/settings/presentation/pages/settings_screen.dart';
|
||||
import 'package:eatvio_app/features/settings/repositories/service_repository.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:mockito/annotations.dart';
|
||||
import 'package:mockito/mockito.dart';
|
||||
|
||||
import 'settings_screen_smoke_test.mocks.dart';
|
||||
|
||||
@GenerateNiceMocks([
|
||||
MockSpec<ProfileRepository>(),
|
||||
MockSpec<SettingsRepository>(),
|
||||
MockSpec<ConsultantRepository>(),
|
||||
MockSpec<ConsultationsRepository>(),
|
||||
])
|
||||
void main() {
|
||||
group('SettingsScreen smoke', () {
|
||||
late MockProfileRepository profileRepository;
|
||||
late MockSettingsRepository settingsRepository;
|
||||
late MockConsultantRepository consultantRepository;
|
||||
late MockConsultationsRepository consultationsRepository;
|
||||
|
||||
setUpAll(() async {
|
||||
dotenv.loadFromString(envString: 'DEV_BASE_URL=http://localhost\nBASE_URL=http://localhost');
|
||||
});
|
||||
|
||||
setUp(() async {
|
||||
await sl.reset();
|
||||
sl.registerLazySingleton<AppConfig>(() => AppConfig(buildType: AppBuildType.tests)..setBaseUrl('http://localhost'));
|
||||
|
||||
profileRepository = MockProfileRepository();
|
||||
settingsRepository = MockSettingsRepository();
|
||||
consultantRepository = MockConsultantRepository();
|
||||
consultationsRepository = MockConsultationsRepository();
|
||||
|
||||
sl.registerLazySingleton<ProfileRepository>(() => profileRepository);
|
||||
sl.registerLazySingleton<SettingsRepository>(() => settingsRepository);
|
||||
sl.registerLazySingleton<ConsultantRepository>(() => consultantRepository);
|
||||
sl.registerLazySingleton<ConsultationsRepository>(() => consultationsRepository);
|
||||
|
||||
sl.registerFactory<SettingsCubit>(
|
||||
() => SettingsCubit(
|
||||
profileRepository: sl(),
|
||||
settingsRepository: sl(),
|
||||
consultantRepository: sl(),
|
||||
consultationsRepository: sl(),
|
||||
),
|
||||
);
|
||||
});
|
||||
|
||||
Widget wrap(Widget child) {
|
||||
return MaterialApp(
|
||||
theme: ThemeHelper().themeData(),
|
||||
localizationsDelegates: const [
|
||||
S.delegate,
|
||||
GlobalMaterialLocalizations.delegate,
|
||||
GlobalWidgetsLocalizations.delegate,
|
||||
GlobalCupertinoLocalizations.delegate,
|
||||
],
|
||||
supportedLocales: const [Locale('ru')],
|
||||
home: child,
|
||||
);
|
||||
}
|
||||
|
||||
testWidgets('renders loaded settings', (tester) async {
|
||||
const profile = ProfileModel(
|
||||
id: 1,
|
||||
name: 'Test User',
|
||||
email: 'test@example.com',
|
||||
phoneConfirmationState: 'confirmed',
|
||||
slug: 'test-user',
|
||||
isPublicProfile: true,
|
||||
isBlockedByMe: false,
|
||||
isBlockedByAuthor: false,
|
||||
isBlockedByUser: false,
|
||||
isEntitySubscribedByMe: false,
|
||||
profilePhotoUrl: '',
|
||||
roles: [],
|
||||
badges: [],
|
||||
subscriptions: [],
|
||||
subscribers: [],
|
||||
);
|
||||
|
||||
const profileDetails = ProfileDetailsModel(
|
||||
firstName: 'Test',
|
||||
lastName: 'User',
|
||||
birthdate: '1990-01-01',
|
||||
gender: 'male',
|
||||
country: 'Russia',
|
||||
city: 'Moscow',
|
||||
lang: 'ru',
|
||||
weight: 70.0,
|
||||
height: 175,
|
||||
chestCircumference: 100,
|
||||
waistCircumference: 80,
|
||||
hipCircumference: 90,
|
||||
bloodPressureUp: 120,
|
||||
bloodPressureDown: 80,
|
||||
hasHypo: false,
|
||||
hasHyper: false,
|
||||
aboutAlergy: '',
|
||||
aboutTakingPils: '',
|
||||
aboutSportHobbies: '',
|
||||
favoriteFoods: '',
|
||||
unfavoriteFoods: '',
|
||||
additionalInfo: '',
|
||||
isMoodFillable: true,
|
||||
isBedtimeFillable: true,
|
||||
activityCoefficient: 1.5,
|
||||
);
|
||||
|
||||
const profileSettings = ProfileSettingsModel(
|
||||
newUpdatesNotifications: true,
|
||||
diaryReminderNotifications: true,
|
||||
isMoodFillable: true,
|
||||
isBedtimeFillable: true,
|
||||
isPublicProfile: true,
|
||||
);
|
||||
|
||||
when(profileRepository.getMyProfile()).thenAnswer((_) async => profile);
|
||||
when(profileRepository.getMyProfileDetails()).thenAnswer((_) async => profileDetails);
|
||||
when(settingsRepository.getProfileSettings()).thenAnswer((_) async => profileSettings);
|
||||
|
||||
when(consultantRepository.getMyRequestStatus(any)).thenThrow(Exception('skip consultant status in tests'));
|
||||
when(settingsRepository.getOAuthAccounts()).thenThrow(Exception('skip oauth in tests'));
|
||||
when(consultationsRepository.getUserPurchases(any, any)).thenThrow(Exception('skip purchases in tests'));
|
||||
|
||||
await tester.pumpWidget(wrap(const SettingsScreen()));
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
expect(find.text('Настройки'), findsWidgets);
|
||||
expect(find.byType(SettingsScreen), findsOneWidget);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
@@ -0,0 +1,147 @@
|
||||
import 'package:eatvio_app/core/app_config.dart';
|
||||
import 'package:eatvio_app/core/app_initializer.dart';
|
||||
import 'package:eatvio_app/core/locator_service.dart';
|
||||
import 'package:eatvio_app/core/theme/theme_helper.dart';
|
||||
import 'package:eatvio_app/features/profile/models/circumference_model.dart';
|
||||
import 'package:eatvio_app/features/profile/models/profile_model.dart';
|
||||
import 'package:eatvio_app/features/profile/models/weight_model.dart';
|
||||
import 'package:eatvio_app/features/profile/repositories/profile_repository.dart';
|
||||
import 'package:eatvio_app/features/user_details/presentation/cubit/user_details_cubit.dart';
|
||||
import 'package:eatvio_app/features/user_details/presentation/user_details_screen.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_portal/flutter_portal.dart';
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:mockito/annotations.dart';
|
||||
import 'package:mockito/mockito.dart';
|
||||
|
||||
import 'user_details_screen_smoke_test.mocks.dart';
|
||||
|
||||
@GenerateNiceMocks([MockSpec<ProfileRepository>()])
|
||||
void main() {
|
||||
group('UserDetailsScreen smoke', () {
|
||||
setUpAll(() async {
|
||||
dotenv.loadFromString(envString: 'DEV_BASE_URL=http://localhost\nBASE_URL=http://localhost');
|
||||
});
|
||||
|
||||
setUp(() async {
|
||||
await sl.reset();
|
||||
sl.registerLazySingleton<AppConfig>(() => AppConfig(buildType: AppBuildType.tests)..setBaseUrl('http://localhost'));
|
||||
});
|
||||
|
||||
const mockProfileDetails = ProfileDetailsModel(
|
||||
firstName: 'John',
|
||||
lastName: 'Doe',
|
||||
birthdate: '1990-01-01',
|
||||
gender: 'male',
|
||||
country: 'Russia',
|
||||
city: 'Moscow',
|
||||
lang: 'ru',
|
||||
weight: 75.0,
|
||||
height: 180,
|
||||
chestCircumference: 100,
|
||||
waistCircumference: 80,
|
||||
hipCircumference: 90,
|
||||
bloodPressureUp: 120,
|
||||
bloodPressureDown: 80,
|
||||
hasHypo: false,
|
||||
hasHyper: false,
|
||||
aboutAlergy: 'No allergies',
|
||||
aboutTakingPils: 'No pills',
|
||||
aboutSportHobbies: 'Running',
|
||||
favoriteFoods: 'Pizza',
|
||||
unfavoriteFoods: 'Broccoli',
|
||||
additionalInfo: 'Test info',
|
||||
isMoodFillable: false,
|
||||
isBedtimeFillable: false,
|
||||
activityCoefficient: 1.2,
|
||||
);
|
||||
|
||||
final mockWeightPoints = [
|
||||
WeightModel(id: 1, userId: 1, weight: 70, createdAt: DateTime(2025, 1, 1), updatedAt: DateTime(2025, 1, 1), bmi: 21.6),
|
||||
WeightModel(id: 2, userId: 1, weight: 72, createdAt: DateTime(2025, 2, 1), updatedAt: DateTime(2025, 2, 1), bmi: 22.2),
|
||||
];
|
||||
|
||||
final mockCircumferencePoints = [
|
||||
CircumferenceModel(
|
||||
id: 1,
|
||||
userId: 1,
|
||||
chestCircumference: 100,
|
||||
waistCircumference: 80,
|
||||
hipCircumference: 90,
|
||||
createdAt: DateTime(2025, 1, 1),
|
||||
updatedAt: DateTime(2025, 1, 1),
|
||||
),
|
||||
];
|
||||
|
||||
Widget wrap(Widget child) {
|
||||
return Portal(
|
||||
child: MaterialApp(
|
||||
theme: ThemeHelper().themeData(),
|
||||
localizationsDelegates: const [
|
||||
S.delegate,
|
||||
GlobalMaterialLocalizations.delegate,
|
||||
GlobalWidgetsLocalizations.delegate,
|
||||
GlobalCupertinoLocalizations.delegate,
|
||||
],
|
||||
supportedLocales: const [Locale('ru')],
|
||||
home: child,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
testWidgets('renders loading state', (tester) async {
|
||||
final profileRepository = MockProfileRepository();
|
||||
final cubit = UserDetailsCubit(profileRepository: profileRepository);
|
||||
|
||||
when(profileRepository.getProfileDetailsBySlug('test-slug')).thenAnswer((_) async => mockProfileDetails);
|
||||
when(profileRepository.getWeight(period: '1', slug: 'test-slug')).thenAnswer((_) async => mockWeightPoints);
|
||||
when(profileRepository.getCircumferences(period: '1', slug: 'test-slug')).thenAnswer((_) async => mockCircumferencePoints);
|
||||
|
||||
sl.registerLazySingleton<ProfileRepository>(() => profileRepository);
|
||||
sl.registerLazySingleton<UserDetailsCubit>(() => cubit);
|
||||
|
||||
await tester.pumpWidget(wrap(const UserDetailsScreen(slug: 'test-slug')));
|
||||
await tester.pump();
|
||||
|
||||
await tester.pump(const Duration(milliseconds: 100));
|
||||
await tester.pump(const Duration(milliseconds: 100));
|
||||
|
||||
expect(find.byType(UserDetailsScreen), findsOneWidget);
|
||||
});
|
||||
|
||||
testWidgets('renders loaded state with profile details', (tester) async {
|
||||
final profileRepository = MockProfileRepository();
|
||||
final cubit = UserDetailsCubit(profileRepository: profileRepository);
|
||||
|
||||
when(profileRepository.getProfileDetailsBySlug('test-slug')).thenAnswer((_) async => mockProfileDetails);
|
||||
when(profileRepository.getWeight(period: '1', slug: 'test-slug')).thenAnswer((_) async => mockWeightPoints);
|
||||
when(profileRepository.getCircumferences(period: '1', slug: 'test-slug')).thenAnswer((_) async => mockCircumferencePoints);
|
||||
|
||||
sl.registerLazySingleton<ProfileRepository>(() => profileRepository);
|
||||
sl.registerLazySingleton<UserDetailsCubit>(() => cubit);
|
||||
|
||||
await tester.pumpWidget(wrap(const UserDetailsScreen(slug: 'test-slug')));
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
expect(find.textContaining('John Doe'), findsOneWidget);
|
||||
});
|
||||
|
||||
testWidgets('renders error state', (tester) async {
|
||||
final profileRepository = MockProfileRepository();
|
||||
final cubit = UserDetailsCubit(profileRepository: profileRepository);
|
||||
|
||||
when(profileRepository.getProfileDetailsBySlug('test-slug')).thenThrow(Exception('Error loading profile'));
|
||||
|
||||
sl.registerLazySingleton<ProfileRepository>(() => profileRepository);
|
||||
sl.registerLazySingleton<UserDetailsCubit>(() => cubit);
|
||||
|
||||
await tester.pumpWidget(wrap(const UserDetailsScreen(slug: 'test-slug')));
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
expect(find.byType(UserDetailsScreen), findsOneWidget);
|
||||
});
|
||||
});
|
||||
}
|
||||
Reference in New Issue
Block a user