feat(client): 字体可注入 + 启用 golden 视觉回归闸

- pangolin_theme: PangolinFonts 加 useBundled 开关 + sora/manrope/jetBrainsMono/
  manropeTextTheme 封装(生产走 google_fonts / 测试走 bundled family);
  PangolinText 与 _build 改调封装,移除散落的 GoogleFonts 直接调用
- test/flutter_test_config.dart: 全测试前置,FontLoader 注册 test/fonts 真实
  Sora/Manrope/JetBrainsMono + Lucide 图标字体,置 useBundled=true
- 启用 test/golden(去 @Skip):连接键三态+推荐卡+额度卡×明暗 12 基准图
- tool/visual-diff.md: 标注 golden 严格闸已落地

测试: flutter test 76 通过(原 64+1skip → 76 无 skip), analyze 0 error

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
wangjia
2026-06-16 11:36:50 +08:00
parent 281a6261fb
commit 1340b477a7
16 changed files with 96 additions and 36 deletions
+39
View File
@@ -0,0 +1,39 @@
// flutter_test_config.dart — 全测试前置(Flutter 自动应用于 test/ 下所有测试)。
//
// 启用 PangolinFonts.useBundled,并用 FontLoader 注册 test/fonts/ 的真实字体,
// 让 widget/golden 测试用真字体渲染、且不触发 google_fonts 运行时拉取(避免抛异常)。
import 'dart:async';
import 'dart:io';
import 'dart:typed_data';
import 'package:flutter/services.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:pangolin_vpn/pangolin_theme.dart';
Future<void> testExecutable(FutureOr<void> Function() testMain) async {
TestWidgetsFlutterBinding.ensureInitialized();
PangolinFonts.useBundled = true;
await _load('Sora', const ['test/fonts/Sora-Regular.ttf', 'test/fonts/Sora-SemiBold.ttf', 'test/fonts/Sora-Bold.ttf']);
await _load('Manrope', const [
'test/fonts/Manrope-Regular.ttf',
'test/fonts/Manrope-Medium.ttf',
'test/fonts/Manrope-SemiBold.ttf',
'test/fonts/Manrope-Bold.ttf',
]);
await _load('JetBrains Mono', const ['test/fonts/JetBrainsMono-Regular.ttf']);
// Lucide 图标字体(family 须带 package 前缀以匹配 PangolinIcons 的 fontPackage)。
await _load('packages/lucide_icons/Lucide', const ['packages/lucide_icons_patched/fonts/lucide.ttf']);
await testMain();
}
Future<void> _load(String family, List<String> paths) async {
final loader = FontLoader(family);
for (final p in paths) {
final file = File(p);
if (file.existsSync()) {
final bytes = file.readAsBytesSync();
loader.addFont(Future.value(ByteData.view(bytes.buffer, bytes.offsetInBytes, bytes.lengthInBytes)));
}
}
await loader.load();
}
+3 -15
View File
@@ -1,21 +1,9 @@
// components_golden_test.dart — 关键组件 golden(明/暗两主题)
//
// 覆盖:连接键三态、智能选择推荐卡、免费额度卡。
// 首次生成基准图:`flutter test --update-goldens test/golden`。
//
// TODO: 字体依赖待解决
// google_fonts 在测试环境中使用内部变体字体族名(如 `Manrope_400italic0`),
// 难以通过 FontLoader 预加载。需要:
// 1. 确认 google_fonts v6.3.3 各变体的实际族名
// 2. 在 setUpAll 中用 FontLoader 加载 test/fonts/*.ttf
// 3. 调用 `flutter test --update-goldens test/golden` 生成基准图
// 在此之前,跳过 golden 测试以避免 CI 失败;
// 组件行为已由 test/widget/ 覆盖。
// ignore_for_file: directives_ordering
@Skip('Golden tests need bundled fonts + baseline generation. '
'See TODO above for setup steps.')
library;
// 重新生成基准图:`flutter test --update-goldens test/golden`。
// 字体由 test/flutter_test_config.dart 经 FontLoader 注册 + PangolinFonts.useBundled
// 提供真实 Sora/Manrope/JetBrainsMono,无需 google_fonts 运行时拉取
import 'package:flutter/material.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:pangolin_vpn/l10n/strings_zh.dart';
Binary file not shown.

Before

Width:  |  Height:  |  Size: 25 KiB

After

Width:  |  Height:  |  Size: 26 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 18 KiB

After

Width:  |  Height:  |  Size: 19 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 15 KiB

After

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 13 KiB

After

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 27 KiB

After

Width:  |  Height:  |  Size: 31 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 19 KiB

After

Width:  |  Height:  |  Size: 22 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 8.0 KiB

After

Width:  |  Height:  |  Size: 9.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 8.4 KiB

After

Width:  |  Height:  |  Size: 9.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.7 KiB

After

Width:  |  Height:  |  Size: 8.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.1 KiB

After

Width:  |  Height:  |  Size: 8.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 8.4 KiB

After

Width:  |  Height:  |  Size: 9.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 8.4 KiB

After

Width:  |  Height:  |  Size: 9.2 KiB