"""Generate golden HTML/aser test data from the Python evaluator. Evaluates curated component calls through the Python ref evaluator and writes golden_data.json — a list of {name, sx_input, expected_html, expected_aser} triples. Usage: python3 shared/sx/tests/generate_golden.py """ import asyncio import json import os import sys _project_root = os.path.abspath(os.path.join(os.path.dirname(__file__), "../../..")) if _project_root not in sys.path: sys.path.insert(0, _project_root) # Curated test cases — component calls and plain expressions that # exercise the rendering pipeline. Each entry is (name, sx_input). GOLDEN_CASES = [ # --- Basic HTML rendering --- ("div_simple", '(div "hello")'), ("div_class", '(div :class "card" "content")'), ("p_text", '(p "paragraph text")'), ("nested_tags", '(div (p "a") (p "b"))'), ("void_br", "(br)"), ("void_hr", "(hr)"), ("void_img", '(img :src "/photo.jpg" :alt "A photo")'), ("void_input", '(input :type "text" :name "q" :placeholder "Search")'), ("fragment", '(<> (p "a") (p "b"))'), ("boolean_attr", '(input :type "checkbox" :checked true)'), ("nil_attr", '(div :class nil "content")'), ("empty_string_attr", '(div :class "" "visible")'), # --- Control flow --- ("if_true", '(if true (p "yes") (p "no"))'), ("if_false", '(if false (p "yes") (p "no"))'), ("when_true", '(when true (p "shown"))'), ("when_false", '(when false (p "hidden"))'), ("let_binding", '(let ((x "hi")) (p x))'), ("let_multiple", '(let ((x "a") (y "b")) (div (p x) (p y)))'), ("cond_form", '(cond (= 1 2) (p "no") (= 1 1) (p "yes") :else (p "default"))'), ("case_form", '(case "b" "a" "A" "b" "B" :else "?")'), ("and_short", '(and true false)'), ("or_short", '(or false "found")'), # --- Higher-order forms --- ("map_li", '(map (fn (x) (li x)) (list "a" "b" "c"))'), ("filter_even", '(filter even? (list 1 2 3 4 5))'), ("reduce_sum", '(reduce + 0 (list 1 2 3 4 5))'), # --- String operations --- ("str_concat", '(str "hello" " " "world")'), ("str_upcase", '(upcase "hello")'), # --- Component definitions and calls --- ("defcomp_simple", '(do (defcomp ~test-badge (&key label) (span :class "badge" label)) (~test-badge :label "New"))'), ("defcomp_children", '(do (defcomp ~test-wrap (&rest children) (div :class "wrap" children)) (~test-wrap (p "inside")))'), ("defcomp_multi_key", '(do (defcomp ~test-card (&key title subtitle) (div (h2 title) (when subtitle (p subtitle)))) ' '(~test-card :title "Title" :subtitle "Sub"))'), ("defcomp_no_optional", '(do (defcomp ~test-card2 (&key title subtitle) (div (h2 title) (when subtitle (p subtitle)))) ' '(~test-card2 :title "Only Title"))'), # --- Nested components --- ("nested_components", '(do (defcomp ~inner (&key text) (span :class "inner" text)) ' '(defcomp ~outer (&key title &rest children) (div :class "outer" (h2 title) children)) ' '(~outer :title "Hello" (~inner :text "World")))'), # --- Macros --- ("macro_unless", '(do (defmacro unless (cond &rest body) (list \'if (list \'not cond) (cons \'do body))) ' '(unless false (p "shown")))'), # --- Special rendering patterns --- ("do_block", '(div (do (p "a") (p "b")))'), ("nil_child", '(div nil "after-nil")'), ("number_child", '(div 42)'), ("bool_child", '(div true)'), # --- Data attributes --- ("data_attr", '(div :data-id "123" :data-name "test" "content")'), # --- raw! (inject pre-rendered HTML) --- ("raw_simple", '(raw! "bold")'), ("raw_in_div", '(div (raw! "italic"))'), ("raw_component", '(do (defcomp ~rich (&key html) (raw! html)) ' '(~rich :html "
CMS
"))'), # --- Shared template components (if available) --- ("misc_error_inline", '(do (defcomp ~shared:misc/error-inline (&key (message :as string)) ' '(div :class "text-red-600 text-sm" message)) ' '(~shared:misc/error-inline :message "Something went wrong"))'), ("misc_notification_badge", '(do (defcomp ~shared:misc/notification-badge (&key (count :as number)) ' '(span :class "bg-red-500 text-white text-xs rounded-full px-1.5 py-0.5" count)) ' '(~shared:misc/notification-badge :count 5))'), ("misc_cache_cleared", '(do (defcomp ~shared:misc/cache-cleared (&key (time-str :as string)) ' '(span :class "text-green-600 font-bold" "Cache cleared at " time-str)) ' '(~shared:misc/cache-cleared :time-str "12:00"))'), ("misc_error_list_item", '(do (defcomp ~shared:misc/error-list-item (&key (message :as string)) (li message)) ' '(~shared:misc/error-list-item :message "Bad input"))'), ("misc_fragment_error", '(do (defcomp ~shared:misc/fragment-error (&key (service :as string)) ' '(p :class "text-sm text-red-600" "Service " (b service) " is unavailable.")) ' '(~shared:misc/fragment-error :service "blog"))'), ] def _generate_html(sx_input: str) -> str: """Evaluate SX and render to HTML using the Python evaluator.""" from shared.sx.ref.sx_ref import evaluate, render from shared.sx.parser import parse_all env = {} exprs = parse_all(sx_input) # For multi-expression inputs (defcomp then call), use evaluate # to process all defs, then render the final expression if len(exprs) > 1: # Evaluate all expressions — defs install into env result = None for expr in exprs: result = evaluate(expr, env) # Render the final result return render(result, env) else: # Single expression — render directly return render(exprs[0], env) def main(): golden = [] ok = 0 failed = 0 for name, sx_input in GOLDEN_CASES: try: html = _generate_html(sx_input) golden.append({ "name": name, "sx_input": sx_input, "expected_html": html, }) ok += 1 except Exception as e: print(f" SKIP {name}: {e}") failed += 1 outpath = os.path.join(os.path.dirname(__file__), "golden_data.json") with open(outpath, "w") as f: json.dump(golden, f, indent=2, ensure_ascii=False) print(f"Generated {ok} golden cases ({failed} skipped) → {outpath}") if __name__ == "__main__": main()