155 lines
5.2 KiB
TypeScript
155 lines
5.2 KiB
TypeScript
import { TranslationProvider } from "@suaveplan/react-i18n";
|
||
import { describe, expect, it } from "@suaveplan/testing/runner";
|
||
import { renderComponent } from "@suaveplan/testing-dom";
|
||
import { createRef, useState } from "react";
|
||
import { createTestI18n } from "../../../tests/setup/test-i18n.js";
|
||
import { ThemeProvider } from "../../theme/theme-provider/theme-provider.js";
|
||
import { lightTheme } from "../../theme/themes/light.js";
|
||
import { Toggle } from "./toggle.js";
|
||
import { toggleVariants } from "./toggle.variants.js";
|
||
|
||
async function withProviders(
|
||
node: React.ReactElement
|
||
): Promise<React.ReactElement> {
|
||
const i18n = await createTestI18n({ locale: "en-GB" });
|
||
return (
|
||
<TranslationProvider i18n={i18n}>
|
||
<ThemeProvider themes={[lightTheme]} defaultTheme="light">
|
||
{node}
|
||
</ThemeProvider>
|
||
</TranslationProvider>
|
||
);
|
||
}
|
||
|
||
describe("<Toggle>", () => {
|
||
it("renders a button with default classes", { timeout: 5000 }, async () => {
|
||
const tree = await withProviders(<Toggle aria-label="bold">B</Toggle>);
|
||
const { getByRole } = renderComponent(tree);
|
||
const t = getByRole("button");
|
||
expect(t.tagName).toBe("BUTTON");
|
||
expect(t.className).toContain("rounded-md");
|
||
});
|
||
|
||
it("forwards refs to the radix root", { timeout: 5000 }, async () => {
|
||
const ref = createRef<HTMLButtonElement>();
|
||
const tree = await withProviders(
|
||
<Toggle ref={ref} aria-label="ref">
|
||
R
|
||
</Toggle>
|
||
);
|
||
renderComponent(tree);
|
||
expect(ref.current).not.toBeNull();
|
||
expect(ref.current?.tagName).toBe("BUTTON");
|
||
});
|
||
|
||
it("toggles pressed state when clicked (controlled)", {
|
||
timeout: 5000,
|
||
}, async () => {
|
||
function Controlled(): React.ReactElement {
|
||
const [pressed, setPressed] = useState(false);
|
||
return (
|
||
<Toggle
|
||
aria-label="t"
|
||
pressed={pressed}
|
||
onPressedChange={setPressed}
|
||
>
|
||
T
|
||
</Toggle>
|
||
);
|
||
}
|
||
const tree = await withProviders(<Controlled />);
|
||
const { getByRole, user } = renderComponent(tree);
|
||
const t = getByRole("button");
|
||
expect(t.getAttribute("aria-pressed")).toBe("false");
|
||
await user.click(t);
|
||
expect(t.getAttribute("aria-pressed")).toBe("true");
|
||
});
|
||
|
||
it("activates via Enter key", { timeout: 5000 }, async () => {
|
||
let count = 0;
|
||
const tree = await withProviders(
|
||
<Toggle
|
||
aria-label="enter"
|
||
onPressedChange={() => {
|
||
count += 1;
|
||
}}
|
||
>
|
||
E
|
||
</Toggle>
|
||
);
|
||
const { getByRole, user } = renderComponent(tree);
|
||
const t = getByRole("button");
|
||
t.focus();
|
||
await user.keyboard("[Enter]");
|
||
expect(count).toBe(1);
|
||
});
|
||
|
||
it("respects the disabled prop", { timeout: 5000 }, async () => {
|
||
let count = 0;
|
||
const tree = await withProviders(
|
||
<Toggle
|
||
aria-label="dis"
|
||
disabled
|
||
onPressedChange={() => {
|
||
count += 1;
|
||
}}
|
||
>
|
||
D
|
||
</Toggle>
|
||
);
|
||
const { getByRole, user } = renderComponent(tree);
|
||
const t = getByRole("button");
|
||
expect(t.hasAttribute("disabled")).toBe(true);
|
||
await user.click(t);
|
||
expect(count).toBe(0);
|
||
});
|
||
|
||
it("renders every variant × size combination from cva", {
|
||
timeout: 5000,
|
||
}, () => {
|
||
const variants = ["default", "outline"] as const;
|
||
const sizes = ["sm", "default", "lg"] as const;
|
||
for (const v of variants) {
|
||
for (const s of sizes) {
|
||
const cls = toggleVariants({ variant: v, size: s });
|
||
expect(typeof cls).toBe("string");
|
||
expect(cls.length).toBeGreaterThan(0);
|
||
}
|
||
}
|
||
});
|
||
|
||
it("applies the outline variant", { timeout: 5000 }, async () => {
|
||
const tree = await withProviders(
|
||
<Toggle aria-label="o" variant="outline">
|
||
O
|
||
</Toggle>
|
||
);
|
||
const { getByRole } = renderComponent(tree);
|
||
const t = getByRole("button");
|
||
expect(t.className).toContain("border");
|
||
expect(t.className).toContain("border-input");
|
||
});
|
||
|
||
it("merges custom className alongside variant classes", {
|
||
timeout: 5000,
|
||
}, async () => {
|
||
const tree = await withProviders(
|
||
<Toggle aria-label="cls" className="custom-extra-class">
|
||
C
|
||
</Toggle>
|
||
);
|
||
const { getByRole } = renderComponent(tree);
|
||
const t = getByRole("button");
|
||
expect(t.className).toContain("custom-extra-class");
|
||
expect(t.className).toContain("rounded-md");
|
||
});
|
||
|
||
it("exposes toggleVariants as a callable resolver", {
|
||
timeout: 5000,
|
||
}, () => {
|
||
const cls = toggleVariants({ variant: "default", size: "default" });
|
||
expect(typeof cls).toBe("string");
|
||
expect(cls.length).toBeGreaterThan(0);
|
||
});
|
||
});
|