Wire sexp.js into page template with auto-init and HTMX integration

- Load sexp.js in ~app-layout before body.js
- Auto-process <script type="text/sexp"> tags on DOMContentLoaded
- Re-process after htmx:afterSwap for dynamic content
- Sexp.mount(target, expr, env) for rendering into DOM elements
- Sexp.processScripts() picks up data-components and data-mount tags
- client_components_tag() Python helper serializes Component objects
  back to sexp source for client-side consumption
- 37 parity tests all passing

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-02-28 23:36:49 +00:00
parent 2df1014ee3
commit 39e013a75e
4 changed files with 132 additions and 0 deletions

View File

@@ -133,6 +133,36 @@ class TestComponents:
assert html == '<div><span>hi</span></div>'
class TestClientComponentsTag:
"""client_components_tag() generates valid sexp for JS consumption."""
def test_emits_script_tag(self):
from shared.sexp.jinja_bridge import client_components_tag, register_components, _COMPONENT_ENV
# Register a test component
register_components('(defcomp ~test-cct (&key label) (span label))')
try:
tag = client_components_tag("test-cct")
assert tag.startswith('<script type="text/sexp" data-components>')
assert tag.endswith('</script>')
assert "defcomp ~test-cct" in tag
finally:
_COMPONENT_ENV.pop("~test-cct", None)
def test_roundtrip_through_js(self):
"""Component emitted by client_components_tag renders identically in JS."""
from shared.sexp.jinja_bridge import client_components_tag, register_components, _COMPONENT_ENV
register_components('(defcomp ~test-rt (&key title) (div :class "rt" title))')
try:
tag = client_components_tag("test-rt")
# Extract the sexp source from the script tag
sexp_source = tag.replace('<script type="text/sexp" data-components>', '').replace('</script>', '')
js_html = _js_render('(~test-rt :title "hello")', sexp_source)
py_html = py_render(parse('(~test-rt :title "hello")'), _COMPONENT_ENV)
assert js_html == py_html
finally:
_COMPONENT_ENV.pop("~test-rt", None)
class TestPythonParity:
"""JS string renderer matches Python renderer output."""