95acf5ea38
FakeIperf3Runner implements Iperf3Runner, so the widget tests exercise the actual widget tree — GO, STOP, the status line, the stat row, validation — with no MethodChannel and no native library. That was impossible while the screen constructed its own Iperf3Service. The view model tests share the same fake instead of each file carrying a double that had to mirror the concrete service. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
90 lines
3.0 KiB
Dart
90 lines
3.0 KiB
Dart
// Widget-level smoke tests.
|
|
//
|
|
// These drive the real UI against a FakeIperf3Runner: no MethodChannel, no
|
|
// native library, no device. That is only possible because the presentation
|
|
// layer depends on the Iperf3Runner interface rather than on Iperf3Service.
|
|
|
|
import 'dart:async';
|
|
|
|
import 'package:flutter/material.dart';
|
|
import 'package:flutter_test/flutter_test.dart';
|
|
|
|
import 'package:iperf3gui/domain/models/iperf3_event.dart';
|
|
import 'package:iperf3gui/domain/models/iperf3_result.dart';
|
|
import 'package:iperf3gui/main.dart';
|
|
|
|
import 'support/fake_iperf3_runner.dart';
|
|
|
|
void main() {
|
|
testWidgets('App smoke test', (WidgetTester tester) async {
|
|
await tester.pumpWidget(Iperf3GuiApp(runner: FakeIperf3Runner()));
|
|
expect(find.text('iperf3-gui'), findsOneWidget);
|
|
expect(find.text('GO'), findsOneWidget);
|
|
expect(find.text('Ready'), findsOneWidget);
|
|
});
|
|
|
|
testWidgets('runs a test and renders streamed results', (tester) async {
|
|
final runner = FakeIperf3Runner();
|
|
runner.onRun = (config) async {
|
|
runner.emit(Iperf3Start(
|
|
targetHost: config.server,
|
|
targetPort: config.port,
|
|
protocol: 'TCP',
|
|
streamCount: config.parallelStreams,
|
|
));
|
|
runner.emit(const Iperf3Interval(
|
|
start: 0,
|
|
end: 1,
|
|
bytes: 98566144,
|
|
bitsPerSecond: 788410352,
|
|
));
|
|
await Future<void>.delayed(Duration.zero);
|
|
return const Iperf3Result(rawOutput: '', exitCode: 0);
|
|
};
|
|
|
|
await tester.pumpWidget(Iperf3GuiApp(runner: runner));
|
|
await tester.tap(find.text('GO'));
|
|
await tester.pumpAndSettle();
|
|
|
|
expect(runner.runCallCount, equals(1));
|
|
expect(find.text('Test complete'), findsOneWidget);
|
|
expect(find.textContaining('788 Mbits/sec'), findsWidgets);
|
|
// The stat row is driven by the parsed interval series.
|
|
expect(find.text('peak'), findsOneWidget);
|
|
});
|
|
|
|
testWidgets('shows a stop button while running and can stop', (tester) async {
|
|
final runner = FakeIperf3Runner();
|
|
final gate = Completer<Iperf3Result>();
|
|
runner.onRun = (_) => gate.future;
|
|
|
|
await tester.pumpWidget(Iperf3GuiApp(runner: runner));
|
|
await tester.tap(find.text('GO'));
|
|
await tester.pump();
|
|
|
|
expect(find.text('STOP'), findsOneWidget);
|
|
expect(find.text('Running…'), findsOneWidget);
|
|
|
|
await tester.tap(find.text('STOP'));
|
|
await tester.pump();
|
|
expect(runner.stopCallCount, equals(1));
|
|
|
|
gate.complete(const Iperf3Result(
|
|
rawOutput: '', exitCode: 0, stopped: true));
|
|
await tester.pumpAndSettle();
|
|
expect(find.text('Stopped'), findsOneWidget);
|
|
});
|
|
|
|
testWidgets('rejects an empty server address', (tester) async {
|
|
final runner = FakeIperf3Runner();
|
|
await tester.pumpWidget(Iperf3GuiApp(runner: runner));
|
|
|
|
await tester.enterText(find.byType(TextField).first, '');
|
|
await tester.tap(find.text('GO'));
|
|
await tester.pumpAndSettle();
|
|
|
|
expect(runner.runCallCount, equals(0));
|
|
expect(find.textContaining('Invalid configuration'), findsOneWidget);
|
|
});
|
|
}
|