Mats Bosson ddc5aa3d7f Tabs component
Implements accessible Tabs with List, Tab, Panel parts. Supports controlled/uncontrolled value, keyboard navigation (ArrowRight/Left/Home/End), automatic/manual activation mode, and full WAI-ARIA compliance (role=tablist/tab/tabpanel, aria-selected strings, aria-controls/labelledby wiring).
2026-03-29 08:26:03 +07:00

111 lines
3.6 KiB
TypeScript

// packages/core/tests/components/tabs/tabs.test.tsx
import { fireEvent, render, screen } from "@solidjs/testing-library";
import { describe, expect, it } from "vitest";
import { Tabs } from "../../../src/components/tabs/index";
describe("Tabs", () => {
it("first tab is selected when defaultValue matches", () => {
render(() => (
<Tabs defaultValue="a">
<Tabs.List>
<Tabs.Tab value="a">Tab A</Tabs.Tab>
<Tabs.Tab value="b">Tab B</Tabs.Tab>
</Tabs.List>
<Tabs.Panel value="a">Panel A</Tabs.Panel>
<Tabs.Panel value="b">Panel B</Tabs.Panel>
</Tabs>
));
expect(screen.getByText("Tab A").getAttribute("aria-selected")).toBe("true");
expect(screen.getByText("Tab B").getAttribute("aria-selected")).toBe("false");
});
it("clicking a tab activates it", () => {
render(() => (
<Tabs defaultValue="a">
<Tabs.List>
<Tabs.Tab value="a">Tab A</Tabs.Tab>
<Tabs.Tab value="b">Tab B</Tabs.Tab>
</Tabs.List>
<Tabs.Panel value="a">Panel A</Tabs.Panel>
<Tabs.Panel value="b">Panel B</Tabs.Panel>
</Tabs>
));
fireEvent.click(screen.getByText("Tab B"));
expect(screen.getByText("Tab B").getAttribute("aria-selected")).toBe("true");
expect(screen.getByText("Tab A").getAttribute("aria-selected")).toBe("false");
});
it("inactive panels are hidden", () => {
render(() => (
<Tabs defaultValue="a">
<Tabs.List>
<Tabs.Tab value="a">Tab A</Tabs.Tab>
<Tabs.Tab value="b">Tab B</Tabs.Tab>
</Tabs.List>
<Tabs.Panel value="a" data-testid="panel-a">Panel A</Tabs.Panel>
<Tabs.Panel value="b" data-testid="panel-b">Panel B</Tabs.Panel>
</Tabs>
));
expect(screen.getByTestId("panel-a")).not.toHaveAttribute("hidden");
expect(screen.getByTestId("panel-b")).toHaveAttribute("hidden");
});
it("tab has role=tab and panel has role=tabpanel", () => {
render(() => (
<Tabs defaultValue="a">
<Tabs.List>
<Tabs.Tab value="a">Tab A</Tabs.Tab>
</Tabs.List>
<Tabs.Panel value="a">Panel A</Tabs.Panel>
</Tabs>
));
expect(screen.getByRole("tab")).toBeTruthy();
expect(screen.getByRole("tabpanel")).toBeTruthy();
});
it("tab aria-controls matches panel id", () => {
render(() => (
<Tabs defaultValue="a">
<Tabs.List>
<Tabs.Tab value="a">Tab A</Tabs.Tab>
</Tabs.List>
<Tabs.Panel value="a" data-testid="panel">Panel A</Tabs.Panel>
</Tabs>
));
const tab = screen.getByRole("tab");
const panel = screen.getByTestId("panel");
expect(tab.getAttribute("aria-controls")).toBe(panel.id);
});
it("ArrowRight moves to next tab", () => {
render(() => (
<Tabs defaultValue="a">
<Tabs.List>
<Tabs.Tab value="a">Tab A</Tabs.Tab>
<Tabs.Tab value="b">Tab B</Tabs.Tab>
</Tabs.List>
<Tabs.Panel value="a">Panel A</Tabs.Panel>
<Tabs.Panel value="b">Panel B</Tabs.Panel>
</Tabs>
));
const tabA = screen.getByText("Tab A");
tabA.focus();
fireEvent.keyDown(tabA, { key: "ArrowRight" });
expect(document.activeElement).toBe(screen.getByText("Tab B"));
});
it("controlled value", () => {
render(() => (
<Tabs value="b" onValueChange={() => {}}>
<Tabs.List>
<Tabs.Tab value="a">Tab A</Tabs.Tab>
<Tabs.Tab value="b">Tab B</Tabs.Tab>
</Tabs.List>
<Tabs.Panel value="a">Panel A</Tabs.Panel>
<Tabs.Panel value="b">Panel B</Tabs.Panel>
</Tabs>
));
expect(screen.getByText("Tab B").getAttribute("aria-selected")).toBe("true");
});
});