added widget and unit tests for plans, diets, recipes

This commit is contained in:
misha
2025-12-02 12:34:13 +03:00
parent b420d11996
commit 1738d27b82
17 changed files with 2822 additions and 191 deletions

View File

@@ -0,0 +1,244 @@
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/diets/repositories/diet_repository.dart';
import 'package:eatvio_app/features/feed/models/diets_pagination_model.dart';
import 'package:eatvio_app/features/feed/presentation/bloc/diets_cubit/diets_cubit.dart';
import 'package:eatvio_app/features/feed/presentation/bloc/diets_cubit/diets_state.dart';
import 'package:eatvio_app/features/feed/presentation/widgets/diets.dart';
import 'package:eatvio_app/features/feed/presentation/widgets/diets_widget.dart';
import 'package:eatvio_app/features/feed/repositories/feed_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<FeedRepository>(), MockSpec<DietRepository>()])
void main() {
group('Diets Basic Widget Tests', () {
late MockFeedRepository mockFeedRepository;
late MockDietRepository mockDietRepository;
late DietsCubit dietsCubit;
const testDiet = Diet(
id: 1,
slug: 'test-diet',
name: 'Test Diet',
image: 'test-image.jpg',
duration: 30,
state: DietState.active,
type: DietType(light: false, veg: false, carb: false, meat: false),
details: 'Test diet description',
dailySchedule: {},
author: Author(authorSlug: '', authorName: '', authorProfileIcon: 'https://example.com/icon.jpg', authorIsMe: false),
totalLikes: 1,
totalViews: 1,
totalComments: 1,
wasLikedByMe: false,
isFavorite: false,
dailyNutrition: [],
);
setUp(() async {
await sl.reset();
dotenv.loadFromString(envString: 'DEV_BASE_URL=http://localhost\nBASE_URL=http://localhost');
mockFeedRepository = MockFeedRepository();
mockDietRepository = MockDietRepository();
dietsCubit = DietsCubit(feedRepository: mockFeedRepository, dietsRepository: mockDietRepository);
sl.registerLazySingleton<AppConfig>(() => AppConfig(buildType: AppBuildType.tests)..setBaseUrl('http://localhost'));
VisibilityDetectorController.instance.updateInterval = Duration.zero;
});
tearDown(() async {
await Future.delayed(const Duration(milliseconds: 100));
if (!dietsCubit.isClosed) {
dietsCubit.close();
}
});
Widget createTestWidget(Widget child, {bool wrapInScaffold = true}) {
final widget = BlocProvider<DietsCubit>(
create: (context) => dietsCubit,
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 createDietsTestWidget(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<DietsCubit>(
create: (context) => dietsCubit,
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 DietsCubit successfully', (WidgetTester tester) async {
// Arrange & Act
final cubit = DietsCubit(feedRepository: mockFeedRepository, dietsRepository: mockDietRepository);
// Assert
expect(cubit, isA<DietsCubit>());
expect(cubit.state.dietsList, isEmpty);
cubit.close();
});
testWidgets('should display DietsWidget with diet name', (WidgetTester tester) async {
await mockNetworkImagesFor(() async {
// Arrange
dietsCubit.emit(DietsState.initial().copyWith(dietsList: [testDiet], isLoading: false));
const widget = DietsWidget(diet: testDiet, index: 0);
// Act
await tester.pumpWidget(createTestWidget(widget));
await tester.pump();
await tester.pump(const Duration(milliseconds: 100));
// Assert
expect(find.text('Test Diet'), findsOneWidget);
expect(find.byType(DietsWidget), findsOneWidget);
});
await tester.pump(const Duration(milliseconds: 600));
});
testWidgets('should display DietsWidget with description when details are present', (WidgetTester tester) async {
await mockNetworkImagesFor(() async {
// Arrange
dietsCubit.emit(DietsState.initial().copyWith(dietsList: [testDiet], isLoading: false));
const widget = DietsWidget(diet: testDiet, index: 0);
// Act
await tester.pumpWidget(createTestWidget(widget));
await tester.pump();
await tester.pump(const Duration(milliseconds: 100));
// Assert
expect(find.text('Test Diet'), findsOneWidget);
expect(find.byType(DietsWidget), findsOneWidget);
});
await tester.pump(const Duration(milliseconds: 600));
});
testWidgets('should display DietsWidget structure correctly', (WidgetTester tester) async {
await mockNetworkImagesFor(() async {
// Arrange
dietsCubit.emit(DietsState.initial().copyWith(dietsList: [testDiet], isLoading: false));
const widget = DietsWidget(diet: testDiet, index: 0);
// Act
await tester.pumpWidget(createTestWidget(widget));
await tester.pump();
await tester.pump(const Duration(milliseconds: 100));
// Assert
expect(find.byType(DietsWidget), findsOneWidget);
expect(find.text('Test Diet'), findsOneWidget);
});
await tester.pump(const Duration(milliseconds: 600));
});
testWidgets('should display Diets widget with loading state', (WidgetTester tester) async {
// Arrange
dietsCubit.emit(DietsState.initial());
final widget = Diets(handleVisibility: ({required visibleFraction, required id, required type}) {});
// Act
await tester.pumpWidget(createDietsTestWidget(widget));
await tester.pump();
// Assert
expect(find.byType(Diets), findsOneWidget);
});
testWidgets('should display Diets widget with loaded state', (WidgetTester tester) async {
await mockNetworkImagesFor(() async {
// Arrange
dietsCubit.emit(DietsState.initial().copyWith(dietsList: [testDiet], isLoading: false));
final widget = Diets(handleVisibility: ({required visibleFraction, required id, required type}) {});
// Act
await tester.pumpWidget(createDietsTestWidget(widget));
await tester.pump();
// Assert
expect(find.byType(Diets), findsOneWidget);
expect(find.byType(RefreshIndicator), findsOneWidget);
expect(find.byType(ListView), findsOneWidget);
});
await tester.pump(const Duration(milliseconds: 600));
});
testWidgets('should display Diets widget with error state', (WidgetTester tester) async {
// Arrange
dietsCubit.emit(DietsState.initial().copyWith(isLoading: false, errorMessage: 'Error message'));
final widget = Diets(handleVisibility: ({required visibleFraction, required id, required type}) {});
// Act
await tester.pumpWidget(createDietsTestWidget(widget));
await tester.pump();
// Assert
expect(find.byType(Diets), findsOneWidget);
});
testWidgets('should display Diets widget with multiple diets', (WidgetTester tester) async {
await mockNetworkImagesFor(() async {
// Arrange
final testDiet2 = testDiet.copyWith(id: 2, name: 'Test Diet 2');
dietsCubit.emit(DietsState.initial().copyWith(dietsList: [testDiet, testDiet2], isLoading: false));
final widget = Diets(handleVisibility: ({required visibleFraction, required id, required type}) {});
// Act
await tester.pumpWidget(createDietsTestWidget(widget));
await tester.pump();
// Assert
expect(find.byType(Diets), findsOneWidget);
expect(find.byType(ListView), findsOneWidget);
});
await tester.pump(const Duration(milliseconds: 600));
});
});
}

View File

@@ -0,0 +1,242 @@
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/diets/presentation/widgets/author_card.dart';
import 'package:eatvio_app/features/diets/presentation/widgets/expandable_text.dart';
import 'package:eatvio_app/features/diets/presentation/widgets/info_bouble.dart';
import 'package:eatvio_app/features/diets/presentation/widgets/type_badges.dart';
import 'package:eatvio_app/features/feed/models/diets_pagination_model.dart';
import 'package:eatvio_app/features/feed/models/plan_pagination_model.dart';
import 'package:eatvio_app/generated/l10n.dart';
import 'package:flutter/material.dart';
import 'package:flutter_localizations/flutter_localizations.dart';
import 'package:flutter_portal/flutter_portal.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:flutter_dotenv/flutter_dotenv.dart';
import 'package:network_image_mock/network_image_mock.dart';
void main() {
group('Diets Widgets Tests', () {
setUp(() async {
await sl.reset();
dotenv.loadFromString(envString: 'DEV_BASE_URL=http://localhost\nBASE_URL=http://localhost');
sl.registerLazySingleton<AppConfig>(() => AppConfig(buildType: AppBuildType.tests)..setBaseUrl('http://localhost'));
});
Widget createTestWidget(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: SingleChildScrollView(child: child)),
),
);
}
group('InfoBouble', () {
testWidgets('should display InfoBouble with children', (WidgetTester tester) async {
// Arrange
const widget = InfoBouble(children: [Text('Test Text')]);
// Act
await tester.pumpWidget(createTestWidget(widget));
await tester.pump();
// Assert
expect(find.byType(InfoBouble), findsOneWidget);
expect(find.text('Test Text'), findsOneWidget);
});
testWidgets('should display InfoBouble with multiple children', (WidgetTester tester) async {
// Arrange
const widget = InfoBouble(children: [Text('First'), Text('Second')]);
// Act
await tester.pumpWidget(createTestWidget(widget));
await tester.pump();
// Assert
expect(find.byType(InfoBouble), findsOneWidget);
expect(find.text('First'), findsOneWidget);
expect(find.text('Second'), findsOneWidget);
});
testWidgets('should display InfoBouble with custom radius', (WidgetTester tester) async {
// Arrange
const widget = InfoBouble(radius: 20, children: [Text('Test')]);
// Act
await tester.pumpWidget(createTestWidget(widget));
await tester.pump();
// Assert
expect(find.byType(InfoBouble), findsOneWidget);
expect(find.text('Test'), findsOneWidget);
});
});
group('ExpandableText', () {
testWidgets('should display ExpandableText with short text', (WidgetTester tester) async {
// Arrange
const widget = ExpandableText(text: 'Short text', maxLines: 5);
// Act
await tester.pumpWidget(createTestWidget(widget));
await tester.pump();
await tester.pump(const Duration(milliseconds: 100));
// Assert
expect(find.byType(ExpandableText), findsOneWidget);
expect(find.text('Short text'), findsOneWidget);
});
testWidgets('should display ExpandableText with long text', (WidgetTester tester) async {
// Arrange
const longText =
'This is a very long text that should be truncated and show an expand button when it exceeds the maximum number of lines specified in the widget. This text should be long enough to trigger the expand functionality.';
const widget = ExpandableText(text: longText, maxLines: 3);
// Act
await tester.pumpWidget(createTestWidget(widget));
await tester.pump();
await tester.pump(const Duration(milliseconds: 200));
// Assert
expect(find.byType(ExpandableText), findsOneWidget);
});
testWidgets('should toggle expansion when expand button is tapped', (WidgetTester tester) async {
// Arrange
const longText =
'This is a very long text that should be truncated and show an expand button when it exceeds the maximum number of lines specified in the widget. This text should be long enough to trigger the expand functionality.';
const widget = ExpandableText(text: longText, maxLines: 3);
// Act
await tester.pumpWidget(createTestWidget(widget));
await tester.pump();
await tester.pump(const Duration(milliseconds: 200));
// Try to find and tap expand button if it exists
final expandButtons = find.text('Развернуть');
if (expandButtons.evaluate().isNotEmpty) {
await tester.tap(expandButtons);
await tester.pump();
await tester.pump(const Duration(milliseconds: 200));
}
// Assert
expect(find.byType(ExpandableText), findsOneWidget);
});
});
group('AuthorCard', () {
testWidgets('should display AuthorCard with author information', (WidgetTester tester) async {
await mockNetworkImagesFor(() async {
// Arrange
const author = Author(authorSlug: 'test-author', authorName: 'Test Author', authorProfileIcon: 'https://example.com/icon.jpg', authorIsMe: false);
const widget = AuthorCard(author: author, title: 'Автор');
// Act
await tester.pumpWidget(createTestWidget(widget));
await tester.pump();
await tester.pump(const Duration(milliseconds: 100));
// Assert
expect(find.byType(AuthorCard), findsOneWidget);
expect(find.text('Test Author'), findsOneWidget);
expect(find.text('Автор'), findsOneWidget);
});
await tester.pump(const Duration(milliseconds: 600));
});
testWidgets('should display AuthorCard with rating when available', (WidgetTester tester) async {
await mockNetworkImagesFor(() async {
// Arrange
const author = Author(
authorSlug: 'test-author',
authorName: 'Test Author',
authorProfileIcon: 'https://example.com/icon.jpg',
authorIsMe: false,
authorPlaceInRating: 5,
);
const widget = AuthorCard(author: author, title: 'Автор');
// Act
await tester.pumpWidget(createTestWidget(widget));
await tester.pump();
await tester.pump(const Duration(milliseconds: 100));
// Assert
expect(find.byType(AuthorCard), findsOneWidget);
expect(find.text('Test Author'), findsOneWidget);
});
await tester.pump(const Duration(milliseconds: 600));
});
});
group('TypeBadges', () {
testWidgets('should display TypeBadges with diet type', (WidgetTester tester) async {
// Arrange
const dietType = DietType(light: true, veg: false, carb: false, meat: false);
const widget = TypeBadges(type: dietType, disableDescriptionOfTypes: false);
// Act
await tester.pumpWidget(createTestWidget(widget));
await tester.pump();
await tester.pump(const Duration(milliseconds: 100));
// Assert
expect(find.byType(TypeBadges), findsOneWidget);
});
testWidgets('should display TypeBadges with plan type', (WidgetTester tester) async {
// Arrange
const planType = PlanType(fit: true, slim: false, behavior: false, wellness: false);
const widget = TypeBadges(planType: planType, disableDescriptionOfTypes: false);
// Act
await tester.pumpWidget(createTestWidget(widget));
await tester.pump();
await tester.pump(const Duration(milliseconds: 100));
// Assert
expect(find.byType(TypeBadges), findsOneWidget);
});
testWidgets('should display TypeBadges with multiple types', (WidgetTester tester) async {
// Arrange
const dietType = DietType(light: true, veg: true, carb: false, meat: false);
const widget = TypeBadges(type: dietType, disableDescriptionOfTypes: false);
// Act
await tester.pumpWidget(createTestWidget(widget));
await tester.pump();
await tester.pump(const Duration(milliseconds: 100));
// Assert
expect(find.byType(TypeBadges), findsOneWidget);
});
testWidgets('should not show overlay when disableDescriptionOfTypes is true', (WidgetTester tester) async {
// Arrange
const dietType = DietType(light: true, veg: false, carb: false, meat: false);
const widget = TypeBadges(type: dietType, disableDescriptionOfTypes: true);
// Act
await tester.pumpWidget(createTestWidget(widget));
await tester.pump();
await tester.pump(const Duration(milliseconds: 100));
// Assert
expect(find.byType(TypeBadges), findsOneWidget);
});
});
});
}