Agent Skillsopen-circle/formisch › repo-source-code-test-packages

repo-source-code-test-packages

GitHub

指导为 Formisch 的 core 和 methods 包编写高质量 TypeScript 单元测试。涵盖文件结构、createTestStore 辅助函数使用及类型安全规范,明确排除框架相关测试。

.agents/skills/repo-source-code-test-packages/SKILL.md open-circle/formisch

触发场景

为 packages/core 或 packages/methods 创建新单元测试 修复这些包中测试文件的类型错误 为核心或方法功能增加测试覆盖率

安装

npx skills add open-circle/formisch --skill repo-source-code-test-packages -g -y
更多选项

非标准路径

npx skills add https://github.com/open-circle/formisch/tree/main/.agents/skills/repo-source-code-test-packages -g -y

不安装直接使用

npx skills use open-circle/formisch@repo-source-code-test-packages

指定 Agent (Claude Code)

npx skills add open-circle/formisch --skill repo-source-code-test-packages -a claude-code -g -y

安装 repo 全部 skill

npx skills add open-circle/formisch --all -g -y

预览 repo 内 skill

npx skills add open-circle/formisch --list

SKILL.md

Frontmatter
{
    "name": "repo-source-code-test-packages",
    "metadata": {
        "author": "formisch",
        "version": "2.0"
    },
    "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."
}

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:

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:

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:

// @vitest-environment jsdom
import { describe, expect, test } from 'vitest';

Type-Safe Patterns

Accessing Union Type Properties

InternalFieldStore is a union type:

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:

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:

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 variableconst itemsStore = store.children.items;
  2. Assert the kindexpect(itemsStore.kind).toBe('array'); (test fails if wrong)
  3. Narrow with ifif (itemsStore.kind === 'array') { ... } (TypeScript narrows type)

Required Imports

import type {
  InternalArrayStore,
  InternalFormStore,
  InternalObjectStore,
} from '../../types/index.ts';

Complete Example

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:

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:

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:

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:

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
// ✅ Good
test('should focus first error field when shouldFocus is true', () => {});

// ❌ Bad
test('focus test', () => {});

Running Tests

Replace <pkg> with core or methods:

# 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

const fieldStore = store.children.fieldName;
expect(fieldStore.kind).toBe('array');
if (fieldStore.kind === 'array') {
  expect(fieldStore.children).toHaveLength(2);
}

DOM Element Pattern

const input = document.createElement('input');
const spy = vi.spyOn(input, 'focus');
store.children.name.elements = [input];

Issue Helper Pattern

validationIssue('Error message', [objectPath('field'), arrayPath(0)]);

版本历史

  • 09acd5e 当前 2026-07-24 11:30

同 Skill 集合

.agents/skills/repo-prepare-release/SKILL.md
.agents/skills/repo-source-code-document/SKILL.md
.agents/skills/repo-source-code-review/SKILL.md
.agents/skills/repo-source-code-test-frameworks/SKILL.md
.agents/skills/repo-structure-navigate/SKILL.md
.agents/skills/repo-website-api-create/SKILL.md
.agents/skills/repo-website-api-review/SKILL.md
.agents/skills/repo-website-api-update/SKILL.md
.agents/skills/repo-website-guide-create/SKILL.md
website/public/.well-known/agent-skills/formisch/SKILL.md

元信息

文件数
0
版本
865212e
Hash
d90f7c05
收录时间
2026-07-24 11:30

首页 - Wiki
Copyright © 2011-2026 iteam. Current version is 2.155.2. UTC+08:00, 2026-08-08 06:03
浙ICP备14020137号-1 $访客地图$