"""Tests for the transpiled sx_ref.py evaluator. Runs the same test cases as test_evaluator.py and test_html.py but against the bootstrap-compiled evaluator to verify correctness. """ import pytest from shared.sx.parser import parse from shared.sx.types import Symbol, Keyword, NIL, Lambda, Component, Macro, PageDef from shared.sx.ref import sx_ref # --------------------------------------------------------------------------- # Helpers # --------------------------------------------------------------------------- def ev(text, env=None): """Parse and evaluate a single expression via sx_ref.""" return sx_ref.evaluate(parse(text), env) def render(text, env=None): """Parse and render via sx_ref.""" return sx_ref.render(parse(text), env) # --------------------------------------------------------------------------- # Literals and lookups # --------------------------------------------------------------------------- class TestLiterals: def test_int(self): assert ev("42") == 42 def test_string(self): assert ev('"hello"') == "hello" def test_true(self): assert ev("true") is True def test_nil(self): assert ev("nil") is NIL def test_symbol_lookup(self): assert ev("x", {"x": 10}) == 10 def test_undefined_symbol(self): with pytest.raises(sx_ref.EvalError, match="Undefined symbol"): ev("xyz") def test_keyword_evaluates_to_name(self): assert ev(":foo") == "foo" # --------------------------------------------------------------------------- # Arithmetic # --------------------------------------------------------------------------- class TestArithmetic: def test_add(self): assert ev("(+ 1 2 3)") == 6 def test_sub(self): assert ev("(- 10 3)") == 7 def test_mul(self): assert ev("(* 2 3 4)") == 24 def test_div(self): assert ev("(/ 10 4)") == 2.5 def test_mod(self): assert ev("(mod 7 3)") == 1 # --------------------------------------------------------------------------- # Special forms # --------------------------------------------------------------------------- class TestSpecialForms: def test_if_true(self): assert ev("(if true 1 2)") == 1 def test_if_false(self): assert ev("(if false 1 2)") == 2 def test_if_no_else(self): assert ev("(if false 1)") is NIL def test_when_true(self): assert ev("(when true 42)") == 42 def test_when_false(self): assert ev("(when false 42)") is NIL def test_and_short_circuit(self): assert ev("(and true true 3)") == 3 assert ev("(and true false 3)") is False def test_or_short_circuit(self): assert ev("(or false false 3)") == 3 assert ev("(or false 2 3)") == 2 def test_let_scheme_style(self): assert ev("(let ((x 10) (y 20)) (+ x y))") == 30 def test_let_clojure_style(self): assert ev("(let (x 10 y 20) (+ x y))") == 30 def test_let_sequential(self): assert ev("(let ((x 1) (y (+ x 1))) y)") == 2 def test_begin(self): assert ev("(begin 1 2 3)") == 3 def test_quote(self): result = ev("(quote (a b c))") assert result == [Symbol("a"), Symbol("b"), Symbol("c")] def test_cond_clojure(self): assert ev("(cond false 1 true 2 :else 3)") == 2 def test_cond_else(self): assert ev("(cond false 1 false 2 :else 99)") == 99 def test_case(self): assert ev('(case 2 1 "one" 2 "two" :else "other")') == "two" def test_thread_first(self): assert ev("(-> 5 (+ 3) (* 2))") == 16 def test_define(self): env = {} ev("(define x 42)", env) assert env["x"] == 42 # --------------------------------------------------------------------------- # Lambda # --------------------------------------------------------------------------- class TestLambda: def test_create_and_call(self): assert ev("((fn (x) (* x x)) 5)") == 25 def test_closure(self): result = ev("(let ((a 10)) ((fn (x) (+ x a)) 5))") assert result == 15 def test_higher_order(self): result = ev("(let ((double (fn (x) (* x 2)))) (double 7))") assert result == 14 # --------------------------------------------------------------------------- # Collections # --------------------------------------------------------------------------- class TestCollections: def test_list_constructor(self): assert ev("(list 1 2 3)") == [1, 2, 3] def test_dict_constructor(self): assert ev("(dict :a 1 :b 2)") == {"a": 1, "b": 2} def test_get_dict(self): assert ev('(get {:a 1 :b 2} "a")') == 1 def test_get_list(self): assert ev("(get (list 10 20 30) 1)") == 20 def test_first_last_rest(self): assert ev("(first (list 1 2 3))") == 1 assert ev("(last (list 1 2 3))") == 3 assert ev("(rest (list 1 2 3))") == [2, 3] def test_len(self): assert ev("(len (list 1 2 3))") == 3 def test_concat(self): assert ev("(concat (list 1 2) (list 3 4))") == [1, 2, 3, 4] def test_cons(self): assert ev("(cons 0 (list 1 2))") == [0, 1, 2] def test_merge(self): assert ev("(merge {:a 1} {:b 2} {:a 3})") == {"a": 3, "b": 2} def test_empty(self): assert ev("(empty? (list))") is True assert ev("(empty? (list 1))") is False assert ev("(empty? nil)") is True # --------------------------------------------------------------------------- # Higher-order forms # --------------------------------------------------------------------------- class TestHigherOrder: def test_map(self): assert ev("(map (fn (x) (* x x)) (list 1 2 3 4))") == [1, 4, 9, 16] def test_filter(self): assert ev("(filter (fn (x) (> x 2)) (list 1 2 3 4))") == [3, 4] def test_reduce(self): assert ev("(reduce (fn (acc x) (+ acc x)) 0 (list 1 2 3))") == 6 def test_some(self): assert ev("(some (fn (x) (> x 3)) (list 1 2 3 4 5))") is True def test_for_each(self): result = ev("(for-each (fn (x) x) (list 1 2 3))") assert result is NIL # --------------------------------------------------------------------------- # String ops # --------------------------------------------------------------------------- class TestStrings: def test_str(self): assert ev('(str "hello" " " "world")') == "hello world" def test_upper_lower(self): assert ev('(upper "hello")') == "HELLO" assert ev('(lower "HELLO")') == "hello" def test_split_join(self): assert ev('(split "a,b,c" ",")') == ["a", "b", "c"] assert ev('(join "-" (list "a" "b"))') == "a-b" def test_starts_ends(self): assert ev('(starts-with? "hello" "hel")') is True assert ev('(ends-with? "hello" "llo")') is True # --------------------------------------------------------------------------- # Components # --------------------------------------------------------------------------- class TestComponents: def test_defcomp_and_render(self): env = {} ev("(defcomp ~box (&key title) (div :class \"box\" title))", env) result = render("(~box :title \"hi\")", env) assert result == '
hi
' def test_defcomp_with_children(self): env = {} ev("(defcomp ~wrap (&rest children) (div children))", env) result = render('(~wrap (span "a") (span "b"))', env) assert result == '
ab
' # --------------------------------------------------------------------------- # HTML rendering # --------------------------------------------------------------------------- class TestHTMLRendering: def test_basic_element(self): assert render("(div)") == "
" def test_text_content(self): assert render('(p "hello")') == "

hello

" def test_attributes(self): result = render('(a :href "/about" "link")') assert result == 'link' def test_void_element(self): result = render('(br)') assert result == "
" def test_nested(self): result = render('(div (p "a") (p "b"))') assert result == "

a

b

" def test_fragment(self): result = render('(<> (span "a") (span "b"))') assert result == "ab" def test_conditional_rendering(self): result = render('(if true (span "yes") (span "no"))') assert result == "yes" def test_map_rendering(self): result = render('(map (fn (x) (li x)) (list "a" "b"))') assert result == "
  • a
  • b
  • " def test_html_escaping(self): result = render('(span "bold")') assert result == "<b>bold</b>" # --------------------------------------------------------------------------- # Aser (SX wire format) # --------------------------------------------------------------------------- class TestAser: def test_render_to_sx_basic(self): expr = parse("(div :class \"foo\" \"hello\")") result = sx_ref.render_to_sx(expr, {}) assert result == '(div :class "foo" "hello")' def test_component_not_expanded(self): expr = parse('(~card :title "hi")') result = sx_ref.render_to_sx(expr, {}) assert result == '(~card :title "hi")' def test_fragment(self): expr = parse('(<> "a" "b")') result = sx_ref.render_to_sx(expr, {}) assert result == '(<> "a" "b")' def test_let_evaluates(self): expr = parse('(let ((x 5)) x)') result = sx_ref.render_to_sx(expr, {}) assert result == "5" def test_if_evaluates(self): expr = parse('(if true "yes" "no")') result = sx_ref.render_to_sx(expr, {}) assert result == 'yes' # strings pass through unserialized # --------------------------------------------------------------------------- # Macros # --------------------------------------------------------------------------- class TestDefcomp: def test_defcomp_basic(self): """defcomp should parse &key params without trying to eval &key as a symbol.""" env = {} ev('(defcomp ~card (&key title) (div title))', env) assert isinstance(env.get("~card"), Component) def test_defcomp_render(self): env = {} ev('(defcomp ~card (&key title) (div title))', env) result = render('(~card :title "hello")', env) assert result == '
    hello
    ' def test_defcomp_with_children(self): env = {} ev('(defcomp ~wrap (&key title &rest children) (div title children))', env) comp = env["~wrap"] assert comp.has_children is True assert "title" in comp.params def test_defcomp_multiple_params(self): env = {} ev('(defcomp ~box (&key a b c) (div a b c))', env) result = render('(~box :a "1" :b "2" :c "3")', env) assert result == '
    123
    ' class TestDefhandler: def test_defhandler_basic(self): """defhandler should parse &key params and create a HandlerDef.""" from shared.sx.types import HandlerDef env = {} ev('(defhandler link-card (&key slug keys) (div slug))', env) hdef = env.get("handler:link-card") assert isinstance(hdef, HandlerDef) assert hdef.name == "link-card" assert hdef.params == ["slug", "keys"] def test_defquery_basic(self): from shared.sx.types import QueryDef env = {} ev('(defquery get-post (&key slug) "Fetch a post" (list slug))', env) qdef = env.get("query:get-post") assert isinstance(qdef, QueryDef) assert qdef.params == ["slug"] assert qdef.doc == "Fetch a post" def test_defaction_basic(self): from shared.sx.types import ActionDef env = {} ev('(defaction save-post (&key title body) (list title body))', env) adef = env.get("action:save-post") assert isinstance(adef, ActionDef) assert adef.params == ["title", "body"] class TestHOWithNativeCallable: def test_map_with_native_fn(self): """map should work with native callables (primitives), not just Lambda.""" result = ev('(map str (list 1 2 3))') assert result == ["1", "2", "3"] def test_filter_with_native_fn(self): result = ev('(filter number? (list 1 "a" 2 "b" 3))') assert result == [1, 2, 3] def test_map_with_env_fn(self): """map should work with Python functions registered in env.""" env = {"double": lambda x: x * 2} result = ev('(map double (list 1 2 3))', env) assert result == [2, 4, 6] def test_ho_non_callable_fails_fast(self): """Passing a non-callable to map should error clearly.""" import pytest with pytest.raises(sx_ref.EvalError, match="Not callable"): ev('(map 42 (list 1 2 3))') class TestMacros: def test_defmacro_and_expand(self): env = {} ev("(defmacro unless (test body) (list (quote if) (list (quote not) test) body))", env) result = ev('(unless false "ran")', env) assert result == "ran"