import 'package:flutter/cupertino.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:please_pay_me/app/app.dart'; import 'package:please_pay_me/core/config/app_config.dart'; import 'package:please_pay_me/core/storage/session_storage.dart'; import 'package:please_pay_me/data/demo/demo_backend.dart'; import 'package:please_pay_me/features/auth/session_controller.dart'; import 'package:please_pay_me/theme/theme.dart'; import 'support/test_setup.dart'; void main() { setUpAll(setUpTestEnvironment); SessionController demoSession({bool seeded = true}) { return SessionController( config: AppConfig.demo, storage: InMemorySessionStorage(), demoBackend: seeded ? DemoBackend() : DemoBackend.empty(), ); } /// Skeletons pulse forever, so `pumpAndSettle` would time out. Future settle(WidgetTester tester) async { await tester.pump(); await tester.pump(const Duration(milliseconds: 400)); } testWidgets('restoring session shows the branded splash', (tester) async { final session = demoSession(); await tester.pumpWidget(PleasePayMeApp(session: session)); await tester.pump(); expect(find.text('Дожить до ЗП'), findsOneWidget); expect(find.text('Бюджет от зарплаты до зарплаты'), findsOneWidget); expect(find.byType(CupertinoActivityIndicator), findsOneWidget); }); testWidgets('signed-out app shows the login screen', (tester) async { final session = demoSession(); await session.restore(); await tester.pumpWidget(PleasePayMeApp(session: session)); await settle(tester); expect(find.text('Дожить до ЗП'), findsOneWidget); expect(find.text('Войти через Яндекс'), findsOneWidget); }); testWidgets('demo session opens the overview with the current envelope', (tester) async { final session = demoSession()..startDemo(); await tester.pumpWidget(PleasePayMeApp(session: session)); await settle(tester); expect(find.text('Обзор'), findsWidgets); expect(find.text('До аванса'), findsOneWidget); expect(find.text('Остаток бюджета'), findsOneWidget); await tester.drag(find.byType(CustomScrollView).first, const Offset(0, -500)); await settle(tester); // Grouped headers are uppercased by `AppSectionHeader`, as in iOS Settings. expect(find.text('БЛИЖАЙШАЯ ВЫПЛАТА'), findsOneWidget); expect(tester.takeException(), isNull); }); testWidgets('tabs switch between the main sections', (tester) async { final session = demoSession()..startDemo(); await tester.pumpWidget(PleasePayMeApp(session: session)); await settle(tester); for (final tab in ['Журнал', 'Бюджеты', 'Работа', 'Профиль']) { await tester.tap(find.text(tab).last); await settle(tester); expect(tester.takeException(), isNull, reason: 'tab $tab failed to render'); } expect(find.text('Демо'), findsOneWidget); }); testWidgets('profile theme switcher applies dark appearance', (tester) async { final session = demoSession()..startDemo(); final theme = ThemeController(store: MemoryThemeStore()); await tester.pumpWidget(PleasePayMeApp(session: session, theme: theme)); await settle(tester); await tester.tap(find.text('Профиль').last); await settle(tester); expect(find.text('Системная'), findsOneWidget); expect(find.text('Светлая'), findsOneWidget); await tester.tap(find.text('Тёмная')); await settle(tester); expect(theme.preference, ThemePreference.dark); expect(theme.resolve(Brightness.light), Brightness.dark); }); testWidgets('empty demo backend renders the empty states', (tester) async { final session = demoSession(seeded: false)..startDemo(); await tester.pumpWidget(PleasePayMeApp(session: session)); await settle(tester); expect(find.text('Бюджета пока нет'), findsOneWidget); expect(find.text('Создать бюджет'), findsOneWidget); }); testWidgets('recording an expense updates the remaining amount', (tester) async { final session = demoSession()..startDemo(); await tester.pumpWidget(PleasePayMeApp(session: session)); await settle(tester); await tester.tap(find.text('Добавить трату')); await settle(tester); expect(find.text('Новая трата'), findsOneWidget); await tester.enterText(find.byType(CupertinoTextField).first, '1000'); await settle(tester); await tester.tap(find.text('Записать трату')); await tester.pump(); await tester.pump(const Duration(milliseconds: 500)); expect(find.text('Новая трата'), findsNothing); expect(find.text('Трата записана'), findsOneWidget); // Let the toast auto-dismiss so no timers outlive the test. await tester.pump(const Duration(seconds: 3)); expect(tester.takeException(), isNull); }); }