Files
nj/app/test/features/editor/widgets/stroke_model_test.dart
iven f6d394afb6 test(app): 手写引擎 Canvas 集成测试 — 55 个测试全覆盖
4 个测试文件:
- stroke_model_test.dart (12 tests) — StrokePoint/Stroke 序列化、不可变性、默认值
- stroke_renderer_test.dart (19 tests) — parseHexColor/pointsToOutline/buildStrokePath/createPaintForStroke
- stroke_cache_test.dart (15 tests) — StrokeRasterCache 添加/同步/清除/尺寸变化/边界条件
- handwriting_canvas_test.dart (9 tests) — Widget 渲染结构、手势回调、去抖、预加载、连续笔画
2026-06-03 18:57:41 +08:00

160 lines
5.1 KiB
Dart

// StrokeModel 单元测试 — 笔画数据模型的序列化与不可变性验证
import 'dart:collection';
import 'package:flutter_test/flutter_test.dart';
import 'package:nuanji_app/features/editor/widgets/stroke_model.dart';
void main() {
// ============================================================
// StrokePoint
// ============================================================
group('StrokePoint', () {
test('构造函数设置默认值', () {
const point = StrokePoint(x: 10.0, y: 20.0);
expect(point.x, 10.0);
expect(point.y, 20.0);
expect(point.pressure, 0.5);
expect(point.timestamp, 0);
});
test('copyWith 返回新实例,原实例不变', () {
const original = StrokePoint(x: 1.0, y: 2.0, pressure: 0.3, timestamp: 100);
final copied = original.copyWith(x: 10.0, pressure: 0.8);
expect(copied.x, 10.0);
expect(copied.y, 2.0); // 未变
expect(copied.pressure, 0.8);
expect(copied.timestamp, 100); // 未变
// 原实例不变
expect(original.x, 1.0);
expect(original.pressure, 0.3);
});
test('toJson → fromJson 往返一致', () {
const point = StrokePoint(x: 123.456, y: 789.012, pressure: 0.75, timestamp: 1700000000);
final json = point.toJson();
final restored = StrokePoint.fromJson(json);
expect(restored.x, closeTo(point.x, 0.001));
expect(restored.y, closeTo(point.y, 0.001));
expect(restored.pressure, closeTo(point.pressure, 0.001));
expect(restored.timestamp, point.timestamp);
});
test('fromJson 处理缺失字段使用默认值', () {
final restored = StrokePoint.fromJson({'x': 5.0, 'y': 10.0});
expect(restored.pressure, 0.5);
expect(restored.timestamp, 0);
});
});
// ============================================================
// Stroke
// ============================================================
group('Stroke', () {
List<StrokePoint> makePoints(int count) => List.generate(
count,
(i) => StrokePoint(x: i * 10.0, y: i * 5.0, pressure: 0.5, timestamp: i * 16),
);
test('构造函数设置默认值', () {
final stroke = Stroke(id: 'test-1', points: makePoints(3));
expect(stroke.id, 'test-1');
expect(stroke.brushType, BrushType.pen);
expect(stroke.color, '#2D2420');
expect(stroke.width, 3.0);
});
test('copyWith 返回新实例', () {
final original = Stroke(
id: 's1',
points: makePoints(3),
brushType: BrushType.marker,
color: '#FF0000',
width: 5.0,
);
final copied = original.copyWith(color: '#00FF00', width: 8.0);
expect(copied.id, 's1'); // 未变
expect(copied.brushType, BrushType.marker); // 未变
expect(copied.color, '#00FF00');
expect(copied.width, 8.0);
});
test('toJson → fromJson 往返一致', () {
final stroke = Stroke(
id: 'abc-123',
points: makePoints(5),
brushType: BrushType.pencil,
color: '#81B29A',
width: 2.0,
);
final json = stroke.toJson();
final restored = Stroke.fromJson(json);
expect(restored.id, stroke.id);
expect(restored.brushType, stroke.brushType);
expect(restored.color, stroke.color);
expect(restored.width, stroke.width);
expect(restored.points.length, stroke.points.length);
for (var i = 0; i < stroke.points.length; i++) {
expect(restored.points[i].x, closeTo(stroke.points[i].x, 0.001));
expect(restored.points[i].y, closeTo(stroke.points[i].y, 0.001));
}
});
test('fromJson 产生不可变点列表', () {
final stroke = Stroke.fromJson({
'id': 'immutable-test',
'points': [
{'x': 1.0, 'y': 2.0},
{'x': 3.0, 'y': 4.0},
],
});
expect(stroke.points, isA<UnmodifiableListView<StrokePoint>>());
});
test('fromJson 处理缺失可选字段使用默认值', () {
final restored = Stroke.fromJson({
'id': 'minimal',
'points': [
{'x': 0.0, 'y': 0.0},
],
});
expect(restored.brushType, BrushType.pen);
expect(restored.color, '#2D2420');
expect(restored.width, 3.0);
});
test('fromJson 处理未知 brushType 回退到 pen', () {
final restored = Stroke.fromJson({
'id': 'unknown-brush',
'points': [
{'x': 0.0, 'y': 0.0},
],
'brushType': 'nonexistent',
});
expect(restored.brushType, BrushType.pen);
});
});
// ============================================================
// BrushType 枚举
// ============================================================
group('BrushType', () {
test('包含全部 4 种画笔', () {
expect(BrushType.values.length, 4);
expect(BrushType.values.map((b) => b.value), ['pen', 'pencil', 'marker', 'eraser']);
});
test('value 与枚举一一对应', () {
for (final bt in BrushType.values) {
final found = BrushType.values.firstWhere((b) => b.value == bt.value);
expect(found, bt);
}
});
});
}