"""Tests for schematic editing tools.""" import os import tempfile import pytest from tests.conftest import requires_sch_api @requires_sch_api @pytest.mark.unit class TestModifyComponent: """Tests for the modify_component tool.""" def test_modify_value(self, populated_schematic): from mckicad.tools.schematic_edit import modify_component result = modify_component( schematic_path=populated_schematic, reference="R1", value="22k", ) assert result["success"] is True assert any("value" in m for m in result["modified"]) def test_modify_nonexistent_component(self, populated_schematic): from mckicad.tools.schematic_edit import modify_component result = modify_component( schematic_path=populated_schematic, reference="Z99", value="1k", ) assert result["success"] is False assert "error" in result def test_modify_no_changes(self, populated_schematic): from mckicad.tools.schematic_edit import modify_component result = modify_component( schematic_path=populated_schematic, reference="R1", ) assert result["success"] is True assert result.get("modified") == [] @requires_sch_api @pytest.mark.unit class TestRemoveComponent: """Tests for the remove_component tool.""" def test_remove_existing(self, populated_schematic): from mckicad.tools.schematic_edit import remove_component result = remove_component( schematic_path=populated_schematic, reference="R2", ) assert result["success"] is True assert result["reference"] == "R2" def test_remove_nonexistent(self, populated_schematic): from mckicad.tools.schematic_edit import remove_component result = remove_component( schematic_path=populated_schematic, reference="Z99", ) assert result["success"] is False @requires_sch_api @pytest.mark.unit class TestBackupSchematic: """Tests for the backup_schematic tool.""" def test_backup_creates_file(self, populated_schematic): from mckicad.tools.schematic_edit import backup_schematic result = backup_schematic(schematic_path=populated_schematic) assert result["success"] is True assert "backup_path" in result assert os.path.isfile(result["backup_path"]) @pytest.mark.unit class TestValidation: """Tests for input validation in edit tools.""" def test_invalid_path_extension(self): from mckicad.tools.schematic_edit import modify_component result = modify_component( schematic_path="/tmp/test.txt", reference="R1", value="1k", ) assert result["success"] is False assert ".kicad_sch" in result["error"] def test_empty_path(self): from mckicad.tools.schematic_edit import remove_component result = remove_component(schematic_path="", reference="R1") assert result["success"] is False def test_nonexistent_file(self): from mckicad.tools.schematic_edit import set_title_block result = set_title_block( schematic_path="/tmp/nonexistent.kicad_sch", title="Test", ) assert result["success"] is False @requires_sch_api @pytest.mark.unit class TestSetTitleBlock: """Tests for the set_title_block tool.""" def test_set_fields(self, populated_schematic): from mckicad.tools.schematic_edit import set_title_block result = set_title_block( schematic_path=populated_schematic, title="Test Circuit", revision="1.0", ) assert result["success"] is True assert "title" in result.get("fields_set", []) def test_set_author(self, populated_schematic): from mckicad.tools.schematic_edit import set_title_block result = set_title_block( schematic_path=populated_schematic, author="Test Author", ) assert result["success"] is True assert "author" in result.get("fields_set", []) def test_no_fields_is_noop(self, populated_schematic): from mckicad.tools.schematic_edit import set_title_block result = set_title_block(schematic_path=populated_schematic) assert result["success"] is True assert result.get("fields_set") == [] @requires_sch_api @pytest.mark.unit class TestAnnotationTools: """Tests for add_text_annotation and add_no_connect.""" def test_add_text(self, populated_schematic): from mckicad.tools.schematic_edit import add_text_annotation result = add_text_annotation( schematic_path=populated_schematic, text="Test note", x=50, y=50, ) assert result["success"] is True def test_add_no_connect(self, populated_schematic): from mckicad.tools.schematic_edit import add_no_connect result = add_no_connect( schematic_path=populated_schematic, x=300, y=300, ) assert result["success"] is True # Minimal schematic with wire segments for testing remove_wires_by_criteria. # Does NOT require kicad-sch-api — works on raw s-expression files. _WIRES_SCHEMATIC = """\ (kicad_sch (version 20231120) (generator "eeschema") (uuid "test-root") (paper "A4") (lib_symbols ) (wire (pts (xy 148.59 194.31) (xy 156.21 194.31)) (stroke (width 0) (type default)) (uuid "hw-1") ) (wire (pts (xy 156.21 194.31) (xy 163.83 194.31)) (stroke (width 0) (type default)) (uuid "hw-2") ) (wire (pts (xy 163.83 194.31) (xy 171.45 194.31)) (stroke (width 0) (type default)) (uuid "hw-3") ) (wire (pts (xy 100.0 100.0) (xy 100.0 130.0)) (stroke (width 0) (type default)) (uuid "vw-1") ) (wire (pts (xy 200.0 50.0) (xy 250.0 80.0)) (stroke (width 0) (type default)) (uuid "dw-1") ) ) """ @pytest.fixture def wires_schematic(): """Write a schematic with wires to a temp file for testing.""" with tempfile.NamedTemporaryFile( mode="w", suffix=".kicad_sch", delete=False, encoding="utf-8", ) as f: f.write(_WIRES_SCHEMATIC) path = f.name yield path os.unlink(path) @pytest.mark.unit class TestRemoveWiresByCriteria: """Tests for the remove_wires_by_criteria tool.""" def test_dry_run_horizontal(self, wires_schematic): from mckicad.tools.schematic_edit import remove_wires_by_criteria result = remove_wires_by_criteria( schematic_path=wires_schematic, y=194.31, dry_run=True, ) assert result["success"] is True assert result["dry_run"] is True assert result["matched_count"] == 3 assert result["removed_count"] == 0 def test_remove_horizontal_wires(self, wires_schematic): from mckicad.tools.schematic_edit import remove_wires_by_criteria result = remove_wires_by_criteria( schematic_path=wires_schematic, y=194.31, ) assert result["success"] is True assert result["removed_count"] == 3 # Verify they're gone from mckicad.utils.sexp_parser import parse_wire_segments remaining = parse_wire_segments(wires_schematic) remaining_uuids = {w["uuid"] for w in remaining} assert "hw-1" not in remaining_uuids assert "hw-2" not in remaining_uuids assert "hw-3" not in remaining_uuids assert "vw-1" in remaining_uuids def test_remove_with_x_range(self, wires_schematic): from mckicad.tools.schematic_edit import remove_wires_by_criteria result = remove_wires_by_criteria( schematic_path=wires_schematic, y=194.31, min_x=148.0, max_x=164.0, ) assert result["success"] is True assert result["matched_count"] == 2 # hw-1 and hw-2 only def test_remove_vertical_wire(self, wires_schematic): from mckicad.tools.schematic_edit import remove_wires_by_criteria result = remove_wires_by_criteria( schematic_path=wires_schematic, x=100.0, ) assert result["success"] is True assert result["matched_count"] == 1 assert result["removed_count"] == 1 def test_no_criteria_fails(self, wires_schematic): from mckicad.tools.schematic_edit import remove_wires_by_criteria result = remove_wires_by_criteria( schematic_path=wires_schematic, ) assert result["success"] is False assert "criterion" in result["error"].lower() def test_no_matches(self, wires_schematic): from mckicad.tools.schematic_edit import remove_wires_by_criteria result = remove_wires_by_criteria( schematic_path=wires_schematic, y=999.0, ) assert result["success"] is True assert result["matched_count"] == 0 def test_invalid_path(self): from mckicad.tools.schematic_edit import remove_wires_by_criteria result = remove_wires_by_criteria( schematic_path="/tmp/nonexistent.kicad_sch", y=100.0, ) assert result["success"] is False def test_min_max_y_range(self, wires_schematic): from mckicad.tools.schematic_edit import remove_wires_by_criteria result = remove_wires_by_criteria( schematic_path=wires_schematic, min_y=99.0, max_y=131.0, dry_run=True, ) assert result["success"] is True # vw-1 (100→130) fits, horizontal wires at 194.31 do not assert result["matched_count"] == 1 def test_tolerance(self, wires_schematic): from mckicad.tools.schematic_edit import remove_wires_by_criteria # With tight tolerance, exact match result = remove_wires_by_criteria( schematic_path=wires_schematic, y=194.31, tolerance=0.001, dry_run=True, ) assert result["matched_count"] == 3 # Offset slightly beyond tolerance result = remove_wires_by_criteria( schematic_path=wires_schematic, y=194.35, tolerance=0.01, dry_run=True, ) assert result["matched_count"] == 0