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).
111 lines
3.6 KiB
TypeScript
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");
|
|
});
|
|
});
|