From 0b799d64b7ec5a7d774f239b42b854810b03fdc9 Mon Sep 17 00:00:00 2001 From: Eric Liu Date: Thu, 20 Mar 2025 16:28:46 -0700 Subject: [PATCH] test(dropdown): add unit tests --- tests/Dropdown.test.svelte | 115 ---------- tests/Dropdown/Dropdown.test.svelte | 53 +++++ tests/Dropdown/Dropdown.test.ts | 266 ++++++++++++++++++++++++ tests/Dropdown/DropdownSlot.test.svelte | 25 +++ 4 files changed, 344 insertions(+), 115 deletions(-) delete mode 100644 tests/Dropdown.test.svelte create mode 100644 tests/Dropdown/Dropdown.test.svelte create mode 100644 tests/Dropdown/Dropdown.test.ts create mode 100644 tests/Dropdown/DropdownSlot.test.svelte diff --git a/tests/Dropdown.test.svelte b/tests/Dropdown.test.svelte deleted file mode 100644 index 3ba410d6..00000000 --- a/tests/Dropdown.test.svelte +++ /dev/null @@ -1,115 +0,0 @@ - - - { - console.log(e.detail.selectedId); - }} - translateWithId={(id) => { - console.log(id); // "open" | "close" - return id; - }} - let:item - let:index -> - {item.id} - {index} - - - { - return item.text + " (" + item.id + ")"; - }} - titleText="Contact" - selectedId="0" - items={itemsWithoutConst} -/> - - - - - - - - - - - - diff --git a/tests/Dropdown/Dropdown.test.svelte b/tests/Dropdown/Dropdown.test.svelte new file mode 100644 index 00000000..6e5663f9 --- /dev/null +++ b/tests/Dropdown/Dropdown.test.svelte @@ -0,0 +1,53 @@ + + + diff --git a/tests/Dropdown/Dropdown.test.ts b/tests/Dropdown/Dropdown.test.ts new file mode 100644 index 00000000..4154d81e --- /dev/null +++ b/tests/Dropdown/Dropdown.test.ts @@ -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(); + }); +}); diff --git a/tests/Dropdown/DropdownSlot.test.svelte b/tests/Dropdown/DropdownSlot.test.svelte new file mode 100644 index 00000000..7be8caad --- /dev/null +++ b/tests/Dropdown/DropdownSlot.test.svelte @@ -0,0 +1,25 @@ + + + + + Item {index + 1}: {item.text} + +