import 'package:flutter/material.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:motorcycle_dash/features/entitlements/widgets/pro_lock_badge.dart'; import 'package:motorcycle_dash/features/rides/ride_state_machine.dart'; import 'package:motorcycle_dash/services/billing/product_catalog.dart'; import 'package:motorcycle_dash/services/entitlements/entitlement_state.dart'; import 'package:motorcycle_dash/services/entitlements/feature_access_service.dart'; void main() { TestWidgetsFlutterBinding.ensureInitialized(); group('Rider Safety & Entitlement Separation Tests', () { testWidgets( 'suppresses modal upgrade sheet and shows passive snackbar during active ride', (tester) async { final activeRidingSession = RideSession( state: RideState.riding, tripDistanceMiles: 5.2, ); await tester.pumpWidget( ProviderScope( overrides: [ rideStateMachineProvider.overrideWith( (ref) => _FakeRideStateMachine(activeRidingSession), ), ], child: MaterialApp( home: Scaffold( body: Consumer( builder: (context, ref, _) { return ElevatedButton( onPressed: () => showProUpgradeSheet( context, ref, feature: MotorSignalFeature.csvExport, ), child: const Text('TRIGGER UPGRADE'), ); }, ), ), ), ), ); // Tap the button that triggers upgrade sheet await tester.tap(find.text('TRIGGER UPGRADE')); await tester.pump(); // Ensure NO modal bottom sheet appeared expect(find.byType(BottomSheet), findsNothing); // Ensure non-blocking passive snackbar appeared expect( find.text('Pro feature locked. Upgrade available when stopped.'), findsOneWidget, ); }, ); testWidgets('allows modal upgrade sheet when rider is idle / stopped', ( tester, ) async { final idleSession = RideSession(state: RideState.idle); await tester.pumpWidget( ProviderScope( overrides: [ rideStateMachineProvider.overrideWith( (ref) => _FakeRideStateMachine(idleSession), ), ], child: MaterialApp( home: Scaffold( body: Consumer( builder: (context, ref, _) { return ElevatedButton( onPressed: () => showProUpgradeSheet( context, ref, feature: MotorSignalFeature.csvExport, ), child: const Text('TRIGGER UPGRADE'), ); }, ), ), ), ), ); // Tap the button that triggers upgrade sheet await tester.tap(find.text('TRIGGER UPGRADE')); await tester.pumpAndSettle(); // Ensure modal bottom sheet DID open expect(find.byType(BottomSheet), findsOneWidget); expect(find.text('Forensic CSV & PDF Export'), findsOneWidget); expect(find.text('VIEW MOTORSIGNAL PRO'), findsOneWidget); expect(find.text('NOT NOW'), findsOneWidget); }); test('Entitlement state changes do not disrupt RideSession or RideState', () { final activeSession = RideSession( state: RideState.riding, tripDistanceMiles: 12.4, topSpeedMph: 64.0, ); // Verify that changing EntitlementState from Free to Pro to Expired is independent of RideSession var entitlement = const EntitlementState.free(); expect(entitlement.isPro, isFalse); expect(activeSession.state, RideState.riding); entitlement = EntitlementState.proMonthlyActive( productId: ProductCatalog.proMonthly, ); expect(entitlement.isPro, isTrue); expect(activeSession.state, RideState.riding); entitlement = EntitlementState.normalized( plan: MotorSignalPlan.proMonthly, status: MotorSignalEntitlementStatus.proMonthlyExpired, verificationStatus: EntitlementVerificationStatus.expired, ); expect(entitlement.isPro, isFalse); expect(activeSession.state, RideState.riding); expect(activeSession.tripDistanceMiles, 12.4); }); }); } class _FakeRideStateMachine extends StateNotifier implements RideStateMachine { _FakeRideStateMachine(super.initialState); @override dynamic noSuchMethod(Invocation invocation) => super.noSuchMethod(invocation); }