mcltspice/tests/test_asc_generator.py
2026-02-10 23:35:53 -07:00

177 lines
5.6 KiB
Python

"""Tests for asc_generator module: pin positioning, schematic rendering, templates."""
import pytest
from mcp_ltspice.asc_generator import (
AscSchematic,
GRID,
_PIN_OFFSETS,
_rotate,
generate_inverting_amp,
generate_rc_lowpass,
generate_voltage_divider,
pin_position,
)
class TestPinPosition:
@pytest.mark.parametrize("symbol", ["voltage", "res", "cap", "ind"])
def test_r0_returns_offset_plus_origin(self, symbol):
"""At R0, pin position = origin + raw offset."""
cx, cy = 160, 80
for pin_idx in range(2):
px, py = pin_position(symbol, pin_idx, cx, cy, rotation=0)
offsets = _PIN_OFFSETS[symbol]
ox, oy = offsets[pin_idx]
assert px == cx + ox
assert py == cy + oy
@pytest.mark.parametrize("symbol", ["voltage", "res", "cap", "ind"])
def test_r90(self, symbol):
"""R90 applies (px, py) -> (-py, px)."""
cx, cy = 160, 80
for pin_idx in range(2):
px, py = pin_position(symbol, pin_idx, cx, cy, rotation=90)
offsets = _PIN_OFFSETS[symbol]
ox, oy = offsets[pin_idx]
assert px == cx + (-oy)
assert py == cy + ox
@pytest.mark.parametrize("symbol", ["voltage", "res", "cap", "ind"])
def test_r180(self, symbol):
"""R180 applies (px, py) -> (-px, -py)."""
cx, cy = 160, 80
for pin_idx in range(2):
px, py = pin_position(symbol, pin_idx, cx, cy, rotation=180)
offsets = _PIN_OFFSETS[symbol]
ox, oy = offsets[pin_idx]
assert px == cx + (-ox)
assert py == cy + (-oy)
@pytest.mark.parametrize("symbol", ["voltage", "res", "cap", "ind"])
def test_r270(self, symbol):
"""R270 applies (px, py) -> (py, -px)."""
cx, cy = 160, 80
for pin_idx in range(2):
px, py = pin_position(symbol, pin_idx, cx, cy, rotation=270)
offsets = _PIN_OFFSETS[symbol]
ox, oy = offsets[pin_idx]
assert px == cx + oy
assert py == cy + (-ox)
def test_unknown_symbol_defaults(self):
"""Unknown symbol uses default pin offsets."""
px, py = pin_position("unknown", 0, 0, 0, rotation=0)
# Default is [(0, 0), (0, 80)]
assert (px, py) == (0, 0)
px2, py2 = pin_position("unknown", 1, 0, 0, rotation=0)
assert (px2, py2) == (0, 80)
class TestRotate:
def test_identity(self):
assert _rotate(10, 20, 0) == (10, 20)
def test_90(self):
assert _rotate(10, 20, 90) == (-20, 10)
def test_180(self):
assert _rotate(10, 20, 180) == (-10, -20)
def test_270(self):
assert _rotate(10, 20, 270) == (20, -10)
def test_invalid_rotation(self):
"""Invalid rotation falls through to identity."""
assert _rotate(10, 20, 45) == (10, 20)
class TestAscSchematicRender:
def test_version_header(self):
sch = AscSchematic()
text = sch.render()
assert text.startswith("Version 4\n")
def test_sheet_dimensions(self):
sch = AscSchematic(sheet_w=1200, sheet_h=900)
text = sch.render()
assert "SHEET 1 1200 900" in text
def test_wire_rendering(self):
sch = AscSchematic()
sch.add_wire(80, 96, 176, 96)
text = sch.render()
assert "WIRE 80 96 176 96" in text
def test_component_rendering(self):
sch = AscSchematic()
sch.add_component("res", "R1", "1k", 160, 80)
text = sch.render()
assert "SYMBOL res 160 80 R0" in text
assert "SYMATTR InstName R1" in text
assert "SYMATTR Value 1k" in text
def test_rotated_component(self):
sch = AscSchematic()
sch.add_component("res", "R1", "1k", 160, 80, rotation=90)
text = sch.render()
assert "SYMBOL res 160 80 R90" in text
def test_ground_flag(self):
sch = AscSchematic()
sch.add_ground(80, 176)
text = sch.render()
assert "FLAG 80 176 0" in text
def test_net_label(self):
sch = AscSchematic()
sch.add_net_label("out", 176, 176)
text = sch.render()
assert "FLAG 176 176 out" in text
def test_directive_rendering(self):
sch = AscSchematic()
sch.add_directive(".tran 10m", 80, 300)
text = sch.render()
assert "TEXT 80 300 Left 2 !.tran 10m" in text
def test_chaining(self):
sch = (
AscSchematic()
.add_component("res", "R1", "1k", 160, 80)
.add_wire(80, 96, 176, 96)
.add_ground(80, 176)
)
text = sch.render()
assert "SYMBOL" in text
assert "WIRE" in text
assert "FLAG" in text
class TestAscTemplates:
@pytest.mark.parametrize(
"factory",
[generate_rc_lowpass, generate_voltage_divider, generate_inverting_amp],
)
def test_template_returns_schematic(self, factory):
sch = factory()
assert isinstance(sch, AscSchematic)
@pytest.mark.parametrize(
"factory",
[generate_rc_lowpass, generate_voltage_divider, generate_inverting_amp],
)
def test_template_nonempty(self, factory):
text = factory().render()
assert len(text) > 50
assert "SYMBOL" in text
@pytest.mark.parametrize(
"factory",
[generate_rc_lowpass, generate_voltage_divider, generate_inverting_amp],
)
def test_template_has_expected_components(self, factory):
text = factory().render()
# All templates should have at least a res and a voltage source
assert "res" in text or "voltage" in text