test(client): L1 数值格式化纯函数单测 + 从 widget 抽出(支柱3)

- lib/util/format.dart:抽出 formatSpeed/formatDuration(原私有于 connect_page
  _speed / connect_button _fmt,混在 widget 里没法单测)
- connect_page/connect_button 改用公共纯函数(渲染输出不变)
- test/unit/format_test.dart:10 用例覆盖速率 B/s→GB/s 各单位边界 +
  时长 HH:MM:SS 补零/小时不封顶/取模 60
- 验证:format 10 + widget 8 全过、analyze 无 error/warning

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
wangjia
2026-06-25 12:06:34 +08:00
parent 64f96fe018
commit cb6fc25bc9
4 changed files with 84 additions and 19 deletions
+3 -13
View File
@@ -4,6 +4,7 @@ import 'package:flutter_riverpod/flutter_riverpod.dart';
import '../bridge/vpn_bridge.dart';
import '../core/responsive/form_factor.dart';
import '../util/format.dart';
import '../l10n/app_text.dart';
import '../models/node.dart';
import '../pangolin_theme.dart';
@@ -33,8 +34,8 @@ class ConnectPage extends ConsumerWidget {
final isFree = ref.watch(isFreePlanProvider);
final quota = ref.watch(quotaProvider);
final stats = ref.watch(vpnStatsProvider).valueOrNull;
final down = _speed(stats?.downloadSpeed);
final up = _speed(stats?.uploadSpeed);
final down = formatSpeed(stats?.downloadSpeed);
final up = formatSpeed(stats?.uploadSpeed);
// 延迟来源:连接后优先用内核 urltest 实测(代理真实 RTT);urltest 还没探过时
// 回退用节点页的 TCP 探针 ping,尽量不显示 —。都没有才 —。
var livePing = conn.phase == VpnPhase.on ? _bestUrltest(stats) : node.ping;
@@ -202,17 +203,6 @@ class ConnectPage extends ConsumerWidget {
}
}
/// 字节/秒 → 动态单位 (数值, 单位):B/s · KB/s · MB/s · GB/s(小数据也看得清)。
/// null = 未取到,显示 —。
(String, String) _speed(double? bytesPerSec) {
if (bytesPerSec == null) return ('', 'KB/s');
final b = bytesPerSec;
if (b < 1024) return (b.toStringAsFixed(0), 'B/s');
if (b < 1024 * 1024) return ((b / 1024).toStringAsFixed(1), 'KB/s');
if (b < 1024 * 1024 * 1024) return ((b / (1024 * 1024)).toStringAsFixed(1), 'MB/s');
return ((b / (1024 * 1024 * 1024)).toStringAsFixed(2), 'GB/s');
}
/// 内核 urltest 各出站中的最小正延迟(ms);无则 0。
int _bestUrltest(VpnStatsEvent? s) {
if (s == null) return 0;