import React from "react"; import { DynamicTabs } from "./index"; import { act, fireEvent, render, screen } from "@testing-library/react"; jest.mock("next/link", () => { const MockNextLink = ({ children, href, className, ...props }: any) => ( {children} ); MockNextLink.displayName = "MockNextLink"; return MockNextLink; }); jest.mock("framer-motion", () => ({ AnimatePresence: ({ children }: any) => <>{children}, motion: { div: ({ children, animate }: any) => (
{children}
), }, })); jest.mock("@shared/components/collapse", () => ({ Collapse: ({ open, children }: any) => (
{open ? children : null}
), })); jest.mock("@shared/components/link", () => ({ Link: ({ children, href, className }: any) => ( {children} ), })); jest.mock("@shared/components/material-icon", () => ({ MaterialIcon: ({ name }: any) => ( {name} ), })); jest.mock("@shared/components/text", () => ({ Text: ({ children, className }: any) => ( {children} ), })); jest.mock("@shared/contentful/blocks/button", () => ({ Button: ({ children, onClick, buttonClassName }: any) => ( ), })); jest.mock("@shared/utils", () => ({ cx: (...args: any[]) => args.filter(Boolean).join(" "), })); describe("DynamicTabs", () => { const defaultProps = { tabs: { items: [ { tabName: "Tab 1", anchorId: "tab-1", tabContent:
Content 1
, }, { tabName: "Tab 2", anchorId: "tab-2", tabContent:
Content 2
, }, { tabName: "Tab 3", anchorId: "tab-3", tabContent:
Content 3
, }, ], }, }; beforeEach(() => { jest.clearAllMocks(); window.location.hash = ""; }); describe("Rendering", () => { it("renders all tab buttons", () => { render(); expect(screen.getAllByText("Tab 1").length).toBeGreaterThan(0); expect(screen.getAllByText("Tab 2").length).toBeGreaterThan(0); expect(screen.getAllByText("Tab 3").length).toBeGreaterThan(0); }); it("renders tab content", () => { render(); expect(screen.getAllByText("Content 1").length).toBeGreaterThan(0); }); it("renders title when provided", () => { render(); expect(screen.getByText("My Tabs")).toBeInTheDocument(); }); it("renders subtitle when provided", () => { render(); expect(screen.getByText("A subtitle")).toBeInTheDocument(); }); it("does not render title section when no title or subtitle", () => { const { container } = render(); expect(container.querySelector(".mb-8.mt-5")).not.toBeInTheDocument(); }); }); describe("Tab switching", () => { it("switches tab content on click", () => { render(); const tabButtons = screen.getAllByTestId("tab-button"); // Find a Tab 2 button and click it const tab2Buttons = tabButtons.filter(btn => btn.textContent === "Tab 2"); fireEvent.click(tab2Buttons[0]); // After clicking Tab 2, the motion-div for tab 2 should show opacity 1 const motionDivs = screen.getAllByTestId("motion-div"); expect(motionDivs.length).toBeGreaterThan(0); }); it("first tab is active by default", () => { render(); const tabButtons = screen.getAllByTestId("tab-button"); const firstTabBtn = tabButtons.find(btn => btn.textContent === "Tab 1"); expect(firstTabBtn?.className).toContain("border-b-2"); }); }); describe("Navigation type", () => { it("renders tab view on desktop when navigationType is true", () => { const { container } = render( ); // Desktop view should have the overflow-x-auto div in "hidden md:block" const desktopBlock = container.querySelector(".hidden.md\\:block"); expect(desktopBlock).toBeInTheDocument(); }); it("renders sidebar view on desktop when navigationType is false", () => { const { container } = render( ); const desktopBlock = container.querySelector(".hidden.md\\:block"); expect(desktopBlock).toBeInTheDocument(); }); }); describe("Mobile view", () => { it("renders tab view on mobile when mobileSidebarTabType is true", () => { const { container } = render( ); const mobileBlock = container.querySelector(".md\\:hidden"); expect(mobileBlock).toBeInTheDocument(); }); it("renders accordion view on mobile when mobileSidebarTabType is false", () => { render(); // Accordion uses Collapse component const collapses = screen.getAllByTestId("collapse"); expect(collapses.length).toBeGreaterThan(0); }); it("clicks accordion button to switch tabs", () => { window.location.hash = "#tab-1"; const { container } = render( ); // Accordion buttons are inside the mobile container (.md\:hidden) const mobileContainer = container.querySelector(".md\\:hidden"); const accordionButtons = mobileContainer!.querySelectorAll( "[data-testid='tab-button']" ); // Click the second accordion button (Tab 2) fireEvent.click(accordionButtons[1]); expect(window.location.hash).toBe("#tab-2"); }); }); describe("Sidebar view", () => { it("clicks sidebar button to switch tabs", () => { window.location.hash = "#tab-1"; const { container } = render( ); // Sidebar buttons are inside the desktop container (.hidden.md\:block) const desktopContainer = container.querySelector(".hidden.md\\:block"); const sidebarButtons = desktopContainer!.querySelectorAll( "[data-testid='tab-button']" ); // Click the third sidebar button (Tab 3) fireEvent.click(sidebarButtons[2]); expect(window.location.hash).toBe("#tab-3"); }); }); describe("Hash navigation", () => { it("sets tab based on URL hash", () => { window.location.hash = "#tab-2"; render(); // After useEffect runs, tab 2 should be active const tabButtons = screen.getAllByTestId("tab-button"); const tab2Btns = tabButtons.filter(btn => btn.textContent === "Tab 2"); // At least one Tab 2 button should have the active style const hasActiveTab2 = tab2Btns.some(btn => btn.className.includes("border-b-2") ); expect(hasActiveTab2).toBe(true); }); it("updates hash on tab click when fragment exists", () => { window.location.hash = "#tab-1"; render(); const tabButtons = screen.getAllByTestId("tab-button"); const tab2Btns = tabButtons.filter(btn => btn.textContent === "Tab 2"); fireEvent.click(tab2Btns[0]); expect(window.location.hash).toBe("#tab-2"); }); it("listens to hashchange events", () => { render(); act(() => { window.location.hash = "#tab-3"; window.dispatchEvent(new HashChangeEvent("hashchange")); }); const tabButtons = screen.getAllByTestId("tab-button"); const tab3Btns = tabButtons.filter(btn => btn.textContent === "Tab 3"); const hasActiveTab3 = tab3Btns.some(btn => btn.className.includes("border-b-2") ); expect(hasActiveTab3).toBe(true); }); }); describe("Empty state", () => { it("renders without tabs", () => { const { container } = render(); expect(container.firstChild).toBeInTheDocument(); }); it("renders without tabsContent", () => { render(); expect(screen.getAllByTestId("tab-button").length).toBeGreaterThan(0); }); }); describe("Ordered list mode", () => { const orderedProps = { dynamic: true, isOrderedList: true, tabs: { items: [ { tabName: "Group A", anchorId: "tab-0", tabContent: [ Item A1, Item A2, ], }, { tabName: "Group B", anchorId: "tab-1", tabContent: [Item B1], }, ], }, }; it("renders ordered tab names from the tabs prop", () => { render(); expect(screen.getAllByText("Group A").length).toBeGreaterThan(0); expect(screen.getAllByText("Group B").length).toBeGreaterThan(0); }); it("renders each tab's tabContent children", () => { render(); expect(screen.getAllByText("Item A1").length).toBeGreaterThan(0); expect(screen.getAllByText("Item A2").length).toBeGreaterThan(0); }); it("updates hash when an ordered tab is clicked", () => { window.location.hash = "#tab-0"; render(); const tabButtons = screen.getAllByTestId("tab-button"); const groupB = tabButtons.filter(btn => btn.textContent === "Group B"); fireEvent.click(groupB[0]); expect(window.location.hash).toBe("#tab-1"); }); it("renders sidebar layout when navigationType is false", () => { const { container } = render( ); expect(container.querySelector(".hidden.lg\\:block")).toBeInTheDocument(); expect(screen.getAllByText("Group A").length).toBeGreaterThan(0); }); it("renders accordion layout when mobileSidebarTabType is false", () => { const { container } = render( ); expect(container.querySelector(".lg\\:hidden")).toBeInTheDocument(); expect(screen.getAllByText("Item B1").length).toBeGreaterThan(0); }); }); });