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.
139 lines
4.4 KiB
TypeScript
139 lines
4.4 KiB
TypeScript
import { fireEvent, render, screen } from "@solidjs/testing-library";
|
||
import { createSignal } from "solid-js";
|
||
import { describe, expect, it, vi } from "vitest";
|
||
import { Combobox } from "../../../src/components/combobox/index";
|
||
|
||
describe("Combobox – roles", () => {
|
||
it("input has role=combobox", () => {
|
||
render(() => (
|
||
<Combobox items={["a", "b"]}>
|
||
<Combobox.Input />
|
||
<Combobox.Content>
|
||
<Combobox.Item value="a">A</Combobox.Item>
|
||
<Combobox.Item value="b">B</Combobox.Item>
|
||
</Combobox.Content>
|
||
</Combobox>
|
||
));
|
||
expect(screen.getByRole("combobox")).toBeTruthy();
|
||
});
|
||
|
||
it("content has role=listbox when open", () => {
|
||
render(() => (
|
||
<Combobox items={["a", "b"]} defaultOpen>
|
||
<Combobox.Input />
|
||
<Combobox.Content>
|
||
<Combobox.Item value="a">A</Combobox.Item>
|
||
<Combobox.Item value="b">B</Combobox.Item>
|
||
</Combobox.Content>
|
||
</Combobox>
|
||
));
|
||
expect(screen.getByRole("listbox")).toBeTruthy();
|
||
});
|
||
});
|
||
|
||
describe("Combobox – input", () => {
|
||
it("typing opens content", () => {
|
||
const onInput = vi.fn();
|
||
render(() => (
|
||
<Combobox items={["a", "b"]} onInputChange={onInput}>
|
||
<Combobox.Input />
|
||
<Combobox.Content>
|
||
<Combobox.Item value="a">A</Combobox.Item>
|
||
<Combobox.Item value="b">B</Combobox.Item>
|
||
</Combobox.Content>
|
||
</Combobox>
|
||
));
|
||
fireEvent.input(screen.getByRole("combobox"), { target: { value: "a" } });
|
||
expect(onInput).toHaveBeenCalled();
|
||
});
|
||
|
||
it("controlled value works", () => {
|
||
render(() => (
|
||
<Combobox items={["a", "b"]} value="a" onValueChange={() => {}}>
|
||
<Combobox.Input />
|
||
<Combobox.Content>
|
||
<Combobox.Item value="a">A</Combobox.Item>
|
||
<Combobox.Item value="b">B</Combobox.Item>
|
||
</Combobox.Content>
|
||
</Combobox>
|
||
));
|
||
expect(screen.getByRole("combobox")).toBeTruthy();
|
||
});
|
||
});
|
||
|
||
describe("Combobox – keyboard", () => {
|
||
it("ArrowDown highlights first item", () => {
|
||
render(() => (
|
||
<Combobox items={["a", "b"]} defaultOpen>
|
||
<Combobox.Input />
|
||
<Combobox.Content>
|
||
<Combobox.Item value="a">A</Combobox.Item>
|
||
<Combobox.Item value="b">B</Combobox.Item>
|
||
</Combobox.Content>
|
||
</Combobox>
|
||
));
|
||
fireEvent.keyDown(screen.getByRole("combobox"), { key: "ArrowDown" });
|
||
const options = screen.getAllByRole("option");
|
||
expect(options[0].getAttribute("data-highlighted")).toBe("");
|
||
});
|
||
|
||
it("Enter selects highlighted item", () => {
|
||
const onChange = vi.fn();
|
||
render(() => (
|
||
<Combobox items={["a", "b"]} defaultOpen onValueChange={onChange}>
|
||
<Combobox.Input />
|
||
<Combobox.Content>
|
||
<Combobox.Item value="a">A</Combobox.Item>
|
||
<Combobox.Item value="b">B</Combobox.Item>
|
||
</Combobox.Content>
|
||
</Combobox>
|
||
));
|
||
fireEvent.keyDown(screen.getByRole("combobox"), { key: "ArrowDown" });
|
||
fireEvent.keyDown(screen.getByRole("combobox"), { key: "Enter" });
|
||
expect(onChange).toHaveBeenCalledWith("a");
|
||
});
|
||
|
||
it("Escape closes", () => {
|
||
render(() => (
|
||
<Combobox items={["a", "b"]} defaultOpen>
|
||
<Combobox.Input />
|
||
<Combobox.Content>
|
||
<Combobox.Item value="a">A</Combobox.Item>
|
||
</Combobox.Content>
|
||
</Combobox>
|
||
));
|
||
fireEvent.keyDown(screen.getByRole("combobox"), { key: "Escape" });
|
||
expect(screen.queryByRole("listbox")).toBeNull();
|
||
});
|
||
});
|
||
|
||
describe("Combobox – empty and custom", () => {
|
||
it("Empty message shown when no items", () => {
|
||
render(() => (
|
||
<Combobox items={[]} defaultOpen>
|
||
<Combobox.Input />
|
||
<Combobox.Content>
|
||
<Combobox.Empty>No results</Combobox.Empty>
|
||
</Combobox.Content>
|
||
</Combobox>
|
||
));
|
||
expect(screen.getByText("No results")).toBeTruthy();
|
||
});
|
||
|
||
it("allowCustomValue works", () => {
|
||
const onChange = vi.fn();
|
||
render(() => (
|
||
<Combobox items={[]} defaultOpen allowCustomValue onValueChange={onChange}>
|
||
<Combobox.Input />
|
||
<Combobox.Content>
|
||
<Combobox.Empty>No results</Combobox.Empty>
|
||
</Combobox.Content>
|
||
</Combobox>
|
||
));
|
||
const input = screen.getByRole("combobox") as HTMLInputElement;
|
||
fireEvent.input(input, { target: { value: "custom" } });
|
||
fireEvent.keyDown(input, { key: "Enter" });
|
||
expect(onChange).toHaveBeenCalledWith("custom");
|
||
});
|
||
});
|