Rebrand sexp → sx across web platform (173 files)
All checks were successful
Build and Deploy / build-and-deploy (push) Successful in 11m37s
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>
This commit is contained in:
201
shared/sx/tests/test_jinja_bridge.py
Normal file
201
shared/sx/tests/test_jinja_bridge.py
Normal file
@@ -0,0 +1,201 @@
|
||||
"""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 "<script>" 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 ~badge (&key label)
|
||||
(span :class "badge" label))
|
||||
''')
|
||||
html = sx('(~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 ~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('''
|
||||
(~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('''
|
||||
(~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('''
|
||||
(~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('''
|
||||
(~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(
|
||||
'(~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>"
|
||||
Reference in New Issue
Block a user