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" ); }); }); });