"""Self-check: what the builder makes has to be something YOOtheme will render. The failure this guards against is silent. YOOtheme does not reject a headline placed straight into a section — it renders an empty band, the operator sees a gap where their change should be, and nothing in any log says why. So the nesting contract is enforced before the write, and the presets are checked against the same contract they are supposed to demonstrate. Run: python3 test_builder.py """ import builder def test_the_nesting_contract_is_refused_not_rendered_blank(): for parent, child in ((builder.section(), builder.headline("x")), (builder.section(), builder.column()), (builder.row(), builder.text("x")), (builder.column(), builder.section())): try: builder.place(parent, child) except ValueError as e: assert parent["type"] in str(e) or "column" in str(e) else: raise AssertionError( f"a {child['type']} inside a {parent['type']} must be refused") def test_the_legal_nesting_is_accepted(): page = {"type": "layout", "children": []} sec = builder.place(page, builder.section()) r = builder.place(sec, builder.row()) col = builder.place(r, builder.column("1-2")) builder.place(col, builder.headline("Hello")) builder.place(col, builder.button("Go", "/somewhere")) assert [c["type"] for c in col["children"]] == ["headline", "button"] assert col["props"]["width_default"] == "1-2" def test_a_bare_element_gets_the_column_it_needs(): """The common request is "add a button here". A button with no column around it renders nothing, so the wrapper is built rather than the request refused.""" wrapped = builder.wrap(builder.button("Donate")) assert wrapped["type"] == "section" assert wrapped["children"][0]["type"] == "row" assert wrapped["children"][0]["children"][0]["type"] == "column" assert wrapped["children"][0]["children"][0]["children"][0]["type"] == "button" # something already well-formed is passed through untouched sec = builder.section() assert builder.wrap(sec) is sec def test_a_grid_divides_evenly_unless_told_otherwise(): three = builder.grid([[builder.text("a")], [builder.text("b")], [builder.text("c")]]) assert [c["props"]["width_default"] for c in three["children"]] == ["1-3"] * 3 split = builder.grid([[builder.text("a")], [builder.text("b")]], widths=["2-3", "1-3"]) assert [c["props"]["width_default"] for c in split["children"]] == ["2-3", "1-3"] def test_moving_a_node_stays_within_its_siblings(): col = builder.column(children=[builder.text("a"), builder.text("b"), builder.text("c")]) builder.move(col, 0, 2) assert [c["props"]["content"] for c in col["children"]] == ["b", "c", "a"] builder.move(col, 2, 99) # clamped, not an error assert [c["props"]["content"] for c in col["children"]] == ["b", "c", "a"] try: builder.move(col, 7, 0) except ValueError as e: assert "index" in str(e) else: raise AssertionError("moving a child that is not there must be refused") def test_every_catalogued_preset_loads_and_is_a_section(): """A preset listed but missing is worse than one never offered — the catalogue is what a builder reads before it commits to a plan.""" cat = builder.catalogue() assert cat["sections"], "no presets were found at all" for entry in cat["sections"]: node = builder.preset(entry["name"]) assert node.get("type") == "section", f"{entry['name']} is not a section" assert node.get("children"), f"{entry['name']} is empty" def test_presets_obey_the_contract_they_demonstrate(): for entry in builder.catalogue()["sections"]: node = builder.preset(entry["name"]) stack = [node] while stack: parent = stack.pop() for child in parent.get("children") or []: builder.check(parent, child) # raises if the nesting is wrong stack.append(child) def test_a_preset_is_fresh_every_time(): """Callers mutate what they get. Handing out one shared dict would let the second page inherit the first page's edits.""" first = builder.preset(builder.catalogue()["sections"][0]["name"]) first["props"]["name"] = "changed" second = builder.preset(builder.catalogue()["sections"][0]["name"]) assert second["props"].get("name") != "changed" def test_unknown_elements_and_bad_props_say_what_is_wrong(): try: builder.element("carousel") except ValueError as e: assert "carousel" in str(e) and "headline" in str(e) else: raise AssertionError("an unknown element must be named in the error") try: builder.element("headline", nonsense=1) except ValueError as e: assert "headline" in str(e) else: raise AssertionError("an impossible prop must be refused") def test_a_prop_key_is_only_ever_recorded_when_it_was_seen(): """The line this whole module draws. The docs name 39 elements and describe their settings in words; the real keys live in element.php inside the theme, which nothing here can read. So a documented element stays unconfirmed until a layout proves its slug, and a wrong key is worse than a missing one — it renders without complaint and the setting silently does nothing.""" import vocabulary for slug, entry in vocabulary.documented().items(): assert not entry.get("props"), f"{slug} was never seen but claims prop keys" for slug in vocabulary.confirmed(): assert vocabulary.props(slug) or slug in vocabulary.STRUCTURAL, \ f"{slug} is marked confirmed with nothing observed" def test_learning_a_layout_adds_only_what_that_layout_contained(): import vocabulary layout = {"type": "layout", "children": [ {"type": "section", "props": {}, "children": [ {"type": "row", "props": {}, "children": [ {"type": "column", "props": {}, "children": [ {"type": "invented_element", "props": {"made_up_key": 1}}]}]}]}]} got = vocabulary.learn(layout, save=False) assert "invented_element" in got["new_elements"] assert "invented_element.made_up_key" in got["new_props"] # and nothing was invented on its behalf assert not [p for p in got["new_props"] if p.startswith("invented_element.") and p != "invented_element.made_up_key"] def test_the_documented_catalogue_matches_what_yootheme_publishes(): """39 elements across three pages, checked against the live docs on 2026-07-28: 16 basic + 16 multiple-items + 7 system. If a future import disagrees, the scrape lost something.""" import json import vocabulary data = json.loads(vocabulary.FILE.read_text()) pages = [e.get("documented_in") for e in data["elements"].values() if e.get("documented_in")] assert pages.count("basic-elements") == 16, pages.count("basic-elements") assert pages.count("multiple-items-elements") == 16 assert pages.count("system-elements") == 7 if __name__ == "__main__": for name, fn in sorted(globals().items()): if name.startswith("test_"): fn() print(f"ok {name}") print("builder self-check passed")