Files
jiu/client/test/support/golden_harness.dart
T
wangjia 6238b86dcb feat(client): 登录/注册页照原型重建,ds 真相源组件族统一全部屏
- 登录/注册(login.html/register.html 1:1):两栏卡片+品牌渐变面板+主题小衣服
  pill(onSurface 变体)+记住我(记录并预填最近账号)+原型式 toast 校验;
  登录 fidelity 1.4–2.1% 三主题全绿;注册暂不入闸(少 门店编号/兑换券 字段,
  已记 CONTRACT,screens.mjs 留存根)
- ds 原子补齐:DsToast(.toast 单例)/DsCheck(.check/.agree)/DsButton lg 档/
  DsSelect 替换全部旧 DropdownButton/DsField label 在上/DsInput 后缀与密码形态
- 全屏统一:对话框按钮全 DsButton、盒式输入主题钉死(visualDensity.standard、
  h38、InputDecorationTheme 渗漏修复)、图标全 lucide、JetBrains Mono 三端同源、
  BrandMark 真相源 logo、只读模式写操作全量守卫(WriteGuard+DsToast)
- 出入库列表:版式对齐原型(卡片对齐+搜索框居中)、KPI 近30天滚动、结清后
  失效财务应收应付表;商品编辑抽屉介绍库改搜索下拉、图片双击全屏预览
- 删除旧 UI 死代码:DataTableCard/FormDialog/PageScaffold/SearchChip/
  SelectProductDialog/tabStateProvider
- golden/fidelity:stock-in/out 补注册(9%)、login 入册(8%)、goldens 全量重打;
  修复 pubspec flutter_web_plugins 非法声明(CI pub get 阻断)

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01JJ1g8XV1YhhmHRzhwWEW7o
2026-07-03 09:58:14 +08:00

114 lines
4.4 KiB
Dart
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import 'package:flutter/material.dart';
import 'package:flutter/services.dart' show rootBundle, FontLoader;
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:jiu_client/core/theme/themes.dart';
// 加载真实中文字体(消除 golden 豆腐块),使 golden 能与原型截图做保真 diff。
// 原型截图侧同样注入 Noto Sans SC(见 tools/fidelity.mjs),两边字体一致。
bool _fontsLoaded = false;
Future<void> ensureGoldenFonts() async {
if (_fontsLoaded) return;
final loader = FontLoader('NotoSansSC')
..addFont(rootBundle.load('assets/fonts/NotoSansSC.ttf'));
await loader.load();
// 等宽真相源 JetBrainsMonoAppFonts.mono400+700)——与原型截图注入、
// 真机打包同一字体文件,等宽渲染三方同源(未加载会渲染成黑块 tofu)。
final mono = FontLoader('JetBrainsMono')
..addFont(rootBundle.load('assets/fonts/JetBrainsMono-Regular.ttf'))
..addFont(rootBundle.load('assets/fonts/JetBrainsMono-Bold.ttf'));
await mono.load();
// 真实 Material 图标字体(否则 golden 里图标渲染成豆腐块 □)。
for (final key in const [
'fonts/MaterialIcons-Regular.otf',
'packages/flutter/fonts/MaterialIcons-Regular.otf',
]) {
try {
final f = FontLoader('MaterialIcons')..addFont(rootBundle.load(key));
await f.load();
break;
} catch (_) {/* 换下一个 asset key */}
}
// lucide 图标字体(与原型同款图形库)。
try {
// package 字体在运行时 family 带 packages/<pkg>/ 前缀,FontLoader 须用全名。
final lucide = FontLoader('packages/lucide_icons_flutter/Lucide')
..addFont(
rootBundle.load('packages/lucide_icons_flutter/assets/lucide.ttf'));
await lucide.load();
} catch (_) {/* 包未装时跳过 */}
_fontsLoaded = true;
}
/// 把 NotoSansSC 套到主题文字上(含 hardcoded TextStyle 经 DefaultTextStyle 继承)。
ThemeData withGoldenFont(ThemeData base) => base.copyWith(
textTheme: base.textTheme.apply(fontFamily: 'NotoSansSC'),
primaryTextTheme: base.primaryTextTheme.apply(fontFamily: 'NotoSansSC'),
);
/// design-distill 阶段4Flutter 端)可复用 golden 骨架。
/// 在指定主题 / viewport 下渲染 [child](含 provider 覆盖),比对/更新 golden。
///
/// 验收分两类闸(见 ~/.claude/skills/design-distill/references/flutter-golden.md):
/// - 保真:原型基准 vs 本 golden 用 montage.mjs 人工目检(跨渲染器,不跑严格 diff)。
/// - 回归:本 golden 多主题入库,CI `flutter test` 同渲染器自比抓串色/漏 token。
///
/// 更新基准:flutter test --update-goldens test/golden/<x>_golden_test.dart
///
/// 坑(详见 reference):viewport 逻辑尺寸 = physical ÷ dpr(已按 logical*dpr 设好);
/// 高度给足防 RenderFlex 溢出;测试无中文字体 → CJK 显示豆腐块属正常。
Future<void> pumpGolden(
WidgetTester tester, {
required Widget child,
required String theme,
required String goldenName,
List<Override> overrides = const [],
Size logical = const Size(1280, 900),
double dpr = 2.0,
}) async {
await ensureGoldenFonts();
tester.view.physicalSize = Size(logical.width * dpr, logical.height * dpr);
tester.view.devicePixelRatio = dpr;
addTearDown(tester.view.resetPhysicalSize);
addTearDown(tester.view.resetDevicePixelRatio);
await tester.pumpWidget(ProviderScope(
overrides: overrides,
child: MaterialApp(
debugShowCheckedModeBanner: false,
theme: withGoldenFont(buildTheme(theme)),
home: child,
),
));
await tester.pumpAndSettle();
await expectLater(
find.byType(MaterialApp),
matchesGoldenFile('goldens/$goldenName.png'),
);
}
/// 跑「同一 child × A/B/C 三主题」的 golden 组(回归闸常用)。
void goldenAcrossThemes(
String description, {
required Widget Function() child,
required String goldenPrefix,
List<Override> Function()? overrides,
Size logical = const Size(1280, 900),
double dpr = 2.0,
}) {
for (final theme in const ['a', 'b', 'c']) {
testWidgets('$description · theme $theme', (tester) async {
await pumpGolden(
tester,
child: child(),
theme: theme,
goldenName: '${goldenPrefix}_$theme',
overrides: overrides?.call() ?? const [],
logical: logical,
dpr: dpr,
);
});
}
}