fix: Add manual TCP connect flow

This commit is contained in:
Janez T
2026-04-18 08:59:36 +02:00
parent b333b3737e
commit f9dfdc2340
10 changed files with 1001 additions and 521 deletions

View File

@@ -6,6 +6,7 @@ import 'package:meshcore_sar_app/providers/connection_provider.dart';
import 'package:meshcore_sar_app/services/network_scanner_service.dart';
import 'package:meshcore_sar_app/widgets/connection_dialog.dart';
import 'package:provider/provider.dart';
import 'package:shared_preferences/shared_preferences.dart';
class _FakeConnectionProvider extends ConnectionProvider {
int startScanCalls = 0;
@@ -75,10 +76,18 @@ class _TcpConnectableFakeConnectionProvider extends ConnectionProvider {
}
class _FakeNetworkScannerService extends NetworkScannerService {
_FakeNetworkScannerService({
this.keepScanning = false,
bool initiallyScanning = false,
}) : _isScanning = initiallyScanning;
final bool keepScanning;
int scanCalls = 0;
int stopScanCalls = 0;
bool _isScanning;
@override
bool get isScanning => false;
bool get isScanning => _isScanning;
@override
bool get hasCachedResults => false;
@@ -89,6 +98,10 @@ class _FakeNetworkScannerService extends NetworkScannerService {
@override
Future<List<DiscoveredServer>> scan({int? port}) async {
scanCalls += 1;
_isScanning = keepScanning;
if (keepScanning) {
onProgressUpdate?.call(1, 10);
}
return const [];
}
@@ -96,10 +109,17 @@ class _FakeNetworkScannerService extends NetworkScannerService {
void clearCache() {}
@override
void stopScan() {}
void stopScan() {
stopScanCalls += 1;
_isScanning = false;
}
}
void main() {
setUp(() {
SharedPreferences.setMockInitialValues({});
});
testWidgets('BLE scan waits for explicit user action', (tester) async {
final connectionProvider = _FakeConnectionProvider();
@@ -173,9 +193,82 @@ void main() {
expect(find.byType(ConnectionDialog), findsNothing);
});
testWidgets('manual TCP connect accepts an IP address from the network tab', (
testWidgets('manual TCP connect can cancel discovery and use a custom port', (
tester,
) async {
tester.view.physicalSize = const Size(1200, 1600);
tester.view.devicePixelRatio = 1;
addTearDown(tester.view.reset);
final connectionProvider = _TcpConnectableFakeConnectionProvider();
final networkScanner = _FakeNetworkScannerService(initiallyScanning: true);
await tester.pumpWidget(
ChangeNotifierProvider<ConnectionProvider>.value(
value: connectionProvider,
child: MaterialApp(
localizationsDelegates: AppLocalizations.localizationsDelegates,
supportedLocales: AppLocalizations.supportedLocales,
home: Builder(
builder: (context) => Scaffold(
body: Center(
child: FilledButton(
onPressed: () {
showModalBottomSheet<Object?>(
context: context,
isScrollControlled: true,
builder: (_) => ConnectionDialog(
networkScanner: networkScanner,
),
);
},
child: const Text('Open'),
),
),
),
),
),
),
);
await tester.tap(find.text('Open'));
await tester.pumpAndSettle();
await tester.tap(find.text('Network'));
await tester.pump();
await tester.pump(const Duration(milliseconds: 100));
final manualEntryButton = find.widgetWithText(
TextButton,
'Cancel and enter manually',
);
final onPressed = tester.widget<TextButton>(manualEntryButton).onPressed;
expect(onPressed, isNotNull);
onPressed!();
await tester.pumpAndSettle();
expect(networkScanner.stopScanCalls, greaterThanOrEqualTo(1));
await tester.enterText(find.widgetWithText(TextField, 'IP address'), '192.168.1.42');
await tester.enterText(find.widgetWithText(TextField, 'TCP port'), '6001');
await tester.tap(find.widgetWithText(FilledButton, 'Connect'));
await tester.pumpAndSettle();
expect(connectionProvider.connectTcpCalls, 1);
expect(connectionProvider.connectedHost, '192.168.1.42');
expect(connectionProvider.connectedPort, 6001);
expect(find.byType(ConnectionDialog), findsNothing);
});
testWidgets('recent servers show saved metadata and can reconnect', (
tester,
) async {
SharedPreferences.setMockInitialValues({
'recent_tcp_connections_v1': [
'{"name":"Mesh Node Alpha","host":"10.0.0.5","port":5001,"lastUsedAt":"2026-04-18T12:00:00.000Z"}',
],
});
final connectionProvider = _TcpConnectableFakeConnectionProvider();
final networkScanner = _FakeNetworkScannerService();
@@ -213,18 +306,15 @@ void main() {
await tester.tap(find.text('Network'));
await tester.pumpAndSettle();
expect(networkScanner.scanCalls, 1);
expect(find.text('Recently used'), findsOneWidget);
expect(find.text('Mesh Node Alpha'), findsOneWidget);
expect(find.text('10.0.0.5:5001'), findsOneWidget);
await tester.tap(find.byTooltip('Add IP address'));
await tester.pumpAndSettle();
await tester.enterText(find.byType(TextField), '192.168.1.42');
await tester.tap(find.widgetWithText(FilledButton, 'Connect'));
await tester.tap(find.widgetWithText(FilledButton, 'Connect').first);
await tester.pumpAndSettle();
expect(connectionProvider.connectTcpCalls, 1);
expect(connectionProvider.connectedHost, '192.168.1.42');
expect(connectionProvider.connectedPort, NetworkScannerService.defaultPort);
expect(find.byType(ConnectionDialog), findsNothing);
expect(connectionProvider.connectedHost, '10.0.0.5');
expect(connectionProvider.connectedPort, 5001);
});
}