SignalMesh / editor /test_builder.py
KyklosIO's picture
Claude Opus 5
feat(vocabulary): record the element names that are proven, name none that are not
113e711
Raw History Blame Contribute Delete
7.4 kB
"""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")