import 'package:flutter/material.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:app_boilerplate/ui/relay_management/relay_management_screen.dart'; import 'package:app_boilerplate/ui/relay_management/relay_management_controller.dart'; import 'package:app_boilerplate/data/nostr/nostr_service.dart'; import 'package:app_boilerplate/data/sync/sync_engine.dart'; import 'package:app_boilerplate/data/local/local_storage_service.dart'; import 'package:path/path.dart' as path; import 'package:sqflite_common_ffi/sqflite_ffi.dart'; import 'dart:io'; void main() { // Initialize Flutter bindings and sqflite for testing TestWidgetsFlutterBinding.ensureInitialized(); sqfliteFfiInit(); databaseFactory = databaseFactoryFfi; late NostrService nostrService; late SyncEngine syncEngine; late LocalStorageService localStorage; late Directory testDir; late String testDbPath; late Directory testCacheDir; late RelayManagementController controller; setUp(() async { // Create temporary directory for testing testDir = await Directory.systemTemp.createTemp('relay_ui_test_'); testDbPath = path.join(testDir.path, 'test_local_storage.db'); testCacheDir = Directory(path.join(testDir.path, 'image_cache')); // Initialize local storage localStorage = LocalStorageService( testDbPath: testDbPath, testCacheDir: testCacheDir, ); await localStorage.initialize(); // Create services nostrService = NostrService(); syncEngine = SyncEngine( localStorage: localStorage, nostrService: nostrService, ); // Create controller controller = RelayManagementController( nostrService: nostrService, syncEngine: syncEngine, ); }); tearDown(() async { controller.dispose(); syncEngine.dispose(); nostrService.dispose(); await localStorage.close(); try { if (await testDir.exists()) { await testDir.delete(recursive: true); } } catch (_) { // Ignore cleanup errors } }); Widget createTestWidget() { return MaterialApp( home: RelayManagementScreen(controller: controller), ); } group('RelayManagementScreen', () { testWidgets('displays empty state when no relays', (WidgetTester tester) async { await tester.pumpWidget(createTestWidget()); expect(find.text('No relays configured'), findsOneWidget); expect(find.text('Add a relay to get started'), findsOneWidget); expect(find.byIcon(Icons.cloud_off), findsOneWidget); }); testWidgets('displays relay list correctly', (WidgetTester tester) async { controller.addRelay('wss://relay1.example.com'); controller.addRelay('wss://relay2.example.com'); await tester.pumpWidget(createTestWidget()); await tester.pump(); // Relay URLs may appear in both placeholder and list, so use textContaining expect(find.textContaining('wss://relay1.example.com'), findsWidgets); expect(find.textContaining('wss://relay2.example.com'), findsWidgets); // Verify we have relay list items (Cards) expect(find.byType(Card), findsNWidgets(2)); expect(find.text('Disconnected'), findsNWidgets(2)); }); testWidgets('adds relay when Add button is pressed', (WidgetTester tester) async { await tester.pumpWidget(createTestWidget()); // Find and enter relay URL final urlField = find.byType(TextField); expect(urlField, findsOneWidget); await tester.enterText(urlField, 'wss://new-relay.example.com'); // Find and tap Add button (by text inside) final addButton = find.text('Add'); expect(addButton, findsOneWidget); await tester.tap(addButton); await tester.pump(); // Verify relay was added expect(find.textContaining('wss://new-relay.example.com'), findsWidgets); expect(find.text('Relay added successfully'), findsOneWidget); }); testWidgets('shows error for invalid URL', (WidgetTester tester) async { await tester.pumpWidget(createTestWidget()); // Enter invalid URL final urlField = find.byType(TextField); await tester.enterText(urlField, 'invalid-url'); // Tap Add button final addButton = find.text('Add'); await tester.tap(addButton); await tester.pump(); // Verify error message is shown expect(find.textContaining('Invalid relay URL'), findsOneWidget); expect(find.byIcon(Icons.error), findsOneWidget); }); testWidgets('removes relay when delete button is pressed', (WidgetTester tester) async { controller.addRelay('wss://relay.example.com'); await tester.pumpWidget(createTestWidget()); await tester.pump(); // Verify relay is in list expect(find.text('wss://relay.example.com'), findsWidgets); expect(controller.relays.length, equals(1)); // Find and tap delete button final deleteButton = find.byIcon(Icons.delete); expect(deleteButton, findsOneWidget); await tester.tap(deleteButton); await tester.pumpAndSettle(); // Verify relay was removed (check controller state) expect(controller.relays, isEmpty); // Verify empty state is shown expect(find.text('No relays configured'), findsOneWidget); }); testWidgets('displays check health button', (WidgetTester tester) async { await tester.pumpWidget(createTestWidget()); expect(find.text('Check Health'), findsOneWidget); expect(find.byIcon(Icons.health_and_safety), findsOneWidget); }); testWidgets('displays manual sync button when sync engine is configured', (WidgetTester tester) async { await tester.pumpWidget(createTestWidget()); expect(find.text('Manual Sync'), findsOneWidget); expect(find.byIcon(Icons.sync), findsOneWidget); }); testWidgets('shows loading state during health check', (WidgetTester tester) async { controller.addRelay('wss://relay.example.com'); await tester.pumpWidget(createTestWidget()); await tester.pump(); // Tap check health button final healthButton = find.text('Check Health'); await tester.tap(healthButton); await tester.pump(); // Check for loading indicator (may be brief) expect(find.byType(CircularProgressIndicator), findsWidgets); // Wait for health check to complete await tester.pumpAndSettle(); }); testWidgets('shows error message when present', (WidgetTester tester) async { await tester.pumpWidget(createTestWidget()); // Trigger an error by adding invalid URL final urlField = find.byType(TextField); await tester.enterText(urlField, 'invalid-url'); final addButton = find.text('Add'); await tester.tap(addButton); await tester.pump(); // Verify error container is displayed expect(find.byIcon(Icons.error), findsOneWidget); expect(find.textContaining('Invalid relay URL'), findsOneWidget); }); testWidgets('dismisses error when close button is pressed', (WidgetTester tester) async { await tester.pumpWidget(createTestWidget()); // Trigger an error final urlField = find.byType(TextField); await tester.enterText(urlField, 'invalid-url'); final addButton = find.text('Add'); await tester.tap(addButton); await tester.pump(); expect(find.textContaining('Invalid relay URL'), findsOneWidget); // Tap close button final closeButtons = find.byIcon(Icons.close); expect(closeButtons, findsOneWidget); await tester.tap(closeButtons); await tester.pump(); // Error should be cleared expect(find.textContaining('Invalid relay URL'), findsNothing); }); testWidgets('displays relay URL in list item', (WidgetTester tester) async { controller.addRelay('wss://relay.example.com'); await tester.pumpWidget(createTestWidget()); await tester.pump(); // Verify relay URL is displayed (may appear in ListTile title) expect(find.textContaining('wss://relay.example.com'), findsWidgets); // Verify status indicator is present expect(find.byType(CircleAvatar), findsWidgets); // Verify we have a relay card expect(find.byType(Card), findsWidgets); }); }); }