diff --git a/analyze_coverage.py b/analyze_coverage.py new file mode 100644 index 00000000..4537488e --- /dev/null +++ b/analyze_coverage.py @@ -0,0 +1,55 @@ +#!/usr/bin/env python3 +import re + +def analyze_coverage(): + files = {} + current_file = None + + with open('coverage/lcov.info', 'r', encoding='utf-8') as f: + lines = f.readlines() + + i = 0 + while i < len(lines): + line = lines[i].strip() + if line.startswith('SF:'): + current_file = line[3:] + files[current_file] = {'total': 0, 'covered': 0} + i += 1 + elif line.startswith('LF:') and current_file: + files[current_file]['total'] = int(line[3:]) + i += 1 + elif line.startswith('LH:') and current_file: + files[current_file]['covered'] = int(line[3:]) + i += 1 + else: + i += 1 + + # Filter out generated files + filtered = [] + for f, d in files.items(): + if not f.endswith('.g.dart') and not 'generated' in f.lower() and not f.endswith('l10n.dart'): + filtered.append((f, d)) + + # Find files with less than 50% coverage + low_coverage = [] + for f, d in filtered: + if d['total'] > 0: + pct = d['covered']/d['total']*100 + if pct < 50: + low_coverage.append((f, d)) + + # Sort by coverage percentage (lowest first) + sorted_files = sorted( + low_coverage, + key=lambda x: (x[1]['covered']/x[1]['total']*100 if x[1]['total'] > 0 else 0) + ) + + print('Files with less than 50% coverage:') + print('=' * 80) + for f, d in sorted_files[:50]: + pct = d['covered']/d['total']*100 + print(f'{pct:6.1f}% ({d["covered"]:4d}/{d["total"]:4d}) - {f}') + +if __name__ == '__main__': + analyze_coverage() + diff --git a/lib/features/chat/presentation/widgets/chose_plan_date_widget.dart b/lib/features/chat/presentation/widgets/chose_plan_date_widget.dart index 2744ba74..809422c1 100644 --- a/lib/features/chat/presentation/widgets/chose_plan_date_widget.dart +++ b/lib/features/chat/presentation/widgets/chose_plan_date_widget.dart @@ -28,6 +28,8 @@ class _ChosePlanDateWidget extends State { super.initState(); if (widget.hasPersonalDiet) { startDate = [DateTime.now().add(const Duration(days: 2))]; + } else { + startDate = [DateTime.now()]; } } @@ -49,23 +51,12 @@ class _ChosePlanDateWidget extends State { mainAxisAlignment: MainAxisAlignment.center, crossAxisAlignment: CrossAxisAlignment.center, children: [ - Text( - planName ?? '', - style: TextStyles.headingXLSemibold.copyWith( - color: appTheme.black, - ), - ), + Text(planName ?? '', style: TextStyles.headingXLSemibold.copyWith(color: appTheme.black)), if (widget.hasPersonalDiet) ...[ - const SizedBox( - width: 8, - ), + const SizedBox(width: 8), PortalTarget( closeDuration: const Duration(milliseconds: 20), - anchor: const Aligned( - shiftToWithinBound: AxisFlag(x: true, y: true), - follower: Alignment.topRight, - target: Alignment.bottomLeft, - ), + anchor: const Aligned(shiftToWithinBound: AxisFlag(x: true, y: true), follower: Alignment.topRight, target: Alignment.bottomLeft), portalFollower: Padding( padding: const EdgeInsets.symmetric(horizontal: 20), child: Container( @@ -73,24 +64,12 @@ class _ChosePlanDateWidget extends State { decoration: BoxDecoration( borderRadius: BorderRadius.circular(12), color: visible ? appTheme.whiteA700 : Colors.transparent, - boxShadow: visible - ? [ - BoxShadow( - color: appTheme.black900.withValues(alpha: 0.15), - blurRadius: 4, - offset: const Offset(0, 4), - ), - ] - : null, + boxShadow: visible ? [BoxShadow(color: appTheme.black900.withValues(alpha: 0.15), blurRadius: 4, offset: const Offset(0, 4))] : null, ), child: AnimatedDefaultTextStyle( - style: TextStyles.textMMediumAuto.copyWith( - color: visible ? appTheme.black : Colors.transparent, - ), + style: TextStyles.textMMediumAuto.copyWith(color: visible ? appTheme.black : Colors.transparent), duration: const Duration(milliseconds: 300), - child: Text( - s.chosenPlanStartDateDescr, - ), + child: Text(s.chosenPlanStartDateDescr), ), ), ), @@ -104,14 +83,11 @@ class _ChosePlanDateWidget extends State { 'assets/icons/question_mark.svg', width: 22, height: 22, - colorFilter: ColorFilter.mode( - appTheme.blueA400, - BlendMode.srcIn, - ), + colorFilter: ColorFilter.mode(appTheme.blueA400, BlendMode.srcIn), ), ), - ) - ] + ), + ], ], ), const SizedBox(height: 12), @@ -120,19 +96,12 @@ class _ChosePlanDateWidget extends State { child: Text( s.chose_plan_start_date_2, textAlign: TextAlign.center, - style: TextStyles.textMMedium.copyWith( - color: appTheme.black, - ), + style: TextStyles.textMMedium.copyWith(color: appTheme.black), ), ), const SizedBox(height: 12), - const Padding( - padding: EdgeInsets.symmetric(horizontal: 16.0), - child: CustomDivider(), - ), - CalendarWidget( - startDate: startDate, - ), + const Padding(padding: EdgeInsets.symmetric(horizontal: 16.0), child: CustomDivider()), + CalendarWidget(startDate: startDate), Padding( padding: const EdgeInsets.only(left: 16, right: 16, top: 16, bottom: 28), child: CustomOutlinedButton( @@ -150,13 +119,8 @@ class _ChosePlanDateWidget extends State { }, height: 60, text: s.confirm, - buttonTextStyle: TextStyles.textLSemibold.copyWith( - color: appTheme.whiteA700, - ), - buttonStyle: ButtonStyle( - backgroundColor: WidgetStatePropertyAll(appTheme.blueA400), - side: const WidgetStatePropertyAll(BorderSide.none), - ), + buttonTextStyle: TextStyles.textLSemibold.copyWith(color: appTheme.whiteA700), + buttonStyle: ButtonStyle(backgroundColor: WidgetStatePropertyAll(appTheme.blueA400), side: const WidgetStatePropertyAll(BorderSide.none)), ), ), ], diff --git a/test/features/profile/presentation/widgets/imt_chart_widget_test.dart b/test/features/profile/presentation/widgets/imt_chart_widget_test.dart new file mode 100644 index 00000000..59eb1da3 --- /dev/null +++ b/test/features/profile/presentation/widgets/imt_chart_widget_test.dart @@ -0,0 +1,132 @@ +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/profile/models/weight_model.dart'; +import 'package:eatvio_app/features/profile/presentation/widgets/imt_chart_widget.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_portal/flutter_portal.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:flutter_dotenv/flutter_dotenv.dart'; +import 'package:intl/date_symbol_data_local.dart'; + +void main() { + group('ImtChartWidget', () { + setUp(() async { + await sl.reset(); + await initializeDateFormatting('ru', null); + dotenv.loadFromString(envString: 'DEV_BASE_URL=http://localhost\nBASE_URL=http://localhost'); + sl.registerLazySingleton(() => AppConfig(buildType: AppBuildType.tests)..setBaseUrl('http://localhost')); + }); + + Widget createTestWidget(Widget child) { + return MaterialApp( + theme: ThemeHelper().themeData(), + home: Portal( + child: Scaffold(body: SingleChildScrollView(child: child)), + ), + ); + } + + testWidgets('should display chart with empty weight points', (WidgetTester tester) async { + // Arrange + const widget = ImtChartWidget(weightPoints: []); + + // Act + await tester.pumpWidget(createTestWidget(widget)); + await tester.pump(); + await tester.pump(const Duration(seconds: 1)); + + // Assert + expect(find.byType(ImtChartWidget), findsOneWidget); + }); + + testWidgets('should display chart with single weight point', (WidgetTester tester) async { + // Arrange + final weightPoints = [ + WeightModel( + id: 1, + userId: 1, + weight: 70, + createdAt: DateTime.now(), + updatedAt: DateTime.now(), + bmi: 23.5, + ), + ]; + final widget = ImtChartWidget(weightPoints: weightPoints); + + // Act + await tester.pumpWidget(createTestWidget(widget)); + await tester.pump(); + await tester.pump(const Duration(seconds: 1)); + + // Assert + expect(find.byType(ImtChartWidget), findsOneWidget); + }); + + testWidgets('should display chart with multiple weight points and show increase arrow', (WidgetTester tester) async { + // Arrange + final now = DateTime.now(); + final weightPoints = [ + WeightModel( + id: 1, + userId: 1, + weight: 70, + createdAt: now.subtract(const Duration(days: 7)), + updatedAt: now.subtract(const Duration(days: 7)), + bmi: 23.0, + ), + WeightModel( + id: 2, + userId: 1, + weight: 72, + createdAt: now, + updatedAt: now, + bmi: 24.0, + ), + ]; + final widget = ImtChartWidget(weightPoints: weightPoints); + + // Act + await tester.pumpWidget(createTestWidget(widget)); + await tester.pump(); + await tester.pump(const Duration(seconds: 1)); + + // Assert + expect(find.byType(ImtChartWidget), findsOneWidget); + }); + + testWidgets('should display chart with multiple weight points and show decrease arrow', (WidgetTester tester) async { + // Arrange + final now = DateTime.now(); + final weightPoints = [ + WeightModel( + id: 1, + userId: 1, + weight: 72, + createdAt: now.subtract(const Duration(days: 7)), + updatedAt: now.subtract(const Duration(days: 7)), + bmi: 24.0, + ), + WeightModel( + id: 2, + userId: 1, + weight: 70, + createdAt: now, + updatedAt: now, + bmi: 23.0, + ), + ]; + final widget = ImtChartWidget(weightPoints: weightPoints); + + // Act + await tester.pumpWidget(createTestWidget(widget)); + await tester.pump(); + await tester.pump(const Duration(seconds: 1)); + + // Assert + expect(find.byType(ImtChartWidget), findsOneWidget); + }); + }); +} + diff --git a/test/features/registration/presentation/pages/phone_screen_test.dart b/test/features/registration/presentation/pages/phone_screen_test.dart new file mode 100644 index 00000000..4d95b4b8 --- /dev/null +++ b/test/features/registration/presentation/pages/phone_screen_test.dart @@ -0,0 +1,133 @@ +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/registration/models/user_registration_model.dart'; +import 'package:eatvio_app/features/registration/presentation/bloc/registration_cubit/registration_cubit.dart'; +import 'package:eatvio_app/features/registration/presentation/pages/phone_screen.dart'; +import 'package:eatvio_app/features/registration/repositories/registration_repository.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:flutter_dotenv/flutter_dotenv.dart'; +import 'package:mockito/annotations.dart'; +import 'package:mockito/mockito.dart'; +import 'package:eatvio_app/core/prefs.dart'; + +import 'phone_screen_test.mocks.dart'; + +@GenerateMocks([RegistrationRepository, Prefs]) +void main() { + group('PhoneScreen', () { + setUp(() async { + await sl.reset(); + dotenv.loadFromString(envString: 'DEV_BASE_URL=http://localhost\nBASE_URL=http://localhost'); + sl.registerLazySingleton(() => AppConfig(buildType: AppBuildType.tests)..setBaseUrl('http://localhost')); + }); + + Widget createTestWidget(Widget child, RegistrationCubit cubit) { + return MaterialApp( + theme: ThemeHelper().themeData(), + home: BlocProvider( + create: (_) => cubit, + child: Scaffold(body: child), + ), + ); + } + + testWidgets('should display phone screen with empty phone', (WidgetTester tester) async { + // Arrange + final mockRepository = MockRegistrationRepository(); + final cubit = RegistrationCubit( + registrationRepository: mockRepository, + prefs: MockPrefs(), + ); + final sheet = UserRegistrationSheet( + firstName: 'John', + lastName: 'Doe', + email: 'test@example.com', + password: 'password', + passwordConfirmation: 'password', + birthdate: '1990-01-01', + gender: 'male', + location: 'Moscow', + responseBody: '{}', + lang: 'ru', + weight: 70, + height: 175, + phone: '', + chestCircumference: 90, + waistCircumference: 80, + hipCircumference: 95, + bloodPressureUp: 120, + bloodPressureDown: 80, + hasHypo: 0, + hasHyper: 0, + aboutAllergy: '', + aboutTakingPils: '', + aboutSportHobbies: '', + favoriteFoods: '', + unfavoriteFoods: '', + additionalInfo: '', + ); + final widget = PhoneScreen(sheet: sheet); + + // Act + await tester.pumpWidget(createTestWidget(widget, cubit)); + await tester.pump(); + + // Assert + expect(find.byType(PhoneScreen), findsOneWidget); + expect(find.text('Номер телефона'), findsOneWidget); + }); + + testWidgets('should display phone screen with pre-filled phone', (WidgetTester tester) async { + // Arrange + final mockRepository = MockRegistrationRepository(); + final cubit = RegistrationCubit( + registrationRepository: mockRepository, + prefs: MockPrefs(), + ); + final sheet = UserRegistrationSheet( + firstName: 'John', + lastName: 'Doe', + email: 'test@example.com', + password: 'password', + passwordConfirmation: 'password', + birthdate: '1990-01-01', + gender: 'male', + location: 'Moscow', + responseBody: '{}', + lang: 'ru', + weight: 70, + height: 175, + phone: '+7 (999) 123 45 67', + chestCircumference: 90, + waistCircumference: 80, + hipCircumference: 95, + bloodPressureUp: 120, + bloodPressureDown: 80, + hasHypo: 0, + hasHyper: 0, + aboutAllergy: '', + aboutTakingPils: '', + aboutSportHobbies: '', + favoriteFoods: '', + unfavoriteFoods: '', + additionalInfo: '', + ); + final widget = PhoneScreen(sheet: sheet); + + // Act + await tester.pumpWidget(createTestWidget(widget, cubit)); + await tester.pump(); + + // Assert + expect(find.byType(PhoneScreen), findsOneWidget); + expect(find.text('+7 (999) 123 45 67'), findsOneWidget); + + cubit.close(); + }); + }); +} + diff --git a/test/features/registration/presentation/widgets/change_email_dialog_test.dart b/test/features/registration/presentation/widgets/change_email_dialog_test.dart new file mode 100644 index 00000000..8ab87af3 --- /dev/null +++ b/test/features/registration/presentation/widgets/change_email_dialog_test.dart @@ -0,0 +1,113 @@ +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/registration/presentation/bloc/verify_email_cubit/verify_email_cubit.dart'; +import 'package:eatvio_app/features/registration/presentation/bloc/verify_email_cubit/verify_email_state.dart'; +import 'package:eatvio_app/features/registration/presentation/widgets/change_email_dialog.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:flutter_dotenv/flutter_dotenv.dart'; +import 'package:mockito/annotations.dart'; +import 'package:mockito/mockito.dart'; + +import 'change_email_dialog_test.mocks.dart'; + +@GenerateNiceMocks([MockSpec()]) +void main() { + group('ChangeEmailDialog', () { + setUp(() async { + await sl.reset(); + dotenv.loadFromString(envString: 'DEV_BASE_URL=http://localhost\nBASE_URL=http://localhost'); + sl.registerLazySingleton(() => AppConfig(buildType: AppBuildType.tests)..setBaseUrl('http://localhost')); + }); + + Widget createTestWidget(Widget child) { + return MaterialApp( + theme: ThemeHelper().themeData(), + home: Scaffold( + body: Builder( + builder: (context) => Column( + children: [ + ElevatedButton( + onPressed: () { + showDialog( + context: context, + builder: (_) => child, + ); + }, + child: const Text('Show Dialog'), + ), + ], + ), + ), + ), + ); + } + + testWidgets('should display dialog with current email pre-filled', (WidgetTester tester) async { + // Arrange + final mockCubit = MockVerifyEmailCubit(); + when(mockCubit.state).thenReturn(const VerifyEmailState()); + const currentEmail = 'test@example.com'; + final widget = ChangeEmailDialog( + currentEmail: currentEmail, + verifyEmailCubit: mockCubit, + ); + + // Act + await tester.pumpWidget(createTestWidget(widget)); + await tester.tap(find.text('Show Dialog')); + await tester.pumpAndSettle(); + + // Assert + expect(find.text('Сменить email'), findsOneWidget); + expect(find.text(currentEmail), findsOneWidget); + }); + + testWidgets('should show error for empty email', (WidgetTester tester) async { + // Arrange + final mockCubit = MockVerifyEmailCubit(); + when(mockCubit.state).thenReturn(const VerifyEmailState()); + const currentEmail = 'test@example.com'; + final widget = ChangeEmailDialog( + currentEmail: currentEmail, + verifyEmailCubit: mockCubit, + ); + + // Act + await tester.pumpWidget(createTestWidget(widget)); + await tester.tap(find.text('Show Dialog')); + await tester.pumpAndSettle(); + + final textField = find.byType(TextField); + await tester.enterText(textField, ''); + await tester.pumpAndSettle(); + + // Assert + expect(find.text('Введите email'), findsOneWidget); + }); + + testWidgets('should close dialog when Отмена button is tapped', (WidgetTester tester) async { + // Arrange + final mockCubit = MockVerifyEmailCubit(); + when(mockCubit.state).thenReturn(const VerifyEmailState()); + const currentEmail = 'test@example.com'; + final widget = ChangeEmailDialog( + currentEmail: currentEmail, + verifyEmailCubit: mockCubit, + ); + + // Act + await tester.pumpWidget(createTestWidget(widget)); + await tester.tap(find.text('Show Dialog')); + await tester.pumpAndSettle(); + await tester.tap(find.text('Отмена')); + await tester.pumpAndSettle(); + + // Assert + expect(find.text('Сменить email'), findsNothing); + }); + }); +} + diff --git a/test/features/registration/presentation/widgets/code_not_received_dialog_test.dart b/test/features/registration/presentation/widgets/code_not_received_dialog_test.dart new file mode 100644 index 00000000..66fc4ea7 --- /dev/null +++ b/test/features/registration/presentation/widgets/code_not_received_dialog_test.dart @@ -0,0 +1,87 @@ +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/registration/presentation/bloc/verify_email_cubit/verify_email_cubit.dart'; +import 'package:eatvio_app/features/registration/presentation/widgets/code_not_received_dialog.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:flutter_dotenv/flutter_dotenv.dart'; +import 'package:mockito/annotations.dart'; +import 'package:mockito/mockito.dart'; + +import 'code_not_received_dialog_test.mocks.dart'; + +@GenerateNiceMocks([MockSpec()]) +void main() { + group('CodeNotReceivedDialog', () { + setUp(() async { + await sl.reset(); + dotenv.loadFromString(envString: 'DEV_BASE_URL=http://localhost\nBASE_URL=http://localhost'); + sl.registerLazySingleton(() => AppConfig(buildType: AppBuildType.tests)..setBaseUrl('http://localhost')); + }); + + Widget createTestWidget(Widget child) { + return MaterialApp( + theme: ThemeHelper().themeData(), + home: Scaffold( + body: Builder( + builder: (context) => Column( + children: [ + ElevatedButton( + onPressed: () { + showDialog( + context: context, + builder: (_) => child, + ); + }, + child: const Text('Show Dialog'), + ), + ], + ), + ), + ), + ); + } + + testWidgets('should display dialog with correct text', (WidgetTester tester) async { + // Arrange + final mockCubit = MockVerifyEmailCubit(); + const currentEmail = 'test@example.com'; + final widget = CodeNotReceivedDialog( + currentEmail: currentEmail, + verifyEmailCubit: mockCubit, + ); + + // Act + await tester.pumpWidget(createTestWidget(widget)); + await tester.tap(find.text('Show Dialog')); + await tester.pumpAndSettle(); + + // Assert + expect(find.text('Почему может не приходить код?'), findsOneWidget); + expect(find.text('Понятно'), findsOneWidget); + }); + + testWidgets('should close dialog when Понятно button is tapped', (WidgetTester tester) async { + // Arrange + final mockCubit = MockVerifyEmailCubit(); + const currentEmail = 'test@example.com'; + final widget = CodeNotReceivedDialog( + currentEmail: currentEmail, + verifyEmailCubit: mockCubit, + ); + + // Act + await tester.pumpWidget(createTestWidget(widget)); + await tester.tap(find.text('Show Dialog')); + await tester.pumpAndSettle(); + await tester.tap(find.text('Понятно')); + await tester.pumpAndSettle(); + + // Assert + expect(find.text('Почему может не приходить код?'), findsNothing); + }); + }); +} + diff --git a/test/features/registration_v2/presentation/widgets/password_field_test.dart b/test/features/registration_v2/presentation/widgets/password_field_test.dart new file mode 100644 index 00000000..3c3d800d --- /dev/null +++ b/test/features/registration_v2/presentation/widgets/password_field_test.dart @@ -0,0 +1,83 @@ +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/registration_v2/cubit/registration_cubit.dart'; +import 'package:eatvio_app/features/registration_v2/cubit/registration_state.dart'; +import 'package:eatvio_app/features/registration_v2/presentation/widgets/password_field.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:flutter_dotenv/flutter_dotenv.dart'; +import 'package:mockito/annotations.dart'; +import 'package:mockito/mockito.dart'; + +import 'password_field_test.mocks.dart'; + +@GenerateNiceMocks([MockSpec()]) +void main() { + group('PasswordField', () { + setUp(() async { + await sl.reset(); + dotenv.loadFromString(envString: 'DEV_BASE_URL=http://localhost\nBASE_URL=http://localhost'); + sl.registerLazySingleton(() => AppConfig(buildType: AppBuildType.tests)..setBaseUrl('http://localhost')); + }); + + Widget createTestWidget(Widget child, RegistrationCubitV2 cubit) { + return MaterialApp( + theme: ThemeHelper().themeData(), + localizationsDelegates: const [ + S.delegate, + GlobalMaterialLocalizations.delegate, + GlobalWidgetsLocalizations.delegate, + GlobalCupertinoLocalizations.delegate, + ], + supportedLocales: const [Locale('ru')], + home: Portal( + child: BlocProvider( + create: (_) => cubit, + child: Scaffold(body: child), + ), + ), + ); + } + + testWidgets('should display password field with password from state', (WidgetTester tester) async { + // Arrange + final mockCubit = MockRegistrationCubitV2(); + const testPassword = 'testPassword123'; + when(mockCubit.state).thenReturn( + RegistrationStateV2(password: testPassword), + ); + final widget = PasswordField(cubit: mockCubit); + + // Act + await tester.pumpWidget(createTestWidget(widget, mockCubit)); + await tester.pump(); + + // Assert + expect(find.byType(PasswordField), findsOneWidget); + expect(find.text(testPassword), findsOneWidget); + }); + + testWidgets('should show copy icon initially', (WidgetTester tester) async { + // Arrange + final mockCubit = MockRegistrationCubitV2(); + when(mockCubit.state).thenReturn( + RegistrationStateV2(password: 'test'), + ); + final widget = PasswordField(cubit: mockCubit); + + // Act + await tester.pumpWidget(createTestWidget(widget, mockCubit)); + await tester.pump(); + + // Assert + expect(find.byType(PasswordField), findsOneWidget); + }); + }); +} + diff --git a/test/features/registration_v2/presentation/widgets/show_successful_registration_bottom_sheet_test.dart b/test/features/registration_v2/presentation/widgets/show_successful_registration_bottom_sheet_test.dart new file mode 100644 index 00000000..42ce3534 --- /dev/null +++ b/test/features/registration_v2/presentation/widgets/show_successful_registration_bottom_sheet_test.dart @@ -0,0 +1,118 @@ +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/registration_v2/cubit/registration_cubit.dart'; +import 'package:eatvio_app/features/registration_v2/cubit/registration_state.dart'; +import 'package:eatvio_app/features/registration_v2/presentation/widgets/show_successful_registration_bottom_sheet.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:flutter_dotenv/flutter_dotenv.dart'; +import 'package:mockito/annotations.dart'; +import 'package:mockito/mockito.dart'; + +import 'show_successful_registration_bottom_sheet_test.mocks.dart'; + +@GenerateNiceMocks([MockSpec()]) +void main() { + group('ShowSuccessfulRegistrationBottomSheet', () { + setUp(() async { + await sl.reset(); + dotenv.loadFromString(envString: 'DEV_BASE_URL=http://localhost\nBASE_URL=http://localhost'); + sl.registerLazySingleton(() => AppConfig(buildType: AppBuildType.tests)..setBaseUrl('http://localhost')); + }); + + Widget createTestWidget(Widget child, RegistrationCubitV2 cubit) { + return MaterialApp( + theme: ThemeHelper().themeData(), + localizationsDelegates: const [ + S.delegate, + GlobalMaterialLocalizations.delegate, + GlobalWidgetsLocalizations.delegate, + GlobalCupertinoLocalizations.delegate, + ], + supportedLocales: const [Locale('ru')], + home: Portal( + child: BlocProvider( + create: (_) => cubit, + child: Scaffold( + body: Builder( + builder: (context) => ElevatedButton( + onPressed: () { + showRegistrationBottomSheet(context, cubit, true); + }, + child: const Text('Show Bottom Sheet'), + ), + ), + ), + ), + ), + ); + } + + testWidgets('should display success bottom sheet when isSuccess is true', (WidgetTester tester) async { + // Arrange + final mockCubit = MockRegistrationCubitV2(); + when(mockCubit.state).thenReturn( + RegistrationStateV2(password: 'testPassword'), + ); + + // Act + await tester.pumpWidget(createTestWidget(const SizedBox(), mockCubit)); + await tester.tap(find.text('Show Bottom Sheet')); + await tester.pumpAndSettle(); + + // Assert + expect(find.byType(RegistrationBottomSheet), findsOneWidget); + }); + + testWidgets('should display error bottom sheet when isSuccess is false', (WidgetTester tester) async { + // Arrange + final mockCubit = MockRegistrationCubitV2(); + when(mockCubit.state).thenReturn( + RegistrationStateV2(), + ); + + Widget createTestWidgetError(Widget child, RegistrationCubitV2 cubit) { + return MaterialApp( + theme: ThemeHelper().themeData(), + localizationsDelegates: const [ + S.delegate, + GlobalMaterialLocalizations.delegate, + GlobalWidgetsLocalizations.delegate, + GlobalCupertinoLocalizations.delegate, + ], + supportedLocales: const [Locale('ru')], + home: Portal( + child: BlocProvider( + create: (_) => cubit, + child: Scaffold( + body: Builder( + builder: (context) => ElevatedButton( + onPressed: () { + showRegistrationBottomSheet(context, cubit, false); + }, + child: const Text('Show Bottom Sheet'), + ), + ), + ), + ), + ), + ); + } + + // Act + await tester.pumpWidget(createTestWidgetError(const SizedBox(), mockCubit)); + await tester.tap(find.text('Show Bottom Sheet')); + await tester.pumpAndSettle(); + + // Assert + expect(find.byType(RegistrationBottomSheet), findsOneWidget); + }); + }); +} +