mobile-eda/test/data_format_test.dart

425 lines
12 KiB
Dart

/// 数据格式模块测试
///
/// 测试 Tile 序列化、KiCad 导入、增量保存功能
///
/// @date 2026-03-07
import 'dart:typed_data';
import 'package:flutter_test/flutter_test.dart';
import 'package:mobile_eda/data/data_format.dart';
void main() {
// ============================================================================
// Tile 格式测试
// ============================================================================
group('TileSerializer', () {
test('should serialize simple design', () {
final design = {
'id': 'design-001',
'name': 'Test Circuit',
'version': '1.0.0',
'components': [
{
'id': 'comp-001',
'name': 'R1',
'type': 'resistor',
'value': '10k',
'position': {'x': 1000000, 'y': 2000000},
},
{
'id': 'comp-002',
'name': 'C1',
'type': 'capacitor',
'value': '100nF',
'position': {'x': 3000000, 'y': 4000000},
},
],
'metadata': {
'createdAt': 1234567890,
'updatedAt': 1234567890,
},
};
final bytes = designToTile(design);
expect(bytes, isNotNull);
expect(bytes.length, greaterThan(0));
expect(bytes.length, lessThan(1000)); // 压缩后应该很小
});
test('should deserialize to original design', () {
final originalDesign = {
'id': 'design-002',
'name': 'Round Trip Test',
'components': [
{'id': 'c1', 'name': 'R1', 'type': 'resistor', 'value': '1k'},
{'id': 'c2', 'name': 'R2', 'type': 'resistor', 'value': '2k'},
],
};
final bytes = designToTile(originalDesign);
final restoredDesign = tileToDesign(bytes);
expect(restoredDesign['id'], equals(originalDesign['id']));
expect(restoredDesign['name'], equals(originalDesign['name']));
expect(restoredDesign['components'], isA<List>());
expect(restoredDesign['components'].length, equals(2));
});
test('should handle empty design', () {
final emptyDesign = {
'id': 'empty',
'name': 'Empty',
'components': [],
'nets': [],
};
final bytes = designToTile(emptyDesign);
final restored = tileToDesign(bytes);
expect(restored['id'], equals('empty'));
expect(restored['components'], equals([]));
});
test('should compress repeated strings', () {
// 创建包含大量重复字符串的设计
final design = {
'id': 'test',
'components': List.generate(100, (i) => {
'id': 'comp-$i',
'name': 'R$i',
'type': 'resistor', // 重复字符串
'value': '10k', // 重复字符串
}),
};
final bytes = designToTile(design);
final jsonSize = design.toString().length;
final tileSize = bytes.length;
// Tile 格式应该显著小于 JSON
expect(tileSize, lessThan(jsonSize));
print('Compression: JSON=$jsonSize bytes, Tile=$tileSize bytes, ratio=${tileSize/jsonSize*100}%');
});
test('should handle coordinate delta encoding', () {
// 创建坐标连续的设计
final design = {
'id': 'trace-test',
'trace': {
'points': [
{'x': 0, 'y': 0},
{'x': 100, 'y': 100},
{'x': 200, 'y': 150},
{'x': 350, 'y': 200},
{'x': 500, 'y': 300},
],
},
};
final bytes = designToTile(design);
final restored = tileToDesign(bytes);
expect(restored['trace']['points'], isA<List>());
expect(restored['trace']['points'].length, equals(5));
});
});
// ============================================================================
// KiCad 导入器测试
// ============================================================================
group('KicadImporter', () {
test('should parse simple kicad schematic', () {
final content = '''
(kicad_sch (version 20211014)
(component
(reference "R1")
(value "10k")
(footprint "Resistor_SMD:R_0805")
(at 100 200 0)
)
(component
(reference "C1")
(value "100nF")
(footprint "Capacitor_SMD:C_0805")
(at 300 400 90)
)
)
''';
final importer = KicadImporter();
final schematic = importer.import(content);
expect(schematic.version, equals('20211014'));
expect(schematic.components.length, equals(2));
expect(schematic.components[0].reference, equals('R1'));
expect(schematic.components[0].value, equals('10k'));
expect(schematic.components[0].x, equals(100));
expect(schematic.components[0].y, equals(200));
});
test('should convert kicad to EDA design', () {
final content = '''
(kicad_sch (version 20211014)
(component
(reference "U1")
(value "ATmega328P")
(footprint "Package_DIP:DIP-28")
)
(net "VCC"
(node (pin "U1" "VCC"))
)
(net "GND"
(node (pin "U1" "GND"))
)
)
''';
final design = importKicadSchematic(content);
expect(design['id'], isNotNull);
expect(design['name'], equals('Imported from KiCad'));
expect(design['tables']['components'], isA<List>());
expect(design['tables']['nets'], isA<List>());
});
test('should handle component with pins', () {
final content = '''
(kicad_sch (version 20211014)
(component
(reference "U1")
(value "ATmega328P")
(pin (at 0 0 0) (property "Name" "VCC") (property "Number" "7"))
(pin (at 0 10 0) (property "Name" "GND") (property "Number" "8"))
)
)
''';
final importer = KicadImporter();
final schematic = importer.import(content);
expect(schematic.components.length, equals(1));
expect(schematic.components[0].pins.length, equals(2));
});
});
// ============================================================================
// 增量保存测试
// ============================================================================
group('IncrementalSave', () {
test('should record operations', () {
final manager = createIncrementalSaveManager();
manager.recordOperation(AddComponentCommand(
component: {'id': 'c1', 'name': 'R1'},
));
expect(manager.history.canUndo, isTrue);
expect(manager.history.undoStackSize, equals(1));
});
test('should support undo', () {
final manager = createIncrementalSaveManager();
manager.recordOperation(AddComponentCommand(
component: {'id': 'c1', 'name': 'R1'},
));
expect(manager.history.canUndo, isTrue);
manager.history.undo();
expect(manager.history.canUndo, isFalse);
expect(manager.history.canRedo, isTrue);
});
test('should support redo', () {
final manager = createIncrementalSaveManager();
manager.recordOperation(AddComponentCommand(
component: {'id': 'c1', 'name': 'R1'},
));
manager.history.undo();
expect(manager.history.canRedo, isTrue);
manager.history.redo();
expect(manager.history.canRedo, isFalse);
expect(manager.history.canUndo, isTrue);
});
test('should create snapshot', () {
final manager = createIncrementalSaveManager();
final design = {
'id': 'test',
'components': [{'id': 'c1', 'name': 'R1'}],
};
manager.setCurrentState(design);
manager.createSnapshot(design);
expect(manager.lastSnapshot, isNotNull);
expect(manager.isDirty, isFalse);
});
test('should save and restore', () {
final manager = createIncrementalSaveManager();
final design = {
'id': 'test',
'components': [{'id': 'c1', 'name': 'R1'}],
};
manager.setCurrentState(design);
manager.createSnapshot(design);
// 添加一些操作
manager.recordOperation(MoveComponentCommand(
componentId: 'c1',
oldX: 0,
oldY: 0,
newX: 100,
newY: 100,
));
// 保存
final saveData = manager.save();
expect(saveData.snapshot, isNotNull);
expect(saveData.deltaLog.length, equals(1));
// 恢复
manager.restore(saveData);
expect(manager.currentState, isNotNull);
});
test('should serialize save data', () {
final saveData = IncrementalSaveData(
snapshot: {'id': 'test'},
deltaLog: [
{'type': 'componentMove', 'componentId': 'c1'},
],
timestamp: DateTime.now(),
);
final bytes = saveData.toBytes();
final restored = IncrementalSaveData.fromBytes(bytes);
expect(restored.snapshot, isNotNull);
expect(restored.deltaLog.length, equals(1));
expect(restored.timestamp.millisecondsSinceEpoch,
equals(saveData.timestamp.millisecondsSinceEpoch));
});
});
// ============================================================================
// 断点恢复测试
// ============================================================================
group('CheckpointManager', () {
test('should create checkpoint', () {
final manager = createCheckpointManager();
final state = {'id': 'test', 'value': 123};
manager.createCheckpoint('test_checkpoint', state, 'Test reason');
expect(manager.checkpoints.length, equals(1));
});
test('should restore from checkpoint', () {
final manager = createCheckpointManager();
final state = {'id': 'test', 'value': 123};
manager.createCheckpoint('before_change', state, 'Before modification');
final restored = manager.restoreFromCheckpoint('before_change');
expect(restored, isNotNull);
expect(restored!['id'], equals('test'));
expect(restored['value'], equals(123));
});
test('should restore from latest checkpoint', () {
final manager = createCheckpointManager();
manager.createCheckpoint('v1', {'version': 1}, 'First version');
manager.createCheckpoint('v2', {'version': 2}, 'Second version');
manager.createCheckpoint('v3', {'version': 3}, 'Third version');
final restored = manager.restoreFromLatest();
expect(restored, isNotNull);
expect(restored!['version'], equals(3));
});
test('should limit checkpoint count', () {
final manager = createCheckpointManager(maxCheckpoints: 3);
for (int i = 0; i < 5; i++) {
manager.createCheckpoint('v$i', {'version': i}, 'Version $i');
}
expect(manager.checkpoints.length, equals(3));
expect(manager.checkpoints[0].name, equals('v2'));
expect(manager.checkpoints[2].name, equals('v4'));
});
});
// ============================================================================
// 性能测试
// ============================================================================
group('Performance', () {
test('should serialize 100 components quickly', () {
final design = {
'id': 'perf-test',
'components': List.generate(100, (i) => {
'id': 'comp-$i',
'name': 'R$i',
'type': 'resistor',
'value': '${1000 + i}Ω',
'position': {'x': i * 1000, 'y': i * 1000},
}),
};
final sw = Stopwatch()..start();
final bytes = designToTile(design);
sw.stop();
print('Serialize 100 components: ${sw.elapsedMilliseconds}ms');
expect(sw.elapsedMilliseconds, lessThan(100));
sw.reset();
sw.start();
final restored = tileToDesign(bytes);
sw.stop();
print('Deserialize 100 components: ${sw.elapsedMilliseconds}ms');
expect(sw.elapsedMilliseconds, lessThan(100));
});
test('should compress large design', () {
final design = {
'id': 'large-test',
'components': List.generate(500, (i) => {
'id': 'comp-$i',
'name': 'C$i',
'type': 'capacitor',
'value': '100nF',
'footprint': '0805',
}),
};
final jsonSize = design.toString().length;
final bytes = designToTile(design);
final tileSize = bytes.length;
final ratio = tileSize / jsonSize * 100;
print('Compression: JSON=$jsonSize bytes, Tile=$tileSize bytes, ratio=${ratio.toStringAsFixed(1)}%');
expect(ratio, lessThan(50)); // 应该压缩到 50% 以下
});
});
}