feat: 新增多模块后端管理、数据同步工具与鸿蒙路由适配
本次提交新增了以下核心内容: 1. 后端管理模块:包含字体同步、插件元数据、插件用户设置、稍后读消息/共享列表的控制器、模型、验证器与多语言配置 2. Flutter数据同步模块:统一的事件总线与兼容层,替代分散的StreamController 3. 鸿蒙端路由适配:完整的路由定义、构建器与占位组件 4. 后端API接口:字体同步与插件更新的服务端API,支持自动建表与跨域请求 5. 鸿蒙权限校验脚本:用于校验module.json5与string.json的权限声明一致性
This commit is contained in:
@@ -0,0 +1,682 @@
|
||||
// ============================================================
|
||||
// 闲言APP — 协作画布CanvasProvider单元测试
|
||||
// 创建时间: 2026-06-01
|
||||
// 更新时间: 2026-06-01
|
||||
// 作用: 测试CanvasState初始状态、工具/颜色/线宽设置、
|
||||
// 笔画创建/撤销/重做/清空、画布加入/离开、dispose防护
|
||||
// 上次更新: 重写Mock为@override方式,解决mocktail when()冲突
|
||||
// ============================================================
|
||||
|
||||
import 'dart:async';
|
||||
import 'dart:ui';
|
||||
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
|
||||
import 'package:xianyan/features/file_transfer/collaboration/canvas/models/stroke.dart';
|
||||
import 'package:xianyan/features/file_transfer/collaboration/canvas/providers/canvas_provider.dart';
|
||||
import 'package:xianyan/features/file_transfer/providers/shared_signaling_provider.dart';
|
||||
import 'package:xianyan/features/file_transfer/services/signaling_service.dart';
|
||||
|
||||
// ============================================================
|
||||
// 测试用SignalingService:@override方式避免mocktail when()冲突
|
||||
// CanvasSyncService仅使用isConnected/onMessage/sendCustomMessage
|
||||
// ============================================================
|
||||
|
||||
class TestSignalingService implements SignalingService {
|
||||
bool _isConnected = false;
|
||||
final StreamController<SignalingMessage> _messageController =
|
||||
StreamController<SignalingMessage>.broadcast();
|
||||
final List<SignalingMessage> sentMessages = [];
|
||||
|
||||
void setConnected(bool value) => _isConnected = value;
|
||||
|
||||
@override
|
||||
bool get isConnected => _isConnected;
|
||||
|
||||
@override
|
||||
Stream<SignalingMessage> get onMessage => _messageController.stream;
|
||||
|
||||
@override
|
||||
void sendCustomMessage(SignalingMessage message) {
|
||||
sentMessages.add(message);
|
||||
}
|
||||
|
||||
@override
|
||||
dynamic noSuchMethod(Invocation invocation) => null;
|
||||
|
||||
void close() {
|
||||
_messageController.close();
|
||||
}
|
||||
}
|
||||
|
||||
// ============================================================
|
||||
// 测试主体
|
||||
// ============================================================
|
||||
|
||||
void main() {
|
||||
late TestSignalingService testSignaling;
|
||||
late ProviderContainer container;
|
||||
|
||||
// ----------------------------------------------------------
|
||||
// 初始化:每个测试前创建测试信令服务和容器
|
||||
// ----------------------------------------------------------
|
||||
setUp(() {
|
||||
testSignaling = TestSignalingService();
|
||||
|
||||
container = ProviderContainer(
|
||||
overrides: [
|
||||
sharedSignalingProvider.overrideWithValue(testSignaling),
|
||||
],
|
||||
);
|
||||
});
|
||||
|
||||
// ----------------------------------------------------------
|
||||
// 清理:每个测试后销毁容器和信令服务
|
||||
// ----------------------------------------------------------
|
||||
tearDown(() {
|
||||
container.dispose();
|
||||
testSignaling.close();
|
||||
});
|
||||
|
||||
// ----------------------------------------------------------
|
||||
// 辅助方法:等待microtask队列清空
|
||||
// CanvasNotifier通过Future.microtask异步更新state,
|
||||
// 测试中需要等待microtask完成后才能断言state
|
||||
// ----------------------------------------------------------
|
||||
Future<void> flushMicrotasks({int rounds = 3}) async {
|
||||
for (var i = 0; i < rounds; i++) {
|
||||
await Future.microtask(() {});
|
||||
}
|
||||
}
|
||||
|
||||
// ===========================================================
|
||||
// 1. CanvasState 初始状态
|
||||
// ===========================================================
|
||||
group('CanvasState 初始状态', () {
|
||||
test('所有字段应为默认值', () {
|
||||
final state = container.read(canvasProvider);
|
||||
|
||||
expect(state.strokes, isEmpty);
|
||||
expect(state.activeStroke, isNull);
|
||||
expect(state.currentTool, StrokeType.pen);
|
||||
expect(state.currentColor, '#000000');
|
||||
expect(state.currentWidth, 3.0);
|
||||
expect(state.remoteCursors, isEmpty);
|
||||
expect(state.cursorOpacities, isEmpty);
|
||||
expect(state.participants, isEmpty);
|
||||
expect(state.isConnected, isFalse);
|
||||
expect(state.isSyncing, isFalse);
|
||||
expect(state.canvasId, isNull);
|
||||
});
|
||||
});
|
||||
|
||||
// ===========================================================
|
||||
// 2. setTool
|
||||
// ===========================================================
|
||||
group('setTool', () {
|
||||
test('正确更新currentTool为eraser', () async {
|
||||
final notifier = container.read(canvasProvider.notifier);
|
||||
notifier.setTool(StrokeType.eraser);
|
||||
await flushMicrotasks();
|
||||
expect(notifier.state.currentTool, StrokeType.eraser);
|
||||
});
|
||||
|
||||
test('正确更新currentTool为circle', () async {
|
||||
final notifier = container.read(canvasProvider.notifier);
|
||||
notifier.setTool(StrokeType.circle);
|
||||
await flushMicrotasks();
|
||||
expect(notifier.state.currentTool, StrokeType.circle);
|
||||
});
|
||||
|
||||
test('遍历所有StrokeType均能正确设置', () async {
|
||||
final notifier = container.read(canvasProvider.notifier);
|
||||
for (final tool in StrokeType.values) {
|
||||
notifier.setTool(tool);
|
||||
await flushMicrotasks();
|
||||
expect(notifier.state.currentTool, tool);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
// ===========================================================
|
||||
// 3. setColor
|
||||
// ===========================================================
|
||||
group('setColor', () {
|
||||
test('正确更新currentColor', () async {
|
||||
final notifier = container.read(canvasProvider.notifier);
|
||||
notifier.setColor('#FF0000');
|
||||
await flushMicrotasks();
|
||||
expect(notifier.state.currentColor, '#FF0000');
|
||||
});
|
||||
|
||||
test('多次设置颜色保留最后一次', () async {
|
||||
final notifier = container.read(canvasProvider.notifier);
|
||||
notifier.setColor('#FF0000');
|
||||
await flushMicrotasks();
|
||||
notifier.setColor('#00FF00');
|
||||
await flushMicrotasks();
|
||||
expect(notifier.state.currentColor, '#00FF00');
|
||||
});
|
||||
});
|
||||
|
||||
// ===========================================================
|
||||
// 4. setWidth
|
||||
// ===========================================================
|
||||
group('setWidth', () {
|
||||
test('正确更新currentWidth', () async {
|
||||
final notifier = container.read(canvasProvider.notifier);
|
||||
notifier.setWidth(8.0);
|
||||
await flushMicrotasks();
|
||||
expect(notifier.state.currentWidth, 8.0);
|
||||
});
|
||||
|
||||
test('设置极小线宽', () async {
|
||||
final notifier = container.read(canvasProvider.notifier);
|
||||
notifier.setWidth(0.5);
|
||||
await flushMicrotasks();
|
||||
expect(notifier.state.currentWidth, 0.5);
|
||||
});
|
||||
});
|
||||
|
||||
// ===========================================================
|
||||
// 5. startStroke
|
||||
// ===========================================================
|
||||
group('startStroke', () {
|
||||
test('创建新的activeStroke', () async {
|
||||
final notifier = container.read(canvasProvider.notifier);
|
||||
notifier.startStroke(const Offset(10, 20));
|
||||
await flushMicrotasks();
|
||||
|
||||
final active = notifier.state.activeStroke;
|
||||
expect(active, isNotNull);
|
||||
expect(active!.points, isNotEmpty);
|
||||
expect(active.points.first, const Offset(10, 20));
|
||||
expect(active.type, StrokeType.pen);
|
||||
expect(active.color, '#000000');
|
||||
});
|
||||
|
||||
test('eraser工具创建白色粗笔画', () async {
|
||||
final notifier = container.read(canvasProvider.notifier);
|
||||
notifier.setTool(StrokeType.eraser);
|
||||
await flushMicrotasks();
|
||||
notifier.startStroke(const Offset(5, 5));
|
||||
await flushMicrotasks();
|
||||
|
||||
final active = notifier.state.activeStroke;
|
||||
expect(active, isNotNull);
|
||||
expect(active!.color, '#FFFFFF');
|
||||
expect(active.width, greaterThan(3.0));
|
||||
});
|
||||
|
||||
test('使用当前颜色和线宽', () async {
|
||||
final notifier = container.read(canvasProvider.notifier);
|
||||
notifier.setColor('#0000FF');
|
||||
notifier.setWidth(6.0);
|
||||
await flushMicrotasks();
|
||||
notifier.startStroke(const Offset(0, 0));
|
||||
await flushMicrotasks();
|
||||
|
||||
final active = notifier.state.activeStroke;
|
||||
expect(active, isNotNull);
|
||||
expect(active!.color, '#0000FF');
|
||||
expect(active.width, 6.0);
|
||||
});
|
||||
});
|
||||
|
||||
// ===========================================================
|
||||
// 6. addPoint
|
||||
// ===========================================================
|
||||
group('addPoint', () {
|
||||
test('向activeStroke添加点', () async {
|
||||
final notifier = container.read(canvasProvider.notifier);
|
||||
notifier.startStroke(const Offset(0, 0));
|
||||
await flushMicrotasks();
|
||||
|
||||
notifier.addPoint(const Offset(10, 10));
|
||||
await flushMicrotasks();
|
||||
|
||||
final active = notifier.state.activeStroke;
|
||||
expect(active, isNotNull);
|
||||
expect(active!.points.length, 2);
|
||||
expect(active.points.last, const Offset(10, 10));
|
||||
});
|
||||
|
||||
test('连续添加多个点', () async {
|
||||
final notifier = container.read(canvasProvider.notifier);
|
||||
notifier.startStroke(const Offset(0, 0));
|
||||
await flushMicrotasks();
|
||||
|
||||
for (var i = 1; i <= 5; i++) {
|
||||
notifier.addPoint(Offset(i * 10.0, i * 10.0));
|
||||
}
|
||||
await flushMicrotasks();
|
||||
|
||||
expect(notifier.state.activeStroke!.points.length, 6);
|
||||
});
|
||||
});
|
||||
|
||||
// ===========================================================
|
||||
// 7. endStroke
|
||||
// ===========================================================
|
||||
group('endStroke', () {
|
||||
test('将activeStroke移到strokes列表', () async {
|
||||
final notifier = container.read(canvasProvider.notifier);
|
||||
notifier.startStroke(const Offset(0, 0));
|
||||
notifier.addPoint(const Offset(10, 10));
|
||||
await flushMicrotasks();
|
||||
|
||||
notifier.endStroke();
|
||||
await flushMicrotasks();
|
||||
|
||||
expect(notifier.state.activeStroke, isNull);
|
||||
expect(notifier.state.strokes.length, 1);
|
||||
expect(notifier.state.strokes.first.points.length, 2);
|
||||
});
|
||||
|
||||
test('少于2个点时endStroke不添加到strokes', () async {
|
||||
final notifier = container.read(canvasProvider.notifier);
|
||||
notifier.startStroke(const Offset(0, 0));
|
||||
await flushMicrotasks();
|
||||
|
||||
notifier.endStroke();
|
||||
await flushMicrotasks();
|
||||
|
||||
expect(notifier.state.activeStroke, isNull);
|
||||
expect(notifier.state.strokes, isEmpty);
|
||||
});
|
||||
|
||||
test('无activeStroke时endStroke不报错', () async {
|
||||
final notifier = container.read(canvasProvider.notifier);
|
||||
notifier.endStroke();
|
||||
await flushMicrotasks();
|
||||
expect(notifier.state.strokes, isEmpty);
|
||||
});
|
||||
|
||||
test('多笔绘制后strokes列表递增', () async {
|
||||
final notifier = container.read(canvasProvider.notifier);
|
||||
for (var i = 0; i < 3; i++) {
|
||||
notifier.startStroke(Offset(i * 10.0, 0));
|
||||
notifier.addPoint(Offset(i * 10.0, 10.0));
|
||||
await flushMicrotasks();
|
||||
notifier.endStroke();
|
||||
await flushMicrotasks();
|
||||
}
|
||||
expect(notifier.state.strokes.length, 3);
|
||||
});
|
||||
});
|
||||
|
||||
// ===========================================================
|
||||
// 8. undo
|
||||
// ===========================================================
|
||||
group('undo', () {
|
||||
test('撤销最后一笔', () async {
|
||||
final notifier = container.read(canvasProvider.notifier);
|
||||
notifier.startStroke(const Offset(0, 0));
|
||||
notifier.addPoint(const Offset(10, 10));
|
||||
notifier.endStroke();
|
||||
await flushMicrotasks();
|
||||
|
||||
expect(notifier.state.strokes.length, 1);
|
||||
|
||||
notifier.undo();
|
||||
await flushMicrotasks();
|
||||
|
||||
expect(notifier.state.strokes, isEmpty);
|
||||
});
|
||||
|
||||
test('多笔时只撤销最后一笔', () async {
|
||||
final notifier = container.read(canvasProvider.notifier);
|
||||
|
||||
notifier.startStroke(const Offset(0, 0));
|
||||
notifier.addPoint(const Offset(10, 10));
|
||||
notifier.endStroke();
|
||||
await flushMicrotasks();
|
||||
|
||||
notifier.startStroke(const Offset(20, 20));
|
||||
notifier.addPoint(const Offset(30, 30));
|
||||
notifier.endStroke();
|
||||
await flushMicrotasks();
|
||||
|
||||
expect(notifier.state.strokes.length, 2);
|
||||
|
||||
notifier.undo();
|
||||
await flushMicrotasks();
|
||||
|
||||
expect(notifier.state.strokes.length, 1);
|
||||
});
|
||||
|
||||
test('无可撤销笔画时undo不报错', () async {
|
||||
final notifier = container.read(canvasProvider.notifier);
|
||||
notifier.undo();
|
||||
await flushMicrotasks();
|
||||
expect(notifier.state.strokes, isEmpty);
|
||||
});
|
||||
});
|
||||
|
||||
// ===========================================================
|
||||
// 9. redo
|
||||
// ===========================================================
|
||||
group('redo', () {
|
||||
test('恢复撤销的笔画', () async {
|
||||
final notifier = container.read(canvasProvider.notifier);
|
||||
notifier.startStroke(const Offset(0, 0));
|
||||
notifier.addPoint(const Offset(10, 10));
|
||||
notifier.endStroke();
|
||||
await flushMicrotasks();
|
||||
|
||||
notifier.undo();
|
||||
await flushMicrotasks();
|
||||
expect(notifier.state.strokes, isEmpty);
|
||||
|
||||
notifier.redo();
|
||||
await flushMicrotasks();
|
||||
|
||||
expect(notifier.state.strokes.length, 1);
|
||||
});
|
||||
|
||||
test('新笔画后redo栈被清空,无法redo', () async {
|
||||
final notifier = container.read(canvasProvider.notifier);
|
||||
|
||||
notifier.startStroke(const Offset(0, 0));
|
||||
notifier.addPoint(const Offset(10, 10));
|
||||
notifier.endStroke();
|
||||
await flushMicrotasks();
|
||||
|
||||
notifier.undo();
|
||||
await flushMicrotasks();
|
||||
|
||||
notifier.startStroke(const Offset(20, 20));
|
||||
notifier.addPoint(const Offset(30, 30));
|
||||
notifier.endStroke();
|
||||
await flushMicrotasks();
|
||||
|
||||
notifier.redo();
|
||||
await flushMicrotasks();
|
||||
|
||||
expect(notifier.state.strokes.length, 1);
|
||||
});
|
||||
|
||||
test('无可用redo时redo不报错', () async {
|
||||
final notifier = container.read(canvasProvider.notifier);
|
||||
notifier.redo();
|
||||
await flushMicrotasks();
|
||||
expect(notifier.state.strokes, isEmpty);
|
||||
});
|
||||
|
||||
test('undo→redo→undo循环正确', () async {
|
||||
final notifier = container.read(canvasProvider.notifier);
|
||||
notifier.startStroke(const Offset(0, 0));
|
||||
notifier.addPoint(const Offset(10, 10));
|
||||
notifier.endStroke();
|
||||
await flushMicrotasks();
|
||||
|
||||
notifier.undo();
|
||||
await flushMicrotasks();
|
||||
expect(notifier.state.strokes, isEmpty);
|
||||
|
||||
notifier.redo();
|
||||
await flushMicrotasks();
|
||||
expect(notifier.state.strokes.length, 1);
|
||||
|
||||
notifier.undo();
|
||||
await flushMicrotasks();
|
||||
expect(notifier.state.strokes, isEmpty);
|
||||
});
|
||||
});
|
||||
|
||||
// ===========================================================
|
||||
// 10. clearCanvas
|
||||
// ===========================================================
|
||||
group('clearCanvas', () {
|
||||
test('清空所有笔画', () async {
|
||||
final notifier = container.read(canvasProvider.notifier);
|
||||
|
||||
notifier.startStroke(const Offset(0, 0));
|
||||
notifier.addPoint(const Offset(10, 10));
|
||||
notifier.endStroke();
|
||||
await flushMicrotasks();
|
||||
|
||||
notifier.startStroke(const Offset(20, 20));
|
||||
notifier.addPoint(const Offset(30, 30));
|
||||
notifier.endStroke();
|
||||
await flushMicrotasks();
|
||||
|
||||
expect(notifier.state.strokes.length, 2);
|
||||
|
||||
notifier.clearCanvas();
|
||||
await flushMicrotasks();
|
||||
|
||||
expect(notifier.state.strokes, isEmpty);
|
||||
});
|
||||
|
||||
test('清空后undo和redo均不可用', () async {
|
||||
final notifier = container.read(canvasProvider.notifier);
|
||||
|
||||
notifier.startStroke(const Offset(0, 0));
|
||||
notifier.addPoint(const Offset(10, 10));
|
||||
notifier.endStroke();
|
||||
await flushMicrotasks();
|
||||
|
||||
notifier.clearCanvas();
|
||||
await flushMicrotasks();
|
||||
|
||||
notifier.undo();
|
||||
await flushMicrotasks();
|
||||
expect(notifier.state.strokes, isEmpty);
|
||||
|
||||
notifier.redo();
|
||||
await flushMicrotasks();
|
||||
expect(notifier.state.strokes, isEmpty);
|
||||
});
|
||||
|
||||
test('清空后activeStroke为null', () async {
|
||||
final notifier = container.read(canvasProvider.notifier);
|
||||
notifier.startStroke(const Offset(0, 0));
|
||||
await flushMicrotasks();
|
||||
expect(notifier.state.activeStroke, isNotNull);
|
||||
|
||||
notifier.clearCanvas();
|
||||
await flushMicrotasks();
|
||||
expect(notifier.state.activeStroke, isNull);
|
||||
});
|
||||
});
|
||||
|
||||
// ===========================================================
|
||||
// 11. _disposed 防护
|
||||
// ===========================================================
|
||||
group('dispose防护', () {
|
||||
test('dispose后_disposed阻止microtask状态更新,不抛异常', () async {
|
||||
final testSignaling2 = TestSignalingService();
|
||||
final testContainer = ProviderContainer(
|
||||
overrides: [
|
||||
sharedSignalingProvider.overrideWithValue(testSignaling2),
|
||||
],
|
||||
);
|
||||
final notifier = testContainer.read(canvasProvider.notifier);
|
||||
|
||||
notifier.setTool(StrokeType.eraser);
|
||||
|
||||
testContainer.dispose();
|
||||
testSignaling2.close();
|
||||
|
||||
await flushMicrotasks();
|
||||
});
|
||||
|
||||
test('leaveCanvas在dispose后不更新状态', () async {
|
||||
final testSignaling2 = TestSignalingService();
|
||||
testSignaling2.setConnected(true);
|
||||
final testContainer = ProviderContainer(
|
||||
overrides: [
|
||||
sharedSignalingProvider.overrideWithValue(testSignaling2),
|
||||
],
|
||||
);
|
||||
final notifier = testContainer.read(canvasProvider.notifier);
|
||||
|
||||
notifier.joinCanvas('test-canvas', 'device-1');
|
||||
notifier.leaveCanvas();
|
||||
|
||||
testContainer.dispose();
|
||||
testSignaling2.close();
|
||||
|
||||
await flushMicrotasks();
|
||||
});
|
||||
});
|
||||
|
||||
// ===========================================================
|
||||
// 12. joinCanvas
|
||||
// ===========================================================
|
||||
group('joinCanvas', () {
|
||||
test('设置canvasId和isConnected', () {
|
||||
testSignaling.setConnected(true);
|
||||
final notifier = container.read(canvasProvider.notifier);
|
||||
|
||||
notifier.joinCanvas('test-canvas-123', 'device-A');
|
||||
|
||||
expect(notifier.state.canvasId, 'test-canvas-123');
|
||||
expect(notifier.state.isConnected, isTrue);
|
||||
});
|
||||
|
||||
test('joinCanvas后participants包含当前设备', () async {
|
||||
testSignaling.setConnected(true);
|
||||
final notifier = container.read(canvasProvider.notifier);
|
||||
|
||||
notifier.joinCanvas('test-canvas', 'device-A');
|
||||
await flushMicrotasks();
|
||||
|
||||
expect(notifier.state.participants, contains('device-A'));
|
||||
});
|
||||
|
||||
test('重复joinCanvas覆盖旧canvasId', () {
|
||||
testSignaling.setConnected(true);
|
||||
final notifier = container.read(canvasProvider.notifier);
|
||||
|
||||
notifier.joinCanvas('canvas-1', 'device-A');
|
||||
notifier.joinCanvas('canvas-2', 'device-A');
|
||||
|
||||
expect(notifier.state.canvasId, 'canvas-2');
|
||||
});
|
||||
});
|
||||
|
||||
// ===========================================================
|
||||
// 13. leaveCanvas
|
||||
// ===========================================================
|
||||
group('leaveCanvas', () {
|
||||
test('清除isConnected', () async {
|
||||
testSignaling.setConnected(true);
|
||||
final notifier = container.read(canvasProvider.notifier);
|
||||
|
||||
notifier.joinCanvas('test-canvas', 'device-A');
|
||||
expect(notifier.state.isConnected, isTrue);
|
||||
|
||||
notifier.leaveCanvas();
|
||||
await flushMicrotasks();
|
||||
|
||||
expect(notifier.state.isConnected, isFalse);
|
||||
});
|
||||
|
||||
test('清除canvasId', () async {
|
||||
testSignaling.setConnected(true);
|
||||
final notifier = container.read(canvasProvider.notifier);
|
||||
|
||||
notifier.joinCanvas('test-canvas', 'device-A');
|
||||
expect(notifier.state.canvasId, 'test-canvas');
|
||||
|
||||
notifier.leaveCanvas();
|
||||
await flushMicrotasks();
|
||||
|
||||
expect(notifier.state.canvasId, isNull);
|
||||
});
|
||||
|
||||
test('清除remoteCursors和cursorOpacities', () async {
|
||||
testSignaling.setConnected(true);
|
||||
final notifier = container.read(canvasProvider.notifier);
|
||||
|
||||
notifier.joinCanvas('test-canvas', 'device-A');
|
||||
await flushMicrotasks();
|
||||
|
||||
notifier.leaveCanvas();
|
||||
await flushMicrotasks();
|
||||
|
||||
expect(notifier.state.remoteCursors, isEmpty);
|
||||
expect(notifier.state.cursorOpacities, isEmpty);
|
||||
});
|
||||
|
||||
test('清除participants', () async {
|
||||
testSignaling.setConnected(true);
|
||||
final notifier = container.read(canvasProvider.notifier);
|
||||
|
||||
notifier.joinCanvas('test-canvas', 'device-A');
|
||||
await flushMicrotasks();
|
||||
|
||||
notifier.leaveCanvas();
|
||||
await flushMicrotasks();
|
||||
|
||||
expect(notifier.state.participants, isEmpty);
|
||||
});
|
||||
});
|
||||
|
||||
// ===========================================================
|
||||
// CanvasState.copyWith 额外测试
|
||||
// ===========================================================
|
||||
group('CanvasState.copyWith', () {
|
||||
test('clearCanvasId将canvasId置为null', () {
|
||||
const state = CanvasState(canvasId: 'test-id');
|
||||
final updated = state.copyWith(clearCanvasId: true);
|
||||
expect(updated.canvasId, isNull);
|
||||
});
|
||||
|
||||
test('clearCanvasId为false时保留原值', () {
|
||||
const state = CanvasState(canvasId: 'test-id');
|
||||
final updated = state.copyWith(clearCanvasId: false);
|
||||
expect(updated.canvasId, 'test-id');
|
||||
});
|
||||
|
||||
test('clearActiveStroke将activeStroke置为null', () {
|
||||
final stroke = Stroke(
|
||||
id: 's1',
|
||||
userId: 'u1',
|
||||
color: '#000000',
|
||||
width: 3.0,
|
||||
points: [const Offset(0, 0)],
|
||||
lamportClock: 1,
|
||||
type: StrokeType.pen,
|
||||
createdAt: DateTime.now(),
|
||||
);
|
||||
final state = CanvasState(activeStroke: stroke);
|
||||
final updated = state.copyWith(clearActiveStroke: true);
|
||||
expect(updated.activeStroke, isNull);
|
||||
});
|
||||
|
||||
test('部分字段更新不影响其他字段', () {
|
||||
const state = CanvasState(
|
||||
currentColor: '#FF0000',
|
||||
currentWidth: 5.0,
|
||||
isConnected: true,
|
||||
);
|
||||
final updated = state.copyWith(currentColor: '#00FF00');
|
||||
expect(updated.currentColor, '#00FF00');
|
||||
expect(updated.currentWidth, 5.0);
|
||||
expect(updated.isConnected, isTrue);
|
||||
});
|
||||
});
|
||||
|
||||
// ===========================================================
|
||||
// StrokeType枚举测试
|
||||
// ===========================================================
|
||||
group('StrokeType', () {
|
||||
test('fromId正确映射所有枚举值', () {
|
||||
expect(StrokeType.fromId('pen'), StrokeType.pen);
|
||||
expect(StrokeType.fromId('eraser'), StrokeType.eraser);
|
||||
expect(StrokeType.fromId('line'), StrokeType.line);
|
||||
expect(StrokeType.fromId('rect'), StrokeType.rect);
|
||||
expect(StrokeType.fromId('circle'), StrokeType.circle);
|
||||
expect(StrokeType.fromId('text'), StrokeType.text);
|
||||
});
|
||||
|
||||
test('fromId未知值返回pen', () {
|
||||
expect(StrokeType.fromId('unknown'), StrokeType.pen);
|
||||
expect(StrokeType.fromId(''), StrokeType.pen);
|
||||
});
|
||||
});
|
||||
}
|
||||
Reference in New Issue
Block a user