You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
svelte-kit-vice/docs/testing-and-tooling.md

16 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 (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 §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 — 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.
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/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.

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 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

Powered by TurnKey Linux.