import 'package:flutter/material.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:go_router/go_router.dart'; import 'package:motorcycle_dash/core/theme/theme.dart'; import 'package:motorcycle_dash/core/widgets/main_layout.dart'; void main() { TestWidgetsFlutterBinding.ensureInitialized(); Widget buildTestRouter({ required Size surfaceSize, ThemeData? theme, int initialIndex = 0, void Function(int)? onIndexChanged, }) { final router = GoRouter( initialLocation: switch (initialIndex) { 1 => '/map', 2 => '/activity', 3 => '/garage', 4 => '/social', 5 => '/settings', _ => '/', }, routes: [ StatefulShellRoute.indexedStack( builder: (context, state, navigationShell) { return MainLayout(navigationShell: navigationShell); }, branches: [ StatefulShellBranch( routes: [ GoRoute( path: '/', builder: (context, state) => const Scaffold( body: Center(child: Text('Dash Screen Content')), ), ), ], ), StatefulShellBranch( routes: [ GoRoute( path: '/map', builder: (context, state) => const Scaffold( body: Center(child: Text('Map Screen Content')), ), ), ], ), StatefulShellBranch( routes: [ GoRoute( path: '/activity', builder: (context, state) => const Scaffold( body: Center(child: Text('Activity Screen Content')), ), ), ], ), StatefulShellBranch( routes: [ GoRoute( path: '/garage', builder: (context, state) => const Scaffold( body: Center(child: Text('Garage Screen Content')), ), ), ], ), StatefulShellBranch( routes: [ GoRoute( path: '/social', builder: (context, state) => const Scaffold( body: Center(child: Text('Social Screen Content')), ), ), ], ), StatefulShellBranch( routes: [ GoRoute( path: '/settings', builder: (context, state) => const Scaffold( body: Center(child: Text('Settings Screen Content')), ), ), ], ), ], ), ], ); return ProviderScope( child: MaterialApp.router( theme: theme ?? AppTheme.darkTheme, routerConfig: router, ), ); } group('MainLayout Viewport Matrix Tests', () { const testViewports = { '320x640 (Narrow Portrait)': Size(320, 640), '400x800 (Standard Portrait)': Size(400, 800), '412x915 (Pixel 8a Portrait)': Size(412, 915), '840x390 (Short Landscape)': Size(840, 390), '780x360 (Short Landscape)': Size(780, 360), '800x360 (Motorcycle Cockpit Mount)': Size(800, 360), '600x900 (Medium Tablet)': Size(600, 900), '840x600 (Medium Landscape)': Size(840, 600), '1024x768 (Expanded Tablet)': Size(1024, 768), }; for (final entry in testViewports.entries) { testWidgets('Renders cleanly without overflow on ${entry.key}', (tester) async { tester.view.physicalSize = entry.value; tester.view.devicePixelRatio = 1.0; addTearDown(tester.view.resetPhysicalSize); addTearDown(tester.view.resetDevicePixelRatio); await tester.pumpWidget(buildTestRouter(surfaceSize: entry.value)); await tester.pumpAndSettle(); expect(tester.takeException(), isNull, reason: 'No RenderFlex overflow on ${entry.key}'); expect(find.text('Dash Screen Content'), findsOneWidget); // Verify all 6 navigation labels exist regardless of layout expect(find.text('Dash'), findsOneWidget); expect(find.text('Map'), findsOneWidget); expect(find.text('Activity'), findsOneWidget); expect(find.text('Garage'), findsOneWidget); expect(find.text('Social'), findsOneWidget); expect(find.text('Settings'), findsOneWidget); }); } }); group('MainLayout Navigation Presentation Adaptation', () { testWidgets('Presents BottomNavigationBar on compact portrait (400x800)', (tester) async { tester.view.physicalSize = const Size(400, 800); tester.view.devicePixelRatio = 1.0; addTearDown(tester.view.resetPhysicalSize); addTearDown(tester.view.resetDevicePixelRatio); await tester.pumpWidget(buildTestRouter(surfaceSize: const Size(400, 800))); await tester.pumpAndSettle(); expect(find.byType(BottomNavigationBar), findsOneWidget); expect(find.byType(NavigationRail), findsNothing); expect(find.byKey(const ValueKey('nav_dash_icon')), findsOneWidget); expect(find.byKey(const ValueKey('nav_map_icon')), findsOneWidget); }); testWidgets('Presents NavigationRail on short landscape phone mount (800x360)', (tester) async { tester.view.physicalSize = const Size(800, 360); tester.view.devicePixelRatio = 1.0; addTearDown(tester.view.resetPhysicalSize); addTearDown(tester.view.resetDevicePixelRatio); await tester.pumpWidget(buildTestRouter(surfaceSize: const Size(800, 360))); await tester.pumpAndSettle(); expect(find.byType(NavigationRail), findsOneWidget); expect(find.byType(BottomNavigationBar), findsNothing); expect(find.byKey(const ValueKey('rail_dash_icon')), findsOneWidget); expect(find.byKey(const ValueKey('rail_map_icon')), findsOneWidget); }); testWidgets('Presents NavigationRail on medium width (600x900)', (tester) async { tester.view.physicalSize = const Size(600, 900); tester.view.devicePixelRatio = 1.0; addTearDown(tester.view.resetPhysicalSize); addTearDown(tester.view.resetDevicePixelRatio); await tester.pumpWidget(buildTestRouter(surfaceSize: const Size(600, 900))); await tester.pumpAndSettle(); expect(find.byType(NavigationRail), findsOneWidget); expect(find.byType(BottomNavigationBar), findsNothing); }); }); group('MainLayout Navigation Interactions & Touch Targets', () { testWidgets('Tapping destinations switches routes cleanly on BottomNavigationBar', (tester) async { tester.view.physicalSize = const Size(400, 800); tester.view.devicePixelRatio = 1.0; addTearDown(tester.view.resetPhysicalSize); addTearDown(tester.view.resetDevicePixelRatio); await tester.pumpWidget(buildTestRouter(surfaceSize: const Size(400, 800))); await tester.pumpAndSettle(); expect(find.text('Dash Screen Content'), findsOneWidget); await tester.tap(find.text('Garage')); await tester.pumpAndSettle(); expect(find.text('Garage Screen Content'), findsOneWidget); await tester.tap(find.text('Settings')); await tester.pumpAndSettle(); expect(find.text('Settings Screen Content'), findsOneWidget); }); testWidgets('Tapping destinations switches routes cleanly on NavigationRail', (tester) async { tester.view.physicalSize = const Size(800, 360); tester.view.devicePixelRatio = 1.0; addTearDown(tester.view.resetPhysicalSize); addTearDown(tester.view.resetDevicePixelRatio); await tester.pumpWidget(buildTestRouter(surfaceSize: const Size(800, 360))); await tester.pumpAndSettle(); expect(find.text('Dash Screen Content'), findsOneWidget); await tester.tap(find.text('Map')); await tester.pumpAndSettle(); expect(find.text('Map Screen Content'), findsOneWidget); await tester.tap(find.text('Activity')); await tester.pumpAndSettle(); expect(find.text('Activity Screen Content'), findsOneWidget); }); testWidgets('NavigationRail touch targets have adequate interactive hit bounds', (tester) async { tester.view.physicalSize = const Size(800, 360); tester.view.devicePixelRatio = 1.0; addTearDown(tester.view.resetPhysicalSize); addTearDown(tester.view.resetDevicePixelRatio); await tester.pumpWidget(buildTestRouter(surfaceSize: const Size(800, 360))); await tester.pumpAndSettle(); final railFinder = find.byType(NavigationRail); expect(railFinder, findsOneWidget); final railSize = tester.getSize(railFinder); expect(railSize.width, greaterThanOrEqualTo(48.0)); }); }); group('MainLayout State & Theme Preservation Across Resize', () { testWidgets('Preserves selected route and active content across viewport resize', (tester) async { // Start in portrait (400x800) tester.view.physicalSize = const Size(400, 800); tester.view.devicePixelRatio = 1.0; addTearDown(tester.view.resetPhysicalSize); addTearDown(tester.view.resetDevicePixelRatio); await tester.pumpWidget(buildTestRouter(surfaceSize: const Size(400, 800))); await tester.pumpAndSettle(); // Navigate to Activity await tester.tap(find.text('Activity')); await tester.pumpAndSettle(); expect(find.text('Activity Screen Content'), findsOneWidget); // Rotate / Resize to short landscape (800x360) tester.view.physicalSize = const Size(800, 360); await tester.pumpAndSettle(); // Shell adapts to NavigationRail, while maintaining Activity selection! expect(find.byType(NavigationRail), findsOneWidget); expect(find.text('Activity Screen Content'), findsOneWidget); // Resize to large tablet (1024x768) tester.view.physicalSize = const Size(1024, 768); await tester.pumpAndSettle(); expect(find.text('Activity Screen Content'), findsOneWidget); // Resize back to portrait (320x640) tester.view.physicalSize = const Size(320, 640); await tester.pumpAndSettle(); expect(find.byType(BottomNavigationBar), findsOneWidget); expect(find.text('Activity Screen Content'), findsOneWidget); }); testWidgets('Renders cleanly under Light Theme without visual regressions', (tester) async { tester.view.physicalSize = const Size(400, 800); tester.view.devicePixelRatio = 1.0; addTearDown(tester.view.resetPhysicalSize); addTearDown(tester.view.resetDevicePixelRatio); await tester.pumpWidget( buildTestRouter( surfaceSize: const Size(400, 800), theme: AppTheme.lightTheme, ), ); await tester.pumpAndSettle(); expect(tester.takeException(), isNull); expect(find.text('Dash Screen Content'), findsOneWidget); }); }); }