"""Symbol writer regression: synthetic SYMBOL doc → KiCad lib symbol entry.""" from tools.epro2.kicad._sexpr_reader import parse from tools.epro2.kicad.sch_writer import write_sch_page from tools.epro2.kicad.sexpr import to_sexpr from tools.epro2.kicad.sym_writer import write_lib_symbol from tools.epro2.project_relations import ProjectRelations from tools.epro2.replay import Document, Project def _sym_doc(uuid: str, part_id: str, primitives: list[tuple[str, dict]]) -> Document: d = Document(doc_uuid=uuid, doc_type="SYMBOL") d.objects[part_id] = {"_type": "PART", "BBOX": [-10, 10, 10, -10], "title": part_id} for k, v in primitives: d.objects[k] = v return d def _block(parsed, name): return [c for c in parsed if isinstance(c, list) and c and c[0] == name] def test_write_lib_symbol_emits_outer_wrapper_and_body(): d = _sym_doc("sym1", "MyPart.1", []) entry = write_lib_symbol(d) assert entry is not None text = to_sexpr(entry) p = parse(text) assert p[0] == "symbol" assert p[1] == "facere:MyPart.1" # Properties: Reference / Value / Footprint / Datasheet props = _block(p, "property") by_name = {x[1]: x[2] for x in props} assert by_name["Reference"] == "U" assert by_name["Value"] == "MyPart.1" assert by_name["Footprint"] == "" # Inner body wrapper named _1_1 inner = _block(p, "symbol") assert len(inner) == 1 assert inner[0][1] == "MyPart.1_1_1" def test_pin_renders_with_attr_pulled_metadata(): d = _sym_doc("sym1", "MyPart.1", [ ("e5", {"_type": "PIN", "partId": "MyPart.1", "x": -15, "y": 0, "length": 10, "rotation": 0}), ("e6", {"_type": "ATTR", "parentId": "e5", "key": "Pin Name", "value": "VCC"}), ("e7", {"_type": "ATTR", "parentId": "e5", "key": "Pin Number", "value": "1"}), ("e8", {"_type": "ATTR", "parentId": "e5", "key": "Pin Type", "value": "POWER_IN"}), ]) entry = write_lib_symbol(d) text = to_sexpr(entry) parsed = parse(text) inner_body = next(c for c in parsed if isinstance(c, list) and c and c[0] == "symbol") pins = [c for c in inner_body if isinstance(c, list) and c and c[0] == "pin"] assert len(pins) == 1 pin = pins[0] assert pin[1] == "power_in" # electrical type mapped assert pin[2] == "line" # shape name_block = next(c for c in pin if isinstance(c, list) and c and c[0] == "name") num_block = next(c for c in pin if isinstance(c, list) and c and c[0] == "number") assert name_block[1] == "VCC" assert num_block[1] == "1" def test_rect_poly_circle_text_emit_correct_shapes(): d = _sym_doc("sym1", "MyPart.1", [ ("r1", {"_type": "RECT", "partId": "MyPart.1", "dotX1": -10, "dotY1": -5, "dotX2": 10, "dotY2": 5}), ("p1", {"_type": "POLY", "partId": "MyPart.1", "points": [{"x": 0, "y": 0}, {"x": 5, "y": 0}, {"x": 5, "y": 5}], "closed": True}), ("c1", {"_type": "CIRCLE", "partId": "MyPart.1", "centerX": 0, "centerY": 0, "radius": 3}), ("t1", {"_type": "TEXT", "partId": "MyPart.1", "x": 0, "y": -5, "value": "label"}), ]) entry = write_lib_symbol(d) parsed = parse(to_sexpr(entry)) inner = next(c for c in parsed if isinstance(c, list) and c[0] == "symbol") types = [c[0] for c in inner if isinstance(c, list)] assert "rectangle" in types assert "polyline" in types assert "circle" in types assert "text" in types def test_non_symbol_doc_returns_none(): d = Document(doc_uuid="x", doc_type="SCH_PAGE") assert write_lib_symbol(d) is None def test_sch_writer_embeds_lib_symbols_via_project_relations(): sym = _sym_doc("sym1", "MyPart.1", [ ("e5", {"_type": "PIN", "partId": "MyPart.1", "x": 0, "y": 0, "length": 10}), ("e6", {"_type": "ATTR", "parentId": "e5", "key": "Pin Name", "value": "1"}), ("e7", {"_type": "ATTR", "parentId": "e5", "key": "Pin Number", "value": "1"}), ]) sch = Document(doc_uuid="sch1", doc_type="SCH_PAGE") sch.objects["e1"] = {"_type": "COMPONENT", "partId": "MyPart.1", "x": 0, "y": 0, "rotation": 0} sch.objects["e2"] = {"_type": "COMPONENT", "partId": "GhostPart.99", "x": 50, "y": 50} p = Project(project_uuid="p") p.documents["sym1"] = sym p.documents["sch1"] = sch pr = ProjectRelations.build(p) text = write_sch_page(sch, project_relations=pr) parsed = parse(text) lib = next(c for c in parsed if isinstance(c, list) and c[0] == "lib_symbols") embedded = [c for c in lib[1:] if isinstance(c, list) and c[0] == "symbol"] assert len(embedded) == 1 # MyPart found, GhostPart missed assert embedded[0][1] == "facere:MyPart.1" stats = getattr(write_sch_page, "last_stats") assert stats.lib_symbols_embedded == 1 assert stats.lib_symbols_missing == 1 def test_sch_writer_without_project_relations_emits_empty_lib_symbols(): sch = Document(doc_uuid="sch1", doc_type="SCH_PAGE") sch.objects["e1"] = {"_type": "COMPONENT", "partId": "X.1", "x": 0, "y": 0} text = write_sch_page(sch) # no pr passed parsed = parse(text) lib = next(c for c in parsed if isinstance(c, list) and c[0] == "lib_symbols") # Phase-1 stub: just `(lib_symbols)` with no children assert lib == ["lib_symbols"]