You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

245 lines
8.4 KiB

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));
// New UI shows "Enabled (not connected)" or "Disabled" instead of "Disconnected"
expect(find.textContaining('Enabled'), findsWidgets);
});
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('Test All'), findsOneWidget);
expect(find.byIcon(Icons.network_check), findsOneWidget);
});
testWidgets('displays toggle all button', (WidgetTester tester) async {
await tester.pumpWidget(createTestWidget());
expect(find.text('Turn All On'), findsOneWidget);
expect(find.byIcon(Icons.power_settings_new), 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 test all button
final testAllButton = find.text('Test All');
await tester.tap(testAllButton);
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
expect(find.textContaining('wss://relay.example.com'), findsWidgets);
// Verify status indicator is present (now a Container with decoration, not CircleAvatar)
// The status indicator is a Container with BoxDecoration, so we check for the Card instead
expect(find.byType(Card), findsWidgets);
// Verify we have test button and toggle switch
expect(find.text('Test'), findsWidgets);
expect(find.byType(Switch), findsWidgets);
});
});
}

Powered by TurnKey Linux.