6.6 KiB
| title | type | audience | status |
|---|---|---|---|
| Testing, Validation & Codegen | reference | human + agent | 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)
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.
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 §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 — 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.
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:falseinjects a shareddisabledDomno-op consumed by Soma / Sema / Eidos — there is no silent fallback to direct DOM writes (seearts/adom/READMEandsrc/uix/contracts.ts).ActiveDomresolves the ownerdocument/window(getDocument(node)/getWindow(node)), so it is correct under iframes, popups and happy-dom — not bound to the globaldocument.- 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 foundor effect loop would; that is exactly whatnpm run smokeexists to catch.
See also
docs/getting-started.md— the short verification loop in context.soma/COMPONENT_GUIDE.md— the build checklist that calls these.COMPONENT_COMPLETION_CHECKLIST.md— whatcomponent:auditenforces.