The className prop was sometimes set onto the nested element and sometimes on the container element, which was not consistent. Now we always set the className onto the upmost element.
1999 lines
53 KiB
TypeScript
1999 lines
53 KiB
TypeScript
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("<Select/>", () => {
|
|
describe("Searchable", () => {
|
|
it("shows all options when clicking on the input", async () => {
|
|
const user = userEvent.setup();
|
|
render(
|
|
<CunninghamProvider>
|
|
<Select
|
|
label="City"
|
|
options={[
|
|
{
|
|
label: "Paris",
|
|
},
|
|
{
|
|
label: "Panama",
|
|
},
|
|
{
|
|
label: "London",
|
|
},
|
|
{
|
|
label: "New York",
|
|
},
|
|
{
|
|
label: "Tokyo",
|
|
},
|
|
]}
|
|
searchable={true}
|
|
/>
|
|
</CunninghamProvider>,
|
|
);
|
|
|
|
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(
|
|
<CunninghamProvider>
|
|
<Select
|
|
label="City"
|
|
options={[
|
|
{
|
|
label: "Paris",
|
|
value: "paris",
|
|
},
|
|
{
|
|
label: "Panama",
|
|
value: "panama",
|
|
},
|
|
{
|
|
label: "London",
|
|
value: "london",
|
|
},
|
|
{
|
|
label: "New York",
|
|
value: "new_york",
|
|
},
|
|
{
|
|
label: "Tokyo",
|
|
value: "tokyo",
|
|
},
|
|
]}
|
|
searchable={true}
|
|
/>
|
|
</CunninghamProvider>,
|
|
);
|
|
|
|
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", { skipClick: true });
|
|
expectOptions(["Paris"]);
|
|
|
|
// Select option.
|
|
const option: HTMLLIElement = screen.getByRole("option", {
|
|
name: "Paris",
|
|
});
|
|
await user.click(option);
|
|
|
|
expect(input).toHaveValue("Paris");
|
|
});
|
|
it("clears the text input if no item is selected", async () => {
|
|
const Wrapper = () => {
|
|
const [value, setValue] = useState<string | number | undefined>();
|
|
return (
|
|
<CunninghamProvider>
|
|
<div>Value = {value}|</div>
|
|
<Select
|
|
label="City"
|
|
options={[
|
|
{
|
|
label: "Paris",
|
|
value: "paris",
|
|
},
|
|
{
|
|
label: "Panama",
|
|
value: "panama",
|
|
},
|
|
{
|
|
label: "London",
|
|
value: "london",
|
|
},
|
|
{
|
|
label: "New York",
|
|
value: "new_york",
|
|
},
|
|
{
|
|
label: "Tokyo",
|
|
value: "tokyo",
|
|
},
|
|
]}
|
|
searchable={true}
|
|
value={value}
|
|
onChange={(e) => setValue(e.target.value as any)}
|
|
/>
|
|
<Input name="Something else" />
|
|
</CunninghamProvider>
|
|
);
|
|
};
|
|
render(<Wrapper />);
|
|
|
|
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<string | number | undefined>(
|
|
"london",
|
|
);
|
|
return (
|
|
<CunninghamProvider>
|
|
<div>Value = {value}|</div>
|
|
<Select
|
|
label="City"
|
|
options={[
|
|
{
|
|
label: "Paris",
|
|
value: "paris",
|
|
},
|
|
{
|
|
label: "Panama",
|
|
value: "panama",
|
|
},
|
|
{
|
|
label: "London",
|
|
value: "london",
|
|
},
|
|
{
|
|
label: "New York",
|
|
value: "new_york",
|
|
},
|
|
{
|
|
label: "Tokyo",
|
|
value: "tokyo",
|
|
},
|
|
]}
|
|
searchable={true}
|
|
value={value}
|
|
onChange={(e) => setValue(e.target.value as any)}
|
|
/>
|
|
<Input name="Something else" />
|
|
</CunninghamProvider>
|
|
);
|
|
};
|
|
render(<Wrapper />);
|
|
|
|
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(
|
|
<CunninghamProvider>
|
|
<Select
|
|
label="City"
|
|
options={[
|
|
{
|
|
label: "Paris",
|
|
},
|
|
{
|
|
label: "Panama",
|
|
},
|
|
{
|
|
label: "London",
|
|
},
|
|
{
|
|
label: "New York",
|
|
value: "new_york",
|
|
},
|
|
{
|
|
label: "Tokyo",
|
|
value: "tokyo",
|
|
},
|
|
]}
|
|
searchable={true}
|
|
defaultValue="new_york"
|
|
/>
|
|
</CunninghamProvider>,
|
|
);
|
|
|
|
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(
|
|
<CunninghamProvider>
|
|
<Select
|
|
label="City"
|
|
options={[
|
|
{
|
|
label: "Paris",
|
|
},
|
|
{
|
|
label: "Panama",
|
|
},
|
|
{
|
|
label: "London",
|
|
},
|
|
{
|
|
label: "New York",
|
|
},
|
|
{
|
|
label: "Tokyo",
|
|
},
|
|
]}
|
|
searchable={true}
|
|
defaultValue="New York"
|
|
/>
|
|
</CunninghamProvider>,
|
|
);
|
|
|
|
const input = screen.getByRole("combobox", {
|
|
name: "City",
|
|
});
|
|
expect(input).toHaveValue("New York");
|
|
});
|
|
it("should select with defaultValue using value", async () => {
|
|
render(
|
|
<CunninghamProvider>
|
|
<Select
|
|
label="City"
|
|
options={[
|
|
{
|
|
label: "Paris",
|
|
},
|
|
{
|
|
label: "Panama",
|
|
},
|
|
{
|
|
label: "London",
|
|
},
|
|
{
|
|
label: "New York",
|
|
value: "new_york",
|
|
},
|
|
{
|
|
label: "Tokyo",
|
|
value: "tokyo",
|
|
},
|
|
]}
|
|
searchable={true}
|
|
defaultValue="new_york"
|
|
/>
|
|
</CunninghamProvider>,
|
|
);
|
|
|
|
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(
|
|
<CunninghamProvider>
|
|
<Select
|
|
label="City"
|
|
options={[
|
|
{
|
|
label: "Paris",
|
|
},
|
|
{
|
|
label: "Panama",
|
|
},
|
|
{
|
|
label: "London",
|
|
},
|
|
{
|
|
label: "New York",
|
|
value: "new_york",
|
|
},
|
|
{
|
|
label: "Tokyo",
|
|
value: "tokyo",
|
|
},
|
|
]}
|
|
searchable={true}
|
|
defaultValue="not_found"
|
|
/>
|
|
</CunninghamProvider>,
|
|
);
|
|
|
|
const input = screen.getByRole("combobox", {
|
|
name: "City",
|
|
});
|
|
expect(input).toHaveValue("");
|
|
});
|
|
it("works controlled", async () => {
|
|
const Wrapper = () => {
|
|
const [value, setValue] = useState<string | number | undefined>(
|
|
"london",
|
|
);
|
|
const [onChangeCounts, setOnChangeCounts] = useState(0);
|
|
return (
|
|
<CunninghamProvider>
|
|
<div>
|
|
<div>Value = {value}|</div>
|
|
<div>onChangeCounts = {onChangeCounts}|</div>
|
|
<Button onClick={() => setValue(undefined)}>Clear</Button>
|
|
<Button onClick={() => setValue("paris")}>Set Paris</Button>
|
|
<Select
|
|
label="City"
|
|
options={[
|
|
{
|
|
label: "Paris",
|
|
value: "paris",
|
|
},
|
|
{
|
|
label: "London",
|
|
value: "london",
|
|
},
|
|
{
|
|
label: "New York",
|
|
value: "new_york",
|
|
},
|
|
{
|
|
label: "Tokyo",
|
|
value: "tokyo",
|
|
},
|
|
]}
|
|
value={value}
|
|
onChange={(e) => {
|
|
setValue(e.target.value as string);
|
|
setOnChangeCounts(onChangeCounts + 1);
|
|
}}
|
|
searchable={true}
|
|
/>
|
|
</div>
|
|
</CunninghamProvider>
|
|
);
|
|
};
|
|
render(<Wrapper />);
|
|
|
|
const input = screen.getByRole("combobox", {
|
|
name: "City",
|
|
});
|
|
|
|
// Make sure value is selected.
|
|
screen.getByText("Value = london|");
|
|
screen.getByText("onChangeCounts = 0|");
|
|
|
|
// 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 display all available options when re-opening.
|
|
expectOptions(["Paris", "London", "New York", "Tokyo"]);
|
|
|
|
// Clear value.
|
|
const button = screen.getByRole("button", {
|
|
name: "Clear",
|
|
});
|
|
await user.click(button);
|
|
screen.getByText("Value = |");
|
|
await screen.findByText("onChangeCounts = 0|");
|
|
|
|
// 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|");
|
|
screen.getByText("onChangeCounts = 1|");
|
|
|
|
// clear value.
|
|
await user.click(button);
|
|
|
|
// Make sure value is cleared.
|
|
screen.getByText("Value = |");
|
|
|
|
// Make sure setting value works
|
|
const buttonParis = screen.getByRole("button", {
|
|
name: "Set Paris",
|
|
});
|
|
await user.click(buttonParis);
|
|
|
|
screen.getByText("Value = paris|");
|
|
expect(input).toHaveValue("Paris");
|
|
screen.getByText("onChangeCounts = 1|");
|
|
});
|
|
it("renders disabled", async () => {
|
|
render(
|
|
<CunninghamProvider>
|
|
<Select
|
|
label="City"
|
|
options={[
|
|
{
|
|
label: "Paris",
|
|
value: "paris",
|
|
},
|
|
{
|
|
label: "London",
|
|
value: "london",
|
|
},
|
|
{
|
|
label: "New York",
|
|
value: "new_york",
|
|
},
|
|
{
|
|
label: "Tokyo",
|
|
value: "tokyo",
|
|
},
|
|
]}
|
|
disabled={true}
|
|
searchable={true}
|
|
/>
|
|
</CunninghamProvider>,
|
|
);
|
|
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<HTMLFormElement>) => {
|
|
e.preventDefault();
|
|
const data = new FormData(e.currentTarget);
|
|
formData = {
|
|
city: data.get("city"),
|
|
};
|
|
};
|
|
|
|
return (
|
|
<CunninghamProvider>
|
|
<div>
|
|
<form onSubmit={onSubmit}>
|
|
<Select
|
|
label="City"
|
|
name="city"
|
|
options={[
|
|
{
|
|
label: "Paris",
|
|
value: "paris",
|
|
},
|
|
{
|
|
label: "London",
|
|
value: "london",
|
|
},
|
|
{
|
|
label: "New York",
|
|
value: "new_york",
|
|
},
|
|
{
|
|
label: "Tokyo",
|
|
value: "tokyo",
|
|
},
|
|
]}
|
|
searchable={true}
|
|
/>
|
|
<Button>Submit</Button>
|
|
</form>
|
|
</div>
|
|
</CunninghamProvider>
|
|
);
|
|
};
|
|
render(<Wrapper />);
|
|
|
|
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,
|
|
});
|
|
});
|
|
|
|
[
|
|
{
|
|
defaultValue: "panama",
|
|
type: "default value",
|
|
expected: "Panama",
|
|
},
|
|
{
|
|
value: "panama",
|
|
type: "value",
|
|
expected: "Panama",
|
|
},
|
|
{
|
|
type: "without values props",
|
|
expected: "",
|
|
},
|
|
].forEach(({ type, expected, ...props }) => {
|
|
it(`render mutated option when select open and keep the filter activated with ${type}`, async () => {
|
|
const myOptions = [
|
|
{
|
|
label: "Paris",
|
|
value: "paris",
|
|
},
|
|
{
|
|
label: "Panama",
|
|
value: "panama",
|
|
},
|
|
{
|
|
label: "London",
|
|
value: "london",
|
|
},
|
|
];
|
|
|
|
const Wrapper = ({ options }: { options: Option[] }) => {
|
|
return (
|
|
<Select
|
|
label="City"
|
|
name="city"
|
|
options={options}
|
|
searchable={true}
|
|
{...props}
|
|
/>
|
|
);
|
|
};
|
|
|
|
const { rerender } = render(<Wrapper options={myOptions} />, {
|
|
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(<Wrapper options={[...myOptions]} />);
|
|
|
|
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(<Wrapper options={[...myOptions]} />);
|
|
|
|
// 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<SelectHandle>();
|
|
render(
|
|
<CunninghamProvider>
|
|
<Select
|
|
label="City"
|
|
options={[
|
|
{
|
|
label: "Paris",
|
|
value: "paris",
|
|
},
|
|
{
|
|
label: "Panama",
|
|
value: "panama",
|
|
},
|
|
{
|
|
label: "London",
|
|
value: "london",
|
|
},
|
|
]}
|
|
searchable={true}
|
|
ref={ref}
|
|
/>
|
|
</CunninghamProvider>,
|
|
);
|
|
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?.tagName).toEqual("BODY");
|
|
|
|
// Focus the select by focusing input.
|
|
await user.click(input);
|
|
expectMenuToBeOpen(menu);
|
|
expect(document.activeElement?.tagName).toEqual("INPUT");
|
|
|
|
// Blur the select.
|
|
ref.current?.blur();
|
|
|
|
// Make sure the select is blured.
|
|
await waitFor(() => expectMenuToBeClosed(menu));
|
|
expect(document.activeElement?.tagName).toEqual("BODY");
|
|
});
|
|
|
|
it("renders custom options", async () => {
|
|
const Wrapper = (props: SelectProps) => {
|
|
return (
|
|
<CunninghamProvider>
|
|
<Select {...props} />
|
|
<Button>Blur</Button>
|
|
</CunninghamProvider>
|
|
);
|
|
};
|
|
|
|
const props: SelectProps = {
|
|
label: "City",
|
|
searchable: true,
|
|
options: [
|
|
{
|
|
label: "Paris",
|
|
value: "paris",
|
|
render: () => (
|
|
<div>
|
|
<img src="paris.png" alt="Paris flag" />
|
|
Paris
|
|
</div>
|
|
),
|
|
},
|
|
{
|
|
label: "Panama",
|
|
value: "panama",
|
|
render: () => (
|
|
<div>
|
|
<img src="panama.png" alt="Panama flag" />
|
|
Panama
|
|
</div>
|
|
),
|
|
},
|
|
{
|
|
label: "London",
|
|
value: "london",
|
|
render: () => (
|
|
<div>
|
|
<img src="london.png" alt="London flag" />
|
|
London
|
|
</div>
|
|
),
|
|
},
|
|
],
|
|
};
|
|
|
|
const { rerender } = render(<Wrapper {...props} />);
|
|
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(<Wrapper {...props} showLabelWhenSelected={false} />);
|
|
|
|
// 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(
|
|
<CunninghamProvider>
|
|
<Select
|
|
label="City"
|
|
options={[
|
|
{
|
|
label: "Paris",
|
|
value: "paris",
|
|
},
|
|
{
|
|
label: "Panama",
|
|
value: "panama",
|
|
},
|
|
{
|
|
label: "London",
|
|
value: "london",
|
|
},
|
|
{
|
|
label: "New York",
|
|
value: "new_york",
|
|
},
|
|
{
|
|
label: "Tokyo",
|
|
value: "tokyo",
|
|
},
|
|
]}
|
|
searchable={true}
|
|
onSearchInputChange={(e) => {
|
|
searchTerm = e.target.value;
|
|
}}
|
|
/>
|
|
</CunninghamProvider>,
|
|
);
|
|
|
|
const input = screen.getByRole("combobox", {
|
|
name: "City",
|
|
});
|
|
|
|
await user.click(input);
|
|
expect(searchTerm).toBeUndefined();
|
|
|
|
await user.type(input, "Pa");
|
|
expect(searchTerm).toEqual("Pa");
|
|
|
|
await user.type(input, "r", { skipClick: true });
|
|
expect(searchTerm).toEqual("Par");
|
|
|
|
// Select option.
|
|
const option: HTMLLIElement = screen.getByRole("option", {
|
|
name: "Paris",
|
|
});
|
|
await user.click(option);
|
|
expect(searchTerm).toBeUndefined();
|
|
});
|
|
});
|
|
|
|
describe("Simple", () => {
|
|
it("should select an option and unselect it", async () => {
|
|
const user = userEvent.setup();
|
|
render(
|
|
<CunninghamProvider>
|
|
<Select
|
|
label="City"
|
|
options={[
|
|
{
|
|
label: "Paris",
|
|
},
|
|
{
|
|
label: "London",
|
|
},
|
|
{
|
|
label: "New York",
|
|
},
|
|
{
|
|
label: "Tokyo",
|
|
},
|
|
]}
|
|
/>
|
|
</CunninghamProvider>,
|
|
);
|
|
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(
|
|
<CunninghamProvider>
|
|
<Select
|
|
label="City"
|
|
options={[
|
|
{
|
|
label: "Paris",
|
|
},
|
|
{
|
|
label: "London",
|
|
},
|
|
{
|
|
label: "New York",
|
|
},
|
|
{
|
|
label: "Tokyo",
|
|
},
|
|
]}
|
|
defaultValue="Tokyo"
|
|
/>
|
|
</CunninghamProvider>,
|
|
);
|
|
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(
|
|
<CunninghamProvider>
|
|
<Select
|
|
label="City"
|
|
options={[
|
|
{
|
|
label: "Paris",
|
|
value: "paris",
|
|
},
|
|
{
|
|
label: "London",
|
|
value: "london",
|
|
},
|
|
{
|
|
label: "New York",
|
|
value: "new_york",
|
|
},
|
|
{
|
|
label: "Tokyo",
|
|
value: "tokyo",
|
|
},
|
|
]}
|
|
defaultValue="new_york"
|
|
/>
|
|
</CunninghamProvider>,
|
|
);
|
|
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<string | number | undefined>(
|
|
"london",
|
|
);
|
|
return (
|
|
<CunninghamProvider>
|
|
<div>
|
|
<div>Value = {value}|</div>
|
|
<Button onClick={() => setValue(undefined)}>Clear</Button>
|
|
<Select
|
|
label="City"
|
|
options={[
|
|
{
|
|
label: "Paris",
|
|
value: "paris",
|
|
},
|
|
{
|
|
label: "London",
|
|
value: "london",
|
|
},
|
|
{
|
|
label: "New York",
|
|
value: "new_york",
|
|
},
|
|
{
|
|
label: "Tokyo",
|
|
value: "tokyo",
|
|
},
|
|
]}
|
|
value={value}
|
|
onChange={(e) => setValue(e.target.value as string)}
|
|
/>
|
|
</div>
|
|
</CunninghamProvider>
|
|
);
|
|
};
|
|
render(<Wrapper />);
|
|
|
|
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(
|
|
<CunninghamProvider>
|
|
<Select
|
|
label="City"
|
|
options={[
|
|
{
|
|
label: "Paris",
|
|
value: "paris",
|
|
},
|
|
{
|
|
label: "London",
|
|
value: "london",
|
|
},
|
|
{
|
|
label: "New York",
|
|
value: "new_york",
|
|
},
|
|
{
|
|
label: "Tokyo",
|
|
value: "tokyo",
|
|
},
|
|
]}
|
|
disabled={true}
|
|
/>
|
|
</CunninghamProvider>,
|
|
);
|
|
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(
|
|
<CunninghamProvider>
|
|
<Select
|
|
label="City"
|
|
options={[
|
|
{
|
|
label: "Paris",
|
|
value: "paris",
|
|
},
|
|
{
|
|
label: "London",
|
|
value: "london",
|
|
},
|
|
{
|
|
label: "New York",
|
|
value: "new_york",
|
|
},
|
|
{
|
|
label: "Tokyo",
|
|
value: "tokyo",
|
|
},
|
|
]}
|
|
text="This is a text"
|
|
/>
|
|
</CunninghamProvider>,
|
|
);
|
|
screen.getByText("This is a text");
|
|
});
|
|
it("renders with state=error", async () => {
|
|
render(
|
|
<CunninghamProvider>
|
|
<Select
|
|
label="City"
|
|
options={[
|
|
{
|
|
label: "Paris",
|
|
value: "paris",
|
|
},
|
|
{
|
|
label: "London",
|
|
value: "london",
|
|
},
|
|
{
|
|
label: "New York",
|
|
value: "new_york",
|
|
},
|
|
{
|
|
label: "Tokyo",
|
|
value: "tokyo",
|
|
},
|
|
]}
|
|
text="This is a text"
|
|
state="error"
|
|
/>
|
|
</CunninghamProvider>,
|
|
);
|
|
expect(
|
|
document.querySelector(".c__select.c__select--error"),
|
|
).toBeInTheDocument();
|
|
});
|
|
it("renders with state=success", async () => {
|
|
render(
|
|
<CunninghamProvider>
|
|
<Select
|
|
label="City"
|
|
options={[
|
|
{
|
|
label: "Paris",
|
|
value: "paris",
|
|
},
|
|
{
|
|
label: "London",
|
|
value: "london",
|
|
},
|
|
{
|
|
label: "New York",
|
|
value: "new_york",
|
|
},
|
|
{
|
|
label: "Tokyo",
|
|
value: "tokyo",
|
|
},
|
|
]}
|
|
text="This is a text"
|
|
state="success"
|
|
/>
|
|
</CunninghamProvider>,
|
|
);
|
|
expect(
|
|
document.querySelector(".c__select.c__select--success"),
|
|
).toBeInTheDocument();
|
|
});
|
|
it("submits form data", async () => {
|
|
let formData: any;
|
|
const Wrapper = () => {
|
|
const onSubmit = (e: FormEvent<HTMLFormElement>) => {
|
|
e.preventDefault();
|
|
const data = new FormData(e.currentTarget);
|
|
formData = {
|
|
city: data.get("city"),
|
|
};
|
|
};
|
|
|
|
return (
|
|
<CunninghamProvider>
|
|
<div>
|
|
<form onSubmit={onSubmit}>
|
|
<Select
|
|
label="City"
|
|
name="city"
|
|
options={[
|
|
{
|
|
label: "Paris",
|
|
value: "paris",
|
|
},
|
|
{
|
|
label: "London",
|
|
value: "london",
|
|
},
|
|
{
|
|
label: "New York",
|
|
value: "new_york",
|
|
},
|
|
{
|
|
label: "Tokyo",
|
|
value: "tokyo",
|
|
},
|
|
]}
|
|
/>
|
|
<Button>Submit</Button>
|
|
</form>
|
|
</div>
|
|
</CunninghamProvider>
|
|
);
|
|
};
|
|
render(<Wrapper />);
|
|
|
|
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(
|
|
<CunninghamProvider>
|
|
<Select
|
|
label="City"
|
|
options={[
|
|
{
|
|
label: "Paris",
|
|
},
|
|
{
|
|
label: "London",
|
|
},
|
|
{
|
|
label: "New York",
|
|
},
|
|
{
|
|
label: "Tokyo",
|
|
},
|
|
]}
|
|
clearable={false}
|
|
defaultValue="Paris"
|
|
/>
|
|
</CunninghamProvider>,
|
|
);
|
|
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(
|
|
<CunninghamProvider>
|
|
<Select
|
|
label="City"
|
|
options={[
|
|
{
|
|
label: "Paris",
|
|
},
|
|
{
|
|
label: "London",
|
|
},
|
|
{
|
|
label: "New York",
|
|
disabled: true,
|
|
},
|
|
{
|
|
label: "Tokyo",
|
|
},
|
|
]}
|
|
/>
|
|
</CunninghamProvider>,
|
|
);
|
|
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(
|
|
<CunninghamProvider>
|
|
<Select
|
|
label="City"
|
|
options={[
|
|
{
|
|
label: "Paris",
|
|
},
|
|
{
|
|
label: "London",
|
|
},
|
|
{
|
|
label: "New York",
|
|
disabled: true,
|
|
},
|
|
{
|
|
label: "Tokyo",
|
|
},
|
|
]}
|
|
hideLabel={true}
|
|
/>
|
|
</CunninghamProvider>,
|
|
);
|
|
// Make sure the input is accessible.
|
|
screen.getByRole("combobox", {
|
|
name: "City",
|
|
});
|
|
const label = screen.getByText("City")!.parentElement!;
|
|
expect(Array.from(label.classList)).toContain("c__offscreen");
|
|
});
|
|
|
|
it("renders menu empty placeholder when there are no options to display", async () => {
|
|
render(
|
|
<CunninghamProvider>
|
|
<Select label="City" options={[]} hideLabel={true} />
|
|
</CunninghamProvider>,
|
|
);
|
|
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 <Select label="City" name="city" options={options} />;
|
|
};
|
|
|
|
const { rerender } = render(<Wrapper options={myOptions} />, {
|
|
wrapper: CunninghamProvider,
|
|
});
|
|
|
|
const user = userEvent.setup();
|
|
const input = screen.getByRole("combobox", {
|
|
name: "City",
|
|
});
|
|
const valueRendered = document.querySelector(".c__select__inner__value");
|
|
const menu: HTMLDivElement = screen.getByRole("listbox", {
|
|
name: "City",
|
|
});
|
|
|
|
// Check init value (defaultValue / value / nothing)
|
|
expect(valueRendered).toHaveTextContent("");
|
|
|
|
await user.click(input);
|
|
expectMenuToBeOpen(menu);
|
|
expectOptions(["Paris", "Panama", "London"]);
|
|
|
|
// Select London.
|
|
await user.click(screen.getByRole("option", { name: "London" }));
|
|
expect(valueRendered).toHaveTextContent("London");
|
|
|
|
// Remove London from the options.
|
|
myOptions = myOptions.filter((option) => option.value !== "london");
|
|
// Rerender the select with the options mutated
|
|
rerender(<Wrapper options={[...myOptions]} />);
|
|
|
|
await user.click(input);
|
|
expectMenuToBeOpen(menu);
|
|
expectOptions(["Paris", "Panama"]);
|
|
expect(valueRendered).toHaveTextContent("");
|
|
});
|
|
|
|
it("still shows the selected current choice even if the options are different objects", async () => {
|
|
const Wrapper = () => {
|
|
const [value, setValue] = useState<string | number | undefined>();
|
|
const [count, setCount] = useState(0);
|
|
return (
|
|
<CunninghamProvider>
|
|
<div>
|
|
<div>Value = {value}|</div>
|
|
<Button onClick={() => setCount(count + 1)}>Rerender</Button>
|
|
<Select
|
|
label="City"
|
|
options={[
|
|
{
|
|
label: "Paris",
|
|
value: "paris",
|
|
},
|
|
{
|
|
label: "Panama",
|
|
value: "panama",
|
|
},
|
|
{
|
|
label: "London",
|
|
value: "london",
|
|
},
|
|
]}
|
|
value={value}
|
|
onChange={(e) => {
|
|
setValue(e.target.value as string);
|
|
}}
|
|
/>
|
|
</div>
|
|
</CunninghamProvider>
|
|
);
|
|
};
|
|
|
|
render(<Wrapper />, {
|
|
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<string | number | undefined>();
|
|
const [onChangeCounts, setOnChangeCounts] = useState(0);
|
|
return (
|
|
<CunninghamProvider>
|
|
<div>
|
|
<div>Value = {value}|</div>
|
|
<div>onChangeCounts = {onChangeCounts}|</div>
|
|
<Select
|
|
label="City"
|
|
options={options}
|
|
value={value}
|
|
onChange={(e) => {
|
|
setValue(e.target.value as string);
|
|
setOnChangeCounts(onChangeCounts + 1);
|
|
}}
|
|
/>
|
|
</div>
|
|
</CunninghamProvider>
|
|
);
|
|
};
|
|
|
|
const { rerender } = render(<Wrapper options={myOptions} />, {
|
|
wrapper: CunninghamProvider,
|
|
});
|
|
|
|
const user = userEvent.setup();
|
|
const input = screen.getByRole("combobox", {
|
|
name: "City",
|
|
});
|
|
const valueRendered = document.querySelector(".c__select__inner__value");
|
|
const menu: HTMLDivElement = screen.getByRole("listbox", {
|
|
name: "City",
|
|
});
|
|
|
|
// Check init value (defaultValue / value / nothing)
|
|
expect(valueRendered).toHaveTextContent("");
|
|
screen.getByText("Value = |");
|
|
screen.getByText("onChangeCounts = 0|");
|
|
|
|
await user.click(input);
|
|
expectMenuToBeOpen(menu);
|
|
expectOptions(["Paris", "Panama", "London"]);
|
|
|
|
// Select London.
|
|
await user.click(screen.getByRole("option", { name: "London" }));
|
|
screen.getByText("Value = london|");
|
|
screen.getByText("onChangeCounts = 1|");
|
|
expect(valueRendered).toHaveTextContent("London");
|
|
|
|
// Remove London from the options.
|
|
myOptions = myOptions.filter((option) => option.value !== "london");
|
|
// Rerender the select with the options mutated
|
|
rerender(<Wrapper options={[...myOptions]} />);
|
|
|
|
await user.click(input);
|
|
expectMenuToBeOpen(menu);
|
|
expectOptions(["Paris", "Panama"]);
|
|
expect(valueRendered).toHaveTextContent("");
|
|
screen.getByText("Value = |");
|
|
screen.getByText("onChangeCounts = 2|");
|
|
});
|
|
|
|
it("blurs from ref", async () => {
|
|
const ref = createRef<SelectHandle>();
|
|
render(
|
|
<CunninghamProvider>
|
|
<Select
|
|
label="City"
|
|
options={[
|
|
{
|
|
label: "Paris",
|
|
value: "paris",
|
|
},
|
|
{
|
|
label: "Panama",
|
|
value: "panama",
|
|
},
|
|
{
|
|
label: "London",
|
|
value: "london",
|
|
},
|
|
]}
|
|
ref={ref}
|
|
/>
|
|
</CunninghamProvider>,
|
|
);
|
|
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 (
|
|
<CunninghamProvider>
|
|
<Select {...props} />
|
|
</CunninghamProvider>
|
|
);
|
|
};
|
|
|
|
const props: SelectProps = {
|
|
label: "City",
|
|
options: [
|
|
{
|
|
label: "Paris",
|
|
value: "paris",
|
|
render: () => (
|
|
<div>
|
|
<img src="paris.png" alt="Paris flag" />
|
|
Paris
|
|
</div>
|
|
),
|
|
},
|
|
{
|
|
label: "Panama",
|
|
value: "panama",
|
|
render: () => (
|
|
<div>
|
|
<img src="panama.png" alt="Panama flag" />
|
|
Panama
|
|
</div>
|
|
),
|
|
},
|
|
{
|
|
label: "London",
|
|
value: "london",
|
|
render: () => (
|
|
<div>
|
|
<img src="london.png" alt="London flag" />
|
|
London
|
|
</div>
|
|
),
|
|
},
|
|
],
|
|
};
|
|
|
|
const { rerender } = render(<Wrapper {...props} />);
|
|
const input = screen.getByRole("combobox", {
|
|
name: "City",
|
|
});
|
|
const menu: HTMLDivElement = screen.getByRole("listbox", {
|
|
name: "City",
|
|
});
|
|
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.click(
|
|
screen.getByRole("option", { name: "London flag London" }),
|
|
);
|
|
|
|
// Make sure only the label is rendered by default.
|
|
expect(valueRendered).toHaveTextContent("London");
|
|
expect(
|
|
within(valueRendered).queryByRole("img", {
|
|
name: "London flag",
|
|
}),
|
|
).not.toBeInTheDocument();
|
|
|
|
// Now showLabelWhenSelected to false.
|
|
rerender(<Wrapper {...props} showLabelWhenSelected={false} />);
|
|
|
|
// Make sure the HTML content of the option is rendered.
|
|
expect(valueRendered).toHaveTextContent("London");
|
|
within(valueRendered).getByRole("img", {
|
|
name: "London flag",
|
|
});
|
|
});
|
|
});
|
|
|
|
it("renders with className", async () => {
|
|
render(
|
|
<CunninghamProvider>
|
|
<Select
|
|
label="City"
|
|
options={[
|
|
{
|
|
label: "Paris",
|
|
},
|
|
]}
|
|
className="my-custom-class"
|
|
/>
|
|
</CunninghamProvider>,
|
|
);
|
|
expect(
|
|
document.querySelector(".c__field.my-custom-class"),
|
|
).toBeInTheDocument();
|
|
});
|
|
});
|