|
|
---
|
|
|
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:gate # types WITH the policy: src/ owes ZERO; web/ measured
|
|
|
# against the shrinking ledger scripts/check-debt.ts
|
|
|
npm run check # raw svelte-check (src/ expect 0; web/ carries the
|
|
|
# frozen ledger errors, which may only shrink)
|
|
|
npm run test # the vitest suite (one run)
|
|
|
npm run gate # everything the pre-push hook runs (see §The gate)
|
|
|
npm run lint # prettier --check · npm run format to fix
|
|
|
# (NOT in `gate` yet — pre-existing repo-wide format debt;
|
|
|
# the one-shot is `npm run format` on a quiet tree)
|
|
|
```
|
|
|
|
|
|
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 (a guard fails when an active provider ships without one — the
|
|
|
test tree IS the coverage inventory); the reusable engines they consume live
|
|
|
in `$libs/datagrid`, `$libs/forms`, `$libs/strings` and are tested there, not
|
|
|
inside soma. See
|
|
|
[`soma-architecture.md`](./architecture/soma-architecture.md) §6.
|
|
|
|
|
|
## 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 [`completion-checklist.md`](./guides/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 layer:check` | a **shared visual layer losing a cascade fight** — for every element carrying a layer hook (`data-viewport-placement`), asserts the computed `position`, that the stacking token resolved, and that no override slot declared inline computes to nothing (the signature of a custom-property CYCLE). Needs `npm run dev`. Its consumer list is DERIVED from who imports the layer, so a component joins the day it migrates. **Deliberate hole, stated in the script**: geometry. `getComputedStyle` reports the USED value, so an `inset: auto` reads back as pixels (measured: `-1976.7px`) — there is no property-level way to tell a dead `calc()` from an intended value. |
|
|
|
| `npm run translations:check` | missing / malformed translation keys. |
|
|
|
| `npm run docs:check` | **doc-corpus drift** — copied vocabulary counts vs the source consts, phantom fields (the legacy morfo text field; rejected API shapes), dependency claims vs `package.json`, the variant-vocab mirror in `component-audit.ts`, checklist↔audit rule-ID sync, and relative links (warn severity). Guards the "link the canon, never copy it" law of [`docs/authoring.md`](./authoring.md). |
|
|
|
| `npm run eidos:lint` | classifies every `[data-*]` selector in eidos CSS as **morfo-backed / eidos-only / invalid** — drift between the morfo contract and the CSS. A `gate` member since P0 fase B (audit 2026-08-26); the architectural defense is still the typed `semaSelector` builder — see the "Eidos drift defense" rule in CLAUDE.md. |
|
|
|
| `src/uix/eidos/shared-layer-contract.test.ts` (vitest) | the **text half** a browser cannot cover for a shared layer: every zone has a rule, `stretch` stays on the axes it was scoped to, no axis is read without its override slot, no geometry rule keys on the component identity, and each consumer imports the layer / stamps the hook / mints no parallel token / re-asserts `position` when the primitive it composes declares one. It exists for ONE thing the computed check is blind to: `getComputedStyle` of a safe-area slot returns `"0px"` on desktop, so a `:dir(rtl)` remap with one half flipped reads identical to a correct one on every CI machine and only surfaces on a notched phone, sideways, in RTL. |
|
|
|
| `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.
|
|
|
- **The morfo contract server-renders** — since the render bag became the
|
|
|
single attr pipeline (P0 fase C, audit 2026-08-26), a part's `role` /
|
|
|
`aria-*` / `data-state` / literals resolve at render time, server included.
|
|
|
The mechanism is SHARED (`partPropsForRegistration`) and pinned by
|
|
|
`src/uix/soma/ssr-contract.test.ts` (server project, node — no window) with
|
|
|
two representatives, Toggle and RadioGroup — a CANARY, not a per-provider
|
|
|
census; the per-component SSR snapshot census is still owed (informe P1).
|
|
|
It was born red against the old client-only effect.
|
|
|
|
|
|
## The gate
|
|
|
|
|
|
The drift-defense scripts stopped being advisory in P0 fase B (audit
|
|
|
2026-08-26). Three additions:
|
|
|
|
|
|
- **`npm run check:gate`** — `svelte-check` with policy: any ERROR under
|
|
|
`src/` fails (framework source owes zero); errors in the demo tree are
|
|
|
measured against the per-file ledger in `scripts/check-debt.ts`, which only
|
|
|
SHRINKS (the theming-census-debt discipline — a file above its entry, or an
|
|
|
erroring file the ledger does not name, fails). The run must end with the
|
|
|
`COMPLETED` marker and the parsed count must match it: a gate that
|
|
|
inspected nothing fails, never passes.
|
|
|
- **`npm run gate`** — the chain the pre-push hook runs: `check:gate` → the
|
|
|
fast validators (`morfo:vocabulary`, `docs:check`, `arts:check`,
|
|
|
`blocks:check`, `packs:check`, `rtl:check`, `translations:check`,
|
|
|
`agent:check`, `eidos:lint`) → the full vitest suite last. `lint` is
|
|
|
deliberately NOT a member yet: the tree carries pre-existing repo-wide
|
|
|
format debt; the pending one-shot is `npm run format` on a quiet tree, then
|
|
|
add `npm run lint &&` back.
|
|
|
- **The pre-push hook** — source of truth in `scripts/hooks/pre-push`; arm it
|
|
|
with `cp scripts/hooks/pre-push .git/hooks/pre-push`. Browser-dependent
|
|
|
validators (`morfo:check`, `perm:check`, `smoke`, `layer:check`) stay
|
|
|
manual — they need a dev server.
|
|
|
|
|
|
## See also
|
|
|
|
|
|
- [`docs/getting-started.md`](./getting-started.md) — the short verification loop in context.
|
|
|
- [`component-guide.md`](./guides/component-guide.md) — the build checklist that calls these.
|
|
|
- [`completion-checklist.md`](./guides/completion-checklist.md) — what `component:audit` enforces.
|