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/create_recipe/reporitories/recipe_repository.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/recipes_cubit/recipes_cubit.dart'; import 'package:eatvio_app/features/feed/presentation/bloc/recipes_cubit/recipes_state.dart'; import 'package:eatvio_app/features/feed/presentation/widgets/recipes.dart'; import 'package:eatvio_app/features/feed/presentation/widgets/recipes_widget.dart'; import 'package:eatvio_app/features/feed/repositories/feed_repository.dart'; import 'package:eatvio_app/features/recipe/repositories/about_recipe_repository.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_portal/flutter_portal.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:mockito/annotations.dart'; import 'package:flutter_dotenv/flutter_dotenv.dart'; import 'package:network_image_mock/network_image_mock.dart'; import 'package:visibility_detector/visibility_detector.dart'; import 'basic_widget_test.mocks.dart'; @GenerateNiceMocks([MockSpec(), MockSpec(), MockSpec()]) void main() { group('Recipes Basic Widget Tests', () { late MockFeedRepository mockFeedRepository; late MockRecipeRepository mockRecipeRepository; late MockAboutRecipeRepository mockAboutRecipeRepository; late RecipesCubit recipesCubit; const testRecipe = Recipe( id: 1, image: 'test-image.jpg', state: 'active', cookingTime: 30, activeCookingTime: 15, ingredients: [], description: 'Test recipe description', steps: [], videoUrl: null, createdAt: null, updatedAt: null, title: 'Test Recipe', type: 'main', slug: 'test-recipe-slug', commentsCount: 5, wasLikedByMe: false, isFavorite: false, calories: 300.0, servings: 4, likesCount: 10, viewsCount: 100, tags: [], author: Author(authorSlug: '', authorName: '', authorProfileIcon: 'https://example.com/icon.jpg', authorIsMe: false), ); setUp(() async { await sl.reset(); dotenv.loadFromString(envString: 'DEV_BASE_URL=http://localhost\nBASE_URL=http://localhost'); mockFeedRepository = MockFeedRepository(); mockRecipeRepository = MockRecipeRepository(); mockAboutRecipeRepository = MockAboutRecipeRepository(); recipesCubit = RecipesCubit(feedRepository: mockFeedRepository, recipeRepository: mockRecipeRepository, aboutRecipeRepository: mockAboutRecipeRepository); sl.registerLazySingleton(() => AppConfig(buildType: AppBuildType.tests)..setBaseUrl('http://localhost')); VisibilityDetectorController.instance.updateInterval = Duration.zero; }); tearDown(() async { await Future.delayed(const Duration(milliseconds: 100)); if (!recipesCubit.isClosed) { recipesCubit.close(); } }); Widget createTestWidget(Widget child, {bool wrapInScaffold = true}) { final widget = BlocProvider( create: (context) => recipesCubit, child: Portal( child: wrapInScaffold ? Scaffold(body: SingleChildScrollView(child: child)) : SizedBox(width: 800, height: 1200, child: SingleChildScrollView(child: child)), ), ); return MaterialApp( theme: ThemeHelper().themeData(), localizationsDelegates: const [ S.delegate, GlobalMaterialLocalizations.delegate, GlobalWidgetsLocalizations.delegate, GlobalCupertinoLocalizations.delegate, ], supportedLocales: const [Locale('ru')], home: widget, ); } Widget createRecipesTestWidget(Widget child) { return MaterialApp( theme: ThemeHelper().themeData(), localizationsDelegates: const [ S.delegate, GlobalMaterialLocalizations.delegate, GlobalWidgetsLocalizations.delegate, GlobalCupertinoLocalizations.delegate, ], supportedLocales: const [Locale('ru')], home: Portal( child: Scaffold( body: BlocProvider( create: (context) => recipesCubit, child: LayoutBuilder( builder: (context, constraints) => SizedBox( width: constraints.maxWidth > 0 ? constraints.maxWidth : 800, height: constraints.maxHeight > 0 ? constraints.maxHeight : 600, child: child, ), ), ), ), ), ); } testWidgets('should create RecipesCubit successfully', (WidgetTester tester) async { // Arrange & Act final cubit = RecipesCubit(feedRepository: mockFeedRepository, recipeRepository: mockRecipeRepository, aboutRecipeRepository: mockAboutRecipeRepository); // Assert expect(cubit, isA()); expect(cubit.state.recipesList, isEmpty); cubit.close(); }); testWidgets('should display RecipesWidget with recipe title', (WidgetTester tester) async { await mockNetworkImagesFor(() async { // Arrange recipesCubit.emit(RecipesState.initial().copyWith(recipesList: [testRecipe], isLoading: false)); const widget = RecipesWidget(recipe: testRecipe, index: 0); // Act await tester.pumpWidget(createTestWidget(widget)); await tester.pump(); await tester.pump(const Duration(milliseconds: 100)); // Assert expect(find.text('Test Recipe'), findsOneWidget); expect(find.byType(RecipesWidget), findsOneWidget); }); await tester.pump(const Duration(milliseconds: 600)); }); testWidgets('should display RecipesWidget with description when description is present', (WidgetTester tester) async { await mockNetworkImagesFor(() async { // Arrange recipesCubit.emit(RecipesState.initial().copyWith(recipesList: [testRecipe], isLoading: false)); const widget = RecipesWidget(recipe: testRecipe, index: 0); // Act await tester.pumpWidget(createTestWidget(widget)); await tester.pump(); await tester.pump(const Duration(milliseconds: 100)); // Assert expect(find.text('Test Recipe'), findsOneWidget); expect(find.byType(RecipesWidget), findsOneWidget); }); await tester.pump(const Duration(milliseconds: 600)); }); testWidgets('should display RecipesWidget structure correctly', (WidgetTester tester) async { await mockNetworkImagesFor(() async { // Arrange recipesCubit.emit(RecipesState.initial().copyWith(recipesList: [testRecipe], isLoading: false)); const widget = RecipesWidget(recipe: testRecipe, index: 0); // Act await tester.pumpWidget(createTestWidget(widget)); await tester.pump(); await tester.pump(const Duration(milliseconds: 100)); // Assert expect(find.byType(RecipesWidget), findsOneWidget); expect(find.text('Test Recipe'), findsOneWidget); }); await tester.pump(const Duration(milliseconds: 600)); }); testWidgets('should display Recipes widget with loading state', (WidgetTester tester) async { // Arrange recipesCubit.emit(RecipesState.initial()); final widget = Recipes(handleVisibility: ({required visibleFraction, required id, required type}) {}); // Act await tester.pumpWidget(createRecipesTestWidget(widget)); await tester.pump(); // Assert expect(find.byType(Recipes), findsOneWidget); }); testWidgets('should display Recipes widget with loaded state', (WidgetTester tester) async { await mockNetworkImagesFor(() async { // Arrange recipesCubit.emit(RecipesState.initial().copyWith(recipesList: [testRecipe], isLoading: false)); final widget = Recipes(handleVisibility: ({required visibleFraction, required id, required type}) {}); // Act await tester.pumpWidget(createRecipesTestWidget(widget)); await tester.pump(); // Assert expect(find.byType(Recipes), findsOneWidget); expect(find.byType(RefreshIndicator), findsOneWidget); expect(find.byType(ListView), findsOneWidget); }); await tester.pump(const Duration(milliseconds: 600)); }); testWidgets('should display Recipes widget with error state', (WidgetTester tester) async { // Arrange recipesCubit.emit(RecipesState.initial().copyWith(isLoading: false, errorMessage: 'Error message')); final widget = Recipes(handleVisibility: ({required visibleFraction, required id, required type}) {}); // Act await tester.pumpWidget(createRecipesTestWidget(widget)); await tester.pump(); // Assert expect(find.byType(Recipes), findsOneWidget); }); testWidgets('should display Recipes widget with multiple recipes', (WidgetTester tester) async { await mockNetworkImagesFor(() async { // Arrange final testRecipe2 = testRecipe.copyWith(id: 2, title: 'Test Recipe 2'); recipesCubit.emit(RecipesState.initial().copyWith(recipesList: [testRecipe, testRecipe2], isLoading: false)); final widget = Recipes(handleVisibility: ({required visibleFraction, required id, required type}) {}); // Act await tester.pumpWidget(createRecipesTestWidget(widget)); await tester.pump(); // Assert expect(find.byType(Recipes), findsOneWidget); expect(find.byType(ListView), findsOneWidget); }); await tester.pump(const Duration(milliseconds: 600)); }); }); }