import React from "react";
import { BlogGridBase } from "./index";
import { BlogCategoryOption, BlogGridBaseProps } from "./types";
import { fireEvent, render, screen } from "@testing-library/react";
jest.mock("@shared/components/pagination", () => ({
Pagination: ({ currentPage, totalPages, onPageChange, 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, imageComponent, 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" },
},
{
slug: "article-3",
title: "Article Three",
shortDescription: "Desc 3",
blogCreationDate: "2024-03-01",
category: "tech",
},
];
const defaultProps: BlogGridBaseProps = {
paginatedArticles: mockArticles,
totalArticles: 10,
currentPage: 1,
totalPages: 3,
selectedCategory: categoryOptions[0],
categoryOptions,
onCategoryChange: jest.fn(),
onPageChange: jest.fn(),
};
describe("BlogGridBase", () => {
beforeEach(() => jest.clearAllMocks());
describe("Rendering", () => {
it("renders section with correct 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 default title", () => {
render();
expect(screen.getByText("recent articles")).toBeInTheDocument();
});
it("renders custom title", () => {
render();
expect(screen.getByText("custom title")).toBeInTheDocument();
});
it("renders article count", () => {
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 empty state when no articles", () => {
render();
expect(screen.getByText("No articles found.")).toBeInTheDocument();
});
it("hides category controls when categoryOptions is empty", () => {
render();
expect(screen.queryByTestId("category-select")).not.toBeInTheDocument();
});
});
describe("Slug handling", () => {
it("keeps href unchanged 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"
);
});
});
describe("Category filter", () => {
it("calls onCategoryChange with selected category", () => {
render();
fireEvent.change(screen.getByTestId("category-select"), {
target: { value: "tech" },
});
expect(defaultProps.onCategoryChange).toHaveBeenCalledWith({
value: "tech",
label: "Technology",
});
});
it("does nothing when value is falsy", () => {
render();
fireEvent.change(screen.getByTestId("category-select"), {
target: { value: "nonexistent" },
});
expect(defaultProps.onCategoryChange).not.toHaveBeenCalled();
});
it("works without onCategoryChange callback", () => {
render();
expect(() => {
fireEvent.change(screen.getByTestId("category-select"), {
target: { value: "tech" },
});
}).not.toThrow();
});
});
describe("Pagination", () => {
it("renders pagination when totalPages > 1", () => {
render();
expect(screen.getByTestId("pagination")).toBeInTheDocument();
});
it("does not render pagination when totalPages is 1", () => {
render();
expect(screen.queryByTestId("pagination")).not.toBeInTheDocument();
});
it("calls onPageChange with page number", () => {
render();
fireEvent.click(screen.getByTestId("page-next"));
expect(defaultProps.onPageChange).toHaveBeenCalledWith(2);
});
it("works without onPageChange callback", () => {
render();
expect(() => {
fireEvent.click(screen.getByTestId("page-next"));
}).not.toThrow();
});
});
describe("Cover image handling", () => {
it("passes image data when cover has url", () => {
render();
expect(screen.getByTestId("blog-card-0")).toHaveAttribute(
"data-has-image",
"true"
);
});
it("passes undefined image when no cover", () => {
render();
expect(screen.getByTestId("blog-card-2")).toHaveAttribute(
"data-has-image",
"false"
);
});
it("uses default width/height when not provided", () => {
// article-2 has cover with url but no width/height
render();
expect(screen.getByTestId("blog-card-1")).toHaveAttribute(
"data-has-image",
"true"
);
});
it("uses fallback alt when cover title and article title are missing", () => {
const articles = [
{ slug: "no-title", cover: { url: "https://img.test/x.jpg" } },
];
render();
expect(screen.getByTestId("blog-card-0")).toHaveAttribute(
"data-has-image",
"true"
);
});
it("passes imageComponent to BlogCard", () => {
const MockImage = (props: any) =>
;
render();
expect(screen.getByTestId("blog-card-0")).toHaveAttribute(
"data-has-image-component",
"true"
);
});
});
});