import { describe, it, expect, vi, beforeEach } from "vitest"; import { getBlogPostFiles, parseBlogPost, getAllBlogPosts, getBlogPostBySlug, getRelatedBlogPosts, getAllTags, getBlogPostsByTag, } from "../../lib/content.js"; // Mock fs and path modules vi.mock("fs", () => ({ readdirSync: vi.fn(), readFileSync: vi.fn(), })); vi.mock("path", () => ({ join: vi.fn(), })); describe("Content Processing", () => { let mockReaddirSync, mockReadFileSync, mockPathJoin; beforeEach(() => { vi.clearAllMocks(); // Get references to the mocked functions const fs = require("fs"); const path = require("path"); mockReaddirSync = fs.readdirSync; mockReadFileSync = fs.readFileSync; mockPathJoin = path.join; // Mock process.cwd to return a predictable path vi.spyOn(process, "cwd").mockReturnValue("/mock/project/root"); // Mock path.join to return predictable paths if (mockPathJoin && mockPathJoin.mockImplementation) { mockPathJoin.mockImplementation((...args) => args.join("/")); } }); describe("getBlogPostFiles", () => { it("should return markdown files from content directory", () => { const mockFiles = ["post1.md", "post2.mdx", "image.png", "post3.md"]; mockReaddirSync.mockReturnValue(mockFiles); const result = getBlogPostFiles(); expect(result).toEqual(["post1.md", "post2.mdx", "post3.md"]); expect(mockReaddirSync).toHaveBeenCalledWith( "/mock/project/root/content/blog" ); }); it("should handle directory read errors gracefully", () => { mockReaddirSync.mockImplementation(() => { throw new Error("Directory not found"); }); const result = getBlogPostFiles(); expect(result).toEqual([]); expect(mockReaddirSync).toHaveBeenCalledWith( "/mock/project/root/content/blog" ); }); }); describe("parseBlogPost", () => { it("should parse a valid markdown file", () => { const mockContent = `--- title: "Test Post" description: "A test description that meets the minimum length requirement" author: "Test Author" date: "2025-04-15" tags: ["test"] related: [] --- # Test Content This is the content.`; mockReadFileSync.mockReturnValue(mockContent); const result = parseBlogPost("test-post.md"); expect(result).toMatchObject({ slug: "test-post", frontmatter: { title: "Test Post", description: "A test description that meets the minimum length requirement", author: "Test Author", date: "2025-04-15", tags: ["test"], related: [], }, content: "\n# Test Content\nThis is the content.", filePath: "test-post.md", }); expect(mockReadFileSync).toHaveBeenCalledWith( "/mock/project/root/content/blog/test-post.md", "utf8" ); }); it("should return null for invalid frontmatter", () => { const mockContent = `--- title: "" # Invalid title description: "A test description" author: "Test Author" date: "2025-04-15" --- # Test Content`; mockReadFileSync.mockReturnValue(mockContent); const result = parseBlogPost("invalid-post.md"); expect(result).toBeNull(); }); it("should handle file read errors gracefully", () => { mockReadFileSync.mockImplementation(() => { throw new Error("File not found"); }); const result = parseBlogPost("non-existent-post.md"); expect(result).toBeNull(); }); }); describe("getAllBlogPosts", () => { it("should return all valid blog posts sorted by date", () => { const mockFiles = ["post1.md", "post2.md", "post3.md"]; mockReaddirSync.mockReturnValue(mockFiles); // Mock fs.readFileSync for each post mockReadFileSync.mockReturnValueOnce(`--- title: "Post 1" description: "Desc 1" author: "Author 1" date: "2025-04-10" --- # Content 1`).mockReturnValueOnce(`--- title: "Post 2" description: "Desc 2" author: "Author 2" date: "2025-04-20" --- # Content 2`).mockReturnValueOnce(`--- title: "Post 3" description: "Desc 3" author: "Author 3" date: "2025-04-05" --- # Content 3`); const result = getAllBlogPosts(); expect(result).toHaveLength(3); expect(result[0].slug).toBe("post2"); // Latest date expect(result[1].slug).toBe("post1"); expect(result[2].slug).toBe("post3"); // Oldest date }); }); describe("getBlogPostBySlug", () => { it("should return blog post for valid slug", () => { const mockFiles = ["test-post.md"]; mockReaddirSync.mockReturnValue(mockFiles); const mockContent = `--- title: "Test Post" description: "A test description that meets the minimum length requirement" author: "Test Author" date: "2025-04-15" --- # Test Content`; mockReadFileSync.mockReturnValue(mockContent); const result = getBlogPostBySlug("test-post"); expect(result).not.toBeNull(); expect(result.slug).toBe("test-post"); }); it("should return null for invalid slug", () => { const mockFiles = ["test-post.md"]; mockReaddirSync.mockReturnValue(mockFiles); const result = getBlogPostBySlug("invalid-slug"); expect(result).toBeNull(); }); }); describe("getRelatedBlogPosts", () => { it("should return related posts when slugs are provided", () => { const mockFiles = ["post1.md", "post2.md", "post3.md"]; mockReaddirSync.mockReturnValue(mockFiles); // Mock content for all posts mockReadFileSync.mockReturnValueOnce(`--- title: "Post 1" description: "Desc 1" author: "Author 1" date: "2025-04-10" related: ["post2"] --- # Content 1`).mockReturnValueOnce(`--- title: "Post 2" description: "Desc 2" author: "Author 2" date: "2025-04-20" --- # Content 2`).mockReturnValueOnce(`--- title: "Post 3" description: "Desc 3" author: "Author 3" date: "2025-04-05" --- # Content 3`); const result = getRelatedBlogPosts("post1", ["post2", "post3"], 2); expect(result).toHaveLength(2); expect(result[0].slug).toBe("post2"); expect(result[1].slug).toBe("post3"); }); it("should fallback to recent posts when no related slugs provided", () => { const mockFiles = ["post1.md", "post2.md", "post3.md"]; mockReaddirSync.mockReturnValue(mockFiles); const mockContent = `--- title: "Post 1" description: "Desc 1" author: "Author 1" date: "2025-04-10" --- # Content 1`; mockReadFileSync.mockReturnValue(mockContent); const result = getRelatedBlogPosts("post1", [], 2); expect(result).toHaveLength(2); expect(result[0].slug).toBe("post2"); // Should be the most recent after excluding 'post1' expect(result[1].slug).toBe("post3"); }); }); describe("getAllTags", () => { it("should return unique tags from all posts", () => { const mockFiles = ["post1.md", "post2.md"]; mockReaddirSync.mockReturnValue(mockFiles); const mockContent1 = `--- title: "Post 1" description: "Desc 1" author: "Author 1" date: "2025-04-10" tags: ["tagA", "tagB"] --- # Content 1`; const mockContent2 = `--- title: "Post 2" description: "Desc 2" author: "Author 2" date: "2025-04-20" tags: ["tagB", "tagC"] --- # Content 2`; mockReadFileSync .mockReturnValueOnce(mockContent1) .mockReturnValueOnce(mockContent2); const result = getAllTags(); expect(result).toEqual(expect.arrayContaining(["tagA", "tagB", "tagC"])); expect(result).toHaveLength(3); }); }); describe("getBlogPostsByTag", () => { it("should return posts with matching tag", () => { const mockFiles = ["post1.md", "post2.md"]; mockReaddirSync.mockReturnValue(mockFiles); const mockContent1 = `--- title: "Post 1" description: "Desc 1" author: "Author 1" date: "2025-04-10" tags: ["tagA", "tagB"] --- # Content 1`; const mockContent2 = `--- title: "Post 2" description: "Desc 2" author: "Author 2" date: "2025-04-20" tags: ["tagB", "tagC"] --- # Content 2`; mockReadFileSync .mockReturnValueOnce(mockContent1) .mockReturnValueOnce(mockContent2); const result = getBlogPostsByTag("tagA"); expect(result).toHaveLength(1); expect(result[0].slug).toBe("post1"); }); }); });