forked from eatvio/eatvio_app
333 lines
14 KiB
Dart
333 lines
14 KiB
Dart
import 'package:eatvio_app/core/theme/theme_helper.dart';
|
|
import 'package:eatvio_app/core/locator_service.dart';
|
|
import 'package:eatvio_app/core/app_config.dart';
|
|
import 'package:eatvio_app/core/app_initializer.dart';
|
|
import 'package:eatvio_app/features/create_diet/presentation/widgets/diet_duration_drop_down_widget.dart';
|
|
import 'package:eatvio_app/features/create_diet/presentation/widgets/status_drop_down_widget.dart';
|
|
import 'package:eatvio_app/features/create_diet/presentation/widgets/diet_type_choice_widget.dart';
|
|
import 'package:eatvio_app/features/create_diet/presentation/widgets/diet_image_picker_widget.dart';
|
|
import 'package:eatvio_app/core/widgets/custom_outlined_button.dart';
|
|
import 'package:flutter_dotenv/flutter_dotenv.dart';
|
|
import 'package:flutter_localizations/flutter_localizations.dart';
|
|
import 'package:eatvio_app/generated/l10n.dart';
|
|
import 'package:eatvio_app/features/create_diet/presentation/cubit/create_diet_cubit.dart';
|
|
import 'package:eatvio_app/features/create_diet/presentation/create_diet_screen.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/material.dart';
|
|
import 'package:flutter_bloc/flutter_bloc.dart';
|
|
import 'package:flutter_test/flutter_test.dart';
|
|
import 'package:mockito/annotations.dart';
|
|
import 'package:mockito/mockito.dart';
|
|
|
|
import 'integration_widget_test.mocks.dart';
|
|
|
|
@GenerateMocks([CreateDietRepository, ProfileRepository, GlossaryRepository])
|
|
void main() {
|
|
group('Diet Creation Integration Widget Tests', () {
|
|
late MockCreateDietRepository mockCreateDietRepository;
|
|
late MockProfileRepository mockProfileRepository;
|
|
late MockGlossaryRepository mockGlossaryRepository;
|
|
late CreateDietCubit createDietCubit;
|
|
|
|
setUp(() async {
|
|
await sl.reset();
|
|
// Ensure dotenv has required keys before AppConfig constructor reads them
|
|
dotenv.loadFromString(envString: 'DEV_BASE_URL=http://localhost\nBASE_URL=http://localhost');
|
|
// Register minimal AppConfig for tests to satisfy dependencies (e.g. image URL building)
|
|
sl.registerLazySingleton<AppConfig>(() => AppConfig(buildType: AppBuildType.tests)..setBaseUrl('http://localhost'));
|
|
mockCreateDietRepository = MockCreateDietRepository();
|
|
mockProfileRepository = MockProfileRepository();
|
|
mockGlossaryRepository = MockGlossaryRepository();
|
|
createDietCubit = CreateDietCubit(
|
|
createDietRepository: mockCreateDietRepository,
|
|
profileRepository: mockProfileRepository,
|
|
glossaryRepository: mockGlossaryRepository,
|
|
);
|
|
sl.registerLazySingleton<CreateDietCubit>(() => createDietCubit);
|
|
when(mockCreateDietRepository.getDietPortionsNutrition(any)).thenAnswer((_) async => <int, Map<int, Map<int, DailySchedule>>>{});
|
|
});
|
|
|
|
tearDown(() {
|
|
createDietCubit.close();
|
|
});
|
|
|
|
Widget createTestWidget() {
|
|
return MaterialApp(
|
|
theme: ThemeHelper().themeData(),
|
|
localizationsDelegates: const [
|
|
S.delegate,
|
|
GlobalMaterialLocalizations.delegate,
|
|
GlobalWidgetsLocalizations.delegate,
|
|
GlobalCupertinoLocalizations.delegate,
|
|
],
|
|
supportedLocales: const [Locale('ru')],
|
|
home: BlocProvider<CreateDietCubit>(create: (context) => createDietCubit, child: const CreateDietScreen()),
|
|
);
|
|
}
|
|
|
|
testWidgets('should display CreateDietScreen', (WidgetTester tester) async {
|
|
// Arrange & Act
|
|
await tester.pumpWidget(createTestWidget());
|
|
await tester.pumpAndSettle();
|
|
|
|
// Assert
|
|
expect(find.byType(CreateDietScreen), findsOneWidget);
|
|
});
|
|
|
|
testWidgets('should display diet creation form elements', (WidgetTester tester) async {
|
|
// Arrange
|
|
final arranged = Scaffold(
|
|
appBar: AppBar(title: const Text('Создание диеты')),
|
|
body: SingleChildScrollView(
|
|
child: Padding(
|
|
padding: const EdgeInsets.all(16.0),
|
|
child: Column(
|
|
crossAxisAlignment: CrossAxisAlignment.start,
|
|
children: [
|
|
TextFormField(
|
|
key: const Key('dietNameField'),
|
|
decoration: const InputDecoration(labelText: 'Название диеты', hintText: 'Введите название диеты'),
|
|
),
|
|
const SizedBox(height: 16),
|
|
TextFormField(
|
|
key: const Key('dietDescriptionField'),
|
|
decoration: const InputDecoration(labelText: 'Описание диеты', hintText: 'Опишите особенности диеты'),
|
|
maxLines: 3,
|
|
),
|
|
const SizedBox(height: 16),
|
|
DropdownButtonFormField<int>(
|
|
key: const Key('dietDurationField'),
|
|
decoration: const InputDecoration(labelText: 'Продолжительность'),
|
|
items: const [
|
|
DropdownMenuItem(value: 7, child: Text('7 дней')),
|
|
DropdownMenuItem(value: 14, child: Text('14 дней')),
|
|
DropdownMenuItem(value: 21, child: Text('21 день')),
|
|
],
|
|
onChanged: (value) {},
|
|
),
|
|
const SizedBox(height: 16),
|
|
DropdownButtonFormField<String>(
|
|
key: const Key('dietStatusField'),
|
|
decoration: const InputDecoration(labelText: 'Статус диеты'),
|
|
items: const [
|
|
DropdownMenuItem(value: 'active', child: Text('Активная')),
|
|
DropdownMenuItem(value: 'draft', child: Text('Черновик')),
|
|
],
|
|
onChanged: (value) {},
|
|
),
|
|
const SizedBox(height: 32),
|
|
SizedBox(
|
|
width: double.infinity,
|
|
child: ElevatedButton(key: const Key('createDietButton'), onPressed: () {}, child: const Text('Создать диету')),
|
|
),
|
|
],
|
|
),
|
|
),
|
|
),
|
|
);
|
|
|
|
// Act
|
|
await tester.pumpWidget(MaterialApp(theme: ThemeHelper().themeData(), home: arranged));
|
|
await tester.pumpAndSettle();
|
|
|
|
// Assert
|
|
expect(find.text('Создание диеты'), findsOneWidget);
|
|
});
|
|
|
|
testWidgets('should handle diet creation workflow', (WidgetTester tester) async {
|
|
// Arrange
|
|
final arranged = Column(
|
|
children: [
|
|
TextFormField(
|
|
key: const Key('dietNameField'),
|
|
decoration: const InputDecoration(labelText: 'Название диеты'),
|
|
),
|
|
const SizedBox(height: 16),
|
|
TextFormField(
|
|
key: const Key('dietDescriptionField'),
|
|
decoration: const InputDecoration(labelText: 'Описание диеты'),
|
|
),
|
|
const SizedBox(height: 16),
|
|
ElevatedButton(key: const Key('createDietButton'), onPressed: () {}, child: const Text('Создать диету')),
|
|
],
|
|
);
|
|
|
|
await tester.pumpWidget(
|
|
MaterialApp(
|
|
theme: ThemeHelper().themeData(),
|
|
home: Scaffold(body: SingleChildScrollView(child: arranged)),
|
|
),
|
|
);
|
|
await tester.pumpAndSettle();
|
|
|
|
// Act - Fill in diet information
|
|
await tester.enterText(find.byKey(const Key('dietNameField')), 'Тестовая диета');
|
|
await tester.enterText(find.byKey(const Key('dietDescriptionField')), 'Описание тестовой диеты');
|
|
await tester.pump();
|
|
|
|
// Assert
|
|
expect(find.text('Тестовая диета'), findsOneWidget);
|
|
expect(find.text('Описание тестовой диеты'), findsOneWidget);
|
|
});
|
|
|
|
testWidgets('should validate diet creation form', (WidgetTester tester) async {
|
|
// Arrange
|
|
final arranged = Column(
|
|
children: [
|
|
TextFormField(
|
|
key: const Key('dietNameField'),
|
|
decoration: const InputDecoration(labelText: 'Название диеты', errorText: 'Название диеты обязательно'),
|
|
),
|
|
const SizedBox(height: 16),
|
|
TextFormField(
|
|
key: const Key('dietDescriptionField'),
|
|
decoration: const InputDecoration(labelText: 'Описание диеты', errorText: 'Описание диеты обязательно'),
|
|
),
|
|
const SizedBox(height: 16),
|
|
ElevatedButton(key: const Key('createDietButton'), onPressed: () {}, child: const Text('Создать диету')),
|
|
],
|
|
);
|
|
|
|
// Act
|
|
await tester.pumpWidget(
|
|
MaterialApp(
|
|
theme: ThemeHelper().themeData(),
|
|
home: Scaffold(body: arranged),
|
|
),
|
|
);
|
|
await tester.pumpAndSettle();
|
|
|
|
// Assert
|
|
expect(find.text('Название диеты обязательно'), findsOneWidget);
|
|
expect(find.text('Описание диеты обязательно'), findsOneWidget);
|
|
});
|
|
|
|
testWidgets('should display diet creation success state', (WidgetTester tester) async {
|
|
// Arrange
|
|
const arranged = Center(
|
|
child: Column(
|
|
mainAxisAlignment: MainAxisAlignment.center,
|
|
children: [
|
|
Icon(Icons.check_circle, size: 64, color: Colors.green),
|
|
SizedBox(height: 16),
|
|
Text('Диета успешно создана!', style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold)),
|
|
SizedBox(height: 8),
|
|
Text('Ваша диета была сохранена и готова к использованию.'),
|
|
],
|
|
),
|
|
);
|
|
|
|
// Act
|
|
await tester.pumpWidget(MaterialApp(theme: ThemeHelper().themeData(), home: arranged));
|
|
await tester.pumpAndSettle();
|
|
|
|
// Assert
|
|
expect(find.text('Диета успешно создана!'), findsOneWidget);
|
|
expect(find.text('Ваша диета была сохранена и готова к использованию.'), findsOneWidget);
|
|
});
|
|
|
|
testWidgets('should display diet creation error state', (WidgetTester tester) async {
|
|
// Arrange
|
|
const arranged = Center(
|
|
child: Column(
|
|
mainAxisAlignment: MainAxisAlignment.center,
|
|
children: [
|
|
Icon(Icons.error, size: 64, color: Colors.red),
|
|
SizedBox(height: 16),
|
|
Text('Ошибка создания диеты', style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold)),
|
|
SizedBox(height: 8),
|
|
Text('Произошла ошибка при создании диеты. Попробуйте еще раз.'),
|
|
],
|
|
),
|
|
);
|
|
|
|
// Act
|
|
await tester.pumpWidget(MaterialApp(theme: ThemeHelper().themeData(), home: arranged));
|
|
await tester.pumpAndSettle();
|
|
|
|
// Assert
|
|
expect(find.text('Ошибка создания диеты'), findsOneWidget);
|
|
expect(find.text('Произошла ошибка при создании диеты. Попробуйте еще раз.'), findsOneWidget);
|
|
});
|
|
|
|
testWidgets('should display diet creation loading state', (WidgetTester tester) async {
|
|
// Arrange
|
|
const arranged = Center(
|
|
child: Column(mainAxisAlignment: MainAxisAlignment.center, children: [CircularProgressIndicator(), SizedBox(height: 16), Text('Создание диеты...')]),
|
|
);
|
|
|
|
// Act
|
|
await tester.pumpWidget(MaterialApp(theme: ThemeHelper().themeData(), home: arranged));
|
|
await tester.pump();
|
|
|
|
// Assert
|
|
expect(find.byType(CircularProgressIndicator), findsOneWidget);
|
|
expect(find.text('Создание диеты...'), findsOneWidget);
|
|
});
|
|
|
|
testWidgets('should handle diet editing workflow', (WidgetTester tester) async {
|
|
// Arrange
|
|
const testDiet = Diet(
|
|
id: 1,
|
|
slug: 'test-diet',
|
|
name: 'Тестовая диета',
|
|
image: 'base64image',
|
|
duration: 7,
|
|
state: DietState.active,
|
|
type: DietType(meat: true, veg: false, carb: true, light: false),
|
|
details: 'Описание тестовой диеты',
|
|
dailySchedule: {},
|
|
author: Author(authorSlug: 'test-author', authorName: 'Test Author', authorProfileIcon: '', authorIsMe: true, isEntitySubscribedByMe: false),
|
|
totalLikes: 0,
|
|
totalViews: 0,
|
|
totalComments: 0,
|
|
wasLikedByMe: false,
|
|
isFavorite: false,
|
|
dailyNutrition: [],
|
|
);
|
|
|
|
Widget editWidget = MaterialApp(
|
|
theme: ThemeHelper().themeData(),
|
|
localizationsDelegates: const [
|
|
S.delegate,
|
|
GlobalMaterialLocalizations.delegate,
|
|
GlobalWidgetsLocalizations.delegate,
|
|
GlobalCupertinoLocalizations.delegate,
|
|
],
|
|
supportedLocales: const [Locale('ru')],
|
|
home: BlocProvider<CreateDietCubit>(
|
|
create: (context) => createDietCubit,
|
|
child: const CreateDietScreen(diet: testDiet),
|
|
),
|
|
);
|
|
|
|
// Act
|
|
await tester.pumpWidget(editWidget);
|
|
await tester.pumpAndSettle();
|
|
|
|
// Assert
|
|
expect(find.byType(CreateDietScreen), findsOneWidget);
|
|
});
|
|
|
|
testWidgets('should display diet creation form with all required fields', (WidgetTester tester) async {
|
|
// Act
|
|
await tester.pumpWidget(createTestWidget());
|
|
await tester.pumpAndSettle();
|
|
|
|
// Assert
|
|
// Title
|
|
expect(find.text('Создание диеты'), findsOneWidget);
|
|
// Core widgets present on real screen
|
|
expect(find.byType(DietDurationDropDownWidget), findsOneWidget);
|
|
expect(find.byType(StatusDropDownWidget), findsOneWidget);
|
|
expect(find.byType(DietTypeChoiceWidget), findsOneWidget);
|
|
expect(find.byType(DietImagePickerWidget), findsOneWidget);
|
|
// Action buttons
|
|
expect(find.byType(CustomOutlinedButton), findsNWidgets(3));
|
|
});
|
|
});
|
|
}
|