Files
jiu/client/test/support/golden_harness.dart
T
wangjia 904bfbe67d feat(client): Phase 2 库存屏照原型重建(去tab/KPI货值/库存·成本价·单价列)
照原型 inventory.html 把库存屏从「3 tab」重排为单视图:
- 去 3 tab(库存查询/预警/流水)→ 单视图;预警并入 KPI 缺货卡点击筛选;流水移商品详情
- 头部:库存管理 + 共 N 个 SKU·数据实时 + 列设置/导出(盘点/新增入库去掉,走专门菜单)
- KPI 4 卡走全店汇总接口:SKU总数/库存货值(Σqty×进价)/在库数量/缺货预警(可点筛选)
- 表格 12 列:加 库存(低库存染色)/成本价(进价)/单价(售价);操作列只「查看详情」;状态圆点
- 仓库/备注默认隐藏;工具栏 搜索+状态筛选+重置
- 后端 GET /inventory/summary 全店汇总(守 shop_id);前端 model/repository/provider 配套
- golden_harness:monospace 指向 Noto(消等宽数字黑块,全 golden 受益);库存屏存量 grey 清零

后端编译/库存测试通过;前端 analyze 0 error,flutter test 242 通过;代码端颜色闸 0 违规。

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

104 lines
3.8 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();
// 把内置 'monospace' family 也指向 Noto(否则等宽数字/批次号在 golden 渲染成黑块)。
final mono = FontLoader('monospace')
..addFont(rootBundle.load('assets/fonts/NotoSansSC.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 */}
}
_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,
);
});
}
}