test: Create unit tests for react components in packages/ui/components/form/select (#10459)
Co-authored-by: gitstart-calcom <gitstart@users.noreply.github.com> Co-authored-by: Keith Williams <keithwillcode@gmail.com>pull/10705/head^2
parent
ac190b89dd
commit
6f5d9dc41f
|
@ -1,6 +1,7 @@
|
|||
import type { Page, Frame } from "@playwright/test";
|
||||
import { test, expect } from "@playwright/test";
|
||||
|
||||
// eslint-disable-next-line no-restricted-imports
|
||||
import prisma from "@calcom/prisma";
|
||||
|
||||
export function todo(title: string) {
|
||||
|
|
|
@ -141,7 +141,7 @@ export function SelectWithValidation<
|
|||
}: SelectProps<Option, IsMulti, Group> & { required?: boolean }) {
|
||||
const [hiddenInputValue, _setHiddenInputValue] = React.useState(() => {
|
||||
if (value instanceof Array || !value) {
|
||||
return;
|
||||
return "";
|
||||
}
|
||||
return value.value || "";
|
||||
});
|
||||
|
|
|
@ -0,0 +1,308 @@
|
|||
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||
|
||||
/* eslint-disable playwright/missing-playwright-await */
|
||||
import { render, screen, fireEvent, waitFor } from "@testing-library/react";
|
||||
import { vi } from "vitest";
|
||||
|
||||
import { Select, SelectField, SelectWithValidation } from "./Select";
|
||||
|
||||
const options = [
|
||||
{ label: "Option 1", value: "option1" },
|
||||
{ label: "Option 2", value: "option2" },
|
||||
{ label: "Option 3", value: "option3" },
|
||||
];
|
||||
|
||||
const onChangeMock = vi.fn();
|
||||
|
||||
const props: any = {
|
||||
name: "test",
|
||||
options: options,
|
||||
defaultValue: { label: "Option 3", value: "option3" },
|
||||
onChange: onChangeMock,
|
||||
};
|
||||
|
||||
const classNames = {
|
||||
singleValue: () => "w-1",
|
||||
valueContainer: () => "w-2",
|
||||
control: () => "w-3",
|
||||
input: () => "w-4",
|
||||
option: () => "w-5",
|
||||
menuList: () => "w-6",
|
||||
menu: () => "w-7",
|
||||
multiValue: () => "w-8",
|
||||
};
|
||||
|
||||
const renderSelectWithForm = (newProps?: any) => {
|
||||
render(
|
||||
<form aria-label="test-form">
|
||||
<label htmlFor="test">Test</label>
|
||||
<Select {...props} {...newProps} inputId="test" />
|
||||
<p>Click Outside</p>
|
||||
</form>
|
||||
);
|
||||
};
|
||||
|
||||
const selectOption = async (optionText: string) => {
|
||||
const element = screen.getByLabelText("Test");
|
||||
element.focus();
|
||||
fireEvent.keyDown(element, { key: "ArrowDown", code: "ArrowDown" });
|
||||
screen.getByText(optionText);
|
||||
const option = screen.getByText(optionText);
|
||||
fireEvent.click(option);
|
||||
};
|
||||
|
||||
describe("Tests for Select File", () => {
|
||||
beforeEach(() => {
|
||||
vi.clearAllMocks();
|
||||
});
|
||||
|
||||
describe("Test Select Component", () => {
|
||||
describe("Tests the default Behavior of Select component", () => {
|
||||
beforeEach(() => {
|
||||
renderSelectWithForm();
|
||||
});
|
||||
|
||||
test("Should render with the correct default value", async () => {
|
||||
expect(screen.getByText("Option 3")).toBeInTheDocument();
|
||||
expect(screen.getByRole("form")).toHaveFormValues({ test: "option3" });
|
||||
});
|
||||
|
||||
test("Should select a correct option", async () => {
|
||||
await waitFor(async () => {
|
||||
await selectOption("Option 2");
|
||||
});
|
||||
|
||||
expect(onChangeMock).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
label: "Option 2",
|
||||
value: "option2",
|
||||
}),
|
||||
expect.objectContaining({
|
||||
action: "select-option",
|
||||
name: "test",
|
||||
option: undefined,
|
||||
})
|
||||
);
|
||||
expect(screen.getByRole("form")).toHaveFormValues({ test: "option2" });
|
||||
});
|
||||
|
||||
test("Should keep the default value after selections are shown and the user clicks outside the selection element", async () => {
|
||||
await waitFor(async () => {
|
||||
const element = screen.getByLabelText("Test");
|
||||
element.focus();
|
||||
fireEvent.keyDown(element, { key: "ArrowDown", code: "ArrowDown" });
|
||||
|
||||
screen.getByText("Option 2");
|
||||
const outsideButton = screen.getByText("Click Outside");
|
||||
fireEvent.click(outsideButton);
|
||||
});
|
||||
|
||||
expect(screen.getByRole("form")).toHaveFormValues({ test: "option3" });
|
||||
});
|
||||
|
||||
test("Should keep the selected value after the user has selected an option and clicked out of the selection element", async () => {
|
||||
await waitFor(async () => {
|
||||
await selectOption("Option 2");
|
||||
const outsideButton = screen.getByText("Click Outside");
|
||||
fireEvent.click(outsideButton);
|
||||
});
|
||||
|
||||
expect(screen.getByRole("form")).toHaveFormValues({ test: "option2" });
|
||||
});
|
||||
});
|
||||
|
||||
describe("Tests the Select Component with isMulti", () => {
|
||||
test("Should have the right behavior when it has the prop isMulti", async () => {
|
||||
renderSelectWithForm({ isMulti: true });
|
||||
|
||||
await waitFor(async () => {
|
||||
const optionText = options[1].label;
|
||||
const element = screen.getByLabelText("Test");
|
||||
element.focus();
|
||||
fireEvent.keyDown(element, { key: "ArrowDown", code: "ArrowDown" });
|
||||
const option = screen.getByText(optionText);
|
||||
fireEvent.click(option);
|
||||
fireEvent.keyDown(element, { key: "ArrowDown", code: "ArrowDown" });
|
||||
});
|
||||
|
||||
const option2Selected = screen.getByLabelText("Remove Option 2");
|
||||
const option3Selected = screen.getAllByLabelText("Remove Option 3");
|
||||
|
||||
expect(option2Selected).toBeInTheDocument();
|
||||
expect(option3Selected.length).toBeGreaterThan(0);
|
||||
|
||||
fireEvent.click(option2Selected);
|
||||
|
||||
expect(option2Selected).not.toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
|
||||
describe("Tests the classes and CSS of the Select component", () => {
|
||||
test("Should render classes correctly when isDisabled is true", async () => {
|
||||
renderSelectWithForm({ isDisabled: true });
|
||||
const singleValueEl = screen.getByText("Option 3");
|
||||
const valueContainerEl = singleValueEl.parentElement;
|
||||
const cotrolEl = valueContainerEl?.parentElement;
|
||||
|
||||
expect(cotrolEl).toHaveClass("bg-subtle");
|
||||
});
|
||||
|
||||
test("Should render classes correctly when classNames props is passed", async () => {
|
||||
renderSelectWithForm({ classNames });
|
||||
|
||||
const singleValueEl = screen.getByText("Option 3");
|
||||
const valueContainerEl = singleValueEl.parentElement;
|
||||
const cotrolEl = valueContainerEl?.parentElement;
|
||||
const inputEl = screen.getByRole("combobox", { hidden: true }).parentElement;
|
||||
|
||||
expect(singleValueEl).toHaveClass("w-1");
|
||||
expect(valueContainerEl).toHaveClass("w-2");
|
||||
expect(cotrolEl).toHaveClass("w-3");
|
||||
expect(inputEl).toHaveClass("w-4");
|
||||
|
||||
await waitFor(async () => {
|
||||
const optionText = options[1].label;
|
||||
const element = screen.getByLabelText("Test");
|
||||
element.focus();
|
||||
fireEvent.keyDown(element, { key: "ArrowDown", code: "ArrowDown" });
|
||||
screen.getByText(optionText);
|
||||
});
|
||||
const optionEl = screen.getByText("Option 2").parentElement?.parentElement;
|
||||
const menuListEl = optionEl?.parentElement;
|
||||
const menuEl = menuListEl?.parentElement;
|
||||
|
||||
expect(optionEl).toHaveClass("w-5");
|
||||
expect(menuListEl).toHaveClass("w-6");
|
||||
expect(menuEl).toHaveClass("w-7");
|
||||
});
|
||||
|
||||
test("Should render classes correctly for multiValue when classNames and isMulti props are passed and menu is open", async () => {
|
||||
renderSelectWithForm({ classNames, isMulti: true });
|
||||
|
||||
const singleValueEl = screen.getByText("Option 3");
|
||||
const multiValueEl = singleValueEl.parentElement;
|
||||
|
||||
expect(singleValueEl).not.toHaveClass("w-1");
|
||||
expect(multiValueEl).toHaveClass("w-8");
|
||||
|
||||
await waitFor(async () => {
|
||||
const optionText = options[1].label;
|
||||
const element = screen.getByLabelText("Test");
|
||||
element.focus();
|
||||
fireEvent.keyDown(element, { key: "ArrowDown", code: "ArrowDown" });
|
||||
const option = screen.getByText(optionText);
|
||||
fireEvent.click(option);
|
||||
fireEvent.keyDown(element, { key: "ArrowDown", code: "ArrowDown" });
|
||||
});
|
||||
|
||||
const option2 = screen.getByText(options[1].label);
|
||||
const menuIsOpenEl = option2.parentElement?.parentElement?.nextSibling;
|
||||
expect(menuIsOpenEl).toHaveClass(
|
||||
"[&>*:last-child]:rotate-180 [&>*:last-child]:transition-transform "
|
||||
);
|
||||
});
|
||||
|
||||
test("Should render classes correctly when focused, selected and menu is open", async () => {
|
||||
renderSelectWithForm();
|
||||
await waitFor(async () => {
|
||||
const optionText = options[1].label;
|
||||
const element = screen.getByLabelText("Test");
|
||||
element.focus();
|
||||
fireEvent.keyDown(element, { key: "ArrowDown", code: "ArrowDown" });
|
||||
const option = screen.getByText(optionText);
|
||||
option.focus();
|
||||
});
|
||||
const option1 = screen.getByText("Option 1");
|
||||
const option1Parent = option1.parentElement?.parentElement;
|
||||
const option3 = screen.getAllByText("Option 3");
|
||||
const option3Parent = option3[1].parentElement?.parentElement;
|
||||
const menuIsOpenEl = option3[0].parentElement?.nextSibling;
|
||||
|
||||
expect(option1).toBeInTheDocument();
|
||||
expect(option1Parent).toHaveClass("bg-subtle");
|
||||
expect(option3[1]).toBeInTheDocument();
|
||||
expect(option3Parent).toHaveClass("bg-emphasis text-default");
|
||||
expect(menuIsOpenEl).toHaveClass("rotate-180 transition-transform");
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe("Tests for SelectField component", () => {
|
||||
const renderSelectField = (newProps?: any) => {
|
||||
render(
|
||||
<form aria-label="test-form">
|
||||
<SelectField {...{ ...props, ...newProps }} />
|
||||
</form>
|
||||
);
|
||||
};
|
||||
|
||||
test("Should render name as fallback label", () => {
|
||||
renderSelectField();
|
||||
expect(screen.getByText(props.name)).toBeInTheDocument();
|
||||
});
|
||||
|
||||
test("Should not render the label element when label not passed and name is undefined", () => {
|
||||
renderSelectField({ name: undefined });
|
||||
expect(screen.queryByRole("label")).not.toBeInTheDocument();
|
||||
});
|
||||
|
||||
test("Should render with the default value and label", () => {
|
||||
renderSelectField({ label: "Test SelectField", name: "test" });
|
||||
|
||||
expect(screen.getByText("Option 3")).toBeInTheDocument();
|
||||
expect(screen.getByRole("form")).toHaveFormValues({ test: "option3" });
|
||||
|
||||
const labelElement = screen.getByText("Test SelectField");
|
||||
expect(labelElement).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
|
||||
describe("Tests for SelectWithValidation component", () => {
|
||||
const handleSubmit = vi.fn((event) => {
|
||||
event.preventDefault();
|
||||
});
|
||||
|
||||
const renderSelectWithValidation = (required: boolean) => {
|
||||
render(
|
||||
<form onSubmit={handleSubmit} aria-label="test-form">
|
||||
<label htmlFor="test">Test</label>
|
||||
<SelectWithValidation {...props} required={required} inputId="test" />
|
||||
<button type="submit">Submit</button>
|
||||
</form>
|
||||
);
|
||||
};
|
||||
|
||||
test("Should render with the default value", () => {
|
||||
renderSelectWithValidation(true);
|
||||
|
||||
expect(screen.getByText("Option 3")).toBeInTheDocument();
|
||||
expect(screen.getByRole("form")).toHaveFormValues({ test: "option3" });
|
||||
});
|
||||
|
||||
test("Should render an input element with the value passed as prop", () => {
|
||||
renderSelectWithValidation(false);
|
||||
expect(screen.getAllByDisplayValue("option3")).toHaveLength(1);
|
||||
});
|
||||
|
||||
test("Should submit the form with the selected value after validation", async () => {
|
||||
renderSelectWithValidation(true);
|
||||
|
||||
await waitFor(async () => {
|
||||
await selectOption("Option 2");
|
||||
const submitButton = screen.getByRole("button", { name: "Submit" });
|
||||
fireEvent.click(submitButton);
|
||||
});
|
||||
|
||||
expect(handleSubmit).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
test("Should fail to submit if nothing is selected", async () => {
|
||||
renderSelectWithValidation(true);
|
||||
|
||||
const submitButton = screen.getByRole("button", { name: "Submit" });
|
||||
fireEvent.click(submitButton);
|
||||
|
||||
expect(handleSubmit).not.toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
});
|
|
@ -44,6 +44,14 @@ vi.mock("@calcom/lib/OgImages", async () => {
|
|||
return {};
|
||||
});
|
||||
|
||||
vi.mock("@calcom/lib/hooks/useLocale", () => ({
|
||||
useLocale: () => {
|
||||
return {
|
||||
t: (str: string) => str,
|
||||
};
|
||||
},
|
||||
}));
|
||||
|
||||
expect.extend({
|
||||
tabToBeDisabled(received) {
|
||||
const isDisabled = received.classList.contains("pointer-events-none");
|
||||
|
|
Loading…
Reference in New Issue