import React from "react"; import { Modal } from "./index"; import { fireEvent, render, screen } from "@testing-library/react"; import "@testing-library/jest-dom"; // Mock react-modal jest.mock("react-modal", () => { const MockReactModal = ({ isOpen, children, className, overlayClassName, onRequestClose, parentSelector, }: any) => { // Call parentSelector to cover the arrow function if (parentSelector) parentSelector(); if (!isOpen) return null; return (
{children}
); }; MockReactModal.setAppElement = jest.fn(); return MockReactModal; }); // Allow overriding the transition state for specific tests let mockTransitionState: string | null = null; const setMockTransitionState = (state: string | null) => { mockTransitionState = state; }; // Mock react-transition-group jest.mock("react-transition-group", () => ({ Transition: ({ children, in: inProp }: any) => { const state = mockTransitionState ?? (inProp ? "entered" : "exited"); return children(state); }, })); // Mock useBodyScrollLock jest.mock("../../hooks/use-body-scroll-lock", () => jest.fn()); describe("Modal", () => { const defaultProps = { isOpen: true, onRequestClose: jest.fn(), }; beforeEach(() => { jest.clearAllMocks(); }); describe("Rendering", () => { it("renders when isOpen is true", () => { render(Content); expect(screen.getByTestId("react-modal")).toBeInTheDocument(); expect(screen.getByText("Content")).toBeInTheDocument(); }); it("does not render when isOpen is false", () => { render( Content ); expect(screen.queryByTestId("react-modal")).not.toBeInTheDocument(); }); it("has correct displayName", () => { expect(Modal.displayName).toBe("Modal"); }); it("renders children in body div", () => { render(Hello World); expect(screen.getByText("Hello World")).toBeInTheDocument(); }); }); describe("Title", () => { it("renders title when provided", () => { render(); expect(screen.getByText("My Modal")).toBeInTheDocument(); }); it("does not render title section when not provided", () => { const { container } = render(); expect(container.querySelector(".border-b")).not.toBeInTheDocument(); }); }); describe("Close button", () => { it("renders close button by default", () => { render(); expect(screen.getByTestId("close-button")).toBeInTheDocument(); }); it("hides close button when hideCloseButton is true", () => { render(); expect(screen.queryByTestId("close-button")).not.toBeInTheDocument(); }); it("calls onRequestClose when close button is clicked", () => { const onRequestClose = jest.fn(); render(); fireEvent.click(screen.getByTestId("close-button")); expect(onRequestClose).toHaveBeenCalledTimes(1); }); it("renders close icon in close button", () => { render(); expect(screen.getByText("close")).toBeInTheDocument(); }); }); describe("Size", () => { it("applies md size classes by default", () => { render(); const modal = screen.getByTestId("react-modal"); expect(modal.className).toContain("inset-[15%]"); }); it("applies xl size classes", () => { render(); const modal = screen.getByTestId("react-modal"); expect(modal.className).toContain("inset-[5%]"); }); it("applies lg size classes", () => { render(); const modal = screen.getByTestId("react-modal"); expect(modal.className).toContain("inset-[10%]"); }); it("applies sm size classes", () => { render(); const modal = screen.getByTestId("react-modal"); expect(modal.className).toContain("w-[640px]"); }); it("applies xs size classes", () => { render(); const modal = screen.getByTestId("react-modal"); expect(modal.className).toContain("w-[calc(100%-30px)]"); }); }); describe("Shape", () => { it("applies default shape", () => { render(); const modal = screen.getByTestId("react-modal"); expect(modal.className).not.toContain("rounded-3xl"); }); it("applies rounded shape", () => { render(); const modal = screen.getByTestId("react-modal"); expect(modal.className).toContain("rounded-3xl"); }); }); describe("Centered", () => { it("applies centered classes", () => { render(); const modal = screen.getByTestId("react-modal"); expect(modal.className).toContain("top-0"); }); }); describe("CSS classes", () => { it("applies custom className", () => { render(); const modal = screen.getByTestId("react-modal"); expect(modal.className).toContain("custom-modal"); }); it("applies bodyClassName to body div", () => { render(); const body = screen .getByTestId("react-modal") .querySelector(".custom-body"); expect(body).toBeInTheDocument(); }); it("applies closeButtonClassName", () => { render(); const closeBtn = screen.getByTestId("close-button"); expect(closeBtn.className).toContain("custom-close"); }); }); describe("Body style", () => { it("applies bodyStyle to body div", () => { render( Content ); const body = screen.getByText("Content"); // Body content should be present inside the modal expect(body).toBeInTheDocument(); }); }); describe("Animation", () => { it("applies popper animation classes by default", () => { render(); const modal = screen.getByTestId("react-modal"); expect(modal.className).toContain("transition-transform"); }); it("applies bottomSheet animation classes", () => { render(); const modal = screen.getByTestId("react-modal"); expect(modal.className).toContain("ease-in-out"); }); it("applies exited state classes for popper when not transitioning", () => { // Since our mock always returns 'entered' state when in=true, // we can verify it includes entered classes render(); const modal = screen.getByTestId("react-modal"); expect(modal.className).toContain("pointer-events-auto"); }); it("returns default popper classes for unknown transition state", () => { setMockTransitionState("unmounted"); render(); const modal = screen.getByTestId("react-modal"); expect(modal.className).toContain("opacity-0"); expect(modal.className).toContain("translate-y-[-18px]"); setMockTransitionState(null); }); it("returns default bottomSheet classes for unknown transition state", () => { setMockTransitionState("unmounted"); render(); const modal = screen.getByTestId("react-modal"); expect(modal.className).toContain("opacity-0"); expect(modal.className).toContain("translate-y-full"); setMockTransitionState(null); }); }); describe("Data attributes", () => { it("passes data-testid to body div", () => { render(); const elements = screen.getAllByTestId("modal-body"); expect(elements.length).toBeGreaterThan(0); }); it("supports parentSelector prop", () => { // Add a main element to the document const main = document.createElement("main"); document.body.appendChild(main); render( HTMLElement)} /> ); expect(screen.getByTestId("react-modal")).toBeInTheDocument(); document.body.removeChild(main); }); it("falls back to document.body when parentSelector element not found", () => { render( HTMLElement) } /> ); expect(screen.getByTestId("react-modal")).toBeInTheDocument(); }); it("applies closeWrapperClassName", () => { const { container } = render( ); const wrapper = container.querySelector(".wrapper-class"); expect(wrapper).toBeInTheDocument(); }); it("applies bottomSheet animation classes", () => { render(); const modal = screen.getByTestId("react-modal"); expect(modal.className).toContain("translate-y-0"); expect(modal.className).toContain("pointer-events-auto"); }); it("returns empty animation classes for unknown animation type", () => { render(); const modal = screen.getByTestId("react-modal"); // No animation-specific classes, just the base classes expect(modal.className).not.toContain("translate-y"); expect(modal.className).not.toContain("opacity-0"); }); }); });