Skip to main content

repo-source-code-test-frameworks

Write unit and type tests for Formisch framework packages (frameworks/angular, frameworks/preact, frameworks/solid, frameworks/svelte, frameworks/vue, frameworks/react, frameworks/react-native). Use when adding tests for hooks/composables/runes/inject functions (useForm/createForm/injectForm, useField/injectField, useFieldArray/injectFieldArray) or components and directives (Form, Field, FieldArray, formischForm, formischField) in any framework wrapper.

Informations de source

Dépôt
open-circle/formisch
Dernière activité de la source
29 juillet 2026 à 23:25
Langue détectée de SKILL.md
anglais
Étoiles
1 205
Forks
36

Options d'installation

Le prompt qui vérifie d'abord la source est sélectionné par défaut. Vous pouvez passer à une commande directe ou télécharger une copie locale.

Vérifiez les fichiers source

Lisez SKILL.md et les fichiers associés affichés par SkillsMP avant de décider de l'installer.

Affichage de SKILL.md

SKILL.md
Instructions source · Aperçu en lecture seule
name
repo-source-code-test-frameworks
description
Write unit and type tests for Formisch framework packages (frameworks/angular, frameworks/preact, frameworks/solid, frameworks/svelte, frameworks/vue, frameworks/react, frameworks/react-native). Use when adding tests for hooks/composables/runes/inject functions (useForm/createForm/injectForm, useField/injectField, useFieldArray/injectFieldArray) or components and directives (Form, Field, FieldArray, formischForm, formischField) in any framework wrapper.
metadata
{"author":"formisch","version":"1.0"}
# Writing Framework Tests Tests for `frameworks/<fw>/`. For `packages/core/` or `packages/methods/`, use `repo-source-code-test-packages`. ## Rules 1. **React is the canonical reference.** New shared tests land in `frameworks/react` first, then port. 2. **Cross-framework consistency.** Shared API (`useForm`/`createForm`/`injectForm`, `useField`, `useFieldArray`, `Field`, `FieldArray`) keeps the same `describe`/`test` names, schemas, `initialInput`, expected outputs. Only mechanical adapters differ. Test what the adapter actually exports: React Native has no `Form` component, and Angular ships directives instead of components. 3. **No framework hint in test names.** Never `'should ... (Solid-only)'`. Use a `// ...` comment above the test if framework-specific reasoning matters. 4. **No tautological assertions.** A test must be able to fail under a real source change. 5. **100% coverage** on non-excluded files. Match React's `vitest.config.ts` exclusions: `src/types`, `src/vitest`, `**/index.ts(x)`, test files. 6. **`vi.waitFor` for async, never `setTimeout(0)`.** ## File layout ``` frameworks/<fw>/ ├── vitest.config.ts # framework plugin + jsdom + setupFiles + coverage exclusions ├── package.json # `test: vitest run --typecheck`, framework testing-library devDep └── src/ ├── vitest/ │ ├── setup.ts # jest-dom + cleanup │ ├── renderHook.ts # (Vue/Svelte only) │ └── *Host.svelte # (Svelte only) shared hosts ├── hooks|primitives|composables|runes|functions/<name>/<name>.test.{ts,tsx} + .test-d.ts └── components|directives/<Name>/<Name>.test.{ts,tsx} └── *Host.test.svelte # (Svelte only) per-test hosts ``` ## Standard test set Identical `describe`/`test` names across all frameworks: | Source | `describe` blocks | Tests | | --------------------------------------- | ----------------------------------------------------------------------------- | ----------- | | `useForm` / `createForm` / `injectForm` | `initialization`, `initial validation`, `reactivity` | 4 + 2 type | | `useField` / `injectField` | `initialization`, `input updates`, `validation modes`, `element registration` | 10 + 4 type | | `useFieldArray` / `injectFieldArray` | `initialization`, `reactivity` | 6 + 4 type | | `Form` / `Field` / `FieldArray` | (flat) | 3 each | `Form` only exists where the adapter provides it: React Native has no `Form` component, and Angular ships the `[formischForm]` directive instead. The `store stability` describe tests `useMemo` across re-renders and exists only in **React and React Native**. In other frameworks omit it with a one-line `// Note: ...` comment. ## Per-framework adapters | Concern | Preact | Solid | Svelte | Vue | React | React Native | Angular | | --------------- | ------------------------- | -------------------------- | ------------------------- | ---------------------- | ------------------------ | ------------------------ | ---------------------------------- | | Hook name | `useForm` | `createForm` | `createForm` | `useForm` | `useForm` | `useForm` | `injectForm` | | Read state | `form.isValid.value` | `form.isValid` | `form.isValid` | `form.isValid` | `form.isValid` | `form.isValid` | `form.isValid()` | | Imperative set | `field.onInput(v)` | `field.onInput(v)` | `field.onInput(v)` | `field.input = v` | `field.onChange(v)` | `field.onChange(v)` | `field.setInput(v)` | | DOM input event | `fireEvent.input` | `fireEvent.input` | `fireEvent.input` | `input.setValue(v)` | `fireEvent.change` | `fireEvent.change` | `input.dispatchEvent(new Event())` | | Auto-focus prop | `autofocus` | `autofocus` | `autofocus` | `autofocus` | `autoFocus` | none | none | | Field children | function | function | `{#snippet children}` | `v-slot` / scoped slot | function | function | `*formischField` + `let field` | | Testing library | `@testing-library/preact` | `@solidjs/testing-library` | `@testing-library/svelte` | `@vue/test-utils` | `@testing-library/react` | `@testing-library/react` | `@angular/core/testing` (TestBed) | ## `renderHook` - **Preact / React / React Native:** from the testing library — `{ result: { current }, rerender, unmount }`. - **Solid:** from `@solidjs/testing-library` — `{ result, owner, cleanup }`. `result` is direct, not wrapped in `.current`. Use `createRoot` from `solid-js` only if no JSX context is needed; our primitives need an owner so `renderHook` is the default. - **Vue:** custom helper at `src/vitest/renderHook.ts` — mounts a `defineComponent({ setup() { return hook(); } })` (the canonical Vue Test Utils [TestComponent recipe](https://test-utils.vuejs.org/guide/advanced/reusability-composition)). Required because `onBeforeMount`/`computed` need component context. - **Angular:** no `renderHook`. Call the inject functions inside `TestBed.runInInjectionContext(() => …)` (or `runInInjectionContext(injector, …)` when you need to control destruction), and mount a host `@Component` with `TestBed.createComponent` for directive tests. - **Svelte:** custom helper at `src/vitest/renderHook.ts` — mounts `Hook.svelte`. Required because `onMount` and `[createAttachmentKey()]` need template context. For pure rune logic with no lifecycle, `$effect.root(() => { ... })()` in a `.test.svelte.ts` is lighter — but no covered rune in this repo qualifies. ## Async ```ts // ❌ flaky await new Promise((r) => setTimeout(r, 0)); // ✅ await vi.waitFor(() => expect(form.isValid).toBe(false)); ``` For Svelte rune updates, `flushSync()` from `svelte` synchronously drains derived/effect updates before the assertion. For Vue, `flushPromises()` from `@vue/test-utils` is fine for one-shot microtask drains; prefer `vi.waitFor` for multi-tick reactive flows. ## Type tests (`*.test-d.ts`) ```ts import * as v from 'valibot'; import { describe, expectTypeOf, test } from 'vitest'; import type { FieldStore } from '../../types/index.ts'; import { useForm } from '../useForm/index.ts'; import { useField } from './useField.ts'; describe('useField', () => { test('should narrow input type for primitive leaves', () => { const schema = v.object({ name: v.string() }); const form = useForm({ schema }); expectTypeOf(useField(form, { path: ['name'] }).input).toEqualTypeOf< string | undefined >(); }); test('should reject invalid paths', () => { const form = useForm({ schema: v.object({ name: v.string() }) }); // @ts-expect-error nonexistent field useField(form, { path: ['nonexistent'] }); }); }); ``` For Preact, wrap leaf types in `ReadonlySignal<...>`, and for Angular in its `Signal<...>` — those are the only framework-specific deltas in `.test-d.ts` files. Angular's `types/field.test-d.ts` additionally checks that a `FieldStore` stays assignable across schema and path generics, which is what its directives rely on. ## Schema types Always derive with `typeof schema`. If the schema is referenced only at the type level (host owns runtime), suppress the lint warning rather than hand-writing the valibot generic shape: ```ts // eslint-disable-next-line @typescript-eslint/no-unused-vars -- referenced only via `typeof schema` const schema = v.object({ name: v.string() }); type Schema = typeof schema; ``` ## Framework gotchas **Angular** — every reactive `field.*` / `form.*` is an Angular `Signal<T>`; read by calling it (`field.isDirty()`). Setter is `field.setInput(v)`. There is no `props` object: templates bind `[formischControl]="field"` and pass the value with `[value]="field.input() ?? ''"`. Field tests mount a host `@Component` whose template uses `*formischField="['path'] of form; let field"`, configure `TestBed` with `provideZonelessChangeDetection()`, and drive the DOM with `input.dispatchEvent(new Event('input'))` followed by `await fixture.whenStable()` and `fixture.detectChanges()`. `src/vitest/setup.ts` already calls `TestBed.initTestEnvironment` and resets the module after each test. **Preact** — every reactive `field.*` / `form.*` is a `ReadonlySignal<T>`; read with `.value`. Setter is `field.onInput(v)`. DOM events fire on `oninput`. `props.autofocus` is lowercase. **React Native** — tests render through `react-native-web` with `@testing-library/react` and jsdom, so `fireEvent.change` / `fireEvent.focus` work on the rendered `TextInput`. `field.props` has no `name` and no `autoFocus`; text is set via `onChangeText`. There is no `Form` component and no `submit` method, so submission tests call `handleSubmit`. Element cleanup differs by React version: `react-native-web` discards ref cleanup functions, so tests that assert per-element unregistration must render raw elements — see the comments in `useField.test.tsx`. **Solid** — primitive is `createForm` (not `useForm`). Methods (`insert`, `swap`) don't need `act()`. Use `class=` not `className=` in JSX. `vite-plugin-solid` ≥ 2.8.2 auto-configures vitest; don't add manual deps overrides. **Vue** — `field.props` has no `onInput`; wire DOM input via the setter: ```ts h('input', { ...field.props, value: field.input ?? '', onInput: (e: Event) => { field.input = (e.target as HTMLInputElement).value; }, }); ``` For "element removed" assertions, mount with `attachTo: document.body` and query `document.querySelector` — `wrapper.find` keeps finding stale nodes after `unmount()`. **Svelte** — runes need Svelte compilation. Three test patterns: - `Hook.svelte` via `renderHook` — default; required when source calls `onMount` or uses `[createAttachmentKey()]`. - `*Host.test.svelte` per-test components — for snippet/slot rendering of `Field`/`FieldArray`/`Form`. - `$effect.root` in a `*.test.svelte.ts` — only for pure rune logic with no lifecycle. Snippets can't run statements; forward values via `{@const _ = onField(field)}` with a comment explaining the pattern. After every test, `cleanup()` from `@testing-library/svelte` must run in `setup.ts` or `screen.getByTestId` finds duplicates. **Svelte build hygiene** — `package.json` `files` excludes `**/*.test.*`, `**/*.test-d.*`, `dist/vitest`. Verify after adding new fixtures: ```bash
Voir sur GitHub
Ce SKILL.md est tres volumineux, SkillsMP affiche donc ici seulement la premiere section. Voir sur GitHub