import 'dart:io'; import 'package:eatvio_app/core/app_initializer.dart'; import 'package:eatvio_app/core/app_config.dart'; import 'package:eatvio_app/core/locator_service.dart'; import 'package:eatvio_app/core/theme/theme_helper.dart'; import 'package:eatvio_app/features/create_diet/presentation/cubit/create_diet_cubit.dart'; import 'package:eatvio_app/features/feed/models/diets_pagination_model.dart'; import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_dotenv/flutter_dotenv.dart'; import 'package:flutter_image_compress/flutter_image_compress.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:image/image.dart' as img; import '../../fixtures/doubles/create_diet_doubles.dart'; import '../../fixtures/fixture_reader.dart'; void main() { group('Diet Creation Basic Widget Tests', () { late FixtureCreateDietRepository createDietRepository; late FixtureProfileRepository profileRepository; late FixtureGlossaryRepository glossaryRepository; late CreateDietCubit createDietCubit; late Map dietFixture; setUp(() async { // Initialize AppConfig for tests to enable compression bypass await sl.reset(); dotenv.loadFromString(envString: 'DEV_BASE_URL=http://localhost\nBASE_URL=http://localhost'); sl.registerLazySingleton(() => AppConfig(buildType: AppBuildType.tests)..setBaseUrl('http://localhost')); dietFixture = FixtureReader.readAsMap('create_diet/create_diet_request.json'); createDietRepository = FixtureCreateDietRepository(); profileRepository = FixtureProfileRepository(); glossaryRepository = FixtureGlossaryRepository(); createDietCubit = CreateDietCubit( createDietRepository: createDietRepository, profileRepository: profileRepository, glossaryRepository: glossaryRepository, ); }); tearDown(() { createDietCubit.close(); }); Widget createTestWidget(Widget child) { return MaterialApp( theme: ThemeHelper().themeData(), home: BlocProvider( create: (context) => createDietCubit, child: Scaffold(body: SingleChildScrollView(child: child)), ), ); } testWidgets('should create CreateDietCubit successfully', (WidgetTester tester) async { // Arrange & Act final cubit = CreateDietCubit( createDietRepository: createDietRepository, profileRepository: profileRepository, glossaryRepository: glossaryRepository, ); // Assert expect(cubit, isA()); expect(cubit.state.isLoading, isFalse); expect(cubit.state.isDietPublishing, isFalse); cubit.close(); }); testWidgets('should display basic diet form fields', (WidgetTester tester) async { // Arrange Widget formWidget = Column( children: [ TextFormField( key: const Key('dietNameField'), decoration: const InputDecoration(labelText: 'Название диеты', hintText: 'Например: Здоровая диета'), ), const SizedBox(height: 16), TextFormField( key: const Key('dietDescriptionField'), decoration: const InputDecoration(labelText: 'Описание диеты'), maxLines: 3, ), const SizedBox(height: 16), DropdownButtonFormField( 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), ElevatedButton(key: const Key('publishDietButton'), onPressed: () {}, child: const Text('Опубликовать диету')), ], ); // Act await tester.pumpWidget(createTestWidget(formWidget)); await tester.pumpAndSettle(); // Assert expect(find.byKey(const Key('dietNameField')), findsOneWidget); expect(find.byKey(const Key('dietDescriptionField')), findsOneWidget); expect(find.byKey(const Key('dietDurationField')), findsOneWidget); expect(find.byKey(const Key('publishDietButton')), findsOneWidget); }); testWidgets('should update diet name when user types in name field', (WidgetTester tester) async { // Arrange Widget formWidget = TextFormField( key: const Key('dietNameField'), decoration: const InputDecoration(labelText: 'Название диеты', hintText: 'Например: Здоровая диета'), ); await tester.pumpWidget(createTestWidget(formWidget)); await tester.pumpAndSettle(); const testName = 'Тестовая диета'; // Act await tester.enterText(find.byKey(const Key('dietNameField')), testName); await tester.pump(); // Assert expect(find.text(testName), findsOneWidget); }); testWidgets('should update diet description when user types in description field', (WidgetTester tester) async { // Arrange Widget formWidget = TextFormField( key: const Key('dietDescriptionField'), decoration: const InputDecoration(labelText: 'Описание диеты'), maxLines: 3, ); await tester.pumpWidget(createTestWidget(formWidget)); await tester.pumpAndSettle(); const testDescription = 'Это тестовое описание диеты для похудения'; // Act await tester.enterText(find.byKey(const Key('dietDescriptionField')), testDescription); await tester.pump(); // Assert expect(find.text(testDescription), findsOneWidget); }); testWidgets('should display diet type selection', (WidgetTester tester) async { // Arrange Widget typeWidget = Column( children: [ const Text('Тип диеты'), const SizedBox(height: 8), Row( children: [ Expanded( child: CheckboxListTile(key: const Key('veganTypeCheckbox'), title: const Text('Веганская'), value: false, onChanged: (value) {}), ), Expanded( child: CheckboxListTile(key: const Key('meatTypeCheckbox'), title: const Text('Мясная'), value: false, onChanged: (value) {}), ), ], ), Row( children: [ Expanded( child: CheckboxListTile(key: const Key('carbTypeCheckbox'), title: const Text('Углеводная'), value: false, onChanged: (value) {}), ), Expanded( child: CheckboxListTile(key: const Key('lightTypeCheckbox'), title: const Text('Легкая'), value: false, onChanged: (value) {}), ), ], ), ], ); // Act await tester.pumpWidget(createTestWidget(typeWidget)); await tester.pumpAndSettle(); // Assert expect(find.text('Тип диеты'), findsOneWidget); expect(find.byKey(const Key('veganTypeCheckbox')), findsOneWidget); expect(find.byKey(const Key('meatTypeCheckbox')), findsOneWidget); expect(find.byKey(const Key('carbTypeCheckbox')), findsOneWidget); expect(find.byKey(const Key('lightTypeCheckbox')), findsOneWidget); }); testWidgets('should handle diet type selection', (WidgetTester tester) async { // Arrange bool veganSelected = false; Widget typeWidget = CheckboxListTile( key: const Key('veganTypeCheckbox'), title: const Text('Веганская'), value: veganSelected, onChanged: (value) { veganSelected = value ?? false; }, ); await tester.pumpWidget(createTestWidget(typeWidget)); await tester.pumpAndSettle(); // Act await tester.tap(find.byKey(const Key('veganTypeCheckbox'))); await tester.pump(); // Assert expect(veganSelected, isTrue); }); testWidgets('should display diet duration dropdown', (WidgetTester tester) async { // Arrange Widget durationWidget = DropdownButtonFormField( 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 день')), DropdownMenuItem(value: 30, child: Text('30 дней')), ], onChanged: (value) {}, ); // Act await tester.pumpWidget(createTestWidget(durationWidget)); await tester.pumpAndSettle(); // Assert expect(find.byKey(const Key('dietDurationField')), findsOneWidget); expect(find.text('Продолжительность диеты'), findsOneWidget); }); testWidgets('should display diet status dropdown', (WidgetTester tester) async { // Arrange Widget statusWidget = DropdownButtonFormField( key: const Key('dietStatusField'), decoration: const InputDecoration(labelText: 'Статус диеты'), items: const [ DropdownMenuItem(value: 'active', child: Text('Активная')), DropdownMenuItem(value: 'draft', child: Text('Черновик')), DropdownMenuItem(value: 'personal', child: Text('Личная')), ], onChanged: (value) {}, ); // Act await tester.pumpWidget(createTestWidget(statusWidget)); await tester.pumpAndSettle(); // Assert expect(find.byKey(const Key('dietStatusField')), findsOneWidget); expect(find.text('Статус диеты'), findsOneWidget); }); testWidgets('should display form labels correctly', (WidgetTester tester) async { // Arrange Widget formWidget = const Column( children: [Text('Название диеты'), Text('Описание диеты'), Text('Продолжительность'), Text('Тип диеты'), Text('Статус')], ); // Act await tester.pumpWidget(createTestWidget(formWidget)); await tester.pumpAndSettle(); // Assert expect(find.text('Название диеты'), findsOneWidget); expect(find.text('Описание диеты'), findsOneWidget); expect(find.text('Продолжительность'), findsOneWidget); expect(find.text('Тип диеты'), findsOneWidget); expect(find.text('Статус'), findsOneWidget); }); testWidgets('should display placeholder text in name field', (WidgetTester tester) async { // Arrange Widget formWidget = TextFormField( key: const Key('dietNameField'), decoration: const InputDecoration(labelText: 'Название диеты', hintText: 'Например: Здоровая диета'), ); // Act await tester.pumpWidget(createTestWidget(formWidget)); await tester.pumpAndSettle(); // Assert expect(find.text('Например: Здоровая диета'), findsOneWidget); }); testWidgets('should display publish button', (WidgetTester tester) async { // Arrange Widget buttonWidget = ElevatedButton(key: const Key('publishDietButton'), onPressed: () {}, child: const Text('Опубликовать диету')); // Act await tester.pumpWidget(createTestWidget(buttonWidget)); await tester.pumpAndSettle(); // Assert expect(find.byKey(const Key('publishDietButton')), findsOneWidget); expect(find.text('Опубликовать диету'), findsOneWidget); }); testWidgets('should handle publish button tap', (WidgetTester tester) async { // Arrange bool buttonPressed = false; Widget buttonWidget = ElevatedButton( key: const Key('publishDietButton'), onPressed: () { buttonPressed = true; }, child: const Text('Опубликовать диету'), ); await tester.pumpWidget(createTestWidget(buttonWidget)); await tester.pumpAndSettle(); // Act await tester.tap(find.byKey(const Key('publishDietButton'))); await tester.pump(); // Assert expect(buttonPressed, isTrue); }); testWidgets('should display loading state', (WidgetTester tester) async { // Arrange Widget loadingWidget = const Center(child: CircularProgressIndicator()); // Act await tester.pumpWidget(createTestWidget(loadingWidget)); await tester.pump(); // Assert expect(find.byType(CircularProgressIndicator), findsOneWidget); }); testWidgets('should display error message', (WidgetTester tester) async { // Arrange Widget errorWidget = const Center(child: Text('Произошла ошибка при создании диеты')); // Act await tester.pumpWidget(createTestWidget(errorWidget)); await tester.pumpAndSettle(); // Assert expect(find.text('Произошла ошибка при создании диеты'), findsOneWidget); }); testWidgets('should display success message', (WidgetTester tester) async { // Arrange Widget successWidget = const Center(child: Text('Диета успешно создана!')); // Act await tester.pumpWidget(createTestWidget(successWidget)); await tester.pumpAndSettle(); // Assert expect(find.text('Диета успешно создана!'), findsOneWidget); }); testWidgets('should validate required fields', (WidgetTester tester) async { // Arrange Widget validationWidget = 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: 'Описание диеты обязательно'), ), ], ); // Act await tester.pumpWidget(createTestWidget(validationWidget)); await tester.pumpAndSettle(); // Assert expect(find.text('Название диеты обязательно'), findsOneWidget); expect(find.text('Описание диеты обязательно'), findsOneWidget); }); testWidgets('should display complete diet creation form', (WidgetTester tester) async { // Arrange Widget completeForm = Column( children: [ TextFormField( key: const Key('dietNameField'), decoration: const InputDecoration(labelText: 'Название диеты', hintText: 'Например: Здоровая диета'), ), const SizedBox(height: 16), TextFormField( key: const Key('dietDescriptionField'), decoration: const InputDecoration(labelText: 'Описание диеты'), maxLines: 3, ), const SizedBox(height: 16), DropdownButtonFormField( 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( 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), ElevatedButton(key: const Key('publishDietButton'), onPressed: () {}, child: const Text('Опубликовать диету')), ], ); // Act await tester.pumpWidget(createTestWidget(completeForm)); await tester.pumpAndSettle(); // Assert expect(find.byKey(const Key('dietNameField')), findsOneWidget); expect(find.byKey(const Key('dietDescriptionField')), findsOneWidget); expect(find.byKey(const Key('dietDurationField')), findsOneWidget); expect(find.byKey(const Key('dietStatusField')), findsOneWidget); expect(find.byKey(const Key('publishDietButton')), findsOneWidget); }); testWidgets('should display diet type options', (WidgetTester tester) async { // Arrange Widget typeOptionsWidget = const Column( children: [ Text('Выберите тип диеты:'), SizedBox(height: 8), Text('• Веганская - без продуктов животного происхождения'), Text('• Мясная - с мясными продуктами'), Text('• Углеводная - с высоким содержанием углеводов'), Text('• Легкая - низкокалорийная диета'), ], ); // Act await tester.pumpWidget(createTestWidget(typeOptionsWidget)); await tester.pumpAndSettle(); // Assert expect(find.text('Выберите тип диеты:'), findsOneWidget); expect(find.text('• Веганская - без продуктов животного происхождения'), findsOneWidget); expect(find.text('• Мясная - с мясными продуктами'), findsOneWidget); expect(find.text('• Углеводная - с высоким содержанием углеводов'), findsOneWidget); expect(find.text('• Легкая - низкокалорийная диета'), findsOneWidget); }); testWidgets('should display diet duration options', (WidgetTester tester) async { // Arrange Widget durationOptionsWidget = const Column( children: [ Text('Доступные продолжительности:'), SizedBox(height: 8), Text('• 7 дней - недельная диета'), Text('• 14 дней - двухнедельная диета'), Text('• 21 день - трехнедельная диета'), Text('• 30 дней - месячная диета'), ], ); // Act await tester.pumpWidget(createTestWidget(durationOptionsWidget)); await tester.pumpAndSettle(); // Assert expect(find.text('Доступные продолжительности:'), findsOneWidget); expect(find.text('• 7 дней - недельная диета'), findsOneWidget); expect(find.text('• 14 дней - двухнедельная диета'), findsOneWidget); expect(find.text('• 21 день - трехнедельная диета'), findsOneWidget); expect(find.text('• 30 дней - месячная диета'), findsOneWidget); }); testWidgets('should send correct request when creating diet', (WidgetTester tester) async { // Arrange // Create a valid JPEG file using image package (100x100 pixel image) final testImage = img.Image(width: 100, height: 100); img.fill(testImage, color: img.ColorRgb8(128, 128, 128)); final jpegBytes = img.encodeJpg(testImage, quality: 85); final tempFile = File('${Directory.systemTemp.path}/test_image_${DateTime.now().millisecondsSinceEpoch}.jpg'); await tempFile.writeAsBytes(jpegBytes); final xFile = XFile(tempFile.path); createDietCubit.setName(dietFixture['name'] as String); createDietCubit.setDetails(dietFixture['details'] as String); createDietCubit.setDuration(0); // 7 days - this creates dailySchedule with 7 days createDietCubit.setDietState(DietState.draft); final types = dietFixture['types'] as Map; createDietCubit.setDietType(key: 'meat', value: types['meat'] as bool); createDietCubit.setDietType(key: 'carb', value: types['carb'] as bool); createDietCubit.setDietType(key: 'veg', value: types['veg'] as bool); createDietCubit.setDietType(key: 'light', value: types['light'] as bool); // Add at least one product to each day to pass validation final glossaryResults = await glossaryRepository.getGlossaryListByQuery('test'); if (glossaryResults.isNotEmpty) { final product = glossaryResults.first; final dailySchedule = DailySchedule( name: product.name, portion: Portion.grams, quantity: product.grInPortion, productNutritionalValueId: product.id.toString(), ); // Add product to first meal of first day for all 7 days for (int day = 0; day < 7; day++) { createDietCubit.addPortion(dayIndex: day, mealIndex: 0, portion: dailySchedule); } } createDietCubit.setImage(xFile, imageUrl: null); // Act final result = await createDietCubit.createDiet().timeout( const Duration(seconds: 10), ); await tester.pump(); // Allow state changes to propagate await tester.pump(); // Additional pump for any delayed updates // Assert expect(result, isNull); // Should return null on success expect(createDietRepository.createRequests.length, 1); final request = createDietRepository.createRequests.single; expect(request['name'], dietFixture['name']); expect(request['details'], dietFixture['details']); expect(request['duration'], dietFixture['duration']); expect(request['state'], dietFixture['state']); expect(request['types'], dietFixture['types']); // Cleanup - remove original and compressed files if (await tempFile.exists()) { await tempFile.delete(); } // ImageCompressUtility creates compressed file with _compressed suffix final filePathWithoutExtension = tempFile.path.replaceAll(RegExp(r'\.(jpg|jpeg)$', caseSensitive: false), ''); final compressedFile = File('${filePathWithoutExtension}_compressed.jpg'); if (await compressedFile.exists()) { await compressedFile.delete(); } }); }); }