From 0be7392a393fdc605fb618869d599e6dd5ccaf59 Mon Sep 17 00:00:00 2001 From: mso7nueh Date: Fri, 21 Nov 2025 23:24:49 +0300 Subject: [PATCH] feat(tests): add fixtures --- .../advanced_widget_test.dart | 4 +- .../integration_widget_test.dart | 3 +- .../create_diet/basic_widget_test.dart | 80 ++++++++++ .../create_plan/basic_widget_test.dart | 43 ++++++ .../create_diet/created_diet_response.json | 4 + .../create_diet/glossary_search_response.json | 23 ++- .../create_diet/nutrition_response.json | 13 ++ test/fixtures/create_diet/update_request.json | 15 ++ .../fixtures/doubles/create_diet_doubles.dart | 146 +++++++++--------- .../fixtures/doubles/create_plan_doubles.dart | 30 +++- .../fixtures/profile/my_profile_response.json | 18 ++- 11 files changed, 292 insertions(+), 87 deletions(-) create mode 100644 test/fixtures/create_diet/created_diet_response.json create mode 100644 test/fixtures/create_diet/nutrition_response.json create mode 100644 test/fixtures/create_diet/update_request.json diff --git a/test/features/blog_post_create/advanced_widget_test.dart b/test/features/blog_post_create/advanced_widget_test.dart index 6de7195d..e052fa3c 100644 --- a/test/features/blog_post_create/advanced_widget_test.dart +++ b/test/features/blog_post_create/advanced_widget_test.dart @@ -17,12 +17,14 @@ void main() { late BlogPostCreateCubit blogPostCreateCubit; late Map fullCreateFixture; late Map updateFixture; + late Map deleteFixture; setUp(() { blogPostRepository = FixtureBlogPostRepository(); blogPostCreateCubit = BlogPostCreateCubit(blogPostRepository: blogPostRepository); fullCreateFixture = FixtureReader.readAsMap('blog_post_create/create_request_full.json'); updateFixture = FixtureReader.readAsMap('blog_post_create/update_request.json'); + deleteFixture = FixtureReader.readAsMap('blog_post_create/delete_request.json'); }); tearDown(() { @@ -257,7 +259,7 @@ void main() { await tester.pump(); // Assert - expect(blogPostRepository.deleteCalls.single, 1); + expect(blogPostRepository.deleteCalls.single, deleteFixture['id']); }); testWidgets('should handle loading state during blog post creation', (WidgetTester tester) async { diff --git a/test/features/blog_post_create/integration_widget_test.dart b/test/features/blog_post_create/integration_widget_test.dart index 33f7650e..d4fd63f8 100644 --- a/test/features/blog_post_create/integration_widget_test.dart +++ b/test/features/blog_post_create/integration_widget_test.dart @@ -230,7 +230,8 @@ void main() { await tester.pump(); // Assert - expect(blogPostRepository.deleteCalls.single, 1); + final deleteFixture = FixtureReader.readAsMap('blog_post_create/delete_request.json'); + expect(blogPostRepository.deleteCalls.single, deleteFixture['id']); expect(feedProbe.loadRequests, 1); }); diff --git a/test/features/create_diet/basic_widget_test.dart b/test/features/create_diet/basic_widget_test.dart index 7c90dcc7..54e9a328 100644 --- a/test/features/create_diet/basic_widget_test.dart +++ b/test/features/create_diet/basic_widget_test.dart @@ -1,10 +1,15 @@ +import 'dart:io'; + 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_image_compress/flutter_image_compress.dart'; import 'package:flutter_test/flutter_test.dart'; import '../../fixtures/doubles/create_diet_doubles.dart'; +import '../../fixtures/fixture_reader.dart'; void main() { group('Diet Creation Basic Widget Tests', () { @@ -12,8 +17,10 @@ void main() { late FixtureProfileRepository profileRepository; late FixtureGlossaryRepository glossaryRepository; late CreateDietCubit createDietCubit; + late Map dietFixture; setUp(() { + dietFixture = FixtureReader.readAsMap('create_diet/create_diet_request.json'); createDietRepository = FixtureCreateDietRepository(); profileRepository = FixtureProfileRepository(); glossaryRepository = FixtureGlossaryRepository(); @@ -475,5 +482,78 @@ void main() { expect(find.text('• 21 день - трехнедельная диета'), findsOneWidget); expect(find.text('• 30 дней - месячная диета'), findsOneWidget); }); + + testWidgets('should send correct request when creating diet', (WidgetTester tester) async { + // Arrange + // Create a minimal valid JPEG file (1x1 pixel) + final jpegHeader = [ + 0xFF, 0xD8, 0xFF, 0xE0, 0x00, 0x10, 0x4A, 0x46, 0x49, 0x46, 0x00, 0x01, + 0x01, 0x01, 0x00, 0x48, 0x00, 0x48, 0x00, 0x00, 0xFF, 0xDB, 0x00, 0x43, + 0x00, 0x08, 0x06, 0x06, 0x07, 0x06, 0x05, 0x08, 0x07, 0x07, 0x07, 0x09, + 0x09, 0x08, 0x0A, 0x0C, 0x14, 0x0D, 0x0C, 0x0B, 0x0B, 0x0C, 0x19, 0x12, + 0x13, 0x0F, 0x14, 0x1D, 0x1A, 0x1F, 0x1E, 0x1D, 0x1A, 0x1C, 0x1C, 0x20, + 0x24, 0x2E, 0x27, 0x20, 0x22, 0x2C, 0x23, 0x1C, 0x1C, 0x28, 0x37, 0x29, + 0x2C, 0x30, 0x31, 0x34, 0x34, 0x34, 0x1F, 0x27, 0x39, 0x3D, 0x38, 0x32, + 0x3C, 0x2E, 0x33, 0x34, 0x32, 0xFF, 0xC0, 0x00, 0x11, 0x08, 0x00, 0x01, + 0x00, 0x01, 0x01, 0x01, 0x11, 0x00, 0x02, 0x11, 0x01, 0x03, 0x11, 0x01, + 0xFF, 0xC4, 0x00, 0x14, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x08, 0xFF, 0xC4, + 0x00, 0x14, 0x10, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xDA, 0x00, 0x0C, + 0x03, 0x01, 0x00, 0x02, 0x11, 0x03, 0x11, 0x00, 0x3F, 0x00, 0x3F, 0xFF, + 0xD9 + ]; + final tempFile = File('${Directory.systemTemp.path}/test_image.jpg'); + await tempFile.writeAsBytes(jpegHeader); + 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(); + await tester.pumpAndSettle(); + + // 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 + if (await tempFile.exists()) { + await tempFile.delete(); + } + }); }); } diff --git a/test/features/create_plan/basic_widget_test.dart b/test/features/create_plan/basic_widget_test.dart index fb3b6fa0..34425727 100644 --- a/test/features/create_plan/basic_widget_test.dart +++ b/test/features/create_plan/basic_widget_test.dart @@ -1,7 +1,10 @@ +import 'dart:io'; + import 'package:eatvio_app/core/theme/theme_helper.dart'; import 'package:eatvio_app/features/create_plan/presentation/cubit/create_plan_cubit.dart'; import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:flutter_image_compress/flutter_image_compress.dart'; import 'package:flutter_test/flutter_test.dart'; import '../../fixtures/doubles/create_plan_doubles.dart'; @@ -581,5 +584,45 @@ void main() { expect(find.byKey(const Key('basicOptionContainer')), findsOneWidget); expect(find.byKey(const Key('premiumOptionContainer')), findsOneWidget); }); + + testWidgets('should send correct request when creating plan', (WidgetTester tester) async { + // Arrange + final tempFile = File('${Directory.systemTemp.path}/test_image.jpg'); + await tempFile.writeAsBytes([0, 1, 2, 3]); + final xFile = XFile(tempFile.path); + + createPlanCubit.setName(planFixture['name'] as String); + createPlanCubit.setDetails(planFixture['description'] as String); + createPlanCubit.setDuration(0); // 7 days + createPlanCubit.setPlanState(planFixture['status'] as String); + createPlanCubit.setImage(xFile); + createPlanCubit.setPlanType([true, false, false, false]); // At least one type must be selected + + // Set at least one option (required for validation) + final options = planFixture['options'] as List; + if (options.isNotEmpty) { + final firstOption = options[0] as Map; + createPlanCubit.setOptionTitle(firstOption['title'] as String); + createPlanCubit.setPrice((firstOption['price'] as num).toDouble()); + createPlanCubit.setOptionDescription(firstOption['description'] as String); + } + + // Act + await createPlanCubit.publishPlan(); + await tester.pumpAndSettle(); + + // Assert + expect(createPlanRepository.createPlanRequests.length, 1); + final request = createPlanRepository.createPlanRequests.single; + expect(request['name'], planFixture['name']); + expect(request['description'], planFixture['description']); + expect(request['duration'], planFixture['duration']); + expect(request['status'], planFixture['status']); + + // Cleanup + if (await tempFile.exists()) { + await tempFile.delete(); + } + }); }); } diff --git a/test/fixtures/create_diet/created_diet_response.json b/test/fixtures/create_diet/created_diet_response.json new file mode 100644 index 00000000..8240ed62 --- /dev/null +++ b/test/fixtures/create_diet/created_diet_response.json @@ -0,0 +1,4 @@ +{ + "id": 1 +} + diff --git a/test/fixtures/create_diet/glossary_search_response.json b/test/fixtures/create_diet/glossary_search_response.json index 460706ca..8a7fc90f 100644 --- a/test/fixtures/create_diet/glossary_search_response.json +++ b/test/fixtures/create_diet/glossary_search_response.json @@ -3,15 +3,28 @@ { "id": 10, "name": "Авокадо", - "portion": "100 г", - "nutritional_value_id": 501 + "portion_mass_gr": 100, + "energy_on_100gr": 160.0, + "proteins_on_100gr": 2.0, + "fats_on_100gr": 14.0, + "carbohydrates_on_100gr": 9.0, + "energy_on_portion": 160.0, + "proteins_on_portion": 2.0, + "fats_on_portion": 14.0, + "carbohydrates_on_portion": 9.0 }, { "id": 11, "name": "Лосось", - "portion": "150 г", - "nutritional_value_id": 502 + "portion_mass_gr": 150, + "energy_on_100gr": 208.0, + "proteins_on_100gr": 20.0, + "fats_on_100gr": 13.0, + "carbohydrates_on_100gr": 0.0, + "energy_on_portion": 312.0, + "proteins_on_portion": 30.0, + "fats_on_portion": 19.5, + "carbohydrates_on_portion": 0.0 } ] } - diff --git a/test/fixtures/create_diet/nutrition_response.json b/test/fixtures/create_diet/nutrition_response.json new file mode 100644 index 00000000..6dd2119e --- /dev/null +++ b/test/fixtures/create_diet/nutrition_response.json @@ -0,0 +1,13 @@ +{ + "1": { + "1": { + "0": { + "name": "Авокадо", + "portion": "grams", + "quantity": 100, + "product_nutritional_value_id": "501" + } + } + } +} + diff --git a/test/fixtures/create_diet/update_request.json b/test/fixtures/create_diet/update_request.json new file mode 100644 index 00000000..745f02fa --- /dev/null +++ b/test/fixtures/create_diet/update_request.json @@ -0,0 +1,15 @@ +{ + "id": 1, + "name": "Обновленный рацион", + "duration": 14, + "details": "Обновленное описание диеты", + "state": "active", + "types": { + "meat": false, + "carb": true, + "veg": true, + "light": false + } +} + + diff --git a/test/fixtures/doubles/create_diet_doubles.dart b/test/fixtures/doubles/create_diet_doubles.dart index f27a4a4e..f4ed17bc 100644 --- a/test/fixtures/doubles/create_diet_doubles.dart +++ b/test/fixtures/doubles/create_diet_doubles.dart @@ -15,26 +15,63 @@ import 'package:retrofit/dio.dart' show HttpResponse; import '../../fixtures/fixture_reader.dart'; class FixtureCreateDietRepository extends CreateDietRepository { - FixtureCreateDietRepository() - : super( - createDietService: _NoopCreateDietService(), - ); + FixtureCreateDietRepository() : super(createDietService: _NoopCreateDietService()); final List> createRequests = >[]; final List> updateRequests = >[]; - Map>> nutritionFixture = >>{ - 1: { - 1: { - 0: const DailySchedule( - name: 'Авокадо', - portion: Portion.grams, - quantity: 100, - productNutritionalValueId: '501', - ), - }, - }, - }; + Map>> get nutritionFixture { + final nutritionData = FixtureReader.readAsMap('create_diet/nutrition_response.json'); + final result = >>{}; + + for (final dayEntry in nutritionData.entries) { + final dayIndex = int.parse(dayEntry.key); + final dayData = dayEntry.value as Map; + final dayMap = >{}; + + for (final mealEntry in dayData.entries) { + final mealIndex = int.parse(mealEntry.key); + final mealData = mealEntry.value as Map; + final mealMap = {}; + + for (final portionEntry in mealData.entries) { + final portionIndex = int.parse(portionEntry.key); + final portionData = portionEntry.value as Map; + + // Convert portion string to Portion enum + final portionStr = portionData['portion'] as String; + Portion portion; + switch (portionStr) { + case 'gr': + case 'grams': + portion = Portion.grams; + break; + case 'milliliters': + portion = Portion.milliliters; + break; + case 'pcs': + portion = Portion.pcs; + break; + default: + portion = Portion.grams; + } + + mealMap[portionIndex] = DailySchedule( + name: portionData['name'] as String, + portion: portion, + quantity: portionData['quantity'] as int, + productNutritionalValueId: portionData['product_nutritional_value_id'] as String?, + ); + } + + dayMap[mealIndex] = mealMap; + } + + result[dayIndex] = dayMap; + } + + return result; + } @override Future createDiet({ @@ -52,16 +89,12 @@ class FixtureCreateDietRepository extends CreateDietRepository { createRequests.add({ 'name': name, 'duration': duration, - 'state': state.toString(), - 'meatType': meatType, - 'carbType': carbType, - 'vegType': vegType, - 'lightType': lightType, 'details': details, - 'image': image.path, - 'days': dailySchedule.length, + 'state': state.toString(), + 'types': {'meat': meatType == '1', 'carb': carbType == '1', 'veg': vegType == '1', 'light': lightType == '1'}, }); - return const CreatedDiet(id: 1); + final createdDietFixture = FixtureReader.readAsMap('create_diet/created_diet_response.json'); + return CreatedDiet.fromJson(createdDietFixture); } @override @@ -84,75 +117,39 @@ class FixtureCreateDietRepository extends CreateDietRepository { List>>? dailySchedule, }) async { updateRequests.add({ - 'dietId': dietId, - 'state': state.toString(), + 'id': dietId, 'name': name, 'duration': duration, - 'meatType': meatType, - 'carbType': carbType, - 'vegType': vegType, - 'lightType': lightType, 'details': details, - 'image': image?.path, - 'days': dailySchedule?.length, + 'state': state.toString(), + if (meatType != null || carbType != null || vegType != null || lightType != null) + 'types': { + if (meatType != null) 'meat': meatType == '1', + if (carbType != null) 'carb': carbType == '1', + if (vegType != null) 'veg': vegType == '1', + if (lightType != null) 'light': lightType == '1', + }, }); } } class FixtureProfileRepository extends ProfileRepository { - FixtureProfileRepository() - : super( - profileService: _NoopProfileService(), - ); + FixtureProfileRepository() : super(profileService: _NoopProfileService()); @override Future getMyProfile() async { - return const ProfileModel( - id: 99, - name: 'Анна Нутрициолог', - email: 'anna@example.com', - phoneConfirmationState: 'verified', - isPublicProfile: true, - isBlockedByMe: false, - isBlockedByAuthor: false, - isBlockedByUser: false, - isEntitySubscribedByMe: false, - slug: 'diet-author', - profilePhotoUrl: 'avatars/anna.png', - roles: ['author'], - badges: [], - subscriptions: [], - subscribers: [], - ); + final profileFixture = FixtureReader.readAsMap('profile/my_profile_response.json'); + return ProfileModel.fromJson(profileFixture); } } class FixtureGlossaryRepository extends GlossaryRepository { - FixtureGlossaryRepository() - : super( - glossaryService: _NoopGlossaryService(), - ); + FixtureGlossaryRepository() : super(glossaryService: _NoopGlossaryService()); @override Future> getGlossaryListByQuery(String query) async { final results = FixtureReader.readAsMap('create_diet/glossary_search_response.json')['results'] as List; - return results - .map( - (item) => GlossaryModel( - id: item['id'] as int, - name: item['name'] as String, - grInPortion: 100, - energyOn100Gr: 160.0, - proteinsOn100Gr: 2.0, - fatsOn100Gr: 14.0, - carbohydratesOn100Gr: 9.0, - energyOnPortion: 160.0, - proteinsOnPortion: 2.0, - fatsOnPortion: 14.0, - carbohydratesOnPortion: 9.0, - ), - ) - .toList(); + return results.map((item) => GlossaryModel.fromJson(item as Map)).toList(); } } @@ -202,4 +199,3 @@ class _NoopGlossaryService implements GlossaryService { @override noSuchMethod(Invocation invocation) => throw UnimplementedError('GlossaryService call ${invocation.memberName}'); } - diff --git a/test/fixtures/doubles/create_plan_doubles.dart b/test/fixtures/doubles/create_plan_doubles.dart index c96989c3..6c32d0bb 100644 --- a/test/fixtures/doubles/create_plan_doubles.dart +++ b/test/fixtures/doubles/create_plan_doubles.dart @@ -9,7 +9,35 @@ class FixtureCreatePlanRepository extends CreatePlanRepository { @override Future createPlan({required Map model}) async { - createPlanRequests.add(Map.from(model)); + // Normalize request to match fixture format + final normalized = { + 'name': model['name'], + 'description': model['description_md'], + 'duration': model['duration'], + 'status': model['state'] == 'active' ? 'active' : 'draft', + }; + + // Extract options if present + if (model.containsKey('packets_count')) { + final options = >[]; + final packetsCount = model['packets_count'] as int; + for (int i = 1; i <= packetsCount; i++) { + final packetKey = 'packet${i}_options'; + if (model.containsKey(packetKey)) { + final packet = model[packetKey] as Map; + options.add({ + 'title': packet['short'], + 'price': packet['price'], + 'description': packet['description_md'], + }); + } + } + if (options.isNotEmpty) { + normalized['options'] = options; + } + } + + createPlanRequests.add(normalized); } } diff --git a/test/fixtures/profile/my_profile_response.json b/test/fixtures/profile/my_profile_response.json index ae1267f5..0f91454b 100644 --- a/test/fixtures/profile/my_profile_response.json +++ b/test/fixtures/profile/my_profile_response.json @@ -1,7 +1,17 @@ { + "id": 99, + "name": "Анна Нутрициолог", + "email": "anna@example.com", + "phone_confirmation_state": "verified", + "is_public_profile": true, + "is_blocked_by_me": false, + "is_blocked_by_author": false, + "is_blocked_by_user": false, + "is_entity_subscribed_by_me": false, "slug": "diet-author", - "first_name": "Анна", - "last_name": "Нутрициолог", - "avatar": "avatars/anna.png" + "profile_photo_url": "avatars/anna.png", + "roles": ["author"], + "badges": [], + "subscriptions": [], + "subscribers": [] } -