test(combo-box): add unit tests

This commit is contained in:
Eric Liu 2025-03-17 21:19:24 -07:00
commit 0e1177f398
4 changed files with 271 additions and 77 deletions

View file

@ -1,77 +0,0 @@
<script lang="ts">
import { ComboBox } from "carbon-components-svelte";
import type { ComboBoxItem } from "carbon-components-svelte/ComboBox/ComboBox.svelte";
const items: ComboBoxItem[] = [
{ id: 0, text: "Slack" },
{ id: "1", text: "Email" },
{ id: "2", text: "Fax", disabled: true },
];
let ref: ComboBox;
$: ref?.clear({ focus: false });
$: ref?.clear();
</script>
<ComboBox
bind:this={ref}
direction="top"
titleText="Contact"
placeholder="Select contact method"
{items}
on:select={(e) => {
console.log(e.detail.selectedId);
}}
on:clear={(e) => {
console.log(e.detail);
}}
translateWithId={(id) => {
console.log(id); // "open" | "close"
return id;
}}
translateWithIdSelection={(id) => {
console.log(id); // "clearSelection"
return id;
}}
let:item
let:index
>
{item.id}
{index}
</ComboBox>
<ComboBox
titleText="Contact"
placeholder="Select contact method"
selectedId="1"
{items}
/>
<ComboBox
light
titleText="Contact"
placeholder="Select contact method"
{items}
/>
<ComboBox
titleText="Contact"
placeholder="Select contact method"
size="xl"
{items}
/>
<ComboBox
titleText="Contact"
placeholder="Select contact method"
size="sm"
{items}
/>
<ComboBox
disabled
titleText="Contact"
placeholder="Select contact method"
{items}
/>

View file

@ -0,0 +1,49 @@
<script lang="ts">
import { ComboBox } from "carbon-components-svelte";
import type { ComboBoxItem } from "carbon-components-svelte/ComboBox/ComboBox.svelte";
export let items: ComboBoxItem[] = [
{ id: "0", text: "Slack" },
{ id: "1", text: "Email" },
{ id: "2", text: "Fax" },
];
export let selectedId: string | undefined = undefined;
export let value = "";
export let disabled = false;
export let invalid = false;
export let warn = false;
export let light = false;
export let open = false;
export let titleText = "Contact";
export let placeholder = "Select contact method";
export let invalidText = "";
export let warnText = "";
export let helperText = "";
export let size: "sm" | "xl" | undefined = undefined;
export let shouldFilterItem = (item: ComboBoxItem, value: string) =>
item.text.toLowerCase().includes(value.toLowerCase());
</script>
<ComboBox
{disabled}
{helperText}
{invalid}
{invalidText}
{items}
{light}
{open}
{placeholder}
{selectedId}
{size}
{titleText}
{value}
{warn}
{warnText}
{shouldFilterItem}
on:select={(e) => {
console.log("select", e.detail);
}}
on:clear={(e) => {
console.log("clear", e.type);
}}
/>

View file

@ -0,0 +1,186 @@
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", () => {
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",
},
});
const clearButton = screen.getByRole("button", { name: /clear/i });
await user.click(clearButton);
expect(consoleLog).toHaveBeenCalledWith("clear", expect.any(String));
expect(screen.getByRole("textbox")).toHaveValue("");
});
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 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");
});
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");
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 programmatically clear selection", async () => {
render(ComboBoxCustom, { props: { selectedId: "1" } });
expect(screen.getByRole("textbox")).toHaveValue("Email");
await user.click(screen.getByText("Clear"));
expect(screen.getByRole("textbox")).toHaveValue("");
});
it("should close menu on Escape key", async () => {
render(ComboBox);
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();
});
});

View file

@ -0,0 +1,36 @@
<script lang="ts">
import { ComboBox } from "carbon-components-svelte";
import type { ComboBoxItem } from "carbon-components-svelte/ComboBox/ComboBox.svelte";
let comboBoxRef: ComboBox;
export let items: ComboBoxItem[] = [
{ id: "0", text: "Slack" },
{ id: "1", text: "Email" },
{ id: "2", text: "Fax", disabled: true },
];
export let selectedId: string | undefined = undefined;
export let direction: "top" | "bottom" = "bottom";
export let shouldFilterItem = (item: ComboBoxItem, value: string) =>
item.text.toLowerCase().includes(value.toLowerCase());
export let itemToString = (item: ComboBoxItem) => item.text;
</script>
<ComboBox
bind:this={comboBoxRef}
{items}
{selectedId}
{direction}
{shouldFilterItem}
{itemToString}
titleText="Contact with icons"
placeholder="Select contact method"
on:select={(e) => {
console.log("select", e.detail);
}}
let:item
>
<span>Item {item.text}</span>
</ComboBox>
<button type="button" on:click={() => comboBoxRef.clear()}>Clear</button>