Files
heroui-inc--heroui/packages/react/tests/components/input-otp/input-otp.test.tsx
T
WK bb9c4b5582 V3.2.4 (#6758)
* fix(tabs): clamp ListContainer chevron scrolling to the scrollable range (#6749)

* chore(deps): bump react-aria dependencies (#6752)

* chore(deps): bump tailwind-variants to v3.3.1 (#6760)

* chore(docs): release note for 3.2.4

* test(react): behavioral testing infra (#6754)

* fix(link): expose data-slot on link root

* fix(react): put style export condition before default

* test(react): behavioral testing infra and suites

* chore(testing): own vitest-browser-react and clarify coverage gates

* chore: remove pinned version

* fix(autocomplete): prevent popover from clipping the last options (#6766)

* chore(docs): update release note

* fix(styles): prevent subpixel border overflow on badge (#6768)

* chore(docs): update release note

* fix(styles): allow backdrop click to dismiss modal with scroll outside (#6770)

* chore(docs): update release note

* fix(tooltip): restore exit animation when swapping between tooltips (#6772)

* chore(docs): update release note

---------

Co-authored-by: Tyler <56234978+runnerboy22@users.noreply.github.com>
Co-authored-by: Tianen Pang <32772271+tianenpang@users.noreply.github.com>
2026-08-07 06:19:12 -07:00

136 lines
3.9 KiB
TypeScript

import type {InputOTPProps} from "@/components/input-otp";
import {cleanup, render, screen, setupUser} from "@heroui/testing/helpers";
import {FieldError} from "@/components/field-error";
import {InputOTP} from "@/components/input-otp";
const renderOtp = (props: Omit<Partial<InputOTPProps>, "children"> = {}) => {
return render(
<InputOTP maxLength={6} {...props}>
<InputOTP.Group>
<InputOTP.Slot index={0} />
<InputOTP.Slot index={1} />
<InputOTP.Slot index={2} />
</InputOTP.Group>
<InputOTP.Separator />
<InputOTP.Group>
<InputOTP.Slot index={3} />
<InputOTP.Slot index={4} />
<InputOTP.Slot index={5} />
</InputOTP.Group>
</InputOTP>,
);
};
describe("InputOTP", () => {
let user: ReturnType<typeof setupUser>;
beforeAll(() => {
user = setupUser();
});
afterEach(() => {
// Unmount before jsdom tears down `window` (pending rAF/timeout).
cleanup();
});
it("exposes root and compound data-slots", () => {
renderOtp();
expect(document.querySelector('[data-slot="input-otp"]')).not.toBeNull();
expect(document.querySelectorAll('[data-slot="input-otp-group"]')).toHaveLength(2);
expect(document.querySelectorAll('[data-slot="input-otp-slot"]')).toHaveLength(6);
expect(document.querySelector('[data-slot="input-otp-separator"]')).not.toBeNull();
});
it("exposes BEM block on the container", () => {
renderOtp();
const container = document.querySelector('[data-input-otp-container="true"]');
expect(container?.className).toEqual(expect.stringContaining("input-otp"));
});
it("calls onChange while typing digits", async () => {
const onChange = vi.fn();
renderOtp({onChange});
const input = document.querySelector('[data-slot="input-otp"]') as HTMLInputElement;
expect(input).not.toBeNull();
await user.click(input);
await user.keyboard("12");
expect(onChange).toHaveBeenCalled();
expect(onChange.mock.calls.at(-1)?.[0]).toBe("12");
});
it("calls onComplete when maxLength is reached", async () => {
const onComplete = vi.fn();
renderOtp({maxLength: 4, onComplete});
const input = document.querySelector('[data-slot="input-otp"]') as HTMLInputElement;
await user.click(input);
await user.keyboard("1234");
expect(onComplete).toHaveBeenCalledWith("1234");
});
it("supports disabled state", async () => {
const onChange = vi.fn();
renderOtp({isDisabled: true, onChange});
const input = document.querySelector('[data-slot="input-otp"]') as HTMLInputElement;
expect(input).toBeDisabled();
expect(input).toHaveAttribute("data-disabled", "true");
await user.click(input);
await user.keyboard("1");
expect(onChange).not.toHaveBeenCalled();
});
it("supports invalid state with FieldError", () => {
render(
<InputOTP isInvalid maxLength={6} validationErrors={["Invalid code"]}>
<InputOTP.Group>
<InputOTP.Slot index={0} />
<InputOTP.Slot index={1} />
<InputOTP.Slot index={2} />
</InputOTP.Group>
<FieldError>Invalid code</FieldError>
</InputOTP>,
);
const root = document.querySelector('[data-slot="input-otp"]');
expect(root).toHaveAttribute("data-invalid", "true");
expect(document.querySelector('[data-slot="input-otp-slot"]')).toHaveAttribute(
"data-invalid",
"true",
);
expect(screen.getByText("Invalid code")).toBeInTheDocument();
});
it("supports filling slots from paste", async () => {
const onChange = vi.fn();
renderOtp({onChange});
const input = document.querySelector('[data-slot="input-otp"]') as HTMLInputElement;
await user.click(input);
await user.paste("654321");
expect(onChange).toHaveBeenCalled();
expect(onChange.mock.calls.at(-1)?.[0]).toBe("654321");
expect(document.querySelectorAll('[data-filled="true"]')).toHaveLength(6);
});
});