test(app): 前端第一批测试 — EditorBloc 19用例 + JournalElement 11用例 + InMemoryRepo 12用例

添加 mocktail 测试依赖 + 字体文件占位
总计 42 测试通过,覆盖工具/笔画/元素/撤销/重做/序列化/乐观锁/CRUD
This commit is contained in:
iven
2026-06-01 23:02:14 +08:00
parent 3eaf83c79a
commit 4c743e150e
11 changed files with 723 additions and 0 deletions

View File

@@ -0,0 +1,202 @@
// JournalElement 模型单元测试
//
// 覆盖工厂方法、便利工厂、copyWith 不可变性、序列化往返、默认值
import 'dart:math';
import 'package:flutter_test/flutter_test.dart';
import 'package:nuanji_app/data/models/journal_element.dart';
void main() {
group('JournalElement', () {
// ===== create 工厂 =====
test('create 工厂生成正确的默认值', () {
final element = JournalElement.create(
journalId: 'journal-1',
elementType: ElementType.text,
positionX: 10,
positionY: 20,
content: {'text': 'Hello'},
);
expect(element.journalId, 'journal-1');
expect(element.elementType, ElementType.text);
expect(element.positionX, 10.0);
expect(element.positionY, 20.0);
expect(element.width, 100.0);
expect(element.height, 100.0);
expect(element.rotation, 0.0);
expect(element.zIndex, 0);
expect(element.version, 1);
expect(element.id, isNotEmpty);
expect(element.createdAt, isNotNull);
expect(element.updatedAt, isNotNull);
});
// ===== createText =====
test('createText 便利工厂', () {
final element = JournalElement.createText(
journalId: 'j-1',
text: '你好',
position: const Offset(50, 100),
fontSize: 24.0,
fontColor: '#E07A5F',
);
expect(element.elementType, ElementType.text);
expect(element.content['text'], '你好');
expect(element.content['fontSize'], 24.0);
expect(element.content['fontColor'], '#E07A5F');
expect(element.positionX, 50.0);
expect(element.positionY, 100.0);
});
test('createText 使用默认字号和颜色', () {
final element = JournalElement.createText(
journalId: 'j-1',
text: '默认',
position: Offset.zero,
);
expect(element.content['fontSize'], 18.0);
expect(element.content['fontColor'], '#2D2420');
});
// ===== createImage =====
test('createImage 便利工厂', () {
final element = JournalElement.createImage(
journalId: 'j-1',
filePath: '/path/to/image.jpg',
position: Offset.zero,
thumbnailPath: '/path/to/thumb.jpg',
);
expect(element.elementType, ElementType.image);
expect(element.content['filePath'], '/path/to/image.jpg');
expect(element.content['thumbnailPath'], '/path/to/thumb.jpg');
});
test('createImage 无 thumbnailPath 时不包含该字段', () {
final element = JournalElement.createImage(
journalId: 'j-1',
filePath: '/path/to/img.jpg',
position: Offset.zero,
);
expect(element.content.containsKey('thumbnailPath'), isFalse);
expect(element.content['filePath'], '/path/to/img.jpg');
});
// ===== createSticker =====
test('createSticker 便利工厂', () {
final element = JournalElement.createSticker(
journalId: 'j-1',
emoji: '🐱',
position: const Offset(50, 50),
stickerPackId: 'pack-1',
stickerId: 's-1',
);
expect(element.elementType, ElementType.sticker);
expect(element.content['emoji'], '🐱');
expect(element.content['stickerPackId'], 'pack-1');
expect(element.content['stickerId'], 's-1');
});
test('createSticker 无可选字段时不包含', () {
final element = JournalElement.createSticker(
journalId: 'j-1',
emoji: '🌸',
position: Offset.zero,
);
expect(element.content.containsKey('stickerPackId'), isFalse);
expect(element.content.containsKey('stickerId'), isFalse);
expect(element.content['emoji'], '🌸');
});
// ===== copyWith 不可变性 =====
test('copyWith 返回新实例且不修改原始', () {
final original = JournalElement.createText(
journalId: 'j-1',
text: '原始',
position: Offset.zero,
);
final modified = original.copyWith(
positionX: 100.0,
positionY: 200.0,
);
expect(identical(original, modified), isFalse);
expect(original.positionX, 0.0);
expect(original.positionY, 0.0);
expect(modified.positionX, 100.0);
expect(modified.positionY, 200.0);
expect(modified.journalId, original.journalId);
expect(modified.id, original.id);
});
// ===== 序列化 =====
test('toJson / fromJson 往返一致', () {
final original = JournalElement.createText(
journalId: 'j-1',
text: '序列化测试',
position: const Offset(42, 84),
fontSize: 20.0,
);
final json = original.toJson();
final restored = JournalElement.fromJson(json);
expect(restored.id, original.id);
expect(restored.journalId, original.journalId);
expect(restored.elementType, original.elementType);
expect(restored.positionX, original.positionX);
expect(restored.positionY, original.positionY);
expect(restored.content['text'], original.content['text']);
expect(restored.version, original.version);
});
test('fromJson 处理空 content', () {
final json = {
'id': 'test-id',
'journal_id': 'j-1',
'element_type': 'text',
'position_x': 0.0,
'position_y': 0.0,
'width': 100.0,
'height': 100.0,
'rotation': 0.0,
'z_index': 0,
'content': <String, dynamic>{},
'version': 1,
'created_at': DateTime.now().toIso8601String(),
'updated_at': DateTime.now().toIso8601String(),
};
final element = JournalElement.fromJson(json);
expect(element.content, isA<Map>());
expect(element.content, isEmpty);
});
// ===== ElementType 枚举 =====
test('ElementType 枚举值完整', () {
expect(ElementType.values.length, 5);
expect(ElementType.values, containsAll([
ElementType.text,
ElementType.image,
ElementType.sticker,
ElementType.handwritingRef,
ElementType.tape,
]));
});
});
}