mirror of
https://github.com/carbon-design-system/carbon-components-svelte.git
synced 2025-09-14 18:01:06 +00:00
test(dropdown): add unit tests
This commit is contained in:
parent
3607c70070
commit
0b799d64b7
4 changed files with 344 additions and 115 deletions
|
@ -1,115 +0,0 @@
|
||||||
<script lang="ts">
|
|
||||||
import { Dropdown, DropdownSkeleton } from "carbon-components-svelte";
|
|
||||||
import type { DropdownProps } from "carbon-components-svelte/Dropdown/Dropdown.svelte";
|
|
||||||
|
|
||||||
let items = [
|
|
||||||
{ id: 0, text: "Slack" },
|
|
||||||
{ id: "1", text: "Email" },
|
|
||||||
{ id: "2", text: "Fax" },
|
|
||||||
] satisfies NonNullable<DropdownProps["items"]>;
|
|
||||||
|
|
||||||
let itemsWithoutConst = [...items];
|
|
||||||
|
|
||||||
type FieldId = (typeof items)[number]["id"]; // 'foo' | 'bar' | 'baz'
|
|
||||||
|
|
||||||
export const fieldId: FieldId = "bar";
|
|
||||||
|
|
||||||
$: items[0] = { id: "0", text: "Slack" };
|
|
||||||
$: {
|
|
||||||
items[0] = { id: "0", text: "Slack" };
|
|
||||||
}
|
|
||||||
$: {
|
|
||||||
items = [...items, { id: "3", text: "Email" }];
|
|
||||||
items = items.map((item, index) => {
|
|
||||||
if (index === 0) {
|
|
||||||
return { id: "0", text: "Slack" };
|
|
||||||
}
|
|
||||||
return item;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<Dropdown
|
|
||||||
direction="top"
|
|
||||||
titleText="Contact"
|
|
||||||
selectedId="0"
|
|
||||||
{items}
|
|
||||||
on:select={(e) => {
|
|
||||||
console.log(e.detail.selectedId);
|
|
||||||
}}
|
|
||||||
translateWithId={(id) => {
|
|
||||||
console.log(id); // "open" | "close"
|
|
||||||
return id;
|
|
||||||
}}
|
|
||||||
let:item
|
|
||||||
let:index
|
|
||||||
>
|
|
||||||
{item.id}
|
|
||||||
{index}
|
|
||||||
</Dropdown>
|
|
||||||
|
|
||||||
<Dropdown
|
|
||||||
itemToString={(item) => {
|
|
||||||
return item.text + " (" + item.id + ")";
|
|
||||||
}}
|
|
||||||
titleText="Contact"
|
|
||||||
selectedId="0"
|
|
||||||
items={itemsWithoutConst}
|
|
||||||
/>
|
|
||||||
|
|
||||||
<Dropdown
|
|
||||||
light
|
|
||||||
titleText="Contact"
|
|
||||||
selectedId="0"
|
|
||||||
items={[
|
|
||||||
{ id: "0", text: "Slack" },
|
|
||||||
{ id: "1", text: "Email" },
|
|
||||||
{ id: "2", text: "Fax" },
|
|
||||||
]}
|
|
||||||
/>
|
|
||||||
|
|
||||||
<Dropdown
|
|
||||||
type="inline"
|
|
||||||
titleText="Contact"
|
|
||||||
selectedId="0"
|
|
||||||
items={[
|
|
||||||
{ id: "0", text: "Slack" },
|
|
||||||
{ id: "1", text: "Email" },
|
|
||||||
{ id: "2", text: "Fax" },
|
|
||||||
]}
|
|
||||||
/>
|
|
||||||
|
|
||||||
<Dropdown
|
|
||||||
size="xl"
|
|
||||||
titleText="Contact"
|
|
||||||
selectedId="0"
|
|
||||||
items={[
|
|
||||||
{ id: "0", text: "Slack" },
|
|
||||||
{ id: "1", text: "Email" },
|
|
||||||
{ id: "2", text: "Fax" },
|
|
||||||
]}
|
|
||||||
/>
|
|
||||||
|
|
||||||
<Dropdown
|
|
||||||
size="sm"
|
|
||||||
titleText="Contact"
|
|
||||||
selectedId="0"
|
|
||||||
items={[
|
|
||||||
{ id: "0", text: "Slack" },
|
|
||||||
{ id: "1", text: "Email" },
|
|
||||||
{ id: "2", text: "Fax" },
|
|
||||||
]}
|
|
||||||
/>
|
|
||||||
|
|
||||||
<Dropdown
|
|
||||||
disabled
|
|
||||||
titleText="Contact"
|
|
||||||
selectedId="0"
|
|
||||||
items={[
|
|
||||||
{ id: "0", text: "Slack" },
|
|
||||||
{ id: "1", text: "Email" },
|
|
||||||
{ id: "2", text: "Fax" },
|
|
||||||
]}
|
|
||||||
/>
|
|
||||||
|
|
||||||
<DropdownSkeleton />
|
|
53
tests/Dropdown/Dropdown.test.svelte
Normal file
53
tests/Dropdown/Dropdown.test.svelte
Normal file
|
@ -0,0 +1,53 @@
|
||||||
|
<script lang="ts">
|
||||||
|
import { Dropdown } from "carbon-components-svelte";
|
||||||
|
import type { ComponentProps } from "svelte";
|
||||||
|
|
||||||
|
export let items: ComponentProps<Dropdown>["items"] = [];
|
||||||
|
export let itemToString: ComponentProps<Dropdown>["itemToString"] = undefined;
|
||||||
|
export let selectedId: ComponentProps<Dropdown>["selectedId"] = undefined;
|
||||||
|
export let type: ComponentProps<Dropdown>["type"] = "default";
|
||||||
|
export let direction: ComponentProps<Dropdown>["direction"] = "bottom";
|
||||||
|
export let size: ComponentProps<Dropdown>["size"] = undefined;
|
||||||
|
export let open: ComponentProps<Dropdown>["open"] = false;
|
||||||
|
export let light: ComponentProps<Dropdown>["light"] = false;
|
||||||
|
export let disabled: ComponentProps<Dropdown>["disabled"] = false;
|
||||||
|
export let titleText: ComponentProps<Dropdown>["titleText"] = "";
|
||||||
|
export let invalid: ComponentProps<Dropdown>["invalid"] = false;
|
||||||
|
export let invalidText: ComponentProps<Dropdown>["invalidText"] = "";
|
||||||
|
export let warn: ComponentProps<Dropdown>["warn"] = false;
|
||||||
|
export let warnText: ComponentProps<Dropdown>["warnText"] = "";
|
||||||
|
export let helperText: ComponentProps<Dropdown>["helperText"] = "";
|
||||||
|
export let label: ComponentProps<Dropdown>["label"] = undefined;
|
||||||
|
export let hideLabel: ComponentProps<Dropdown>["hideLabel"] = false;
|
||||||
|
export let translateWithId: ComponentProps<Dropdown>["translateWithId"] =
|
||||||
|
undefined;
|
||||||
|
export let id: ComponentProps<Dropdown>["id"] = "test-dropdown";
|
||||||
|
export let name: ComponentProps<Dropdown>["name"] = undefined;
|
||||||
|
export let ref: ComponentProps<Dropdown>["ref"] = null;
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<Dropdown
|
||||||
|
{items}
|
||||||
|
{itemToString}
|
||||||
|
bind:selectedId
|
||||||
|
{type}
|
||||||
|
{direction}
|
||||||
|
{size}
|
||||||
|
bind:open
|
||||||
|
{light}
|
||||||
|
{disabled}
|
||||||
|
{titleText}
|
||||||
|
{invalid}
|
||||||
|
{invalidText}
|
||||||
|
{warn}
|
||||||
|
{warnText}
|
||||||
|
{helperText}
|
||||||
|
{label}
|
||||||
|
{hideLabel}
|
||||||
|
{translateWithId}
|
||||||
|
{id}
|
||||||
|
{name}
|
||||||
|
bind:ref
|
||||||
|
on:select
|
||||||
|
{...$$restProps}
|
||||||
|
/>
|
266
tests/Dropdown/Dropdown.test.ts
Normal file
266
tests/Dropdown/Dropdown.test.ts
Normal file
|
@ -0,0 +1,266 @@
|
||||||
|
import { render, screen } from "@testing-library/svelte";
|
||||||
|
import { user } from "../setup-tests";
|
||||||
|
import Dropdown from "./Dropdown.test.svelte";
|
||||||
|
import DropdownSlot from "./DropdownSlot.test.svelte";
|
||||||
|
|
||||||
|
const items = [
|
||||||
|
{ id: "0", text: "Slack" },
|
||||||
|
{ id: "1", text: "Email" },
|
||||||
|
{ id: "2", text: "Fax" },
|
||||||
|
] as const;
|
||||||
|
|
||||||
|
describe("Dropdown", () => {
|
||||||
|
it("should render with default props", () => {
|
||||||
|
render(Dropdown, {
|
||||||
|
props: { items, selectedId: "0", titleText: "Contact" },
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(screen.getByText("Contact")).toBeInTheDocument();
|
||||||
|
const button = screen.getByRole("button");
|
||||||
|
expect(button.querySelector(".bx--list-box__label")).toHaveTextContent(
|
||||||
|
"Slack",
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should handle custom item display text", () => {
|
||||||
|
render(Dropdown, {
|
||||||
|
props: {
|
||||||
|
items,
|
||||||
|
selectedId: "0",
|
||||||
|
titleText: "Contact",
|
||||||
|
itemToString: (item) => `${item.text} (${item.id})`,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const button = screen.getByRole("button");
|
||||||
|
expect(button.querySelector(".bx--list-box__label")).toHaveTextContent(
|
||||||
|
"Slack (0)",
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should handle hidden label", () => {
|
||||||
|
render(Dropdown, {
|
||||||
|
props: {
|
||||||
|
items,
|
||||||
|
selectedId: "0",
|
||||||
|
titleText: "Contact",
|
||||||
|
hideLabel: true,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const label = screen.getByText("Contact");
|
||||||
|
expect(label).toHaveClass("bx--visually-hidden");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should handle light variant", () => {
|
||||||
|
render(Dropdown, {
|
||||||
|
props: {
|
||||||
|
items,
|
||||||
|
selectedId: "0",
|
||||||
|
light: true,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const button = screen.getByRole("button");
|
||||||
|
expect(button.closest(".bx--dropdown")).toHaveClass("bx--dropdown--light");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should handle inline variant", () => {
|
||||||
|
render(Dropdown, {
|
||||||
|
props: {
|
||||||
|
items,
|
||||||
|
selectedId: "0",
|
||||||
|
type: "inline",
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const button = screen.getByRole("button");
|
||||||
|
expect(button).toBeEnabled();
|
||||||
|
expect(button).toHaveTextContent("Slack");
|
||||||
|
expect(button.closest(".bx--dropdown__wrapper")).toHaveClass(
|
||||||
|
"bx--dropdown__wrapper--inline",
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should handle size variants", async () => {
|
||||||
|
const { rerender } = render(Dropdown, {
|
||||||
|
props: {
|
||||||
|
items,
|
||||||
|
selectedId: "0",
|
||||||
|
size: "sm",
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const button = screen.getByRole("button");
|
||||||
|
expect(button.closest(".bx--dropdown")).toHaveClass("bx--dropdown--sm");
|
||||||
|
|
||||||
|
await rerender({ items, selectedId: "0", size: "xl" });
|
||||||
|
expect(button.closest(".bx--dropdown")).toHaveClass("bx--dropdown--xl");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should handle invalid state", () => {
|
||||||
|
render(Dropdown, {
|
||||||
|
props: {
|
||||||
|
items,
|
||||||
|
selectedId: "0",
|
||||||
|
invalid: true,
|
||||||
|
invalidText: "Invalid selection",
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const button = screen.getByRole("button");
|
||||||
|
expect(button).toBeEnabled();
|
||||||
|
expect(button).toHaveTextContent("Slack");
|
||||||
|
expect(button.closest(".bx--dropdown")).toHaveAttribute(
|
||||||
|
"data-invalid",
|
||||||
|
"true",
|
||||||
|
);
|
||||||
|
expect(screen.getByText("Invalid selection")).toBeInTheDocument();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should handle warning state", () => {
|
||||||
|
render(Dropdown, {
|
||||||
|
props: {
|
||||||
|
items,
|
||||||
|
selectedId: "0",
|
||||||
|
warn: true,
|
||||||
|
warnText: "Warning message",
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const button = screen.getByRole("button");
|
||||||
|
expect(button).toBeEnabled();
|
||||||
|
expect(button).toHaveTextContent("Slack");
|
||||||
|
expect(button.closest(".bx--dropdown")).toHaveClass(
|
||||||
|
"bx--dropdown--warning",
|
||||||
|
);
|
||||||
|
expect(screen.getByText("Warning message")).toBeInTheDocument();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should handle disabled state", () => {
|
||||||
|
render(Dropdown, {
|
||||||
|
props: {
|
||||||
|
items,
|
||||||
|
selectedId: "0",
|
||||||
|
disabled: true,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(screen.queryByRole("listbox")).not.toBeInTheDocument();
|
||||||
|
expect(screen.getByRole("button")).toHaveAttribute("disabled");
|
||||||
|
expect(screen.getByRole("button")).toHaveTextContent("Slack");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should handle helper text", () => {
|
||||||
|
render(Dropdown, {
|
||||||
|
props: {
|
||||||
|
items,
|
||||||
|
selectedId: "0",
|
||||||
|
helperText: "Help text",
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(screen.getByText("Help text")).toHaveClass("bx--form__helper-text");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should handle item selection", async () => {
|
||||||
|
const { component } = render(Dropdown, {
|
||||||
|
props: {
|
||||||
|
items,
|
||||||
|
selectedId: "0",
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const selectHandler = vi.fn();
|
||||||
|
component.$on("select", selectHandler);
|
||||||
|
|
||||||
|
const button = screen.getByRole("button");
|
||||||
|
await user.click(button);
|
||||||
|
|
||||||
|
const menuItemText = screen.getByText("Email");
|
||||||
|
const menuItem = menuItemText.closest(".bx--list-box__menu-item");
|
||||||
|
expect(menuItem).not.toBeNull();
|
||||||
|
await user.click(menuItem!);
|
||||||
|
|
||||||
|
expect(selectHandler).toHaveBeenCalledWith(
|
||||||
|
expect.objectContaining({
|
||||||
|
detail: { selectedId: "1", selectedItem: items[1] },
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should handle keyboard navigation", async () => {
|
||||||
|
render(Dropdown, {
|
||||||
|
props: {
|
||||||
|
items,
|
||||||
|
selectedId: "0",
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const button = screen.getByRole("button");
|
||||||
|
await user.tab();
|
||||||
|
expect(button).toHaveFocus();
|
||||||
|
|
||||||
|
await user.keyboard("{Enter}");
|
||||||
|
expect(screen.getByRole("listbox")).toBeVisible();
|
||||||
|
expect(screen.getByRole("option", { selected: true })).toHaveTextContent(
|
||||||
|
"Slack",
|
||||||
|
);
|
||||||
|
|
||||||
|
await user.keyboard("{ArrowDown}{ArrowDown}");
|
||||||
|
await user.keyboard("{Enter}");
|
||||||
|
|
||||||
|
expect(screen.queryByRole("listbox")).not.toBeInTheDocument();
|
||||||
|
expect(button).toHaveTextContent("Email");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should handle disabled items", async () => {
|
||||||
|
const itemsWithDisabled = [
|
||||||
|
{ id: "0", text: "Slack" },
|
||||||
|
{ id: "1", text: "Email", disabled: true },
|
||||||
|
{ id: "2", text: "Fax" },
|
||||||
|
];
|
||||||
|
|
||||||
|
render(Dropdown, {
|
||||||
|
props: {
|
||||||
|
items: itemsWithDisabled,
|
||||||
|
selectedId: "0",
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const button = screen.getByRole("button");
|
||||||
|
await user.click(button);
|
||||||
|
|
||||||
|
const menuItemText = screen.getByText("Email");
|
||||||
|
const menuItem = menuItemText.closest(".bx--list-box__menu-item");
|
||||||
|
expect(menuItem).not.toBeNull();
|
||||||
|
expect(menuItem).toHaveAttribute("disabled");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should handle custom slot content", async () => {
|
||||||
|
render(DropdownSlot);
|
||||||
|
|
||||||
|
await user.click(screen.getByRole("button"));
|
||||||
|
|
||||||
|
const customItems = screen.getAllByTestId("custom-item");
|
||||||
|
expect(customItems).toHaveLength(3);
|
||||||
|
expect(customItems[0]).toHaveTextContent("Item 1: Option 1");
|
||||||
|
expect(customItems[1]).toHaveTextContent("Item 2: Option 2");
|
||||||
|
expect(customItems[2]).toHaveTextContent("Item 3: Option 3");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should close on outside click", async () => {
|
||||||
|
render(Dropdown, {
|
||||||
|
props: {
|
||||||
|
items,
|
||||||
|
selectedId: "0",
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
await user.click(screen.getByRole("button"));
|
||||||
|
expect(screen.getByRole("listbox")).toBeVisible();
|
||||||
|
|
||||||
|
await user.click(document.body);
|
||||||
|
expect(screen.queryByRole("listbox")).not.toBeInTheDocument();
|
||||||
|
});
|
||||||
|
});
|
25
tests/Dropdown/DropdownSlot.test.svelte
Normal file
25
tests/Dropdown/DropdownSlot.test.svelte
Normal file
|
@ -0,0 +1,25 @@
|
||||||
|
<script lang="ts">
|
||||||
|
import { Dropdown } from "carbon-components-svelte";
|
||||||
|
import type { ComponentProps } from "svelte";
|
||||||
|
|
||||||
|
export let items: ComponentProps<Dropdown>["items"] = [
|
||||||
|
{ id: "0", text: "Option 1" },
|
||||||
|
{ id: "1", text: "Option 2" },
|
||||||
|
{ id: "2", text: "Option 3" },
|
||||||
|
];
|
||||||
|
export let selectedId: ComponentProps<Dropdown>["selectedId"] = "0";
|
||||||
|
export let id: ComponentProps<Dropdown>["id"] = "test-dropdown-slot";
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<Dropdown
|
||||||
|
{items}
|
||||||
|
{selectedId}
|
||||||
|
{id}
|
||||||
|
titleText="Custom slot dropdown"
|
||||||
|
let:item
|
||||||
|
let:index
|
||||||
|
>
|
||||||
|
<span data-testid="custom-item">
|
||||||
|
Item {index + 1}: {item.text}
|
||||||
|
</span>
|
||||||
|
</Dropdown>
|
Loading…
Add table
Add a link
Reference in a new issue