import userEvent from "@testing-library/user-event"; import { render, screen, waitFor } from "@testing-library/react"; import { expect } from "vitest"; import React, { createRef, FormEvent, useState } from "react"; import { within } from "@testing-library/dom"; import { Select, Option, SelectHandle, SelectProps, } from ":/components/Forms/Select/index"; import { Button } from ":/components/Button"; import { CunninghamProvider } from ":/components/Provider"; import { expectMenuToBeClosed, expectMenuToBeOpen, expectNoOptions, expectOptions, expectOptionToBeDisabled, expectOptionToBeSelected, expectOptionToBeUnselected, } from ":/components/Forms/Select/test-utils"; import { Input } from ":/components/Forms/Input"; describe(" , ); const input = screen.getByRole("combobox", { name: "City", }); // It returns the input. expect(input.tagName).toEqual("INPUT"); const menu: HTMLDivElement = screen.getByRole("listbox", { name: "City", }); expectMenuToBeClosed(menu); // Click on the input. await user.click(input); expectMenuToBeOpen(menu); expectOptions(["Paris", "Panama", "London", "New York", "Tokyo"]); // Select an option. const option: HTMLLIElement = screen.getByRole("option", { name: "New York", }); await user.click(option); // The menu should be closed. expectMenuToBeClosed(menu); // The input should have the selected value. expect(input).toHaveValue("New York"); }); it("filters options when typing", async () => { const user = userEvent.setup(); render( setValue(e.target.value as any)} /> ); }; render(); const user = userEvent.setup(); const input = screen.getByRole("combobox", { name: "City", }); screen.getByText("Value = |"); // It returns the input. expect(input.tagName).toEqual("INPUT"); const menu: HTMLDivElement = screen.getByRole("listbox", { name: "City", }); expectMenuToBeClosed(menu); // Click on the input. await user.click(input); expectMenuToBeOpen(menu); expectOptions(["Paris", "Panama", "London", "New York", "Tokyo"]); // Verify that filtering works. await user.type(input, "Pa"); expectMenuToBeOpen(menu); expectOptions(["Paris", "Panama"]); await user.type(input, "rr", { skipClick: true }); expectNoOptions(); expect(input).toHaveValue("Parr"); // This is a way to blur the combobox. await user.click(screen.getByRole("textbox")); expect(input).toHaveValue(""); screen.getByText("Value = |"); }); it("clears the added text to the existing value input on blur if no other item is selected", async () => { const Wrapper = () => { const [value, setValue] = useState( "london", ); return (
Value = {value}|
); }; render(); const user = userEvent.setup(); const input = screen.getByRole("combobox", { name: "City", }); screen.getByText("Value = london|"); // It returns the input. expect(input.tagName).toEqual("INPUT"); expect(input).toHaveValue("London"); await user.type(input, "rr"); expect(input).toHaveValue("Londonrr"); // This is a way to blur the combobox. await user.click(screen.getByRole("textbox")); expect(input).toHaveValue("London"); screen.getByText("Value = london|"); }); it("clears value", async () => { render( , ); const input = screen.getByRole("combobox", { name: "City", }); expect(input).toHaveValue("New York"); }); it("should select with defaultValue using value", async () => { render( , ); const input = screen.getByRole("combobox", { name: "City", }); expect(input).toHaveValue(""); }); it("works controlled", async () => { const Wrapper = () => { const [value, setValue] = useState( "london", ); const [onChangeCounts, setOnChangeCounts] = useState(0); return (
Value = {value}|
onChangeCounts = {onChangeCounts}|
, ); const input = screen.getByRole("combobox", { name: "City", }); expect(input).toHaveAttribute("disabled"); const button: HTMLButtonElement = document.querySelector( ".c__select__inner__actions__open", )!; expect(button).toBeDisabled(); const menu: HTMLDivElement = screen.getByRole("listbox", { name: "City", }); expectMenuToBeClosed(menu); const user = userEvent.setup(); // Try to open the menu. await user.click(input); // Make sure menu is still closed. expectMenuToBeClosed(menu); // Make sure no value is rendered const valueRendered = document.querySelector(".c__select__inner__value"); expect(valueRendered).toHaveTextContent(""); // Try to type await user.type(input, "Pa"); expectMenuToBeClosed(menu); }); it("submits form data", async () => { let formData: any; const Wrapper = () => { const onSubmit = (e: FormEvent) => { e.preventDefault(); const data = new FormData(e.currentTarget); formData = { city: data.get("city"), }; }; return (
); }; const { rerender } = render(, { wrapper: CunninghamProvider, }); const user = userEvent.setup(); const input = screen.getByRole("combobox", { name: "City", }); const menu: HTMLDivElement = screen.getByRole("listbox", { name: "City", }); // Check init value (defaultValue / value / nothing) expect(input).toHaveValue(expected); // Add filter await user.clear(input); await user.type(input, "Pa"); expectMenuToBeOpen(menu); expectOptions(["Paris", "Panama"]); myOptions.shift(); // Rerender the select with the options mutated rerender(); expectMenuToBeOpen(menu); // Options is refreshed expectOptions(["Panama"]); // Filter is still active expect(input).toHaveValue("Pa"); myOptions.shift(); // Rerender the select with the options mutated (only london left) rerender(); // Filter is still active expect(input).toHaveValue("Pa"); expect(screen.getByText("No options available")).toBeInTheDocument(); await user.clear(input); expectOptions(["London"]); await user.click( screen.getByRole("option", { name: "London", }), ); expect(input).toHaveValue("London"); }); }); it("blurs from ref", async () => { const ref = createRef(); render( ); }; const props: SelectProps = { label: "City", searchable: true, options: [ { label: "Paris", value: "paris", render: () => (
Paris flag Paris
), }, { label: "Panama", value: "panama", render: () => (
Panama flag Panama
), }, { label: "London", value: "london", render: () => (
London flag London
), }, ], }; const { rerender } = render(); const input = screen.getByRole("combobox", { name: "City", }); const menu: HTMLDivElement = screen.getByRole("listbox", { name: "City", }); const blurButton = screen.getByRole("button", { name: "Blur" }); const user = userEvent.setup(); const valueRendered = document.querySelector( ".c__select__inner__value", ) as HTMLElement; await user.click(input); expectMenuToBeOpen(menu); screen.getByRole("img", { name: "Paris flag" }); screen.getByRole("img", { name: "Panama flag" }); screen.getByRole("img", { name: "London flag" }); await user.type(input, "Pa"); screen.getByRole("img", { name: "Paris flag" }); screen.getByRole("img", { name: "Panama flag" }); expect( screen.queryByRole("img", { name: "London flag" }), ).not.toBeInTheDocument(); await user.click( screen.getByRole("option", { name: "Paris flag Paris" }), ); await user.click(blurButton); // Make sure only the label is rendered by default. expect(input).toHaveValue("Paris"); expect(input).not.toHaveClass("c__select__inner__value__input--hidden"); expect( within(valueRendered).queryByRole("img", { name: "Paris flag", }), ).not.toBeInTheDocument(); // Now showLabelWhenSelected to false. rerender(); // Make sure the HTML content of the option is rendered. // The input is still present in the DOM ( but hidden for users ). expect(input).toHaveValue("Paris"); expect(input).toHaveClass("c__select__inner__value__input--hidden"); within(valueRendered).getByRole("img", { name: "Paris flag", }); // Focus on the input and make sure the custom HTML is removed. await user.click(input); expect(input).toHaveValue("Paris"); expect(input).not.toHaveClass("c__select__inner__value__input--hidden"); expect( within(valueRendered).queryByRole("img", { name: "Paris flag", }), ).not.toBeInTheDocument(); // Blur the input and make sure the custom HTML is rendered. await user.click(blurButton); expect(input).toHaveValue("Paris"); expect(input).toHaveClass("c__select__inner__value__input--hidden"); within(valueRendered).getByRole("img", { name: "Paris flag", }); }); it("get the search term using onSearchInputChange", async () => { const user = userEvent.setup(); let searchTerm: string | undefined; render( , ); const input = screen.getByRole("combobox", { name: "City", }); const menu: HTMLDivElement = screen.getByRole("listbox", { name: "City", }); const label = screen.getByText("City")!.parentElement!; const valueRendered = document.querySelector(".c__select__inner__value"); // Make sure no value is rendered. expect(valueRendered).toHaveTextContent(""); expectMenuToBeClosed(menu); expect( screen.queryByRole("option", { name: "Paris" }), ).not.toBeInTheDocument(); // Make sure the label is set as placeholder. expect(Array.from(label.classList)).toContain("placeholder"); await user.click(input); // Make sure the menu is opened and options are rendered. expectMenuToBeOpen(menu); expect( screen.queryByRole("option", { name: "Paris" }), ).toBeInTheDocument(); // Make sure the option is not selected. let option: HTMLLIElement = screen.getByRole("option", { name: "London", }); expectOptionToBeUnselected(option); // Select an option. await user.click(option); // Make sure option is selected. expect(valueRendered).toHaveTextContent("London"); expect(Array.from(label.classList)).not.toContain("placeholder"); // Make sure menu is automatically closed. expectMenuToBeClosed(menu); // Open it again await user.click(input); expectMenuToBeOpen(menu); // Make sure the option is marked as selected. option = screen.getByRole("option", { name: "London" }); expectOptionToBeSelected(option); // Clear selection. const clearButton = screen.getByRole("button", { name: "Clear selection", }); await userEvent.click(clearButton); // Make sure value is cleared. expect(valueRendered).toHaveTextContent(""); expect(Array.from(label.classList)).toContain("placeholder"); // Make sure the option is unselected. option = screen.getByRole("option", { name: "London" }); await waitFor(() => expectOptionToBeUnselected(option)); }); it("should select with defaultValue using label", () => { render( , ); const menu: HTMLDivElement = screen.getByRole("listbox", { name: "City", }); const label = screen.getByText("City"); const valueRendered = document.querySelector(".c__select__inner__value"); // Make sure option is selected. expect(valueRendered).toHaveTextContent("New York"); expect(Array.from(label.classList)).not.toContain("placeholder"); // Make sure menu is automatically closed. expectMenuToBeClosed(menu); }); it("works controlled", async () => { const Wrapper = () => { const [value, setValue] = useState( "london", ); return (
Value = {value}|
, ); const input = screen.getByRole("combobox", { name: "City", }); expect(input).toHaveAttribute("disabled"); const button: HTMLButtonElement = document.querySelector( ".c__select__inner__actions__open", )!; expect(button).toBeDisabled(); const menu: HTMLDivElement = screen.getByRole("listbox", { name: "City", }); expectMenuToBeClosed(menu); const user = userEvent.setup(); // Try to open the menu. await user.click(input); // Make sure menu is still closed. expectMenuToBeClosed(menu); // Make sure no value is rendered const valueRendered = document.querySelector(".c__select__inner__value"); expect(valueRendered).toHaveTextContent(""); }); it("renders with text", async () => { render( , ); expect( document.querySelector(".c__select.c__select--error"), ).toBeInTheDocument(); }); it("renders with state=success", async () => { render(
); }; render(); const user = userEvent.setup(); const input = screen.getByRole("combobox", { name: "City", }); const menu: HTMLDivElement = screen.getByRole("listbox", { name: "City", }); const button = screen.getByRole("button", { name: "Submit", }); // Submit the form being empty. await user.click(button); expect(formData).toEqual({ city: null, }); // Select an option await user.click(input); expectMenuToBeOpen(menu); await user.click( screen.getByRole("option", { name: "New York", }), ); // Submit the form being fulfilled. await user.click(button); expect(formData).toEqual({ city: "new_york", }); // Clear selection. const clearButton = screen.getByRole("button", { name: "Clear selection", }); await userEvent.click(clearButton); // Submit again. await user.click(button); expect(formData).toEqual({ city: null, }); }); it("should not be clearable", async () => { render( , ); const input = screen.getByRole("combobox", { name: "City", }); const menu: HTMLDivElement = screen.getByRole("listbox", { name: "City", }); const valueRendered = document.querySelector(".c__select__inner__value"); // Make sure the select is empty. expect(valueRendered).toHaveTextContent(""); const user = userEvent.setup(); await user.click(input); expectMenuToBeOpen(menu); // Make sure the disabled option is not selectable. let option: HTMLLIElement = screen.getByRole("option", { name: "New York", }); expectOptionToBeDisabled(option); // Try to click on the disabled option. await user.click(option); expectMenuToBeOpen(menu); // Make sure the select is still empty. expect(valueRendered).toHaveTextContent(""); // Select a not disabled option. option = screen.getByRole("option", { name: "Tokyo", }); await user.click(option); expectMenuToBeClosed(menu); expect(valueRendered).toHaveTextContent("Tokyo"); }); it("is accessible if the the label is not shown", async () => { render( , ); const input = screen.getByRole("combobox", { name: "City", }); const user = userEvent.setup(); await user.click(input); screen.getByText("No options available"); }); it("updates the value if the value is removed from the options (uncontrolled)", async () => { let myOptions = [ { label: "Paris", value: "paris", }, { label: "Panama", value: "panama", }, { label: "London", value: "london", }, ]; const Wrapper = ({ options }: { options: Option[] }) => { return { setValue(e.target.value as string); }} />
); }; render(, { wrapper: CunninghamProvider, }); const user = userEvent.setup(); const input = screen.getByRole("combobox", { name: "City", }); // Select an option await user.click(input); await user.click( screen.getByRole("option", { name: "Panama", }), ); // Verify that the value is selected. await user.click(input); expectOptionToBeSelected(screen.getByRole("option", { name: "Panama" })); // Rerender the select with the options mutated. await user.click(screen.getByRole("button", { name: "Rerender" })); // Verify that the value is still selected. await user.click(input); expectOptionToBeSelected(screen.getByRole("option", { name: "Panama" })); }); it("updates the value if the value is removed from the options (controlled)", async () => { let myOptions = [ { label: "Paris", value: "paris", }, { label: "Panama", value: "panama", }, { label: "London", value: "london", }, ]; const Wrapper = ({ options }: { options: Option[] }) => { const [value, setValue] = useState(); const [onChangeCounts, setOnChangeCounts] = useState(0); return (
Value = {value}|
onChangeCounts = {onChangeCounts}|
, ); const input = screen.getByRole("combobox", { name: "City", }); const menu: HTMLDivElement = screen.getByRole("listbox", { name: "City", }); const user = userEvent.setup(); // Make sure the select is not focused. expect(document.activeElement?.className).toEqual(""); // Focus the select. await user.click(input); expectMenuToBeOpen(menu); expect(document.activeElement?.className).toContain("c__select__wrapper"); // Blur the select. ref.current?.blur(); // Make sure the select is blured. await waitFor(() => expectMenuToBeClosed(menu)); expect(document.activeElement?.className).toEqual(""); }); it("renders custom options", async () => { const Wrapper = (props: SelectProps) => { return ( , ); expect( document.querySelector(".c__field.my-custom-class"), ).toBeInTheDocument(); }); });