"""Tests for diff module: schematic comparison.""" from pathlib import Path import pytest from mcp_ltspice.diff import ( ComponentChange, DirectiveChange, SchematicDiff, _diff_components, _diff_directives, _diff_nets, _diff_wires, diff_schematics, ) from mcp_ltspice.schematic import Component, Flag, Schematic, Text, Wire, write_schematic def _make_schematic(**kwargs) -> Schematic: """Helper to build a Schematic with overrides.""" sch = Schematic() sch.components = kwargs.get("components", []) sch.wires = kwargs.get("wires", []) sch.flags = kwargs.get("flags", []) sch.texts = kwargs.get("texts", []) return sch class TestDiffComponents: def test_added_component(self): sch_a = _make_schematic(components=[ Component(name="R1", symbol="res", x=160, y=80, rotation=0, mirror=False, attributes={"Value": "1k"}), ]) sch_b = _make_schematic(components=[ Component(name="R1", symbol="res", x=160, y=80, rotation=0, mirror=False, attributes={"Value": "1k"}), Component(name="C1", symbol="cap", x=256, y=176, rotation=0, mirror=False, attributes={"Value": "100n"}), ]) changes = _diff_components(sch_a, sch_b) added = [c for c in changes if c.change_type == "added"] assert len(added) == 1 assert added[0].name == "C1" def test_removed_component(self): sch_a = _make_schematic(components=[ Component(name="R1", symbol="res", x=160, y=80, rotation=0, mirror=False, attributes={"Value": "1k"}), Component(name="R2", symbol="res", x=320, y=80, rotation=0, mirror=False, attributes={"Value": "2.2k"}), ]) sch_b = _make_schematic(components=[ Component(name="R1", symbol="res", x=160, y=80, rotation=0, mirror=False, attributes={"Value": "1k"}), ]) changes = _diff_components(sch_a, sch_b) removed = [c for c in changes if c.change_type == "removed"] assert len(removed) == 1 assert removed[0].name == "R2" def test_modified_value(self): sch_a = _make_schematic(components=[ Component(name="R1", symbol="res", x=160, y=80, rotation=0, mirror=False, attributes={"Value": "1k"}), ]) sch_b = _make_schematic(components=[ Component(name="R1", symbol="res", x=160, y=80, rotation=0, mirror=False, attributes={"Value": "2.2k"}), ]) changes = _diff_components(sch_a, sch_b) modified = [c for c in changes if c.change_type == "modified"] assert len(modified) == 1 assert modified[0].old_value == "1k" assert modified[0].new_value == "2.2k" def test_moved_component(self): sch_a = _make_schematic(components=[ Component(name="R1", symbol="res", x=160, y=80, rotation=0, mirror=False, attributes={"Value": "1k"}), ]) sch_b = _make_schematic(components=[ Component(name="R1", symbol="res", x=320, y=160, rotation=0, mirror=False, attributes={"Value": "1k"}), ]) changes = _diff_components(sch_a, sch_b) assert len(changes) == 1 assert changes[0].moved is True def test_no_changes(self): comp = Component(name="R1", symbol="res", x=160, y=80, rotation=0, mirror=False, attributes={"Value": "1k"}) sch = _make_schematic(components=[comp]) changes = _diff_components(sch, sch) assert len(changes) == 0 class TestDiffDirectives: def test_added_directive(self): sch_a = _make_schematic(texts=[ Text(80, 296, ".tran 10m", type="spice"), ]) sch_b = _make_schematic(texts=[ Text(80, 296, ".tran 10m", type="spice"), Text(80, 320, ".meas tran vmax MAX V(out)", type="spice"), ]) changes = _diff_directives(sch_a, sch_b) added = [c for c in changes if c.change_type == "added"] assert len(added) == 1 def test_removed_directive(self): sch_a = _make_schematic(texts=[ Text(80, 296, ".tran 10m", type="spice"), Text(80, 320, ".op", type="spice"), ]) sch_b = _make_schematic(texts=[ Text(80, 296, ".tran 10m", type="spice"), ]) changes = _diff_directives(sch_a, sch_b) removed = [c for c in changes if c.change_type == "removed"] assert len(removed) == 1 def test_modified_directive(self): sch_a = _make_schematic(texts=[ Text(80, 296, ".tran 10m", type="spice"), ]) sch_b = _make_schematic(texts=[ Text(80, 296, ".tran 50m", type="spice"), ]) changes = _diff_directives(sch_a, sch_b) modified = [c for c in changes if c.change_type == "modified"] assert len(modified) == 1 assert modified[0].old_text == ".tran 10m" assert modified[0].new_text == ".tran 50m" class TestDiffNets: def test_added_nets(self): sch_a = _make_schematic(flags=[Flag(80, 176, "0")]) sch_b = _make_schematic(flags=[Flag(80, 176, "0"), Flag(176, 176, "out")]) added, removed = _diff_nets(sch_a, sch_b) assert "out" in added assert len(removed) == 0 def test_removed_nets(self): sch_a = _make_schematic(flags=[Flag(80, 176, "0"), Flag(176, 176, "out")]) sch_b = _make_schematic(flags=[Flag(80, 176, "0")]) added, removed = _diff_nets(sch_a, sch_b) assert "out" in removed assert len(added) == 0 class TestDiffWires: def test_added_wires(self): sch_a = _make_schematic(wires=[Wire(80, 96, 176, 96)]) sch_b = _make_schematic(wires=[ Wire(80, 96, 176, 96), Wire(176, 176, 272, 176), ]) added, removed = _diff_wires(sch_a, sch_b) assert added == 1 assert removed == 0 def test_removed_wires(self): sch_a = _make_schematic(wires=[ Wire(80, 96, 176, 96), Wire(176, 176, 272, 176), ]) sch_b = _make_schematic(wires=[Wire(80, 96, 176, 96)]) added, removed = _diff_wires(sch_a, sch_b) assert added == 0 assert removed == 1 class TestSchematicDiff: def test_has_changes_false(self): diff = SchematicDiff() assert diff.has_changes is False def test_has_changes_true(self): diff = SchematicDiff(wires_added=1) assert diff.has_changes is True def test_summary_no_changes(self): diff = SchematicDiff() assert "No changes" in diff.summary() def test_to_dict(self): diff = SchematicDiff( component_changes=[ ComponentChange(name="R1", change_type="modified", old_value="1k", new_value="2.2k") ] ) d = diff.to_dict() assert d["has_changes"] is True assert len(d["component_changes"]) == 1 class TestDiffSchematicsIntegration: """Write two schematics to disk and compare them end-to-end.""" def test_full_diff(self, valid_schematic, tmp_path): # Create "before" schematic path_a = tmp_path / "before.asc" write_schematic(valid_schematic, path_a) # Create "after" schematic with a modified R1 value modified = Schematic() modified.flags = list(valid_schematic.flags) modified.wires = list(valid_schematic.wires) modified.texts = list(valid_schematic.texts) modified.components = [] for comp in valid_schematic.components: if comp.name == "R1": new_comp = Component( name=comp.name, symbol=comp.symbol, x=comp.x, y=comp.y, rotation=comp.rotation, mirror=comp.mirror, attributes={"Value": "4.7k"}, ) modified.components.append(new_comp) else: modified.components.append(comp) path_b = tmp_path / "after.asc" write_schematic(modified, path_b) diff = diff_schematics(path_a, path_b) assert diff.has_changes r1_changes = [c for c in diff.component_changes if c.name == "R1"] assert len(r1_changes) == 1 assert r1_changes[0].old_value == "1k" assert r1_changes[0].new_value == "4.7k"