Mats Bosson 6382a59eaf Combobox, DropdownMenu, ContextMenu, Toast
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.
2026-03-29 19:23:33 +07:00

108 lines
3.8 KiB
TypeScript

import { fireEvent, render, screen } from "@solidjs/testing-library";
import { describe, expect, it, vi } from "vitest";
import { ContextMenu } from "../../../src/components/context-menu/index";
describe("ContextMenu — open/close and roles", () => {
it("right-click opens menu", () => {
render(() => (
<ContextMenu items={["edit", "delete"]}>
<ContextMenu.Trigger>
<div data-testid="area">Right click me</div>
</ContextMenu.Trigger>
<ContextMenu.Content>
<ContextMenu.Item value="edit">Edit</ContextMenu.Item>
<ContextMenu.Item value="delete">Delete</ContextMenu.Item>
</ContextMenu.Content>
</ContextMenu>
));
expect(screen.queryByRole("menu")).toBeNull();
fireEvent.contextMenu(screen.getByTestId("area"));
expect(screen.getByRole("menu")).toBeTruthy();
});
it("content has role=menu", () => {
render(() => (
<ContextMenu items={["edit"]} defaultOpen>
<ContextMenu.Trigger><div>Area</div></ContextMenu.Trigger>
<ContextMenu.Content>
<ContextMenu.Item value="edit">Edit</ContextMenu.Item>
</ContextMenu.Content>
</ContextMenu>
));
expect(screen.getByRole("menu")).toBeTruthy();
});
it("items have role=menuitem", () => {
render(() => (
<ContextMenu items={["edit", "delete"]} defaultOpen>
<ContextMenu.Trigger><div>Area</div></ContextMenu.Trigger>
<ContextMenu.Content>
<ContextMenu.Item value="edit">Edit</ContextMenu.Item>
<ContextMenu.Item value="delete">Delete</ContextMenu.Item>
</ContextMenu.Content>
</ContextMenu>
));
expect(screen.getAllByRole("menuitem")).toHaveLength(2);
});
it("trigger area does NOT have aria-haspopup", () => {
render(() => (
<ContextMenu items={["edit"]}>
<ContextMenu.Trigger>
<div data-testid="area">Area</div>
</ContextMenu.Trigger>
<ContextMenu.Content>
<ContextMenu.Item value="edit">Edit</ContextMenu.Item>
</ContextMenu.Content>
</ContextMenu>
));
expect(screen.getByTestId("area").getAttribute("aria-haspopup")).toBeNull();
});
});
describe("ContextMenu — keyboard navigation", () => {
it("Enter activates item", () => {
const onSelect = vi.fn();
render(() => (
<ContextMenu items={["edit"]} defaultOpen>
<ContextMenu.Trigger><div>Area</div></ContextMenu.Trigger>
<ContextMenu.Content>
<ContextMenu.Item value="edit" onSelect={onSelect}>Edit</ContextMenu.Item>
</ContextMenu.Content>
</ContextMenu>
));
const menu = screen.getByRole("menu");
fireEvent.keyDown(menu, { key: "ArrowDown" });
fireEvent.keyDown(menu, { key: "Enter" });
expect(onSelect).toHaveBeenCalled();
});
it("Escape closes", () => {
render(() => (
<ContextMenu items={["edit"]} defaultOpen>
<ContextMenu.Trigger><div>Area</div></ContextMenu.Trigger>
<ContextMenu.Content>
<ContextMenu.Item value="edit">Edit</ContextMenu.Item>
</ContextMenu.Content>
</ContextMenu>
));
fireEvent.keyDown(screen.getByRole("menu"), { key: "Escape" });
expect(screen.queryByRole("menu")).toBeNull();
});
it("ArrowDown navigates items", () => {
render(() => (
<ContextMenu items={["edit", "delete"]} defaultOpen>
<ContextMenu.Trigger><div>Area</div></ContextMenu.Trigger>
<ContextMenu.Content>
<ContextMenu.Item value="edit">Edit</ContextMenu.Item>
<ContextMenu.Item value="delete">Delete</ContextMenu.Item>
</ContextMenu.Content>
</ContextMenu>
));
const menu = screen.getByRole("menu");
fireEvent.keyDown(menu, { key: "ArrowDown" });
expect(screen.getAllByRole("menuitem")[0].getAttribute("data-highlighted")).toBe("");
});
});