Files
nj/app/test/features/editor/widgets/handwriting_canvas_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

273 lines
8.4 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.
// HandwritingCanvas Widget 集成测试 — 指针事件驱动笔画完成回调
//
// 验证:
// 1. Widget 正确渲染(双层 CustomPaint
// 2. 手势事件触发 onStrokeCompleted 回调
// 3. 不同画笔类型/颜色/宽度正确传递
// 4. 去抖过滤(微小移动被丢弃)
import 'package:flutter/material.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:nuanji_app/features/editor/widgets/handwriting_canvas.dart';
import 'package:nuanji_app/features/editor/widgets/stroke_model.dart';
void main() {
// ============================================================
// 辅助
// ============================================================
/// 包裹 HandwritingCanvas 在必要的父组件中,提供约束尺寸
Widget buildTestSubject({
Key? key,
BrushType brushType = BrushType.pen,
String brushColor = '#2D2420',
double brushWidth = 3.0,
List<Stroke> strokes = const [],
ValueChanged<Stroke>? onStrokeCompleted,
}) {
return MaterialApp(
home: Scaffold(
body: SizedBox(
width: 800,
height: 600,
child: HandwritingCanvas(
key: key,
brushType: brushType,
brushColor: brushColor,
brushWidth: brushWidth,
strokes: strokes,
onStrokeCompleted: onStrokeCompleted,
),
),
),
);
}
/// 使用标准 TestGesture 模拟一条完整的拖拽手势
Future<void> simulateDragStroke(
WidgetTester tester,
List<Offset> points,
) async {
assert(points.length >= 2, '至少需要 down 和 up 两个点');
final gesture = await tester.startGesture(points.first);
await tester.pump();
for (var i = 1; i < points.length; i++) {
await gesture.moveTo(points[i]);
await tester.pump();
}
await gesture.up();
await tester.pump();
}
// ============================================================
// 渲染结构验证
// ============================================================
group('HandwritingCanvas — 渲染结构', () {
testWidgets('正确渲染双层 CustomPaint', (tester) async {
await tester.pumpWidget(buildTestSubject());
await tester.pumpAndSettle();
// 应找到 CustomPaint两层CachedStrokesPainter + ActiveStrokePainter
final customPaints = find.byType(CustomPaint);
expect(customPaints, findsAtLeast(2));
// 应找到 ListenerHandwritingCanvas 的 Listener + Gesture 识别器可能有额外 Listener
expect(find.byType(Listener), findsAtLeast(1));
// 应找到 RepaintBoundaryMaterialApp/Scaffold 可能添加额外的)
expect(find.byType(RepaintBoundary), findsAtLeast(1));
});
testWidgets('初始无笔画时仍正确渲染', (tester) async {
await tester.pumpWidget(buildTestSubject());
await tester.pumpAndSettle();
expect(find.byType(HandwritingCanvas), findsOneWidget);
});
});
// ============================================================
// 手势事件 → onStrokeCompleted
// ============================================================
group('HandwritingCanvas — 笔画完成回调', () {
testWidgets('有效拖拽触发 onStrokeCompleted', (tester) async {
Stroke? completedStroke;
await tester.pumpWidget(buildTestSubject(
onStrokeCompleted: (stroke) => completedStroke = stroke,
));
await tester.pumpAndSettle();
// 模拟 5 个点的笔画(距离足够大,避免去抖过滤)
await simulateDragStroke(tester, [
const Offset(100, 100),
const Offset(150, 120),
const Offset(200, 140),
const Offset(250, 160),
const Offset(300, 180),
]);
// 应触发回调
expect(completedStroke, isNotNull);
expect(completedStroke!.points.length, greaterThanOrEqualTo(2));
expect(completedStroke!.brushType, BrushType.pen);
expect(completedStroke!.color, '#2D2420');
expect(completedStroke!.width, 3.0);
});
testWidgets('笔画携带正确的画笔类型', (tester) async {
Stroke? completedStroke;
await tester.pumpWidget(buildTestSubject(
brushType: BrushType.marker,
onStrokeCompleted: (stroke) => completedStroke = stroke,
));
await tester.pumpAndSettle();
await simulateDragStroke(tester, [
const Offset(100, 100),
const Offset(200, 200),
const Offset(300, 100),
]);
expect(completedStroke, isNotNull);
expect(completedStroke!.brushType, BrushType.marker);
});
testWidgets('笔画携带正确的颜色和宽度', (tester) async {
Stroke? completedStroke;
await tester.pumpWidget(buildTestSubject(
brushColor: '#E07A5F',
brushWidth: 8.0,
onStrokeCompleted: (stroke) => completedStroke = stroke,
));
await tester.pumpAndSettle();
await simulateDragStroke(tester, [
const Offset(50, 50),
const Offset(150, 150),
const Offset(250, 50),
]);
expect(completedStroke!.color, '#E07A5F');
expect(completedStroke!.width, 8.0);
});
testWidgets('tap无拖拽不触发回调', (tester) async {
Stroke? completedStroke;
await tester.pumpWidget(buildTestSubject(
onStrokeCompleted: (stroke) => completedStroke = stroke,
));
await tester.pumpAndSettle();
// 仅 tapdown + up 在同一位置)— 单点不足以构成笔画
await tester.tapAt(const Offset(100, 100));
await tester.pumpAndSettle();
// Tap 产生 1 个点down 和 up 位置相同),不触发回调
expect(completedStroke, isNull);
});
});
// ============================================================
// 预加载笔画
// ============================================================
group('HandwritingCanvas — 预加载笔画', () {
testWidgets('初始笔画列表正确传入', (tester) async {
final existingStrokes = [
Stroke(
id: 'existing-1',
points: [
const StrokePoint(x: 10, y: 10),
const StrokePoint(x: 100, y: 100),
],
),
];
await tester.pumpWidget(buildTestSubject(
strokes: existingStrokes,
));
await tester.pumpAndSettle();
expect(find.byType(HandwritingCanvas), findsOneWidget);
});
testWidgets('笔画更新触发 didUpdateWidget', (tester) async {
final key = GlobalKey();
// 第一次渲染 — 无笔画
await tester.pumpWidget(MaterialApp(
home: Scaffold(
body: SizedBox(
width: 800,
height: 600,
child: HandwritingCanvas(
key: key,
strokes: const [],
),
),
),
));
await tester.pumpAndSettle();
// 更新 — 添加笔画
final updatedStrokes = [
Stroke(
id: 'new-1',
points: [
const StrokePoint(x: 50, y: 50),
const StrokePoint(x: 200, y: 200),
],
),
];
await tester.pumpWidget(MaterialApp(
home: Scaffold(
body: SizedBox(
width: 800,
height: 600,
child: HandwritingCanvas(
key: key,
strokes: updatedStrokes,
),
),
),
));
await tester.pumpAndSettle();
expect(find.byType(HandwritingCanvas), findsOneWidget);
});
});
// ============================================================
// 连续多笔画
// ============================================================
group('HandwritingCanvas — 连续多笔画', () {
testWidgets('连续绘制多条笔画,每条都触发回调', (tester) async {
final completedStrokes = <Stroke>[];
await tester.pumpWidget(buildTestSubject(
onStrokeCompleted: (stroke) => completedStrokes.add(stroke),
));
await tester.pumpAndSettle();
// 第一条笔画
await simulateDragStroke(tester, [
const Offset(50, 50),
const Offset(150, 100),
const Offset(250, 50),
]);
// 第二条笔画(使用新的 gesture
await simulateDragStroke(tester, [
const Offset(100, 200),
const Offset(200, 300),
const Offset(300, 200),
]);
expect(completedStrokes.length, 2);
expect(completedStrokes[0].id, isNot(equals(completedStrokes[1].id)));
});
});
}