import { resolve } from "node:path";
import { describe, expect, it, vi } from "vitest";
import { render, fireEvent } from "@testing-library/preact";
import { Button } from "./Button.js";
describe("Button", () => {
// ── Rendering ──
it("renders with children text", () => {
const { getByRole } = render();
expect(getByRole("button")).toHaveTextContent("Click me");
});
it("renders as a button element by default", () => {
const { getByRole } = render();
expect(getByRole("button").tagName).toBe("BUTTON");
});
it("has type='button' by default to prevent form submission", () => {
const { getByRole } = render();
expect(getByRole("button")).toHaveAttribute("type", "button");
});
it("allows type override to submit", () => {
const { getByRole } = render();
expect(getByRole("button")).toHaveAttribute("type", "submit");
});
// ── Variants ──
// ── Sizes ──
// ── Icon-only ──
// ── Interaction ──
it("calls onClick when clicked", () => {
const onClick = vi.fn();
const { getByRole } = render();
fireEvent.click(getByRole("button"));
expect(onClick).toHaveBeenCalledTimes(1);
});
it("does not call onClick when disabled", () => {
const onClick = vi.fn();
const { getByRole } = render(
);
fireEvent.click(getByRole("button"));
expect(onClick).not.toHaveBeenCalled();
});
// ── Disabled state ──
it("sets disabled attribute when disabled", () => {
const { getByRole } = render();
expect(getByRole("button")).toBeDisabled();
});
it("sets aria-disabled when disabled", () => {
const { getByRole } = render();
expect(getByRole("button")).toHaveAttribute("aria-disabled", "true");
});
// ── Loading state ──
it("shows loading state", () => {
const { getByRole } = render();
const btn = getByRole("button");
expect(btn.className).toContain("strand-btn--loading");
expect(btn).toHaveAttribute("aria-busy", "true");
});
it("disables interaction when loading", () => {
const onClick = vi.fn();
const { getByRole } = render(
);
fireEvent.click(getByRole("button"));
expect(onClick).not.toHaveBeenCalled();
});
it("renders spinner element when loading", () => {
const { container } = render();
expect(container.querySelector(".strand-btn__spinner")).toBeTruthy();
});
// ── Accessibility ──
it("is focusable via keyboard", () => {
const { getByRole } = render();
const btn = getByRole("button");
btn.focus();
expect(document.activeElement).toBe(btn);
});
it("is not focusable when disabled", () => {
const { getByRole } = render();
const btn = getByRole("button");
expect(btn).toBeDisabled();
});
it("supports aria-label", () => {
const { getByRole } = render();
expect(getByRole("button")).toHaveAttribute("aria-label", "Close dialog");
});
it("forwards additional props", () => {
const { getByRole } = render(
);
expect(getByRole("button")).toHaveAttribute("id", "my-btn");
});
// ── Custom className ──
// ── Full width ──
// ── Anchor (link styled as a button) ──
it("renders an anchor with the button classes when href is given", () => {
const { getByRole } = render();
const link = getByRole("link");
expect(link.tagName).toBe("A");
expect(link).toHaveAttribute("href", "/go");
expect(link.className).toContain("strand-btn");
expect(link.className).toContain("strand-btn--primary");
});
it("wraps anchor children in the strand-btn__content span", () => {
const { getByRole } = render();
expect(
getByRole("link").querySelector(".strand-btn__content"),
).toBeTruthy();
});
it("forwards anchor attributes (target, rel, download)", () => {
const { getByRole } = render(
,
);
const link = getByRole("link");
expect(link).toHaveAttribute("download", "f.ics");
expect(link).toHaveAttribute("target", "_blank");
});
it("drops href and marks aria-disabled when a disabled anchor", () => {
const { getByText } = render(
,
);
const link = getByText("Go").closest("a");
expect(link).not.toHaveAttribute("href");
expect(link).toHaveAttribute("aria-disabled", "true");
});
});
import { snapshotFixtures } from "../../test/snapshot.js";
import { snapshotStylesheet } from "../../test/stylesheet.js";
import { fixtures } from "./Button.fixtures.js";
snapshotFixtures(Button, fixtures);
snapshotStylesheet(resolve(__dirname, "./Button.css"));