import "@testing-library/jest-dom"; import type {UserEvent} from "@testing-library/user-event"; import * as React from "react"; import {render, renderHook, screen} from "@testing-library/react"; import {Controller, useForm} from "react-hook-form"; import userEvent from "@testing-library/user-event"; import {Form} from "@heroui/form"; import {InputOtp} from "../src"; // Mock document.elementFromPoint to avoid test environment errors beforeAll(() => { document.elementFromPoint = jest.fn(() => { const mockElement = document.createElement("div"); return mockElement; }); }); describe("InputOtp Component", () => { let user: UserEvent; beforeAll(() => { user = userEvent.setup(); }); it("should render correctly", () => { const wrapper = render(); expect(() => wrapper.unmount()).not.toThrow(); }); it("should forward ref correctly", () => { const ref = React.createRef(); render(); expect(ref.current).not.toBeNull(); }); it("should create segments according to length prop", () => { render(); const segments = screen.getAllByRole("presentation"); expect(segments.length).toBe(5); }); it("should display error message when isInvalid is true", () => { const errorMessage = "custom error message"; render(); expect(screen.getByText(errorMessage)).toBeInTheDocument(); }); it("should display description message", () => { const descriptionMessage = "custom description message"; render(); expect(screen.getByText(descriptionMessage)).toBeInTheDocument(); }); it("should not focus when disabled", async () => { render(); const input = screen.getByRole("textbox"); await user.click(input); expect(input).not.toHaveAttribute("data-focus", "true"); }); it("should activate the first segment on click", async () => { render(); const input = screen.getByRole("textbox"); const segments = screen.getAllByRole("presentation"); await user.click(input); expect(segments[0]).toHaveAttribute("data-active", "true"); expect(segments[1]).not.toHaveAttribute("data-active"); }); it("should move focus to the next segment on valid input", async () => { render(); const input = screen.getByRole("textbox"); const segments = screen.getAllByRole("presentation"); await user.click(input); expect(segments[1]).not.toHaveAttribute("data-active"); await user.keyboard("1"); expect(segments[1]).toHaveAttribute("data-active", "true"); expect(input).toHaveAttribute("value", "1"); }); it("should clear input on backspace", async () => { render(); const input = screen.getByRole("textbox"); const segments = screen.getAllByRole("presentation"); await user.click(input); await user.keyboard("12"); expect(input).toHaveAttribute("value", "12"); expect(segments[2]).toHaveAttribute("data-active", "true"); await user.keyboard("[Backspace]"); expect(input).toHaveAttribute("value", "1"); expect(segments[1]).toHaveAttribute("data-active", "true"); }); it("should paste values", async () => { render(); const input = screen.getByRole("textbox"); await user.click(input); await user.paste("1234"); expect(input).toHaveAttribute("value", "1234"); // Test partial paste await user.clear(input); await user.paste("12"); expect(input).toHaveAttribute("value", "12"); // Test longer input await user.clear(input); await user.paste("12345"); expect(input).toHaveAttribute("value", "1234"); // Test invalid characters await user.clear(input); await user.paste("12ab"); expect(input).toHaveAttribute("value", ""); }); it("should restrict non-allowed inputs", async () => { render(); const input = screen.getByRole("textbox"); const segments = screen.getAllByRole("presentation"); await user.click(input); await user.keyboard("a"); expect(input).toHaveAttribute("value", ""); expect(segments[0]).toHaveAttribute("data-active", "true"); }); it("should allow inputs based on custom regex", async () => { const regEx = "^[a-z]*$"; render(); const input = screen.getByRole("textbox"); await user.click(input); await user.keyboard("a"); expect(input).toHaveAttribute("value", "a"); }); it("should call onComplete when all segments are filled", async () => { const onComplete = jest.fn(); render(); const input = screen.getByRole("textbox"); await user.click(input); await user.paste("1234"); expect(onComplete).toHaveBeenCalledTimes(1); expect(onComplete).toHaveBeenCalledWith("1234"); // Test partial input followed by paste await user.clear(input); await user.keyboard("1"); await user.paste("234"); expect(onComplete).toHaveBeenCalledTimes(2); expect(onComplete).toHaveBeenCalledWith("1234"); }); it("should autofocus when autofocus prop is passed.", () => { // eslint-disable-next-line jsx-a11y/no-autofocus render(); const segments = screen.getAllByRole("presentation"); expect(segments[0]).toHaveAttribute("data-focus", "true"); }); it("should not autofocus when autofocus prop is not passed.", () => { render(); const segments = screen.getAllByRole("presentation"); expect(segments[0]).not.toHaveAttribute("data-focus", "true"); }); }); describe("Validation", () => { let user: UserEvent; beforeAll(() => { user = userEvent.setup(); }); it("supports isRequired when validationBehavior=native", async () => { const {getByTestId} = render(
, ); const button = container.querySelector("button"); if (!button) { throw new Error("Button not found"); } await user.click(button); expect(onSubmit).toHaveBeenCalledTimes(0); const inputOtp3 = screen.getByTestId("input-otp-3"); const input = inputOtp3.querySelector("input"); if (!input) { throw new Error("Input not found"); } await user.click(input); await user.type(input, "1234"); await user.click(button); expect(onSubmit).toHaveBeenCalledTimes(1); }); it("should work correctly with react-hook-form controller", async () => { const {result} = renderHook(() => useForm({ defaultValues: { withController: "", }, }), ); const {control} = result.current; render(
} /> , ); const inputOtp = screen.getByTestId("input-otp"); const input = inputOtp.querySelector("input"); if (!input) { throw new Error("Input not found"); } await user.click(input); await user.type(input, "1nj23aa4"); expect(input).toHaveAttribute("value", "1234"); }); });