import { render, screen } from "@testing-library/react"; import userEvent from "@testing-library/user-event"; import { describe, expect, it, vi } from "vitest"; import { Button } from "./button"; import { setReducedMotionPreference } from "../test/a11y"; describe("Button", () => { it("renders a native button with root and label slots", () => { render(); const button = screen.getByRole("button", { name: "Save changes" }); expect(button).toHaveAttribute("data-slot", "root"); expect(button).toHaveAttribute("data-variant", "secondary"); expect(button).toHaveAttribute("type", "button"); expect(screen.getByText("Save changes")).toHaveAttribute("data-slot", "label"); }); it("disables interaction and exposes loading hooks when loading", async () => { const user = userEvent.setup(); const onClick = vi.fn(); render( ); const button = screen.getByRole("button", { name: "Saving" }); await user.click(button); expect(button).toBeDisabled(); expect(button).toHaveAttribute("data-loading", ""); expect(button.querySelector('[data-slot="icon"]')).toBeInTheDocument(); expect(onClick).not.toHaveBeenCalled(); }); it("supports asChild rendering without forcing button semantics", () => { render( ); const link = screen.getByRole("link", { name: "Release notes" }); expect(link).toHaveAttribute("data-slot", "root"); expect(link).toHaveAttribute("data-variant", "ghost"); expect(link).not.toHaveAttribute("type"); }); it("preserves the loading contract when reduced motion is preferred", () => { setReducedMotionPreference(true); render(); const button = screen.getByRole("button", { name: "Saving" }); expect(button).toBeDisabled(); expect(button).toHaveAttribute("data-loading", ""); expect(button.querySelector('[data-slot="icon"]')).toBeInTheDocument(); }); });