import "@testing-library/jest-dom"; import CartRetentionBanner from "./index"; import { TypeCartRetentionBannerFields } from "./types"; import { act, fireEvent, render, screen } from "@testing-library/react"; // Mock dependencies jest.mock("@shared/components/text", () => ({ Text: ({ children, ...props }: any) => {children}, })); jest.mock("@shared/contentful/blocks/button", () => ({ Button: ({ children, onClick, buttonLabel, ...props }: any) => ( ), })); jest.mock("@shared/contentful/blocks/modal", () => ({ Modal: ({ isOpen, onRequestClose, children, ...props }: any) => (
{isOpen && ( <> {children} )}
), })); jest.mock("@shared/hooks/contentful/use-contentful-rich-text", () => ({ renderContentfulRichText: (doc: any, _: any, className: string) => (
Rich Text Content
), })); jest.mock("@shared/utils/contentful/to-document", () => ({ toDocument: (json: any) => json, })); describe("CartRetentionBanner", () => { const defaultFields: TypeCartRetentionBannerFields = { entryTitle: "Cart Banner", anchorId: "cart-banner", mainHeading: "Welcome back!", description: { json: { content: [{ nodeType: "paragraph" }] } }, cta: { buttonLabel: "Continue checkout" }, }; beforeEach(() => { jest.useFakeTimers(); }); afterEach(() => { jest.useRealTimers(); }); describe("modal behavior", () => { it("renders a section with anchorId", () => { render(); const section = document.querySelector("section#cart-banner"); expect(section).toBeInTheDocument(); }); it("renders with data-testid cart-retention-banner", () => { render(); expect(screen.getByTestId("cart-retention-banner")).toBeInTheDocument(); }); it("opens modal when checkShouldShowBanner returns true", async () => { const checkShouldShowBanner = jest.fn().mockReturnValue(true); render( ); // Allow useEffect to fire act(() => { jest.advanceTimersByTime(200); }); const modal = screen.getByTestId("modal"); expect(modal).toHaveAttribute("data-open", "true"); }); it("does not open modal when checkShouldShowBanner returns false", () => { const checkShouldShowBanner = jest.fn().mockReturnValue(false); render( ); act(() => { jest.advanceTimersByTime(200); }); const modal = screen.getByTestId("modal"); expect(modal).toHaveAttribute("data-open", "false"); }); it("calls onBannerShown when modal opens", () => { const onBannerShown = jest.fn(); const checkShouldShowBanner = jest.fn().mockReturnValue(true); render( ); act(() => { jest.advanceTimersByTime(200); }); expect(onBannerShown).toHaveBeenCalled(); }); it("closes modal and calls onClose", () => { const onClose = jest.fn(); const checkShouldShowBanner = jest.fn().mockReturnValue(true); render( ); act(() => { jest.advanceTimersByTime(200); }); fireEvent.click(screen.getByTestId("modal-close")); expect(onClose).toHaveBeenCalled(); }); it("calls onContinueCheckout when CTA button is clicked in modal", () => { const onContinueCheckout = jest.fn(); const checkShouldShowBanner = jest.fn().mockReturnValue(true); render( ); act(() => { jest.advanceTimersByTime(200); }); fireEvent.click(screen.getByTestId("banner-button")); expect(onContinueCheckout).toHaveBeenCalled(); }); it("uses default heading when mainHeading is not provided", () => { const checkShouldShowBanner = jest.fn().mockReturnValue(true); const fields = { ...defaultFields, mainHeading: undefined }; render( ); act(() => { jest.advanceTimersByTime(200); }); expect(screen.getByText(/Welcome back/i)).toBeInTheDocument(); }); it("uses default CTA label when cta.buttonLabel is not provided", () => { const checkShouldShowBanner = jest.fn().mockReturnValue(true); const fields = { ...defaultFields, cta: {} }; render( ); act(() => { jest.advanceTimersByTime(200); }); expect(screen.getByTestId("banner-button")).toHaveTextContent( "Continue checkout" ); }); it("renders rich text description in modal", () => { const checkShouldShowBanner = jest.fn().mockReturnValue(true); render( ); act(() => { jest.advanceTimersByTime(200); }); expect(screen.getByTestId("rich-text")).toBeInTheDocument(); }); it("does not render rich text when description is undefined", () => { const checkShouldShowBanner = jest.fn().mockReturnValue(true); const fields = { ...defaultFields, description: undefined }; render( ); act(() => { jest.advanceTimersByTime(200); }); expect(screen.queryByTestId("rich-text")).not.toBeInTheDocument(); }); it("passes type=cart-retention-banner to Modal", () => { render(); const modal = screen.getByTestId("modal"); expect(modal).toHaveAttribute("data-type", "cart-retention-banner"); }); }); describe("popup container mode", () => { it("renders inline content when isInPopupContainer is true", () => { render( ); // Should render the pop-up-body div directly const popupBody = document.querySelector(".pop-up-body"); expect(popupBody).toBeInTheDocument(); }); it("renders data-section-type attribute in popup mode", () => { render( ); const div = document.querySelector( '[data-section-type="cart-retention-banner"]' ); expect(div).toBeInTheDocument(); }); it("renders heading in popup mode", () => { render( ); expect(screen.getByText("Welcome back!")).toBeInTheDocument(); }); it("renders default heading in popup mode when mainHeading is undefined", () => { const fields = { ...defaultFields, mainHeading: undefined }; render(); expect(screen.getByText(/Welcome back/)).toBeInTheDocument(); }); it("renders CTA in popup mode", () => { render( ); expect(screen.getByTestId("banner-button")).toHaveTextContent( "Continue checkout" ); }); it("calls onContinueCheckout in popup mode", () => { const onContinueCheckout = jest.fn(); render( ); fireEvent.click(screen.getByTestId("banner-button")); expect(onContinueCheckout).toHaveBeenCalled(); }); it("does not render Modal in popup mode", () => { render( ); expect(screen.queryByTestId("modal")).not.toBeInTheDocument(); }); it("renders rich text in popup mode", () => { render( ); expect(screen.getByTestId("rich-text")).toBeInTheDocument(); }); }); describe("timer cleanup", () => { it("cleans up timeout on unmount", () => { const checkShouldShowBanner = jest.fn().mockReturnValue(false); const { unmount } = render( ); unmount(); // No error should occur after unmount act(() => { jest.advanceTimersByTime(200); }); }); }); describe("optional callback branches", () => { it("handles close without onClose callback", () => { const checkShouldShowBanner = jest.fn().mockReturnValue(true); render( ); act(() => { jest.advanceTimersByTime(200); }); // Should not throw when clicking close without onClose fireEvent.click(screen.getByTestId("modal-close")); expect(screen.getByTestId("modal")).toHaveAttribute("data-open", "false"); }); it("handles continue checkout without onContinueCheckout callback", () => { const checkShouldShowBanner = jest.fn().mockReturnValue(true); render( ); act(() => { jest.advanceTimersByTime(200); }); // Should not throw when clicking CTA without onContinueCheckout fireEvent.click(screen.getByTestId("banner-button")); expect(screen.getByTestId("banner-button")).toBeInTheDocument(); }); it("handles continue checkout in popup mode without callback", () => { render( ); // Should not throw fireEvent.click(screen.getByTestId("banner-button")); expect(screen.getByTestId("banner-button")).toBeInTheDocument(); }); it("opens modal without onBannerShown callback", () => { const checkShouldShowBanner = jest.fn().mockReturnValue(true); render( ); act(() => { jest.advanceTimersByTime(200); }); expect(screen.getByTestId("modal")).toHaveAttribute("data-open", "true"); }); it("does not open if checkShouldShowBanner is not provided", () => { render(); act(() => { jest.advanceTimersByTime(200); }); expect(screen.getByTestId("modal")).toHaveAttribute("data-open", "false"); }); it("renders popup without description", () => { const fields = { ...defaultFields, description: undefined }; render(); expect(screen.queryByTestId("rich-text")).not.toBeInTheDocument(); }); }); });