refactor: move run accumulation out of the view model into the domain
The view model was doing two jobs. Alongside presentation state it held what happened during a run — the interval series, the rule that iperf3's omitted warm-up intervals do not count, the retained end summary — and the screen computed mean and peak inline in a widget builder. All of that is the part that has to be correct, and all of it would have been rewritten by anyone replacing the UI. Iperf3Session accumulates a run in the domain layer: samples, totals, errors, whether it was stopped, and the statistics over them. The view model now feeds it events and keeps only a formatted transcript and the repaint schedule; the screen reads every displayed number off the session and derives none. Two details the session now owns rather than leaving to a caller. Omitted intervals are excluded, so a warm-up cannot drag the mean down. And totalRetransmits stays null when no interval reported a count — UDP does not measure retransmits, and "not measured" must not render as zero. The aggregation tests move with the logic, into a pure-Dart test with no widgets or bindings involved. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
@@ -82,48 +82,13 @@ void main() {
|
||||
expect(vm.output, isNot(contains('ignored')));
|
||||
});
|
||||
|
||||
test('intervals are collected as structured samples', () async {
|
||||
test('events are fed into the session', () async {
|
||||
final mock = FakeIperf3Runner();
|
||||
mock.onRun = (_) async {
|
||||
mock.emit(const Iperf3Interval(
|
||||
start: 0, end: 1, bytes: 1000, bitsPerSecond: 8000));
|
||||
mock.emit(const Iperf3Interval(
|
||||
start: 1, end: 2, bytes: 2000, bitsPerSecond: 16000));
|
||||
await Future<void>.delayed(Duration.zero);
|
||||
return const Iperf3Result(rawOutput: '', exitCode: 0);
|
||||
};
|
||||
|
||||
final vm = TestRunViewModel(iperf3Service: mock);
|
||||
await vm.runTest(const TestConfiguration(server: '192.168.1.1'));
|
||||
|
||||
expect(vm.intervals, hasLength(2));
|
||||
expect(vm.intervals[1].bitsPerSecond, equals(16000));
|
||||
});
|
||||
|
||||
test('omitted warm-up intervals are excluded from the series', () async {
|
||||
final mock = FakeIperf3Runner();
|
||||
mock.onRun = (_) async {
|
||||
mock.emit(const Iperf3Interval(
|
||||
start: 0, end: 1, bytes: 1, bitsPerSecond: 1, omitted: true));
|
||||
mock.emit(const Iperf3Interval(
|
||||
start: 1, end: 2, bytes: 2000, bitsPerSecond: 16000));
|
||||
await Future<void>.delayed(Duration.zero);
|
||||
return const Iperf3Result(rawOutput: '', exitCode: 0);
|
||||
};
|
||||
|
||||
final vm = TestRunViewModel(iperf3Service: mock);
|
||||
await vm.runTest(const TestConfiguration(server: '192.168.1.1'));
|
||||
|
||||
expect(vm.intervals, hasLength(1));
|
||||
expect(vm.intervals.single.bitsPerSecond, equals(16000));
|
||||
});
|
||||
|
||||
test('end event is retained as the summary', () async {
|
||||
final mock = FakeIperf3Runner();
|
||||
mock.onRun = (_) async {
|
||||
mock.emit(const Iperf3End(
|
||||
sent: Iperf3Total(
|
||||
seconds: 10, bytes: 987654321, bitsPerSecond: 790000000),
|
||||
sent: Iperf3Total(seconds: 1, bytes: 1000, bitsPerSecond: 8000),
|
||||
));
|
||||
await Future<void>.delayed(Duration.zero);
|
||||
return const Iperf3Result(rawOutput: '', exitCode: 0);
|
||||
@@ -132,11 +97,11 @@ void main() {
|
||||
final vm = TestRunViewModel(iperf3Service: mock);
|
||||
await vm.runTest(const TestConfiguration(server: '192.168.1.1'));
|
||||
|
||||
expect(vm.summary?.sent?.bitsPerSecond, equals(790000000));
|
||||
expect(vm.output, contains('790 Mbits/sec'));
|
||||
expect(vm.session.sampleCount, equals(1));
|
||||
expect(vm.session.summary, isNotNull);
|
||||
});
|
||||
|
||||
test('a new run clears the previous interval series', () async {
|
||||
test('a new run clears the previous session', () async {
|
||||
final mock = FakeIperf3Runner();
|
||||
mock.onRun = (_) async {
|
||||
mock.emit(const Iperf3Interval(
|
||||
@@ -147,10 +112,11 @@ void main() {
|
||||
|
||||
final vm = TestRunViewModel(iperf3Service: mock);
|
||||
await vm.runTest(const TestConfiguration(server: '192.168.1.1'));
|
||||
expect(vm.intervals, hasLength(1));
|
||||
expect(vm.session.sampleCount, equals(1));
|
||||
|
||||
await vm.runTest(const TestConfiguration(server: '192.168.1.1'));
|
||||
expect(vm.intervals, hasLength(1), reason: 'not appended to the old run');
|
||||
expect(vm.session.sampleCount, equals(1),
|
||||
reason: 'not appended to the old run');
|
||||
});
|
||||
|
||||
test('falls back to result output when nothing streamed', () async {
|
||||
|
||||
Reference in New Issue
Block a user