SxExpr aser wire format fix + Playwright test infrastructure + blob protocol

Aser serialization: aser-call/fragment now return SxExpr instead of String.
serialize/inspect passes SxExpr through unquoted, preventing the double-
escaping (\" → \\\" ) that broke client-side parsing when aser wire format
was output via raw! into <script> tags. Added make-sx-expr + sx-expr-source
primitives to OCaml and JS hosts.

Binary blob protocol: eval, aser, aser-slot, and sx-page-full now send SX
source as length-prefixed blobs instead of escaped strings. Eliminates pipe
desync from concurrent requests and removes all string-escape round-trips
between Python and OCaml.

Bridge safety: re-entrancy guard (_in_io_handler) raises immediately if an
IO handler tries to call the bridge, preventing silent deadlocks.

Fetch error logging: orchestration.sx error callback now logs method + URL
via log-warn. Platform catches (fetchAndRestore, fetchPreload, bindBoostForm)
also log errors instead of silently swallowing them.

Transpiler fixes: makeEnv, scopePeek, scopeEmit, makeSxExpr added as
platform function definitions + transpiler mappings — were referenced in
transpiled code but never defined as JS functions.

Playwright test infrastructure:
- nav() captures JS errors and fails fast with the actual error message
- Checks for [object Object] rendering artifacts
- New tests: delete-row interaction, full page refresh, back button,
  direct load with fresh context, code block content verification
- Default base URL changed to localhost:8013 (standalone dev server)
- docker-compose.dev-sx.yml: port 8013 exposed for local testing
- test-sx-build.sh: build + unit tests + Playwright smoke tests

Geography content: index page component written (sx/sx/geography/index.sx)
describing OCaml evaluator, wire formats, rendering pipeline, and topic
links. Wiring blocked by aser-expand-component children passing issue.

Tests: 1080/1080 JS, 952/952 OCaml, 66/66 Playwright

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-03-22 22:17:43 +00:00
parent 6d73edf297
commit df461beec2
17 changed files with 684 additions and 82 deletions

93
test-sx-build.sh Executable file
View File

@@ -0,0 +1,93 @@
#!/bin/bash
# test-sx-build.sh — Build sx-browser.js and run all tests.
#
# Usage:
# ./test-sx-build.sh # build + unit tests + playwright smoke
# ./test-sx-build.sh --unit-only # build + unit tests only (no server needed)
# ./test-sx-build.sh --no-build # skip build, run tests only
#
# Requires:
# - Python 3 with hosts/javascript/cli.py deps
# - Node.js for unit tests
# - pytest-playwright for browser tests (pip install pytest-playwright)
#
# Environment:
# SX_TEST_BASE — override test server URL (default: https://sx.rose-ash.com)
set -euo pipefail
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[0;33m'
NC='\033[0m'
UNIT_ONLY=false
NO_BUILD=false
for arg in "$@"; do
case "$arg" in
--unit-only) UNIT_ONLY=true ;;
--no-build) NO_BUILD=true ;;
esac
done
cd "$(dirname "$0")"
# --- Step 1: Build ---
if [ "$NO_BUILD" = false ]; then
echo -e "${YELLOW}[1/3] Building sx-browser.js...${NC}"
python3 hosts/javascript/cli.py --output shared/static/scripts/sx-browser.js
echo -e "${GREEN}Build OK${NC}"
else
echo -e "${YELLOW}[1/3] Skipping build (--no-build)${NC}"
fi
# --- Step 2: Unit tests ---
echo -e "${YELLOW}[2/3] Running JS unit tests...${NC}"
UNIT_RESULT=$(node hosts/javascript/run_tests.js --full 2>&1)
UNIT_LAST=$(echo "$UNIT_RESULT" | tail -1)
if echo "$UNIT_LAST" | grep -q "0 failed"; then
echo -e "${GREEN}$UNIT_LAST${NC}"
else
echo -e "${RED}$UNIT_LAST${NC}"
echo "$UNIT_RESULT" | grep "FAIL:" | head -20
exit 1
fi
# Also run aser tests
echo -e "${YELLOW} Running aser tests...${NC}"
ASER_RESULT=$(node hosts/javascript/run_tests.js test-aser 2>&1)
ASER_LAST=$(echo "$ASER_RESULT" | tail -1)
ASER_PASS=$(echo "$ASER_LAST" | grep -oP '\d+ passed' || echo "0 passed")
ASER_FAIL=$(echo "$ASER_LAST" | grep -oP '\d+ failed' || echo "0 failed")
echo -e "${GREEN} Aser: $ASER_PASS, $ASER_FAIL${NC}"
# --- Step 3: Playwright smoke tests ---
if [ "$UNIT_ONLY" = true ]; then
echo -e "${YELLOW}[3/3] Skipping Playwright tests (--unit-only)${NC}"
echo -e "${GREEN}All done.${NC}"
exit 0
fi
echo -e "${YELLOW}[3/3] Running Playwright smoke tests...${NC}"
BASE="${SX_TEST_BASE:-http://localhost:8013}"
# Quick connectivity check
HTTP_CODE=$(curl -s -o /dev/null -w "%{http_code}" "$BASE/sx/" 2>/dev/null || echo "000")
if [ "$HTTP_CODE" = "000" ]; then
echo -e "${RED}Server unreachable at $BASE — skipping Playwright tests${NC}"
echo -e "${YELLOW}Run with SX_TEST_BASE=http://localhost:PORT to test locally${NC}"
exit 0
fi
python3 -m pytest sx/tests/test_demos.py -v -x -k "test_page_loads" --tb=short 2>&1 | tail -20
PW_EXIT=${PIPESTATUS[0]}
if [ "$PW_EXIT" -eq 0 ]; then
echo -e "${GREEN}Playwright smoke tests passed${NC}"
else
echo -e "${RED}Playwright smoke tests failed (exit $PW_EXIT)${NC}"
exit 1
fi
echo -e "${GREEN}All tests passed.${NC}"