// 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 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>()); }); 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); } }); }); }