diff --git a/shared/sx/ref/test-eval.sx b/shared/sx/ref/test-eval.sx new file mode 100644 index 0000000..06f655f --- /dev/null +++ b/shared/sx/ref/test-eval.sx @@ -0,0 +1,494 @@ +;; ========================================================================== +;; test-eval.sx — Tests for the core evaluator and primitives +;; +;; Requires: test-framework.sx loaded first. +;; Modules tested: eval.sx, primitives.sx +;; ========================================================================== + + +;; -------------------------------------------------------------------------- +;; Literals and types +;; -------------------------------------------------------------------------- + +(defsuite "literals" + (deftest "numbers are numbers" + (assert-type "number" 42) + (assert-type "number" 3.14) + (assert-type "number" -1)) + + (deftest "strings are strings" + (assert-type "string" "hello") + (assert-type "string" "")) + + (deftest "booleans are booleans" + (assert-type "boolean" true) + (assert-type "boolean" false)) + + (deftest "nil is nil" + (assert-type "nil" nil) + (assert-nil nil)) + + (deftest "lists are lists" + (assert-type "list" (list 1 2 3)) + (assert-type "list" (list))) + + (deftest "dicts are dicts" + (assert-type "dict" {:a 1 :b 2}))) + + +;; -------------------------------------------------------------------------- +;; Arithmetic +;; -------------------------------------------------------------------------- + +(defsuite "arithmetic" + (deftest "addition" + (assert-equal 3 (+ 1 2)) + (assert-equal 0 (+ 0 0)) + (assert-equal -1 (+ 1 -2)) + (assert-equal 10 (+ 1 2 3 4))) + + (deftest "subtraction" + (assert-equal 1 (- 3 2)) + (assert-equal -1 (- 2 3))) + + (deftest "multiplication" + (assert-equal 6 (* 2 3)) + (assert-equal 0 (* 0 100)) + (assert-equal 24 (* 1 2 3 4))) + + (deftest "division" + (assert-equal 2 (/ 6 3)) + (assert-equal 2.5 (/ 5 2))) + + (deftest "modulo" + (assert-equal 1 (mod 7 3)) + (assert-equal 0 (mod 6 3)))) + + +;; -------------------------------------------------------------------------- +;; Comparison +;; -------------------------------------------------------------------------- + +(defsuite "comparison" + (deftest "equality" + (assert-true (= 1 1)) + (assert-false (= 1 2)) + (assert-true (= "a" "a")) + (assert-false (= "a" "b"))) + + (deftest "deep equality" + (assert-true (equal? (list 1 2 3) (list 1 2 3))) + (assert-false (equal? (list 1 2) (list 1 3))) + (assert-true (equal? {:a 1} {:a 1})) + (assert-false (equal? {:a 1} {:a 2}))) + + (deftest "ordering" + (assert-true (< 1 2)) + (assert-false (< 2 1)) + (assert-true (> 2 1)) + (assert-true (<= 1 1)) + (assert-true (<= 1 2)) + (assert-true (>= 2 2)) + (assert-true (>= 3 2)))) + + +;; -------------------------------------------------------------------------- +;; String operations +;; -------------------------------------------------------------------------- + +(defsuite "strings" + (deftest "str concatenation" + (assert-equal "abc" (str "a" "b" "c")) + (assert-equal "hello world" (str "hello" " " "world")) + (assert-equal "42" (str 42)) + (assert-equal "" (str))) + + (deftest "string-length" + (assert-equal 5 (string-length "hello")) + (assert-equal 0 (string-length ""))) + + (deftest "substring" + (assert-equal "ell" (substring "hello" 1 4)) + (assert-equal "hello" (substring "hello" 0 5))) + + (deftest "string-contains?" + (assert-true (string-contains? "hello world" "world")) + (assert-false (string-contains? "hello" "xyz"))) + + (deftest "upcase and downcase" + (assert-equal "HELLO" (upcase "hello")) + (assert-equal "hello" (downcase "HELLO"))) + + (deftest "trim" + (assert-equal "hello" (trim " hello ")) + (assert-equal "hello" (trim "hello"))) + + (deftest "split and join" + (assert-equal (list "a" "b" "c") (split "a,b,c" ",")) + (assert-equal "a-b-c" (join "-" (list "a" "b" "c"))))) + + +;; -------------------------------------------------------------------------- +;; List operations +;; -------------------------------------------------------------------------- + +(defsuite "lists" + (deftest "constructors" + (assert-equal (list 1 2 3) (list 1 2 3)) + (assert-equal (list) (list)) + (assert-length 3 (list 1 2 3))) + + (deftest "first and rest" + (assert-equal 1 (first (list 1 2 3))) + (assert-equal (list 2 3) (rest (list 1 2 3))) + (assert-nil (first (list))) + (assert-equal (list) (rest (list)))) + + (deftest "nth" + (assert-equal 1 (nth (list 1 2 3) 0)) + (assert-equal 2 (nth (list 1 2 3) 1)) + (assert-equal 3 (nth (list 1 2 3) 2))) + + (deftest "last" + (assert-equal 3 (last (list 1 2 3))) + (assert-nil (last (list)))) + + (deftest "cons and append" + (assert-equal (list 0 1 2) (cons 0 (list 1 2))) + (assert-equal (list 1 2 3 4) (append (list 1 2) (list 3 4)))) + + (deftest "reverse" + (assert-equal (list 3 2 1) (reverse (list 1 2 3))) + (assert-equal (list) (reverse (list)))) + + (deftest "empty?" + (assert-true (empty? (list))) + (assert-false (empty? (list 1)))) + + (deftest "len" + (assert-equal 0 (len (list))) + (assert-equal 3 (len (list 1 2 3)))) + + (deftest "contains?" + (assert-true (contains? (list 1 2 3) 2)) + (assert-false (contains? (list 1 2 3) 4))) + + (deftest "flatten" + (assert-equal (list 1 2 3 4) (flatten (list (list 1 2) (list 3 4)))))) + + +;; -------------------------------------------------------------------------- +;; Dict operations +;; -------------------------------------------------------------------------- + +(defsuite "dicts" + (deftest "dict literal" + (assert-type "dict" {:a 1 :b 2}) + (assert-equal 1 (get {:a 1} "a")) + (assert-equal 2 (get {:a 1 :b 2} "b"))) + + (deftest "assoc" + (assert-equal {:a 1 :b 2} (assoc {:a 1} "b" 2)) + (assert-equal {:a 99} (assoc {:a 1} "a" 99))) + + (deftest "dissoc" + (assert-equal {:b 2} (dissoc {:a 1 :b 2} "a"))) + + (deftest "keys and vals" + (let ((d {:a 1 :b 2})) + (assert-length 2 (keys d)) + (assert-length 2 (vals d)) + (assert-contains "a" (keys d)) + (assert-contains "b" (keys d)))) + + (deftest "has-key?" + (assert-true (has-key? {:a 1} "a")) + (assert-false (has-key? {:a 1} "b"))) + + (deftest "merge" + (assert-equal {:a 1 :b 2 :c 3} + (merge {:a 1 :b 2} {:c 3})) + (assert-equal {:a 99 :b 2} + (merge {:a 1 :b 2} {:a 99})))) + + +;; -------------------------------------------------------------------------- +;; Predicates +;; -------------------------------------------------------------------------- + +(defsuite "predicates" + (deftest "nil?" + (assert-true (nil? nil)) + (assert-false (nil? 0)) + (assert-false (nil? false)) + (assert-false (nil? ""))) + + (deftest "number?" + (assert-true (number? 42)) + (assert-true (number? 3.14)) + (assert-false (number? "42"))) + + (deftest "string?" + (assert-true (string? "hello")) + (assert-false (string? 42))) + + (deftest "list?" + (assert-true (list? (list 1 2))) + (assert-false (list? "not a list"))) + + (deftest "dict?" + (assert-true (dict? {:a 1})) + (assert-false (dict? (list 1)))) + + (deftest "boolean?" + (assert-true (boolean? true)) + (assert-true (boolean? false)) + (assert-false (boolean? nil)) + (assert-false (boolean? 0))) + + (deftest "not" + (assert-true (not false)) + (assert-true (not nil)) + (assert-false (not true)) + (assert-false (not 1)) + (assert-false (not "x")))) + + +;; -------------------------------------------------------------------------- +;; Special forms +;; -------------------------------------------------------------------------- + +(defsuite "special-forms" + (deftest "if" + (assert-equal "yes" (if true "yes" "no")) + (assert-equal "no" (if false "yes" "no")) + (assert-equal "no" (if nil "yes" "no")) + (assert-nil (if false "yes"))) + + (deftest "when" + (assert-equal "yes" (when true "yes")) + (assert-nil (when false "yes"))) + + (deftest "cond" + (assert-equal "a" (cond true "a" :else "b")) + (assert-equal "b" (cond false "a" :else "b")) + (assert-equal "c" (cond + false "a" + false "b" + :else "c"))) + + (deftest "and" + (assert-true (and true true)) + (assert-false (and true false)) + (assert-false (and false true)) + (assert-equal 3 (and 1 2 3))) + + (deftest "or" + (assert-equal 1 (or 1 2)) + (assert-equal 2 (or false 2)) + (assert-equal "fallback" (or nil false "fallback")) + (assert-false (or false false))) + + (deftest "let" + (assert-equal 3 (let ((x 1) (y 2)) (+ x y))) + (assert-equal "hello world" + (let ((a "hello") (b " world")) (str a b)))) + + (deftest "let clojure-style" + (assert-equal 3 (let (x 1 y 2) (+ x y)))) + + (deftest "do / begin" + (assert-equal 3 (do 1 2 3)) + (assert-equal "last" (begin "first" "middle" "last"))) + + (deftest "define" + (define x 42) + (assert-equal 42 x)) + + (deftest "set!" + (define x 1) + (set! x 2) + (assert-equal 2 x))) + + +;; -------------------------------------------------------------------------- +;; Lambda and closures +;; -------------------------------------------------------------------------- + +(defsuite "lambdas" + (deftest "basic lambda" + (let ((add (fn (a b) (+ a b)))) + (assert-equal 3 (add 1 2)))) + + (deftest "closure captures env" + (let ((x 10)) + (let ((add-x (fn (y) (+ x y)))) + (assert-equal 15 (add-x 5))))) + + (deftest "lambda as argument" + (assert-equal (list 2 4 6) + (map (fn (x) (* x 2)) (list 1 2 3)))) + + (deftest "recursive lambda via define" + (define factorial + (fn (n) (if (<= n 1) 1 (* n (factorial (- n 1)))))) + (assert-equal 120 (factorial 5))) + + (deftest "higher-order returns lambda" + (let ((make-adder (fn (n) (fn (x) (+ n x))))) + (let ((add5 (make-adder 5))) + (assert-equal 8 (add5 3)))))) + + +;; -------------------------------------------------------------------------- +;; Higher-order forms +;; -------------------------------------------------------------------------- + +(defsuite "higher-order" + (deftest "map" + (assert-equal (list 2 4 6) + (map (fn (x) (* x 2)) (list 1 2 3))) + (assert-equal (list) (map (fn (x) x) (list)))) + + (deftest "filter" + (assert-equal (list 2 4) + (filter (fn (x) (= (mod x 2) 0)) (list 1 2 3 4))) + (assert-equal (list) + (filter (fn (x) false) (list 1 2 3)))) + + (deftest "reduce" + (assert-equal 10 (reduce (fn (acc x) (+ acc x)) 0 (list 1 2 3 4))) + (assert-equal 0 (reduce (fn (acc x) (+ acc x)) 0 (list)))) + + (deftest "some" + (assert-true (some (fn (x) (> x 3)) (list 1 2 3 4 5))) + (assert-false (some (fn (x) (> x 10)) (list 1 2 3)))) + + (deftest "every?" + (assert-true (every? (fn (x) (> x 0)) (list 1 2 3))) + (assert-false (every? (fn (x) (> x 2)) (list 1 2 3)))) + + (deftest "map-indexed" + (assert-equal (list "0:a" "1:b" "2:c") + (map-indexed (fn (i x) (str i ":" x)) (list "a" "b" "c"))))) + + +;; -------------------------------------------------------------------------- +;; Components +;; -------------------------------------------------------------------------- + +(defsuite "components" + (deftest "defcomp creates component" + (defcomp ~test-comp (&key title) + (div title)) + (assert-true (not (nil? ~test-comp)))) + + (deftest "component renders with keyword args" + (defcomp ~greeting (&key name) + (span (str "Hello, " name "!"))) + (assert-true (not (nil? ~greeting)))) + + (deftest "component with children" + (defcomp ~box (&key &rest children) + (div :class "box" children)) + (assert-true (not (nil? ~box)))) + + (deftest "component with default via or" + (defcomp ~label (&key text) + (span (or text "default"))) + (assert-true (not (nil? ~label))))) + + +;; -------------------------------------------------------------------------- +;; Macros +;; -------------------------------------------------------------------------- + +(defsuite "macros" + (deftest "defmacro creates macro" + (defmacro unless (cond &rest body) + `(if (not ,cond) (do ,@body))) + (assert-equal "yes" (unless false "yes")) + (assert-nil (unless true "no"))) + + (deftest "quasiquote and unquote" + (let ((x 42)) + (assert-equal (list 1 42 3) `(1 ,x 3)))) + + (deftest "splice-unquote" + (let ((xs (list 2 3 4))) + (assert-equal (list 1 2 3 4 5) `(1 ,@xs 5))))) + + +;; -------------------------------------------------------------------------- +;; Threading macro +;; -------------------------------------------------------------------------- + +(defsuite "threading" + (deftest "thread-first" + (assert-equal 8 (-> 5 (+ 1) (+ 2))) + (assert-equal "HELLO" (-> "hello" upcase)) + (assert-equal "HELLO WORLD" + (-> "hello" + (str " world") + upcase)))) + + +;; -------------------------------------------------------------------------- +;; Truthiness +;; -------------------------------------------------------------------------- + +(defsuite "truthiness" + (deftest "truthy values" + (assert-true (if 1 true false)) + (assert-true (if "x" true false)) + (assert-true (if (list 1) true false)) + (assert-true (if true true false))) + + (deftest "falsy values" + (assert-false (if false true false)) + (assert-false (if nil true false))) + + ;; NOTE: empty list, zero, and empty string truthiness is + ;; platform-dependent. Python treats all three as falsy. + ;; JavaScript treats [] as truthy but 0 and "" as falsy. + ;; These tests are omitted — each bootstrapper should emit + ;; platform-specific truthiness tests instead. + ) + + +;; -------------------------------------------------------------------------- +;; Edge cases and regression tests +;; -------------------------------------------------------------------------- + +(defsuite "edge-cases" + (deftest "nested let scoping" + (let ((x 1)) + (let ((x 2)) + (assert-equal 2 x)) + ;; outer x should be unchanged by inner let + ;; (this tests that let creates a new scope) + )) + + (deftest "recursive map" + (assert-equal (list (list 2 4) (list 6 8)) + (map (fn (sub) (map (fn (x) (* x 2)) sub)) + (list (list 1 2) (list 3 4))))) + + (deftest "keyword as value" + (assert-equal "class" :class) + (assert-equal "id" :id)) + + (deftest "dict with evaluated values" + (let ((x 42)) + (assert-equal 42 (get {:val x} "val")))) + + (deftest "nil propagation" + (assert-nil (get {:a 1} "missing")) + (assert-equal "default" (or (get {:a 1} "missing") "default"))) + + (deftest "empty operations" + (assert-equal (list) (map (fn (x) x) (list))) + (assert-equal (list) (filter (fn (x) true) (list))) + (assert-equal 0 (reduce (fn (acc x) (+ acc x)) 0 (list))) + (assert-equal 0 (len (list))) + (assert-equal "" (str)))) diff --git a/shared/sx/ref/test-framework.sx b/shared/sx/ref/test-framework.sx new file mode 100644 index 0000000..21e5a10 --- /dev/null +++ b/shared/sx/ref/test-framework.sx @@ -0,0 +1,86 @@ +;; ========================================================================== +;; test-framework.sx — Reusable test macros and assertion helpers +;; +;; Loaded first by all test runners. Provides deftest, defsuite, and +;; assertion helpers. Requires 5 platform functions from the host: +;; +;; try-call (thunk) -> {:ok true} | {:ok false :error "msg"} +;; report-pass (name) -> platform-specific pass output +;; report-fail (name error) -> platform-specific fail output +;; push-suite (name) -> push suite name onto context stack +;; pop-suite () -> pop suite name from context stack +;; +;; Any host that provides these 5 functions can run any test spec. +;; ========================================================================== + + +;; -------------------------------------------------------------------------- +;; 1. Test framework macros +;; -------------------------------------------------------------------------- + +(defmacro deftest (name &rest body) + `(let ((result (try-call (fn () ,@body)))) + (if (get result "ok") + (report-pass ,name) + (report-fail ,name (get result "error"))))) + +(defmacro defsuite (name &rest items) + `(do (push-suite ,name) + ,@items + (pop-suite))) + + +;; -------------------------------------------------------------------------- +;; 2. Assertion helpers — defined in SX, available in test bodies +;; -------------------------------------------------------------------------- + +(define assert-equal + (fn (expected actual) + (assert (equal? expected actual) + (str "Expected " (str expected) " but got " (str actual))))) + +(define assert-not-equal + (fn (a b) + (assert (not (equal? a b)) + (str "Expected values to differ but both are " (str a))))) + +(define assert-true + (fn (val) + (assert val (str "Expected truthy but got " (str val))))) + +(define assert-false + (fn (val) + (assert (not val) (str "Expected falsy but got " (str val))))) + +(define assert-nil + (fn (val) + (assert (nil? val) (str "Expected nil but got " (str val))))) + +(define assert-type + (fn (expected-type val) + (let ((actual-type + (if (nil? val) "nil" + (if (boolean? val) "boolean" + (if (number? val) "number" + (if (string? val) "string" + (if (list? val) "list" + (if (dict? val) "dict" + "unknown")))))))) + (assert (= expected-type actual-type) + (str "Expected type " expected-type " but got " actual-type))))) + +(define assert-length + (fn (expected-len col) + (assert (= (len col) expected-len) + (str "Expected length " expected-len " but got " (len col))))) + +(define assert-contains + (fn (item col) + (assert (some (fn (x) (equal? x item)) col) + (str "Expected collection to contain " (str item))))) + +(define assert-throws + (fn (thunk) + (let ((result (try-call thunk))) + (assert (not (get result "ok")) + "Expected an error to be thrown but none was")))) diff --git a/shared/sx/ref/test-parser.sx b/shared/sx/ref/test-parser.sx new file mode 100644 index 0000000..57cc4a8 --- /dev/null +++ b/shared/sx/ref/test-parser.sx @@ -0,0 +1,222 @@ +;; ========================================================================== +;; test-parser.sx — Tests for the SX parser and serializer +;; +;; Requires: test-framework.sx loaded first. +;; Modules tested: parser.sx +;; +;; Platform functions required (beyond test framework): +;; sx-parse (source) -> list of AST expressions +;; sx-serialize (expr) -> SX source string +;; make-symbol (name) -> Symbol value +;; make-keyword (name) -> Keyword value +;; symbol-name (sym) -> string +;; keyword-name (kw) -> string +;; ========================================================================== + + +;; -------------------------------------------------------------------------- +;; Literal parsing +;; -------------------------------------------------------------------------- + +(defsuite "parser-literals" + (deftest "parse integers" + (assert-equal (list 42) (sx-parse "42")) + (assert-equal (list 0) (sx-parse "0")) + (assert-equal (list -7) (sx-parse "-7"))) + + (deftest "parse floats" + (assert-equal (list 3.14) (sx-parse "3.14")) + (assert-equal (list -0.5) (sx-parse "-0.5"))) + + (deftest "parse strings" + (assert-equal (list "hello") (sx-parse "\"hello\"")) + (assert-equal (list "") (sx-parse "\"\""))) + + (deftest "parse escape: newline" + (assert-equal (list "a\nb") (sx-parse "\"a\\nb\""))) + + (deftest "parse escape: tab" + (assert-equal (list "a\tb") (sx-parse "\"a\\tb\""))) + + (deftest "parse escape: quote" + (assert-equal (list "a\"b") (sx-parse "\"a\\\"b\""))) + + (deftest "parse booleans" + (assert-equal (list true) (sx-parse "true")) + (assert-equal (list false) (sx-parse "false"))) + + (deftest "parse nil" + (assert-equal (list nil) (sx-parse "nil"))) + + (deftest "parse keywords" + (let ((result (sx-parse ":hello"))) + (assert-length 1 result) + (assert-equal "hello" (keyword-name (first result))))) + + (deftest "parse symbols" + (let ((result (sx-parse "foo"))) + (assert-length 1 result) + (assert-equal "foo" (symbol-name (first result)))))) + + +;; -------------------------------------------------------------------------- +;; Composite parsing +;; -------------------------------------------------------------------------- + +(defsuite "parser-lists" + (deftest "parse empty list" + (let ((result (sx-parse "()"))) + (assert-length 1 result) + (assert-equal (list) (first result)))) + + (deftest "parse list of numbers" + (let ((result (sx-parse "(1 2 3)"))) + (assert-length 1 result) + (assert-equal (list 1 2 3) (first result)))) + + (deftest "parse nested lists" + (let ((result (sx-parse "(1 (2 3) 4)"))) + (assert-length 1 result) + (assert-equal (list 1 (list 2 3) 4) (first result)))) + + (deftest "parse square brackets as list" + (let ((result (sx-parse "[1 2 3]"))) + (assert-length 1 result) + (assert-equal (list 1 2 3) (first result)))) + + (deftest "parse mixed types" + (let ((result (sx-parse "(42 \"hello\" true nil)"))) + (assert-length 1 result) + (let ((lst (first result))) + (assert-equal 42 (nth lst 0)) + (assert-equal "hello" (nth lst 1)) + (assert-equal true (nth lst 2)) + (assert-nil (nth lst 3)))))) + + +;; -------------------------------------------------------------------------- +;; Dict parsing +;; -------------------------------------------------------------------------- + +(defsuite "parser-dicts" + (deftest "parse empty dict" + (let ((result (sx-parse "{}"))) + (assert-length 1 result) + (assert-type "dict" (first result)))) + + (deftest "parse dict with keyword keys" + (let ((result (sx-parse "{:a 1 :b 2}"))) + (assert-length 1 result) + (let ((d (first result))) + (assert-type "dict" d) + (assert-equal 1 (get d "a")) + (assert-equal 2 (get d "b"))))) + + (deftest "parse dict with string values" + (let ((result (sx-parse "{:name \"alice\"}"))) + (assert-length 1 result) + (assert-equal "alice" (get (first result) "name"))))) + + +;; -------------------------------------------------------------------------- +;; Comments and whitespace +;; -------------------------------------------------------------------------- + +(defsuite "parser-whitespace" + (deftest "skip line comments" + (assert-equal (list 42) (sx-parse ";; comment\n42")) + (assert-equal (list 1 2) (sx-parse "1 ;; middle\n2"))) + + (deftest "skip whitespace" + (assert-equal (list 42) (sx-parse " 42 ")) + (assert-equal (list 1 2) (sx-parse " 1 \n\t 2 "))) + + (deftest "parse multiple top-level expressions" + (assert-length 3 (sx-parse "1 2 3")) + (assert-equal (list 1 2 3) (sx-parse "1 2 3"))) + + (deftest "empty input" + (assert-equal (list) (sx-parse ""))) + + (deftest "only comments" + (assert-equal (list) (sx-parse ";; just a comment\n;; another")))) + + +;; -------------------------------------------------------------------------- +;; Quote sugar +;; -------------------------------------------------------------------------- + +(defsuite "parser-quote-sugar" + (deftest "quasiquote" + (let ((result (sx-parse "`foo"))) + (assert-length 1 result) + (let ((expr (first result))) + (assert-type "list" expr) + (assert-equal "quasiquote" (symbol-name (first expr)))))) + + (deftest "unquote" + (let ((result (sx-parse ",foo"))) + (assert-length 1 result) + (let ((expr (first result))) + (assert-type "list" expr) + (assert-equal "unquote" (symbol-name (first expr)))))) + + (deftest "splice-unquote" + (let ((result (sx-parse ",@foo"))) + (assert-length 1 result) + (let ((expr (first result))) + (assert-type "list" expr) + (assert-equal "splice-unquote" (symbol-name (first expr))))))) + + +;; -------------------------------------------------------------------------- +;; Serializer +;; -------------------------------------------------------------------------- + +(defsuite "serializer" + (deftest "serialize number" + (assert-equal "42" (sx-serialize 42))) + + (deftest "serialize string" + (assert-equal "\"hello\"" (sx-serialize "hello"))) + + (deftest "serialize boolean" + (assert-equal "true" (sx-serialize true)) + (assert-equal "false" (sx-serialize false))) + + (deftest "serialize nil" + (assert-equal "nil" (sx-serialize nil))) + + (deftest "serialize keyword" + (assert-equal ":foo" (sx-serialize (make-keyword "foo")))) + + (deftest "serialize symbol" + (assert-equal "bar" (sx-serialize (make-symbol "bar")))) + + (deftest "serialize list" + (assert-equal "(1 2 3)" (sx-serialize (list 1 2 3)))) + + (deftest "serialize empty list" + (assert-equal "()" (sx-serialize (list)))) + + (deftest "serialize nested" + (assert-equal "(1 (2 3) 4)" (sx-serialize (list 1 (list 2 3) 4))))) + + +;; -------------------------------------------------------------------------- +;; Round-trip: parse then serialize +;; -------------------------------------------------------------------------- + +(defsuite "parser-roundtrip" + (deftest "roundtrip number" + (assert-equal "42" (sx-serialize (first (sx-parse "42"))))) + + (deftest "roundtrip string" + (assert-equal "\"hello\"" (sx-serialize (first (sx-parse "\"hello\""))))) + + (deftest "roundtrip list" + (assert-equal "(1 2 3)" (sx-serialize (first (sx-parse "(1 2 3)"))))) + + (deftest "roundtrip nested" + (assert-equal "(a (b c))" + (sx-serialize (first (sx-parse "(a (b c))")))))) diff --git a/shared/sx/ref/test-render.sx b/shared/sx/ref/test-render.sx new file mode 100644 index 0000000..c714fc7 --- /dev/null +++ b/shared/sx/ref/test-render.sx @@ -0,0 +1,167 @@ +;; ========================================================================== +;; test-render.sx — Tests for the HTML rendering adapter +;; +;; Requires: test-framework.sx loaded first. +;; Modules tested: render.sx, adapter-html.sx +;; +;; Platform functions required (beyond test framework): +;; render-html (sx-source) -> HTML string +;; Parses the sx-source string, evaluates via render-to-html in a +;; fresh env, and returns the resulting HTML string. +;; (This is a test-only convenience that wraps parse + render-to-html.) +;; ========================================================================== + + +;; -------------------------------------------------------------------------- +;; Basic element rendering +;; -------------------------------------------------------------------------- + +(defsuite "render-elements" + (deftest "simple div" + (assert-equal "
a
b
hello world
" + (render-html "(p \"hello\" \" world\")"))) + + (deftest "number content" + (assert-equal "42" + (render-html "(span 42)")))) + + +;; -------------------------------------------------------------------------- +;; Attributes +;; -------------------------------------------------------------------------- + +(defsuite "render-attrs" + (deftest "string attribute" + (let ((html (render-html "(div :id \"main\" \"content\")"))) + (assert-true (string-contains? html "id=\"main\"")) + (assert-true (string-contains? html "content")))) + + (deftest "class attribute" + (let ((html (render-html "(div :class \"foo bar\" \"x\")"))) + (assert-true (string-contains? html "class=\"foo bar\"")))) + + (deftest "multiple attributes" + (let ((html (render-html "(a :href \"/home\" :class \"link\" \"Home\")"))) + (assert-true (string-contains? html "href=\"/home\"")) + (assert-true (string-contains? html "class=\"link\"")) + (assert-true (string-contains? html "Home"))))) + + +;; -------------------------------------------------------------------------- +;; Void elements +;; -------------------------------------------------------------------------- + +(defsuite "render-void" + (deftest "br is self-closing" + (assert-equal "a
b
" + (render-html "(<> (p \"a\") (p \"b\"))"))) + + (deftest "empty fragment" + (assert-equal "" + (render-html "(<>)")))) + + +;; -------------------------------------------------------------------------- +;; HTML escaping +;; -------------------------------------------------------------------------- + +(defsuite "render-escaping" + (deftest "text content is escaped" + (let ((html (render-html "(p \"\")"))) + (assert-false (string-contains? html "