import 'package:flutter_test/flutter_test.dart'; import 'package:flutter/material.dart'; /// 编辑器状态管理测试 /// 测试撤销/重做、元件管理、画布状态等核心功能 void main() { group('Editor State Management Tests', () { // 模拟编辑器状态 class MockEditorState { final List> _history = []; final List> _redoStack = []; final List> _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 component) { _saveState(); _components.add(component); } void removeComponent(Map 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>); return true; } bool redo() { if (!canRedo) return false; _saveState(); _components.clear(); _components.addAll(_redoStack.removeLast() as List>); 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); }); }); }