|
|
---
|
|
|
title: Testing, Validation & Codegen
|
|
|
type: reference
|
|
|
audience: human + agent
|
|
|
status: current
|
|
|
---
|
|
|
|
|
|
# Testing, Validation & Codegen
|
|
|
|
|
|
The cross-cutting story of how the framework stays correct: the test suite, the
|
|
|
contract validators, what is generated vs authored, and the SSR posture. The
|
|
|
commands are the `scripts` in `package.json`; this groups them by what they are
|
|
|
*for*.
|
|
|
|
|
|
## The verification loop (the short version)
|
|
|
|
|
|
```bash
|
|
|
npm run check # types — svelte-kit sync && svelte-check (expect 0 errors)
|
|
|
npm run test # the vitest suite (one run)
|
|
|
npm run lint # prettier --check · npm run format to fix
|
|
|
```
|
|
|
|
|
|
For a component you also run the contract validators (below). A change is not
|
|
|
done until `check` is clean and the relevant validators pass.
|
|
|
|
|
|
## Tests — two projects
|
|
|
|
|
|
`vite.config.ts` defines **two vitest projects** (see CLAUDE.md → "Vitest
|
|
|
Two-Project Structure"):
|
|
|
|
|
|
- **client** — browser tests via Playwright, for `*.svelte.{test,spec}.{js,ts}`.
|
|
|
This is where component providers are exercised with real runes + DOM.
|
|
|
- **server** — Node environment for `*.{test,spec}.{js,ts}` (excludes the svelte
|
|
|
tests). Pure engines, libs, and server logic.
|
|
|
|
|
|
```bash
|
|
|
npm run test:unit # watch mode
|
|
|
npm run test # one run
|
|
|
npx vitest run src/uix/morfo/compile.test.ts # a single file
|
|
|
npx vitest run -t "describe name" # by test name
|
|
|
```
|
|
|
|
|
|
Component providers carry their own `{name}-provider.svelte.test.ts` under the
|
|
|
client project; the reusable engines they consume live in `$libs/datagrid`,
|
|
|
`$libs/forms`, `$libs/strings` and are tested there, not inside soma. See
|
|
|
[`SOMA_ARCHITECTURE.md`](../src/uix/soma/SOMA_ARCHITECTURE.md) §6 for the
|
|
|
per-component coverage map.
|
|
|
|
|
|
## Validation — what each script catches
|
|
|
|
|
|
These catch classes of bug that `check` (types) and an HTTP 200 (SSR) miss.
|
|
|
|
|
|
| Command | Catches |
|
|
|
| --- | --- |
|
|
|
| `npm run morfo:check` | the **real DOM vs the morfo contract** — navigates each demo and validates emitted `data-*` against the declaration. |
|
|
|
| `npm run morfo:vocabulary` | **canonical-verb / vocabulary drift** in morfo events (e.g. `open\|closed`, declared verbs not in `SEMA_VERBS`). Hard-fails CI on declared-verb drift. |
|
|
|
| `npm run component:audit` | the **acceptance matrix** of [`COMPONENT_COMPLETION_CHECKLIST.md`](../src/uix/COMPONENT_COMPLETION_CHECKLIST.md) — per-rule severity/applicability across all four layers + recipe + demo. |
|
|
|
| `npm run perm:check` | **state-transition** bugs — cycles a component through its declared states (via `data-perm-step` annotations) and re-validates morfo after each. Catches reactivity loops and transition-time drift `morfo:check` can't. |
|
|
|
| `npm run smoke` | **runtime / hydration** errors — walks every `+page.svelte` with Playwright and surfaces `pageerror`, `console.error`, missing translation keys, `Context "X" not found`, and `__uix_lang_missing__` markers. Needs `npm run dev` running. HTTP 200 is SSR only; smoke exercises client hydration. |
|
|
|
| `npm run translations:check` | missing / malformed translation keys. |
|
|
|
| `scripts/eidos-lint.ts` · `eidos-lint-all.ts` | classifies every `[data-*]` selector in eidos CSS as **morfo-backed / eidos-only / invalid** — drift between the morfo contract and the CSS. (Opt-in safety net; the architectural defense is the typed `semaSelector` builder — see the "Eidos drift defense" rule in CLAUDE.md.) |
|
|
|
| `src/uix/contracts.test.ts` (vitest, via `npm run test`) | **catalogue invariants** that keep declarations / recipes coherent as the framework grows. Each fails on drift naming the offender, and excludes the active-dev-track set so it stays green for the maintained catalogue: **VG-8** every morfo is `as const satisfies Morfo`, never `: Morfo` (a `: Morfo` annotation widens the literal so the schema can't check it); **SYS-1 scope-drift** a component shipping an `eidos/components/{c}/` recipe declares `'eidos'` in `scope`; **A31** no per-item membership predicate (`isSelected` / `isItemPressed` / …) doing `.current.includes` (O(N²) — lift a `Set`, use `.has()`); **A30** `inputId` registered with the parent Field in the constructor, not wrapped in a `$effect`; **THEME-SYS-1** overlay z-index references the named `--z-index-overlay-*` scale, never a raw integer. |
|
|
|
|
|
|
## Codegen — what is generated vs authored
|
|
|
|
|
|
Some surfaces are produced from a source of truth, not hand-maintained. Don't
|
|
|
edit the output; edit the source and regenerate.
|
|
|
|
|
|
| Command | Generates from |
|
|
|
| --- | --- |
|
|
|
| `npm run generate:eidos-css` | the eidos foundation/recipe CSS, from `EidosConfig.recipes`. (`generated/base.css` is output — regenerate, don't hand-edit.) |
|
|
|
|
|
|
The `data-*` contract is **not** a generated doc — it is the morfo itself, validated
|
|
|
live by `npm run morfo:check`. (A legacy `generate:contracts-docs` script and its
|
|
|
output `DATA_ATTRS.md`, both fossils of the removed `terra` layer, were deleted.)
|
|
|
|
|
|
The **morfo compiler** itself is the central codegen primitive: `compileMorfo(morfo)`
|
|
|
turns a declaration into a `CompiledMorfo` (resolved attr / keyboard / action
|
|
|
plans + the closed set of CSS selectors eidos may use), cached by morfo identity
|
|
|
(WeakMap). Every consumer reads the compiled morfo, never the raw declaration.
|
|
|
|
|
|
```bash
|
|
|
npm run eidos:purge # production: drop unreached foundation CSS (−46 to −55%)
|
|
|
```
|
|
|
|
|
|
## SSR posture
|
|
|
|
|
|
Components are **headless-functional without a DOM**. The architecture keeps SSR
|
|
|
and headless tests working:
|
|
|
|
|
|
- **`dom:false`** injects a shared `disabledDom` no-op consumed by Soma / Sema /
|
|
|
Eidos — there is no silent fallback to direct DOM writes (see
|
|
|
[`arts/adom/README`](../src/arts/adom/README.md) and `src/uix/contracts.ts`).
|
|
|
- **`ActiveDom`** resolves the *owner* `document` / `window` (`getDocument(node)`
|
|
|
/ `getWindow(node)`), so it is correct under iframes, popups and happy-dom —
|
|
|
not bound to the global `document`.
|
|
|
- **Sema is ornamental** — `ActiveUix.events` (the perceptual engine) is optional;
|
|
|
with no engine, `SomaRuntime.trigger()` skips the emit and the component stays
|
|
|
functional. So sound/haptic-free, audio-disabled and server environments work.
|
|
|
- **Hydration is what HTTP 200 misses** — server render succeeds long before a
|
|
|
hydration-time `Context not found` or effect loop would; that is exactly what
|
|
|
`npm run smoke` exists to catch.
|
|
|
|
|
|
## See also
|
|
|
|
|
|
- [`docs/getting-started.md`](./getting-started.md) — the short verification loop in context.
|
|
|
- [`soma/COMPONENT_GUIDE.md`](../src/uix/soma/COMPONENT_GUIDE.md) — the build checklist that calls these.
|
|
|
- [`COMPONENT_COMPLETION_CHECKLIST.md`](../src/uix/COMPONENT_COMPLETION_CHECKLIST.md) — what `component:audit` enforces.
|