import React from "react";
import { Select } from "./index";
import { SelectOption } from "./types";
import * as utils from "../../utils";
import { fireEvent, render, screen } from "@testing-library/react";
// Mock react-select to simplify testing
jest.mock("react-select", () => {
// eslint-disable-next-line @typescript-eslint/no-require-imports
const { forwardRef } = require("react");
// eslint-disable-next-line react/display-name
const MockSelect = forwardRef((props: any, ref: any) => {
const {
options,
value,
placeholder,
onChange,
className,
classNames,
...rest
} = props;
// Call classNames functions to exercise them for coverage
if (classNames) {
classNames.control?.({ isFocused: false });
classNames.control?.({ isFocused: true });
classNames.indicatorSeparator?.();
classNames.dropdownIndicator?.({ selectProps: { value: null } });
classNames.dropdownIndicator?.({ selectProps: { value: "test" } });
classNames.singleValue?.();
classNames.menu?.();
classNames.option?.({ isFocused: false, isSelected: false, label: "A" });
classNames.option?.({ isFocused: true, isSelected: false, label: "A" });
classNames.option?.({ isFocused: false, isSelected: true, label: "A" });
classNames.placeholder?.();
classNames.input?.();
classNames.valueContainer?.();
}
return (
);
});
return {
__esModule: true,
default: MockSelect,
// eslint-disable-next-line @typescript-eslint/no-unused-vars
createFilter: (filterConfig: any) => () => true,
};
});
const defaultOptions: SelectOption[] = [
{ label: "Option A", value: "a" },
{ label: "Option B", value: "b" },
{ label: "Option C", value: "c" },
];
describe("Select", () => {
it("renders with options", () => {
render();
expect(screen.getByTestId("my-select")).toBeInTheDocument();
});
it("renders placeholder", () => {
render();
expect(screen.getByText("Pick one")).toBeInTheDocument();
});
it("appends * to placeholder when required", () => {
render(
);
expect(screen.getByText("Pick*")).toBeInTheDocument();
});
it("calls onChange when an option is selected", () => {
const onChange = jest.fn();
render(
);
fireEvent.change(screen.getByTestId("sel"), { target: { value: "b" } });
expect(onChange).toHaveBeenCalledWith(
defaultOptions[1],
expect.objectContaining({ action: "select-option" })
);
});
it("renders label when provided", () => {
render();
expect(screen.getByText("My Label")).toBeInTheDocument();
});
it("renders label with custom className", () => {
render(
);
const label = screen.getByText("Label");
expect(label).toHaveClass("custom-class");
});
it("does not render label when not provided", () => {
const { container } = render();
expect(container.querySelector("label")).not.toBeInTheDocument();
});
it("renders error message when error prop is provided", () => {
render();
expect(screen.getByText("Required field")).toBeInTheDocument();
});
it("renders helperText when no error", () => {
render();
expect(screen.getByText("Choose wisely")).toBeInTheDocument();
});
it("does not render helperText when error is present", () => {
render(
);
expect(screen.getByText("Error")).toBeInTheDocument();
expect(screen.queryByText("Choose wisely")).not.toBeInTheDocument();
});
it("renders unstyled variant without label/error wrapper", () => {
const { container } = render(
);
expect(screen.queryByText("Should not appear")).not.toBeInTheDocument();
// unstyled variant doesn't wrap in a div.w-full
expect(container.querySelector(".w-full")).not.toBeInTheDocument();
});
it("applies size sm classes via classNames", () => {
const cxSpy = jest.spyOn(utils, "cx");
render(
);
expect(screen.getByTestId("sm-select")).toBeInTheDocument();
expect(
cxSpy.mock.calls.some(call => call[0] === "h-12 px-3 rounded-lg")
).toBe(true);
cxSpy.mockRestore();
});
it("applies default (md) size classes", () => {
const cxSpy = jest.spyOn(utils, "cx");
render(
);
expect(screen.getByTestId("md-select")).toBeInTheDocument();
expect(
cxSpy.mock.calls.some(call => call[0] === "h-14 px-3 rounded-xl")
).toBe(true);
cxSpy.mockRestore();
});
it("applies size lg classes via classNames", () => {
const cxSpy = jest.spyOn(utils, "cx");
render(
);
expect(screen.getByTestId("lg-select")).toBeInTheDocument();
expect(
cxSpy.mock.calls.some(call => call[0] === "h-16 px-3 rounded-2xl")
).toBe(true);
cxSpy.mockRestore();
});
it("falls back to md size classes when size is not provided", () => {
const cxSpy = jest.spyOn(utils, "cx");
render(
);
expect(screen.getByTestId("default-select")).toBeInTheDocument();
expect(
cxSpy.mock.calls.some(call => call[0] === "h-14 px-3 rounded-xl")
).toBe(true);
cxSpy.mockRestore();
});
it("handles singleValueClassName prop", () => {
render(
);
expect(screen.getByTestId("sv-select")).toBeInTheDocument();
});
it("passes singleValueClassName into the singleValue cx computation", () => {
const cxSpy = jest.spyOn(utils, "cx");
render(
);
// The mock invokes classNames.singleValue(), which builds its class list
// via cx(...). Assert the call originates from the singleValue computation
// (first arg is the base "text-text-secondary" class in the default,
// non-custom style) and that the prop is passed through as the second arg.
expect(cxSpy).toHaveBeenCalledWith(
"text-text-secondary",
"custom-single-value"
);
cxSpy.mockRestore();
});
it("handles isCustomStyle prop", () => {
render(
);
expect(screen.getByTestId("custom-select")).toBeInTheDocument();
});
it("handles hasError prop", () => {
render(
);
expect(screen.getByTestId("err-select")).toBeInTheDocument();
});
it("handles filterOptions prop", () => {
render(
);
expect(screen.getByTestId("filter-select")).toBeInTheDocument();
});
it("passes data-cy prop", () => {
render();
expect(
document.querySelector('[data-cy="cypress-sel"]')
).toBeInTheDocument();
});
it("renders with a selected value", () => {
render(
);
const select = screen.getByTestId("val-select") as HTMLSelectElement;
expect(select.value).toBe("c");
});
it("has the correct displayName", () => {
expect(Select.displayName).toBe("Select");
});
it("exercises option classNames with last option label", () => {
// Ensures the last-option border logic is covered
const opts = [
{ label: "First", value: "1" },
{ label: "Last", value: "2" },
];
render(
);
expect(screen.getByTestId("last-opt-select")).toBeInTheDocument();
});
});