test(multi-select): more unit tests

This commit is contained in:
Eric Liu 2025-03-20 16:42:13 -07:00
commit f200dadb97

View file

@ -2,7 +2,13 @@ import { render, screen } from "@testing-library/svelte";
import { MultiSelect } from "carbon-components-svelte";
import { user } from "../setup-tests";
describe("MultiSelect sorts items correctly", () => {
const items = [
{ id: "0", text: "Slack" },
{ id: "1", text: "Email" },
{ id: "2", text: "Fax" },
] as const;
describe("MultiSelect", () => {
/** Opens the dropdown. */
const openMenu = async () =>
await user.click(
@ -29,6 +35,7 @@ describe("MultiSelect sorts items correctly", () => {
const nthRenderedOptionText = (index: number) =>
screen.queryAllByRole("option").at(index)?.textContent?.trim();
describe("sorting behavior", () => {
it("initially sorts items alphabetically", async () => {
render(MultiSelect, {
items: [
@ -38,6 +45,7 @@ describe("MultiSelect sorts items correctly", () => {
],
});
// Initially, items should be sorted alphabetically.
await openMenu();
expect(nthRenderedOptionText(0)).toBe("A");
expect(nthRenderedOptionText(1)).toBe("B");
@ -54,6 +62,7 @@ describe("MultiSelect sorts items correctly", () => {
selectionFeedback: "top",
});
// Initially, items should be sorted alphabetically.
await openMenu();
expect(nthRenderedOptionText(0)).toBe("A");
@ -122,3 +131,221 @@ describe("MultiSelect sorts items correctly", () => {
expect(nthRenderedOptionText(0)).toBe("A");
});
});
describe("filtering behavior", () => {
it("should filter items based on input value", async () => {
render(MultiSelect, {
items,
filterable: true,
placeholder: "Filter items...",
});
await openMenu();
const input = screen.getByPlaceholderText("Filter items...");
await user.type(input, "em");
expect(screen.queryByText("Slack")).not.toBeInTheDocument();
expect(screen.getByText("Email")).toBeInTheDocument();
expect(screen.queryByText("Fax")).not.toBeInTheDocument();
});
it("should use custom filter function", async () => {
render(MultiSelect, {
items,
filterable: true,
filterItem: (item, value) =>
item.text.toLowerCase().startsWith(value.toLowerCase()),
});
await openMenu();
const input = screen.getByRole("combobox");
await user.type(input, "e");
expect(screen.queryByText("Slack")).not.toBeInTheDocument();
expect(screen.getByText("Email")).toBeInTheDocument();
expect(screen.queryByText("Fax")).not.toBeInTheDocument();
});
// TODO(bug): ListBoxSelection aria-labels should be user-friendly
it.skip("should clear filter on selection clear", async () => {
render(MultiSelect, {
items,
filterable: true,
selectedIds: ["0"],
});
const clearButton = screen.getByLabelText("Clear all");
await user.click(clearButton);
const input = screen.getByRole("combobox");
expect(input).toHaveValue("");
});
});
describe("keyboard navigation", () => {
it("should handle arrow keys for navigation", async () => {
render(MultiSelect, { items });
await openMenu();
await user.keyboard("{ArrowDown}");
const options = screen.getAllByRole("option");
expect(options[0]).toHaveClass("bx--list-box__menu-item--highlighted");
});
it("should select item with Enter key", async () => {
const { component } = render(MultiSelect, { items });
const selectHandler = vi.fn();
component.$on("select", selectHandler);
await openMenu();
await user.keyboard("{ArrowDown}");
await user.keyboard("{Enter}");
expect(selectHandler).toHaveBeenCalled();
});
it("should close menu with Escape key", async () => {
render(MultiSelect, { items });
await openMenu();
await user.keyboard("{Escape}");
const button = screen.getByRole("button");
expect(button).toHaveAttribute("aria-expanded", "false");
});
});
describe("accessibility", () => {
it("should handle hidden label", () => {
render(MultiSelect, {
items,
titleText: "Contact methods",
hideLabel: true,
});
const label = screen.getByText("Contact methods");
expect(label).toHaveClass("bx--visually-hidden");
});
it("should handle custom aria-label", async () => {
render(MultiSelect, {
items,
"aria-label": "Custom label",
});
await openMenu();
const menu = screen.getByLabelText("Custom label");
expect(menu).toBeInTheDocument();
});
});
describe("variants and states", () => {
it("should render in light variant", async () => {
render(MultiSelect, {
items,
light: true,
});
await openMenu();
const listBox = screen.getByRole("listbox").closest(".bx--list-box");
expect(listBox).toHaveClass("bx--list-box--light");
});
it("should render in inline variant", () => {
render(MultiSelect, {
items,
type: "inline",
});
const wrapper = screen
.getByRole("button")
.closest(".bx--multi-select__wrapper");
expect(wrapper).toHaveClass("bx--multi-select__wrapper--inline");
});
it("should handle invalid state", () => {
render(MultiSelect, {
items,
invalid: true,
invalidText: "Invalid selection",
});
expect(screen.getByText("Invalid selection")).toBeInTheDocument();
const wrapper = screen.getByRole("button").closest(".bx--list-box");
expect(wrapper).toHaveClass("bx--multi-select--invalid");
});
it("should handle warning state", () => {
render(MultiSelect, {
items,
warn: true,
warnText: "Warning message",
});
expect(screen.getByText("Warning message")).toBeInTheDocument();
const wrapper = screen.getByRole("button").closest(".bx--list-box");
expect(wrapper).toHaveClass("bx--list-box--warning");
});
it("should handle disabled state", () => {
render(MultiSelect, { items, disabled: true });
const field = screen.getByRole("button");
expect(field).toHaveAttribute("aria-disabled", "true");
expect(field).toHaveAttribute("tabindex", "-1");
expect(field.closest(".bx--multi-select")).toHaveAttribute(
"tabindex",
"-1",
);
});
it("should handle disabled items", async () => {
const itemsWithDisabled = [
{ id: "0", text: "Slack" },
{ id: "1", text: "Email", disabled: true },
{ id: "2", text: "Fax" },
];
render(MultiSelect, {
items: itemsWithDisabled,
});
await openMenu();
const emailOption = screen
.getByText("Email")
.closest(".bx--list-box__menu-item");
expect(emailOption).toHaveAttribute("disabled");
});
});
describe("custom formatting", () => {
it("should handle custom itemToString", () => {
render(MultiSelect, {
items,
selectedIds: ["0"],
itemToString: (item) => `${item.text} (${item.id})`,
});
expect(screen.getByText("Slack (0)")).toBeInTheDocument();
});
it("should handle custom itemToInput", async () => {
render(MultiSelect, {
items,
itemToInput: (item) => ({
name: `contact_${item.id}`,
value: item.text.toLowerCase(),
}),
});
await openMenu();
const checkbox = screen.getByText("Slack");
const checkboxWrapper = checkbox.closest(".bx--checkbox-wrapper");
assert(checkboxWrapper);
const checkboxInput = checkboxWrapper.querySelector("input");
expect(checkboxInput).toHaveAttribute("name", "contact_0");
});
});
});