Files
rose-ash/shared/tests/test_jinja_bridge_render.py
giles b0920a1121 Rename all 1,169 components to path-based names with namespace support
Component names now reflect filesystem location using / as path separator
and : as namespace separator for shared components:
  ~sx-header → ~layouts/header
  ~layout-app-body → ~shared:layout/app-body
  ~blog-admin-dashboard → ~admin/dashboard

209 files, 4,941 replacements across all services.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-12 22:00:12 +00:00

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 ~shared:misc/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 "&lt;script&gt;" 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>"