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"); }); }); });