Agent SkillsTangleML/tangle-ui › vitest-testing

vitest-testing

GitHub

提供基于Vitest的React单元测试与组件测试规范,涵盖测试文件组织、纯函数/组件/Hook测试写法及Mock策略,用于指导前端代码质量保障。

.claude/skills/vitest-testing/SKILL.md TangleML/tangle-ui

Trigger Scenarios

编写单元测试 编写组件测试 编写Hook测试

Install

npx skills add TangleML/tangle-ui --skill vitest-testing -g -y
More Options

Non-standard path

npx skills add https://github.com/TangleML/tangle-ui/tree/master/.claude/skills/vitest-testing -g -y

Use without installing

npx skills use TangleML/tangle-ui@vitest-testing

指定 Agent (Claude Code)

npx skills add TangleML/tangle-ui --skill vitest-testing -a claude-code -g -y

安装 repo 全部 skill

npx skills add TangleML/tangle-ui --all -g -y

预览 repo 内 skill

npx skills add TangleML/tangle-ui --list

SKILL.md

Frontmatter
{
    "name": "vitest-testing",
    "description": "Vitest unit and component testing patterns. Use when writing unit tests, component tests, or hook tests."
}

Vitest Testing Patterns

Focus tests on the component/hook under test. Assume that dependencies (services, hooks, utilities) are independently tested in their own test files. Only mock what's necessary to isolate the unit under test — don't re-test dependency behavior or create elaborate mock setups for services that aren't the focus of the test.

Setup

  • Framework: Vitest with jsdom environment
  • Globals: describe, it, expect are globally available (no imports needed)
  • DOM matchers: @testing-library/jest-dom is configured in vitest-setup.js
  • Component rendering: @testing-library/react with render, screen, fireEvent, waitFor
  • No MSW: Mock functions directly with vi.mock() and vi.fn(), not HTTP interception

Test File Location

Tests are co-located next to source files:

src/utils/searchUtils.ts
src/utils/searchUtils.test.ts

src/hooks/useIOSelectionPersistence.ts
src/hooks/useIOSelectionPersistence.test.ts

src/components/shared/SuspenseWrapper.tsx
src/components/shared/SuspenseWrapper.test.tsx

Utility / Pure Function Tests

describe("formatDuration", () => {
  it("formats seconds correctly", () => {
    const start = "2024-01-01T10:00:00.000Z";
    const end = "2024-01-01T10:00:30.000Z";
    expect(formatDuration(start, end)).toBe("30s");
  });
});

Component Tests

Render with providers using a wrapper function:

const renderWithProviders = (component: React.ReactElement) => {
  return render(component, {
    wrapper: ({ children }) => (
      <ComponentSpecProvider spec={mockComponentSpec}>
        <QueryClientProvider client={queryClient}>
          {children}
        </QueryClientProvider>
      </ComponentSpecProvider>
    ),
  });
};

it("renders the toolbar", async () => {
  renderWithProviders(<RunToolbar />);
  await waitFor(() => {
    expect(screen.getByTestId("inspect-pipeline-button")).toBeInTheDocument();
  });
});

Hook Tests

Use renderHook with a wrapper for hooks that need providers:

const createWrapper = () => {
  const queryClient = new QueryClient({
    defaultOptions: { queries: { retry: false } },
  });
  return ({ children }: { children: React.ReactNode }) => (
    <QueryClientProvider client={queryClient}>{children}</QueryClientProvider>
  );
};

it("should hydrate component", async () => {
  vi.mocked(hydrateComponentReference).mockResolvedValue(mockHydratedRef);

  const { result } = renderHook(
    () => useHydrateComponentReference(mockComponent),
    { wrapper: createWrapper() },
  );

  await waitFor(() => {
    expect(result.current).toEqual(mockHydratedRef);
  });
});

Use act for state updates in hooks:

act(() => {
  result.current.preserveIOSelectionOnSpecChange(initialSpec);
});
expect(mockSetNodes).toHaveBeenCalledWith(expect.any(Function));

Mocking Patterns

Module mocks with vi.mock()

vi.mock("@/utils/localforage", () => ({
  componentExistsByUrl: vi.fn(),
  getComponentByUrl: vi.fn(),
  saveComponent: vi.fn(),
}));

vi.mock("@monaco-editor/react", () => ({
  default: ({ defaultValue }: { defaultValue: string }) => (
    <pre data-testid="monaco-mock">{defaultValue}</pre>
  ),
}));

vi.mock("@/providers/ComponentSpecProvider", () => ({
  useComponentSpec: () => ({
    componentSpec: mockSpec,
    setComponentSpec: mockSetComponentSpec,
  }),
}));

Function spies

const consoleSpy = vi.spyOn(console, "error").mockImplementation(() => {});
// ...
expect(consoleSpy).toHaveBeenCalledWith("Error:", expect.any(Error));

Global mocks

const mockFetch = vi.fn();
global.fetch = mockFetch;

mockFetch.mockResolvedValue({
  ok: true,
  text: () => Promise.resolve(yamlContent),
} as Response);

Environment variables

vi.stubEnv("VITE_GITHUB_CLIENT_ID", "test-client-id");

Mock Factories

Create inline helper functions for test data — don't over-abstract:

const createMockNode = (
  id: string,
  type: "input" | "output" | "task",
  label: string,
  selected = false,
) => ({
  id,
  type,
  position: { x: 0, y: 0 },
  data: { label },
  selected,
});

Setup / Teardown

beforeEach(() => {
  vi.clearAllMocks();
});

afterEach(() => {
  cleanup();
  queryClient.clear();
  vi.restoreAllMocks();
});

Only call queryClient.clear() when the suite uses a QueryClient.

Assertion Patterns

DOM:

expect(screen.getByTestId("submit")).toBeInTheDocument();
expect(screen.queryByTestId("hidden")).not.toBeInTheDocument();

Mocks:

expect(mockFn).toHaveBeenCalledWith(url);
expect(mockFn).toHaveBeenCalledTimes(1);
expect(mockFn).not.toHaveBeenCalled();

Partial matching:

expect(mockSave).toHaveBeenCalledWith({
  id: expect.stringMatching(/^component-\w+$/),
  createdAt: expect.any(Number),
});

User Interactions

Prefer fireEvent for simple interactions in this project:

const input = screen.getByLabelText("Name") as HTMLInputElement;
fireEvent.change(input, { target: { value: "NewName" } });
fireEvent.blur(input);
expect(mockCallback).toHaveBeenCalled();

Async Testing

await waitFor(() => {
  expect(screen.getByTestId("content")).toBeInTheDocument();
});

await expect(promise).resolves.toBe(expectedValue);

pnpm Scripts

  • pnpm test — Run all unit tests once
  • pnpm run test:coverage — Run with coverage report
  • pnpm run validate:test — Full validate + unit tests

Version History

  • d7768e8 Current 2026-09-02 20:59

Same Skill Collection

.claude/skills/accessibility/SKILL.md
.claude/skills/address-pr-comments/SKILL.md
.claude/skills/analytics-tracking/SKILL.md
.claude/skills/audit-tickets/SKILL.md
.claude/skills/docs-update/SKILL.md
.claude/skills/e2e-testing/SKILL.md
.claude/skills/list-skills/SKILL.md
.claude/skills/open-source/SKILL.md
.claude/skills/project-conventions/SKILL.md
.claude/skills/react-patterns/SKILL.md
.claude/skills/review/SKILL.md
.claude/skills/tangle-domain/SKILL.md
.claude/skills/tanstack-query/SKILL.md
.claude/skills/tanstack-router/SKILL.md
.claude/skills/typescript-standards/SKILL.md
.claude/skills/ui-primitives/SKILL.md
.claude/skills/validate/SKILL.md
.cursor/skills/playwright-testing/SKILL.md
public/agent-skills/componentYamlFormat/SKILL.md
public/agent-skills/tangleBestPractices/SKILL.md
.claude/skills/gardening/SKILL.md

Metadata

Files
0
Version
d7768e8
Hash
8cc54efa
Indexed
2026-09-02 20:59

trang chủ - Wiki
Copyright © 2011-2026 iteam. Current version is 2.155.2. UTC+08:00, 2026-09-03 01:38
浙ICP备14020137号-1 $bản đồ khách truy cập$