#!/usr/bin/env bash # Fast hyperscript test runner — pipes directly to sx_server.exe via epoch protocol. # No MCP, no Docker, no web server. Runs in <2 seconds. # # Usage: # bash lib/hyperscript/test.sh # run all tests # bash lib/hyperscript/test.sh -v # verbose — show actual output set -euo pipefail cd "$(git rev-parse --show-toplevel)" SX_SERVER="hosts/ocaml/_build/default/bin/sx_server.exe" if [ ! -x "$SX_SERVER" ]; then echo "ERROR: $SX_SERVER not found. Run: cd hosts/ocaml && dune build" exit 1 fi VERBOSE="${1:-}" PASS=0 FAIL=0 ERRORS="" TMPFILE=$(mktemp) trap "rm -f $TMPFILE" EXIT # ── Write epoch commands to temp file ───────────────────────────── cat > "$TMPFILE" << 'EPOCHS' (epoch 1) (load "lib/r7rs.sx") (epoch 2) (load "lib/hyperscript/tokenizer.sx") (epoch 3) (load "lib/hyperscript/parser.sx") (epoch 4) (load "lib/hyperscript/compiler.sx") (epoch 10) (eval "(hs-compile \"on click add .red to me\")") (epoch 11) (eval "(hs-compile \"on click toggle .active on me\")") (epoch 12) (eval "(hs-compile \"on click add .red to me then remove .blue from me\")") (epoch 13) (eval "(hs-compile \"on click set my innerHTML to 'hello'\")") (epoch 14) (eval "(hs-compile \"on click log me\")") (epoch 15) (eval "(hs-compile \"add .highlight to me\")") (epoch 16) (eval "(hs-compile \"remove .highlight from me\")") (epoch 17) (eval "(hs-compile \"toggle .visible on me\")") (epoch 18) (eval "(hs-compile \"hide me\")") (epoch 19) (eval "(hs-compile \"show me\")") (epoch 20) (eval "(hs-compile \"wait 500ms\")") (epoch 21) (eval "(hs-compile \"wait 2s\")") (epoch 22) (eval "(hs-compile \"set x to 1 + 2\")") (epoch 23) (eval "(hs-compile \"set x to 3 * 4\")") (epoch 24) (eval "(hs-compile \"init add .loaded to me end\")") (epoch 25) (eval "(hs-compile \"set x to 42\")") (epoch 26) (eval "(hs-compile \"put 'hello' into me\")") (epoch 27) (eval "(hs-compile \"increment x\")") (epoch 28) (eval "(hs-compile \"decrement x\")") (epoch 29) (eval "(hs-compile \"on every click log me end\")") (epoch 30) (eval "(hs-compile \"on click from .btn log me end\")") (epoch 40) (eval "(hs-to-sx-from-source \"on click add .red to me\")") (epoch 41) (eval "(hs-to-sx-from-source \"on click toggle .active on me\")") (epoch 42) (eval "(hs-to-sx-from-source \"on click set my innerHTML to 'hello'\")") (epoch 43) (eval "(hs-to-sx-from-source \"on click add .red to me then remove .blue from me\")") EPOCHS # ── Run ─────────────────────────────────────────────────────────── OUTPUT=$(timeout 30 "$SX_SERVER" < "$TMPFILE" 2>/dev/null) # ── Check function ──────────────────────────────────────────────── check() { local epoch="$1" desc="$2" expected="$3" local actual actual=$(echo "$OUTPUT" | grep -A1 "^(ok-len $epoch " | tail -1) if [ -z "$actual" ]; then actual=$(echo "$OUTPUT" | grep "^(ok $epoch " || true) fi if [ -z "$actual" ]; then actual=$(echo "$OUTPUT" | grep "^(error $epoch " || true) fi if [ -z "$actual" ]; then actual="" fi if echo "$actual" | grep -qF "$expected"; then PASS=$((PASS + 1)) [ "$VERBOSE" = "-v" ] && echo " ✓ $desc" else FAIL=$((FAIL + 1)) ERRORS+=" ✗ $desc (epoch $epoch) expected: $expected actual: $actual " fi } # ── Parser assertions ───────────────────────────────────────────── check 10 "on click basic" '(on "click" (add-class "red" (me)))' check 11 "on click toggle" '(on "click" (toggle-class "active" (me)))' check 12 "on click chain" '(on "click" (do (add-class "red" (me)) (remove-class "blue" (me))))' check 13 "on click set prop" '(on "click" (set!' check 14 "on click log" '(on "click" (log (me)))' check 15 "add class cmd" '(add-class "highlight" (me))' check 16 "remove class cmd" '(remove-class "highlight" (me))' check 17 "toggle class cmd" '(toggle-class "visible" (me))' check 18 "hide cmd" '(hide (me))' check 19 "show cmd" '(show (me))' check 20 "wait ms" '(wait 500)' check 21 "wait seconds" '(wait 2000)' check 22 "arithmetic add" '+' check 23 "arithmetic mul" '*' check 24 "init feature" '(init' check 25 "set variable" '(set! (ref "x") 42)' check 26 "put into" '(set! (me) "hello")' check 27 "increment" 'increment' check 28 "decrement" 'decrement' check 29 "on every click" '(on' check 30 "on click from" '(on' # ── Compiler assertions ─────────────────────────────────────────── check 40 "compiled: on click" '(hs-on me "click"' check 41 "compiled: toggle" 'hs-toggle-class!' check 42 "compiled: set prop" 'dom-set-prop' check 43 "compiled: chain" 'dom-remove-class' # ── Report ──────────────────────────────────────────────────────── TOTAL=$((PASS + FAIL)) if [ $FAIL -eq 0 ]; then echo "✓ $PASS/$TOTAL hyperscript tests passed" else echo "✗ $PASS/$TOTAL passed, $FAIL failed:" echo "" echo "$ERRORS" fi [ $FAIL -eq 0 ]