import "@testing-library/jest-dom"; import React from "react"; import { ProductCardCarousel, TabSwitch, TestimonialCarousel } from "./helper"; import { CarouselWithProductCards, CarouselWithTestimonialCards, } from "./types"; import { fireEvent, render, screen, waitFor } from "@testing-library/react"; // Mock sub-components jest.mock("@shared/components/button", () => ({ Button: ({ children, onClick, ...props }: any) => ( ), })); jest.mock("@shared/components/material-icon", () => ({ MaterialIcon: ({ name }: any) => ( {name} ), })); jest.mock("@shared/contentful/blocks/cards/product-card", () => ({ ProductCard: ({ planName, speed, onCtaClick, onToggleExpand, isExpanded, }: any) => (
{planName}
), })); jest.mock("@shared/contentful/blocks/cards/testimonial-card", () => ({ TestimonialCard: ({ title, isActive }: any) => (
{title}
), })); jest.mock("@shared/hooks/use-carousel-swipe", () => ({ useCarouselSwipe: () => ({ currentIndex: 0, swipeOffset: 0, isSwiping: false, isMobile: false, containerWidth: 1024, containerRef: { current: null }, handleTouchStart: jest.fn(), handleTouchMove: jest.fn(), handleTouchEnd: jest.fn(), prevSlide: jest.fn(), nextSlide: jest.fn(), goToSlide: jest.fn(), constants: { CARD_OFFSET_PERCENTAGE: 105 }, }), })); jest.mock("@shared/utils", () => ({ cx: (...args: any[]) => args.filter(Boolean).join(" "), })); describe("ProductCardCarousel", () => { beforeEach(() => { Object.defineProperty(HTMLElement.prototype, "offsetWidth", { configurable: true, get: () => 900, }); }); const createProductFields = ( items: any[] = [] ): CarouselWithProductCards => ({ __typename: "ComponentCarousel", sys: { id: "test-carousel" }, title: "Product Carousel", items: { items }, }); const sampleProduct = { __typename: "ComponentProductCard" as const, sys: { id: "prod-1" }, speed: "500 Mbps|Fast internet", price: "49", priceSuffix: "99/mo", techType: "Fiber", highlighted: false, productCardDescription: "Great plan", benefitsTitle: "Benefits", benefitsExpanded: false, innerBadge: "New", benefits: { items: [{ text: "Benefit 1" }] }, giftRewards: { list: { items: [{ text: "Gift" }] } }, cta: { buttonLabel: "Select" }, innerBadgeIcon: { url: "https://example.com/icon.png" }, }; it("returns null when no items and no title", () => { const fields = createProductFields([]); fields.title = undefined; const { container } = render(); expect(container.firstChild).toBeNull(); }); it("returns null when items is undefined", () => { const fields = { __typename: "ComponentCarousel" as const, sys: { id: "test" }, items: { items: undefined as any }, title: undefined, } as any; const { container } = render(); expect(container.firstChild).toBeNull(); }); it("renders product cards when items exist", () => { const fields = createProductFields([sampleProduct]); render(); expect(screen.getAllByTestId("product-card")).toHaveLength(1); }); it("renders non-carousel layout for 2 or fewer items", () => { const fields = createProductFields([ sampleProduct, { ...sampleProduct, sys: { id: "prod-2" } }, ]); const { container } = render(); // Non-carousel: wrapped in flex layout expect(container.querySelector(".flex.w-full")).toBeInTheDocument(); }); it("renders carousel layout for more than 2 items", () => { const items = Array.from({ length: 4 }, (_, i) => ({ ...sampleProduct, sys: { id: `prod-${i}` }, })); const fields = createProductFields(items); const { container } = render(); // Should have mobile stack view (flex-col for md:hidden) expect(container.querySelector(".flex-col")).toBeInTheDocument(); }); it("parses speed field correctly (planName | planSubtext)", () => { const fields = createProductFields([sampleProduct]); render(); const card = screen.getByTestId("product-card"); expect(card).toHaveAttribute("data-plan", "500 Mbps"); expect(card).toHaveAttribute("data-speed", "500 Mbps|Fast internet"); }); it("handles missing speed gracefully", () => { const item = { ...sampleProduct, speed: undefined }; const fields = createProductFields([item]); render(); const card = screen.getByTestId("product-card"); expect(card).toHaveAttribute("data-plan", ""); }); it("handles item with all optional fields missing", () => { const minimalItem = { __typename: "ComponentProductCard" as const, sys: { id: "minimal" }, }; const fields = createProductFields([minimalItem]); render(); const card = screen.getByTestId("product-card"); expect(card).toBeInTheDocument(); }); it("handles item with empty benefits and giftRewards", () => { const item = { ...sampleProduct, benefits: undefined, giftRewards: undefined, innerBadge: undefined, innerBadgeIcon: undefined, priceSuffix: undefined, price: undefined, cta: undefined, }; const fields = createProductFields([item]); render(); const card = screen.getByTestId("product-card"); expect(card).toBeInTheDocument(); }); it("handles speed without pipe separator", () => { const item = { ...sampleProduct, speed: "500 Mbps" }; const fields = createProductFields([item]); render(); const card = screen.getByTestId("product-card"); expect(card).toHaveAttribute("data-plan", "500 Mbps"); }); it("renders Next arrow and hides Previous on first slide in carousel mode", async () => { const items = Array.from({ length: 4 }, (_, i) => ({ ...sampleProduct, sys: { id: `prod-${i}` }, })); const fields = createProductFields(items); render(); fireEvent(window, new Event("resize")); await waitFor(() => expect(screen.getByLabelText("Next")).toBeInTheDocument() ); expect(screen.queryByLabelText("Previous")).not.toBeInTheDocument(); }); it("shows Previous after moving forward, then hides it after returning to first slide", async () => { const items = Array.from({ length: 4 }, (_, i) => ({ ...sampleProduct, sys: { id: `prod-${i}` }, })); const fields = createProductFields(items); render(); fireEvent(window, new Event("resize")); const nextBtn = await screen.findByLabelText("Next"); fireEvent.click(nextBtn); const prevBtn = await screen.findByLabelText("Previous"); expect(prevBtn).toBeInTheDocument(); fireEvent.click(prevBtn); expect(screen.queryByLabelText("Previous")).not.toBeInTheDocument(); }); it("calls nextSlide on Next button click", async () => { const items = Array.from({ length: 4 }, (_, i) => ({ ...sampleProduct, sys: { id: `prod-${i}` }, })); const fields = createProductFields(items); render(); fireEvent(window, new Event("resize")); const nextBtn = await screen.findByLabelText("Next"); fireEvent.click(nextBtn); expect(nextBtn).toBeInTheDocument(); }); it("handles toggle expand click for desktop (non-carousel) cards", () => { const fields = createProductFields([ sampleProduct, { ...sampleProduct, sys: { id: "prod-2" } }, ]); render(); const toggleButtons = screen.getAllByTestId("product-toggle"); // Click desktop toggle fireEvent.click(toggleButtons[0]); // No error - toggle state changes internally expect(toggleButtons[0]).toBeInTheDocument(); }); it("handles CTA click on product card", () => { const fields = createProductFields([sampleProduct]); render(); const ctaButton = screen.getByTestId("product-cta"); fireEvent.click(ctaButton); // handleCtaClick just returns; no error expect(ctaButton).toBeInTheDocument(); }); it("handles toggle expand for mobile cards in carousel mode", () => { const items = Array.from({ length: 4 }, (_, i) => ({ ...sampleProduct, sys: { id: `prod-${i}` }, })); const fields = createProductFields(items); render(); // Mobile view cards are in the flex-col hidden section const toggleButtons = screen.getAllByTestId("product-toggle"); // The first set of cards are in mobile view (first 4 items) fireEvent.click(toggleButtons[0]); expect(toggleButtons[0]).toBeInTheDocument(); }); it("renders with benefitsExpanded true in fields", () => { const item = { ...sampleProduct, benefitsExpanded: true }; const fields = createProductFields([item, { ...item, sys: { id: "p2" } }]); render(); // Cards should render with expanded state const cards = screen.getAllByTestId("product-card"); expect(cards[0]).toHaveAttribute("data-expanded", "true"); }); it("renders when title exists but items are empty", () => { const fields = createProductFields([]); fields.title = "Has Title"; const { container } = render(); // Should NOT return null because title exists expect(container.firstChild).not.toBeNull(); }); it("handles highlighted item (truthy branch)", () => { const item = { ...sampleProduct, highlighted: true, topBadgeText: "Best Deal", }; const fields = createProductFields([item]); render(); const card = screen.getByTestId("product-card"); expect(card).toBeInTheDocument(); }); it("handles item with giftRewards.list but no items", () => { const item = { ...sampleProduct, giftRewards: { list: { items: undefined as any } }, }; const fields = createProductFields([item]); render(); const card = screen.getByTestId("product-card"); expect(card).toBeInTheDocument(); }); it("handles item with giftRewards but no list", () => { const item = { ...sampleProduct, giftRewards: { list: undefined as any }, }; const fields = createProductFields([item]); render(); expect(screen.getByTestId("product-card")).toBeInTheDocument(); }); it("handles item with priceSuffix without slash", () => { const item = { ...sampleProduct, priceSuffix: "99" }; const fields = createProductFields([item]); render(); expect(screen.getByTestId("product-card")).toBeInTheDocument(); }); it("handles item with innerBadgeIcon but no url", () => { const item = { ...sampleProduct, innerBadgeIcon: {} as any, }; const fields = createProductFields([item]); render(); expect(screen.getByTestId("product-card")).toBeInTheDocument(); }); it("handles fields with items but items.items is null", () => { const fields = { __typename: "ComponentCarousel" as const, sys: { id: "test" }, items: { items: null as any }, title: "Title present", } as any; const { container } = render(); // title exists so it does not return null but items array is empty expect(container.firstChild).not.toBeNull(); }); it("handles fields with no items property at all", () => { const fields = { __typename: "ComponentCarousel" as const, sys: { id: "test" }, items: undefined as any, title: undefined, } as any; const { container } = render(); expect(container.firstChild).toBeNull(); }); it("renders tabbed view when tabs and activeTab are provided", () => { const items = [ { ...sampleProduct, sys: { id: "prod-1" }, productCategory: "Internet" }, { ...sampleProduct, sys: { id: "prod-2" }, productCategory: "Phone" }, ]; const fields = createProductFields(items); const { container } = render( ); // Should render tab containers with display block/none const visibleTab = container.querySelector("[style*='display: block']"); expect(visibleTab).toBeInTheDocument(); }); }); describe("TestimonialCarousel", () => { const createTestimonialFields = ( items: any[] = [] ): CarouselWithTestimonialCards => ({ __typename: "ComponentCarousel", sys: { id: "test-carousel" }, title: "Testimonials", items: { items }, }); const sampleTestimonial = { __typename: "ComponentTestimonialCard" as const, sys: { id: "test-1" }, title: "Great Service", author: "John Doe", role: "CEO", rating: 5, quote: "Amazing experience!", }; it("returns null when no testimonials", () => { const fields = createTestimonialFields([]); const { container } = render(); expect(container.firstChild).toBeNull(); }); it("returns null when testimonials items is undefined", () => { const fields = { __typename: "ComponentCarousel" as const, sys: { id: "test" }, title: "Testimonials", items: { items: undefined as any }, } as any; const { container } = render(); expect(container.firstChild).toBeNull(); }); it("returns null when testimonials field items is null", () => { const fields = { __typename: "ComponentCarousel" as const, sys: { id: "test" }, title: "Testimonials", items: { items: null as any }, } as any; const { container } = render(); expect(container.firstChild).toBeNull(); }); it("renders testimonial cards when items exist", () => { const fields = createTestimonialFields([sampleTestimonial]); render(); expect(screen.getByTestId("testimonial-card")).toBeInTheDocument(); }); it("renders navigation dots for each testimonial", () => { const items = [ sampleTestimonial, { ...sampleTestimonial, sys: { id: "test-2" }, title: "Second" }, { ...sampleTestimonial, sys: { id: "test-3" }, title: "Third" }, ]; const fields = createTestimonialFields(items); render(); const dots = screen.getAllByRole("button", { name: /Go to slide/ }); expect(dots).toHaveLength(3); }); it("renders Previous and Next navigation buttons", () => { const items = [ sampleTestimonial, { ...sampleTestimonial, sys: { id: "test-2" } }, ]; const fields = createTestimonialFields(items); render(); expect(screen.getByLabelText("Previous")).toBeInTheDocument(); expect(screen.getByLabelText("Next")).toBeInTheDocument(); }); it("renders dot navigation with correct aria labels", () => { const items = [ sampleTestimonial, { ...sampleTestimonial, sys: { id: "test-2" } }, ]; const fields = createTestimonialFields(items); render(); expect(screen.getByLabelText("Go to slide 1")).toBeInTheDocument(); expect(screen.getByLabelText("Go to slide 2")).toBeInTheDocument(); }); it("calls goToSlide when dot is clicked", () => { const items = [ sampleTestimonial, { ...sampleTestimonial, sys: { id: "test-2" } }, { ...sampleTestimonial, sys: { id: "test-3" } }, ]; const fields = createTestimonialFields(items); render(); const dot2 = screen.getByLabelText("Go to slide 2"); fireEvent.click(dot2); expect(dot2).toBeInTheDocument(); }); }); describe("TabSwitch", () => { const defaultTabProps = { tabs: ["Tab A", "Tab B", "Tab C"], activeTab: "Tab A", onChange: jest.fn(), }; it("renders all tabs", () => { render(); expect(screen.getByText("Tab A")).toBeInTheDocument(); expect(screen.getByText("Tab B")).toBeInTheDocument(); expect(screen.getByText("Tab C")).toBeInTheDocument(); }); it("calls onChange when a tab is clicked", () => { const onChange = jest.fn(); render(); fireEvent.click(screen.getByText("Tab B")); expect(onChange).toHaveBeenCalledWith("Tab B"); }); it("applies active style to selected tab", () => { render(); const activeBtn = screen.getByText("Tab A"); expect(activeBtn.className).toContain("text-text-inverse"); }); it("applies inactive style to non-selected tabs", () => { render(); const inactiveBtn = screen.getByText("Tab B"); expect(inactiveBtn.className).toContain("text-text-disabled"); }); it("applies custom className", () => { const { container } = render( ); expect(container.innerHTML).toContain("custom-class"); }); it("renders sliding indicator", () => { const { container } = render(); const indicator = container.querySelector(".bg-bg-fill-brand"); expect(indicator).toBeInTheDocument(); }); });