Add integration tests for content
This commit is contained in:
@@ -0,0 +1,171 @@
|
|||||||
|
import { describe, it, expect, vi, beforeEach } from "vitest";
|
||||||
|
import { render, screen } from "@testing-library/react";
|
||||||
|
import RelatedArticles from "../../app/components/RelatedArticles";
|
||||||
|
|
||||||
|
// Mock ContentThumbnailTemplate with a simple implementation
|
||||||
|
vi.mock("../../app/components/ContentThumbnailTemplate", () => ({
|
||||||
|
default: ({ post, variant }) => (
|
||||||
|
<div data-testid={`thumbnail-${post.slug}`} data-variant={variant}>
|
||||||
|
<a href={`/blog/${post.slug}`}>
|
||||||
|
<h3>{post.frontmatter?.title || "Untitled"}</h3>
|
||||||
|
<p>{post.frontmatter?.description || "No description"}</p>
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
),
|
||||||
|
}));
|
||||||
|
|
||||||
|
// Mock blog post data
|
||||||
|
const mockRelatedPosts = [
|
||||||
|
{
|
||||||
|
slug: "resolving-active-conflicts",
|
||||||
|
frontmatter: {
|
||||||
|
title: "Resolving Active Conflicts",
|
||||||
|
description:
|
||||||
|
"Practical steps for resolving conflicts while maintaining trust",
|
||||||
|
author: "Test Author",
|
||||||
|
date: "2025-04-15",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
slug: "operational-security-mutual-aid",
|
||||||
|
frontmatter: {
|
||||||
|
title: "Operational Security for Mutual Aid",
|
||||||
|
description:
|
||||||
|
"Tactics to protect members, secure communication, and prevent infiltration",
|
||||||
|
author: "Test Author",
|
||||||
|
date: "2025-04-14",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
slug: "making-decisions-without-hierarchy",
|
||||||
|
frontmatter: {
|
||||||
|
title: "Making Decisions Without Hierarchy",
|
||||||
|
description:
|
||||||
|
"A brief guide to collaborative nonhierarchical decision making",
|
||||||
|
author: "Test Author",
|
||||||
|
date: "2025-04-13",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
describe("Blog Core Integration", () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
// Mock window.innerWidth for responsive tests
|
||||||
|
Object.defineProperty(window, "innerWidth", {
|
||||||
|
writable: true,
|
||||||
|
configurable: true,
|
||||||
|
value: 1024, // Desktop width
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should render RelatedArticles component with correct structure", () => {
|
||||||
|
render(
|
||||||
|
<RelatedArticles
|
||||||
|
relatedPosts={mockRelatedPosts}
|
||||||
|
currentPostSlug="resolving-active-conflicts"
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
|
||||||
|
// Verify the section exists
|
||||||
|
expect(screen.getByRole("heading", { level: 2 })).toHaveTextContent(
|
||||||
|
"Related Articles"
|
||||||
|
);
|
||||||
|
|
||||||
|
// Verify thumbnails are rendered
|
||||||
|
expect(
|
||||||
|
screen.getByTestId("thumbnail-operational-security-mutual-aid")
|
||||||
|
).toBeInTheDocument();
|
||||||
|
expect(
|
||||||
|
screen.getByTestId("thumbnail-making-decisions-without-hierarchy")
|
||||||
|
).toBeInTheDocument();
|
||||||
|
|
||||||
|
// Current post should not be displayed
|
||||||
|
expect(
|
||||||
|
screen.queryByTestId("thumbnail-resolving-active-conflicts")
|
||||||
|
).not.toBeInTheDocument();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should filter out current post from related articles", () => {
|
||||||
|
render(
|
||||||
|
<RelatedArticles
|
||||||
|
relatedPosts={mockRelatedPosts}
|
||||||
|
currentPostSlug="resolving-active-conflicts"
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
|
||||||
|
// Current post should not be displayed
|
||||||
|
expect(
|
||||||
|
screen.queryByTestId("thumbnail-resolving-active-conflicts")
|
||||||
|
).not.toBeInTheDocument();
|
||||||
|
|
||||||
|
// Other posts should be displayed
|
||||||
|
expect(
|
||||||
|
screen.getByTestId("thumbnail-operational-security-mutual-aid")
|
||||||
|
).toBeInTheDocument();
|
||||||
|
expect(
|
||||||
|
screen.getByTestId("thumbnail-making-decisions-without-hierarchy")
|
||||||
|
).toBeInTheDocument();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should display all posts when no current post is specified", () => {
|
||||||
|
render(<RelatedArticles relatedPosts={mockRelatedPosts} />);
|
||||||
|
|
||||||
|
// All posts should be displayed
|
||||||
|
expect(
|
||||||
|
screen.getByTestId("thumbnail-resolving-active-conflicts")
|
||||||
|
).toBeInTheDocument();
|
||||||
|
expect(
|
||||||
|
screen.getByTestId("thumbnail-operational-security-mutual-aid")
|
||||||
|
).toBeInTheDocument();
|
||||||
|
expect(
|
||||||
|
screen.getByTestId("thumbnail-making-decisions-without-hierarchy")
|
||||||
|
).toBeInTheDocument();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should handle empty related posts array", () => {
|
||||||
|
const { container } = render(
|
||||||
|
<RelatedArticles relatedPosts={[]} currentPostSlug="test-post" />
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(container.firstChild).toBeNull();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should create correct links for each thumbnail", () => {
|
||||||
|
render(
|
||||||
|
<RelatedArticles
|
||||||
|
relatedPosts={mockRelatedPosts}
|
||||||
|
currentPostSlug="resolving-active-conflicts"
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
|
||||||
|
// Verify links are created correctly
|
||||||
|
const operationalLink = screen
|
||||||
|
.getByTestId("thumbnail-operational-security-mutual-aid")
|
||||||
|
.querySelector("a");
|
||||||
|
const hierarchyLink = screen
|
||||||
|
.getByTestId("thumbnail-making-decisions-without-hierarchy")
|
||||||
|
.querySelector("a");
|
||||||
|
|
||||||
|
expect(operationalLink).toHaveAttribute(
|
||||||
|
"href",
|
||||||
|
"/blog/operational-security-mutual-aid"
|
||||||
|
);
|
||||||
|
expect(hierarchyLink).toHaveAttribute(
|
||||||
|
"href",
|
||||||
|
"/blog/making-decisions-without-hierarchy"
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should display section heading", () => {
|
||||||
|
render(
|
||||||
|
<RelatedArticles
|
||||||
|
relatedPosts={mockRelatedPosts}
|
||||||
|
currentPostSlug="resolving-active-conflicts"
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(screen.getByRole("heading", { level: 2 })).toHaveTextContent(
|
||||||
|
"Related Articles"
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -0,0 +1,98 @@
|
|||||||
|
import { describe, it, expect, vi, beforeEach } from "vitest";
|
||||||
|
import fs from "fs";
|
||||||
|
import path from "path";
|
||||||
|
|
||||||
|
// Mock fs and path modules
|
||||||
|
vi.mock("fs");
|
||||||
|
vi.mock("path");
|
||||||
|
|
||||||
|
// Import the content processing functions
|
||||||
|
import { getBlogPostFiles, markdownToHtml } from "../../lib/content";
|
||||||
|
|
||||||
|
describe("Content Processing Integration", () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
vi.clearAllMocks();
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("File System Integration", () => {
|
||||||
|
it("should read blog post files from content directory", () => {
|
||||||
|
const mockFiles = ["post1.md", "post2.md", "image.png", "post3.md"];
|
||||||
|
fs.readdirSync.mockReturnValue(mockFiles);
|
||||||
|
|
||||||
|
const result = getBlogPostFiles();
|
||||||
|
|
||||||
|
expect(fs.readdirSync).toHaveBeenCalledWith(
|
||||||
|
path.join(process.cwd(), "content/blog")
|
||||||
|
);
|
||||||
|
expect(result).toEqual(["post1.md", "post2.md", "post3.md"]);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should handle directory read errors gracefully", () => {
|
||||||
|
fs.readdirSync.mockImplementation(() => {
|
||||||
|
throw new Error("Directory not found");
|
||||||
|
});
|
||||||
|
|
||||||
|
const result = getBlogPostFiles();
|
||||||
|
|
||||||
|
expect(result).toEqual([]);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should filter out non-markdown files", () => {
|
||||||
|
const mockFiles = [
|
||||||
|
"post1.md",
|
||||||
|
"post2.mdx",
|
||||||
|
"image.png",
|
||||||
|
"post3.md",
|
||||||
|
"readme.txt",
|
||||||
|
];
|
||||||
|
fs.readdirSync.mockReturnValue(mockFiles);
|
||||||
|
|
||||||
|
const result = getBlogPostFiles();
|
||||||
|
|
||||||
|
expect(result).toEqual(["post1.md", "post2.mdx", "post3.md"]);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("Markdown to HTML Integration", () => {
|
||||||
|
it("should convert markdown to HTML with proper formatting", () => {
|
||||||
|
const markdown = `# Main Title
|
||||||
|
|
||||||
|
## Subtitle
|
||||||
|
|
||||||
|
This is a paragraph with **bold** and *italic* text.
|
||||||
|
|
||||||
|
- List item 1
|
||||||
|
- List item 2
|
||||||
|
|
||||||
|
[Link text](https://example.com)`;
|
||||||
|
|
||||||
|
const result = markdownToHtml(markdown);
|
||||||
|
|
||||||
|
expect(result).toContain("<h1>Main Title</h1>");
|
||||||
|
expect(result).toContain("<h2>Subtitle</h2>");
|
||||||
|
expect(result).toContain("<strong>bold</strong>");
|
||||||
|
expect(result).toContain("<em>italic</em>");
|
||||||
|
expect(result).toContain('<a href="https://example.com">Link text</a>');
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should handle empty markdown gracefully", () => {
|
||||||
|
const result = markdownToHtml("");
|
||||||
|
|
||||||
|
expect(result).toBe("");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should handle markdown with special characters", () => {
|
||||||
|
const markdown = `# Title with Special Characters: & < > " '
|
||||||
|
|
||||||
|
Content with **bold** and *italic* text.`;
|
||||||
|
|
||||||
|
const result = markdownToHtml(markdown);
|
||||||
|
|
||||||
|
expect(result).toContain(
|
||||||
|
"<h1>Title with Special Characters: & < > \" '</h1>"
|
||||||
|
);
|
||||||
|
expect(result).toContain("<strong>bold</strong>");
|
||||||
|
expect(result).toContain("<em>italic</em>");
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -0,0 +1,214 @@
|
|||||||
|
import { describe, it, expect, vi, beforeEach } from "vitest";
|
||||||
|
import { render, screen, fireEvent, waitFor } from "@testing-library/react";
|
||||||
|
import RelatedArticles from "../../app/components/RelatedArticles";
|
||||||
|
|
||||||
|
// Mock ContentThumbnailTemplate
|
||||||
|
vi.mock("../../app/components/ContentThumbnailTemplate", () => ({
|
||||||
|
default: ({ post, variant }) => (
|
||||||
|
<div data-testid={`thumbnail-${post.slug}`} data-variant={variant}>
|
||||||
|
<a
|
||||||
|
href={`/blog/${post.slug}`}
|
||||||
|
data-testid={`thumbnail-link-${post.slug}`}
|
||||||
|
>
|
||||||
|
<h3>{post.frontmatter?.title || "Untitled"}</h3>
|
||||||
|
<p>{post.frontmatter?.description || "No description"}</p>
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
),
|
||||||
|
}));
|
||||||
|
|
||||||
|
// Mock blog post data
|
||||||
|
const mockRelatedPosts = [
|
||||||
|
{
|
||||||
|
slug: "resolving-active-conflicts",
|
||||||
|
frontmatter: {
|
||||||
|
title: "Resolving Active Conflicts",
|
||||||
|
description:
|
||||||
|
"Practical steps for resolving conflicts while maintaining trust",
|
||||||
|
author: "Test Author",
|
||||||
|
date: "2025-04-15",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
slug: "operational-security-mutual-aid",
|
||||||
|
frontmatter: {
|
||||||
|
title: "Operational Security for Mutual Aid",
|
||||||
|
description:
|
||||||
|
"Tactics to protect members, secure communication, and prevent infiltration",
|
||||||
|
author: "Test Author",
|
||||||
|
date: "2025-04-14",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
slug: "making-decisions-without-hierarchy",
|
||||||
|
frontmatter: {
|
||||||
|
title: "Making Decisions Without Hierarchy",
|
||||||
|
description:
|
||||||
|
"A brief guide to collaborative nonhierarchical decision making",
|
||||||
|
author: "Test Author",
|
||||||
|
date: "2025-04-13",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
slug: "building-community-trust",
|
||||||
|
frontmatter: {
|
||||||
|
title: "Building Community Trust",
|
||||||
|
description: "Strategies for fostering trust in community organizations",
|
||||||
|
author: "Test Author",
|
||||||
|
date: "2025-04-12",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
describe("Related Articles Integration", () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
// Mock window.innerWidth for responsive tests
|
||||||
|
Object.defineProperty(window, "innerWidth", {
|
||||||
|
writable: true,
|
||||||
|
configurable: true,
|
||||||
|
value: 1024, // Desktop width
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should filter out current post from related articles", () => {
|
||||||
|
render(
|
||||||
|
<RelatedArticles
|
||||||
|
relatedPosts={mockRelatedPosts}
|
||||||
|
currentPostSlug="resolving-active-conflicts"
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
|
||||||
|
// Current post should not be displayed
|
||||||
|
expect(
|
||||||
|
screen.queryByTestId("thumbnail-resolving-active-conflicts")
|
||||||
|
).not.toBeInTheDocument();
|
||||||
|
|
||||||
|
// Other posts should be displayed
|
||||||
|
expect(
|
||||||
|
screen.getByTestId("thumbnail-operational-security-mutual-aid")
|
||||||
|
).toBeInTheDocument();
|
||||||
|
expect(
|
||||||
|
screen.getByTestId("thumbnail-making-decisions-without-hierarchy")
|
||||||
|
).toBeInTheDocument();
|
||||||
|
expect(
|
||||||
|
screen.getByTestId("thumbnail-building-community-trust")
|
||||||
|
).toBeInTheDocument();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should display all posts when no current post is specified", () => {
|
||||||
|
render(<RelatedArticles relatedPosts={mockRelatedPosts} />);
|
||||||
|
|
||||||
|
// All posts should be displayed
|
||||||
|
expect(
|
||||||
|
screen.getByTestId("thumbnail-resolving-active-conflicts")
|
||||||
|
).toBeInTheDocument();
|
||||||
|
expect(
|
||||||
|
screen.getByTestId("thumbnail-operational-security-mutual-aid")
|
||||||
|
).toBeInTheDocument();
|
||||||
|
expect(
|
||||||
|
screen.getByTestId("thumbnail-making-decisions-without-hierarchy")
|
||||||
|
).toBeInTheDocument();
|
||||||
|
expect(
|
||||||
|
screen.getByTestId("thumbnail-building-community-trust")
|
||||||
|
).toBeInTheDocument();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should create correct links for each thumbnail", () => {
|
||||||
|
render(
|
||||||
|
<RelatedArticles
|
||||||
|
relatedPosts={mockRelatedPosts}
|
||||||
|
currentPostSlug="resolving-active-conflicts"
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
|
||||||
|
// Verify links are created correctly
|
||||||
|
expect(
|
||||||
|
screen.getByTestId("thumbnail-link-operational-security-mutual-aid")
|
||||||
|
).toHaveAttribute("href", "/blog/operational-security-mutual-aid");
|
||||||
|
expect(
|
||||||
|
screen.getByTestId("thumbnail-link-making-decisions-without-hierarchy")
|
||||||
|
).toHaveAttribute("href", "/blog/making-decisions-without-hierarchy");
|
||||||
|
expect(
|
||||||
|
screen.getByTestId("thumbnail-link-building-community-trust")
|
||||||
|
).toHaveAttribute("href", "/blog/building-community-trust");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should handle empty related posts array", () => {
|
||||||
|
const { container } = render(
|
||||||
|
<RelatedArticles relatedPosts={[]} currentPostSlug="test-post" />
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(container.firstChild).toBeNull();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should handle single related post", () => {
|
||||||
|
const singlePost = [mockRelatedPosts[0]];
|
||||||
|
|
||||||
|
render(
|
||||||
|
<RelatedArticles
|
||||||
|
relatedPosts={singlePost}
|
||||||
|
currentPostSlug="different-post"
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(
|
||||||
|
screen.getByTestId("thumbnail-resolving-active-conflicts")
|
||||||
|
).toBeInTheDocument();
|
||||||
|
expect(
|
||||||
|
screen.queryByTestId("thumbnail-operational-security-mutual-aid")
|
||||||
|
).not.toBeInTheDocument();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should handle all posts being filtered out", () => {
|
||||||
|
const currentPostOnly = [mockRelatedPosts[0]];
|
||||||
|
|
||||||
|
const { container } = render(
|
||||||
|
<RelatedArticles
|
||||||
|
relatedPosts={currentPostOnly}
|
||||||
|
currentPostSlug="resolving-active-conflicts"
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(container.firstChild).toBeNull();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should display section heading", () => {
|
||||||
|
render(
|
||||||
|
<RelatedArticles
|
||||||
|
relatedPosts={mockRelatedPosts}
|
||||||
|
currentPostSlug="resolving-active-conflicts"
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(screen.getByRole("heading", { level: 2 })).toHaveTextContent(
|
||||||
|
"Related Articles"
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should maintain consistent structure across different current posts", () => {
|
||||||
|
const slugs = [
|
||||||
|
"resolving-active-conflicts",
|
||||||
|
"operational-security-mutual-aid",
|
||||||
|
"making-decisions-without-hierarchy",
|
||||||
|
];
|
||||||
|
|
||||||
|
slugs.forEach((slug) => {
|
||||||
|
const { unmount } = render(
|
||||||
|
<RelatedArticles
|
||||||
|
relatedPosts={mockRelatedPosts}
|
||||||
|
currentPostSlug={slug}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
|
||||||
|
// Verify consistent structure
|
||||||
|
expect(screen.getByRole("heading", { level: 2 })).toHaveTextContent(
|
||||||
|
"Related Articles"
|
||||||
|
);
|
||||||
|
// Check that we have some thumbnails (the exact ones depend on the current post)
|
||||||
|
const thumbnails = screen.getAllByTestId(/thumbnail-/);
|
||||||
|
expect(thumbnails.length).toBeGreaterThan(0);
|
||||||
|
|
||||||
|
unmount();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
Reference in New Issue
Block a user