mirror of
https://github.com/ChuckBuilds/LEDMatrix.git
synced 2026-08-22 19:08:17 +00:00
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
81 lines
3.0 KiB
Python
81 lines
3.0 KiB
Python
"""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
|