Files
jiu/client/test/support/golden_harness.dart
T
wangjia 1b43dfc75d refactor(client): Phase 1 照原型重建 app 外壳(顶栏两级品牌 + 主题器 + 通知铃 + 分组侧栏 + 状态栏)
照 shell.js/statusbar.js/atoms.css 把外壳从「旧单品牌+换色」彻底重建为原型样式:
- 顶栏:岩美酒库(线描山形 mark CustomPaint,软件品牌) │分隔│ 鼎晟酒行(门店字标+店名);
  右侧 ThemePickerPill(主题器,从系统设置迁来,A/B/C 切换) + NotificationBell(通知)
- 侧栏(218w):分组 单据/经营/系统(nav-label) + nav(active 高亮) + 底部 side-user(头像/姓名/店·角色→菜单)
- 状态栏:AppStatusBar(原型 .statusbar,AppChrome 主题无关色):店号/操作员/登录/当前(走字) · 连接/版本/授权
- 保留全部既有行为:更新/强制更新/授权横幅与弹窗、离线横幅、心跳、apiMessage、窄屏 Drawer
- 新增 widgets/theme_picker_pill.dart · notification_bell.dart · app_status_bar.dart
- golden:加载真实 MaterialIcons 字体(消图标豆腐块,全 golden 受益);
  app_shell golden 用 9 分支+冻结时钟(可测试缝)确定化,对齐原型数据(鼎晟酒行/王经理/库存激活)
- fidelity:外壳 vs 原型 diff 3.5% ≤ 阈,montage 目检 chrome 与原型一致

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01TSKEiHsvauyxYUW2itzUXX
2026-06-25 18:02:38 +08:00

100 lines
3.6 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();
// 真实 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 */}
}
_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,
);
});
}
}