diff --git a/CLAUDE.md b/CLAUDE.md index 02e51a2a..77787be1 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -98,6 +98,12 @@ Read the error carefully. Fragment errors give the parse failure in the new sour | `sx_diff_branch` | Structural diff of all `.sx` changes on branch vs base ref | | `sx_blame` | Git blame for `.sx` file, optionally focused on a tree path | +**Test harness:** + +| Tool | Purpose | +|------|---------| +| `sx_harness_eval` | Evaluate SX in a sandboxed harness with mock IO. Returns result + IO trace. Params: `expr`, optional `mock` (SX dict of overrides), optional `file` (load definitions) | + **Analysis:** | Tool | Purpose | @@ -168,6 +174,8 @@ The SX language is defined by a self-hosting specification in `shared/sx/ref/`. - **`shared/sx/ref/primitives.sx`** — All ~80 built-in pure functions: arithmetic, comparison, predicates, string ops, collection ops, dict ops, format helpers, CSSX style primitives. - **`shared/sx/ref/render.sx`** — Three rendering modes: `render-to-html` (server HTML), `render-to-sx`/`aser` (SX wire format for client), `render-to-dom` (browser). HTML tag registry, void elements, boolean attrs. - **`shared/sx/ref/bootstrap_js.py`** — Transpiler: reads the `.sx` spec files and emits `sx-ref.js`. +- **`spec/harness.sx`** — Test harness: mock IO platform for testing components. Sessions, IO interception, log queries, assertions (`assert-io-called`, `assert-io-count`, `assert-io-args`, `assert-no-io`, `assert-state`). Extensible — new platforms add entries to the platform dict. Loaded automatically by test runners. +- **`spec/tests/test-harness.sx`** — Tests for the harness itself (15 tests). ### Type system @@ -212,6 +220,26 @@ lambda, component, macro, thunk (TCO deferred eval) The `aser` (async-serialize) mode evaluates control flow and function calls but serializes HTML tags and component calls as SX source — the client renders them. This is the wire format for HTMX-like responses. +### Test harness (from harness.sx) + +The harness provides sandboxed testing of IO behavior. It's a spec-level facility — works on every host. + +**Core concepts:** +- **Session** — `(make-harness &key platform)` creates a session with mock IO operations +- **Interceptor** — `(make-interceptor session op-name mock-fn)` wraps a mock to record calls +- **IO log** — append-only trace of every IO call. Query with `io-calls`, `io-call-count`, `io-call-args` +- **Assertions** — `assert-io-called`, `assert-no-io`, `assert-io-count`, `assert-io-args`, `assert-state` + +**Default platform** provides 30+ mock IO operations (fetch, query, action, cookies, DOM, storage, etc.) that return sensible empty values. Override per-test with `:platform` on `make-harness`. + +**Extensibility:** New platforms add entries to the platform dict. The harness intercepts any registered operation — no harness code changes needed for new IO types. + +**Platform-specific test extensions** live in the platform spec, not the core harness: +- `web/harness-web.sx` — DOM assertions, `simulate-click`, CSS class checks +- `web/harness-reactive.sx` — signal assertions: `assert-signal-value`, `assert-signal-subscribers` + +**Components ship with tests** via `deftest` forms. Tests reference components by name or CID (`:for` param). Tests are independent content-addressed objects — anyone can publish tests for any component. + ### Platform interface Each target (JS, Python) must provide: type inspection (`type-of`), constructors (`make-lambda`, `make-component`, `make-macro`, `make-thunk`), accessors, environment operations (`env-has?`, `env-get`, `env-set!`, `env-extend`, `env-merge`), and DOM/HTML rendering primitives.