import 'package:eatvio_app/app_keys.dart'; import 'package:eatvio_app/features/create_recipe/entities/recipe_create_entity.dart'; import 'package:eatvio_app/features/create_recipe/presentation/pages/recipe_create_screen.dart'; import 'package:flutter/material.dart'; import 'package:flutter_test/flutter_test.dart'; /// Helper функции для интеграционных тестов создания рецепта /// Навигация к экрану создания рецепта Future navigateToCreateRecipeScreen(WidgetTester tester) async { // Переключаемся на вкладку "Рецепты" если она есть final recipesTab = find.text('Рецепты'); if (recipesTab.evaluate().isNotEmpty) { await tester.tap(recipesTab); await tester.pumpAndSettle(); } // Находим кнопку добавления рецепта (круглая кнопка с плюсом) final addRecipeButton = find.byType(InkWell).first; await tester.tap(addRecipeButton); await tester.pumpAndSettle(const Duration(seconds: 2)); } /// Заполняет основную информацию о рецепте Future fillRecipeBasicInfo( WidgetTester tester, { String title = 'Интеграционный Тестовый Рецепт', TypeOfRecipe type = TypeOfRecipe.salads, int portions = 4, double calories = 250.0, int totalHours = 1, int totalMinutes = 30, int activeHours = 0, int activeMinutes = 45, String description = 'Этот рецепт создан для интеграционного тестирования. Он содержит все необходимые ингредиенты и пошаговую инструкцию приготовления.', }) async { // Заполняем название рецепта final titleField = find.byKey(AppKeys.recipeCreateScreenTitleField); await tester.enterText(titleField, title); await tester.pumpAndSettle(); // Выбираем тип рецепта await selectRecipeType(tester, type); // Заполняем количество порций final portionsField = find.byKey(AppKeys.recipeCreateScreenPortionsField); await tester.enterText(portionsField, portions.toString()); await tester.pumpAndSettle(); // Заполняем калорийность final caloriesField = find.byKey(AppKeys.recipeCreateScreenCaloriesField); await tester.enterText(caloriesField, calories.toString()); await tester.pumpAndSettle(); // Заполняем общее время приготовления final totalHoursField = find.byKey(AppKeys.recipeCreateScreenTotalHoursField); await tester.enterText(totalHoursField, totalHours.toString()); await tester.pumpAndSettle(); final totalMinutesField = find.byKey(AppKeys.recipeCreateScreenTotalMinutesField); await tester.enterText(totalMinutesField, totalMinutes.toString()); await tester.pumpAndSettle(); // Заполняем активное время приготовления final activeHoursField = find.byKey(AppKeys.recipeCreateScreenActiveHoursField); await tester.enterText(activeHoursField, activeHours.toString()); await tester.pumpAndSettle(); final activeMinutesField = find.byKey(AppKeys.recipeCreateScreenActiveMinutesField); await tester.enterText(activeMinutesField, activeMinutes.toString()); await tester.pumpAndSettle(); // Заполняем описание final descriptionField = find.byKey(AppKeys.recipeCreateScreenDescriptionField); await tester.enterText(descriptionField, description); await tester.pumpAndSettle(); } /// Выбирает тип рецепта из выпадающего списка Future selectRecipeType(WidgetTester tester, TypeOfRecipe type) async { final typeDropdown = find.byKey(AppKeys.recipeCreateScreenTypeDropdown); await tester.tap(typeDropdown); await tester.pumpAndSettle(); // Выбираем нужный тип из выпадающего списка final typeOption = find.text(type.toString()); await tester.tap(typeOption); await tester.pumpAndSettle(); } /// Добавляет теги к рецепту Future addTags(WidgetTester tester, List tags) async { final tagField = find.byKey(AppKeys.recipeCreateScreenTagField); final addTagButton = find.byKey(AppKeys.recipeCreateScreenAddTagButton); for (final tag in tags) { await tester.enterText(tagField, tag); await tester.pumpAndSettle(); await tester.tap(addTagButton); await tester.pumpAndSettle(); } } /// Добавляет ингредиенты к рецепту Future addIngredients(WidgetTester tester, List> ingredients) async { final addIngredientButton = find.byKey(AppKeys.recipeCreateScreenAddIngredientButton); for (final ingredient in ingredients) { await tester.ensureVisible(addIngredientButton); await tester.tap(addIngredientButton); await tester.pumpAndSettle(); await addIngredientInModal(tester, ingredient['name']!, ingredient['quantity']!); await tester.pumpAndSettle(); } } /// Добавляет ингредиент в модальном окне Future addIngredientInModal(WidgetTester tester, String name, String quantity) async { // Ищем поля в модальном окне final nameFields = find.byType(TextFormField); if (nameFields.evaluate().isNotEmpty) { await tester.enterText(nameFields.first, name); await tester.pumpAndSettle(); } // Ищем поле количества (второе поле) if (nameFields.evaluate().length > 1) { await tester.enterText(nameFields.at(1), quantity); await tester.pumpAndSettle(); } // Ищем кнопку добавления в модальном окне final addButton = find.text('Добавить'); if (addButton.evaluate().isNotEmpty) { await tester.tap(addButton); await tester.pumpAndSettle(); } } /// Добавляет шаги приготовления Future addCookingSteps(WidgetTester tester, List> steps) async { final addStepButton = find.byKey(AppKeys.recipeCreateScreenAddStepButton); for (int i = 0; i < steps.length; i++) { final step = steps[i]; // Заполняем описание шага final stepDescription = find.byKey(Key('${AppKeys.recipeCreateScreenStepDescriptionField}_$i')); await tester.enterText(stepDescription, step['description']!); await tester.pumpAndSettle(); // Симулируем добавление изображения к шагу если указано if (step.containsKey('hasImage') && step['hasImage'] == 'true') { await mockStepImageSelection(tester, i); } // Добавляем следующий шаг (кроме последнего) if (i < steps.length - 1) { await tester.ensureVisible(addStepButton); await tester.tap(addStepButton); await tester.pumpAndSettle(); } } } /// Публикует рецепт Future publishRecipe(WidgetTester tester) async { final publishButton = find.byKey(AppKeys.recipeCreateScreenPublishButton); await tester.ensureVisible(publishButton); await tester.tap(publishButton); await tester.pumpAndSettle(const Duration(seconds: 5)); // Дать время на API-запрос } /// Симулирует выбор изображения для основного рецепта Future mockImageSelection(WidgetTester tester) async { final imageWidget = find.byKey(AppKeys.recipeCreateScreenImageWidget); if (imageWidget.evaluate().isNotEmpty) { await tester.tap(imageWidget); await tester.pumpAndSettle(); // В реальном интеграционном тесте здесь будет мок image_picker // который возвращает тестовое изображение } } /// Симулирует выбор изображения для шага приготовления Future mockStepImageSelection(WidgetTester tester, int stepIndex) async { final stepImageWidget = find.byKey(Key('${AppKeys.recipeCreateScreenStepImageWidget}_$stepIndex')); if (stepImageWidget.evaluate().isNotEmpty) { await tester.tap(stepImageWidget); await tester.pumpAndSettle(); // В реальном интеграционном тесте здесь будет мок image_picker // который возвращает тестовое изображение } } /// Проверяет, что рецепт был успешно опубликован void verifyRecipePublished(WidgetTester tester) { // После успешной публикации должен произойти переход обратно на экран ленты expect(find.byKey(AppKeys.recipeCreateScreenPublishButton), findsNothing); // Можно добавить дополнительные проверки в зависимости от того, куда происходит переход } /// Проверяет, что валидация полей работает корректно void verifyValidationErrors(WidgetTester tester) { // Проверяем, что остались на экране создания рецепта (валидация не прошла) expect(find.byType(RecipeCreateScreen), findsOneWidget); expect(find.byKey(AppKeys.recipeCreateScreenPublishButton), findsOneWidget); } /// Создает тестовые данные для рецепта Map createTestRecipeData() { return { 'title': 'Интеграционный Тестовый Рецепт', 'type': TypeOfRecipe.salads, 'portions': 4, 'calories': 250.0, 'totalHours': 1, 'totalMinutes': 30, 'activeHours': 0, 'activeMinutes': 45, 'description': 'Этот рецепт создан для интеграционного тестирования. Он содержит все необходимые ингредиенты и пошаговую инструкцию приготовления.', 'tags': ['здоровое питание', 'быстро', 'легко'], 'ingredients': [ {'name': 'Помидоры', 'quantity': '200'}, {'name': 'Огурцы', 'quantity': '150'}, {'name': 'Лук', 'quantity': '50'}, ], 'steps': [ {'description': 'Помойте и нарежьте помидоры и огурцы кубиками среднего размера.', 'hasImage': 'true'}, {'description': 'Нарежьте лук полукольцами и добавьте к овощам.', 'hasImage': 'false'}, {'description': 'Смешайте все ингредиенты в большой миске, добавьте соль и перец по вкусу.', 'hasImage': 'true'}, ], }; }