docusaurus/packages/docusaurus-plugin-content-blog/src/__tests__/index.test.ts
ozaki 32fa7e0a8d feat(blog): add LastUpdateAuthor & LastUpdateTime (#9912)
Co-authored-by: OzakIOne <OzakIOne@users.noreply.github.com>
Co-authored-by: sebastien <lorber.sebastien@gmail.com>
2024-03-15 14:54:38 +01:00

667 lines
19 KiB
TypeScript

/**
* 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 {jest} from '@jest/globals';
import path from 'path';
import {normalizePluginOptions} from '@docusaurus/utils-validation';
import {
posixPath,
getFileCommitDate,
GIT_FALLBACK_LAST_UPDATE_DATE,
GIT_FALLBACK_LAST_UPDATE_AUTHOR,
} from '@docusaurus/utils';
import pluginContentBlog from '../index';
import {validateOptions} from '../options';
import type {
DocusaurusConfig,
LoadContext,
I18n,
Validate,
MarkdownConfig,
} from '@docusaurus/types';
import type {
BlogPost,
Options,
PluginOptions,
EditUrlFunction,
} from '@docusaurus/plugin-content-blog';
const markdown: MarkdownConfig = {
format: 'mdx',
mermaid: true,
mdx1Compat: {
comments: true,
headingIds: true,
admonitions: true,
},
parseFrontMatter: async (params) => {
// Reuse the default parser
const result = await params.defaultParseFrontMatter(params);
if (result.frontMatter.title === 'Complex Slug') {
result.frontMatter.custom_frontMatter = 'added by parseFrontMatter';
}
return result;
},
remarkRehypeOptions: undefined,
};
function findByTitle(
blogPosts: BlogPost[],
title: string,
): BlogPost | undefined {
return blogPosts.find((v) => v.metadata.title === title);
}
function getByTitle(blogPosts: BlogPost[], title: string): BlogPost {
const post = findByTitle(blogPosts, title);
if (!post) {
throw new Error(`can't find blog post with title ${title}.
Available blog post titles are:\n- ${blogPosts
.map((p) => p.metadata.title)
.join('\n- ')}`);
}
return post;
}
function getI18n(locale: string): I18n {
return {
currentLocale: locale,
locales: [locale],
defaultLocale: locale,
path: 'i18n',
localeConfigs: {
[locale]: {
calendar: 'gregory',
label: locale,
htmlLang: locale,
direction: 'ltr',
path: locale,
},
},
};
}
const DefaultI18N: I18n = getI18n('en');
const PluginPath = 'blog';
const BaseEditUrl = 'https://baseEditUrl.com/edit';
const getPlugin = async (
siteDir: string,
pluginOptions: Partial<PluginOptions> = {},
i18n: I18n = DefaultI18N,
) => {
const generatedFilesDir: string = path.resolve(siteDir, '.docusaurus');
const localizationDir = path.join(
siteDir,
i18n.path,
i18n.localeConfigs[i18n.currentLocale]!.path,
);
const siteConfig = {
title: 'Hello',
baseUrl: '/',
url: 'https://docusaurus.io',
markdown,
} as DocusaurusConfig;
return pluginContentBlog(
{
siteDir,
siteConfig,
generatedFilesDir,
i18n,
localizationDir,
} as LoadContext,
validateOptions({
validate: normalizePluginOptions as Validate<
Options | undefined,
PluginOptions
>,
options: {
path: PluginPath,
editUrl: BaseEditUrl,
...pluginOptions,
},
}),
);
};
const getBlogPosts = async (
siteDir: string,
pluginOptions: Partial<PluginOptions> = {},
i18n: I18n = DefaultI18N,
) => {
const plugin = await getPlugin(siteDir, pluginOptions, i18n);
const {blogPosts} = (await plugin.loadContent!())!;
return blogPosts;
};
const getBlogTags = async (
siteDir: string,
pluginOptions: Partial<PluginOptions> = {},
i18n: I18n = DefaultI18N,
) => {
const plugin = await getPlugin(siteDir, pluginOptions, i18n);
const {blogTags} = (await plugin.loadContent!())!;
return blogTags;
};
describe('blog plugin', () => {
it('getPathsToWatch returns right files', async () => {
const siteDir = path.join(__dirname, '__fixtures__', 'website');
const plugin = await getPlugin(siteDir);
const pathsToWatch = plugin.getPathsToWatch!();
const relativePathsToWatch = pathsToWatch.map((p) =>
posixPath(path.relative(siteDir, p)),
);
expect(relativePathsToWatch).toEqual([
'i18n/en/docusaurus-plugin-content-blog/authors.yml',
'i18n/en/docusaurus-plugin-content-blog/**/*.{md,mdx}',
'blog/**/*.{md,mdx}',
]);
});
it('builds a simple website', async () => {
const siteDir = path.join(__dirname, '__fixtures__', 'website');
const blogPosts = await getBlogPosts(siteDir);
expect({
...getByTitle(blogPosts, 'date-matter').metadata,
...{prevItem: undefined},
}).toEqual({
editUrl: `${BaseEditUrl}/blog/date-matter.md`,
permalink: '/blog/date-matter',
readingTime: 0.02,
source: path.posix.join('@site', PluginPath, 'date-matter.md'),
title: 'date-matter',
description: `date inside front matter`,
authors: [],
date: new Date('2019-01-01'),
frontMatter: {
date: new Date('2019-01-01'),
tags: ['date'],
},
prevItem: undefined,
tags: [
{
label: 'date',
permalink: '/blog/tags/date',
},
],
nextItem: {
permalink: '/blog/2018/12/14/Happy-First-Birthday-Slash',
title: 'Happy 1st Birthday Slash! (translated)',
},
hasTruncateMarker: false,
unlisted: false,
});
expect(
getByTitle(blogPosts, 'Happy 1st Birthday Slash! (translated)').metadata,
).toEqual({
editUrl: `${BaseEditUrl}/blog/2018-12-14-Happy-First-Birthday-Slash.md`,
permalink: '/blog/2018/12/14/Happy-First-Birthday-Slash',
readingTime: 0.015,
source: path.posix.join(
'@site',
path.posix.join('i18n', 'en', 'docusaurus-plugin-content-blog'),
'2018-12-14-Happy-First-Birthday-Slash.md',
),
title: 'Happy 1st Birthday Slash! (translated)',
description: `Happy birthday! (translated)`,
authors: [
{
name: 'Yangshun Tay (translated)',
},
{
email: 'lorber.sebastien@gmail.com',
key: 'slorber',
name: 'Sébastien Lorber (translated)',
title: 'Docusaurus maintainer (translated)',
},
],
date: new Date('2018-12-14'),
frontMatter: {
authors: [
{
name: 'Yangshun Tay (translated)',
},
'slorber',
],
title: 'Happy 1st Birthday Slash! (translated)',
},
tags: [],
prevItem: {
permalink: '/blog/date-matter',
title: 'date-matter',
},
hasTruncateMarker: false,
unlisted: false,
});
expect({
...getByTitle(blogPosts, 'Complex Slug').metadata,
...{prevItem: undefined},
}).toEqual({
editUrl: `${BaseEditUrl}/blog/complex-slug.md`,
permalink: '/blog/hey/my super path/héllô',
readingTime: 0.015,
source: path.posix.join('@site', PluginPath, 'complex-slug.md'),
title: 'Complex Slug',
description: `complex url slug`,
authors: [],
prevItem: undefined,
nextItem: {
permalink: '/blog/simple/slug',
title: 'Simple Slug',
},
date: new Date('2020-08-16'),
frontMatter: {
date: '2020/08/16',
slug: '/hey/my super path/héllô',
title: 'Complex Slug',
tags: ['date', 'complex'],
custom_frontMatter: 'added by parseFrontMatter',
},
tags: [
{
label: 'date',
permalink: '/blog/tags/date',
},
{
label: 'complex',
permalink: '/blog/tags/complex',
},
],
hasTruncateMarker: false,
unlisted: false,
});
expect({
...getByTitle(blogPosts, 'Simple Slug').metadata,
...{prevItem: undefined},
}).toEqual({
editUrl: `${BaseEditUrl}/blog/simple-slug.md`,
permalink: '/blog/simple/slug',
readingTime: 0.015,
source: path.posix.join('@site', PluginPath, 'simple-slug.md'),
title: 'Simple Slug',
description: `simple url slug`,
authors: [
{
name: 'Sébastien Lorber',
title: 'Docusaurus maintainer',
url: 'https://sebastienlorber.com',
imageURL: undefined,
},
],
prevItem: undefined,
nextItem: {
permalink: '/blog/draft',
title: 'draft',
},
date: new Date('2020-08-15'),
frontMatter: {
author: 'Sébastien Lorber',
author_title: 'Docusaurus maintainer',
author_url: 'https://sebastienlorber.com',
date: new Date('2020-08-15'),
slug: '/simple/slug',
title: 'Simple Slug',
},
tags: [],
hasTruncateMarker: false,
unlisted: false,
});
expect({
...getByTitle(blogPosts, 'some heading').metadata,
prevItem: undefined,
}).toEqual({
editUrl: `${BaseEditUrl}/blog/heading-as-title.md`,
permalink: '/blog/heading-as-title',
readingTime: 0,
source: path.posix.join('@site', PluginPath, 'heading-as-title.md'),
title: 'some heading',
description: '',
authors: [],
date: new Date('2019-01-02'),
frontMatter: {
date: new Date('2019-01-02'),
},
prevItem: undefined,
tags: [],
nextItem: {
permalink: '/blog/date-matter',
title: 'date-matter',
},
hasTruncateMarker: false,
unlisted: false,
});
});
it('handles edit URL with editLocalizedBlogs: true', async () => {
const siteDir = path.join(__dirname, '__fixtures__', 'website');
const blogPosts = await getBlogPosts(siteDir, {editLocalizedFiles: true});
const localizedBlogPost = blogPosts.find(
(v) => v.metadata.title === 'Happy 1st Birthday Slash! (translated)',
)!;
expect(localizedBlogPost.metadata.editUrl).toBe(
`${BaseEditUrl}/i18n/en/docusaurus-plugin-content-blog/2018-12-14-Happy-First-Birthday-Slash.md`,
);
});
it('handles edit URL with editUrl function', async () => {
const siteDir = path.join(__dirname, '__fixtures__', 'website');
const hardcodedEditUrl = 'hardcoded-edit-url';
const editUrlFunction: EditUrlFunction = jest.fn(() => hardcodedEditUrl);
const blogPosts = await getBlogPosts(siteDir, {editUrl: editUrlFunction});
blogPosts.forEach((blogPost) => {
expect(blogPost.metadata.editUrl).toEqual(hardcodedEditUrl);
});
expect(editUrlFunction).toHaveBeenCalledTimes(10);
expect(editUrlFunction).toHaveBeenCalledWith({
blogDirPath: 'blog',
blogPath: 'date-matter.md',
permalink: '/blog/date-matter',
locale: 'en',
});
expect(editUrlFunction).toHaveBeenCalledWith({
blogDirPath: 'blog',
blogPath: 'draft.md',
permalink: '/blog/draft',
locale: 'en',
});
expect(editUrlFunction).toHaveBeenCalledWith({
blogDirPath: 'blog',
blogPath: 'mdx-blog-post.mdx',
permalink: '/blog/mdx-blog-post',
locale: 'en',
});
expect(editUrlFunction).toHaveBeenCalledWith({
blogDirPath: 'blog',
blogPath: 'mdx-require-blog-post.mdx',
permalink: '/blog/mdx-require-blog-post',
locale: 'en',
});
expect(editUrlFunction).toHaveBeenCalledWith({
blogDirPath: 'blog',
blogPath: 'complex-slug.md',
permalink: '/blog/hey/my super path/héllô',
locale: 'en',
});
expect(editUrlFunction).toHaveBeenCalledWith({
blogDirPath: 'blog',
blogPath: 'simple-slug.md',
permalink: '/blog/simple/slug',
locale: 'en',
});
expect(editUrlFunction).toHaveBeenCalledWith({
blogDirPath: 'i18n/en/docusaurus-plugin-content-blog',
blogPath: '2018-12-14-Happy-First-Birthday-Slash.md',
permalink: '/blog/2018/12/14/Happy-First-Birthday-Slash',
locale: 'en',
});
expect(editUrlFunction).toHaveBeenCalledWith({
blogDirPath: 'blog',
blogPath: 'heading-as-title.md',
locale: 'en',
permalink: '/blog/heading-as-title',
});
});
it('excludes draft blog post from production build', async () => {
const originalEnv = process.env;
jest.resetModules();
process.env = {...originalEnv, NODE_ENV: 'production'};
const siteDir = path.join(__dirname, '__fixtures__', 'website');
const blogPosts = await getBlogPosts(siteDir);
expect(blogPosts.find((v) => v.metadata.title === 'draft')).toBeUndefined();
process.env = originalEnv;
});
it('creates blog post without date', async () => {
const siteDir = path.join(
__dirname,
'__fixtures__',
'website-blog-without-date',
);
const blogPosts = await getBlogPosts(siteDir);
const noDateSource = path.posix.join('@site', PluginPath, 'no date.md');
const noDateSourceFile = path.posix.join(siteDir, PluginPath, 'no date.md');
// We know the file exists and we know we have git
const result = await getFileCommitDate(noDateSourceFile, {age: 'oldest'});
const noDateSourceTime = result.date;
expect({
...getByTitle(blogPosts, 'no date').metadata,
...{prevItem: undefined},
}).toEqual({
editUrl: `${BaseEditUrl}/blog/no date.md`,
permalink: '/blog/no date',
readingTime: 0.01,
source: noDateSource,
title: 'no date',
description: `no date`,
authors: [],
date: noDateSourceTime,
frontMatter: {},
tags: [],
prevItem: undefined,
nextItem: undefined,
hasTruncateMarker: false,
unlisted: false,
});
});
it('can sort blog posts in ascending order', async () => {
const siteDir = path.join(__dirname, '__fixtures__', 'website');
const normalOrder = await getBlogPosts(siteDir);
const reversedOrder = await getBlogPosts(siteDir, {
sortPosts: 'ascending',
});
expect(normalOrder.reverse().map((x) => x.metadata.date)).toEqual(
reversedOrder.map((x) => x.metadata.date),
);
});
it('works with blog tags', async () => {
const siteDir = path.join(
__dirname,
'__fixtures__',
'website-blog-with-tags',
);
const blogTags = await getBlogTags(siteDir, {
postsPerPage: 2,
});
expect(Object.keys(blogTags)).toHaveLength(3);
expect(blogTags).toMatchSnapshot();
});
it('works on blog tags without pagination', async () => {
const siteDir = path.join(
__dirname,
'__fixtures__',
'website-blog-with-tags',
);
const blogTags = await getBlogTags(siteDir, {
postsPerPage: 'ALL',
});
expect(blogTags).toMatchSnapshot();
});
it('process blog posts load content', async () => {
const siteDir = path.join(
__dirname,
'__fixtures__',
'website-blog-with-tags',
);
const plugin = await getPlugin(
siteDir,
{
postsPerPage: 1,
processBlogPosts: async ({blogPosts}) =>
blogPosts.filter((blog) => blog.metadata.tags[0]?.label === 'tag1'),
},
DefaultI18N,
);
const {blogPosts, blogTags, blogListPaginated} =
(await plugin.loadContent!())!;
expect(blogListPaginated).toHaveLength(3);
expect(Object.keys(blogTags)).toHaveLength(2);
expect(blogTags).toMatchSnapshot();
expect(blogPosts).toHaveLength(3);
expect(blogPosts).toMatchSnapshot();
});
});
describe('last update', () => {
const siteDir = path.join(
__dirname,
'__fixtures__',
'website-blog-with-last-update',
);
const lastUpdateFor = (date: string) => new Date(date).getTime() / 1000;
it('author and time', async () => {
const plugin = await getPlugin(
siteDir,
{
showLastUpdateAuthor: true,
showLastUpdateTime: true,
},
DefaultI18N,
);
const {blogPosts} = (await plugin.loadContent!())!;
expect(blogPosts[0]?.metadata.lastUpdatedBy).toBe('seb');
expect(blogPosts[0]?.metadata.lastUpdatedAt).toBe(
GIT_FALLBACK_LAST_UPDATE_DATE,
);
expect(blogPosts[1]?.metadata.lastUpdatedBy).toBe(
GIT_FALLBACK_LAST_UPDATE_AUTHOR,
);
expect(blogPosts[1]?.metadata.lastUpdatedAt).toBe(
GIT_FALLBACK_LAST_UPDATE_DATE,
);
expect(blogPosts[2]?.metadata.lastUpdatedBy).toBe('seb');
expect(blogPosts[2]?.metadata.lastUpdatedAt).toBe(
lastUpdateFor('2021-01-01'),
);
expect(blogPosts[3]?.metadata.lastUpdatedBy).toBe(
GIT_FALLBACK_LAST_UPDATE_AUTHOR,
);
expect(blogPosts[3]?.metadata.lastUpdatedAt).toBe(
lastUpdateFor('2021-01-01'),
);
});
it('time only', async () => {
const plugin = await getPlugin(
siteDir,
{
showLastUpdateAuthor: false,
showLastUpdateTime: true,
},
DefaultI18N,
);
const {blogPosts} = (await plugin.loadContent!())!;
expect(blogPosts[0]?.metadata.title).toBe('Author');
expect(blogPosts[0]?.metadata.lastUpdatedBy).toBeUndefined();
expect(blogPosts[0]?.metadata.lastUpdatedAt).toBe(
GIT_FALLBACK_LAST_UPDATE_DATE,
);
expect(blogPosts[1]?.metadata.title).toBe('Nothing');
expect(blogPosts[1]?.metadata.lastUpdatedBy).toBeUndefined();
expect(blogPosts[1]?.metadata.lastUpdatedAt).toBe(
GIT_FALLBACK_LAST_UPDATE_DATE,
);
expect(blogPosts[2]?.metadata.title).toBe('Both');
expect(blogPosts[2]?.metadata.lastUpdatedBy).toBeUndefined();
expect(blogPosts[2]?.metadata.lastUpdatedAt).toBe(
lastUpdateFor('2021-01-01'),
);
expect(blogPosts[3]?.metadata.title).toBe('Last update date');
expect(blogPosts[3]?.metadata.lastUpdatedBy).toBeUndefined();
expect(blogPosts[3]?.metadata.lastUpdatedAt).toBe(
lastUpdateFor('2021-01-01'),
);
});
it('author only', async () => {
const plugin = await getPlugin(
siteDir,
{
showLastUpdateAuthor: true,
showLastUpdateTime: false,
},
DefaultI18N,
);
const {blogPosts} = (await plugin.loadContent!())!;
expect(blogPosts[0]?.metadata.lastUpdatedBy).toBe('seb');
expect(blogPosts[0]?.metadata.lastUpdatedAt).toBeUndefined();
expect(blogPosts[1]?.metadata.lastUpdatedBy).toBe(
GIT_FALLBACK_LAST_UPDATE_AUTHOR,
);
expect(blogPosts[1]?.metadata.lastUpdatedAt).toBeUndefined();
expect(blogPosts[2]?.metadata.lastUpdatedBy).toBe('seb');
expect(blogPosts[2]?.metadata.lastUpdatedAt).toBeUndefined();
expect(blogPosts[3]?.metadata.lastUpdatedBy).toBe(
GIT_FALLBACK_LAST_UPDATE_AUTHOR,
);
expect(blogPosts[3]?.metadata.lastUpdatedAt).toBeUndefined();
});
it('none', async () => {
const plugin = await getPlugin(
siteDir,
{
showLastUpdateAuthor: false,
showLastUpdateTime: false,
},
DefaultI18N,
);
const {blogPosts} = (await plugin.loadContent!())!;
expect(blogPosts[0]?.metadata.lastUpdatedBy).toBeUndefined();
expect(blogPosts[0]?.metadata.lastUpdatedAt).toBeUndefined();
expect(blogPosts[1]?.metadata.lastUpdatedBy).toBeUndefined();
expect(blogPosts[1]?.metadata.lastUpdatedAt).toBeUndefined();
expect(blogPosts[2]?.metadata.lastUpdatedBy).toBeUndefined();
expect(blogPosts[2]?.metadata.lastUpdatedAt).toBeUndefined();
expect(blogPosts[3]?.metadata.lastUpdatedBy).toBeUndefined();
expect(blogPosts[3]?.metadata.lastUpdatedAt).toBeUndefined();
});
});