import React from "react";
import { FindKinetic } from "./index";
import { FindKineticProps } from "./types";
import { render, screen } from "@testing-library/react";
// Mock dependencies
jest.mock("@shared/components/link", () => ({
Link: ({
href,
children,
className,
}: {
href: string;
children: React.ReactNode;
className?: string;
}) => (
{children}
),
}));
jest.mock("@shared/components/text", () => ({
Text: ({ as: Tag = "span", children, className }: any) => (
{children}
),
}));
jest.mock("@shared/next", () => ({
NextImage: ({
src,
alt,
width,
height,
}: {
src: string;
alt: string;
width: number;
height: number;
}) =>
,
}));
const mockList = [
{ name: "New York", code: "NY" },
{ name: "California", code: "CA" },
{ name: "Texas", code: "TX" },
{ name: "Florida", code: "FL" },
{ name: "Illinois", code: "IL" },
{ name: "Ohio", code: "OH" },
{ name: "Georgia", code: "GA" },
];
const defaultProps: FindKineticProps = {
title: "Find Kinetic Near You",
subTitle: "Available in many locations",
description: "Check availability in your area.",
list: mockList,
image: "https://example.com/image.png",
};
describe("FindKinetic", () => {
describe("Rendering", () => {
it("renders with default props", () => {
render();
expect(screen.getByText("Find Kinetic Near You")).toBeInTheDocument();
});
it("renders nothing when no props are provided", () => {
const { container } = render();
expect(container.firstChild).toBeInTheDocument();
});
it("renders title when provided", () => {
render();
expect(screen.getByText("Test Title")).toBeInTheDocument();
});
it("does not render title when not provided", () => {
render();
expect(screen.queryByText("Test Title")).not.toBeInTheDocument();
});
it("renders subtitle when provided", () => {
render();
expect(screen.getByText("Subtitle Text")).toBeInTheDocument();
});
it("does not render subtitle when not provided", () => {
render();
expect(screen.queryByText("Subtitle Text")).not.toBeInTheDocument();
});
it("renders description when provided", () => {
render();
expect(screen.getByText("Desc text here")).toBeInTheDocument();
});
it("does not render description when not provided", () => {
render();
expect(screen.queryByText("Desc text here")).not.toBeInTheDocument();
});
});
describe("Heading levels", () => {
it("renders title as h2 when enableHeading is false", () => {
render();
const el = screen.getByText("Heading Test");
expect(el.tagName).toBe("H2");
});
it("renders title as h1 when enableHeading is true", () => {
render();
const el = screen.getByText("Heading Test");
expect(el.tagName).toBe("H1");
});
it("renders subtitle as h3 when enableHeading is false", () => {
render();
const el = screen.getByText("Sub Heading");
expect(el.tagName).toBe("H3");
});
it("renders subtitle as h2 when enableHeading is true", () => {
render();
const el = screen.getByText("Sub Heading");
expect(el.tagName).toBe("H2");
});
});
describe("Background themes", () => {
it("applies white background by default", () => {
const { container } = render();
expect(container.firstChild).toHaveClass("bg-white");
});
it.each([
["blue", "bg-[#07B2E2]"],
["green", "bg-[#26B170]"],
["yellow", "bg-[#F5FF1E]"],
["purple", "bg-[#931D69]"],
["white", "bg-white"],
["navy", "bg-[#00002D]"],
] as const)("applies %s background class", (bg, expectedClass) => {
const { container } = render(
);
expect(container.firstChild).toHaveClass(expectedClass);
});
});
describe("Color theme", () => {
it("applies dark text color by default", () => {
const { container } = render();
expect(container.firstChild).toHaveClass("text-text");
});
it("applies light text color when color is light", () => {
const { container } = render(
);
expect(container.firstChild).toHaveClass("text-white");
});
it("applies dark text color when color is dark", () => {
const { container } = render(
);
expect(container.firstChild).toHaveClass("text-text");
});
});
describe("Max width", () => {
it("applies max-w-120 class when maxWidth is true (default)", () => {
const { container } = render();
const innerDiv = container.firstChild?.firstChild as HTMLElement;
expect(innerDiv).toHaveClass("max-w-120");
});
it("does not apply max-w-120 class when maxWidth is false", () => {
const { container } = render(
);
const innerDiv = container.firstChild?.firstChild as HTMLElement;
expect(innerDiv).not.toHaveClass("max-w-120");
});
});
describe("Location directory list", () => {
it("renders location links from list", () => {
render();
// Each item appears in both mobile (2-col) and desktop (3-col) lists
const nyLinks = screen.getAllByText("New York");
expect(nyLinks.length).toBeGreaterThanOrEqual(2);
});
it("generates correct href with lowercase code", () => {
render();
const links = screen.getAllByText("New York");
links.forEach(link => {
expect(link.closest("a")).toHaveAttribute("href", "/local/ny");
});
});
it("does not append a trailing slash by default", () => {
render();
const links = screen.getAllByText("Alabama");
links.forEach(link => {
expect(link.closest("a")).toHaveAttribute("href", "/local/al");
});
});
it("appends a trailing slash when addTrailingSlash is true", () => {
render(
);
const links = screen.getAllByText("Alabama");
links.forEach(link => {
expect(link.closest("a")).toHaveAttribute("href", "/local/al/");
});
});
it("does not produce a protocol-relative href when localPathPrefix is '/'", () => {
render(
);
const links = screen.getAllByText("Alabama");
links.forEach(link => {
expect(link.closest("a")).toHaveAttribute("href", "/al");
expect(link.closest("a")?.getAttribute("href")).not.toMatch(/^\/\//);
});
});
it("sorts list items alphabetically by name", () => {
const unsortedList = [
{ name: "Texas", code: "TX" },
{ name: "Alabama", code: "AL" },
{ name: "Michigan", code: "MI" },
];
render();
const listItems = screen.getAllByRole("listitem");
const texts = listItems.map(li => li.textContent);
// In both grids, Alabama should come before Michigan, Michigan before Texas
expect(texts[0]).toBe("Alabama");
});
it("does not render lists when list is empty", () => {
render();
expect(screen.queryAllByRole("list")).toHaveLength(0);
});
it("does not render lists when list is not provided", () => {
render();
expect(screen.queryAllByRole("list")).toHaveLength(0);
});
it("renders two column layout for mobile, three for tablet and four for desktop", () => {
const { container } = render();
const lists = container.querySelectorAll("ul");
expect(lists).toHaveLength(3);
expect(lists[0]).toHaveClass("grid-cols-2");
expect(lists[1]).toHaveClass("md:grid-cols-3");
expect(lists[2]).toHaveClass("xl:grid-cols-4");
});
it("rearranges items in column-major order for 2-col layout", () => {
const list = [
{ name: "A", code: "A" },
{ name: "B", code: "B" },
{ name: "C", code: "C" },
{ name: "D", code: "D" },
];
const { container } = render();
// Mobile list (2-col): sorted [A, B, C, D], 2 rows, column-major: A, C, B, D
const mobileList = container.querySelector(
"ul.grid-cols-2"
) as HTMLElement;
const items = mobileList?.querySelectorAll("li");
expect(items?.[0]?.textContent).toBe("A");
expect(items?.[1]?.textContent).toBe("C");
expect(items?.[2]?.textContent).toBe("B");
expect(items?.[3]?.textContent).toBe("D");
});
});
describe("Image", () => {
it("renders image when provided", () => {
render();
const img = screen.getByAltText("image");
expect(img).toHaveAttribute("src", "https://example.com/img.png");
expect(img).toHaveAttribute("width", "472");
});
it("does not render image when not provided", () => {
render();
expect(screen.queryByAltText("image")).not.toBeInTheDocument();
});
});
});