import 'package:drift/native.dart'; import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:glp1_companion/core/database/app_database.dart'; import 'package:glp1_companion/core/platform/health_gateway.dart'; import 'package:glp1_companion/features/health_sync/presentation/controllers/biometric_controller.dart'; import 'package:glp1_companion/features/settings/presentation/screens/settings_screen.dart'; /// Phase 3B: the Settings screen must render the Health Integrations section /// with desktop-safe disabled states when native mobile providers are /// unavailable (Windows / Web fallback driver), and run a full connect / /// disconnect round trip through the UI when they are. /// Fallback driver stand-in: no native SDK anywhere. class _UnsupportedHealthGateway extends IHealthGateway { @override bool get supportsNativeHealth => false; @override NativeHealthPlatform? get nativePlatform => null; @override Future requestPermissions() async => false; @override Future> fetchWeightHistory({ required DateTime start, required DateTime end, }) async => const []; @override Future> fetchActiveEnergyBurned({ required DateTime start, required DateTime end, }) async => const []; @override Future writeWeight({ required double weightLbs, required DateTime timestamp, }) async => false; } /// Android-backed stand-in: Health Connect answers status and grants on /// request; Apple Health stays unsupported. class _HealthConnectGateway extends _UnsupportedHealthGateway { HealthConnectionStatus status = HealthConnectionStatus.notConfigured; @override bool get supportsNativeHealth => true; @override NativeHealthPlatform? get nativePlatform => NativeHealthPlatform.healthConnect; @override Future checkStatus( HealthProviderType provider, ) async { if (provider == HealthProviderType.manualFallback) { return HealthConnectionStatus.connected; } if (provider == HealthProviderType.enterpriseFhir) { return HealthConnectionStatus.notConfigured; } if (provider == HealthProviderType.healthConnect) return status; return HealthConnectionStatus.unsupportedPlatform; } @override Future requestAuthorization(HealthProviderType provider) async { if (provider == HealthProviderType.healthConnect) { status = HealthConnectionStatus.connected; return true; } return false; } @override Future disconnect(HealthProviderType provider) async { if (provider == HealthProviderType.healthConnect) { status = HealthConnectionStatus.notConfigured; } } } void main() { late AppDatabase database; setUp(() { database = AppDatabase.forTesting(NativeDatabase.memory()); }); tearDown(() => database.close()); Widget buildScreen(IHealthGateway gateway) => ProviderScope( overrides: [ appDatabaseProvider.overrideWithValue(database), healthGatewayProvider.overrideWithValue(gateway), ], child: const MaterialApp(home: SettingsScreen()), ); // A tall phone-like viewport keeps every settings card on-screen (the // ListView builds lazily beyond its cache extent). void useTallViewport(WidgetTester tester) { tester.view.physicalSize = const Size(1080, 2400); tester.view.devicePixelRatio = 1.0; addTearDown(tester.view.reset); } testWidgets( 'desktop fallback renders integrations with disabled Connect buttons ' 'and Unsupported badges', (tester) async { useTallViewport(tester); await tester.pumpWidget(buildScreen(_UnsupportedHealthGateway())); await tester.pumpAndSettle(); expect(find.text('Health Integrations'), findsOneWidget); expect(find.text('Apple Health'), findsOneWidget); expect(find.text('Health Connect (Samsung & Pixel)'), findsOneWidget); expect(find.text('Google Health'), findsOneWidget); expect(find.text('SMART on FHIR'), findsOneWidget); // Native providers and the (web-stubbed) Google Health connector are // unsupported on desktop; the FHIR connector is not provisioned yet — // every Connect affordance is disabled. expect(find.text('Unsupported'), findsNWidgets(3)); final connectButtons = find.widgetWithText(FilledButton, 'Connect'); expect(connectButtons, findsNWidgets(4)); for (final button in tester.widgetList(connectButtons)) { expect(button.onPressed, isNull); } // Encrypted storage card and the Supabase sign-out action are present. expect(find.text('SQLCipher AES-256 active'), findsOneWidget); expect(find.widgetWithText(FilledButton, 'Sign Out'), findsOneWidget); }); testWidgets( 'mobile gateway runs the connect / disconnect round trip with live ' 'status badges', (tester) async { useTallViewport(tester); final gateway = _HealthConnectGateway(); await tester.pumpWidget(buildScreen(gateway)); await tester.pumpAndSettle(); // Health Connect row is available with an enabled Connect button (the // FHIR row also reads "Not connected" until its connector ships). expect(find.text('Not connected'), findsNWidgets(2)); final connectFinder = find.widgetWithText(FilledButton, 'Connect'); final allConnect = connectFinder.evaluate().toList(); final enabledIndex = allConnect .indexWhere((element) => (element.widget as FilledButton).onPressed != null); expect(enabledIndex, greaterThanOrEqualTo(0)); await tester.tap(connectFinder.at(enabledIndex)); await tester.pumpAndSettle(); // Connected: badge flips and the button becomes Disconnect. expect(find.text('Connected'), findsOneWidget); final disconnect = find.widgetWithText(OutlinedButton, 'Disconnect'); expect(disconnect, findsOneWidget); await tester.tap(disconnect); await tester.pumpAndSettle(); expect(find.text('Not connected'), findsNWidgets(2)); expect(find.text('Connected'), findsNothing); }); }