// @vitest-environment jsdom // // S5: the shared ); expect(screen.getByRole("button", { name: "Go" }).getAttribute("type")).toBe("button"); }); it("applies the variant paint recipe", () => { render( , ); const btn = screen.getByRole("button", { name: "Delete" }); expect(btn.className).toContain("btn-danger"); expect(btn.className).toContain("py-1.5"); }); it("exposes an accessible name for an icon-only button while dropping the text", () => { render( , ); const btn = screen.getByRole("button", { name: "Block channel" }); expect(btn.getAttribute("title")).toBe("Block channel"); expect(btn.textContent).not.toContain("this text is dropped"); expect(btn.querySelector("[data-testid=ic]")).not.toBeNull(); }); it("loading disables the button, marks aria-busy, and blocks clicks", () => { const onClick = vi.fn(); render( , ); const btn = screen.getByRole("button", { name: "Save" }) as HTMLButtonElement; expect(btn.disabled).toBe(true); expect(btn.getAttribute("aria-busy")).toBe("true"); // the leading icon is swapped for a spinner expect(btn.querySelector("[data-testid=ic]")).toBeNull(); fireEvent.click(btn); expect(onClick).not.toHaveBeenCalled(); }); it("loading disables even when an explicit disabled={false} is passed", () => { // Regression: `disabled ?? loading` let an explicit boolean disabled shadow loading, so a // call-site with `disabled={!valid} loading={pending}` (valid → disabled=false) stayed // clickable mid-mutation and double-submitted. The contract is: loading always disables. const onClick = vi.fn(); render( , ); const btn = screen.getByRole("button", { name: "Create" }) as HTMLButtonElement; expect(btn.disabled).toBe(true); fireEvent.click(btn); expect(onClick).not.toHaveBeenCalled(); }); it("fullWidth adds the w-full utility", () => { render(); expect(screen.getByRole("button", { name: "Wide" }).className).toContain("w-full"); }); });