d67295234e
价签版式原 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
84 lines
3.1 KiB
Dart
84 lines
3.1 KiB
Dart
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 生成基准)');
|
||
});
|
||
}
|