Skip to main content

repo-source-code-test-packages

Write unit tests for Formisch packages (packages/core and packages/methods) with proper TypeScript types. Use when creating new tests, fixing type errors in tests, or adding test coverage for core/methods functions.

Quellinformationen

Repository
open-circle/formisch
Letzte Quellaktivität
8. Mai 2026 um 17:26
Erkannte Sprache von SKILL.md
Englisch
Sterne
1.200
Forks
36

Installationsoptionen

Standardmäßig ist der Prompt ausgewählt, der zuerst die Quelle prüft. Sie können zu einem direkten Befehl wechseln oder eine lokale Kopie herunterladen.

Quelldateien prüfen

Lesen Sie SKILL.md und alle von SkillsMP angezeigten Begleitdateien, bevor Sie sich für eine Installation entscheiden.

SKILL.md wird angezeigt

SKILL.md
Quellanweisungen · Schreibgeschützte Vorschau
name
repo-source-code-test-packages
description
Write unit tests for Formisch packages (packages/core and packages/methods) with proper TypeScript types. Use when creating new tests, fixing type errors in tests, or adding test coverage for core/methods functions.
metadata
{"author":"formisch","version":"2.0"}
# Writing Unit Tests Guide for writing high-quality unit tests in `packages/core/` and `packages/methods/` with proper TypeScript types. Both packages share the same conventions: `createTestStore` helper, `objectPath` / `arrayPath` / `validationIssue` helpers, and the type-guard pattern for narrowing union store types. For tests in `frameworks/<framework>/` (hooks, composables, runes, components), use the **`repo-source-code-test-frameworks`** skill instead — those have framework-specific concerns (DOM testing, signal/rune reactivity, snippets/slots, cross-framework consistency) that are out of scope here. ## When to Use This Guide - Creating new unit tests for functions in `packages/core/src/` or `packages/methods/src/` - Fixing type errors in existing tests in those packages - Adding test coverage for new core or method features ## Core Principles 1. **No type casts** — Get types right, don't use `as` assertions 2. **Type guards only when needed** — Use `if` blocks to narrow types only when TypeScript reports an error 3. **Real DOM elements** — Use `document.createElement()`, not mock objects ## Test File Structure ### File Location Tests live next to their implementation in either package: ``` packages/core/src/ ├── form/ │ └── validateFormInput/ │ ├── validateFormInput.ts │ └── validateFormInput.test.ts ├── field/ │ └── getFieldInput/ │ ├── getFieldInput.ts │ └── getFieldInput.test.ts packages/methods/src/ ├── insert/ │ ├── insert.ts │ └── insert.test.ts ├── validate/ │ ├── validate.ts │ └── validate.test.ts ``` ### Basic Template The global setup file (`src/vitest/setup.ts`) automatically calls `mockFramework()` and sets up `beforeEach` with `resetIdCounter()`. Use the shared `createTestStore` helper: ```typescript import * as v from 'valibot'; import { describe, expect, test } from 'vitest'; import { createTestStore } from '../../vitest/index.ts'; describe('functionName', () => { test('should do something', () => { const store = createTestStore(v.object({ name: v.string() })); // Test logic }); test('should handle initial input', () => { const store = createTestStore(v.object({ name: v.string() }), { initialInput: { name: 'John' }, }); // Test logic }); }); ``` The `createTestStore` helper accepts a schema and an optional config object: ```typescript createTestStore(schema, { initialInput?: unknown, // Initial form values validate?: ValidationMode, // 'initial' | 'touch' | 'input' | 'change' | 'blur' | 'submit' revalidate?: ValidationMode, // Same options except 'initial' issues?: [...], // Mock validation issues }); ``` ### JSDOM Environment For tests that need DOM APIs (focus, createElement, etc.), add the directive: ```typescript // @vitest-environment jsdom import { describe, expect, test } from 'vitest'; ``` ## Type-Safe Patterns ### Accessing Union Type Properties `InternalFieldStore` is a union type: ```typescript type InternalFieldStore = | InternalArrayStore // has: kind: 'array', children: InternalFieldStore[] | InternalObjectStore // has: kind: 'object', children: Record<string, InternalFieldStore> | InternalValueStore; // has: kind: 'value', NO children property ``` **When to use type guards:** Only use `if` blocks for type narrowing when TypeScript reports an error. **❌ Bad — Type cast:** ```typescript expect(store.children.items.children[0].input.value).toBe('a'); // Error: Property 'children' does not exist on type 'InternalFieldStore' // Wrong fix: expect( (store.children.items as InternalArrayStore).children[0].input.value ).toBe('a'); ``` **✅ Good — Type guard with assertion:** ```typescript const itemsStore = store.children.items; expect(itemsStore.kind).toBe('array'); if (itemsStore.kind === 'array') { expect(itemsStore.children[0].input.value).toBe('a'); } ``` The pattern: 1. **Extract to variable** — `const itemsStore = store.children.items;` 2. **Assert the kind** — `expect(itemsStore.kind).toBe('array');` (test fails if wrong) 3. **Narrow with if** — `if (itemsStore.kind === 'array') { ... }` (TypeScript narrows type) ### Required Imports ```typescript import type { InternalArrayStore, InternalFormStore, InternalObjectStore, } from '../../types/index.ts'; ``` ### Complete Example ```typescript test('should initialize array schema', () => { const store = createTestStore(v.object({ items: v.array(v.string()) }), { initialInput: { items: ['a', 'b'] }, }); const itemsStore = store.children.items; expect(itemsStore.kind).toBe('array'); if (itemsStore.kind === 'array') { expect(itemsStore.children).toHaveLength(2); expect(itemsStore.children[0].input.value).toBe('a'); expect(itemsStore.children[1].input.value).toBe('b'); } }); ``` ## DOM Element Mocking ### ❌ Bad — Mock object with cast: ```typescript const mockFocus = vi.fn(); store.children.name.elements = [{ focus: mockFocus } as HTMLElement]; // Error: Type '{ focus: Mock }' is not assignable to type 'FieldElement' ``` ### ✅ Good — Real DOM element with spy: ```typescript const inputElement = document.createElement('input'); const mockFocus = vi.spyOn(inputElement, 'focus'); store.children.name.elements = [inputElement]; await validateFormInput(store, { shouldFocus: true }); expect(mockFocus).toHaveBeenCalledOnce(); ``` **Note:** Requires `// @vitest-environment jsdom` at file top. ## Valibot Issue Helpers When testing validation, create properly typed issue helpers: ```typescript function objectPath(key: string, value: unknown = ''): v.ObjectPathItem { return { type: 'object', origin: 'value', input: {}, key, value }; } function arrayPath(key: number, value: unknown = ''): v.ArrayPathItem { return { type: 'array', origin: 'value', input: [], key, value }; } function validationIssue( message: string, path?: [v.IssuePathItem, ...v.IssuePathItem[]] ): v.BaseIssue<unknown> { return { kind: 'validation', type: 'check', input: '', expected: null, received: 'unknown', message, path, }; } ``` **Note:** The `path` type is `[v.IssuePathItem, ...v.IssuePathItem[]]` (tuple with at least one item). ## Common Type Errors and Fixes ### Error: Property 'children' does not exist ``` Property 'children' does not exist on type 'InternalFieldStore'. ``` **Fix:** Use type guard pattern (see above). ### Error: Type is not assignable to 'FieldElement' ``` Type '{ focus: Mock }' is not assignable to type 'FieldElement'. ``` **Fix:** Use real DOM elements with `document.createElement()`. ### Error: Type not assignable with exactOptionalPropertyTypes ``` Type 'IssuePathItem[]' is not assignable to type '[IssuePathItem, ...IssuePathItem[]]'. ``` **Fix:** Use tuple type `[v.IssuePathItem, ...v.IssuePathItem[]]` for path arrays. ## Test Organization ### Describe Blocks Group tests by functionality: ```typescript describe('functionName', () => { describe('basic behavior', () => { test('should handle simple case', () => {}); }); describe('error handling', () => { test('should return errors for invalid input', () => {}); }); describe('nested fields', () => { test('should handle nested objects', () => {}); }); }); ``` ### Test Naming - Start with "should" - Describe the expected behavior - Be specific about the scenario ```typescript // ✅ Good test('should focus first error field when shouldFocus is true', () => {}); // ❌ Bad test('focus test', () => {}); ``` ## Running Tests Replace `<pkg>` with `core` or `methods`: ```bash # Run all tests pnpm -C packages/<pkg> test # Run tests in watch mode pnpm -C packages/<pkg> test --watch # Run specific test file pnpm -C packages/<pkg> test validateFormInput # Run with coverage pnpm -C packages/<pkg> test --coverage ``` ## Checklist Before committing tests: - [ ] No type casts (`as SomeType`) — use type guards instead - [ ] All imports use `.ts` extension - [ ] Type imports use `import type { ... }` - [ ] DOM tests have `// @vitest-environment jsdom` directive - [ ] DOM elements created with `document.createElement()` - [ ] Union types narrowed with `expect(...).toBe(...)` + `if` guards - [ ] All tests pass: `pnpm test` - [ ] No lint errors: `pnpm lint` ## Quick Reference ### Type Guard Pattern ```typescript const fieldStore = store.children.fieldName; expect(fieldStore.kind).toBe('array'); if (fieldStore.kind === 'array') { expect(fieldStore.children).toHaveLength(2); } ``` ### DOM Element Pattern ```typescript const input = document.createElement('input'); const spy = vi.spyOn(input, 'focus'); store.children.name.elements = [input]; ``` ### Issue Helper Pattern ```typescript validationIssue('Error message', [objectPath('field'), arrayPath(0)]); ```
Auf GitHub ansehen