import userEvent from "@testing-library/user-event";
import { render, screen, waitFor } from "@testing-library/react";
import { expect } from "vitest";
import React, { FormEvent, useState } from "react";
import { Select } from ":/components/Forms/Select/index";
import { Button } from ":/components/Button";
import { CunninghamProvider } from ":/components/Provider";
describe("", () => {
const expectMenuToBeOpen = (menu: HTMLDivElement) => {
expect(Array.from(menu.classList)).contains("c__select__menu--opened");
};
const expectOptions = (expectedOptions: string[]) => {
const options = screen.getAllByRole("option");
expect(options.length).toBe(expectedOptions.length);
options.forEach((option, i) => {
expect(option).toHaveTextContent(expectedOptions[i]);
});
};
const expectMenuToBeClosed = (menu: HTMLDivElement) => {
expect(Array.from(menu.classList)).not.contains("c__select__menu--opened");
};
const expectOptionToBeSelected = (option: HTMLLIElement) => {
expect(option).toHaveAttribute("aria-selected", "true");
expect(Array.from(option.classList)).contains(
"c__select__menu__item--selected"
);
expect(Array.from(option.classList)).contains(
"c__select__menu__item--highlight"
);
};
const expectOptionToBeUnselected = (option: HTMLLIElement) => {
expect(option).toHaveAttribute("aria-selected", "false");
expect(Array.from(option.classList)).not.contains(
"c__select__menu__item--selected"
);
};
const expectOptionToBeDisabled = (option: HTMLLIElement) => {
expect(option).toHaveAttribute("disabled");
expect(Array.from(option.classList)).contains(
"c__select__menu__item--disabled"
);
};
describe("Searchable", () => {
it("shows all options when clicking on the input", async () => {
const user = userEvent.setup();
render(
);
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(
);
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"]);
// Verify that filtering works.
await user.type(input, "Pa");
expectMenuToBeOpen(menu);
expectOptions(["Paris", "Panama"]);
await user.type(input, "r");
expectOptions(["Paris"]);
// Select option.
const option: HTMLLIElement = screen.getByRole("option", {
name: "Paris",
});
await user.click(option);
expect(input).toHaveValue("Paris");
});
it("clears value", async () => {
render(
);
const user = userEvent.setup();
const input = screen.getByRole("combobox", {
name: "City",
});
expect(input).toHaveValue("New York");
// Clear selection.
const clearButton = screen.getByRole("button", {
name: "Clear selection",
});
await user.click(clearButton);
expect(input).toHaveValue("");
});
it("should select with defaultValue using label", 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("New York");
});
it("should not select any value if there is not match", async () => {
render(
);
const input = screen.getByRole("combobox", {
name: "City",
});
expect(input).toHaveValue("");
});
it("works controlled", async () => {
const Wrapper = () => {
const [value, setValue] = useState(
"london"
);
return (
Value = {value}|
);
};
render();
const input = screen.getByRole("combobox", {
name: "City",
});
// Make sure value is selected.
screen.getByText("Value = london|");
// Change value.
const user = userEvent.setup();
await user.click(input);
// Make sure the option is selected.
const option: HTMLLIElement = screen.getByRole("option", {
name: "London",
});
expectOptionToBeSelected(option);
// List should show only selected value.
expectOptions(["London"]);
// Clear value.
const button = screen.getByRole("button", {
name: "Clear",
});
await user.click(button);
// Select an option.
await user.click(input);
await user.click(
screen.getByRole("option", {
name: "New York",
})
);
// Make sure value is selected.
screen.getByText("Value = new_york|");
// clear value.
await user.click(button);
// Make sure value is cleared.
screen.getByText("Value = |");
});
it("renders disabled", async () => {
render(
);
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 (
);
};
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,
});
// Try to type something and verify that is does not submit it.
await user.type(input, "Pa");
await user.click(button);
expect(formData).toEqual({
city: null,
});
// Select an option
await user.clear(input);
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,
});
});
});
describe("Simple", () => {
it("should select an option and unselect it", async () => {
const user = userEvent.setup();
render(
);
const input = screen.getByRole("combobox", {
name: "City",
});
const menu: HTMLDivElement = screen.getByRole("listbox", {
name: "City",
});
const label = screen.getByText("City");
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("Tokyo");
expect(Array.from(label.classList)).not.toContain("placeholder");
// Make sure menu is automatically closed.
expectMenuToBeClosed(menu);
});
it("should select with defaultValue using value", () => {
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}|
);
};
render();
const input = screen.getByRole("combobox", {
name: "City",
});
// Make sure value is selected.
screen.getByText("Value = london|");
// Change value.
const user = userEvent.setup();
await user.click(input);
// Make sure the option is selected.
const option: HTMLLIElement = screen.getByRole("option", {
name: "London",
});
expectOptionToBeSelected(option);
// Select an option.
await user.click(
screen.getByRole("option", {
name: "New York",
})
);
// Make sure value is selected.
screen.getByText("Value = new_york|");
// clear value.
const button = screen.getByRole("button", {
name: "Clear",
});
await user.click(button);
// Make sure value is cleared.
screen.getByText("Value = |");
});
it("renders disabled", async () => {
render(
);
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(
);
screen.getByText("This is a text");
});
it("renders with state=error", async () => {
render(
);
expect(
document.querySelector(".c__select.c__select--error")
).toBeInTheDocument();
});
it("renders with state=success", async () => {
render(
);
expect(
document.querySelector(".c__select.c__select--success")
).toBeInTheDocument();
});
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 (
);
};
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(
);
screen.getByRole("combobox", {
name: "City",
});
const valueRendered = document.querySelector(".c__select__inner__value");
// Make sure default value is rendered.
expect(valueRendered).toHaveTextContent("Paris");
// Make sure the clear button is not rendered.
expect(
screen.queryByRole("button", {
name: "Clear selection",
})
).not.toBeInTheDocument();
});
it("is not possible to select disabled options", 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(
);
// Make sure the input is accessible.
screen.getByRole("combobox", {
name: "City",
});
const label = screen.getByText("City");
expect(Array.from(label.classList)).toContain("offscreen");
});
});
});