"""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