A testing utility for Preact apps using Rspack, enforcing correct import patterns and configurations.

Install

mkdir -p .claude/skills/rstest && curl -L -o skill.zip "https://agentskills.codes/api/skills/download/19064" && unzip -o skill.zip -d .claude/skills/rstest && rm skill.zip

Installs to .claude/skills/rstest

Activation

This is the description your AI agent reads to decide when to run this skill — the better it matches your request, the more reliably it fires.

Rstest patterns for Rspack-native unit testing with Preact. Trigger: When writing tests with @rstest/core, testing-library/preact, or configuring rstest.config.ts.
163 chars✓ has a “when” trigger
Intermediate

Key capabilities

  • Write unit tests using `@rstest/core` for Preact components
  • Configure `rstest.config.ts` for Rspack environments
  • Set up `rstest.setup.ts` for automatic cleanup and matcher extension
  • Mock modules and functions using `vi` from `@rstest/core`
  • Handle asynchronous testing with `findBy*` and `waitFor`

How it works

This skill standardizes Preact unit testing in Rspack environments by enforcing the use of `@rstest/core` and `@testing-library/preact`. It provides configuration patterns for test environments and setup files.

Inputs & outputs

You give it
Preact component code and test specifications
You get back
Test results and coverage reports

When to use rstest

  • Writing Preact component tests
  • Configuring test environments
  • Managing mock APIs

About this skill

Rstest Skill

Critical Rules

  • ALWAYS import from @rstest/core — NEVER from vitest or jest
  • Use @testing-library/preact — NEVER @testing-library/react
  • JSX runtime is Preact: "jsxImportSource": "preact" in tsconfig — NEVER React
  • cleanup() is called automatically via setup file — but know it must be in afterEach
  • vi is the mock API (same as vitest) — imported from @rstest/core
  • globals: true in config means describe/it/expect are available globally, but prefer explicit imports

Config Pattern

// rstest.config.ts
import { pluginPreact } from "@rsbuild/plugin-preact";
import { pluginSass } from "@rsbuild/plugin-sass";
import { defineConfig } from "@rstest/core";

export default defineConfig({
  globals: true,
  testEnvironment: "jsdom",
  setupFiles: ["./rstest.setup.ts"],
  plugins: [pluginPreact(), pluginSass()],
  exclude: ["node_modules", "automation_test/**"],
});

Setup File Pattern

// rstest.setup.ts
import { afterEach, expect } from "@rstest/core";
import * as jestDomMatchers from "@testing-library/jest-dom/matchers";
import { cleanup } from "@testing-library/preact";

afterEach(() => cleanup());
expect.extend(jestDomMatchers);

Component Test Pattern

import { describe, it, expect, beforeEach } from "@rstest/core";
import { render, screen, fireEvent } from "@testing-library/preact";
import { Counter } from "./Counter";

describe("Counter", () => {
  it("renders initial count", () => {
    render(<Counter initialCount={0} />);
    expect(screen.getByText("Count: 0")).toBeInTheDocument();
  });

  it("increments on button click", () => {
    render(<Counter initialCount={0} />);
    fireEvent.click(screen.getByRole("button", { name: /increment/i }));
    expect(screen.getByText("Count: 1")).toBeInTheDocument();
  });
});

Async Test Pattern

import { describe, it, expect } from "@rstest/core";
import { render, screen, waitFor } from "@testing-library/preact";
import { UserProfile } from "./UserProfile";

describe("UserProfile", () => {
  it("loads user data asynchronously", async () => {
    render(<UserProfile userId="1" />);

    // findBy* = getBy* + waitFor (auto-retries)
    const name = await screen.findByText("John Doe");
    expect(name).toBeInTheDocument();
  });

  it("shows error state", async () => {
    render(<UserProfile userId="invalid" />);

    await waitFor(() => {
      expect(screen.getByRole("alert")).toBeInTheDocument();
    });
  });
});

Mocking Pattern

import { describe, it, expect, vi, beforeEach } from "@rstest/core";

// Module mock
vi.mock("../services/api", () => ({
  fetchUser: vi.fn().mockResolvedValue({ id: "1", name: "John" }),
}));

// Spy on method
import * as api from "../services/api";

describe("mocking", () => {
  beforeEach(() => {
    vi.clearAllMocks();
  });

  it("calls fetchUser with correct id", async () => {
    const spy = vi.spyOn(api, "fetchUser").mockResolvedValue({ id: "1", name: "Jane" });

    await api.fetchUser("1");

    expect(spy).toHaveBeenCalledWith("1");
    expect(spy).toHaveBeenCalledTimes(1);
  });

  it("uses vi.fn for callbacks", () => {
    const onChange = vi.fn();
    render(<Input onChange={onChange} />);

    fireEvent.input(screen.getByRole("textbox"), { target: { value: "hello" } });

    expect(onChange).toHaveBeenCalledWith("hello");
  });
});

Commands

bun run test             # run all tests
bun run test --watch     # watch mode
bun run test --coverage  # coverage report

Keywords

rstest, testing, preact, unit test, jsdom, testing-library, @rstest/core, vitest-compatible, component test, mocking, vi.mock, vi.fn, vi.spyOn, waitFor, findBy, fireEvent, screen

When not to use it

  • When writing tests with `vitest` or `jest` directly
  • When using `@testing-library/react` instead of `@testing-library/preact`
  • When the JSX runtime is React instead of Preact

Limitations

  • Must import from `@rstest/core`
  • Must use `@testing-library/preact`
  • JSX runtime must be Preact

How it compares

This workflow uses `@rstest/core` and `@testing-library/preact` for testing, providing a specific setup for Preact components in Rspack, unlike a generic testing approach that might use different frameworks or libraries.

Compared to similar skills

rstest side by side with the closest alternatives in the catalog.

SkillInstallsUpdatedSafetyDifficulty
rstest (this skill)03moReviewIntermediate
dependency-upgrade264moReviewIntermediate
vitest416moNo flagsIntermediate
browser-tools68moReviewIntermediate

Try saying

Example prompts that trigger this skill in your AI assistant.

Search skills

Search the agent skills registry