import { render, screen } from "@testing-library/react"; import userEvent from "@testing-library/user-event"; import React from "react"; import { describe, expect, test, vi } from "vitest"; import { useId } from "../../../utils-external"; import nb from "../../../utils/i18n/locales/nb"; import { ComboboxProps, UNSAFE_Combobox } from "../index"; const options = [ "apple", "apple pie", "banana", "grape", "grape fruit", "kiwi", "mango", "passion fruit", "pear", "pineapple", "strawberry", "tangerine", "watermelon", ]; const App = ({ label = "Hva er dine favorittfrukter?", ...rest }: Omit & { label?: ComboboxProps["label"] }) => { const id = useId(); return (
); }; describe("Render combobox", () => { describe("with multi select", () => { test("Should be able to search, select and remove selections", async () => { render(); const input = screen.getByRole("combobox", { name: "Hva er dine favorittfrukter?", }); await userEvent.click(input); await userEvent.type(input, "apple"); await userEvent.click( await screen.findByRole("option", { name: "apple" }), ); expect( await screen.findByRole("option", { name: "apple", selected: true }), ).toBeInTheDocument(); await userEvent.click( await screen.findByRole("button", { name: "apple slett" }), ); }); }); describe("Combobox state-handling", () => { test("Should show loading icon when loading (used for async search)", async () => { render(); expect(await screen.findByText(nb.Combobox.loading)).toBeInTheDocument(); }); test("Should not select previous focused element when closes", async () => { render(); const input = screen.getByRole("combobox", { name: "Hva er dine favorittfrukter?", }); await userEvent.click(input); await userEvent.type(input, "ban"); await userEvent.keyboard("{ArrowDown}"); await userEvent.keyboard("{ArrowUp}"); await userEvent.keyboard("{Enter}"); expect(screen.queryByRole("button", { name: "banana slett" })).toBeNull(); }); test("Should reset list when resetting input (ESC)", async () => { render(); const input = screen.getByRole("combobox", { name: "Hva er dine favorittfrukter?", }); await userEvent.click(input); await userEvent.type(input, "apple"); await userEvent.keyboard("{ArrowDown}"); await userEvent.keyboard("{Escape}"); await userEvent.keyboard("{ArrowDown}"); expect( await screen.findByRole("option", { name: "banana" }), ).toBeInTheDocument(); }); test("Should handle complex options with label and value", async () => { const onToggleSelected = vi.fn(); render( , ); expect(screen.getByRole("combobox")).toBeInTheDocument(); const option = screen.getByRole("option", { name: "Hjelpemidler [HJE]", selected: false, }); await userEvent.click(option); expect(onToggleSelected).toHaveBeenCalledWith("HJE", true, false); expect( screen.getByRole("option", { name: "Hjelpemidler [HJE]", selected: true, }), ).toBeInTheDocument(); }); test("should trigger onChange for every character typed or removed", async () => { const onChange = vi.fn(); const onToggleSelected = vi.fn(); render( , ); const combobox = screen.getByRole("combobox"); expect(combobox).toBeInTheDocument(); await userEvent.click(combobox); await userEvent.type(combobox, "Lemon"); expect(onChange).toHaveBeenNthCalledWith(1, "L"); expect(onChange).toHaveBeenNthCalledWith(2, "Le"); expect(onChange).toHaveBeenNthCalledWith(3, "Lem"); expect(onChange).toHaveBeenNthCalledWith(4, "Lemo"); expect(onChange).toHaveBeenNthCalledWith(5, "Lemon"); }); test("should trigger onChange while typing and on accepting autocomplete suggestions", async () => { const onChange = vi.fn(); const onToggleSelected = vi.fn(); render( , ); const combobox = screen.getByRole("combobox"); expect(combobox).toBeInTheDocument(); await userEvent.click(combobox); await userEvent.type(combobox, "Syke"); await userEvent.keyboard("{ArrowRight}"); await userEvent.keyboard("{ArrowDown}"); await userEvent.keyboard("{Enter}"); expect(onChange).toHaveBeenNthCalledWith(1, "S"); expect(onChange).toHaveBeenNthCalledWith(2, "Sy"); expect(onChange).toHaveBeenNthCalledWith(3, "Syk"); expect(onChange).toHaveBeenNthCalledWith(4, "Syke"); expect(onChange).toHaveBeenNthCalledWith(5, "Sykepenger [SYK]"); expect(onChange).toHaveBeenCalledWith(""); expect(onToggleSelected).toHaveBeenCalledExactlyOnceWith( "Sykepenger [SYK]", true, false, ); }); }); describe("search", () => { test("should find matches anywhere in the label", async () => { render(); const combobox = screen.getByRole("combobox", { name: "Hva er dine favorittfrukter?", }); await userEvent.click(combobox); await userEvent.type(combobox, "p"); const searchHits = [ "apple", "apple pie", "pear", "grape", "passion fruit", "pineapple", "grape fruit", ]; searchHits.forEach((label) => { expect(screen.getByRole("option", { name: label })).toBeInTheDocument(); }); screen.getAllByRole("option").forEach((option) => { expect( option.textContent && searchHits.includes(option.textContent), ).toBe(true); }); }); test("and pressing enter to select autocompleted word will select the correct word", async () => { const onToggleSelected = vi.fn(); render( , ); const combobox = screen.getByRole("combobox", { name: "Hva er dine favorittfrukter?", }); await userEvent.click(combobox); await userEvent.type(combobox, "p"); expect(combobox.getAttribute("value")).toBe("passion fruit"); await userEvent.keyboard("{Enter}"); expect(onToggleSelected).toHaveBeenCalledWith( "passion fruit", true, false, ); }); test("and pressing enter to select autocompleted word will select existing word when addNewOptions is true", async () => { const onToggleSelected = vi.fn(); render( ({ label: `${opt} (${opt})`, value: opt, }))} shouldAutocomplete allowNewValues />, ); const combobox = screen.getByRole("combobox", { name: "Hva er dine favorittfrukter?", }); await userEvent.click(combobox); await userEvent.type(combobox, "p"); expect(combobox.getAttribute("value")).toBe( "passion fruit (passion fruit)", ); await userEvent.keyboard("{Enter}"); expect(onToggleSelected).toHaveBeenCalledWith( "passion fruit", true, false, ); }); }); describe("has keyboard navigation", () => { test("for PageDown and PageUp", async () => { render(); const combobox = screen.getByRole("combobox", { name: "Hva er dine favorittfrukter?", }); const pressKey = async (key: string) => { await userEvent.keyboard(`{${key}}`); }; const hasVirtualFocus = (option: string) => expect(combobox.getAttribute("aria-activedescendant")).toBe( screen.getByRole("option", { name: option }).id, ); await userEvent.click(combobox); await pressKey("ArrowDown"); hasVirtualFocus("apple"); await pressKey("PageDown"); hasVirtualFocus("mango"); await pressKey("PageDown"); hasVirtualFocus("watermelon"); await pressKey("PageUp"); hasVirtualFocus("mango"); await pressKey("PageUp"); hasVirtualFocus("apple"); }); }); });