Files
jiu/client/test/golden/label_render_golden_test.dart
T
wangjia d67295234e refactor(client): 抽声明式 LabelTemplate 单源,价签渲染改读模型(P0)
价签版式原 100% 硬编码在四条渲染路径。引入 label_template.dart
声明式模型(纸张/打印/抬头/文字栈/QR/条码),builtinDefault() 逐值冻结
重构前常量;画布渲染器 _renderLabelBitmap 与热敏 _printFlatLabelThermal
改为纯读模型,坐标统一到画布权威。facade/web stub 加 template 可选参
(null→默认,现有调用点行为不变)。

新增 label_render_golden 零回归基准:默认模板渲染与重构前逐像素零 diff。
附评审通过的设计文档 label-template-editor.html(5 项决策)。

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Bupi8Kdqkfx2N5acFsHTx5
2026-08-29 00:00:13 +08:00

84 lines
3.1 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 'dart:typed_data';
import 'dart:ui' as ui;
import 'package:flutter_test/flutter_test.dart';
import 'package:jiu_client/core/utils/print_util.dart';
import '../support/golden_harness.dart';
/// 标签价签默认版式「零回归」基准(P0 守法)。
///
/// 目的:`LabelTemplate` 抽单源 + 画布渲染器改读模型(`_renderLabelBitmap`)后,
/// 内置默认模板(`builtinDefault()`)渲染出的价签必须与重构前**逐像素一致**。
///
/// 用法:
/// 基准(必须在动渲染器之前,于当前 main 上生成一次):
/// flutter test --update-goldens test/golden/label_render_golden_test.dart
/// 回归(重构后跑,须零 diff):
/// flutter test test/golden/label_render_golden_test.dart
///
/// 说明:`renderLabelPreview` 走 dart:ui 直画(`Picture.toImage`),不经 widget
/// 管线;在测试 zone 里直接 await 会因无 frame 驱动光栅而挂死,故渲染 + golden
/// 比对整体放进 `tester.runAsync()`。字体复用 `ensureGoldenFonts()` 的 NotoSansSC
/// 与真实渲染同源。QR 用固定棋盘图占位,保证确定性(真实 QR 数据不影响版式几何)。
const _goldenUri = 'goldens/label_render_default.png';
/// 生成一张确定的 QR 占位图(10×10 棋盘 → 240×240 PNG),供版式渲染填充二维码位。
Future<Uint8List> _fixedQrPng() async {
const cells = 10;
const cell = 24.0; // 240×240
final rec = ui.PictureRecorder();
final canvas = ui.Canvas(rec);
final paint = ui.Paint();
for (var y = 0; y < cells; y++) {
for (var x = 0; x < cells; x++) {
paint.color = (x + y) % 2 == 0
? const ui.Color(0xFF000000)
: const ui.Color(0xFFFFFFFF);
canvas.drawRect(ui.Rect.fromLTWH(x * cell, y * cell, cell, cell), paint);
}
}
final img = await rec
.endRecording()
.toImage((cells * cell).round(), (cells * cell).round());
final bd = await img.toByteData(format: ui.ImageByteFormat.png);
return bd!.buffer.asUint8List();
}
void main() {
testWidgets('价签默认版式 · 零回归基准', (tester) async {
await ensureGoldenFonts();
Uint8List? png;
bool comparePassed = false;
await tester.runAsync(() async {
final label = LabelData(
qrBytes: await _fixedQrPng(),
name: '贵州茅台酒',
code: 'P1001',
series: '飞天53度',
spec: '500ml',
productionDate: '2024-05-18',
shopName: '鼎晟酒行',
);
png = await renderLabelPreview(label);
if (png == null) return;
final uri = Uri.parse(_goldenUri);
if (autoUpdateGoldenFiles) {
await goldenFileComparator.update(uri, png!);
comparePassed = true;
} else {
comparePassed = await goldenFileComparator.compare(png!, uri);
}
});
expect(png, isNotNull, reason: 'renderLabelPreview 在测试(VM/桌面)下应返回 PNG');
expect(comparePassed, isTrue,
reason: '默认模板渲染与基准不一致:零回归被破坏(先在重构前 main 上 --update-goldens 生成基准)');
});
}