mobile-eda/test/unit/editor_state_test.dart

358 lines
10 KiB
Dart

import 'package:flutter_test/flutter_test.dart';
import 'package:flutter/material.dart';
/// 编辑器状态管理测试
/// 测试撤销/重做、元件管理、画布状态等核心功能
void main() {
group('Editor State Management Tests', () {
// 模拟编辑器状态
class MockEditorState {
final List<Map<String, dynamic>> _history = [];
final List<Map<String, dynamic>> _redoStack = [];
final List<Map<String, dynamic>> _components = [];
int get historyLength => _history.length;
int get redoStackLength => _redoStack.length;
int get componentCount => _components.length;
bool get canUndo => _history.isNotEmpty;
bool get canRedo => _redoStack.isNotEmpty;
void addComponent(Map<String, dynamic> component) {
_saveState();
_components.add(component);
}
void removeComponent(Map<String, dynamic> component) {
_saveState();
_components.remove(component);
}
void _saveState() {
_history.add(List.from(_components));
_redoStack.clear();
}
bool undo() {
if (!canUndo) return false;
_redoStack.add(List.from(_components));
_components.clear();
_components.addAll(_history.removeLast() as List<Map<String, dynamic>>);
return true;
}
bool redo() {
if (!canRedo) return false;
_saveState();
_components.clear();
_components.addAll(_redoStack.removeLast() as List<Map<String, dynamic>>);
return true;
}
void clear() {
_saveState();
_components.clear();
}
}
test('initial state should be empty', () {
final state = MockEditorState();
expect(state.historyLength, 0);
expect(state.redoStackLength, 0);
expect(state.componentCount, 0);
expect(state.canUndo, false);
expect(state.canRedo, false);
});
test('should add component and save state', () {
final state = MockEditorState();
state.addComponent({'ref': 'R1', 'value': '10k'});
expect(state.componentCount, 1);
expect(state.historyLength, 1);
expect(state.canUndo, true);
});
test('should undo component addition', () {
final state = MockEditorState();
state.addComponent({'ref': 'R1', 'value': '10k'});
expect(state.componentCount, 1);
final result = state.undo();
expect(result, true);
expect(state.componentCount, 0);
expect(state.canUndo, false);
expect(state.canRedo, true);
});
test('should redo after undo', () {
final state = MockEditorState();
state.addComponent({'ref': 'R1', 'value': '10k'});
state.undo();
expect(state.componentCount, 0);
final result = state.redo();
expect(result, true);
expect(state.componentCount, 1);
expect(state.canRedo, false);
});
test('should clear redo stack on new action', () {
final state = MockEditorState();
state.addComponent({'ref': 'R1', 'value': '10k'});
state.undo();
expect(state.canRedo, true);
state.addComponent({'ref': 'C1', 'value': '10u'});
expect(state.canRedo, false);
});
test('should handle multiple undo/redo', () {
final state = MockEditorState();
state.addComponent({'ref': 'R1', 'value': '10k'});
state.addComponent({'ref': 'R2', 'value': '20k'});
state.addComponent({'ref': 'R3', 'value': '30k'});
expect(state.componentCount, 3);
expect(state.historyLength, 3);
state.undo();
expect(state.componentCount, 2);
state.undo();
expect(state.componentCount, 1);
state.undo();
expect(state.componentCount, 0);
state.redo();
expect(state.componentCount, 1);
state.redo();
expect(state.componentCount, 2);
});
test('should handle undo when empty', () {
final state = MockEditorState();
final result = state.undo();
expect(result, false);
expect(state.componentCount, 0);
});
test('should handle redo when empty', () {
final state = MockEditorState();
final result = state.redo();
expect(result, false);
expect(state.componentCount, 0);
});
test('should remove component', () {
final state = MockEditorState();
final component = {'ref': 'R1', 'value': '10k'};
state.addComponent(component);
expect(state.componentCount, 1);
state.removeComponent(component);
expect(state.componentCount, 0);
expect(state.canUndo, true);
});
test('should clear all components', () {
final state = MockEditorState();
state.addComponent({'ref': 'R1', 'value': '10k'});
state.addComponent({'ref': 'C1', 'value': '10u'});
expect(state.componentCount, 2);
state.clear();
expect(state.componentCount, 0);
expect(state.canUndo, true);
});
test('should limit history size', () {
final state = MockEditorState();
const maxHistory = 50;
// 添加超过最大历史数的操作
for (int i = 0; i < 100; i++) {
state.addComponent({'ref': 'R$i', 'value': '${i}k'});
}
// TODO: 实现历史限制后验证
// expect(state.historyLength, maxHistory);
});
});
group('Canvas Transform Tests', () {
test('should calculate visible rect correctly', () {
const screenWidth = 400.0;
const screenHeight = 800.0;
const zoomLevel = 2.0;
const offsetX = 100.0;
const offsetY = 200.0;
// 计算可见区域
final visibleRect = Rect.fromLTWH(
-offsetX / zoomLevel,
-offsetY / zoomLevel,
screenWidth / zoomLevel,
screenHeight / zoomLevel,
);
expect(visibleRect.left, -50.0);
expect(visibleRect.top, -100.0);
expect(visibleRect.width, 200.0);
expect(visibleRect.height, 400.0);
});
test('should clamp zoom level', () {
double zoomLevel = 1.0;
const minZoom = 0.1;
const maxZoom = 10.0;
// 放大
zoomLevel = (zoomLevel * 1.5).clamp(minZoom, maxZoom);
expect(zoomLevel, 1.5);
// 继续放大到上限
zoomLevel = (zoomLevel * 10).clamp(minZoom, maxZoom);
expect(zoomLevel, maxZoom);
// 缩小
zoomLevel = (zoomLevel * 0.1).clamp(minZoom, maxZoom);
expect(zoomLevel, 1.0);
// 继续缩小到下限
zoomLevel = (zoomLevel * 0.01).clamp(minZoom, maxZoom);
expect(zoomLevel, minZoom);
});
test('should check if component is visible', () {
final visibleRect = Rect.fromLTWH(0, 0, 100, 100);
// 完全可见
final component1 = Rect.fromLTWH(10, 10, 20, 20);
expect(visibleRect.overlaps(component1), true);
// 部分可见
final component2 = Rect.fromLTWH(90, 90, 20, 20);
expect(visibleRect.overlaps(component2), true);
// 完全不可见
final component3 = Rect.fromLTWH(200, 200, 20, 20);
expect(visibleRect.overlaps(component3), false);
});
});
group('Component Validation Tests', () {
test('should validate ref designator format', () {
bool isValidRef(String ref) {
// 位号格式:字母 + 数字,如 R1, C10, U3
final regex = RegExp(r'^[A-Z]+\d+$');
return regex.hasMatch(ref);
}
expect(isValidRef('R1'), true);
expect(isValidRef('C10'), true);
expect(isValidRef('U3'), true);
expect(isValidRef('R1A'), false);
expect(isValidRef('1R'), false);
expect(isValidRef('R'), false);
expect(isValidRef(''), false);
});
test('should validate component value format', () {
bool isValidValue(String value) {
// 值格式:数字 + 单位(可选),如 10k, 100u, 5V
final regex = RegExp(r'^\d+(\.\d+)?[a-zA-Z]*$');
return regex.hasMatch(value);
}
expect(isValidValue('10k'), true);
expect(isValidValue('100u'), true);
expect(isValidValue('5V'), true);
expect(isValidValue('1.5k'), true);
expect(isValidValue('10'), true);
expect(isValidValue(''), false);
expect(isValidValue('abc'), false);
});
test('should validate footprint format', () {
bool isValidFootprint(String footprint) {
// 封装格式:数字 + 数字,如 0805, 0603, SOT23
return footprint.isNotEmpty;
}
expect(isValidFootprint('0805'), true);
expect(isValidFootprint('0603'), true);
expect(isValidFootprint('SOT23'), true);
expect(isValidFootprint(''), false);
});
});
group('Grid Snapping Tests', () {
test('should snap to grid', () {
const gridSize = 10.0;
double snapToGrid(double value) {
return (value / gridSize).round() * gridSize;
}
expect(snapToGrid(5.0), 10.0);
expect(snapToGrid(12.0), 10.0);
expect(snapToGrid(17.0), 20.0);
expect(snapToGrid(25.0), 30.0);
expect(snapToGrid(0.0), 0.0);
expect(snapToGrid(-5.0), -10.0);
});
test('should handle different grid sizes', () {
double snapToGrid(double value, double gridSize) {
return (value / gridSize).round() * gridSize;
}
expect(snapToGrid(7.0, 5.0), 5.0);
expect(snapToGrid(7.0, 5.0), 5.0);
expect(snapToGrid(13.0, 5.0), 15.0);
});
});
group('Wire Connection Tests', () {
test('should check if points are connected', () {
const snapDistance = 5.0;
bool areConnected(Offset p1, Offset p2) {
return (p1 - p2).distance <= snapDistance;
}
expect(areConnected(const Offset(0, 0), const Offset(3, 4)), true); // distance = 5
expect(areConnected(const Offset(0, 0), const Offset(6, 8)), false); // distance = 10
expect(areConnected(const Offset(0, 0), const Offset(0, 0)), true);
});
test('should calculate wire length', () {
double wireLength(Offset p1, Offset p2) {
return (p2 - p1).distance;
}
expect(wireLength(const Offset(0, 0), const Offset(3, 4)), 5.0);
expect(wireLength(const Offset(0, 0), const Offset(10, 0)), 10.0);
});
});
}