"""S-expression emitter tests.""" from tools.epro2.kicad._sexpr_reader import parse from tools.epro2.kicad.sexpr import Sym, to_sexpr def test_round_trip_simple(): src = [Sym("kicad_sch"), [Sym("version"), 20231120], [Sym("paper"), "A4"]] text = to_sexpr(src, pretty=False) parsed = parse(text) assert parsed[0] == "kicad_sch" assert parsed[1] == ["version", 20231120] assert parsed[2] == ["paper", "A4"] def test_string_escaping(): text = to_sexpr([Sym("title"), 'a "quote" + \\backslash'], pretty=False) parsed = parse(text) assert parsed[1] == 'a "quote" + \\backslash' def test_string_escapes_newlines_and_tabs(): """KiCad 8 rejects literal newlines/CR/tabs inside quoted strings, so the emitter must escape them. Round-trip restores the original characters.""" src = "Battary\n3.7V 700mAH\twith\rtab" text = to_sexpr([Sym("text"), src], pretty=False) # Raw text must NOT contain literal newline / CR / tab inside quotes assert "\\n" in text assert "\\r" in text assert "\\t" in text quoted = text[text.index('"'):text.rindex('"') + 1] assert "\n" not in quoted assert "\r" not in quoted assert "\t" not in quoted # Round-trip restores the original (our reader handles these escapes) parsed = parse(text) assert parsed[1] == src def test_float_formatting_strips_trailing_zeros(): text = to_sexpr([Sym("x"), 1.500000], pretty=False) assert "1.5" in text assert "1.500000" not in text def test_bool_renders_as_yes_no(): text = to_sexpr([Sym("hide"), True, False], pretty=False) parsed = parse(text) assert parsed == ["hide", "yes", "no"] def test_pretty_indent_keeps_round_trip(): src = [Sym("kicad_sch"), [Sym("wire"), [Sym("pts"), [Sym("xy"), 1.0, 2.0], [Sym("xy"), 3.0, 4.0]]]] pretty = to_sexpr(src, pretty=True) flat = to_sexpr(src, pretty=False) assert parse(pretty) == parse(flat) def test_nan_inf_rejected(): import math try: to_sexpr([Sym("x"), float("nan")]) except ValueError: pass else: raise AssertionError("expected ValueError on NaN") try: to_sexpr([Sym("x"), math.inf]) except ValueError: return raise AssertionError("expected ValueError on Inf")