mirror of
https://github.com/ChuckBuilds/LEDMatrix.git
synced 2026-08-23 03:18:15 +00:00
fix(composer): an element the template cannot draw broke generation
Reproduced from the review comment. A `group` element carrying minWidth
generated:
if width >= 64: # breakpoint: 64px+ displays only
# <nothing>
manager.py.j2 wraps each element in the breakpoint and blink blocks, but the
body comes from the per-type branches -- and a type with no branch contributes
nothing, so the wrapper opens a block with no statements. ast.parse then fails
and the caller is told only "Generated code has a syntax error: expected an
indented block ... line 49", naming a line of generated source they never see.
Two defences:
- _preprocess_elements drops types the template has no branch for, alongside
the existing `section` skip. This is the root cause: those elements should
never have reached the template.
- The branch chain ends in `{% else %}pass`, so a type added to the canvas
before its drawing branch exists degrades to a no-op rather than a plugin
that will not parse.
The review also cited dynamic_text with binding_source != 'config'. That one
does not reproduce -- the branch emits a draw_text regardless -- which is why
an earlier attempt to reproduce this found nothing.
_RENDERABLE_ELEMENT_TYPES has to stay in step with the template: a type listed
with no branch emits an empty block again, and a branch missing from the list
is silently dropped from every generated plugin. A test asserts the two sets
are equal rather than trusting them to be maintained together.
Tests: 12 new, covering group/unknown/section against breakpoint, blink and
both nested, plus the set-equality and fallback checks. 7 fail with both
defences reverted. 172 composer tests pass; full suite 3862 passed, the one
failure being test_install_lowmem (pre-existing, awaiting #492).
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01STMbQE4YctTacQXfbYqKuW
This commit is contained in:
co-authored by
Claude Opus 5
parent
d42593e7ce
commit
732c7d1a30
@@ -0,0 +1,80 @@
|
||||
"""An element the template cannot draw must not produce an empty `if` block.
|
||||
|
||||
manager.py.j2 wraps each element in `if width >= N:` (breakpoint) and/or
|
||||
`if int(time.time() * 2) % 2:` (blink), and the body comes from the per-type
|
||||
branches. A type with no branch contributed nothing, so the wrapper opened a
|
||||
block with no statements in it. ast.parse in _generate_plugin_files then
|
||||
failed and the caller was told only:
|
||||
|
||||
Generated code has a syntax error: expected an indented block after
|
||||
'if' statement on line 49
|
||||
|
||||
which names a line of generated source the user never sees. Confirmed against
|
||||
the code before the fix with a `group` element carrying minWidth.
|
||||
|
||||
Two defences, both covered here: _preprocess_elements drops types the template
|
||||
has no branch for, and the template emits a `pass` fallback so a type added to
|
||||
the canvas before its branch exists degrades to a no-op instead of a broken
|
||||
plugin.
|
||||
"""
|
||||
import re
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
import pytest
|
||||
|
||||
sys.path.insert(0, str(Path(__file__).resolve().parent.parent))
|
||||
|
||||
from web_interface.blueprints import composer as C # noqa: E402
|
||||
|
||||
TEMPLATE = (Path(__file__).resolve().parent.parent
|
||||
/ "web_interface/templates/v3/composer/manager.py.j2")
|
||||
|
||||
BASE_META = {"id": "test-plugin", "name": "Clock", "author": "a",
|
||||
"version": "1.0.0", "description": "d"}
|
||||
|
||||
|
||||
def generate(element):
|
||||
return C._generate_plugin_files({
|
||||
"metadata": BASE_META,
|
||||
"elements": [element],
|
||||
"dataModel": {"configVars": []},
|
||||
})
|
||||
|
||||
|
||||
@pytest.mark.parametrize("wrapper", [
|
||||
{"minWidth": 64}, # breakpoint block
|
||||
{"blink": True}, # blink block
|
||||
{"minWidth": 64, "blink": True}, # both, nested
|
||||
])
|
||||
@pytest.mark.parametrize("etype", ["group", "widget_9000", "section"])
|
||||
def test_undrawable_element_does_not_break_generation(etype, wrapper):
|
||||
element = {"type": etype, "x": 0, "y": 0, "color": "#ffffff", **wrapper}
|
||||
files = generate(element) # must not raise ComposerInputError
|
||||
assert "manager.py" in files
|
||||
|
||||
|
||||
def test_drawable_element_still_renders_inside_a_breakpoint():
|
||||
files = generate({"type": "text", "text": "hi", "x": 0, "y": 0,
|
||||
"minWidth": 64, "color": "#ffffff"})
|
||||
src = files["manager.py"]
|
||||
assert "if width >= 64:" in src
|
||||
assert "draw_text" in src
|
||||
|
||||
|
||||
def test_renderable_types_match_the_template_branches():
|
||||
"""The constant and the template must agree.
|
||||
|
||||
A type listed in the constant with no branch emits an empty block (the bug
|
||||
above); a type with a branch but missing from the constant is silently
|
||||
dropped from every generated plugin. Neither is visible without this check.
|
||||
"""
|
||||
branches = set(re.findall(r"el\.type == '([a-z_]+)'", TEMPLATE.read_text()))
|
||||
assert branches == set(C._RENDERABLE_ELEMENT_TYPES)
|
||||
|
||||
|
||||
def test_template_closes_the_branch_chain_with_a_fallback():
|
||||
"""Belt and braces: even if the constant drifts, no empty block escapes."""
|
||||
text = TEMPLATE.read_text()
|
||||
assert "{% else %}" in text
|
||||
assert "pass # element type" in text
|
||||
Reference in New Issue
Block a user