Remove unnecessary props and data structure
This commit is contained in:
@@ -10,9 +10,8 @@ import ContentContainer from "./ContentContainer";
|
|||||||
*/
|
*/
|
||||||
const ContentThumbnailTemplate = ({
|
const ContentThumbnailTemplate = ({
|
||||||
post,
|
post,
|
||||||
variant = "vertical",
|
|
||||||
className = "",
|
className = "",
|
||||||
showReadingTime = true,
|
variant = "vertical", // Internal prop for testing/development
|
||||||
}) => {
|
}) => {
|
||||||
// Post-specific background selection - different SVG for each post
|
// Post-specific background selection - different SVG for each post
|
||||||
const getBackgroundImage = (slug, variant) => {
|
const getBackgroundImage = (slug, variant) => {
|
||||||
|
|||||||
@@ -9,10 +9,7 @@ const mockPost1 = {
|
|||||||
"Practical steps for resolving conflicts while maintaining trust, cooperation, and shared goals",
|
"Practical steps for resolving conflicts while maintaining trust, cooperation, and shared goals",
|
||||||
author: "Author name",
|
author: "Author name",
|
||||||
date: "2025-04-15",
|
date: "2025-04-15",
|
||||||
tags: ["conflict-resolution", "governance", "community"],
|
|
||||||
},
|
},
|
||||||
wordCount: 467,
|
|
||||||
readingTime: 3,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const mockPost2 = {
|
const mockPost2 = {
|
||||||
@@ -23,10 +20,7 @@ const mockPost2 = {
|
|||||||
"Tactics to protect members, secure communication, and prevent Infiltration",
|
"Tactics to protect members, secure communication, and prevent Infiltration",
|
||||||
author: "Author name",
|
author: "Author name",
|
||||||
date: "2025-04-10",
|
date: "2025-04-10",
|
||||||
tags: ["community-building", "sustainability", "structure"],
|
|
||||||
},
|
},
|
||||||
wordCount: 523,
|
|
||||||
readingTime: 4,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const mockPost3 = {
|
const mockPost3 = {
|
||||||
@@ -37,10 +31,7 @@ const mockPost3 = {
|
|||||||
"A brief guide to collaborative nonhierarchical decision making",
|
"A brief guide to collaborative nonhierarchical decision making",
|
||||||
author: "Author name",
|
author: "Author name",
|
||||||
date: "2025-04-05",
|
date: "2025-04-05",
|
||||||
tags: ["communication", "remote-work", "collaboration"],
|
|
||||||
},
|
},
|
||||||
wordCount: 389,
|
|
||||||
readingTime: 2,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export default function TestThumbnailPage() {
|
export default function TestThumbnailPage() {
|
||||||
@@ -58,23 +49,9 @@ export default function TestThumbnailPage() {
|
|||||||
Vertical Variant
|
Vertical Variant
|
||||||
</h2>
|
</h2>
|
||||||
<div className="flex flex-wrap gap-6">
|
<div className="flex flex-wrap gap-6">
|
||||||
<ContentThumbnailTemplate
|
<ContentThumbnailTemplate post={mockPost1} className="mb-4" />
|
||||||
post={mockPost1}
|
<ContentThumbnailTemplate post={mockPost2} className="mb-4" />
|
||||||
variant="vertical"
|
<ContentThumbnailTemplate post={mockPost3} className="mb-4" />
|
||||||
className="mb-4"
|
|
||||||
/>
|
|
||||||
<ContentThumbnailTemplate
|
|
||||||
post={mockPost2}
|
|
||||||
variant="vertical"
|
|
||||||
className="mb-4"
|
|
||||||
showTags={false}
|
|
||||||
/>
|
|
||||||
<ContentThumbnailTemplate
|
|
||||||
post={mockPost3}
|
|
||||||
variant="vertical"
|
|
||||||
className="mb-4"
|
|
||||||
showReadingTime={false}
|
|
||||||
/>
|
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
@@ -85,16 +62,8 @@ export default function TestThumbnailPage() {
|
|||||||
</h2>
|
</h2>
|
||||||
<div className="space-y-4">
|
<div className="space-y-4">
|
||||||
<ContentThumbnailTemplate post={mockPost1} variant="horizontal" />
|
<ContentThumbnailTemplate post={mockPost1} variant="horizontal" />
|
||||||
<ContentThumbnailTemplate
|
<ContentThumbnailTemplate post={mockPost2} variant="horizontal" />
|
||||||
post={mockPost2}
|
<ContentThumbnailTemplate post={mockPost3} variant="horizontal" />
|
||||||
variant="horizontal"
|
|
||||||
showTags={false}
|
|
||||||
/>
|
|
||||||
<ContentThumbnailTemplate
|
|
||||||
post={mockPost3}
|
|
||||||
variant="horizontal"
|
|
||||||
showReadingTime={false}
|
|
||||||
/>
|
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
@@ -119,18 +88,12 @@ export default function TestThumbnailPage() {
|
|||||||
Optional Props:
|
Optional Props:
|
||||||
</h3>
|
</h3>
|
||||||
<ul className="space-y-1 text-gray-600">
|
<ul className="space-y-1 text-gray-600">
|
||||||
<li>
|
|
||||||
<code>variant</code> - "vertical" (default) or "horizontal"
|
|
||||||
</li>
|
|
||||||
<li>
|
<li>
|
||||||
<code>className</code> - Additional CSS classes
|
<code>className</code> - Additional CSS classes
|
||||||
</li>
|
</li>
|
||||||
<li>
|
<li>
|
||||||
<code>showTags</code> - Show/hide tags (default: true)
|
<code>variant</code> - "vertical" (default) or "horizontal"
|
||||||
</li>
|
(for development/testing)
|
||||||
<li>
|
|
||||||
<code>showReadingTime</code> - Show/hide reading time
|
|
||||||
(default: true)
|
|
||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
+1
-58
@@ -108,8 +108,6 @@ class ContentProcessor {
|
|||||||
frontmatter: processFrontmatter(sanitizedFrontmatter),
|
frontmatter: processFrontmatter(sanitizedFrontmatter),
|
||||||
content: processedContent.content,
|
content: processedContent.content,
|
||||||
htmlContent: processedContent.htmlContent,
|
htmlContent: processedContent.htmlContent,
|
||||||
wordCount: processedContent.wordCount,
|
|
||||||
readingTime: processedContent.readingTime,
|
|
||||||
headings: processedContent.headings,
|
headings: processedContent.headings,
|
||||||
links: processedContent.links,
|
links: processedContent.links,
|
||||||
images: processedContent.images,
|
images: processedContent.images,
|
||||||
@@ -195,46 +193,6 @@ class ContentProcessor {
|
|||||||
return recentPosts;
|
return recentPosts;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Get blog posts by tag
|
|
||||||
* @param {string} tag - The tag to filter by
|
|
||||||
* @returns {Array} Array of blog post objects matching the tag
|
|
||||||
*/
|
|
||||||
getPostsByTag(tag) {
|
|
||||||
const cacheKey = `tag:${tag}`;
|
|
||||||
const cached = getCachedBlogList(cacheKey);
|
|
||||||
if (cached) return cached;
|
|
||||||
|
|
||||||
const allPosts = this.getAllPosts();
|
|
||||||
const taggedPosts = allPosts.filter(
|
|
||||||
(post) => post.frontmatter.tags && post.frontmatter.tags.includes(tag)
|
|
||||||
);
|
|
||||||
|
|
||||||
cacheBlogList(cacheKey, taggedPosts);
|
|
||||||
return taggedPosts;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get all unique tags with caching
|
|
||||||
* @returns {Array} Array of unique tags
|
|
||||||
*/
|
|
||||||
getAllTags() {
|
|
||||||
const cached = getCachedTags();
|
|
||||||
if (cached) return cached;
|
|
||||||
|
|
||||||
const allPosts = this.getAllPosts();
|
|
||||||
const tags = new Set();
|
|
||||||
allPosts.forEach((post) => {
|
|
||||||
if (post.frontmatter.tags) {
|
|
||||||
post.frontmatter.tags.forEach((tag) => tags.add(tag));
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
const tagsArray = Array.from(tags).sort();
|
|
||||||
cacheTags(tagsArray);
|
|
||||||
return tagsArray;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Search blog posts
|
* Search blog posts
|
||||||
* @param {string} query - Search query
|
* @param {string} query - Search query
|
||||||
@@ -255,11 +213,8 @@ class ContentProcessor {
|
|||||||
.toLowerCase()
|
.toLowerCase()
|
||||||
.includes(searchTerm);
|
.includes(searchTerm);
|
||||||
const contentMatch = post.content.toLowerCase().includes(searchTerm);
|
const contentMatch = post.content.toLowerCase().includes(searchTerm);
|
||||||
const tagMatch = post.frontmatter.tags?.some((tag) =>
|
|
||||||
tag.toLowerCase().includes(searchTerm)
|
|
||||||
);
|
|
||||||
|
|
||||||
return titleMatch || descriptionMatch || contentMatch || tagMatch;
|
return titleMatch || descriptionMatch || contentMatch;
|
||||||
});
|
});
|
||||||
|
|
||||||
return results.slice(0, limit);
|
return results.slice(0, limit);
|
||||||
@@ -271,22 +226,12 @@ class ContentProcessor {
|
|||||||
*/
|
*/
|
||||||
getBlogStats() {
|
getBlogStats() {
|
||||||
const allPosts = this.getAllPosts();
|
const allPosts = this.getAllPosts();
|
||||||
const tags = this.getAllTags();
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
totalPosts: allPosts.length,
|
totalPosts: allPosts.length,
|
||||||
totalTags: tags.length,
|
|
||||||
totalAuthors: new Set(
|
totalAuthors: new Set(
|
||||||
allPosts.map((post) => post.frontmatter.author).size
|
allPosts.map((post) => post.frontmatter.author).size
|
||||||
),
|
),
|
||||||
totalWords: allPosts.reduce((sum, post) => sum + post.wordCount, 0),
|
|
||||||
averageReadingTime:
|
|
||||||
allPosts.length > 0
|
|
||||||
? Math.round(
|
|
||||||
allPosts.reduce((sum, post) => sum + post.readingTime, 0) /
|
|
||||||
allPosts.length
|
|
||||||
)
|
|
||||||
: 0,
|
|
||||||
dateRange: {
|
dateRange: {
|
||||||
earliest:
|
earliest:
|
||||||
allPosts.length > 0
|
allPosts.length > 0
|
||||||
@@ -367,8 +312,6 @@ export const getAllPosts = () => contentProcessor.getAllPosts();
|
|||||||
export const getBlogPostBySlug = (slug) =>
|
export const getBlogPostBySlug = (slug) =>
|
||||||
contentProcessor.getBlogPostBySlug(slug);
|
contentProcessor.getBlogPostBySlug(slug);
|
||||||
export const getRecentPosts = (limit) => contentProcessor.getRecentPosts(limit);
|
export const getRecentPosts = (limit) => contentProcessor.getRecentPosts(limit);
|
||||||
export const getPostsByTag = (tag) => contentProcessor.getPostsByTag(tag);
|
|
||||||
export const getAllTags = () => contentProcessor.getAllTags();
|
|
||||||
export const searchPosts = (query, limit) =>
|
export const searchPosts = (query, limit) =>
|
||||||
contentProcessor.searchPosts(query, limit);
|
contentProcessor.searchPosts(query, limit);
|
||||||
export const getBlogStats = () => contentProcessor.getBlogStats();
|
export const getBlogStats = () => contentProcessor.getBlogStats();
|
||||||
|
|||||||
-36
@@ -24,8 +24,6 @@ export function processMarkdown(markdown) {
|
|||||||
return {
|
return {
|
||||||
content: "",
|
content: "",
|
||||||
htmlContent: "",
|
htmlContent: "",
|
||||||
wordCount: 0,
|
|
||||||
readingTime: 0,
|
|
||||||
headings: [],
|
headings: [],
|
||||||
links: [],
|
links: [],
|
||||||
images: [],
|
images: [],
|
||||||
@@ -41,18 +39,12 @@ export function processMarkdown(markdown) {
|
|||||||
// Extract images
|
// Extract images
|
||||||
const images = extractImages(markdown);
|
const images = extractImages(markdown);
|
||||||
|
|
||||||
// Calculate word count and reading time
|
|
||||||
const wordCount = calculateWordCount(markdown);
|
|
||||||
const readingTime = calculateReadingTime(wordCount);
|
|
||||||
|
|
||||||
// Convert markdown to HTML
|
// Convert markdown to HTML
|
||||||
const htmlContent = markdownToHtml(markdown);
|
const htmlContent = markdownToHtml(markdown);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
content: markdown,
|
content: markdown,
|
||||||
htmlContent,
|
htmlContent,
|
||||||
wordCount,
|
|
||||||
readingTime,
|
|
||||||
headings,
|
headings,
|
||||||
links,
|
links,
|
||||||
images,
|
images,
|
||||||
@@ -141,31 +133,6 @@ function generateHeadingId(text) {
|
|||||||
.trim();
|
.trim();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Calculate word count from markdown content
|
|
||||||
* @param {string} markdown - Raw markdown content
|
|
||||||
* @returns {number} Word count
|
|
||||||
*/
|
|
||||||
function calculateWordCount(markdown) {
|
|
||||||
// Remove markdown syntax and count words
|
|
||||||
const cleanText = markdown
|
|
||||||
.replace(/[#*`~\[\]()]/g, "") // Remove markdown characters
|
|
||||||
.replace(/\n+/g, " ") // Replace newlines with spaces
|
|
||||||
.trim();
|
|
||||||
|
|
||||||
return cleanText.split(/\s+/).filter((word) => word.length > 0).length;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Calculate estimated reading time
|
|
||||||
* @param {number} wordCount - Number of words
|
|
||||||
* @returns {number} Reading time in minutes
|
|
||||||
*/
|
|
||||||
function calculateReadingTime(wordCount) {
|
|
||||||
const wordsPerMinute = 200; // Average reading speed
|
|
||||||
return Math.ceil(wordCount / wordsPerMinute);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Convert markdown to HTML with enhanced formatting
|
* Convert markdown to HTML with enhanced formatting
|
||||||
* @param {string} markdown - Raw markdown content
|
* @param {string} markdown - Raw markdown content
|
||||||
@@ -255,9 +222,6 @@ export function processFrontmatter(frontmatter) {
|
|||||||
month: new Date(frontmatter.date).getMonth() + 1,
|
month: new Date(frontmatter.date).getMonth() + 1,
|
||||||
day: new Date(frontmatter.date).getDate(),
|
day: new Date(frontmatter.date).getDate(),
|
||||||
isRecent: isRecentPost(frontmatter.date),
|
isRecent: isRecentPost(frontmatter.date),
|
||||||
readingTime: frontmatter.content
|
|
||||||
? calculateReadingTime(calculateWordCount(frontmatter.content))
|
|
||||||
: 0,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
return processed;
|
return processed;
|
||||||
|
|||||||
@@ -29,16 +29,6 @@ export const BLOG_POST_SCHEMA = {
|
|||||||
required: true,
|
required: true,
|
||||||
pattern: /^\d{4}-\d{2}-\d{2}$/, // YYYY-MM-DD format
|
pattern: /^\d{4}-\d{2}-\d{2}$/, // YYYY-MM-DD format
|
||||||
},
|
},
|
||||||
tags: {
|
|
||||||
type: "array",
|
|
||||||
required: false,
|
|
||||||
default: [],
|
|
||||||
items: {
|
|
||||||
type: "string",
|
|
||||||
minLength: 1,
|
|
||||||
maxLength: 20,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
related: {
|
related: {
|
||||||
type: "array",
|
type: "array",
|
||||||
required: false,
|
required: false,
|
||||||
|
|||||||
@@ -28,17 +28,14 @@ const mockPost = {
|
|||||||
"This is a test description for the blog post that should be long enough to test truncation.",
|
"This is a test description for the blog post that should be long enough to test truncation.",
|
||||||
author: "Test Author",
|
author: "Test Author",
|
||||||
date: "2025-04-15",
|
date: "2025-04-15",
|
||||||
tags: ["test", "blog", "example"],
|
|
||||||
backgroundImages: ["/test-image-1.jpg", "/test-image-2.jpg"],
|
backgroundImages: ["/test-image-1.jpg", "/test-image-2.jpg"],
|
||||||
},
|
},
|
||||||
wordCount: 500,
|
|
||||||
readingTime: 3,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
describe("ContentThumbnailTemplate", () => {
|
describe("ContentThumbnailTemplate", () => {
|
||||||
describe("Vertical Variant", () => {
|
describe("Vertical Variant", () => {
|
||||||
it("should render vertical variant with correct dimensions", () => {
|
it("should render vertical variant with correct dimensions", () => {
|
||||||
render(<ContentThumbnailTemplate post={mockPost} variant="vertical" />);
|
render(<ContentThumbnailTemplate post={mockPost} />);
|
||||||
|
|
||||||
const container = screen.getByRole("link");
|
const container = screen.getByRole("link");
|
||||||
expect(container).toBeInTheDocument();
|
expect(container).toBeInTheDocument();
|
||||||
@@ -49,7 +46,7 @@ describe("ContentThumbnailTemplate", () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it("should display post title and description", () => {
|
it("should display post title and description", () => {
|
||||||
render(<ContentThumbnailTemplate post={mockPost} variant="vertical" />);
|
render(<ContentThumbnailTemplate post={mockPost} />);
|
||||||
|
|
||||||
expect(screen.getByText("Test Blog Post Title")).toBeInTheDocument();
|
expect(screen.getByText("Test Blog Post Title")).toBeInTheDocument();
|
||||||
expect(
|
expect(
|
||||||
@@ -58,57 +55,21 @@ describe("ContentThumbnailTemplate", () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it("should display tags when showTags is true", () => {
|
it("should display tags when showTags is true", () => {
|
||||||
render(
|
render(<ContentThumbnailTemplate post={mockPost} showTags={true} />);
|
||||||
<ContentThumbnailTemplate
|
|
||||||
post={mockPost}
|
|
||||||
variant="vertical"
|
|
||||||
showTags={true}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
|
|
||||||
expect(screen.getByText("test")).toBeInTheDocument();
|
expect(screen.getByText("test")).toBeInTheDocument();
|
||||||
expect(screen.getByText("blog")).toBeInTheDocument();
|
expect(screen.getByText("blog")).toBeInTheDocument();
|
||||||
});
|
});
|
||||||
|
|
||||||
it("should hide tags when showTags is false", () => {
|
it("should hide tags when showTags is false", () => {
|
||||||
render(
|
render(<ContentThumbnailTemplate post={mockPost} showTags={false} />);
|
||||||
<ContentThumbnailTemplate
|
|
||||||
post={mockPost}
|
|
||||||
variant="vertical"
|
|
||||||
showTags={false}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
|
|
||||||
expect(screen.queryByText("test")).not.toBeInTheDocument();
|
expect(screen.queryByText("test")).not.toBeInTheDocument();
|
||||||
expect(screen.queryByText("blog")).not.toBeInTheDocument();
|
expect(screen.queryByText("blog")).not.toBeInTheDocument();
|
||||||
});
|
});
|
||||||
|
|
||||||
it("should display reading time when showReadingTime is true", () => {
|
|
||||||
render(
|
|
||||||
<ContentThumbnailTemplate
|
|
||||||
post={mockPost}
|
|
||||||
variant="vertical"
|
|
||||||
showReadingTime={true}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
|
|
||||||
expect(screen.getByText(/3 min read/)).toBeInTheDocument();
|
|
||||||
});
|
|
||||||
|
|
||||||
it("should hide reading time when showReadingTime is false", () => {
|
|
||||||
render(
|
|
||||||
<ContentThumbnailTemplate
|
|
||||||
post={mockPost}
|
|
||||||
variant="vertical"
|
|
||||||
showReadingTime={false}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
|
|
||||||
expect(screen.queryByText(/min read/)).not.toBeInTheDocument();
|
|
||||||
});
|
|
||||||
|
|
||||||
it("should display author and date", () => {
|
it("should display author and date", () => {
|
||||||
render(<ContentThumbnailTemplate post={mockPost} variant="vertical" />);
|
render(<ContentThumbnailTemplate post={mockPost} />);
|
||||||
|
|
||||||
expect(screen.getByText("Test Author")).toBeInTheDocument();
|
expect(screen.getByText("Test Author")).toBeInTheDocument();
|
||||||
// Check for "Month Year" format (e.g., "April 2025")
|
// Check for "Month Year" format (e.g., "April 2025")
|
||||||
@@ -118,7 +79,7 @@ describe("ContentThumbnailTemplate", () => {
|
|||||||
|
|
||||||
describe("Horizontal Variant", () => {
|
describe("Horizontal Variant", () => {
|
||||||
it("should render horizontal variant", () => {
|
it("should render horizontal variant", () => {
|
||||||
render(<ContentThumbnailTemplate post={mockPost} variant="horizontal" />);
|
render(<ContentThumbnailTemplate post={mockPost} />);
|
||||||
|
|
||||||
const container = screen.getByRole("link");
|
const container = screen.getByRole("link");
|
||||||
expect(container).toBeInTheDocument();
|
expect(container).toBeInTheDocument();
|
||||||
@@ -129,7 +90,7 @@ describe("ContentThumbnailTemplate", () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it("should display post information in horizontal layout", () => {
|
it("should display post information in horizontal layout", () => {
|
||||||
render(<ContentThumbnailTemplate post={mockPost} variant="horizontal" />);
|
render(<ContentThumbnailTemplate post={mockPost} />);
|
||||||
|
|
||||||
expect(screen.getByText("Test Blog Post Title")).toBeInTheDocument();
|
expect(screen.getByText("Test Blog Post Title")).toBeInTheDocument();
|
||||||
expect(
|
expect(
|
||||||
@@ -142,11 +103,7 @@ describe("ContentThumbnailTemplate", () => {
|
|||||||
describe("Props and Customization", () => {
|
describe("Props and Customization", () => {
|
||||||
it("should apply custom className", () => {
|
it("should apply custom className", () => {
|
||||||
render(
|
render(
|
||||||
<ContentThumbnailTemplate
|
<ContentThumbnailTemplate post={mockPost} className="custom-class" />
|
||||||
post={mockPost}
|
|
||||||
variant="vertical"
|
|
||||||
className="custom-class"
|
|
||||||
/>
|
|
||||||
);
|
);
|
||||||
|
|
||||||
const container = screen.getByRole("link");
|
const container = screen.getByRole("link");
|
||||||
@@ -154,7 +111,7 @@ describe("ContentThumbnailTemplate", () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it("should generate correct link href", () => {
|
it("should generate correct link href", () => {
|
||||||
render(<ContentThumbnailTemplate post={mockPost} variant="vertical" />);
|
render(<ContentThumbnailTemplate post={mockPost} />);
|
||||||
|
|
||||||
const link = screen.getByRole("link");
|
const link = screen.getByRole("link");
|
||||||
expect(link).toHaveAttribute("href", "/blog/test-post");
|
expect(link).toHaveAttribute("href", "/blog/test-post");
|
||||||
@@ -169,9 +126,7 @@ describe("ContentThumbnailTemplate", () => {
|
|||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
render(
|
render(<ContentThumbnailTemplate post={postWithoutTags} />);
|
||||||
<ContentThumbnailTemplate post={postWithoutTags} variant="vertical" />
|
|
||||||
);
|
|
||||||
|
|
||||||
// Should still render without errors
|
// Should still render without errors
|
||||||
expect(screen.getByText("Test Blog Post Title")).toBeInTheDocument();
|
expect(screen.getByText("Test Blog Post Title")).toBeInTheDocument();
|
||||||
@@ -186,9 +141,7 @@ describe("ContentThumbnailTemplate", () => {
|
|||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
render(
|
render(<ContentThumbnailTemplate post={postWithoutImages} />);
|
||||||
<ContentThumbnailTemplate post={postWithoutImages} variant="vertical" />
|
|
||||||
);
|
|
||||||
|
|
||||||
// Should still render without errors
|
// Should still render without errors
|
||||||
expect(screen.getByText("Test Blog Post Title")).toBeInTheDocument();
|
expect(screen.getByText("Test Blog Post Title")).toBeInTheDocument();
|
||||||
@@ -208,11 +161,5 @@ describe("ContentThumbnailTemplate", () => {
|
|||||||
|
|
||||||
expect(screen.getByText("test")).toBeInTheDocument();
|
expect(screen.getByText("test")).toBeInTheDocument();
|
||||||
});
|
});
|
||||||
|
|
||||||
it("should show reading time by default", () => {
|
|
||||||
render(<ContentThumbnailTemplate post={mockPost} />);
|
|
||||||
|
|
||||||
expect(screen.getByText(/min read/)).toBeInTheDocument();
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -3,7 +3,6 @@ import {
|
|||||||
contentProcessor,
|
contentProcessor,
|
||||||
getAllPosts,
|
getAllPosts,
|
||||||
getBlogStats,
|
getBlogStats,
|
||||||
getAllTags,
|
|
||||||
} from "../../lib/contentProcessor.js";
|
} from "../../lib/contentProcessor.js";
|
||||||
|
|
||||||
describe("Content Processor", () => {
|
describe("Content Processor", () => {
|
||||||
@@ -25,14 +24,6 @@ describe("Content Processor", () => {
|
|||||||
it("should extract blog statistics", () => {
|
it("should extract blog statistics", () => {
|
||||||
const stats = getBlogStats();
|
const stats = getBlogStats();
|
||||||
expect(stats.totalPosts).toBeGreaterThan(0);
|
expect(stats.totalPosts).toBeGreaterThan(0);
|
||||||
expect(stats.totalTags).toBeGreaterThan(0);
|
|
||||||
expect(stats.totalWords).toBeGreaterThan(0);
|
|
||||||
});
|
|
||||||
|
|
||||||
it("should extract tags from posts", () => {
|
|
||||||
const tags = getAllTags();
|
|
||||||
expect(Array.isArray(tags)).toBe(true);
|
|
||||||
expect(tags.length).toBeGreaterThan(0);
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -44,8 +35,6 @@ describe("Content Processor", () => {
|
|||||||
expect(firstPost).toHaveProperty("frontmatter");
|
expect(firstPost).toHaveProperty("frontmatter");
|
||||||
expect(firstPost).toHaveProperty("content");
|
expect(firstPost).toHaveProperty("content");
|
||||||
expect(firstPost).toHaveProperty("htmlContent");
|
expect(firstPost).toHaveProperty("htmlContent");
|
||||||
expect(firstPost).toHaveProperty("wordCount");
|
|
||||||
expect(firstPost).toHaveProperty("readingTime");
|
|
||||||
expect(firstPost).toHaveProperty("headings");
|
expect(firstPost).toHaveProperty("headings");
|
||||||
expect(firstPost).toHaveProperty("tableOfContents");
|
expect(firstPost).toHaveProperty("tableOfContents");
|
||||||
});
|
});
|
||||||
@@ -58,16 +47,6 @@ describe("Content Processor", () => {
|
|||||||
expect(typeof firstPost.slug).toBe("string");
|
expect(typeof firstPost.slug).toBe("string");
|
||||||
expect(firstPost.slug.length).toBeGreaterThan(0);
|
expect(firstPost.slug.length).toBeGreaterThan(0);
|
||||||
});
|
});
|
||||||
|
|
||||||
it("should calculate word count and reading time", () => {
|
|
||||||
const posts = getAllPosts();
|
|
||||||
const firstPost = posts[0];
|
|
||||||
|
|
||||||
expect(firstPost.wordCount).toBeGreaterThan(0);
|
|
||||||
expect(firstPost.readingTime).toBeGreaterThan(0);
|
|
||||||
expect(typeof firstPost.wordCount).toBe("number");
|
|
||||||
expect(typeof firstPost.readingTime).toBe("number");
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
|
|
||||||
describe("Content Enhancement", () => {
|
describe("Content Enhancement", () => {
|
||||||
|
|||||||
@@ -14,7 +14,6 @@ describe("Blog Post Validation", () => {
|
|||||||
"This is a test description that meets the minimum length requirement",
|
"This is a test description that meets the minimum length requirement",
|
||||||
author: "Test Author",
|
author: "Test Author",
|
||||||
date: "2025-04-15",
|
date: "2025-04-15",
|
||||||
tags: ["test", "blog"],
|
|
||||||
related: ["post-1", "post-2"],
|
related: ["post-1", "post-2"],
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -27,7 +26,6 @@ describe("Blog Post Validation", () => {
|
|||||||
const invalidPost = {
|
const invalidPost = {
|
||||||
title: "Test Title",
|
title: "Test Title",
|
||||||
// Missing description, author, date
|
// Missing description, author, date
|
||||||
tags: ["test"],
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const result = validateBlogPost(invalidPost);
|
const result = validateBlogPost(invalidPost);
|
||||||
@@ -64,41 +62,6 @@ describe("Blog Post Validation", () => {
|
|||||||
expect(result.isValid).toBe(false);
|
expect(result.isValid).toBe(false);
|
||||||
expect(result.errors).toContain("Field date format is invalid");
|
expect(result.errors).toContain("Field date format is invalid");
|
||||||
});
|
});
|
||||||
|
|
||||||
it("should validate tags array", () => {
|
|
||||||
const invalidTags = {
|
|
||||||
title: "Test Title",
|
|
||||||
description:
|
|
||||||
"This is a test description that meets the minimum length requirement",
|
|
||||||
author: "Test Author",
|
|
||||||
date: "2025-04-15",
|
|
||||||
tags: "not-an-array", // Should be array
|
|
||||||
};
|
|
||||||
|
|
||||||
const result = validateBlogPost(invalidTags);
|
|
||||||
expect(result.isValid).toBe(false);
|
|
||||||
expect(result.errors).toContain("Field tags must be an array");
|
|
||||||
});
|
|
||||||
|
|
||||||
it("should validate tag item lengths", () => {
|
|
||||||
const invalidTagItems = {
|
|
||||||
title: "Test Title",
|
|
||||||
description:
|
|
||||||
"This is a test description that meets the minimum length requirement",
|
|
||||||
author: "Test Author",
|
|
||||||
date: "2025-04-15",
|
|
||||||
tags: ["", "very-long-tag-name-that-exceeds-maximum-length"],
|
|
||||||
};
|
|
||||||
|
|
||||||
const result = validateBlogPost(invalidTagItems);
|
|
||||||
expect(result.isValid).toBe(false);
|
|
||||||
expect(result.errors).toContain(
|
|
||||||
"Item 0 in tags must be at least 1 characters"
|
|
||||||
);
|
|
||||||
expect(result.errors).toContain(
|
|
||||||
"Item 1 in tags must be no more than 20 characters"
|
|
||||||
);
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
|
|
||||||
describe("sanitizeBlogPost", () => {
|
describe("sanitizeBlogPost", () => {
|
||||||
@@ -108,7 +71,6 @@ describe("Blog Post Validation", () => {
|
|||||||
description: "Test description",
|
description: "Test description",
|
||||||
author: "Test Author",
|
author: "Test Author",
|
||||||
date: "2025-04-15",
|
date: "2025-04-15",
|
||||||
tags: ["test"],
|
|
||||||
related: ["post-1"],
|
related: ["post-1"],
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -122,11 +84,10 @@ describe("Blog Post Validation", () => {
|
|||||||
description: "Test description",
|
description: "Test description",
|
||||||
author: "Test Author",
|
author: "Test Author",
|
||||||
date: "2025-04-15",
|
date: "2025-04-15",
|
||||||
// Missing tags and related
|
// Missing related
|
||||||
};
|
};
|
||||||
|
|
||||||
const sanitized = sanitizeBlogPost(post);
|
const sanitized = sanitizeBlogPost(post);
|
||||||
expect(sanitized.tags).toEqual([]);
|
|
||||||
expect(sanitized.related).toEqual([]);
|
expect(sanitized.related).toEqual([]);
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -136,12 +97,10 @@ describe("Blog Post Validation", () => {
|
|||||||
description: "Test description",
|
description: "Test description",
|
||||||
author: "Test Author",
|
author: "Test Author",
|
||||||
date: "2025-04-15",
|
date: "2025-04-15",
|
||||||
tags: ["custom-tag"],
|
|
||||||
related: ["custom-post"],
|
related: ["custom-post"],
|
||||||
};
|
};
|
||||||
|
|
||||||
const sanitized = sanitizeBlogPost(post);
|
const sanitized = sanitizeBlogPost(post);
|
||||||
expect(sanitized.tags).toEqual(["custom-tag"]);
|
|
||||||
expect(sanitized.related).toEqual(["custom-post"]);
|
expect(sanitized.related).toEqual(["custom-post"]);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
@@ -152,7 +111,6 @@ describe("Blog Post Validation", () => {
|
|||||||
expect(BLOG_POST_SCHEMA).toHaveProperty("description");
|
expect(BLOG_POST_SCHEMA).toHaveProperty("description");
|
||||||
expect(BLOG_POST_SCHEMA).toHaveProperty("author");
|
expect(BLOG_POST_SCHEMA).toHaveProperty("author");
|
||||||
expect(BLOG_POST_SCHEMA).toHaveProperty("date");
|
expect(BLOG_POST_SCHEMA).toHaveProperty("date");
|
||||||
expect(BLOG_POST_SCHEMA).toHaveProperty("tags");
|
|
||||||
expect(BLOG_POST_SCHEMA).toHaveProperty("related");
|
expect(BLOG_POST_SCHEMA).toHaveProperty("related");
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -161,7 +119,6 @@ describe("Blog Post Validation", () => {
|
|||||||
expect(BLOG_POST_SCHEMA.description.required).toBe(true);
|
expect(BLOG_POST_SCHEMA.description.required).toBe(true);
|
||||||
expect(BLOG_POST_SCHEMA.author.required).toBe(true);
|
expect(BLOG_POST_SCHEMA.author.required).toBe(true);
|
||||||
expect(BLOG_POST_SCHEMA.date.required).toBe(true);
|
expect(BLOG_POST_SCHEMA.date.required).toBe(true);
|
||||||
expect(BLOG_POST_SCHEMA.tags.required).toBe(false);
|
|
||||||
expect(BLOG_POST_SCHEMA.related.required).toBe(false);
|
expect(BLOG_POST_SCHEMA.related.required).toBe(false);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
Reference in New Issue
Block a user