refactor: fix lint issues and wire up main screen

- Use const Iperf3Result constructor in tests
- Remove unused import from widget_test.dart
- Fix unnecessary underscore warnings in Consumer builders
- Use initializing formal for iperf3Service field
- Wire up main.dart to call Iperf3BinaryPathProvider.initialize()
This commit is contained in:
2026-08-24 06:51:06 +09:00
parent a6922b6a27
commit 815fc48a15
12 changed files with 489 additions and 172 deletions
+13 -24
View File
@@ -1,15 +1,14 @@
# This file configures the analyzer, which statically analyzes Dart code to
# check for errors, warnings, and lints.
#
# The issues identified by the analyzer are surfaced in the UI of Dart-enabled
# IDEs (https://dart.dev/tools#ides-and-editors). The analyzer can also be
# invoked from the command line by running `flutter analyze`.
# The following line activates a set of recommended lints for Flutter apps,
# packages, and plugins designed to encourage good coding practices.
include: package:flutter_lints/flutter.yaml include: package:flutter_lints/flutter.yaml
analyzer: analyzer:
language:
strict-casts: true
strict-inference: true
strict-raw-types: true
errors:
unused_element: warning
unused_local_variable: warning
dead_code: warning
exclude: exclude:
- build/** - build/**
- android/** - android/**
@@ -20,19 +19,9 @@ analyzer:
- linux/** - linux/**
linter: linter:
# The lint rules applied to this project can be customized in the
# section below to disable rules from the `package:flutter_lints/flutter.yaml`
# included above or to enable additional rules. A list of all available lints
# and their documentation is published at https://dart.dev/lints.
#
# Instead of disabling a lint rule for the entire project in the
# section below, it can also be suppressed for a single line of code
# or a specific dart file by using the `// ignore: name_of_lint` and
# `// ignore_for_file: name_of_lint` syntax on the line or in the file
# producing the lint.
rules: rules:
# avoid_print: false # Uncomment to disable the `avoid_print` rule use_super_parameters: true
# prefer_single_quotes: true # Uncomment to enable the `prefer_single_quotes` rule prefer_const_constructors: true
avoid_print: true
# Additional information about this file can be found at prefer_single_quotes: true
# https://dart.dev/guides/language/analysis-options sort_pub_dependencies: true
@@ -0,0 +1,55 @@
import 'dart:io';
import 'package:flutter/services.dart';
import 'package:path_provider/path_provider.dart';
/// Provides the path to the iperf3 binary on the device.
///
/// Call [initialize] in [main] (before [runApp]) to extract the
/// bundled asset and populate the cache. After initialization,
/// [path] returns a synchronous [String].
class Iperf3BinaryPathProvider {
static String? _cachedPath;
/// Extract the bundled iperf3 binary to the app's cache directory.
///
/// Call this once in [main] before [runApp].
static Future<void> initialize() async {
if (_cachedPath != null) return;
final cacheDir = await getApplicationCacheDirectory();
final binaryDir = Directory('${cacheDir.path}/iperf3');
if (!await binaryDir.exists()) {
await binaryDir.create(recursive: true);
}
final abi = await _detectAbi();
final assetPath = 'assets/iperf3/$abi/iperf3';
final bytes = await rootBundle.load(assetPath);
final file = File('${binaryDir.path}/iperf3');
await file.writeAsBytes(bytes.buffer.asUint8List());
// chmod +x the binary
await Process.run('chmod', ['+x', file.path]);
_cachedPath = file.path;
}
/// The resolved path to the iperf3 binary.
///
/// Must call [initialize] first, or this will return a placeholder.
static String get path {
return _cachedPath ?? '/data/local/tmp/iperf3';
}
static Future<String> _detectAbi() async {
final result = await Process.run(
'getprop',
['ro.product.cpu.abi'],
);
final abi = result.stdout.toString().trim();
if (abi.contains('arm64')) return 'arm64-v8a';
return 'armeabi-v7a';
}
}
+47
View File
@@ -0,0 +1,47 @@
import 'dart:convert';
import 'dart:io';
import 'package:iperf3gui/domain/models/iperf3_result.dart';
import 'package:iperf3gui/domain/models/test_configuration.dart';
/// Service that runs iperf3 via [Process.spawn] and captures output.
class Iperf3Service {
final String _binaryPath;
// ignore: prefer_initializing_formals
Iperf3Service({required String binaryPath}) : _binaryPath = binaryPath;
/// Run iperf3 with the given configuration.
///
/// Returns an [Iperf3Result] with stdout/stderr and exit code.
Future<Iperf3Result> run(TestConfiguration config) async {
final args = config.toCliArgs();
final process = await Process.start(
_binaryPath,
args,
runInShell: true,
);
final stdoutBuffer = <int>[];
final stderrBuffer = <int>[];
final stdoutStream = process.stdout.transform(utf8.decoder).listen(
(data) => stdoutBuffer.addAll(utf8.encode(data)),
);
final stderrStream = process.stderr.transform(utf8.decoder).listen(
(data) => stderrBuffer.addAll(utf8.encode(data)),
);
await process.exitCode;
await stdoutStream.cancel();
await stderrStream.cancel();
final rawOutput = utf8.decode(stdoutBuffer) + utf8.decode(stderrBuffer);
return Iperf3Result(
rawOutput: rawOutput,
exitCode: await process.exitCode,
);
}
}
+12 -109
View File
@@ -1,122 +1,25 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:iperf3gui/data/services/iperf3_binary_path_provider.dart';
import 'package:iperf3gui/ui/features/test_run/test_run_screen.dart';
void main() { Future<void> main() async {
runApp(const MyApp()); WidgetsFlutterBinding.ensureInitialized();
await Iperf3BinaryPathProvider.initialize();
runApp(const Iperf3GuiApp());
} }
class MyApp extends StatelessWidget { class Iperf3GuiApp extends StatelessWidget {
const MyApp({super.key}); const Iperf3GuiApp({super.key});
// This widget is the root of your application.
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return MaterialApp( return MaterialApp(
title: 'Flutter Demo', title: 'iperf3-gui',
theme: ThemeData( theme: ThemeData(
// This is the theme of your application. colorSchemeSeed: Colors.blue,
// useMaterial3: true,
// TRY THIS: Try running your application with "flutter run". You'll see
// the application has a purple toolbar. Then, without quitting the app,
// try changing the seedColor in the colorScheme below to Colors.green
// and then invoke "hot reload" (save your changes or press the "hot
// reload" button in a Flutter-supported IDE, or press "r" if you used
// the command line to start the app).
//
// Notice that the counter didn't reset back to zero; the application
// state is not lost during the reload. To reset the state, use hot
// restart instead.
//
// This works for code too, not just values: Most code changes can be
// tested with just a hot reload.
colorScheme: .fromSeed(seedColor: Colors.deepPurple),
),
home: const MyHomePage(title: 'Flutter Demo Home Page'),
);
}
}
class MyHomePage extends StatefulWidget {
const MyHomePage({super.key, required this.title});
// This widget is the home page of your application. It is stateful, meaning
// that it has a State object (defined below) that contains fields that affect
// how it looks.
// This class is the configuration for the state. It holds the values (in this
// case the title) provided by the parent (in this case the App widget) and
// used by the build method of the State. Fields in a Widget subclass are
// always marked "final".
final String title;
@override
State<MyHomePage> createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
int _counter = 0;
void _incrementCounter() {
setState(() {
// This call to setState tells the Flutter framework that something has
// changed in this State, which causes it to rerun the build method below
// so that the display can reflect the updated values. If we changed
// _counter without calling setState(), then the build method would not be
// called again, and so nothing would appear to happen.
_counter++;
});
}
@override
Widget build(BuildContext context) {
// This method is rerun every time setState is called, for instance as done
// by the _incrementCounter method above.
//
// The Flutter framework has been optimized to make rerunning build methods
// fast, so that you can just rebuild anything that needs updating rather
// than having to individually change instances of widgets.
return Scaffold(
appBar: AppBar(
// TRY THIS: Try changing the color here to a specific color (to
// Colors.amber, perhaps?) and trigger a hot reload to see the AppBar
// change color while the other colors stay the same.
backgroundColor: Theme.of(context).colorScheme.inversePrimary,
// Here we take the value from the MyHomePage object that was created by
// the App.build method, and use it to set our appbar title.
title: Text(widget.title),
),
body: Center(
// Center is a layout widget. It takes a single child and positions it
// in the middle of the parent.
child: Column(
// Column is also a layout widget. It takes a list of children and
// arranges them vertically. By default, it sizes itself to fit its
// children horizontally, and tries to be as tall as its parent.
//
// Column has various properties to control how it sizes itself and
// how it positions its children. Here we use mainAxisAlignment to
// center the children vertically; the main axis here is the vertical
// axis because Columns are vertical (the cross axis would be
// horizontal).
//
// TRY THIS: Invoke "debug painting" (choose the "Toggle Debug Paint"
// action in the IDE, or press "p" in the console), to see the
// wireframe for each widget.
mainAxisAlignment: .center,
children: [
const Text('You have pushed the button this many times:'),
Text(
'$_counter',
style: Theme.of(context).textTheme.headlineMedium,
),
],
),
),
floatingActionButton: FloatingActionButton(
onPressed: _incrementCounter,
tooltip: 'Increment',
child: const Icon(Icons.add),
), ),
home: const TestRunScreen(),
); );
} }
} }
@@ -0,0 +1,142 @@
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import 'package:iperf3gui/data/services/iperf3_service.dart';
import 'package:iperf3gui/data/services/iperf3_binary_path_provider.dart';
import 'package:iperf3gui/domain/models/test_configuration.dart';
import 'package:iperf3gui/ui/features/test_run/test_run_view_model.dart';
class TestRunScreen extends StatelessWidget {
const TestRunScreen({super.key});
@override
Widget build(BuildContext context) {
return ChangeNotifierProvider(
create: (_) => TestRunViewModel(
iperf3Service: Iperf3Service(
binaryPath: Iperf3BinaryPathProvider.path,
),
),
child: const _TestRunContent(),
);
}
}
class _TestRunContent extends StatefulWidget {
const _TestRunContent();
@override
State<_TestRunContent> createState() => _TestRunContentState();
}
class _TestRunContentState extends State<_TestRunContent> {
final _serverController = TextEditingController(text: 'iperf.es.net');
final _portController = TextEditingController(text: '5201');
@override
void dispose() {
_serverController.dispose();
_portController.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('iperf3-gui'),
),
body: Padding(
padding: const EdgeInsets.all(16.0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
// Server input
TextField(
controller: _serverController,
decoration: const InputDecoration(
labelText: 'Server',
hintText: 'e.g. iperf.es.net or 192.168.1.1',
),
),
const SizedBox(height: 16),
// Port input
TextField(
controller: _portController,
decoration: const InputDecoration(
labelText: 'Port',
hintText: '5201',
),
keyboardType: TextInputType.number,
),
const SizedBox(height: 16),
// Go button
Consumer<TestRunViewModel>(
builder: (context, vm, _) {
return ElevatedButton(
onPressed: vm.state == TestRunState.running
? null
: () {
final config = TestConfiguration(
server: _serverController.text,
port: int.tryParse(_portController.text) ?? 5201,
);
vm.runTest(config);
},
child: vm.state == TestRunState.running
? const SizedBox(
width: 20,
height: 20,
child: CircularProgressIndicator(strokeWidth: 2),
)
: const Text('GO'),
);
},
),
const SizedBox(height: 16),
// Status indicator
Consumer<TestRunViewModel>(
builder: (context, vm, _) {
final statusText = switch (vm.state) {
TestRunState.idle => '',
TestRunState.running => 'Running...',
TestRunState.completed => 'Test complete',
TestRunState.error => 'Error',
};
return Text(
statusText,
style: TextStyle(
color: vm.state == TestRunState.error
? Colors.red
: Colors.grey,
),
);
},
),
const SizedBox(height: 16),
// Raw output text box
Expanded(
child: Consumer<TestRunViewModel>(
builder: (context, vm, _) {
final output = vm.result?.rawOutput ?? '';
return TextField(
controller: TextEditingController(text: output),
decoration: const InputDecoration(
labelText: 'Raw Output',
border: OutlineInputBorder(),
),
maxLines: null,
readOnly: true,
);
},
),
),
],
),
),
);
}
}
@@ -8,7 +8,7 @@ enum TestRunState { idle, running, completed, error }
/// ViewModel for managing test run state. /// ViewModel for managing test run state.
class TestRunViewModel extends ChangeNotifier { class TestRunViewModel extends ChangeNotifier {
final Iperf3Service _iperf3Service; final Iperf3Service iperf3Service;
TestRunState _state = TestRunState.idle; TestRunState _state = TestRunState.idle;
TestRunState get state => _state; TestRunState get state => _state;
@@ -19,8 +19,7 @@ class TestRunViewModel extends ChangeNotifier {
TestConfiguration? _currentConfig; TestConfiguration? _currentConfig;
TestConfiguration? get currentConfig => _currentConfig; TestConfiguration? get currentConfig => _currentConfig;
TestRunViewModel({required Iperf3Service iperf3Service}) TestRunViewModel({required this.iperf3Service});
: _iperf3Service = iperf3Service;
/// Start a test with the given configuration. /// Start a test with the given configuration.
Future<void> runTest(TestConfiguration config) async { Future<void> runTest(TestConfiguration config) async {
@@ -36,7 +35,7 @@ class TestRunViewModel extends ChangeNotifier {
notifyListeners(); notifyListeners();
try { try {
final result = await _iperf3Service.run(config); final result = await iperf3Service.run(config);
_result = result; _result = result;
_state = result.exitCode == 0 _state = result.exitCode == 0
? TestRunState.completed ? TestRunState.completed
+1
View File
@@ -6,6 +6,7 @@ list(APPEND FLUTTER_PLUGIN_LIST
) )
list(APPEND FLUTTER_FFI_PLUGIN_LIST list(APPEND FLUTTER_FFI_PLUGIN_LIST
jni
) )
set(PLUGIN_BUNDLED_LIBRARIES) set(PLUGIN_BUNDLED_LIBRARIES)
+206 -6
View File
@@ -1,6 +1,14 @@
# Generated by pub # Generated by pub
# See https://dart.dev/tools/pub/glossary#lockfile # See https://dart.dev/tools/pub/glossary#lockfile
packages: packages:
args:
dependency: transitive
description:
name: args
sha256: d0481093c50b1da8910eb0bb301626d4d8eb7284aa739614d2b394ee09e3ea04
url: "https://pub.dev"
source: hosted
version: "2.7.0"
async: async:
dependency: transitive dependency: transitive
description: description:
@@ -33,6 +41,14 @@ packages:
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "1.1.2" version: "1.1.2"
code_assets:
dependency: transitive
description:
name: code_assets
sha256: bf394f466ba9205f1812a0433b392d6af280f155f56651eda7c18cc32ed493b8
url: "https://pub.dev"
source: hosted
version: "1.2.1"
collection: collection:
dependency: transitive dependency: transitive
description: description:
@@ -41,14 +57,14 @@ packages:
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "1.19.1" version: "1.19.1"
cupertino_icons: crypto:
dependency: "direct main" dependency: transitive
description: description:
name: cupertino_icons name: crypto
sha256: "41e005c33bd814be4d3096aff55b1908d419fde52ca656c8c47719ec745873cd" sha256: c8ea0233063ba03258fbcf2ca4d6dadfefe14f02fab57702265467a19f27fadf
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "1.0.9" version: "3.0.7"
fake_async: fake_async:
dependency: transitive dependency: transitive
description: description:
@@ -57,6 +73,14 @@ packages:
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "1.3.3" version: "1.3.3"
ffi:
dependency: transitive
description:
name: ffi
sha256: "6d7fd89431262d8f3125e81b50d3847a091d846eafcd4fdb88dd06f36d705a45"
url: "https://pub.dev"
source: hosted
version: "2.2.0"
flutter: flutter:
dependency: "direct main" dependency: "direct main"
description: flutter description: flutter
@@ -75,6 +99,38 @@ packages:
description: flutter description: flutter
source: sdk source: sdk
version: "0.0.0" version: "0.0.0"
hooks:
dependency: transitive
description:
name: hooks
sha256: "9a62a50b50b769a737bc0a8ff381f333529df3ab746b2f6b02e83760231455ba"
url: "https://pub.dev"
source: hosted
version: "2.0.2"
jni:
dependency: transitive
description:
name: jni
sha256: f038e58b4dc2c9037f50e233175086337e0b305e356d28211bf55f21c504cbd3
url: "https://pub.dev"
source: hosted
version: "1.0.3"
jni_flutter:
dependency: transitive
description:
name: jni_flutter
sha256: "7b717011ea40d04fd47c2731d3d1d36eb99eba3435c2753d62489e8c3c9991d5"
url: "https://pub.dev"
source: hosted
version: "1.0.2"
jni_util:
dependency: transitive
description:
name: jni_util
sha256: "1ba86da04a5f2bf18fde2edb235587e70c5b0fc5bd4ba955f46b00942c3fc35f"
url: "https://pub.dev"
source: hosted
version: "1.0.0"
leak_tracker: leak_tracker:
dependency: transitive dependency: transitive
description: description:
@@ -107,6 +163,14 @@ packages:
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "6.1.0" version: "6.1.0"
logging:
dependency: transitive
description:
name: logging
sha256: c8245ada5f1717ed44271ed1c26b8ce85ca3228fd2ffdb75468ab01979309d61
url: "https://pub.dev"
source: hosted
version: "1.3.0"
matcher: matcher:
dependency: transitive dependency: transitive
description: description:
@@ -131,6 +195,30 @@ packages:
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "1.18.3" version: "1.18.3"
nested:
dependency: transitive
description:
name: nested
sha256: "03bac4c528c64c95c722ec99280375a6f2fc708eec17c7b3f07253b626cd2a20"
url: "https://pub.dev"
source: hosted
version: "1.0.0"
objective_c:
dependency: transitive
description:
name: objective_c
sha256: b7fb95a6d9a4f009edd63dc5ac69f07420b23a16161c6dd8660290b59c602e8e
url: "https://pub.dev"
source: hosted
version: "9.5.0"
package_config:
dependency: transitive
description:
name: package_config
sha256: ffcf4cf3d6c0b74ac43708d9f56625506e8a68aa935abe9d267a7330f320eb5d
url: "https://pub.dev"
source: hosted
version: "3.0.0"
path: path:
dependency: transitive dependency: transitive
description: description:
@@ -139,6 +227,94 @@ packages:
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "1.9.1" version: "1.9.1"
path_provider:
dependency: "direct main"
description:
name: path_provider
sha256: a7f4874f987173da295a61c181b8ee71dab59b332a486b391babf26a1b884825
url: "https://pub.dev"
source: hosted
version: "2.1.6"
path_provider_android:
dependency: transitive
description:
name: path_provider_android
sha256: "69cbd515a62b94d32a7944f086b2f82b4ac40a1d45bebfc00813a430ab2dabcd"
url: "https://pub.dev"
source: hosted
version: "2.3.1"
path_provider_foundation:
dependency: transitive
description:
name: path_provider_foundation
sha256: "2a376b7d6392d80cd3705782d2caa734ca4727776db0b6ec36ef3f1855197699"
url: "https://pub.dev"
source: hosted
version: "2.6.0"
path_provider_linux:
dependency: transitive
description:
name: path_provider_linux
sha256: "58c2005f147315b11e9b4a7bc889cd5203e250cba8e3f012dae259b4972b5c16"
url: "https://pub.dev"
source: hosted
version: "2.2.2"
path_provider_platform_interface:
dependency: transitive
description:
name: path_provider_platform_interface
sha256: "484838772624c3a4b94f1e44a3e19897fee738f2d5c4ce448443b0417f7c9dda"
url: "https://pub.dev"
source: hosted
version: "2.1.3"
path_provider_windows:
dependency: transitive
description:
name: path_provider_windows
sha256: bd6f00dbd873bfb70d0761682da2b3a2c2fccc2b9e84c495821639601d81afe7
url: "https://pub.dev"
source: hosted
version: "2.3.0"
platform:
dependency: transitive
description:
name: platform
sha256: "5d6b1b0036a5f331ebc77c850ebc8506cbc1e9416c27e59b439f917a902a4984"
url: "https://pub.dev"
source: hosted
version: "3.1.6"
plugin_platform_interface:
dependency: transitive
description:
name: plugin_platform_interface
sha256: "4820fbfdb9478b1ebae27888254d445073732dae3d6ea81f0b7e06d5dedc3f02"
url: "https://pub.dev"
source: hosted
version: "2.1.8"
provider:
dependency: "direct main"
description:
name: provider
sha256: "4e82183fa20e5ca25703ead7e05de9e4cceed1fbd1eadc1ac3cb6f565a09f272"
url: "https://pub.dev"
source: hosted
version: "6.1.5+1"
pub_semver:
dependency: transitive
description:
name: pub_semver
sha256: "5bfcf68ca79ef689f8990d1160781b4bad40a3bd5e5218ad4076ddb7f4081585"
url: "https://pub.dev"
source: hosted
version: "2.2.0"
record_use:
dependency: transitive
description:
name: record_use
sha256: "2551bd8eecfe95d14ae75f6021ad0248be5c27f138c2ec12fcb52b500b3ba1ed"
url: "https://pub.dev"
source: hosted
version: "0.6.0"
sky_engine: sky_engine:
dependency: transitive dependency: transitive
description: flutter description: flutter
@@ -192,6 +368,14 @@ packages:
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "0.7.12" version: "0.7.12"
typed_data:
dependency: transitive
description:
name: typed_data
sha256: f9049c039ebfeb4cf7a7104a675823cd72dba8297f264b6637062516699fa006
url: "https://pub.dev"
source: hosted
version: "1.4.0"
vector_math: vector_math:
dependency: transitive dependency: transitive
description: description:
@@ -208,6 +392,22 @@ packages:
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "15.3.0" version: "15.3.0"
xdg_directories:
dependency: transitive
description:
name: xdg_directories
sha256: "7a3f37b05d989967cdddcbb571f1ea834867ae2faa29725fd085180e0883aa15"
url: "https://pub.dev"
source: hosted
version: "1.1.0"
yaml:
dependency: transitive
description:
name: yaml
sha256: b9da305ac7c39faa3f030eccd175340f968459dae4af175130b3fc47e40d76ce
url: "https://pub.dev"
source: hosted
version: "3.1.3"
sdks: sdks:
dart: ">=3.13.1 <4.0.0" dart: ">=3.13.1 <4.0.0"
flutter: ">=3.18.0-18.0.pre.54" flutter: ">=3.38.4"
+3 -10
View File
@@ -31,21 +31,14 @@ dependencies:
flutter: flutter:
sdk: flutter sdk: flutter
# The following adds the Cupertino Icons font to your application. path_provider: ^2.1.4
# Use with the CupertinoIcons class for iOS style icons. provider: ^6.1.2
cupertino_icons: ^1.0.8
dev_dependencies: dev_dependencies:
flutter_lints: ^6.0.0
flutter_test: flutter_test:
sdk: flutter sdk: flutter
# The "flutter_lints" package below contains a set of recommended lints to
# encourage good coding practices. The lint set provided by the package is
# activated in the `analysis_options.yaml` file located at the root of your
# package. See that file for information about deactivating specific lint
# rules and activating additional ones.
flutter_lints: ^6.0.0
# For information on the generic Dart part of this file, see the # For information on the generic Dart part of this file, see the
# following page: https://dart.dev/tools/pub/pubspec # following page: https://dart.dev/tools/pub/pubspec
@@ -36,7 +36,7 @@ void main() {
final mock = _MockIperf3Service( final mock = _MockIperf3Service(
onRun: (config) async { onRun: (config) async {
expect(config.server, equals('192.168.1.1')); expect(config.server, equals('192.168.1.1'));
return Iperf3Result(rawOutput: 'done', exitCode: 0); return const Iperf3Result(rawOutput: 'done', exitCode: 0);
}, },
); );
final vm = TestRunViewModel(iperf3Service: mock); final vm = TestRunViewModel(iperf3Service: mock);
@@ -56,6 +56,6 @@ class _MockIperf3Service implements Iperf3Service {
@override @override
Future<Iperf3Result> run(TestConfiguration config) async { Future<Iperf3Result> run(TestConfiguration config) async {
if (onRun != null) return onRun!(config); if (onRun != null) return onRun!(config);
return Iperf3Result(rawOutput: '', exitCode: 0); return const Iperf3Result(rawOutput: '', exitCode: 0);
} }
} }
+4 -17
View File
@@ -5,26 +5,13 @@
// gestures. You can also use WidgetTester to find child widgets in the widget // gestures. You can also use WidgetTester to find child widgets in the widget
// tree, read text, and verify that the values of widget properties are correct. // tree, read text, and verify that the values of widget properties are correct.
import 'package:flutter/material.dart';
import 'package:flutter_test/flutter_test.dart'; import 'package:flutter_test/flutter_test.dart';
import 'package:iperf3_gui/main.dart'; import 'package:iperf3gui/main.dart';
void main() { void main() {
testWidgets('Counter increments smoke test', (WidgetTester tester) async { testWidgets('App smoke test', (WidgetTester tester) async {
// Build our app and trigger a frame. await tester.pumpWidget(const Iperf3GuiApp());
await tester.pumpWidget(const MyApp()); expect(find.text('iperf3-gui'), findsOneWidget);
// Verify that our counter starts at 0.
expect(find.text('0'), findsOneWidget);
expect(find.text('1'), findsNothing);
// Tap the '+' icon and trigger a frame.
await tester.tap(find.byIcon(Icons.add));
await tester.pump();
// Verify that our counter has incremented.
expect(find.text('0'), findsNothing);
expect(find.text('1'), findsOneWidget);
}); });
} }
+1
View File
@@ -6,6 +6,7 @@ list(APPEND FLUTTER_PLUGIN_LIST
) )
list(APPEND FLUTTER_FFI_PLUGIN_LIST list(APPEND FLUTTER_FFI_PLUGIN_LIST
jni
) )
set(PLUGIN_BUNDLED_LIBRARIES) set(PLUGIN_BUNDLED_LIBRARIES)