import "@testing-library/jest-dom";
import { createRef } from "react";
import { Input, InputField } from "./index";
import { fireEvent, render, screen } from "@testing-library/react";
// Mock dependencies
jest.mock("@shared/components/material-icon", () => ({
MaterialIcon: ({ name, size, className }: any) => (
{name}
),
}));
jest.mock("@shared/components/text", () => ({
Text: ({ children, className, as }: any) => {
const Tag = as || "span";
return (
{children}
);
},
}));
jest.mock("@shared/utils", () => ({
cx: (...args: any[]) => args.filter(Boolean).join(" "),
}));
describe("Input", () => {
it("has displayName set to Input", () => {
expect(Input.displayName).toBe("Input");
});
describe("basic rendering", () => {
it("renders an input element", () => {
render();
expect(screen.getByRole("textbox")).toBeInTheDocument();
});
it("renders with placeholder", () => {
render();
expect(screen.getByPlaceholderText("Enter text")).toBeInTheDocument();
});
it("renders with default type text", () => {
render();
const input = screen.getByTestId("input");
expect(input).toHaveAttribute("type", "text");
});
it("renders with custom type", () => {
render();
const input = screen.getByTestId("input");
expect(input).toHaveAttribute("type", "email");
});
});
describe("ref forwarding", () => {
it("forwards ref to input element", () => {
const ref = createRef();
render();
expect(ref.current).toBeInstanceOf(HTMLInputElement);
});
it("can imperatively focus the input via ref", () => {
const ref = createRef();
render();
ref.current?.focus();
expect(ref.current).toBe(document.activeElement);
});
});
describe("label", () => {
it("renders label when provided", () => {
render();
expect(screen.getByText("Email")).toBeInTheDocument();
});
it("does not render label when not provided", () => {
const { container } = render();
expect(container.querySelector("label")).not.toBeInTheDocument();
});
it("associates label with input via htmlFor/id", () => {
render();
const input = screen.getByRole("textbox");
expect(input).toHaveAttribute("id", "Username");
});
it("shows required asterisk when required is true", () => {
render();
expect(screen.getByText("*")).toBeInTheDocument();
});
it("does not show asterisk when required is false", () => {
render();
expect(screen.queryByText("*")).not.toBeInTheDocument();
});
});
describe("size variants", () => {
it("applies medium size class by default", () => {
const { container } = render();
const wrapper = container.querySelector(".relative.flex");
expect(wrapper?.className).toContain("h-14");
});
it("applies slim size class", () => {
const { container } = render();
const wrapper = container.querySelector(".relative.flex");
expect(wrapper?.className).toContain("h-12");
});
it("applies large size class", () => {
const { container } = render();
const wrapper = container.querySelector(".relative.flex");
expect(wrapper?.className).toContain("h-[60px]");
});
});
describe("state management", () => {
it("applies hover state on mouseOver", () => {
render();
const input = screen.getByTestId("input");
fireEvent.mouseOver(input);
const wrapper = input.closest(".relative.flex");
expect(wrapper?.className).toContain("border-input-border-hover");
});
it("removes hover state on mouseOut", () => {
render();
const input = screen.getByTestId("input");
fireEvent.mouseOver(input);
fireEvent.mouseOut(input);
const wrapper = input.closest(".relative.flex");
expect(wrapper?.className).not.toContain("border-input-border-hover");
});
it("applies focus state on focus", () => {
render();
const input = screen.getByTestId("input");
fireEvent.focus(input);
const wrapper = input.closest(".relative.flex");
expect(wrapper?.className).toContain("border-input-border-selected");
});
it("removes focus state on blur", () => {
render();
const input = screen.getByTestId("input");
fireEvent.focus(input);
fireEvent.blur(input);
const wrapper = input.closest(".relative.flex");
expect(wrapper?.className).not.toContain("border-input-border-selected");
});
it("calls onFocus callback", () => {
const onFocus = jest.fn();
render();
fireEvent.focus(screen.getByTestId("input"));
expect(onFocus).toHaveBeenCalled();
});
it("calls onBlur callback", () => {
const onBlur = jest.fn();
render();
fireEvent.blur(screen.getByTestId("input"));
expect(onBlur).toHaveBeenCalled();
});
});
describe("error state", () => {
it("applies error state styling when state is error", () => {
const { container } = render();
const wrapper = container.querySelector(".relative.flex");
expect(wrapper?.className).toContain("border-input-border-critical");
});
it("displays error text when state is error and errorText provided", () => {
render();
expect(screen.getByText("Invalid input")).toBeInTheDocument();
});
it("does not display error text when state is not error", () => {
render();
expect(screen.queryByText("Invalid input")).not.toBeInTheDocument();
});
it("does not display error section when errorText is empty", () => {
render();
expect(screen.queryByTestId("icon-info")).not.toBeInTheDocument();
});
it("applies error state when hasError is true", () => {
const { container } = render();
const wrapper = container.querySelector(".relative.flex");
expect(wrapper?.className).toContain("border-input-border-critical");
});
it("hasError overrides state prop", () => {
const { container } = render();
const wrapper = container.querySelector(".relative.flex");
expect(wrapper?.className).toContain("border-input-border-critical");
});
it("renders info icon with error text", () => {
render();
expect(screen.getByTestId("icon-info")).toBeInTheDocument();
});
});
describe("prefix icon", () => {
it("renders prefix icon when prefixIconName is provided", () => {
render();
expect(screen.getByTestId("icon-search")).toBeInTheDocument();
});
it("does not render prefix icon when not provided", () => {
render();
expect(screen.queryByTestId("icon-search")).not.toBeInTheDocument();
expect(screen.queryByTestId("icon-location_on")).not.toBeInTheDocument();
});
it("applies custom prefix icon size", () => {
render();
const icon = screen.getByTestId("icon-search");
expect(icon).toHaveAttribute("data-size", "40");
});
it("applies prefixIconClassName", () => {
render(
);
const icon = screen.getByTestId("icon-search");
expect(icon).toHaveClass("text-gray-500");
});
});
describe("suffix icon and password toggle", () => {
it("renders suffix icon when suffixIconName is provided", () => {
render();
// Default inputType is "text", so icon shows "visibility"
expect(screen.getByTestId("icon-visibility")).toBeInTheDocument();
});
it("toggles password visibility on suffix icon click", () => {
render(
);
const input = screen.getByTestId("input");
expect(input).toHaveAttribute("type", "password");
// When type is password, icon shows visibility_off
const iconWrapper = screen.getByTestId(
"icon-visibility_off"
).parentElement;
fireEvent.click(iconWrapper!);
expect(input).toHaveAttribute("type", "text");
// After toggle to text, icon shows visibility
const visibleIcon = screen.getByTestId("icon-visibility").parentElement;
fireEvent.click(visibleIcon!);
expect(input).toHaveAttribute("type", "password");
});
it("shows visibility icon when type is text after toggle", () => {
render();
const iconWrapper = screen.getByTestId(
"icon-visibility_off"
).parentElement;
fireEvent.click(iconWrapper!);
expect(screen.getByTestId("icon-visibility")).toBeInTheDocument();
});
});
describe("disabled and loading", () => {
it("disables input when disabled prop is true", () => {
render();
expect(screen.getByTestId("input")).toBeDisabled();
});
it("disables input when loading is true", () => {
render();
expect(screen.getByTestId("input")).toBeDisabled();
});
it("is not disabled by default", () => {
render();
expect(screen.getByTestId("input")).not.toBeDisabled();
});
});
describe("containerClassName", () => {
it("applies containerClassName to the input wrapper", () => {
const { container } = render();
const wrapper = container.querySelector(".relative.flex");
expect(wrapper?.className).toContain("custom-cnt");
});
});
describe("className", () => {
it("applies className to the input element", () => {
render();
const input = screen.getByTestId("input");
expect(input).toHaveClass("custom-input");
});
});
describe("state prop variants", () => {
it("applies focus styling when state is focus", () => {
const { container } = render();
const wrapper = container.querySelector(".relative.flex");
expect(wrapper?.className).toContain("border-input-border-selected");
});
it("applies active styling when state is active", () => {
const { container } = render();
const wrapper = container.querySelector(".relative.flex");
expect(wrapper?.className).toContain("border-input-border-selected");
});
it("applies hover styling when state is hover and not focused", () => {
const { container } = render();
const wrapper = container.querySelector(".relative.flex");
expect(wrapper?.className).toContain("border-input-border-hover");
});
it("does not apply hover styling when state is hover but focused", () => {
render();
const input = screen.getByTestId("input");
fireEvent.focus(input);
const wrapper = input.closest(".relative.flex");
expect(wrapper?.className).not.toContain("border-input-border-hover");
});
});
describe("InputField export", () => {
it("exports InputField as a render function", () => {
expect(typeof InputField).toBe("function");
});
});
});