import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:google_fonts/google_fonts.dart'; import 'package:new_budget/l10n/app_localizations.dart'; import 'package:new_budget/src/app/theme/app_theme.dart'; import 'package:new_budget/src/core/database/converters/enum_converters.dart'; import 'package:new_budget/src/features/accounts/application/accounts_controller.dart'; import 'package:new_budget/src/features/accounts/domain/entities/account.dart'; import 'package:new_budget/src/features/analytics/application/habit_analysis_providers.dart'; import 'package:new_budget/src/features/analytics/presentation/screens/habit_analysis_screen.dart'; import 'package:new_budget/src/features/categories/application/categories_controller.dart'; import 'package:new_budget/src/features/categories/domain/entities/category.dart'; import 'package:new_budget/src/features/transactions/domain/entities/transaction.dart'; import 'package:new_budget/src/features/user/application/active_user_controller.dart'; import 'package:new_budget/src/features/user/domain/entities/user.dart'; // ─── Fakes / данные ────────────────────────────────────────────────────────── const _userId = 'test-uid'; class FakeActiveUserController extends ActiveUserController { @override Future build() async => User(id: _userId, name: 'Test', createdAt: DateTime(2024)); } Transaction _tx( String id, int amount, { SpendingObligation? obligation, SpendingImpulse? impulse, }) => Transaction( id: id, userId: _userId, accountId: 'a', type: TransactionType.expense, amount: amount, date: DateTime(2026, 6, 10), obligation: obligation, impulse: impulse, createdAt: DateTime(2026, 6, 10), ); final _txs = [ _tx('1', 1000, obligation: SpendingObligation.required), _tx('2', 2000, obligation: SpendingObligation.unnecessary, impulse: SpendingImpulse.impulsive), _tx('3', 500, obligation: SpendingObligation.optional, impulse: SpendingImpulse.considered), ]; Widget _buildScreen() => ProviderScope( overrides: [ activeUserControllerProvider .overrideWith(() => FakeActiveUserController()), habitMonthTransactionsProvider(_userId).overrideWithValue(_txs), categoriesStreamProvider(_userId) .overrideWith((ref) => Stream>.value(const [])), accountsStreamProvider(_userId) .overrideWith((ref) => Stream>.value(const [])), ], child: MaterialApp( theme: AppTheme.light(), localizationsDelegates: AppLocalizations.localizationsDelegates, supportedLocales: AppLocalizations.supportedLocales, home: const HabitAnalysisScreen(), ), ); ProviderContainer _container(WidgetTester tester) => ProviderScope.containerOf(tester.element(find.byType(HabitAnalysisScreen))); /// Сегмент «Impulse» в строке фильтров (первый — пилюли в списке ниже). Finder _impulseSegment() => find.text('Impulse').first; // ─── Tests ─────────────────────────────────────────────────────────────────── void main() { setUpAll(() { GoogleFonts.config.allowRuntimeFetching = false; }); testWidgets('по умолчанию: оба «All» активны, чип «Required» виден', (tester) async { await tester.pumpWidget(_buildScreen()); await tester.pump(); // activeUserControllerProvider разрешается // «All» в обеих строках фильтров. expect(find.text('All'), findsNWidgets(2)); // Чип обязательности + пилюля tx1 в списке. expect(find.text('Required'), findsNWidgets(2)); }); testWidgets('выбор «Impulse» скрывает чип «Required»', (tester) async { await tester.pumpWidget(_buildScreen()); await tester.pump(); await tester.tap(_impulseSegment()); // AnimatedSize — доигрываем анимацию. await tester.pumpAndSettle(); // Чип скрыт, tx1 отфильтрована — «Required» нет нигде. expect(find.text('Required'), findsNothing); // Возврат на «All» импульсивности возвращает чип. await tester.tap(find.text('All').first); await tester.pumpAndSettle(); expect(find.text('Required'), findsNWidgets(2)); }); testWidgets('выбор «Impulse» сбрасывает выбранную «Required» (каскад)', (tester) async { await tester.pumpWidget(_buildScreen()); await tester.pump(); await tester.tap(find.text('Required').first); await tester.pumpAndSettle(); final container = _container(tester); expect( container.read(habitObligationFilterProvider), SpendingObligation.required); await tester.tap(_impulseSegment()); await tester.pumpAndSettle(); expect(container.read(habitObligationFilterProvider), isNull); }); testWidgets('повторный тап по чипу обязательности снимает фильтр', (tester) async { await tester.pumpWidget(_buildScreen()); await tester.pump(); await tester.tap(find.text('Unnecessary').first); await tester.pumpAndSettle(); final container = _container(tester); expect(container.read(habitObligationFilterProvider), SpendingObligation.unnecessary); await tester.tap(find.text('Unnecessary').first); await tester.pumpAndSettle(); expect(container.read(habitObligationFilterProvider), isNull); }); }