test(blog): Add basic tests for blog routes. (#11564)

This commit is contained in:
Sébastien Lorber 2025-11-20 16:04:06 +01:00 committed by GitHub
parent 66dbc7da39
commit 366b4a1b26
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
4 changed files with 966 additions and 0 deletions

View File

@ -0,0 +1,637 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`buildAllRoutes works for realistic blog post 2`] = `
[
{
"component": "@theme/BlogPostPage",
"context": {
"blogMetadata": "@aliased/data/blogMetadata-default.json",
},
"exact": true,
"metadata": {
"lastUpdatedAt": undefined,
"sourceFilePath": "blog/post1.md",
},
"modules": {
"content": "@site/blog/post1.md",
"sidebar": "@aliased/data/blog-post-list-prop-default.json",
},
"path": "/blog/post1",
},
{
"component": "@theme/BlogPostPage",
"context": {
"blogMetadata": "@aliased/data/blogMetadata-default.json",
},
"exact": true,
"metadata": {
"lastUpdatedAt": undefined,
"sourceFilePath": "blog/post2.md",
},
"modules": {
"content": "@site/blog/post2.md",
"sidebar": "@aliased/data/blog-post-list-prop-default.json",
},
"path": "/blog/post2",
},
{
"component": "@theme/BlogPostPage",
"context": {
"blogMetadata": "@aliased/data/blogMetadata-default.json",
},
"exact": true,
"metadata": {
"lastUpdatedAt": undefined,
"sourceFilePath": "blog/post3.md",
},
"modules": {
"content": "@site/blog/post3.md",
"sidebar": "@aliased/data/blog-post-list-prop-default.json",
},
"path": "/blog/post3",
},
{
"component": "@theme/BlogPostPage",
"context": {
"blogMetadata": "@aliased/data/blogMetadata-default.json",
},
"exact": true,
"metadata": {
"lastUpdatedAt": undefined,
"sourceFilePath": "blog/post4.md",
},
"modules": {
"content": "@site/blog/post4.md",
"sidebar": "@aliased/data/blog-post-list-prop-default.json",
},
"path": "/blog/post4",
},
{
"component": "@theme/BlogPostPage",
"context": {
"blogMetadata": "@aliased/data/blogMetadata-default.json",
},
"exact": true,
"metadata": {
"lastUpdatedAt": undefined,
"sourceFilePath": "blog/post5.md",
},
"modules": {
"content": "@site/blog/post5.md",
"sidebar": "@aliased/data/blog-post-list-prop-default.json",
},
"path": "/blog/post5",
},
{
"component": "@theme/BlogPostPage",
"context": {
"blogMetadata": "@aliased/data/blogMetadata-default.json",
},
"exact": true,
"metadata": {
"lastUpdatedAt": undefined,
"sourceFilePath": "blog/post6.md",
},
"modules": {
"content": "@site/blog/post6.md",
"sidebar": "@aliased/data/blog-post-list-prop-default.json",
},
"path": "/blog/post6",
},
{
"component": "@theme/BlogListPage",
"exact": true,
"modules": {
"items": [
{
"content": {
"__import": true,
"path": "@site/blog/post1.md",
"query": {
"truncated": true,
},
},
},
{
"content": {
"__import": true,
"path": "@site/blog/post2.md",
"query": {
"truncated": true,
},
},
},
],
"sidebar": "@aliased/data/blog-post-list-prop-default.json",
},
"path": "/blog",
"props": {
"metadata": {
"blogDescription": "Custom blog description",
"blogTitle": "Custom blog title",
"nextPage": "/blog/page/2",
"page": 1,
"permalink": "/blog",
"postsPerPage": 2,
"previousPage": undefined,
"totalCount": 5,
"totalPages": 3,
},
},
},
{
"component": "@theme/BlogListPage",
"exact": true,
"modules": {
"items": [
{
"content": {
"__import": true,
"path": "@site/blog/post4.md",
"query": {
"truncated": true,
},
},
},
{
"content": {
"__import": true,
"path": "@site/blog/post5.md",
"query": {
"truncated": true,
},
},
},
],
"sidebar": "@aliased/data/blog-post-list-prop-default.json",
},
"path": "/blog/page/2",
"props": {
"metadata": {
"blogDescription": "Custom blog description",
"blogTitle": "Custom blog title",
"nextPage": "/blog/page/3",
"page": 2,
"permalink": "/blog/page/2",
"postsPerPage": 2,
"previousPage": "/blog",
"totalCount": 5,
"totalPages": 3,
},
},
},
{
"component": "@theme/BlogListPage",
"exact": true,
"modules": {
"items": [
{
"content": {
"__import": true,
"path": "@site/blog/post6.md",
"query": {
"truncated": true,
},
},
},
],
"sidebar": "@aliased/data/blog-post-list-prop-default.json",
},
"path": "/blog/page/3",
"props": {
"metadata": {
"blogDescription": "Custom blog description",
"blogTitle": "Custom blog title",
"nextPage": undefined,
"page": 3,
"permalink": "/blog/page/3",
"postsPerPage": 2,
"previousPage": "/blog/page/2",
"totalCount": 5,
"totalPages": 3,
},
},
},
{
"component": "@theme/BlogArchivePage",
"exact": true,
"path": "/blog/archive",
"props": {
"archive": {
"blogPosts": [
{
"content": "Content for post1",
"id": "post1",
"metadata": {
"authors": [
{
"key": "author1",
},
],
"date": 2020-01-01T00:00:00.000Z,
"description": "Description for post1",
"frontMatter": {},
"permalink": "/blog/post1",
"readingTime": 2,
"source": "@site/blog/post1.md",
"tags": [],
"title": "Title for post1",
},
},
{
"content": "Content for post2",
"id": "post2",
"metadata": {
"authors": [
{
"key": "author1",
},
],
"date": 2020-01-01T00:00:00.000Z,
"description": "Description for post2",
"frontMatter": {},
"permalink": "/blog/post2",
"readingTime": 2,
"source": "@site/blog/post2.md",
"tags": [],
"title": "Title for post2",
},
},
{
"content": "Content for post4",
"id": "post4",
"metadata": {
"authors": [
{
"key": "author1",
},
{
"key": "author2",
},
],
"date": 2020-01-01T00:00:00.000Z,
"description": "Description for post4",
"frontMatter": {},
"permalink": "/blog/post4",
"readingTime": 2,
"source": "@site/blog/post4.md",
"tags": [],
"title": "Title for post4",
},
},
{
"content": "Content for post5",
"id": "post5",
"metadata": {
"authors": [
{
"key": "author2",
},
{
"key": "author3",
},
],
"date": 2020-01-01T00:00:00.000Z,
"description": "Description for post5",
"frontMatter": {},
"permalink": "/blog/post5",
"readingTime": 2,
"source": "@site/blog/post5.md",
"tags": [],
"title": "Title for post5",
},
},
{
"content": "Content for post6",
"id": "post6",
"metadata": {
"authors": [],
"date": 2020-01-01T00:00:00.000Z,
"description": "Description for post6",
"frontMatter": {},
"permalink": "/blog/post6",
"readingTime": 2,
"source": "@site/blog/post6.md",
"tags": [],
"title": "Title for post6",
},
},
],
},
},
},
{
"component": "@theme/Blog/Pages/BlogAuthorsListPage",
"context": {
"blogMetadata": "@aliased/data/blogMetadata-default.json",
},
"exact": true,
"modules": {
"sidebar": "@aliased/data/blog-post-list-prop-default.json",
},
"path": "/blog/authors",
"props": {
"authors": [
{
"count": 3,
"key": "author1",
"name": "Author 1",
"page": {
"permalink": "/blog/authors/author1",
},
},
{
"count": 2,
"key": "author2",
"name": "Author 2",
"page": null,
},
{
"count": 1,
"key": "author3",
"name": "Author 3",
"page": {
"permalink": "/blog/authors/author3",
},
},
],
},
},
{
"component": "@theme/Blog/Pages/BlogAuthorsPostsPage",
"context": {
"blogMetadata": "@aliased/data/blogMetadata-default.json",
},
"exact": true,
"modules": {
"items": [
{
"content": {
"__import": true,
"path": "@site/blog/post1.md",
"query": {
"truncated": true,
},
},
},
{
"content": {
"__import": true,
"path": "@site/blog/post2.md",
"query": {
"truncated": true,
},
},
},
],
"sidebar": "@aliased/data/blog-post-list-prop-default.json",
},
"path": "/blog/authors/author1",
"props": {
"author": {
"count": 3,
"key": "author1",
"name": "Author 1",
"page": {
"permalink": "/blog/authors/author1",
},
},
"listMetadata": {
"blogDescription": "Custom blog description",
"blogTitle": "Custom blog title",
"nextPage": "/blog/authors/author1/authors/2",
"page": 1,
"permalink": "/blog/authors/author1",
"postsPerPage": 2,
"previousPage": undefined,
"totalCount": 3,
"totalPages": 2,
},
},
},
{
"component": "@theme/Blog/Pages/BlogAuthorsPostsPage",
"context": {
"blogMetadata": "@aliased/data/blogMetadata-default.json",
},
"exact": true,
"modules": {
"items": [
{
"content": {
"__import": true,
"path": "@site/blog/post4.md",
"query": {
"truncated": true,
},
},
},
],
"sidebar": "@aliased/data/blog-post-list-prop-default.json",
},
"path": "/blog/authors/author1/authors/2",
"props": {
"author": {
"count": 3,
"key": "author1",
"name": "Author 1",
"page": {
"permalink": "/blog/authors/author1",
},
},
"listMetadata": {
"blogDescription": "Custom blog description",
"blogTitle": "Custom blog title",
"nextPage": undefined,
"page": 2,
"permalink": "/blog/authors/author1/authors/2",
"postsPerPage": 2,
"previousPage": "/blog/authors/author1",
"totalCount": 3,
"totalPages": 2,
},
},
},
{
"component": "@theme/Blog/Pages/BlogAuthorsPostsPage",
"context": {
"blogMetadata": "@aliased/data/blogMetadata-default.json",
},
"exact": true,
"modules": {
"items": [
{
"content": {
"__import": true,
"path": "@site/blog/post5.md",
"query": {
"truncated": true,
},
},
},
],
"sidebar": "@aliased/data/blog-post-list-prop-default.json",
},
"path": "/blog/authors/author3",
"props": {
"author": {
"count": 1,
"key": "author3",
"name": "Author 3",
"page": {
"permalink": "/blog/authors/author3",
},
},
"listMetadata": {
"blogDescription": "Custom blog description",
"blogTitle": "Custom blog title",
"nextPage": undefined,
"page": 1,
"permalink": "/blog/authors/author3",
"postsPerPage": 2,
"previousPage": undefined,
"totalCount": 1,
"totalPages": 1,
},
},
},
]
`;
exports[`buildAllRoutes works for realistic blog post 3`] = `
{
"blog-post-list-prop-default.json": {
"items": [
{
"date": 2020-01-01T00:00:00.000Z,
"permalink": "/blog/post1",
"title": "Title for post1",
"unlisted": undefined,
},
{
"date": 2020-01-01T00:00:00.000Z,
"permalink": "/blog/post2",
"title": "Title for post2",
"unlisted": undefined,
},
{
"date": 2020-01-01T00:00:00.000Z,
"permalink": "/blog/post3",
"title": "Title for post3",
"unlisted": true,
},
{
"date": 2020-01-01T00:00:00.000Z,
"permalink": "/blog/post4",
"title": "Title for post4",
"unlisted": undefined,
},
{
"date": 2020-01-01T00:00:00.000Z,
"permalink": "/blog/post5",
"title": "Title for post5",
"unlisted": undefined,
},
],
"title": "Custom blog sidebar title",
},
"blogMetadata-default.json": {
"authorsListPath": "/blog/authors",
"blogBasePath": "/blog",
"blogTitle": "Custom blog title",
},
"site-blog-post-1-md-235.json": {
"authors": [
{
"key": "author1",
},
],
"date": 2020-01-01T00:00:00.000Z,
"description": "Description for post1",
"frontMatter": {},
"permalink": "/blog/post1",
"readingTime": 2,
"source": "@site/blog/post1.md",
"tags": [],
"title": "Title for post1",
},
"site-blog-post-2-md-b42.json": {
"authors": [
{
"key": "author1",
},
],
"date": 2020-01-01T00:00:00.000Z,
"description": "Description for post2",
"frontMatter": {},
"permalink": "/blog/post2",
"readingTime": 2,
"source": "@site/blog/post2.md",
"tags": [],
"title": "Title for post2",
},
"site-blog-post-3-md-3b7.json": {
"authors": [
{
"key": "author3",
},
],
"date": 2020-01-01T00:00:00.000Z,
"description": "Description for post3",
"frontMatter": {},
"permalink": "/blog/post3",
"readingTime": 2,
"source": "@site/blog/post3.md",
"tags": [],
"title": "Title for post3",
"unlisted": true,
},
"site-blog-post-4-md-15a.json": {
"authors": [
{
"key": "author1",
},
{
"key": "author2",
},
],
"date": 2020-01-01T00:00:00.000Z,
"description": "Description for post4",
"frontMatter": {},
"permalink": "/blog/post4",
"readingTime": 2,
"source": "@site/blog/post4.md",
"tags": [],
"title": "Title for post4",
},
"site-blog-post-5-md-274.json": {
"authors": [
{
"key": "author2",
},
{
"key": "author3",
},
],
"date": 2020-01-01T00:00:00.000Z,
"description": "Description for post5",
"frontMatter": {},
"permalink": "/blog/post5",
"readingTime": 2,
"source": "@site/blog/post5.md",
"tags": [],
"title": "Title for post5",
},
"site-blog-post-6-md-3ca.json": {
"authors": [],
"date": 2020-01-01T00:00:00.000Z,
"description": "Description for post6",
"frontMatter": {},
"permalink": "/blog/post6",
"readingTime": 2,
"source": "@site/blog/post6.md",
"tags": [],
"title": "Title for post6",
},
}
`;

View File

@ -0,0 +1,324 @@
/**
* Copyright (c) Facebook, Inc. and its affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
import * as _ from 'lodash';
import {fromPartial} from '@total-typescript/shoehorn';
import {buildAllRoutes} from '../routes';
import {DEFAULT_OPTIONS} from '../options';
import type {PartialDeep} from '@total-typescript/shoehorn';
import type {BlogPost, BlogPostMetadata} from '@docusaurus/plugin-content-blog';
type Params = Parameters<typeof buildAllRoutes>[0];
async function testBuildAllRoutes(overrides: PartialDeep<Params> = {}) {
const createData = jest.fn(
async (name: string, _data: unknown) => `/data/${name}`,
);
const params: Params = fromPartial<Params>({
baseUrl: '/',
aliasedSource: (str: string) => `@aliased${str}`,
...overrides,
content: {
blogTitle: 'Blog Title',
blogDescription: 'Blog Description',
blogSidebarTitle: 'Blog Sidebar Title',
authorsMap: {},
blogTagsListPath: '',
blogTags: {},
blogPosts: [],
...overrides?.content,
},
options: {
...DEFAULT_OPTIONS,
...overrides?.options,
},
actions: {
createData,
...overrides?.actions,
},
});
const routes = await buildAllRoutes(params);
const data = Object.fromEntries(
createData.mock.calls.map((call) => [call[0], call[1]]),
);
function getRouteByPath(path: string) {
const route = routes.find((r) => r.path === path);
if (!route) {
throw new Error(`Route not found for path: ${path}`);
}
return route;
}
function getRoutesByComponent(component: string) {
return routes.filter((r) => r.component === component);
}
return {routes, data, utils: {getRouteByPath, getRoutesByComponent}};
}
function blogPost(overrides: PartialDeep<BlogPost> = {}): BlogPost {
const id = overrides.id ?? 'blog-post';
return fromPartial<BlogPost>({
id,
content: `Content for ${id}`,
...overrides,
metadata: fromPartial<BlogPostMetadata>({
title: `Title for ${id}`,
description: `Description for ${id}`,
permalink: `/blog/${id}`,
source: `@site/blog/${id}.md`,
date: new Date('2020-01-01'),
tags: [],
readingTime: 2,
authors: [],
frontMatter: {
...overrides?.metadata?.frontMatter,
},
...overrides?.metadata,
}),
});
}
describe('buildAllRoutes', () => {
it('works for empty blog', async () => {
const {routes, data} = await testBuildAllRoutes({
content: {
blogPosts: [],
},
});
expect(routes).toMatchInlineSnapshot(`
[
{
"component": "@theme/BlogListPage",
"exact": true,
"modules": {
"items": [],
"sidebar": "@aliased/data/blog-post-list-prop-default.json",
},
"path": "/blog",
"props": {
"metadata": {
"blogDescription": "Blog Description",
"blogTitle": "Blog Title",
"nextPage": undefined,
"page": 1,
"permalink": "/blog",
"postsPerPage": 10,
"previousPage": undefined,
"totalCount": 0,
"totalPages": 1,
},
},
},
]
`);
expect(data).toMatchInlineSnapshot(`
{
"blog-post-list-prop-default.json": {
"items": [],
"title": "Blog Sidebar Title",
},
"blogMetadata-default.json": {
"authorsListPath": "/blog/authors",
"blogBasePath": "/blog",
"blogTitle": "Blog Title",
},
}
`);
});
it('works for single blog post', async () => {
const {routes, data} = await testBuildAllRoutes({
content: {
blogPosts: [blogPost()],
},
});
expect(routes).toMatchInlineSnapshot(`
[
{
"component": "@theme/BlogPostPage",
"context": {
"blogMetadata": "@aliased/data/blogMetadata-default.json",
},
"exact": true,
"metadata": {
"lastUpdatedAt": undefined,
"sourceFilePath": "blog/blog-post.md",
},
"modules": {
"content": "@site/blog/blog-post.md",
"sidebar": "@aliased/data/blog-post-list-prop-default.json",
},
"path": "/blog/blog-post",
},
{
"component": "@theme/BlogListPage",
"exact": true,
"modules": {
"items": [
{
"content": {
"__import": true,
"path": "@site/blog/blog-post.md",
"query": {
"truncated": true,
},
},
},
],
"sidebar": "@aliased/data/blog-post-list-prop-default.json",
},
"path": "/blog",
"props": {
"metadata": {
"blogDescription": "Blog Description",
"blogTitle": "Blog Title",
"nextPage": undefined,
"page": 1,
"permalink": "/blog",
"postsPerPage": 10,
"previousPage": undefined,
"totalCount": 1,
"totalPages": 1,
},
},
},
{
"component": "@theme/BlogArchivePage",
"exact": true,
"path": "/blog/archive",
"props": {
"archive": {
"blogPosts": [
{
"content": "Content for blog-post",
"id": "blog-post",
"metadata": {
"authors": [],
"date": 2020-01-01T00:00:00.000Z,
"description": "Description for blog-post",
"frontMatter": {},
"permalink": "/blog/blog-post",
"readingTime": 2,
"source": "@site/blog/blog-post.md",
"tags": [],
"title": "Title for blog-post",
},
},
],
},
},
},
]
`);
expect(data).toMatchInlineSnapshot(`
{
"blog-post-list-prop-default.json": {
"items": [
{
"date": 2020-01-01T00:00:00.000Z,
"permalink": "/blog/blog-post",
"title": "Title for blog-post",
"unlisted": undefined,
},
],
"title": "Blog Sidebar Title",
},
"blogMetadata-default.json": {
"authorsListPath": "/blog/authors",
"blogBasePath": "/blog",
"blogTitle": "Blog Title",
},
"site-blog-blog-post-md-0d7.json": {
"authors": [],
"date": 2020-01-01T00:00:00.000Z,
"description": "Description for blog-post",
"frontMatter": {},
"permalink": "/blog/blog-post",
"readingTime": 2,
"source": "@site/blog/blog-post.md",
"tags": [],
"title": "Title for blog-post",
},
}
`);
});
it('works for realistic blog post', async () => {
const {routes, data} = await testBuildAllRoutes({
options: {
postsPerPage: 2,
},
content: {
blogTitle: 'Custom blog title',
blogDescription: 'Custom blog description',
blogSidebarTitle: 'Custom blog sidebar title',
blogPosts: [
blogPost({id: 'post1', metadata: {authors: [{key: 'author1'}]}}),
blogPost({id: 'post2', metadata: {authors: [{key: 'author1'}]}}),
blogPost({
id: 'post3',
metadata: {
authors: [{key: 'author3'}],
unlisted: true,
},
}),
blogPost({
id: 'post4',
metadata: {
authors: [{key: 'author1'}, {key: 'author2'}],
},
}),
blogPost({
id: 'post5',
metadata: {authors: [{key: 'author2'}, {key: 'author3'}]},
}),
blogPost({id: 'post6'}),
],
authorsMap: {
author1: {
key: 'author1',
name: 'Author 1',
page: {permalink: '/blog/authors/author1'},
},
author2: {
key: 'author2',
name: 'Author 2',
page: null,
},
author3: {
key: 'author3',
name: 'Author 3',
page: {permalink: '/blog/authors/author3'},
},
},
},
});
expect(_.countBy(routes, 'component')).toMatchInlineSnapshot(`
{
"@theme/Blog/Pages/BlogAuthorsListPage": 1,
"@theme/Blog/Pages/BlogAuthorsPostsPage": 3,
"@theme/BlogArchivePage": 1,
"@theme/BlogListPage": 3,
"@theme/BlogPostPage": 6,
}
`);
expect(routes).toMatchSnapshot();
expect(data).toMatchSnapshot();
});
});

View File

@ -401,6 +401,8 @@ export async function generateBlogPosts(
ignore: exclude,
});
// TODO this should be done outside of this function
// directly in plugin loadContent()
const tagsFile = await getTagsFile({contentPaths, tags: options.tags});
async function doProcessBlogSourceFile(blogSourceFile: string) {

View File

@ -289,6 +289,9 @@ export default async function pluginContentBlog(
}
});
// TODO this is not the correct place to aggregate and paginate tags
// for reasons similar to https://github.com/facebook/docusaurus/pull/11562
// What we should do here is only read the tags file (similar to authors)
const blogTags: BlogTags = getBlogTags({
blogPosts,
postsPerPageOption,