Generate Vitest + RTL tests and Storybook CSF 3 stories from your React components.
(npm package name:
react-spec-gen— GitHub repository:react-intel. Both refer to the same project.)
Run it on a .tsx component — get a Vitest test file and a Storybook story file written next to the source.
Status: stable — tested on real-world codebases.
- Eliminates repetitive test and story boilerplate
- Generates context-aware outputs from your component structure
- Works instantly via
npx(no setup) - Optional AI enhancement with safe fallbacks
- No network calls by default — fully offline, deterministic, reproducible
- Enforces consistent patterns across teams
react-spec-gen uses static analysis (AST) to extract component structure, infer prop values, and generate tests and stories. Optional AI enhancement improves edge cases and realism without overriding deterministic output.
Run ad-hoc with npx:
npx react-spec-gen src/components/Button.tsxOr install as a dev dependency:
npm install -D react-spec-genExpected output:
✓ Component: Button
✓ Wrote Button.test.tsx
✓ Wrote Button.stories.tsx
| Flag | Description |
|---|---|
-y, --yes |
Skip overwrite prompts |
--ai |
Enhance inferred values and edge cases using an AI provider (optional) |
The CLI prints a verification checklist after writing — review inferred prop values, assertion meaningfulness, and event-handler coverage before committing.
Given a Button.tsx like:
type ButtonProps = {
label: string;
variant?: "primary" | "secondary";
disabled?: boolean;
onClick?: () => void;
};
export function Button({ label, variant = "primary", disabled, onClick }: ButtonProps) {
return <button disabled={disabled} onClick={onClick}>{label}</button>;
}react-spec-gen produces Button.test.tsx:
// Generated by react-spec-gen — review before committing.
import { describe, it, expect, vi } from "vitest";
import { render, screen } from "@testing-library/react";
import { Button } from "./Button";
describe("Button", () => {
const baseProps = {
label: "Label",
variant: "primary" as const,
disabled: false,
onClick: vi.fn(),
};
it("renders with default props", () => {
render(<Button {...baseProps} />);
expect(screen.getByRole("button")).toBeDefined();
});
it("when disabled is true", () => {
const props = { ...baseProps, disabled: true };
render(<Button {...props} />);
});
it("when variant is \"secondary\"", () => {
const props = { ...baseProps, variant: "secondary" as const };
render(<Button {...props} />);
});
});…and Button.stories.tsx with a Default story plus one variant per edge case.
Simplest call:
import { run } from "react-spec-gen";
const { outputs } = await run("./Button.tsx");
console.log(outputs.testSource);
console.log(outputs.storySource);With the bundled mock AI enhancer:
import { run, buildEnhancer, MockProvider } from "react-spec-gen";
const { model, outputs } = await run("./Button.tsx", {
enhancer: buildEnhancer(new MockProvider()),
});--ai is opt-in and pluggable. The bundled MockProvider produces realistic strings deterministically (no network calls, no API key). Real providers (Anthropic, Ollama) are planned.
The enhancer:
- Times out at 10s and falls back to non-AI output (warning, never throws)
- Validates suggestions against AST-extracted props (drops references to unknown props)
- Is purely additive — never overwrites statically inferred values
- Function components, arrow components,
React.FC<Props> - Default and named exports
- Props from inline types,
typealiases,interface(incl. same-fileextends/&) React.forwardRefandReact.memowrappers (incl. nested combinations)- Realistic value inference for strings, numbers, booleans, functions, literal & non-literal unions,
ReactNode - JSX root-element detection with implicit ARIA role mapping (
getByRole(...)assertions) - Edge cases: boolean flags, optional props, union variants
- Cross-file prop type imports — emits a warning and a minimal test
- Discriminated unions (e.g.
{kind:"a"} | {kind:"b"}) — emits a warning and a minimal test - Generic components — render-prop signatures may need manual review
- Files with multiple exported components — picks the default export, falls back to the first
- Generated tests target Vitest. Jest support is on the roadmap.