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 渲染结构、手势回调、去抖、预加载、连续笔画
160 lines
5.1 KiB
Dart
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);
|
|
}
|
|
});
|
|
});
|
|
}
|