import React from "react";
import { BlogGrid } from "./index";
import { BlogCategoryOption, BlogGridProps } from "./types";
import { fireEvent, render, screen } from "@testing-library/react";
const mockPush = jest.fn();
let mockSearchParams = new URLSearchParams("");
jest.mock("next/navigation", () => ({
useRouter: () => ({ push: mockPush }),
usePathname: () => "/blog",
useSearchParams: () => mockSearchParams,
}));
jest.mock("@shared/components/pagination", () => ({
Pagination: ({
currentPage,
totalPages,
onPageChange,
getPageHref,
ariaLabel,
}: any) => (
),
}));
jest.mock("@shared/components/select", () => ({
Select: ({ options, value, onChange, className }: any) => (
),
}));
jest.mock("@shared/components/text", () => ({
Text: ({ as: Tag = "span", children, className }: any) => (
{children}
),
}));
jest.mock("@shared/contentful/blocks/cards/blog-card", () => ({
BlogCard: ({ href, title, category, image, index }: any) => (
{title} - {category}
),
}));
const categoryOptions: BlogCategoryOption[] = [
{ value: "all", label: "All Categories" },
{ value: "tech", label: "Technology" },
{ value: "news", label: "News" },
];
const mockArticles = [
{
slug: "/article-1",
title: "Article One",
shortDescription: "Desc 1",
blogCreationDate: "2024-01-01",
category: "tech",
cover: {
url: "https://img.test/1.jpg",
width: 600,
height: 338,
title: "Cover 1",
},
},
{
slug: "article-2",
title: "Article Two",
shortDescription: "Desc 2",
blogCreationDate: "2024-02-01",
category: "news",
cover: {
url: "https://img.test/2.jpg",
width: 800,
height: 450,
title: "",
},
},
{
slug: "article-3",
title: "Article Three",
shortDescription: "Desc 3",
blogCreationDate: "2024-03-01",
category: "tech",
},
];
const defaultProps: BlogGridProps = {
paginatedArticles: mockArticles,
totalArticles: 10,
currentPage: 1,
totalPages: 3,
selectedCategory: categoryOptions[0],
categoryOptions,
};
describe("BlogGrid", () => {
beforeEach(() => {
jest.clearAllMocks();
mockSearchParams = new URLSearchParams("");
window.scrollTo = jest.fn();
});
describe("Rendering", () => {
it("renders section with correct aria-label and data attributes", () => {
const { container } = render();
const section = container.querySelector("section");
expect(section).toHaveAttribute("aria-label", "Blog articles");
expect(section).toHaveAttribute("data-section-type", "blogs-grid");
expect(section).toHaveAttribute("data-section-index", "0");
});
it("renders article count text", () => {
render();
expect(screen.getByText("3 of 10 articles")).toBeInTheDocument();
});
it("renders all blog cards", () => {
render();
expect(screen.getByTestId("blog-card-0")).toBeInTheDocument();
expect(screen.getByTestId("blog-card-1")).toBeInTheDocument();
expect(screen.getByTestId("blog-card-2")).toBeInTheDocument();
});
it("renders blog card with correct href when slug starts with /", () => {
render();
expect(screen.getByTestId("blog-card-0")).toHaveAttribute(
"href",
"/article-1"
);
});
it("prepends / to slug when it does not start with /", () => {
render();
expect(screen.getByTestId("blog-card-1")).toHaveAttribute(
"href",
"/article-2"
);
});
it("renders empty state when no articles", () => {
render();
expect(screen.getByText("No articles found.")).toBeInTheDocument();
});
it("does not render pagination when totalPages is 1", () => {
render();
expect(screen.queryByTestId("pagination")).not.toBeInTheDocument();
});
it("renders pagination when totalPages > 1", () => {
render();
expect(screen.getByTestId("pagination")).toBeInTheDocument();
});
});
describe("Category filter", () => {
it("navigates with category param on selection", () => {
render();
fireEvent.change(screen.getByTestId("category-select"), {
target: { value: "tech" },
});
expect(mockPush).toHaveBeenCalledWith("/blog?category=tech", {
scroll: false,
});
});
it("removes category param when 'all' is selected", () => {
render();
fireEvent.change(screen.getByTestId("category-select"), {
target: { value: "all" },
});
expect(mockPush).toHaveBeenCalledWith("/blog?", { scroll: false });
});
it("deletes page param when category changes", () => {
mockSearchParams = new URLSearchParams("page=3&category=news");
render();
fireEvent.change(screen.getByTestId("category-select"), {
target: { value: "tech" },
});
expect(mockPush).toHaveBeenCalledWith("/blog?category=tech", {
scroll: false,
});
});
it("does nothing when onChange receives falsy value", () => {
render();
// Simulate onChange with a value not found in options (returns undefined)
fireEvent.change(screen.getByTestId("category-select"), {
target: { value: "nonexistent" },
});
expect(mockPush).not.toHaveBeenCalled();
});
});
describe("Pagination", () => {
it("builds page href with page param", () => {
render();
expect(screen.getByTestId("page-next")).toHaveAttribute(
"href",
"/blog?page=2"
);
});
it("removes page param when building href for page 1", () => {
mockSearchParams = new URLSearchParams("page=2");
render();
expect(screen.getByTestId("page-first")).toHaveAttribute("href", "/blog");
});
it("keeps other params when building page href", () => {
mockSearchParams = new URLSearchParams("category=tech");
render();
expect(screen.getByTestId("page-first")).toHaveAttribute(
"href",
"/blog?category=tech"
);
});
it("scrolls to top when currentPage changes", () => {
const { rerender } = render();
expect(window.scrollTo).not.toHaveBeenCalled();
rerender();
expect(window.scrollTo).toHaveBeenCalledWith({
top: 0,
behavior: "smooth",
});
});
});
describe("Cover image handling", () => {
it("renders card with full cover image data", () => {
render();
const card = screen.getByTestId("blog-card-0");
expect(card).toHaveAttribute("data-image-src", "https://img.test/1.jpg");
expect(card).toHaveAttribute("data-image-alt", "Cover 1");
expect(card).toHaveAttribute("data-image-width", "600");
expect(card).toHaveAttribute("data-image-height", "338");
});
it("renders card without cover image when cover is undefined", () => {
render();
const card = screen.getByTestId("blog-card-2");
expect(card).toHaveAttribute("data-image-src", "");
});
it("uses article title as alt fallback when cover title is empty", () => {
const articles = [
{
slug: "test-article",
title: "Test Title",
cover: {
url: "https://img.test/x.jpg",
width: 600,
height: 338,
title: "",
},
},
];
render();
expect(screen.getByTestId("blog-card-0")).toHaveAttribute(
"data-image-alt",
"Test Title"
);
});
it("uses default alt text when both cover title and article title are missing", () => {
const articles = [
{
slug: "no-title",
cover: { url: "https://img.test/y.jpg" },
},
];
render();
expect(screen.getByTestId("blog-card-0")).toHaveAttribute(
"data-image-alt",
"Blog article image"
);
});
it("uses default width and height when not provided in cover", () => {
const articles = [
{
slug: "no-dimensions",
title: "No Dimensions",
cover: { url: "https://img.test/z.jpg", title: "Cover" },
},
];
render();
const card = screen.getByTestId("blog-card-0");
expect(card).toHaveAttribute("data-image-width", "600");
expect(card).toHaveAttribute("data-image-height", "338");
});
it("uses provided width and height from cover", () => {
const articles = [
{
slug: "with-dimensions",
title: "With Dimensions",
cover: {
url: "https://img.test/w.jpg",
title: "Cover",
width: 800,
height: 450,
},
},
];
render();
const card = screen.getByTestId("blog-card-0");
expect(card).toHaveAttribute("data-image-width", "800");
expect(card).toHaveAttribute("data-image-height", "450");
});
it("handles article with cover set to null", () => {
const articles = [
{
slug: "null-cover",
title: "Null Cover",
cover: null as any,
},
];
render();
expect(screen.getByTestId("blog-card-0")).toHaveAttribute(
"data-image-src",
""
);
});
it("handles article with cover url empty string", () => {
const articles = [
{
slug: "empty-url",
title: "Empty URL",
cover: { url: "", title: "Cover" },
},
];
render();
expect(screen.getByTestId("blog-card-0")).toHaveAttribute(
"data-image-src",
""
);
});
});
});