Combobox: text input + floating listbox with consumer-driven filtering, allowCustomValue, and aria-autocomplete support. DropdownMenu: activation-mode menu with menuitem, menuitemcheckbox, menuitemradio roles, keyboard navigation, and typeahead. ContextMenu: right-click triggered menu with virtual anchor positioning at pointer coordinates. Toast: imperative API (toast(), toast.success/error/dismiss/clear) with reactive signal-based store and Toast.Region declarative renderer.
158 lines
5.4 KiB
TypeScript
158 lines
5.4 KiB
TypeScript
import { fireEvent, render, screen } from "@solidjs/testing-library";
|
|
import { describe, expect, it, vi } from "vitest";
|
|
import { DropdownMenu } from "../../../src/components/dropdown-menu/index";
|
|
|
|
describe("DropdownMenu — rendering", () => {
|
|
it("content has role=menu when open", () => {
|
|
render(() => (
|
|
<DropdownMenu defaultOpen>
|
|
<DropdownMenu.Trigger>Actions</DropdownMenu.Trigger>
|
|
<DropdownMenu.Content>
|
|
<DropdownMenu.Item value="edit">Edit</DropdownMenu.Item>
|
|
</DropdownMenu.Content>
|
|
</DropdownMenu>
|
|
));
|
|
expect(screen.getByRole("menu")).toBeTruthy();
|
|
});
|
|
|
|
it("items have role=menuitem", () => {
|
|
render(() => (
|
|
<DropdownMenu defaultOpen>
|
|
<DropdownMenu.Trigger>Actions</DropdownMenu.Trigger>
|
|
<DropdownMenu.Content>
|
|
<DropdownMenu.Item value="edit">Edit</DropdownMenu.Item>
|
|
<DropdownMenu.Item value="delete">Delete</DropdownMenu.Item>
|
|
</DropdownMenu.Content>
|
|
</DropdownMenu>
|
|
));
|
|
expect(screen.getAllByRole("menuitem")).toHaveLength(2);
|
|
});
|
|
|
|
it("trigger has correct ARIA", () => {
|
|
render(() => (
|
|
<DropdownMenu>
|
|
<DropdownMenu.Trigger>Actions</DropdownMenu.Trigger>
|
|
<DropdownMenu.Content>
|
|
<DropdownMenu.Item value="edit">Edit</DropdownMenu.Item>
|
|
</DropdownMenu.Content>
|
|
</DropdownMenu>
|
|
));
|
|
const trigger = screen.getByText("Actions");
|
|
expect(trigger.getAttribute("aria-haspopup")).toBe("menu");
|
|
expect(trigger.getAttribute("aria-expanded")).toBe("false");
|
|
});
|
|
});
|
|
|
|
describe("DropdownMenu — interactions", () => {
|
|
it("click trigger opens", () => {
|
|
render(() => (
|
|
<DropdownMenu>
|
|
<DropdownMenu.Trigger>Actions</DropdownMenu.Trigger>
|
|
<DropdownMenu.Content>
|
|
<DropdownMenu.Item value="edit">Edit</DropdownMenu.Item>
|
|
</DropdownMenu.Content>
|
|
</DropdownMenu>
|
|
));
|
|
expect(screen.queryByRole("menu")).toBeNull();
|
|
fireEvent.click(screen.getByText("Actions"));
|
|
expect(screen.getByRole("menu")).toBeTruthy();
|
|
});
|
|
|
|
it("Escape closes", () => {
|
|
render(() => (
|
|
<DropdownMenu defaultOpen>
|
|
<DropdownMenu.Trigger>Actions</DropdownMenu.Trigger>
|
|
<DropdownMenu.Content>
|
|
<DropdownMenu.Item value="edit">Edit</DropdownMenu.Item>
|
|
</DropdownMenu.Content>
|
|
</DropdownMenu>
|
|
));
|
|
fireEvent.keyDown(screen.getByRole("menu"), { key: "Escape" });
|
|
expect(screen.queryByRole("menu")).toBeNull();
|
|
});
|
|
|
|
it("item click activates and closes", () => {
|
|
const onSelect = vi.fn();
|
|
render(() => (
|
|
<DropdownMenu defaultOpen>
|
|
<DropdownMenu.Trigger>Actions</DropdownMenu.Trigger>
|
|
<DropdownMenu.Content>
|
|
<DropdownMenu.Item value="edit" onSelect={onSelect}>Edit</DropdownMenu.Item>
|
|
</DropdownMenu.Content>
|
|
</DropdownMenu>
|
|
));
|
|
fireEvent.click(screen.getByRole("menuitem"));
|
|
expect(onSelect).toHaveBeenCalled();
|
|
expect(screen.queryByRole("menu")).toBeNull();
|
|
});
|
|
});
|
|
|
|
describe("DropdownMenu — keyboard navigation", () => {
|
|
it("Enter activates item", () => {
|
|
const onSelect = vi.fn();
|
|
render(() => (
|
|
<DropdownMenu defaultOpen>
|
|
<DropdownMenu.Trigger>Actions</DropdownMenu.Trigger>
|
|
<DropdownMenu.Content>
|
|
<DropdownMenu.Item value="edit" onSelect={onSelect}>Edit</DropdownMenu.Item>
|
|
</DropdownMenu.Content>
|
|
</DropdownMenu>
|
|
));
|
|
const menu = screen.getByRole("menu");
|
|
fireEvent.keyDown(menu, { key: "ArrowDown" });
|
|
fireEvent.keyDown(menu, { key: "Enter" });
|
|
expect(onSelect).toHaveBeenCalled();
|
|
});
|
|
|
|
it("ArrowDown navigates", () => {
|
|
render(() => (
|
|
<DropdownMenu defaultOpen>
|
|
<DropdownMenu.Trigger>Actions</DropdownMenu.Trigger>
|
|
<DropdownMenu.Content>
|
|
<DropdownMenu.Item value="edit">Edit</DropdownMenu.Item>
|
|
<DropdownMenu.Item value="delete">Delete</DropdownMenu.Item>
|
|
</DropdownMenu.Content>
|
|
</DropdownMenu>
|
|
));
|
|
const menu = screen.getByRole("menu");
|
|
fireEvent.keyDown(menu, { key: "ArrowDown" });
|
|
const items = screen.getAllByRole("menuitem");
|
|
expect(items[0].getAttribute("data-highlighted")).toBe("");
|
|
});
|
|
});
|
|
|
|
describe("DropdownMenu — checkbox and radio items", () => {
|
|
it("CheckboxItem toggles", () => {
|
|
const onChange = vi.fn();
|
|
render(() => (
|
|
<DropdownMenu defaultOpen>
|
|
<DropdownMenu.Trigger>Actions</DropdownMenu.Trigger>
|
|
<DropdownMenu.Content>
|
|
<DropdownMenu.CheckboxItem checked={false} onCheckedChange={onChange}>
|
|
Bookmark
|
|
</DropdownMenu.CheckboxItem>
|
|
</DropdownMenu.Content>
|
|
</DropdownMenu>
|
|
));
|
|
fireEvent.click(screen.getByRole("menuitemcheckbox"));
|
|
expect(onChange).toHaveBeenCalledWith(true);
|
|
});
|
|
|
|
it("RadioItem selects", () => {
|
|
const onChange = vi.fn();
|
|
render(() => (
|
|
<DropdownMenu defaultOpen>
|
|
<DropdownMenu.Trigger>Actions</DropdownMenu.Trigger>
|
|
<DropdownMenu.Content>
|
|
<DropdownMenu.RadioGroup value="list" onValueChange={onChange}>
|
|
<DropdownMenu.RadioItem value="list">List</DropdownMenu.RadioItem>
|
|
<DropdownMenu.RadioItem value="grid">Grid</DropdownMenu.RadioItem>
|
|
</DropdownMenu.RadioGroup>
|
|
</DropdownMenu.Content>
|
|
</DropdownMenu>
|
|
));
|
|
fireEvent.click(screen.getAllByRole("menuitemradio")[1]);
|
|
expect(onChange).toHaveBeenCalledWith("grid");
|
|
});
|
|
});
|