All checks were successful
Build and Deploy / build-and-deploy (push) Successful in 11m37s
Rename all sexp directories, files, identifiers, and references to sx. artdag/ excluded (separate media processing DSL). Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
167 lines
5.9 KiB
Python
167 lines
5.9 KiB
Python
"""Tests for the render() function and component loading in jinja_bridge.
|
|
|
|
These test functionality added in recent commits (render() API,
|
|
load_sx_dir, snake→kebab conversion) that isn't covered by the existing
|
|
shared/sx/tests/test_jinja_bridge.py.
|
|
"""
|
|
from __future__ import annotations
|
|
|
|
import os
|
|
import tempfile
|
|
|
|
import pytest
|
|
|
|
from shared.sx.jinja_bridge import (
|
|
render,
|
|
register_components,
|
|
load_sx_dir,
|
|
_COMPONENT_ENV,
|
|
)
|
|
|
|
|
|
@pytest.fixture(autouse=True)
|
|
def _clean_env():
|
|
"""Clear component env before each test."""
|
|
_COMPONENT_ENV.clear()
|
|
yield
|
|
_COMPONENT_ENV.clear()
|
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# render() — call component by name with Python kwargs
|
|
# ---------------------------------------------------------------------------
|
|
|
|
class TestRender:
|
|
def test_basic_render(self):
|
|
register_components('(defcomp ~badge (&key label) (span :class "badge" label))')
|
|
html = render("badge", label="New")
|
|
assert html == '<span class="badge">New</span>'
|
|
|
|
def test_tilde_prefix_optional(self):
|
|
register_components('(defcomp ~pill (&key text) (em text))')
|
|
# Both forms should work
|
|
assert render("pill", text="Hi") == render("~pill", text="Hi")
|
|
|
|
def test_snake_to_kebab_conversion(self):
|
|
"""Python snake_case kwargs should map to sx kebab-case params."""
|
|
register_components('''
|
|
(defcomp ~card (&key nav-html link-href)
|
|
(div :class "card" (a :href link-href nav-html)))
|
|
''')
|
|
html = render("card", nav_html="Nav", link_href="/about")
|
|
assert 'href="/about"' in html
|
|
assert "Nav" in html
|
|
|
|
def test_multiple_kwargs(self):
|
|
register_components('''
|
|
(defcomp ~item (&key title price image-url)
|
|
(div (h3 title) (span price) (img :src image-url)))
|
|
''')
|
|
html = render("item", title="Widget", price="£10", image_url="/img/w.jpg")
|
|
assert "Widget" in html
|
|
assert "£10" in html
|
|
assert 'src="/img/w.jpg"' in html
|
|
|
|
def test_unknown_component_raises(self):
|
|
with pytest.raises(ValueError, match="Unknown component"):
|
|
render("nonexistent", label="x")
|
|
|
|
def test_empty_kwargs(self):
|
|
register_components('(defcomp ~empty () (hr))')
|
|
html = render("empty")
|
|
assert html == "<hr>"
|
|
|
|
def test_html_escaping_in_values(self):
|
|
register_components('(defcomp ~safe (&key text) (p text))')
|
|
html = render("safe", text='<script>alert("xss")</script>')
|
|
assert "<script>" not in html
|
|
assert "<script>" in html
|
|
|
|
def test_boolean_false_value(self):
|
|
register_components('''
|
|
(defcomp ~toggle (&key active)
|
|
(when active (span "ON")))
|
|
''')
|
|
html = render("toggle", active=False)
|
|
assert "ON" not in html
|
|
|
|
def test_boolean_true_value(self):
|
|
register_components('''
|
|
(defcomp ~toggle (&key active)
|
|
(when active (span "ON")))
|
|
''')
|
|
html = render("toggle", active=True)
|
|
assert "ON" in html
|
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# load_sx_dir
|
|
# ---------------------------------------------------------------------------
|
|
|
|
class TestLoadSxDir:
|
|
def test_loads_sx_files(self):
|
|
with tempfile.TemporaryDirectory() as tmpdir:
|
|
# Write a .sx file
|
|
with open(os.path.join(tmpdir, "components.sx"), "w") as f:
|
|
f.write('(defcomp ~test-comp (&key msg) (div msg))')
|
|
|
|
load_sx_dir(tmpdir)
|
|
html = render("test-comp", msg="loaded!")
|
|
assert html == "<div>loaded!</div>"
|
|
|
|
def test_loads_sx_files_alt(self):
|
|
with tempfile.TemporaryDirectory() as tmpdir:
|
|
with open(os.path.join(tmpdir, "nav.sx"), "w") as f:
|
|
f.write('(defcomp ~nav-item (&key href label) (a :href href label))')
|
|
|
|
load_sx_dir(tmpdir)
|
|
html = render("nav-item", href="/about", label="About")
|
|
assert 'href="/about"' in html
|
|
|
|
def test_loads_multiple_files(self):
|
|
with tempfile.TemporaryDirectory() as tmpdir:
|
|
with open(os.path.join(tmpdir, "a.sx"), "w") as f:
|
|
f.write('(defcomp ~comp-a (&key x) (b x))')
|
|
with open(os.path.join(tmpdir, "b.sx"), "w") as f:
|
|
f.write('(defcomp ~comp-b (&key y) (i y))')
|
|
|
|
load_sx_dir(tmpdir)
|
|
assert render("comp-a", x="A") == "<b>A</b>"
|
|
assert render("comp-b", y="B") == "<i>B</i>"
|
|
|
|
def test_empty_directory(self):
|
|
with tempfile.TemporaryDirectory() as tmpdir:
|
|
load_sx_dir(tmpdir) # should not raise
|
|
|
|
def test_ignores_non_sx_files(self):
|
|
with tempfile.TemporaryDirectory() as tmpdir:
|
|
with open(os.path.join(tmpdir, "readme.txt"), "w") as f:
|
|
f.write("not a sx file")
|
|
with open(os.path.join(tmpdir, "comp.sx"), "w") as f:
|
|
f.write('(defcomp ~real (&key v) (span v))')
|
|
|
|
load_sx_dir(tmpdir)
|
|
assert "~real" in _COMPONENT_ENV
|
|
# txt file should not have been loaded
|
|
assert len([k for k in _COMPONENT_ENV if k.startswith("~")]) == 1
|
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# register_components — multiple definitions in one source
|
|
# ---------------------------------------------------------------------------
|
|
|
|
class TestRegisterComponents:
|
|
def test_multiple_in_one_source(self):
|
|
register_components('''
|
|
(defcomp ~a (&key x) (b x))
|
|
(defcomp ~b (&key y) (i y))
|
|
''')
|
|
assert "~a" in _COMPONENT_ENV
|
|
assert "~b" in _COMPONENT_ENV
|
|
|
|
def test_overwrite_existing(self):
|
|
register_components('(defcomp ~ow (&key x) (b x))')
|
|
assert render("ow", x="v1") == "<b>v1</b>"
|
|
register_components('(defcomp ~ow (&key x) (i x))')
|
|
assert render("ow", x="v2") == "<i>v2</i>"
|