mirror of
https://github.com/carbon-design-system/carbon-components-svelte.git
synced 2025-09-14 18:01:06 +00:00
415 lines
12 KiB
TypeScript
415 lines
12 KiB
TypeScript
import { render, screen } from "@testing-library/svelte";
|
|
import { user } from "../setup-tests";
|
|
import ComboBox from "./ComboBox.test.svelte";
|
|
import ComboBoxCustom from "./ComboBoxCustom.test.svelte";
|
|
|
|
describe("ComboBox", () => {
|
|
const getClearButton = () =>
|
|
screen.getByRole("button", { name: "Clear selected item" });
|
|
|
|
beforeEach(() => {
|
|
vi.clearAllMocks();
|
|
});
|
|
|
|
it("should render with default props", () => {
|
|
render(ComboBox);
|
|
|
|
expect(screen.getByText("Contact")).toBeInTheDocument();
|
|
const input = screen.getByRole("textbox");
|
|
expect(input).toHaveAttribute("placeholder", "Select contact method");
|
|
});
|
|
|
|
it("should open menu on click", async () => {
|
|
render(ComboBox);
|
|
|
|
const input = screen.getByRole("textbox");
|
|
await user.click(input);
|
|
|
|
const dropdown = screen.getAllByRole("listbox")[1];
|
|
expect(dropdown).toBeVisible();
|
|
});
|
|
|
|
it("should handle item selection", async () => {
|
|
const consoleLog = vi.spyOn(console, "log");
|
|
render(ComboBox);
|
|
|
|
await user.click(screen.getByRole("textbox"));
|
|
await user.click(screen.getByText("Email"));
|
|
|
|
expect(consoleLog).toHaveBeenCalledWith("select", {
|
|
selectedId: "1",
|
|
selectedItem: { id: "1", text: "Email" },
|
|
});
|
|
expect(screen.getByRole("textbox")).toHaveValue("Email");
|
|
});
|
|
|
|
it("should handle keyboard navigation", async () => {
|
|
render(ComboBox);
|
|
|
|
const input = screen.getByRole("textbox");
|
|
await user.click(input);
|
|
await user.keyboard("{ArrowDown}");
|
|
await user.keyboard("{Enter}");
|
|
|
|
expect(input).toHaveValue("Slack");
|
|
});
|
|
|
|
it("should handle clear selection", async () => {
|
|
const consoleLog = vi.spyOn(console, "log");
|
|
render(ComboBox, {
|
|
props: {
|
|
selectedId: "1",
|
|
value: "Email",
|
|
},
|
|
});
|
|
|
|
await user.click(getClearButton());
|
|
|
|
expect(consoleLog).toHaveBeenCalledWith("clear", expect.any(String));
|
|
expect(screen.getByRole("textbox")).toHaveValue("");
|
|
});
|
|
|
|
it("should handle clear selection via keyboard navigation (Enter)", async () => {
|
|
const consoleLog = vi.spyOn(console, "log");
|
|
render(ComboBox, {
|
|
props: {
|
|
selectedId: "1",
|
|
value: "Email",
|
|
},
|
|
});
|
|
|
|
expect(consoleLog).not.toHaveBeenCalled();
|
|
expect(screen.getByRole("textbox")).toHaveValue("Email");
|
|
|
|
const clearButton = getClearButton();
|
|
clearButton.focus();
|
|
expect(clearButton).toHaveFocus();
|
|
await user.keyboard("{Enter}");
|
|
|
|
expect(consoleLog).toHaveBeenCalledWith("clear", expect.any(String));
|
|
expect(screen.getByRole("textbox")).toHaveValue("");
|
|
});
|
|
|
|
it("should handle clear selection via keyboard navigation (Space)", async () => {
|
|
const consoleLog = vi.spyOn(console, "log");
|
|
render(ComboBox, {
|
|
props: {
|
|
selectedId: "1",
|
|
value: "Email",
|
|
},
|
|
});
|
|
|
|
expect(consoleLog).not.toHaveBeenCalled();
|
|
expect(screen.getByRole("textbox")).toHaveValue("Email");
|
|
|
|
const clearButton = getClearButton();
|
|
clearButton.focus();
|
|
expect(clearButton).toHaveFocus();
|
|
await user.keyboard(" ");
|
|
|
|
expect(consoleLog).toHaveBeenCalledWith("clear", expect.any(String));
|
|
expect(screen.getByRole("textbox")).toHaveValue("");
|
|
});
|
|
|
|
it("should use custom translations when translateWithId is provided", () => {
|
|
const customTranslations = {
|
|
clearSelection: "Remove selected item",
|
|
clearAll: "Remove all items",
|
|
} as const;
|
|
|
|
render(ComboBox, {
|
|
props: {
|
|
selectedId: "1",
|
|
value: "Email",
|
|
translateWithIdSelection: (id) => customTranslations[id],
|
|
},
|
|
});
|
|
|
|
const clearButton = screen.getByRole("button", {
|
|
name: "Remove selected item",
|
|
});
|
|
expect(clearButton).toBeInTheDocument();
|
|
});
|
|
|
|
it("should handle disabled state", () => {
|
|
render(ComboBox, { props: { disabled: true } });
|
|
|
|
expect(screen.getByRole("textbox")).toBeDisabled();
|
|
expect(screen.getByText("Contact")).toHaveClass("bx--label--disabled");
|
|
});
|
|
|
|
it("should handle hidden label", () => {
|
|
render(ComboBox, {
|
|
props: {
|
|
titleText: "Hidden Label",
|
|
hideLabel: true,
|
|
},
|
|
});
|
|
|
|
expect(screen.getByText("Hidden Label")).toHaveClass("bx--visually-hidden");
|
|
});
|
|
|
|
it("should handle invalid state", () => {
|
|
render(ComboBox, {
|
|
props: {
|
|
invalid: true,
|
|
invalidText: "Invalid selection",
|
|
},
|
|
});
|
|
|
|
expect(screen.getByRole("listbox")).toHaveAttribute("data-invalid", "true");
|
|
expect(screen.getByText("Invalid selection")).toBeInTheDocument();
|
|
});
|
|
|
|
it("should handle warning state", () => {
|
|
render(ComboBox, {
|
|
props: {
|
|
warn: true,
|
|
warnText: "Warning message",
|
|
},
|
|
});
|
|
|
|
expect(screen.getByText("Warning message")).toBeInTheDocument();
|
|
});
|
|
|
|
it("should handle helper text", () => {
|
|
render(ComboBox, { props: { helperText: "Helper message" } });
|
|
|
|
expect(screen.getByText("Helper message")).toBeInTheDocument();
|
|
});
|
|
|
|
it("should handle light variant", () => {
|
|
render(ComboBox, { props: { light: true } });
|
|
|
|
expect(screen.getByRole("textbox")).toHaveClass("bx--text-input--light");
|
|
});
|
|
|
|
test.each([
|
|
["sm", "bx--list-box--sm"],
|
|
["xl", "bx--list-box--xl"],
|
|
] as const)("should handle size variants", (size, className) => {
|
|
render(ComboBox, { props: { size } });
|
|
expect(screen.getByRole("listbox")).toHaveClass(className);
|
|
});
|
|
|
|
it("should handle filtering items", async () => {
|
|
render(ComboBox);
|
|
|
|
const input = screen.getByRole("textbox");
|
|
await user.click(input);
|
|
await user.type(input, "em");
|
|
|
|
const options = screen.getAllByRole("option");
|
|
expect(options).toHaveLength(1);
|
|
expect(options[0]).toHaveTextContent("Email");
|
|
|
|
await user.clear(input);
|
|
expect(input).toHaveValue("");
|
|
expect(screen.getAllByRole("option")).toHaveLength(3);
|
|
|
|
await user.click(document.body);
|
|
expect(input).not.toHaveFocus();
|
|
|
|
await user.keyboard("{Tab}");
|
|
expect(input).toHaveFocus();
|
|
|
|
await user.type(input, "a");
|
|
await user.click(screen.getAllByRole("option")[1]);
|
|
expect(input).toHaveValue("Email");
|
|
|
|
await user.click(document.body);
|
|
expect(input).not.toHaveFocus();
|
|
expect(screen.queryByRole("option")).not.toBeInTheDocument();
|
|
});
|
|
|
|
it("should clear input when clicking clear button", async () => {
|
|
const consoleLog = vi.spyOn(console, "log");
|
|
render(ComboBox, { props: { selectedId: "1" } });
|
|
|
|
expect(consoleLog).not.toBeCalled();
|
|
await user.click(getClearButton());
|
|
|
|
expect(screen.getByRole("textbox")).toHaveValue("");
|
|
expect(consoleLog).toHaveBeenCalledWith("clear", "clear");
|
|
});
|
|
|
|
it("should handle disabled items", async () => {
|
|
render(ComboBoxCustom);
|
|
|
|
await user.click(screen.getByRole("textbox"));
|
|
const disabledOption = screen.getByText(/Fax/).closest('[role="option"]');
|
|
assert(disabledOption);
|
|
expect(disabledOption).toHaveAttribute("disabled", "true");
|
|
expect(disabledOption).toHaveAttribute("aria-disabled", "true");
|
|
|
|
await user.click(disabledOption);
|
|
expect(screen.getByRole("textbox")).toHaveValue("");
|
|
|
|
// Dropdown remains open
|
|
const dropdown = screen.getAllByRole("listbox")[1];
|
|
expect(dropdown).toBeVisible();
|
|
});
|
|
|
|
it("should handle custom item display", async () => {
|
|
render(ComboBoxCustom);
|
|
|
|
await user.click(screen.getByRole("textbox"));
|
|
const options = screen.getAllByRole("option");
|
|
|
|
expect(options[0]).toHaveTextContent("Item Slack");
|
|
expect(options[1]).toHaveTextContent("Item Email");
|
|
expect(options[2]).toHaveTextContent("Item Fax");
|
|
});
|
|
|
|
it("should handle top direction", async () => {
|
|
render(ComboBoxCustom, { props: { direction: "top" } });
|
|
|
|
await user.click(screen.getAllByRole("button")[0]);
|
|
expect(screen.getByRole("listbox")).toHaveClass("bx--list-box--up");
|
|
});
|
|
|
|
it("should clear filter on selection clear", async () => {
|
|
render(ComboBoxCustom, { props: { selectedId: "1" } });
|
|
|
|
await user.click(getClearButton());
|
|
|
|
const input = screen.getByRole("textbox");
|
|
expect(input).toHaveValue("");
|
|
});
|
|
|
|
it("should programmatically clear selection", async () => {
|
|
render(ComboBoxCustom, { props: { selectedId: "1" } });
|
|
|
|
const textbox = screen.getByRole("textbox");
|
|
expect(textbox).toHaveValue("Email");
|
|
await user.click(screen.getByText("Clear"));
|
|
expect(textbox).toHaveValue("");
|
|
expect(textbox).toHaveFocus();
|
|
});
|
|
|
|
it("should not re-focus textbox if clearOptions.focus is false", async () => {
|
|
render(ComboBoxCustom, {
|
|
props: {
|
|
selectedId: "1",
|
|
clearOptions: { focus: false },
|
|
},
|
|
});
|
|
|
|
const textbox = screen.getByRole("textbox");
|
|
expect(textbox).toHaveValue("Email");
|
|
await user.click(screen.getByText("Clear"));
|
|
expect(textbox).toHaveValue("");
|
|
expect(textbox).not.toHaveFocus();
|
|
});
|
|
|
|
it("should close menu on Escape key", async () => {
|
|
render(ComboBox);
|
|
|
|
expect(screen.getByRole("textbox")).toHaveValue("");
|
|
|
|
const input = screen.getByRole("textbox");
|
|
await user.click(input);
|
|
|
|
const dropdown = screen.getAllByRole("listbox")[1];
|
|
expect(dropdown).toBeVisible();
|
|
|
|
await user.keyboard("{Escape}");
|
|
expect(dropdown).not.toBeVisible();
|
|
expect(screen.getByRole("textbox")).toHaveValue("");
|
|
expect(screen.getByRole("textbox")).toHaveFocus();
|
|
});
|
|
|
|
it("should close menu and clear selection on Escape key", async () => {
|
|
render(ComboBox, {
|
|
props: {
|
|
selectedId: "1",
|
|
value: "Email",
|
|
},
|
|
});
|
|
|
|
expect(screen.getByRole("textbox")).toHaveValue("Email");
|
|
|
|
const input = screen.getByRole("textbox");
|
|
await user.click(input);
|
|
|
|
const dropdown = screen.getAllByRole("listbox")[1];
|
|
expect(dropdown).toBeVisible();
|
|
|
|
await user.keyboard("{Escape}");
|
|
expect(dropdown).not.toBeVisible();
|
|
expect(screen.getByRole("textbox")).toHaveValue("");
|
|
expect(screen.getByRole("textbox")).toHaveFocus();
|
|
});
|
|
|
|
it("should use custom shouldFilterItem function", async () => {
|
|
render(ComboBoxCustom, {
|
|
props: {
|
|
items: [
|
|
{ id: "1", text: "Apple" },
|
|
{ id: "2", text: "Banana" },
|
|
{ id: "3", text: "Cherry" },
|
|
],
|
|
shouldFilterItem: (item: { text: string }, value: string) =>
|
|
item.text.startsWith(value),
|
|
},
|
|
});
|
|
const input = screen.getByRole("textbox");
|
|
await user.click(input);
|
|
await user.type(input, "B");
|
|
const options = screen.getAllByRole("option");
|
|
expect(options).toHaveLength(1);
|
|
expect(options[0]).toHaveTextContent("Banana");
|
|
});
|
|
|
|
it("should use custom itemToString function", async () => {
|
|
render(ComboBoxCustom, {
|
|
props: {
|
|
items: [
|
|
{ id: "1", text: "Apple" },
|
|
{ id: "2", text: "Banana" },
|
|
],
|
|
itemToString: (item: { text: string }) => `Item ${item.text}`,
|
|
},
|
|
});
|
|
const input = screen.getByRole("textbox");
|
|
await user.click(input);
|
|
const options = screen.getAllByRole("option");
|
|
expect(options[0]).toHaveTextContent("Item Apple");
|
|
await user.click(options[1]);
|
|
expect(input).toHaveValue("Item Banana");
|
|
});
|
|
|
|
it("should open menu if open prop is true on mount", () => {
|
|
render(ComboBox, { props: { open: true } });
|
|
const dropdown = screen.getAllByRole("listbox")[1];
|
|
expect(dropdown).toBeVisible();
|
|
});
|
|
|
|
it("should skip disabled items during keyboard navigation", async () => {
|
|
render(ComboBoxCustom, {
|
|
props: {
|
|
items: [
|
|
{ id: "1", text: "A" },
|
|
{ id: "2", text: "B", disabled: true },
|
|
{ id: "3", text: "C" },
|
|
],
|
|
},
|
|
});
|
|
const input = screen.getByRole("textbox");
|
|
await user.click(input);
|
|
await user.keyboard("{ArrowDown}"); // should highlight A
|
|
await user.keyboard("{ArrowDown}"); // should skip B and highlight C
|
|
await user.keyboard("{Enter}");
|
|
expect(input).toHaveValue("C");
|
|
});
|
|
|
|
it("should not show helper text if warn is true", () => {
|
|
render(ComboBox, { props: { helperText: "Help", warn: true } });
|
|
expect(screen.queryByText("Help")).not.toBeInTheDocument();
|
|
});
|
|
|
|
it("should not show helper text if invalid is true", () => {
|
|
render(ComboBox, { props: { helperText: "Help", invalid: true } });
|
|
expect(screen.queryByText("Help")).not.toBeInTheDocument();
|
|
});
|
|
});
|