import 'dart:async'; import 'dart:io'; 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/theme.dart'; import 'package:motorcycle_dash/core/theme/theme_mode_provider.dart'; import 'package:motorcycle_dash/features/settings/settings_screen.dart'; import 'package:motorcycle_dash/features/settings/account_screen.dart'; import 'package:motorcycle_dash/features/entitlements/pro_screen.dart'; import 'package:motorcycle_dash/services/auth/auth_providers.dart'; import 'package:motorcycle_dash/services/auth/auth_service.dart'; import 'package:motorcycle_dash/services/auth/auth_state.dart'; import 'package:motorcycle_dash/services/auth/auth_user.dart'; import 'package:motorcycle_dash/services/billing/billing_providers.dart'; import 'package:motorcycle_dash/services/billing/fake_billing_service.dart'; import 'package:motorcycle_dash/services/entitlements/entitlement_providers.dart'; import 'package:motorcycle_dash/services/entitlements/entitlement_storage.dart'; import 'package:motorcycle_dash/database/database.dart'; import 'package:motorcycle_dash/features/records/records_service.dart'; import 'package:drift/native.dart'; import 'package:shared_preferences/shared_preferences.dart'; class _FakeAuth implements AuthService { @override Future ensureBackendIdentity() async {} final _states = StreamController.broadcast(); AuthUser? user; _FakeAuth({this.user}); @override Stream get authStateChanges => _states.stream; @override AuthState get currentState => user == null ? const AuthState.unauthenticated(isFirebaseConfigured: true) : (user!.isAnonymous ? AuthState.anonymous(user!, isFirebaseConfigured: true) : AuthState.authenticated(user!, isFirebaseConfigured: true)); @override AuthUser? get currentUser => user; @override bool get isConfigured => true; @override bool get isEmailVerified => user?.emailVerified ?? false; @override Future deleteCurrentUser() async { user = null; _states.add(currentState); } @override void dispose() => _states.close(); @override Future getIdToken([bool forceRefresh = false]) async => 'token'; @override Future reauthenticate({String? password}) async {} @override Future reloadCurrentUser() async {} @override Future sendEmailVerification() async {} @override Future sendPasswordResetEmail(String email) async {} @override Future signInAnonymously() async { user = const AuthUser( uid: 'anon-123', email: null, isAnonymous: true, providerId: 'anonymous', ); _states.add(currentState); } @override Future signInWithEmailAndPassword(String email, String password) async { user = AuthUser( uid: 'user-123', email: email, isAnonymous: false, providerId: 'password', ); _states.add(currentState); } @override Future createUserWithEmailAndPassword(String email, String password, {String? displayName}) async { user = AuthUser( uid: 'user-123', email: email, displayName: displayName, isAnonymous: false, providerId: 'password', ); _states.add(currentState); } @override Future signInWithGoogle() async { user = const AuthUser( uid: 'google-123', email: 'test@motorcycle.app', displayName: 'Test Rider', isAnonymous: false, providerId: 'google.com', ); _states.add(currentState); } @override Future signOut() async { user = null; _states.add(currentState); } } void main() { TestWidgetsFlutterBinding.ensureInitialized(); final canonicalViewports = { '320x640 (very narrow phone)': const Size(320, 640), '400x800 (standard phone)': const Size(400, 800), '412x915 (tall modern phone)': const Size(412, 915), '640x320 (short landscape)': const Size(640, 320), '780x360 (wide landscape)': const Size(780, 360), '800x360 (wide landscape)': const Size(800, 360), '840x390 (ultra-wide landscape)': const Size(840, 390), '768x1024 (portrait tablet)': const Size(768, 1024), '1024x768 (landscape tablet)': const Size(1024, 768), }; Widget buildTestHost({ required Widget child, required SharedPreferences prefs, required AppDatabase db, _FakeAuth? auth, FakeBillingService? billing, }) { final fakeAuth = auth ?? _FakeAuth(); final fakeBilling = billing ?? FakeBillingService(); return ProviderScope( overrides: [ sharedPreferencesProvider.overrideWithValue(prefs), databaseProvider.overrideWithValue(db), authServiceProvider.overrideWithValue(fakeAuth), billingServiceProvider.overrideWithValue(fakeBilling), entitlementStorageProvider.overrideWithValue(InMemoryEntitlementStorage()), ], child: Consumer( builder: (context, ref, _) { final themeMode = ref.watch(appThemeModeProvider); return MaterialApp( themeMode: themeMode, theme: AppTheme.lightTheme, darkTheme: AppTheme.darkTheme, home: child, ); }, ), ); } group('Canonical 9-Viewport SettingsScreen Responsive Tests', () { for (final entry in canonicalViewports.entries) { testWidgets('renders cleanly on ${entry.key}', (tester) async { tester.view.physicalSize = entry.value; tester.view.devicePixelRatio = 1.0; addTearDown(() => tester.view.resetPhysicalSize()); SharedPreferences.setMockInitialValues({}); final prefs = await SharedPreferences.getInstance(); final db = AppDatabase.forTesting(NativeDatabase.memory()); addTearDown(() => db.close()); await tester.pumpWidget( buildTestHost( child: const SettingsScreen(), prefs: prefs, db: db, ), ); await tester.pumpAndSettle(); expect(find.text('Settings'), findsOneWidget); expect(tester.takeException(), isNull); }); } }); group('Canonical 9-Viewport AccountScreen Responsive Tests', () { for (final entry in canonicalViewports.entries) { testWidgets('renders signed-out view cleanly on ${entry.key}', (tester) async { tester.view.physicalSize = entry.value; tester.view.devicePixelRatio = 1.0; addTearDown(() => tester.view.resetPhysicalSize()); SharedPreferences.setMockInitialValues({}); final prefs = await SharedPreferences.getInstance(); final db = AppDatabase.forTesting(NativeDatabase.memory()); addTearDown(() => db.close()); await tester.pumpWidget( buildTestHost( child: const AccountScreen(), prefs: prefs, db: db, ), ); await tester.pumpAndSettle(); expect(find.text('Account & Cloud Identity'), findsOneWidget); expect(find.text('SIGN IN TO MOTORCLOUD'), findsOneWidget); expect(tester.takeException(), isNull); }); testWidgets('renders signed-in view cleanly on ${entry.key}', (tester) async { tester.view.physicalSize = entry.value; tester.view.devicePixelRatio = 1.0; addTearDown(() => tester.view.resetPhysicalSize()); SharedPreferences.setMockInitialValues({}); final prefs = await SharedPreferences.getInstance(); final db = AppDatabase.forTesting(NativeDatabase.memory()); addTearDown(() => db.close()); final auth = _FakeAuth( user: const AuthUser( uid: 'rider-abc', email: 'rider@motorcycle.app', displayName: 'Rider Alpha', isAnonymous: false, providerId: 'password', emailVerified: true, ), ); await tester.pumpWidget( buildTestHost( child: const AccountScreen(), prefs: prefs, db: db, auth: auth, ), ); await tester.pumpAndSettle(); expect(find.text('Account & Cloud Identity'), findsOneWidget); expect(find.text('Rider Alpha'), findsOneWidget); expect(find.text('SIGN OUT'), findsOneWidget); expect(tester.takeException(), isNull); }); } }); group('Canonical 9-Viewport ProScreen Responsive Tests', () { for (final entry in canonicalViewports.entries) { testWidgets('renders cleanly on ${entry.key}', (tester) async { tester.view.physicalSize = entry.value; tester.view.devicePixelRatio = 1.0; addTearDown(() => tester.view.resetPhysicalSize()); SharedPreferences.setMockInitialValues({}); final prefs = await SharedPreferences.getInstance(); final db = AppDatabase.forTesting(NativeDatabase.memory()); addTearDown(() => db.close()); await tester.pumpWidget( buildTestHost( child: const ProScreen(), prefs: prefs, db: db, ), ); await tester.pumpAndSettle(); expect(find.text('MOTORSIGNAL'), findsOneWidget); expect(find.text('PRO LIFETIME'), findsOneWidget); expect(find.text('PRO MONTHLY'), findsOneWidget); expect(tester.takeException(), isNull); }); } }); group('ThemeMode State & Switching Verification', () { testWidgets('toggles through system, light, dark without data loss', (tester) async { SharedPreferences.setMockInitialValues({}); final prefs = await SharedPreferences.getInstance(); final db = AppDatabase.forTesting(NativeDatabase.memory()); addTearDown(() => db.close()); await tester.pumpWidget( buildTestHost( child: const SettingsScreen(), prefs: prefs, db: db, ), ); await tester.pumpAndSettle(); final container = ProviderScope.containerOf(tester.element(find.byType(SettingsScreen))); expect(container.read(appThemeModeProvider), ThemeMode.system); // Switch to Dark await container.read(appThemeModeProvider.notifier).setThemeMode(ThemeMode.dark); await tester.pumpAndSettle(); expect(container.read(appThemeModeProvider), ThemeMode.dark); // Switch to Light await container.read(appThemeModeProvider.notifier).setThemeMode(ThemeMode.light); await tester.pumpAndSettle(); expect(container.read(appThemeModeProvider), ThemeMode.light); // Switch back to System await container.read(appThemeModeProvider.notifier).setThemeMode(ThemeMode.system); await tester.pumpAndSettle(); expect(container.read(appThemeModeProvider), ThemeMode.system); expect(tester.takeException(), isNull); }); }); group('Short Landscape & IME Safety', () { testWidgets('auth forms in 640x320 landscape allow scrolling and input with keyboard', (tester) async { tester.view.physicalSize = const Size(640, 320); tester.view.devicePixelRatio = 1.0; addTearDown(() => tester.view.resetPhysicalSize()); SharedPreferences.setMockInitialValues({}); final prefs = await SharedPreferences.getInstance(); final db = AppDatabase.forTesting(NativeDatabase.memory()); addTearDown(() => db.close()); await tester.pumpWidget( buildTestHost( child: const AccountScreen(), prefs: prefs, db: db, ), ); await tester.pumpAndSettle(); // Enter email and password await tester.enterText(find.byType(TextFormField).first, 'test@rider.com'); await tester.enterText(find.byType(TextFormField).last, 'secret123'); await tester.pump(); expect(find.text('test@rider.com'), findsOneWidget); expect(tester.takeException(), isNull); }); }); group('Legacy Theme Retirement Audit Assertion', () { test('verifies zero references to retired theme identifiers in lib/', () { final libDir = Directory('lib'); final files = libDir.listSync(recursive: true).whereType().where((f) => f.path.endsWith('.dart')); final retiredTerms = [ 'DashboardThemeMode', 'dashboardThemeModeProvider', 'EvolutionSettingsView', 'app_dashboard_theme_mode', ]; for (final file in files) { final text = file.readAsStringSync(); for (final term in retiredTerms) { expect( text.contains(term), isFalse, reason: 'Found retired term "$term" in ${file.path}', ); } } }); }); }