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