277 lines
8.5 KiB
React
277 lines
8.5 KiB
React
import { describe, it, expect, vi } from "vitest";
|
|
import { render, screen } from "@testing-library/react";
|
|
import ContentContainer from "../../app/components/content/ContentContainer";
|
|
|
|
// Mock asset utils
|
|
vi.mock("../../lib/assetUtils", () => ({
|
|
contentBlogTagPath: vi.fn((slug) => `/content/blog/${slug}-tag.svg`),
|
|
contentCatalogSlugForFallback: vi.fn((slug) => {
|
|
const catalog = [
|
|
"resolving-active-conflicts",
|
|
"operational-security-mutual-aid",
|
|
"making-decisions-without-hierarchy",
|
|
];
|
|
if (!slug) return catalog[0];
|
|
const index =
|
|
Math.abs(
|
|
slug.split("").reduce((acc, c) => acc + c.charCodeAt(0), 0),
|
|
) % catalog.length;
|
|
return catalog[index];
|
|
}),
|
|
CONTENT_CATALOG_SLUG_ORDER: [
|
|
"resolving-active-conflicts",
|
|
"operational-security-mutual-aid",
|
|
"making-decisions-without-hierarchy",
|
|
],
|
|
}));
|
|
|
|
// Mock blog post data
|
|
const mockPost = {
|
|
slug: "test-article",
|
|
frontmatter: {
|
|
title: "Test Article Title",
|
|
description:
|
|
"This is a test article description that should be long enough to test truncation and wrapping behavior.",
|
|
author: "Test Author",
|
|
date: "2025-04-15",
|
|
},
|
|
};
|
|
|
|
// Pure presentational; no provider context needed.
|
|
describe("ContentContainer", () => {
|
|
it("renders with default props", () => {
|
|
render(<ContentContainer post={mockPost} />);
|
|
|
|
// Check that the container exists
|
|
const container = document.querySelector("div[class*='relative z-20']");
|
|
expect(container).toBeInTheDocument();
|
|
expect(container).toHaveClass(
|
|
"relative",
|
|
"z-20",
|
|
"h-full",
|
|
"flex",
|
|
"flex-col",
|
|
);
|
|
});
|
|
|
|
it("displays the icon correctly", () => {
|
|
render(<ContentContainer post={mockPost} />);
|
|
|
|
const icon = screen.getByAltText("Icon for Test Article Title");
|
|
expect(icon).toBeInTheDocument();
|
|
expect(icon).toHaveAttribute(
|
|
"src",
|
|
"/content/blog/resolving-active-conflicts-tag.svg",
|
|
);
|
|
expect(icon).toHaveClass("w-[60px]", "h-[30px]", "object-contain");
|
|
});
|
|
|
|
it("displays the article title", () => {
|
|
render(<ContentContainer post={mockPost} />);
|
|
|
|
const title = screen.getByText("Test Article Title");
|
|
expect(title).toBeInTheDocument();
|
|
expect(title).toHaveClass(
|
|
"font-bricolage",
|
|
"font-medium",
|
|
"text-[18px]",
|
|
"leading-[120%]",
|
|
"text-[var(--color-content-inverse-brand-royal)]",
|
|
);
|
|
});
|
|
|
|
it("displays the article description", () => {
|
|
render(<ContentContainer post={mockPost} />);
|
|
|
|
const description = screen.getByText(/This is a test article description/);
|
|
expect(description).toBeInTheDocument();
|
|
expect(description).toHaveClass(
|
|
"font-inter",
|
|
"font-normal",
|
|
"text-[12px]",
|
|
"leading-[16px]",
|
|
"text-[var(--color-content-inverse-brand-royal)]",
|
|
);
|
|
});
|
|
|
|
it("displays the author and date metadata", () => {
|
|
render(<ContentContainer post={mockPost} />);
|
|
|
|
expect(screen.getByText("Test Author")).toBeInTheDocument();
|
|
expect(screen.getByText("April 2025")).toBeInTheDocument();
|
|
});
|
|
|
|
it("applies correct width when specified", () => {
|
|
render(<ContentContainer post={mockPost} width="300px" size="xs" />);
|
|
|
|
const container = document.querySelector("div[class*='relative z-20']");
|
|
expect(container).toHaveStyle("width: 300px");
|
|
});
|
|
|
|
it("applies default width when not specified", () => {
|
|
render(<ContentContainer post={mockPost} size="xs" />);
|
|
|
|
const container = document.querySelector("div[class*='relative z-20']");
|
|
expect(container).toHaveStyle("width: 200px");
|
|
});
|
|
|
|
it("has proper spacing between icon and text", () => {
|
|
render(<ContentContainer post={mockPost} />);
|
|
|
|
const iconContainer = screen
|
|
.getByAltText("Icon for Test Article Title")
|
|
.closest("div");
|
|
const textContainer = screen.getByText("Test Article Title").closest("div");
|
|
|
|
// Check the content container (parent of icon)
|
|
expect(iconContainer.parentElement).toHaveClass(
|
|
"gap-[var(--measures-spacing-008)]",
|
|
);
|
|
// Check the text container (parent of title) - it has responsive gap classes
|
|
expect(textContainer.parentElement).toHaveClass("flex", "flex-col");
|
|
});
|
|
|
|
it("has proper metadata container styling", () => {
|
|
render(<ContentContainer post={mockPost} />);
|
|
|
|
const metadataContainer = screen.getByText("Test Author").closest("div");
|
|
expect(metadataContainer).toHaveClass(
|
|
"flex",
|
|
"min-w-0",
|
|
"items-end",
|
|
"gap-[var(--measures-spacing-008)]",
|
|
);
|
|
});
|
|
|
|
it("applies correct metadata text styling", () => {
|
|
render(<ContentContainer post={mockPost} />);
|
|
|
|
const author = screen.getByText("Test Author");
|
|
expect(author).toHaveClass(
|
|
"font-inter",
|
|
"font-normal",
|
|
"text-[10px]",
|
|
"leading-[14px]",
|
|
"text-[var(--color-content-inverse-brand-royal)]",
|
|
);
|
|
|
|
const date = screen.getByText("April 2025");
|
|
expect(date).toHaveClass(
|
|
"font-inter",
|
|
"font-normal",
|
|
"text-[10px]",
|
|
"leading-[14px]",
|
|
"text-[var(--color-content-inverse-brand-royal)]",
|
|
);
|
|
});
|
|
|
|
it("uses per-article tag assets for catalog slugs", () => {
|
|
const { rerender } = render(<ContentContainer post={mockPost} />);
|
|
|
|
let icon = screen.getByAltText("Icon for Test Article Title");
|
|
expect(icon).toHaveAttribute(
|
|
"src",
|
|
"/content/blog/resolving-active-conflicts-tag.svg",
|
|
);
|
|
|
|
const post2 = { ...mockPost, slug: "operational-security-mutual-aid" };
|
|
rerender(<ContentContainer post={post2} />);
|
|
|
|
icon = screen.getByAltText("Icon for Test Article Title");
|
|
expect(icon).toHaveAttribute(
|
|
"src",
|
|
"/content/blog/operational-security-mutual-aid-tag.svg",
|
|
);
|
|
|
|
const post3 = { ...mockPost, slug: "making-decisions-without-hierarchy" };
|
|
rerender(<ContentContainer post={post3} />);
|
|
|
|
icon = screen.getByAltText("Icon for Test Article Title");
|
|
expect(icon).toHaveAttribute(
|
|
"src",
|
|
"/content/blog/making-decisions-without-hierarchy-tag.svg",
|
|
);
|
|
});
|
|
|
|
it("handles missing post data gracefully", () => {
|
|
const incompletePost = {
|
|
slug: "incomplete",
|
|
frontmatter: {
|
|
title: "Incomplete Post",
|
|
// Missing other fields
|
|
},
|
|
};
|
|
|
|
render(<ContentContainer post={incompletePost} />);
|
|
|
|
expect(screen.getByText("Incomplete Post")).toBeInTheDocument();
|
|
});
|
|
|
|
it("applies correct responsive sizing for xs breakpoint", () => {
|
|
render(<ContentContainer post={mockPost} size="xs" />);
|
|
|
|
const icon = screen.getByAltText("Icon for Test Article Title");
|
|
expect(icon).toHaveClass("w-[60px]", "h-[30px]");
|
|
|
|
const title = screen.getByText("Test Article Title");
|
|
expect(title).toHaveClass("text-[18px]", "leading-[22px]");
|
|
|
|
const description = screen.getByText(/This is a test article description/);
|
|
expect(description).toHaveClass("text-[12px]", "leading-[16px]");
|
|
});
|
|
|
|
it("applies correct responsive sizing for responsive breakpoint", () => {
|
|
render(<ContentContainer post={mockPost} size="responsive" />);
|
|
|
|
const icon = screen.getByAltText("Icon for Test Article Title");
|
|
expect(icon).toHaveClass("w-[60px]", "h-[30px]");
|
|
|
|
const title = screen.getByText("Test Article Title");
|
|
expect(title).toHaveClass("text-[18px]", "leading-[120%]");
|
|
|
|
const description = screen.getByText(/This is a test article description/);
|
|
expect(description).toHaveClass("text-[12px]", "leading-[16px]");
|
|
});
|
|
|
|
it("has proper accessibility attributes", () => {
|
|
render(<ContentContainer post={mockPost} />);
|
|
|
|
const icon = screen.getByAltText("Icon for Test Article Title");
|
|
expect(icon).toHaveAttribute("alt", "Icon for Test Article Title");
|
|
});
|
|
|
|
it("handles long titles gracefully", () => {
|
|
const longTitlePost = {
|
|
...mockPost,
|
|
frontmatter: {
|
|
...mockPost.frontmatter,
|
|
title:
|
|
"This is a very long article title that should test how the component handles lengthy text content",
|
|
},
|
|
};
|
|
|
|
render(<ContentContainer post={longTitlePost} />);
|
|
|
|
expect(
|
|
screen.getByText(/This is a very long article title/),
|
|
).toBeInTheDocument();
|
|
});
|
|
|
|
it("handles long descriptions gracefully", () => {
|
|
const longDescPost = {
|
|
...mockPost,
|
|
frontmatter: {
|
|
...mockPost.frontmatter,
|
|
description:
|
|
"This is a very long article description that should test how the component handles lengthy text content and ensures proper wrapping and truncation behavior.",
|
|
},
|
|
};
|
|
|
|
render(<ContentContainer post={longDescPost} />);
|
|
|
|
expect(
|
|
screen.getByText(/This is a very long article description/),
|
|
).toBeInTheDocument();
|
|
});
|
|
});
|