Skip to main content
Glama
MultiSelectCombobox.test.tsx18.1 kB
import { render, screen, waitFor } from "@testing-library/react"; import userEvent from "@testing-library/user-event"; import { MultiSelectCombobox, MultiSelectValue } from "./MultiSelectCombobox"; function CustomOption({ label, inButton, }: { label: string; inButton: boolean; }) { void inButton; return <span data-testid={`custom-${label}`}>{label.toUpperCase()}</span>; } describe("MultiSelectCombobox", () => { beforeEach(jest.clearAllMocks); const defaultProps = { options: ["Apple", "Banana", "Cherry", "Date", "Elderberry"], unit: "item", unitPlural: "items", label: "Select Items", }; const setup = ( props: Partial<typeof defaultProps> & { selectedOptions: MultiSelectValue; setSelectedOptions: (value: MultiSelectValue) => void; }, ) => render(<MultiSelectCombobox {...defaultProps} {...props} />); describe("Basic rendering", () => { it("renders with label and button", () => { const setSelectedOptions = jest.fn(); setup({ selectedOptions: [], setSelectedOptions }); expect(screen.getByText("Select Items")).toBeInTheDocument(); expect(screen.getByRole("button")).toBeInTheDocument(); }); it("renders with hidden label when labelHidden is true", () => { const setSelectedOptions = jest.fn(); render( <MultiSelectCombobox {...defaultProps} selectedOptions={[]} setSelectedOptions={setSelectedOptions} labelHidden />, ); expect(screen.getByText("Select Items")).toHaveAttribute("hidden"); }); it("displays correct count when no items selected", () => { const setSelectedOptions = jest.fn(); setup({ selectedOptions: [], setSelectedOptions }); expect(screen.getByText("0 items")).toBeInTheDocument(); }); it("displays correct count when one item selected", () => { const setSelectedOptions = jest.fn(); setup({ selectedOptions: ["Apple"], setSelectedOptions }); expect(screen.getByText("1 item")).toBeInTheDocument(); }); it("displays correct count when multiple items selected", () => { const setSelectedOptions = jest.fn(); setup({ selectedOptions: ["Apple", "Banana"], setSelectedOptions }); expect(screen.getByText("2 items")).toBeInTheDocument(); }); it("displays 'All items' when selectedOptions is 'all'", () => { const setSelectedOptions = jest.fn(); setup({ selectedOptions: "all", setSelectedOptions }); expect(screen.getByText("All items")).toBeInTheDocument(); }); }); describe("Opening and closing dropdown", () => { it("opens dropdown when button is clicked", async () => { const setSelectedOptions = jest.fn(); setup({ selectedOptions: [], setSelectedOptions }); const user = userEvent.setup(); const button = screen.getByRole("button"); await user.click(button); await waitFor(() => { expect(screen.getByText("Select all")).toBeInTheDocument(); }); }); it("closes dropdown when button is clicked again", async () => { const setSelectedOptions = jest.fn(); setup({ selectedOptions: [], setSelectedOptions }); const user = userEvent.setup(); const button = screen.getByRole("button"); await user.click(button); await waitFor(() => { expect(screen.getByText("Select all")).toBeInTheDocument(); }); await user.click(button); await waitFor(() => { expect(screen.queryByText("Select all")).not.toBeInTheDocument(); }); }); it("displays all options when dropdown is open", async () => { const setSelectedOptions = jest.fn(); setup({ selectedOptions: [], setSelectedOptions }); const user = userEvent.setup(); await user.click(screen.getByRole("button")); await waitFor(() => { expect(screen.getByText("Apple")).toBeInTheDocument(); expect(screen.getByText("Banana")).toBeInTheDocument(); expect(screen.getByText("Cherry")).toBeInTheDocument(); expect(screen.getByText("Date")).toBeInTheDocument(); expect(screen.getByText("Elderberry")).toBeInTheDocument(); }); }); }); describe("Selecting and deselecting options", () => { it("selects an option when clicked", async () => { const setSelectedOptions = jest.fn(); setup({ selectedOptions: [], setSelectedOptions }); const user = userEvent.setup(); await user.click(screen.getByRole("button")); await waitFor(() => { expect(screen.getByText("Apple")).toBeInTheDocument(); }); await user.click(screen.getByText("Apple")); expect(setSelectedOptions).toHaveBeenCalledWith(["Apple"]); }); it("deselects an option when clicked again", async () => { const setSelectedOptions = jest.fn(); setup({ selectedOptions: ["Apple"], setSelectedOptions }); const user = userEvent.setup(); await user.click(screen.getByRole("button")); await waitFor(() => { expect(screen.getByText("Apple")).toBeInTheDocument(); }); await user.click(screen.getByText("Apple")); expect(setSelectedOptions).toHaveBeenCalledWith([]); }); it("selects multiple options", async () => { const setSelectedOptions = jest.fn(); setup({ selectedOptions: ["Apple"], setSelectedOptions }); const user = userEvent.setup(); await user.click(screen.getByRole("button")); await waitFor(() => { expect(screen.getByText("Banana")).toBeInTheDocument(); }); await user.click(screen.getByText("Banana")); expect(setSelectedOptions).toHaveBeenCalledWith(["Apple", "Banana"]); }); it("converts to 'all' state when all options are selected", async () => { const setSelectedOptions = jest.fn(); setup({ selectedOptions: ["Apple", "Banana", "Cherry", "Date"], setSelectedOptions, }); const user = userEvent.setup(); await user.click(screen.getByRole("button")); await waitFor(() => { expect(screen.getByText("Elderberry")).toBeInTheDocument(); }); await user.click(screen.getByText("Elderberry")); expect(setSelectedOptions).toHaveBeenCalledWith("all"); }); }); describe("Select all / Deselect all functionality", () => { it("shows 'Select all' button when not all items are selected", async () => { const setSelectedOptions = jest.fn(); setup({ selectedOptions: [], setSelectedOptions }); const user = userEvent.setup(); await user.click(screen.getByRole("button")); await waitFor(() => { expect(screen.getByText("Select all")).toBeInTheDocument(); }); }); it("shows 'Deselect all' button when all items are selected", async () => { const setSelectedOptions = jest.fn(); setup({ selectedOptions: "all", setSelectedOptions }); const user = userEvent.setup(); await user.click(screen.getByRole("button")); await waitFor(() => { expect(screen.getByText("Deselect all")).toBeInTheDocument(); }); }); it("selects all options when 'Select all' is clicked", async () => { const setSelectedOptions = jest.fn(); setup({ selectedOptions: [], setSelectedOptions }); const user = userEvent.setup(); await user.click(screen.getByRole("button")); await waitFor(() => { expect(screen.getByText("Select all")).toBeInTheDocument(); }); await user.click(screen.getByText("Select all")); expect(setSelectedOptions).toHaveBeenCalledWith("all"); }); it("deselects all options when 'Deselect all' is clicked", async () => { const setSelectedOptions = jest.fn(); setup({ selectedOptions: "all", setSelectedOptions }); const user = userEvent.setup(); await user.click(screen.getByRole("button")); await waitFor(() => { expect(screen.getByText("Deselect all")).toBeInTheDocument(); }); await user.click(screen.getByText("Deselect all")); expect(setSelectedOptions).toHaveBeenCalledWith([]); }); }); describe("Search functionality", () => { it("displays search input by default", async () => { const setSelectedOptions = jest.fn(); setup({ selectedOptions: [], setSelectedOptions }); const user = userEvent.setup(); await user.click(screen.getByRole("button")); await waitFor(() => { expect( screen.getByPlaceholderText("Search items..."), ).toBeInTheDocument(); }); }); it("does not display search input when disableSearch is true", async () => { const setSelectedOptions = jest.fn(); render( <MultiSelectCombobox {...defaultProps} selectedOptions={[]} setSelectedOptions={setSelectedOptions} disableSearch />, ); const user = userEvent.setup(); await user.click(screen.getByRole("button")); await waitFor(() => { expect(screen.getByText("Select all")).toBeInTheDocument(); }); expect( screen.queryByPlaceholderText("Search items..."), ).not.toBeInTheDocument(); }); it("filters options based on search query", async () => { const setSelectedOptions = jest.fn(); setup({ selectedOptions: [], setSelectedOptions }); const user = userEvent.setup(); await user.click(screen.getByRole("button")); await waitFor(() => { expect( screen.getByPlaceholderText("Search items..."), ).toBeInTheDocument(); }); const searchInput = screen.getByPlaceholderText("Search items..."); await user.type(searchInput, "ban"); await waitFor(() => { expect(screen.getByText("Banana")).toBeInTheDocument(); expect(screen.queryByText("Apple")).not.toBeInTheDocument(); expect(screen.queryByText("Cherry")).not.toBeInTheDocument(); }); }); it("shows all options when search query is cleared", async () => { const setSelectedOptions = jest.fn(); setup({ selectedOptions: [], setSelectedOptions }); const user = userEvent.setup(); await user.click(screen.getByRole("button")); await waitFor(() => { expect( screen.getByPlaceholderText("Search items..."), ).toBeInTheDocument(); }); const searchInput = screen.getByPlaceholderText("Search items..."); await user.type(searchInput, "ban"); await waitFor(() => { expect(screen.getByText("Banana")).toBeInTheDocument(); expect(screen.queryByText("Apple")).not.toBeInTheDocument(); }); await user.clear(searchInput); await waitFor(() => { expect(screen.getByText("Apple")).toBeInTheDocument(); expect(screen.getByText("Banana")).toBeInTheDocument(); expect(screen.getByText("Cherry")).toBeInTheDocument(); }); }); }); describe("'Only' button functionality", () => { it("displays 'only' button on hover for each option", async () => { const setSelectedOptions = jest.fn(); setup({ selectedOptions: [], setSelectedOptions }); const user = userEvent.setup(); await user.click(screen.getByRole("button")); await waitFor(() => { expect(screen.getByText("Apple")).toBeInTheDocument(); }); const appleOption = screen.getByText("Apple").closest("div"); expect(appleOption).toBeInTheDocument(); const onlyButtons = screen.getAllByText("only"); expect(onlyButtons.length).toBeGreaterThan(0); }); it("selects only the clicked option when 'only' is clicked", async () => { const setSelectedOptions = jest.fn(); setup({ selectedOptions: ["Apple", "Banana"], setSelectedOptions }); const user = userEvent.setup(); await user.click(screen.getByRole("button")); await waitFor(() => { expect(screen.getByText("Cherry")).toBeInTheDocument(); }); const onlyButtons = screen.getAllByText("only"); const cherryOnlyButton = onlyButtons[2]; await user.click(cherryOnlyButton); expect(setSelectedOptions).toHaveBeenCalledWith(["Cherry"]); }); }); describe("Max displayed options", () => { it("shows message when more than 100 options are available", async () => { const manyOptions = Array.from( { length: 150 }, (_, i) => `Option ${i + 1}`, ); const setSelectedOptions = jest.fn(); render( <MultiSelectCombobox {...defaultProps} options={manyOptions} selectedOptions={[]} setSelectedOptions={setSelectedOptions} />, ); const user = userEvent.setup(); await user.click(screen.getByRole("button")); await waitFor(() => { expect( screen.getByText( /Too many items to display, use the searchbar to filter items/, ), ).toBeInTheDocument(); }); }); it("displays only first 100 options when more are available", async () => { const manyOptions = Array.from( { length: 150 }, (_, i) => `Option ${i + 1}`, ); const setSelectedOptions = jest.fn(); render( <MultiSelectCombobox {...defaultProps} options={manyOptions} selectedOptions={[]} setSelectedOptions={setSelectedOptions} />, ); const user = userEvent.setup(); await user.click(screen.getByRole("button")); await waitFor(() => { expect(screen.getByText("Option 1")).toBeInTheDocument(); expect(screen.getByText("Option 100")).toBeInTheDocument(); expect(screen.queryByText("Option 101")).not.toBeInTheDocument(); }); }); it("filters down options when searching with many options", async () => { const manyOptions = Array.from( { length: 150 }, (_, i) => `Option ${i + 1}`, ); const setSelectedOptions = jest.fn(); render( <MultiSelectCombobox {...defaultProps} options={manyOptions} selectedOptions={[]} setSelectedOptions={setSelectedOptions} />, ); const user = userEvent.setup(); await user.click(screen.getByRole("button")); await waitFor(() => { expect( screen.getByPlaceholderText("Search items..."), ).toBeInTheDocument(); }); const searchInput = screen.getByPlaceholderText("Search items..."); await user.type(searchInput, "Option 1"); await waitFor(() => { expect(screen.getByText("Option 1")).toBeInTheDocument(); expect(screen.queryByText("Option 2")).not.toBeInTheDocument(); }); }); }); describe("Custom Option component", () => { it("renders custom Option component when provided", async () => { const setSelectedOptions = jest.fn(); render( <MultiSelectCombobox {...defaultProps} selectedOptions={[]} setSelectedOptions={setSelectedOptions} Option={CustomOption} />, ); const user = userEvent.setup(); await user.click(screen.getByRole("button")); await waitFor(() => { expect(screen.getByTestId("custom-Apple")).toBeInTheDocument(); expect(screen.getByText("APPLE")).toBeInTheDocument(); }); }); }); describe("processFilterOption", () => { it("uses processFilterOption to filter options", async () => { const setSelectedOptions = jest.fn(); const processFilterOption = (option: string) => option.toLowerCase(); render( <MultiSelectCombobox {...defaultProps} selectedOptions={[]} setSelectedOptions={setSelectedOptions} processFilterOption={processFilterOption} />, ); const user = userEvent.setup(); await user.click(screen.getByRole("button")); await waitFor(() => { expect( screen.getByPlaceholderText("Search items..."), ).toBeInTheDocument(); }); const searchInput = screen.getByPlaceholderText("Search items..."); await user.type(searchInput, "APPLE"); await waitFor(() => { expect(screen.getByText("Apple")).toBeInTheDocument(); }); }); }); describe("Accessibility", () => { it("has proper ARIA attributes", () => { const setSelectedOptions = jest.fn(); setup({ selectedOptions: [], setSelectedOptions }); const button = screen.getByRole("button"); expect(button).toBeInTheDocument(); }); it("sets tabindex to 0 on the button", async () => { const setSelectedOptions = jest.fn(); setup({ selectedOptions: [], setSelectedOptions }); await waitFor(() => { const button = screen.getByRole("button"); expect(button).toHaveAttribute("tabindex", "0"); }); }); }); describe("Edge cases", () => { it("handles empty options array", async () => { const setSelectedOptions = jest.fn(); render( <MultiSelectCombobox {...defaultProps} options={[]} selectedOptions={[]} setSelectedOptions={setSelectedOptions} />, ); const user = userEvent.setup(); await user.click(screen.getByRole("button")); await waitFor(() => { expect(screen.getByText("Select all")).toBeInTheDocument(); }); }); it("filters out _other from count", () => { const setSelectedOptions = jest.fn(); setup({ selectedOptions: ["Apple", "_other"], setSelectedOptions }); expect(screen.getByText("1 item")).toBeInTheDocument(); }); it("handles selectedOptions with _other correctly", () => { const setSelectedOptions = jest.fn(); setup({ selectedOptions: ["Apple", "Banana", "_other"], setSelectedOptions, }); expect(screen.getByText("2 items")).toBeInTheDocument(); }); }); });

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/get-convex/convex-backend'

If you have feedback or need assistance with the MCP directory API, please join our Discord server