import 'package:flutter/services.dart'; import 'package:flutter/widgets.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:go_router/go_router.dart'; import 'package:gopeed/app/router/mobile_exit_guard.dart'; import 'package:gopeed/shared/navigation/app_exit_confirmation_controller.dart'; import 'package:gopeed/shared/theme/app_theme.dart'; import 'package:gopeed/shared/widgets/app_toast.dart'; import 'package:shadcn_flutter/shadcn_flutter.dart' as shad; void main() { late List exits; late GoRouter router; setUp(() { exits = []; TestDefaultBinaryMessengerBinding.instance.defaultBinaryMessenger.setMockMethodCallHandler( SystemChannels.platform, (call) async { if (call.method == 'SystemNavigator.pop') exits.add(call); return null; }, ); router = GoRouter( routes: [ ShellRoute( builder: (_, _, child) => child, routes: [ GoRoute( path: '/', builder: (_, _) => const MobileExitGuard(child: SizedBox.expand()), routes: [ GoRoute(path: 'details', builder: (_, _) => const SizedBox.expand()), for (final path in ['extensions', 'settings']) GoRoute(path: path, builder: (_, _) => const SizedBox.expand()), ], ), ], ), ], ); }); tearDown(() { router.dispose(); TestDefaultBinaryMessengerBinding.instance.defaultBinaryMessenger.setMockMethodCallHandler( SystemChannels.platform, null, ); }); Future mount(WidgetTester tester) async { await tester.pumpWidget( shad.ShadcnApp.router(theme: AppTheme.dark(), materialTheme: AppTheme.materialDark(), routerConfig: router), ); await tester.pumpAndSettle(); } testWidgets('root back shows centered prompt, second back exits', (tester) async { await mount(tester); await tester.binding.handlePopRoute(); await tester.pump(); expect(exits, isEmpty); expect(find.byType(AppToastContent), findsOneWidget); expect(tester.getCenter(find.byType(AppToastContent)), const Offset(400, 300)); await tester.pump(const Duration(milliseconds: 1900)); await tester.binding.handlePopRoute(); await tester.pump(); expect(exits, hasLength(1)); expect(find.byType(AppToastContent), findsNothing); }, variant: TargetPlatformVariant.only(TargetPlatform.android)); testWidgets('expired prompt requires a fresh pair of back actions', (tester) async { await mount(tester); await tester.binding.handlePopRoute(); await tester.pump(); await tester.pump(AppExitConfirmationController.confirmationWindow); expect(find.byType(AppToastContent), findsNothing); await tester.binding.handlePopRoute(); await tester.pump(); expect(exits, isEmpty); expect(find.byType(AppToastContent), findsOneWidget); await tester.pumpWidget(const SizedBox()); }, variant: TargetPlatformVariant.only(TargetPlatform.android)); testWidgets('nested page pops without prompting or exiting', (tester) async { await mount(tester); router.push('/details'); await tester.pumpAndSettle(); await tester.binding.handlePopRoute(); await tester.pumpAndSettle(); expect(router.routeInformationProvider.value.uri.path, '/'); expect(find.byType(AppToastContent), findsNothing); expect(exits, isEmpty); }, variant: TargetPlatformVariant.only(TargetPlatform.android)); testWidgets('backgrounding clears the exit confirmation', (tester) async { await mount(tester); await tester.binding.handlePopRoute(); await tester.pump(); tester.binding.handleAppLifecycleStateChanged(AppLifecycleState.inactive); tester.binding.handleAppLifecycleStateChanged(AppLifecycleState.resumed); await tester.pump(); expect(find.byType(AppToastContent), findsNothing); await tester.binding.handlePopRoute(); await tester.pump(); expect(exits, isEmpty); expect(find.byType(AppToastContent), findsOneWidget); await tester.pumpWidget(const SizedBox()); }, variant: TargetPlatformVariant.only(TargetPlatform.android)); for (final path in ['/extensions', '/settings']) { testWidgets('$path returns to tasks before confirming exit', (tester) async { await mount(tester); router.go(path); await tester.pumpAndSettle(); expect(router.canPop(), isTrue); await tester.binding.handlePopRoute(); await tester.pumpAndSettle(); expect(router.routeInformationProvider.value.uri.path, '/'); expect(find.byType(AppToastContent), findsNothing); expect(exits, isEmpty); await tester.binding.handlePopRoute(); await tester.pump(); expect(find.byType(AppToastContent), findsOneWidget); expect(exits, isEmpty); await tester.binding.handlePopRoute(); await tester.pump(); expect(exits, hasLength(1)); }, variant: TargetPlatformVariant.only(TargetPlatform.android)); } }