import 'package:flutter/material.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:motorcycle_dash/core/theme/app_colors.dart'; import 'package:motorcycle_dash/core/theme/app_spacing.dart'; import 'package:motorcycle_dash/core/theme/app_typography.dart'; import 'package:motorcycle_dash/core/theme/theme.dart'; import 'package:motorcycle_dash/core/theme/theme_extensions.dart'; import 'package:motorcycle_dash/core/widgets/hybrid_card.dart'; import 'package:motorcycle_dash/core/widgets/hybrid_metric_tile.dart'; import 'package:motorcycle_dash/core/widgets/hybrid_riding_action_bar.dart'; import 'package:motorcycle_dash/core/widgets/hybrid_static_rec_badge.dart'; import 'package:motorcycle_dash/features/rides/ride_state_machine.dart'; /// Test double for [RideStateMachine] allowing inspection of invoked transitions. class _FakeRideStateMachine extends StateNotifier implements RideStateMachine { _FakeRideStateMachine([RideSession? initial]) : super( initial ?? RideSession( state: RideState.idle, startTime: DateTime.now(), ), ); bool startRideCalled = false; bool pauseRideCalled = false; bool resumeRideCalled = false; bool finishRideCalled = false; @override Future startRide() async { startRideCalled = true; state = RideSession(state: RideState.riding, startTime: DateTime.now()); } @override void pauseRide() { pauseRideCalled = true; state = RideSession(state: RideState.paused, startTime: DateTime.now()); } @override void resumeRide() { resumeRideCalled = true; state = RideSession(state: RideState.riding, startTime: DateTime.now()); } @override Future finishRide() async { finishRideCalled = true; state = RideSession(state: RideState.finished, startTime: DateTime.now()); } @override dynamic noSuchMethod(Invocation invocation) => super.noSuchMethod(invocation); } void main() { TestWidgetsFlutterBinding.ensureInitialized(); // =========================================================================== // SECTION 1: Canonical Tokens in AppColors and MotorSignalThemeColors // =========================================================================== group('1. Canonical Tokens in AppColors and MotorSignalThemeColors', () { test('AppColors defines canonical hybrid canvases', () { expect(AppColors.hybridNightCanvas, const Color(0xFF0B1326)); expect(AppColors.hybridDayCanvas, const Color(0xFFF8FAFC)); }); test('MotorSignalThemeColors surfaceCanvas matches canonical canvases', () { expect( MotorSignalThemeColors.dark.surfaceCanvas, const Color(0xFF0B1326), ); expect( MotorSignalThemeColors.light.surfaceCanvas, const Color(0xFFF8FAFC), ); }); test('AppColors defines canonical hybrid core state tokens', () { // Night tokens expect(AppColors.hybridNightBrandCyan, const Color(0xFF06B6D4)); expect(AppColors.hybridNightRecGreen, const Color(0xFF10B981)); expect(AppColors.hybridNightCautionAmber, const Color(0xFFF59E0B)); expect(AppColors.hybridNightDangerRed, const Color(0xFFEF4444)); // Day tokens expect(AppColors.hybridDayBrandCyan, const Color(0xFF0284C7)); expect(AppColors.hybridDayRecGreen, const Color(0xFF10B981)); expect(AppColors.hybridDayCautionAmber, const Color(0xFFD97706)); expect(AppColors.hybridDayDangerRed, const Color(0xFFDC2626)); }); test('MotorSignalThemeColors presets map brandCyan, recStaticGreen, cautionAmber, and dangerRed', () { // Dark mode preset final dark = MotorSignalThemeColors.dark; expect(dark.brandCyan, AppColors.hybridNightBrandCyan); expect(dark.brandCyan, const Color(0xFF06B6D4)); expect(dark.recStaticGreen, AppColors.hybridNightRecGreen); expect(dark.recStaticGreen, const Color(0xFF10B981)); expect(dark.cautionAmber, AppColors.hybridNightCautionAmber); expect(dark.cautionAmber, const Color(0xFFF59E0B)); expect(dark.dangerRed, AppColors.hybridNightDangerRed); expect(dark.dangerRed, const Color(0xFFEF4444)); // Light mode preset final light = MotorSignalThemeColors.light; expect(light.brandCyan, AppColors.hybridDayBrandCyan); expect(light.brandCyan, const Color(0xFF0284C7)); expect(light.recStaticGreen, AppColors.hybridDayRecGreen); expect(light.recStaticGreen, const Color(0xFF10B981)); expect(light.cautionAmber, AppColors.hybridDayCautionAmber); expect(light.cautionAmber, const Color(0xFFD97706)); expect(light.dangerRed, AppColors.hybridDayDangerRed); expect(light.dangerRed, const Color(0xFFDC2626)); }); test('MotorSignalThemeColors.copyWith supports all 15 hybrid tokens', () { const dark = MotorSignalThemeColors.dark; final modified = dark.copyWith( surfaceCanvas: const Color(0xFF100001), surfaceCardPrimary: const Color(0xFF100002), surfaceCardSecondary: const Color(0xFF100003), surfaceCardLowest: const Color(0xFF100004), surfaceBorder: const Color(0xFF100005), surfaceBorderSubtle: const Color(0xFF100006), textPrimary: const Color(0xFF100007), textMuted: const Color(0xFF100008), brandCyan: const Color(0xFF100009), brandCyanBright: const Color(0xFF10000A), brandCyanContainer: const Color(0xFF10000B), recStaticGreen: const Color(0xFF10000C), recStaticGreenBg: const Color(0xFF10000D), cautionAmber: const Color(0xFF10000E), dangerRed: const Color(0xFF10000F), ); expect(modified.surfaceCanvas, const Color(0xFF100001)); expect(modified.surfaceCardPrimary, const Color(0xFF100002)); expect(modified.surfaceCardSecondary, const Color(0xFF100003)); expect(modified.surfaceCardLowest, const Color(0xFF100004)); expect(modified.surfaceBorder, const Color(0xFF100005)); expect(modified.surfaceBorderSubtle, const Color(0xFF100006)); expect(modified.textPrimary, const Color(0xFF100007)); expect(modified.textMuted, const Color(0xFF100008)); expect(modified.brandCyan, const Color(0xFF100009)); expect(modified.brandCyanBright, const Color(0xFF10000A)); expect(modified.brandCyanContainer, const Color(0xFF10000B)); expect(modified.recStaticGreen, const Color(0xFF10000C)); expect(modified.recStaticGreenBg, const Color(0xFF10000D)); expect(modified.cautionAmber, const Color(0xFF10000E)); expect(modified.dangerRed, const Color(0xFF10000F)); // Existing non-hybrid tokens remain preserved expect(modified.cyberCyan, dark.cyberCyan); expect(modified.velocityBlue, dark.velocityBlue); expect(modified.electricCyan, dark.electricCyan); // Unmodified copy retains all original values final unchanged = dark.copyWith(); expect(unchanged.surfaceCanvas, dark.surfaceCanvas); expect(unchanged.brandCyan, dark.brandCyan); expect(unchanged.recStaticGreen, dark.recStaticGreen); expect(unchanged.cautionAmber, dark.cautionAmber); expect(unchanged.dangerRed, dark.dangerRed); }); test('MotorSignalThemeColors.lerp interpolates all 15 hybrid tokens', () { const dark = MotorSignalThemeColors.dark; const light = MotorSignalThemeColors.light; final lerped = dark.lerp(light, 0.5); expect(lerped, isA()); expect(lerped.surfaceCanvas, Color.lerp(dark.surfaceCanvas, light.surfaceCanvas, 0.5)); expect(lerped.surfaceCardPrimary, Color.lerp(dark.surfaceCardPrimary, light.surfaceCardPrimary, 0.5)); expect(lerped.surfaceCardSecondary, Color.lerp(dark.surfaceCardSecondary, light.surfaceCardSecondary, 0.5)); expect(lerped.surfaceCardLowest, Color.lerp(dark.surfaceCardLowest, light.surfaceCardLowest, 0.5)); expect(lerped.surfaceBorder, Color.lerp(dark.surfaceBorder, light.surfaceBorder, 0.5)); expect(lerped.surfaceBorderSubtle, Color.lerp(dark.surfaceBorderSubtle, light.surfaceBorderSubtle, 0.5)); expect(lerped.textPrimary, Color.lerp(dark.textPrimary, light.textPrimary, 0.5)); expect(lerped.textMuted, Color.lerp(dark.textMuted, light.textMuted, 0.5)); expect(lerped.brandCyan, Color.lerp(dark.brandCyan, light.brandCyan, 0.5)); expect(lerped.brandCyanBright, Color.lerp(dark.brandCyanBright, light.brandCyanBright, 0.5)); expect(lerped.brandCyanContainer, Color.lerp(dark.brandCyanContainer, light.brandCyanContainer, 0.5)); expect(lerped.recStaticGreen, Color.lerp(dark.recStaticGreen, light.recStaticGreen, 0.5)); expect(lerped.recStaticGreenBg, Color.lerp(dark.recStaticGreenBg, light.recStaticGreenBg, 0.5)); expect(lerped.cautionAmber, Color.lerp(dark.cautionAmber, light.cautionAmber, 0.5)); expect(lerped.dangerRed, Color.lerp(dark.dangerRed, light.dangerRed, 0.5)); // Null or non-matching other returns this expect(dark.lerp(null, 0.5), same(dark)); }); }); // =========================================================================== // SECTION 2: Reusable Components in lib/core/widgets/ // =========================================================================== group('2. Reusable Components', () { group('HybridCard', () { testWidgets('renders with 1px border, primary surface color, and padding', (tester) async { await tester.pumpWidget( MaterialApp( theme: AppTheme.darkTheme, home: const Scaffold( body: HybridCard( child: Text('Card Content'), ), ), ), ); final containerFinder = find.descendant( of: find.byType(HybridCard), matching: find.byType(Container), ).first; final containerWidget = tester.widget(containerFinder); final decoration = containerWidget.decoration as ShapeDecoration; final shape = decoration.shape as RoundedRectangleBorder; // 1px border verification expect(shape.side.width, 1.0); expect(shape.side.color, AppColors.hybridNightBorder); // Surface color: primary by default expect(decoration.color, AppColors.hybridNightSurfaceCardPrimary); // Padding: default AppSpacing.md (12.0) final paddingFinder = find.ancestor( of: find.text('Card Content'), matching: find.byType(Padding), ); final paddingWidget = tester.widget(paddingFinder.first); expect(paddingWidget.padding, const EdgeInsets.all(AppSpacing.md)); // Child content present expect(find.text('Card Content'), findsOneWidget); }); testWidgets('renders secondary surface color and custom border color when specified', (tester) async { await tester.pumpWidget( MaterialApp( theme: AppTheme.lightTheme, home: const Scaffold( body: HybridCard( secondary: true, borderColor: Color(0xFF123456), padding: EdgeInsets.all(20.0), child: Text('Secondary Card'), ), ), ), ); final containerFinder = find.descendant( of: find.byType(HybridCard), matching: find.byType(Container), ).first; final containerWidget = tester.widget(containerFinder); final decoration = containerWidget.decoration as ShapeDecoration; final shape = decoration.shape as RoundedRectangleBorder; expect(shape.side.width, 1.0); expect(shape.side.color, const Color(0xFF123456)); expect(decoration.color, AppColors.hybridDaySurfaceCardSecondary); final paddingFinder = find.ancestor( of: find.text('Secondary Card'), matching: find.byType(Padding), ); final paddingWidget = tester.widget(paddingFinder.first); expect(paddingWidget.padding, const EdgeInsets.all(20.0)); }); testWidgets('handles onTap callback and InkWell presence', (tester) async { bool tapped = false; await tester.pumpWidget( MaterialApp( theme: AppTheme.darkTheme, home: Scaffold( body: HybridCard( onTap: () { tapped = true; }, child: const Text('Tappable Card'), ), ), ), ); expect(find.byType(InkWell), findsOneWidget); await tester.tap(find.text('Tappable Card')); await tester.pumpAndSettle(); expect(tapped, isTrue); }); testWidgets('omits InkWell when onTap is null', (tester) async { await tester.pumpWidget( MaterialApp( theme: AppTheme.darkTheme, home: const Scaffold( body: HybridCard( child: Text('Non-tappable Card'), ), ), ), ); expect(find.byType(InkWell), findsNothing); }); }); group('HybridStaticRecBadge', () { testWidgets('renders isRecording true badge (REC • LIVE, green)', (tester) async { await tester.pumpWidget( MaterialApp( theme: AppTheme.darkTheme, home: const Scaffold( body: HybridStaticRecBadge(isRecording: true), ), ), ); expect(find.text('REC • LIVE'), findsOneWidget); final textWidget = tester.widget(find.text('REC • LIVE')); expect(textWidget.style?.color, AppColors.hybridNightRecGreen); final outerContainer = tester.widget(find.byType(Container).first); final boxDec = outerContainer.decoration as BoxDecoration; expect(boxDec.color, AppColors.hybridNightRecGreenBg); }); testWidgets('renders isPaused true badge (PAUSED, amber)', (tester) async { await tester.pumpWidget( MaterialApp( theme: AppTheme.darkTheme, home: const Scaffold( body: HybridStaticRecBadge( isRecording: false, isPaused: true, ), ), ), ); expect(find.text('PAUSED'), findsOneWidget); final textWidget = tester.widget(find.text('PAUSED')); expect(textWidget.style?.color, AppColors.hybridNightCautionAmber); }); testWidgets('renders standby badge (STANDBY) when neither recording nor paused', (tester) async { await tester.pumpWidget( MaterialApp( theme: AppTheme.darkTheme, home: const Scaffold( body: HybridStaticRecBadge( isRecording: false, isPaused: false, ), ), ), ); expect(find.text('STANDBY'), findsOneWidget); final textWidget = tester.widget(find.text('STANDBY')); expect(textWidget.style?.color, AppColors.hybridNightTextMuted); }); testWidgets('verifies zero animation controllers or ticker providers are used', (tester) async { await tester.pumpWidget( MaterialApp( theme: AppTheme.darkTheme, home: const Scaffold( body: HybridStaticRecBadge(isRecording: true), ), ), ); // HybridStaticRecBadge is strictly a StatelessWidget final badgeWidget = tester.widget(find.byType(HybridStaticRecBadge)); expect(badgeWidget, isA()); // Zero StatefulWidgets or TickerProviders anywhere in the badge tree expect( find.descendant( of: find.byType(HybridStaticRecBadge), matching: find.byWidgetPredicate((w) => w is StatefulWidget), ), findsNothing, ); // Zero AnimatedWidget descendants expect( find.descendant( of: find.byType(HybridStaticRecBadge), matching: find.byType(AnimatedWidget), ), findsNothing, ); // Zero active transient animation callbacks expect(tester.binding.transientCallbackCount, 0); }); }); group('HybridMetricTile', () { testWidgets('renders tabular value, unit, and label with uppercase tracking', (tester) async { await tester.pumpWidget( MaterialApp( theme: AppTheme.darkTheme, home: const Scaffold( body: HybridMetricTile( label: 'Speed', value: '72', unit: 'mph', ), ), ), ); // Label: uppercase tracking expect(find.text('SPEED'), findsOneWidget); final labelText = tester.widget(find.text('SPEED')); expect(labelText.style?.fontFamily, AppTypography.fontHeadline); expect(labelText.style?.fontSize, 11); expect(labelText.style?.fontWeight, FontWeight.w700); expect(labelText.style?.letterSpacing, 1.0); expect(labelText.style?.color, AppColors.hybridNightTextMuted); // Value: tabular JetBrains Mono font expect(find.text('72'), findsOneWidget); final valueText = tester.widget(find.text('72')); expect(valueText.style?.fontFamily, AppTypography.fontTelemetry); expect(valueText.style?.fontSize, 26); expect(valueText.style?.fontWeight, FontWeight.w700); expect(valueText.style?.color, AppColors.hybridNightTextPrimary); // Unit: uppercase tracking expect(find.text('MPH'), findsOneWidget); final unitText = tester.widget(find.text('MPH')); expect(unitText.style?.fontFamily, AppTypography.fontTelemetry); expect(unitText.style?.fontSize, 11); expect(unitText.style?.letterSpacing, 0.5); }); testWidgets('renders compact mode with scaled font sizes and icon', (tester) async { await tester.pumpWidget( MaterialApp( theme: AppTheme.darkTheme, home: const Scaffold( body: HybridMetricTile( label: 'Lean', value: '42°', unit: 'deg', icon: Icons.navigation_rounded, compact: true, ), ), ), ); expect(find.text('LEAN'), findsOneWidget); final labelText = tester.widget(find.text('LEAN')); expect(labelText.style?.fontSize, 10); expect(find.text('42°'), findsOneWidget); final valueText = tester.widget(find.text('42°')); expect(valueText.style?.fontSize, 22); expect(find.text('DEG'), findsOneWidget); final unitText = tester.widget(find.text('DEG')); expect(unitText.style?.fontSize, 10); final iconWidget = tester.widget(find.byType(Icon)); expect(iconWidget.size, 12); }); }); group('HybridRidingActionBar', () { testWidgets('verifies 56dp height and 48dp min touch bounds in standard mode', (tester) async { final fakeMachine = _FakeRideStateMachine( RideSession(state: RideState.ready, startTime: DateTime.now()), ); await tester.pumpWidget( ProviderScope( overrides: [ rideStateMachineProvider.overrideWith((ref) => fakeMachine), ], child: MaterialApp( theme: AppTheme.darkTheme, home: const Scaffold( body: HybridRidingActionBar(compact: false), ), ), ), ); final buttonFinder = find.byKey(const ValueKey('hybrid_start_ride_button')); expect(buttonFinder, findsOneWidget); final size = tester.getSize(buttonFinder); expect(size.height, AppSpacing.primaryRidingCtaHeight); // 56dp expect(size.height, greaterThanOrEqualTo(AppSpacing.minTouchTarget)); // >= 48dp }); testWidgets('verifies 48dp compact height and min touch bounds', (tester) async { final fakeMachine = _FakeRideStateMachine( RideSession(state: RideState.ready, startTime: DateTime.now()), ); await tester.pumpWidget( ProviderScope( overrides: [ rideStateMachineProvider.overrideWith((ref) => fakeMachine), ], child: MaterialApp( theme: AppTheme.darkTheme, home: const Scaffold( body: HybridRidingActionBar(compact: true), ), ), ), ); final buttonFinder = find.byKey(const ValueKey('hybrid_start_ride_button')); expect(buttonFinder, findsOneWidget); final size = tester.getSize(buttonFinder); expect(size.height, AppSpacing.minTouchTarget); // 48dp expect(size.height, greaterThanOrEqualTo(AppSpacing.minTouchTarget)); // >= 48dp }); testWidgets('renders start button when ready / standby and invokes startRide', (tester) async { final fakeMachine = _FakeRideStateMachine( RideSession(state: RideState.ready, startTime: DateTime.now()), ); await tester.pumpWidget( ProviderScope( overrides: [ rideStateMachineProvider.overrideWith((ref) => fakeMachine), ], child: MaterialApp( theme: AppTheme.darkTheme, home: const Scaffold( body: HybridRidingActionBar(), ), ), ), ); expect(find.byKey(const ValueKey('hybrid_start_ride_button')), findsOneWidget); expect(find.text('Start Session'), findsOneWidget); expect(find.byKey(const ValueKey('hybrid_pause_resume_ride_button')), findsNothing); expect(find.byKey(const ValueKey('hybrid_finish_ride_button')), findsNothing); await tester.tap(find.byKey(const ValueKey('hybrid_start_ride_button'))); await tester.pumpAndSettle(); expect(fakeMachine.startRideCalled, isTrue); }); testWidgets('renders Pause Ride and Finish Ride buttons when riding', (tester) async { final fakeMachine = _FakeRideStateMachine( RideSession(state: RideState.riding, startTime: DateTime.now()), ); await tester.pumpWidget( ProviderScope( overrides: [ rideStateMachineProvider.overrideWith((ref) => fakeMachine), ], child: MaterialApp( theme: AppTheme.darkTheme, home: const Scaffold( body: HybridRidingActionBar(), ), ), ), ); expect(find.byKey(const ValueKey('hybrid_start_ride_button')), findsNothing); final pauseFinder = find.byKey(const ValueKey('hybrid_pause_resume_ride_button')); final hazardFinder = find.byKey(const ValueKey('hybrid_report_hazard_button')); final finishFinder = find.byKey(const ValueKey('hybrid_finish_ride_button')); expect(pauseFinder, findsOneWidget); expect(hazardFinder, findsOneWidget); expect(finishFinder, findsOneWidget); expect(find.text('Pause Ride'), findsOneWidget); expect(find.text('Hazard'), findsOneWidget); expect(find.text('Finish Ride'), findsOneWidget); // Tap Pause await tester.tap(pauseFinder); await tester.pumpAndSettle(); expect(fakeMachine.pauseRideCalled, isTrue); // Tap Finish & Confirm Dialog await tester.tap(finishFinder); await tester.pumpAndSettle(); if (find.text('End Ride').evaluate().isNotEmpty) { await tester.tap(find.text('End Ride')); await tester.pumpAndSettle(); } expect(fakeMachine.finishRideCalled, isTrue); }); testWidgets('cancelling End Ride confirmation dialog preserves active ride session', (tester) async { final fakeMachine = _FakeRideStateMachine( RideSession(state: RideState.riding, startTime: DateTime.now()), ); await tester.pumpWidget( ProviderScope( overrides: [ rideStateMachineProvider.overrideWith((ref) => fakeMachine), ], child: MaterialApp( theme: AppTheme.darkTheme, home: const Scaffold( body: HybridRidingActionBar(), ), ), ), ); final finishFinder = find.byKey(const ValueKey('hybrid_finish_ride_button')); await tester.tap(finishFinder); await tester.pumpAndSettle(); expect(find.text('End Ride?'), findsOneWidget); expect(find.text('Cancel'), findsOneWidget); await tester.tap(find.text('Cancel')); await tester.pumpAndSettle(); expect(fakeMachine.finishRideCalled, isFalse); expect(find.text('End Ride?'), findsNothing); }); testWidgets('renders Resume button when paused and invokes resumeRide', (tester) async { final fakeMachine = _FakeRideStateMachine( RideSession(state: RideState.paused, startTime: DateTime.now()), ); await tester.pumpWidget( ProviderScope( overrides: [ rideStateMachineProvider.overrideWith((ref) => fakeMachine), ], child: MaterialApp( theme: AppTheme.darkTheme, home: const Scaffold( body: HybridRidingActionBar(), ), ), ), ); final resumeFinder = find.byKey(const ValueKey('hybrid_pause_resume_ride_button')); expect(resumeFinder, findsOneWidget); expect(find.text('Resume'), findsOneWidget); expect(find.text('Pause Ride'), findsNothing); await tester.tap(resumeFinder); await tester.pumpAndSettle(); expect(fakeMachine.resumeRideCalled, isTrue); }); }); }); // =========================================================================== // SECTION 3: Large Text Accessibility (200% Text Scale) // =========================================================================== group('3. Large Text Accessibility (200% text scale)', () { const largeTextMedia = MediaQueryData( size: Size(390, 844), textScaler: TextScaler.linear(2.0), ); testWidgets('HybridCard renders under 200% text scale without RenderFlex overflow', (tester) async { await tester.pumpWidget( MaterialApp( theme: AppTheme.darkTheme, home: const MediaQuery( data: largeTextMedia, child: Scaffold( body: HybridCard( child: Text('Hybrid Card under large text scaling mode'), ), ), ), ), ); expect(tester.takeException(), isNull); expect(find.text('Hybrid Card under large text scaling mode'), findsOneWidget); }); testWidgets('HybridMetricTile renders under 200% text scale without RenderFlex overflow', (tester) async { await tester.pumpWidget( MaterialApp( theme: AppTheme.darkTheme, home: const MediaQuery( data: largeTextMedia, child: Scaffold( body: Column( mainAxisSize: MainAxisSize.min, children: [ HybridMetricTile( label: 'Speed', value: '120', unit: 'mph', icon: Icons.speed, ), SizedBox(height: 16), HybridMetricTile( label: 'Max Lean Angle Left', value: '48.5°', unit: 'deg', compact: true, ), ], ), ), ), ), ); expect(tester.takeException(), isNull); expect(find.text('SPEED'), findsOneWidget); expect(find.text('120'), findsOneWidget); expect(find.text('MAX LEAN ANGLE LEFT'), findsOneWidget); }); testWidgets('HybridStaticRecBadge renders under 200% text scale without RenderFlex overflow', (tester) async { await tester.pumpWidget( MaterialApp( theme: AppTheme.darkTheme, home: const MediaQuery( data: largeTextMedia, child: Scaffold( body: Column( mainAxisSize: MainAxisSize.min, children: [ HybridStaticRecBadge(isRecording: true), SizedBox(height: 8), HybridStaticRecBadge(isRecording: false, isPaused: true), SizedBox(height: 8), HybridStaticRecBadge(isRecording: false, isPaused: false), ], ), ), ), ), ); expect(tester.takeException(), isNull); expect(find.text('REC • LIVE'), findsOneWidget); expect(find.text('PAUSED'), findsOneWidget); expect(find.text('STANDBY'), findsOneWidget); }); testWidgets('HybridRidingActionBar riding mode renders under 200% text scale without RenderFlex overflow', (tester) async { final ridingMachine = _FakeRideStateMachine( RideSession(state: RideState.riding, startTime: DateTime.now()), ); await tester.pumpWidget( ProviderScope( overrides: [ rideStateMachineProvider.overrideWith((ref) => ridingMachine), ], child: MaterialApp( theme: AppTheme.darkTheme, home: const MediaQuery( data: largeTextMedia, child: Scaffold( body: Padding( padding: EdgeInsets.all(16.0), child: HybridRidingActionBar(), ), ), ), ), ), ); expect(tester.takeException(), isNull); expect(find.text('Pause Ride'), findsOneWidget); expect(find.text('Finish Ride'), findsOneWidget); }); testWidgets('HybridRidingActionBar paused mode renders under 200% text scale without RenderFlex overflow', (tester) async { final pausedMachine = _FakeRideStateMachine( RideSession(state: RideState.paused, startTime: DateTime.now()), ); await tester.pumpWidget( ProviderScope( overrides: [ rideStateMachineProvider.overrideWith((ref) => pausedMachine), ], child: MaterialApp( theme: AppTheme.darkTheme, home: const MediaQuery( data: largeTextMedia, child: Scaffold( body: Padding( padding: EdgeInsets.all(16.0), child: HybridRidingActionBar(compact: true), ), ), ), ), ), ); expect(tester.takeException(), isNull); expect(find.text('Resume'), findsOneWidget); expect(find.text('Finish Ride'), findsOneWidget); }); testWidgets('HybridRidingActionBar standby/ready mode renders under 200% text scale without RenderFlex overflow', (tester) async { final readyMachine = _FakeRideStateMachine( RideSession(state: RideState.ready, startTime: DateTime.now()), ); await tester.pumpWidget( ProviderScope( overrides: [ rideStateMachineProvider.overrideWith((ref) => readyMachine), ], child: MaterialApp( theme: AppTheme.darkTheme, home: const MediaQuery( data: largeTextMedia, child: Scaffold( body: Padding( padding: EdgeInsets.all(16.0), child: HybridRidingActionBar(), ), ), ), ), ), ); expect(tester.takeException(), isNull); expect(find.text('Start Session'), findsOneWidget); }); testWidgets('Composite hybrid cockpit dashboard renders under 200% text scale without RenderFlex overflow', (tester) async { final machine = _FakeRideStateMachine( RideSession(state: RideState.riding, startTime: DateTime.now()), ); await tester.pumpWidget( ProviderScope( overrides: [ rideStateMachineProvider.overrideWith((ref) => machine), ], child: MaterialApp( theme: AppTheme.darkTheme, home: const MediaQuery( data: largeTextMedia, child: Scaffold( body: SingleChildScrollView( child: Padding( padding: EdgeInsets.all(16.0), child: Column( children: [ HybridStaticRecBadge(isRecording: true), SizedBox(height: 16), HybridCard( child: Column( children: [ HybridMetricTile( label: 'Speed', value: '65', unit: 'mph', ), SizedBox(height: 12), HybridMetricTile( label: 'Lean Angle', value: '38°', unit: 'deg', compact: true, ), ], ), ), SizedBox(height: 16), HybridRidingActionBar(), ], ), ), ), ), ), ), ), ); expect(tester.takeException(), isNull); expect(find.byType(HybridStaticRecBadge), findsOneWidget); expect(find.byType(HybridCard), findsOneWidget); expect(find.byType(HybridMetricTile), findsNWidgets(2)); expect(find.byType(HybridRidingActionBar), findsOneWidget); }); }); }