Files
rose-ash/shared/sx/tests/test_jinja_bridge.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

202 lines
6.9 KiB
Python

"""Tests for the Jinja ↔ s-expression bridge."""
import asyncio
from shared.sx.jinja_bridge import (
sx,
sx_async,
register_components,
get_component_env,
_COMPONENT_ENV,
)
# ---------------------------------------------------------------------------
# Helpers
# ---------------------------------------------------------------------------
def run(coro):
return asyncio.run(coro)
def setup_function():
"""Clear component env before each test."""
_COMPONENT_ENV.clear()
# ---------------------------------------------------------------------------
# sx() — synchronous rendering
# ---------------------------------------------------------------------------
class TestSx:
def test_simple_html(self):
assert sx('(div "Hello")') == "<div>Hello</div>"
def test_with_kwargs(self):
html = sx('(p name)', name="Alice")
assert html == "<p>Alice</p>"
def test_multiple_kwargs(self):
html = sx('(a :href url title)', url="/about", title="About")
assert html == '<a href="/about">About</a>'
def test_escaping(self):
html = sx('(p text)', text="<script>alert(1)</script>")
assert "&lt;script&gt;" in html
assert "<script>" not in html
def test_nested(self):
html = sx('(div :class "card" (h1 title))', title="Hi")
assert html == '<div class="card"><h1>Hi</h1></div>'
# ---------------------------------------------------------------------------
# register_components() + sx()
# ---------------------------------------------------------------------------
class TestComponents:
def test_register_and_use(self):
register_components('''
(defcomp ~shared:misc/badge (&key label)
(span :class "badge" label))
''')
html = sx('(~shared:misc/badge :label "New")')
assert html == '<span class="badge">New</span>'
def test_multiple_components(self):
register_components('''
(defcomp ~tag (&key text)
(span :class "tag" text))
(defcomp ~pill (&key text)
(span :class "pill" text))
''')
assert '<span class="tag">A</span>' == sx('(~tag :text "A")')
assert '<span class="pill">B</span>' == sx('(~pill :text "B")')
def test_component_with_children(self):
register_components('''
(defcomp ~box (&key title &rest children)
(div :class "box" (h2 title) children))
''')
html = sx('(~box :title "Box" (p "Content"))')
assert '<div class="box">' in html
assert "<h2>Box</h2>" in html
assert "<p>Content</p>" in html
def test_component_with_kwargs_override(self):
"""Kwargs passed to sx() are available alongside components."""
register_components('''
(defcomp ~greeting (&key name)
(p (str "Hello " name)))
''')
html = sx('(~greeting :name user)', user="Bob")
assert html == "<p>Hello Bob</p>"
def test_component_env_persists(self):
"""Components registered once are available in subsequent calls."""
register_components('(defcomp ~x (&key v) (b v))')
assert sx('(~x :v "1")') == "<b>1</b>"
assert sx('(~x :v "2")') == "<b>2</b>"
def test_get_component_env(self):
register_components('(defcomp ~foo (&key x) (span x))')
env = get_component_env()
assert "~foo" in env
# ---------------------------------------------------------------------------
# Link card example — the first migration target
# ---------------------------------------------------------------------------
class TestLinkCard:
def setup_method(self):
_COMPONENT_ENV.clear()
register_components('''
(defcomp ~shared:fragments/link-card (&key link title image icon brand)
(a :href link
:class "block rounded border border-stone-200 bg-white hover:bg-stone-50 transition-colors no-underline"
(div :class "flex flex-row items-start gap-3 p-3"
(if image
(img :src image :alt "" :class "flex-shrink-0 w-16 h-16 rounded object-cover")
(div :class "flex-shrink-0 w-16 h-16 rounded bg-stone-100 flex items-center justify-center text-stone-400"
(i :class icon)))
(div :class "flex-1 min-w-0"
(div :class "font-medium text-stone-900 text-sm clamp-2" title)
(when brand
(div :class "text-xs text-stone-500 mt-0.5" brand))))))
''')
def test_with_image(self):
html = sx('''
(~shared:fragments/link-card
:link "/products/apple/"
:title "Apple"
:image "/img/apple.jpg"
:icon "fas fa-shopping-bag")
''')
assert 'href="/products/apple/"' in html
assert '<img src="/img/apple.jpg"' in html
assert "Apple" in html
def test_without_image(self):
html = sx('''
(~shared:fragments/link-card
:link "/posts/hello/"
:title "Hello World"
:icon "fas fa-file-alt")
''')
assert 'href="/posts/hello/"' in html
assert "<img" not in html
assert "fas fa-file-alt" in html
assert "Hello World" in html
def test_with_brand(self):
html = sx('''
(~shared:fragments/link-card
:link "/p/x/"
:title "Widget"
:image "/img/w.jpg"
:brand "Acme Corp")
''')
assert "Acme Corp" in html
def test_without_brand(self):
html = sx('''
(~shared:fragments/link-card
:link "/p/x/"
:title "Widget"
:image "/img/w.jpg")
''')
# brand div should not appear
assert "mt-0.5" not in html
def test_kwargs_from_python(self):
"""Pass data from Python (like a route handler would)."""
html = sx(
'(~shared:fragments/link-card :link link :title title :image image :icon "fas fa-box")',
link="/products/banana/",
title="Banana",
image="/img/banana.jpg",
)
assert 'href="/products/banana/"' in html
assert "Banana" in html
# ---------------------------------------------------------------------------
# sx_async() — async rendering (no real I/O, just passthrough)
# ---------------------------------------------------------------------------
class TestSxAsync:
def test_simple(self):
html = run(sx_async('(div "Async")'))
assert html == "<div>Async</div>"
def test_with_kwargs(self):
html = run(sx_async('(p name)', name="Alice"))
assert html == "<p>Alice</p>"
def test_with_component(self):
register_components('(defcomp ~x (&key v) (b v))')
html = run(sx_async('(~x :v "OK")'))
assert html == "<b>OK</b>"