Examples
A few representative inputs and the output you can expect.
A simple button
Input:
src/Button.tsx
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>;
}
Generated test:
src/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} />);
});
});
Generated story:
src/Button.stories.tsx
import type { Meta, StoryObj } from "@storybook/react";
import { Button } from "./Button";
const meta: Meta<typeof Button> = {
title: "Components/Button",
component: Button,
};
export default meta;
type Story = StoryObj<typeof Button>;
export const Default: Story = {
args: { label: "Label", variant: "primary" },
};
export const Secondary: Story = {
args: { label: "Label", variant: "secondary" },
};
export const Disabled: Story = {
args: { label: "Label", disabled: true },
};
A forwardRef input
Input:
src/Input.tsx
import { forwardRef } from "react";
type InputProps = {
placeholder?: string;
type?: "text" | "email" | "password";
required?: boolean;
};
export const Input = forwardRef<HTMLInputElement, InputProps>((props, ref) => {
return <input ref={ref} {...props} />;
});
Input.displayName = "Input";
react-spec-gen detects the forwardRef wrapper, extracts the prop type, and
emits a test that uses getByRole("textbox") based on the JSX root.
A React.memo wrapper
type CardProps = { title: string; children: React.ReactNode };
function CardImpl({ title, children }: CardProps) {
return <article><h3>{title}</h3>{children}</article>;
}
export const Card = React.memo(CardImpl);
Both forwardRef and memo (and combinations of them) are supported.
Programmatic batch script
For a small monorepo helper:
scripts/gen-specs.ts
import { run } from "react-spec-gen";
import { writeFile } from "node:fs/promises";
import { glob } from "glob";
const files = await glob("packages/*/src/components/**/*.tsx", {
ignore: ["**/*.test.tsx", "**/*.stories.tsx"],
});
for (const file of files) {
const { outputs } = await run(file);
await writeFile(outputs.testFilePath, outputs.testSource);
await writeFile(outputs.storyFilePath, outputs.storySource);
console.log("✓", file);
}