mirror of
https://github.com/facebook/docusaurus.git
synced 2025-06-05 12:22:45 +02:00
refactor: unify how validateOptions is handled (#6961)
* refactor: unify how validateOptions is handled * fix types * fix again
This commit is contained in:
parent
44107fb879
commit
6e2eb44964
43 changed files with 542 additions and 540 deletions
|
@ -0,0 +1,5 @@
|
|||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`validateOptions throws Error in case of invalid feed type 1`] = `"\\"feedOptions.type\\" does not match any of the allowed types"`;
|
||||
|
||||
exports[`validateOptions throws Error in case of invalid options 1`] = `"\\"postsPerPage\\" must be greater than or equal to 1"`;
|
|
@ -1,5 +0,0 @@
|
|||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`blog plugin options schema throws Error in case of invalid feed type 1`] = `[ValidationError: "feedOptions.type" does not match any of the allowed types]`;
|
||||
|
||||
exports[`blog plugin options schema throws Error in case of invalid options 1`] = `[ValidationError: "postsPerPage" must be greater than or equal to 1]`;
|
|
@ -11,7 +11,7 @@ import fs from 'fs-extra';
|
|||
import {createBlogFeedFiles} from '../feed';
|
||||
import type {LoadContext, I18n} from '@docusaurus/types';
|
||||
import type {BlogContentPaths} from '../types';
|
||||
import {DEFAULT_OPTIONS} from '../pluginOptionSchema';
|
||||
import {DEFAULT_OPTIONS} from '../options';
|
||||
import {generateBlogPosts} from '../blogUtils';
|
||||
import type {PluginOptions} from '@docusaurus/plugin-content-blog';
|
||||
|
||||
|
|
|
@ -5,7 +5,7 @@
|
|||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
import {validateBlogPostFrontMatter} from '../blogFrontMatter';
|
||||
import {validateBlogPostFrontMatter} from '../frontMatter';
|
||||
import escapeStringRegexp from 'escape-string-regexp';
|
||||
import type {BlogPostFrontMatter} from '@docusaurus/plugin-content-blog';
|
||||
|
|
@ -9,9 +9,9 @@ import {jest} from '@jest/globals';
|
|||
import path from 'path';
|
||||
import pluginContentBlog from '../index';
|
||||
import type {DocusaurusConfig, LoadContext, I18n} from '@docusaurus/types';
|
||||
import {PluginOptionSchema} from '../pluginOptionSchema';
|
||||
import {validateOptions} from '../options';
|
||||
import type {BlogPost} from '../types';
|
||||
import type {Joi} from '@docusaurus/utils-validation';
|
||||
import {normalizePluginOptions} from '@docusaurus/utils-validation';
|
||||
import {posixPath, getFileCommitDate} from '@docusaurus/utils';
|
||||
import type {
|
||||
PluginOptions,
|
||||
|
@ -47,18 +47,6 @@ function getI18n(locale: string): I18n {
|
|||
|
||||
const DefaultI18N: I18n = getI18n('en');
|
||||
|
||||
function validateAndNormalize(
|
||||
schema: Joi.ObjectSchema,
|
||||
options: Partial<PluginOptions>,
|
||||
) {
|
||||
const {value, error} = schema.validate(options);
|
||||
if (error) {
|
||||
throw error;
|
||||
} else {
|
||||
return value;
|
||||
}
|
||||
}
|
||||
|
||||
const PluginPath = 'blog';
|
||||
|
||||
const BaseEditUrl = 'https://baseEditUrl.com/edit';
|
||||
|
@ -81,11 +69,14 @@ const getPlugin = async (
|
|||
generatedFilesDir,
|
||||
i18n,
|
||||
} as LoadContext,
|
||||
validateAndNormalize(PluginOptionSchema, {
|
||||
path: PluginPath,
|
||||
editUrl: BaseEditUrl,
|
||||
...pluginOptions,
|
||||
}),
|
||||
validateOptions({
|
||||
validate: normalizePluginOptions,
|
||||
options: {
|
||||
path: PluginPath,
|
||||
editUrl: BaseEditUrl,
|
||||
...pluginOptions,
|
||||
},
|
||||
}) as PluginOptions,
|
||||
);
|
||||
};
|
||||
|
||||
|
|
|
@ -0,0 +1,160 @@
|
|||
/**
|
||||
* 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 {validateOptions, DEFAULT_OPTIONS} from '../options';
|
||||
import {normalizePluginOptions} from '@docusaurus/utils-validation';
|
||||
import type {Options} from '@docusaurus/plugin-content-blog';
|
||||
|
||||
function testValidate(options: Options) {
|
||||
return validateOptions({validate: normalizePluginOptions, options});
|
||||
}
|
||||
|
||||
// the type of remark/rehype plugins can be either function, object or array
|
||||
const markdownPluginsFunctionStub = () => {};
|
||||
const markdownPluginsObjectStub = {};
|
||||
|
||||
const defaultOptions = {...DEFAULT_OPTIONS, id: 'default'};
|
||||
|
||||
describe('validateOptions', () => {
|
||||
it('returns default options for undefined user options', () => {
|
||||
expect(testValidate(undefined)).toEqual(defaultOptions);
|
||||
});
|
||||
|
||||
it('returns default options for empty user options', () => {
|
||||
expect(testValidate({})).toEqual(defaultOptions);
|
||||
});
|
||||
|
||||
it('accepts correctly defined user options', () => {
|
||||
const userOptions = {
|
||||
...defaultOptions,
|
||||
feedOptions: {type: 'rss' as const, title: 'myTitle'},
|
||||
path: 'not_blog',
|
||||
routeBasePath: 'myBlog',
|
||||
postsPerPage: 5,
|
||||
include: ['api/*', 'docs/*'],
|
||||
};
|
||||
expect(testValidate(userOptions)).toEqual({
|
||||
...userOptions,
|
||||
feedOptions: {type: ['rss'], title: 'myTitle', copyright: ''},
|
||||
});
|
||||
});
|
||||
|
||||
it('accepts valid user options', () => {
|
||||
const userOptions = {
|
||||
...defaultOptions,
|
||||
routeBasePath: 'myBlog',
|
||||
beforeDefaultRemarkPlugins: [],
|
||||
beforeDefaultRehypePlugins: [markdownPluginsFunctionStub],
|
||||
remarkPlugins: [[markdownPluginsFunctionStub, {option1: '42'}]],
|
||||
rehypePlugins: [
|
||||
markdownPluginsObjectStub,
|
||||
[markdownPluginsFunctionStub, {option1: '42'}],
|
||||
],
|
||||
};
|
||||
expect(testValidate(userOptions)).toEqual(userOptions);
|
||||
});
|
||||
|
||||
it('throws Error in case of invalid options', () => {
|
||||
expect(() =>
|
||||
testValidate({
|
||||
path: 'not_blog',
|
||||
postsPerPage: -1,
|
||||
include: ['api/*', 'docs/*'],
|
||||
routeBasePath: 'not_blog',
|
||||
}),
|
||||
).toThrowErrorMatchingSnapshot();
|
||||
});
|
||||
|
||||
it('throws Error in case of invalid feed type', () => {
|
||||
expect(() =>
|
||||
testValidate({
|
||||
feedOptions: {
|
||||
type: 'none',
|
||||
},
|
||||
}),
|
||||
).toThrowErrorMatchingSnapshot();
|
||||
});
|
||||
|
||||
it('converts all feed type to array with other feed type', () => {
|
||||
expect(
|
||||
testValidate({
|
||||
feedOptions: {type: 'all'},
|
||||
}),
|
||||
).toEqual({
|
||||
...defaultOptions,
|
||||
feedOptions: {type: ['rss', 'atom', 'json'], copyright: ''},
|
||||
});
|
||||
});
|
||||
|
||||
it('accepts null type and return same', () => {
|
||||
expect(
|
||||
testValidate({
|
||||
feedOptions: {type: null},
|
||||
}),
|
||||
).toEqual({
|
||||
...defaultOptions,
|
||||
feedOptions: {type: null},
|
||||
});
|
||||
});
|
||||
|
||||
it('contains array with rss + atom for missing feed type', () => {
|
||||
expect(
|
||||
testValidate({
|
||||
feedOptions: {},
|
||||
}),
|
||||
).toEqual(defaultOptions);
|
||||
});
|
||||
|
||||
it('has array with rss + atom, title for missing feed type', () => {
|
||||
expect(
|
||||
testValidate({
|
||||
feedOptions: {title: 'title'},
|
||||
}),
|
||||
).toEqual({
|
||||
...defaultOptions,
|
||||
feedOptions: {type: ['rss', 'atom'], title: 'title', copyright: ''},
|
||||
});
|
||||
});
|
||||
|
||||
it('accepts 0 sidebar count', () => {
|
||||
const userOptions = {blogSidebarCount: 0};
|
||||
expect(testValidate(userOptions)).toEqual({
|
||||
...defaultOptions,
|
||||
...userOptions,
|
||||
});
|
||||
});
|
||||
|
||||
it('accepts "ALL" sidebar count', () => {
|
||||
const userOptions = {blogSidebarCount: 'ALL' as const};
|
||||
expect(testValidate(userOptions)).toEqual({
|
||||
...defaultOptions,
|
||||
...userOptions,
|
||||
});
|
||||
});
|
||||
|
||||
it('rejects "abcdef" sidebar count', () => {
|
||||
const userOptions = {blogSidebarCount: 'abcdef'};
|
||||
expect(() => testValidate(userOptions)).toThrowErrorMatchingInlineSnapshot(
|
||||
`"\\"blogSidebarCount\\" must be one of [ALL, number]"`,
|
||||
);
|
||||
});
|
||||
|
||||
it('accepts "all posts" sidebar title', () => {
|
||||
const userOptions = {blogSidebarTitle: 'all posts'};
|
||||
expect(testValidate(userOptions)).toEqual({
|
||||
...defaultOptions,
|
||||
...userOptions,
|
||||
});
|
||||
});
|
||||
|
||||
it('rejects 42 sidebar title', () => {
|
||||
const userOptions = {blogSidebarTitle: 42};
|
||||
expect(() => testValidate(userOptions)).toThrowErrorMatchingInlineSnapshot(
|
||||
`"\\"blogSidebarTitle\\" must be a string"`,
|
||||
);
|
||||
});
|
||||
});
|
|
@ -1,152 +0,0 @@
|
|||
/**
|
||||
* 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 {PluginOptionSchema, DEFAULT_OPTIONS} from '../pluginOptionSchema';
|
||||
|
||||
// the type of remark/rehype plugins can be either function, object or array
|
||||
const markdownPluginsFunctionStub = () => {};
|
||||
const markdownPluginsObjectStub = {};
|
||||
|
||||
describe('blog plugin options schema', () => {
|
||||
it('normalizes options', () => {
|
||||
const {value, error} = PluginOptionSchema.validate({});
|
||||
expect(value).toEqual(DEFAULT_OPTIONS);
|
||||
expect(error).toBeUndefined();
|
||||
});
|
||||
|
||||
it('accepts correctly defined user options', () => {
|
||||
const userOptions = {
|
||||
...DEFAULT_OPTIONS,
|
||||
feedOptions: {type: 'rss', title: 'myTitle'},
|
||||
path: 'not_blog',
|
||||
routeBasePath: 'myBlog',
|
||||
postsPerPage: 5,
|
||||
include: ['api/*', 'docs/*'],
|
||||
};
|
||||
const {value, error} = PluginOptionSchema.validate(userOptions);
|
||||
expect(value).toEqual({
|
||||
...userOptions,
|
||||
feedOptions: {type: ['rss'], title: 'myTitle', copyright: ''},
|
||||
});
|
||||
expect(error).toBeUndefined();
|
||||
});
|
||||
|
||||
it('accepts valid user options', async () => {
|
||||
const userOptions = {
|
||||
...DEFAULT_OPTIONS,
|
||||
routeBasePath: 'myBlog',
|
||||
beforeDefaultRemarkPlugins: [],
|
||||
beforeDefaultRehypePlugins: [markdownPluginsFunctionStub],
|
||||
remarkPlugins: [[markdownPluginsFunctionStub, {option1: '42'}]],
|
||||
rehypePlugins: [
|
||||
markdownPluginsObjectStub,
|
||||
[markdownPluginsFunctionStub, {option1: '42'}],
|
||||
],
|
||||
};
|
||||
const {value, error} = PluginOptionSchema.validate(userOptions);
|
||||
expect(value).toEqual(userOptions);
|
||||
expect(error).toBeUndefined();
|
||||
});
|
||||
|
||||
it('throws Error in case of invalid options', () => {
|
||||
const {error} = PluginOptionSchema.validate({
|
||||
path: 'not_blog',
|
||||
postsPerPage: -1,
|
||||
include: ['api/*', 'docs/*'],
|
||||
routeBasePath: 'not_blog',
|
||||
});
|
||||
|
||||
expect(error).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it('throws Error in case of invalid feed type', () => {
|
||||
const {error} = PluginOptionSchema.validate({
|
||||
feedOptions: {
|
||||
type: 'none',
|
||||
},
|
||||
});
|
||||
|
||||
expect(error).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it('converts all feed type to array with other feed type', () => {
|
||||
const {value} = PluginOptionSchema.validate({
|
||||
feedOptions: {type: 'all'},
|
||||
});
|
||||
expect(value).toEqual({
|
||||
...DEFAULT_OPTIONS,
|
||||
feedOptions: {type: ['rss', 'atom', 'json'], copyright: ''},
|
||||
});
|
||||
});
|
||||
|
||||
it('accepts null type and return same', () => {
|
||||
const {value, error} = PluginOptionSchema.validate({
|
||||
feedOptions: {type: null},
|
||||
});
|
||||
expect(value).toEqual({
|
||||
...DEFAULT_OPTIONS,
|
||||
feedOptions: {type: null},
|
||||
});
|
||||
expect(error).toBeUndefined();
|
||||
});
|
||||
|
||||
it('contains array with rss + atom for missing feed type', () => {
|
||||
const {value} = PluginOptionSchema.validate({
|
||||
feedOptions: {},
|
||||
});
|
||||
expect(value).toEqual(DEFAULT_OPTIONS);
|
||||
});
|
||||
|
||||
it('has array with rss + atom, title for missing feed type', () => {
|
||||
const {value} = PluginOptionSchema.validate({
|
||||
feedOptions: {title: 'title'},
|
||||
});
|
||||
expect(value).toEqual({
|
||||
...DEFAULT_OPTIONS,
|
||||
feedOptions: {type: ['rss', 'atom'], title: 'title', copyright: ''},
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('blog sidebar', () => {
|
||||
it('accepts 0 sidebar count', () => {
|
||||
const userOptions = {blogSidebarCount: 0};
|
||||
const {value, error} = PluginOptionSchema.validate(userOptions);
|
||||
expect(value).toEqual({...DEFAULT_OPTIONS, ...userOptions});
|
||||
expect(error).toBeUndefined();
|
||||
});
|
||||
|
||||
it('accepts "ALL" sidebar count', () => {
|
||||
const userOptions = {blogSidebarCount: 'ALL'};
|
||||
const {value, error} = PluginOptionSchema.validate(userOptions);
|
||||
expect(value).toEqual({...DEFAULT_OPTIONS, ...userOptions});
|
||||
expect(error).toBeUndefined();
|
||||
});
|
||||
|
||||
it('rejects "abcdef" sidebar count', () => {
|
||||
const userOptions = {blogSidebarCount: 'abcdef'};
|
||||
const {error} = PluginOptionSchema.validate(userOptions);
|
||||
expect(error).toMatchInlineSnapshot(
|
||||
`[ValidationError: "blogSidebarCount" must be one of [ALL, number]]`,
|
||||
);
|
||||
});
|
||||
|
||||
it('accepts "all posts" sidebar title', () => {
|
||||
const userOptions = {blogSidebarTitle: 'all posts'};
|
||||
const {value, error} = PluginOptionSchema.validate(userOptions);
|
||||
expect(value).toEqual({...DEFAULT_OPTIONS, ...userOptions});
|
||||
expect(error).toBeUndefined();
|
||||
});
|
||||
|
||||
it('rejects 42 sidebar title', () => {
|
||||
const userOptions = {blogSidebarTitle: 42};
|
||||
const {error} = PluginOptionSchema.validate(userOptions);
|
||||
expect(error).toMatchInlineSnapshot(
|
||||
`[ValidationError: "blogSidebarTitle" must be a string]`,
|
||||
);
|
||||
});
|
||||
});
|
|
@ -7,7 +7,7 @@
|
|||
|
||||
import type {BlogPost, BlogContent} from '../types';
|
||||
import {getTranslationFiles, translateContent} from '../translations';
|
||||
import {DEFAULT_OPTIONS} from '../pluginOptionSchema';
|
||||
import {DEFAULT_OPTIONS} from '../options';
|
||||
import {updateTranslationFileMessages} from '@docusaurus/utils';
|
||||
import type {PluginOptions} from '@docusaurus/plugin-content-blog';
|
||||
|
||||
|
|
|
@ -31,7 +31,7 @@ import {
|
|||
getContentPathList,
|
||||
} from '@docusaurus/utils';
|
||||
import type {LoadContext} from '@docusaurus/types';
|
||||
import {validateBlogPostFrontMatter} from './blogFrontMatter';
|
||||
import {validateBlogPostFrontMatter} from './frontMatter';
|
||||
import {type AuthorsMap, getAuthorsMap, getBlogPostAuthors} from './authors';
|
||||
import logger from '@docusaurus/logger';
|
||||
import type {
|
||||
|
|
|
@ -30,14 +30,7 @@ import type {
|
|||
BlogContentPaths,
|
||||
BlogMarkdownLoaderOptions,
|
||||
} from './types';
|
||||
import {PluginOptionSchema} from './pluginOptionSchema';
|
||||
import type {
|
||||
LoadContext,
|
||||
Plugin,
|
||||
HtmlTags,
|
||||
OptionValidationContext,
|
||||
ValidationResult,
|
||||
} from '@docusaurus/types';
|
||||
import type {LoadContext, Plugin, HtmlTags} from '@docusaurus/types';
|
||||
import {
|
||||
generateBlogPosts,
|
||||
getSourceToPermalink,
|
||||
|
@ -572,10 +565,4 @@ export default async function pluginContentBlog(
|
|||
};
|
||||
}
|
||||
|
||||
export function validateOptions({
|
||||
validate,
|
||||
options,
|
||||
}: OptionValidationContext<PluginOptions>): ValidationResult<PluginOptions> {
|
||||
const validatedOptions = validate(PluginOptionSchema, options);
|
||||
return validatedOptions;
|
||||
}
|
||||
export {validateOptions} from './options';
|
||||
|
|
|
@ -13,7 +13,8 @@ import {
|
|||
URISchema,
|
||||
} from '@docusaurus/utils-validation';
|
||||
import {GlobExcludeDefault} from '@docusaurus/utils';
|
||||
import type {PluginOptions} from '@docusaurus/plugin-content-blog';
|
||||
import type {PluginOptions, Options} from '@docusaurus/plugin-content-blog';
|
||||
import type {OptionValidationContext} from '@docusaurus/types';
|
||||
|
||||
export const DEFAULT_OPTIONS: PluginOptions = {
|
||||
feedOptions: {type: ['rss', 'atom'], copyright: ''},
|
||||
|
@ -46,7 +47,7 @@ export const DEFAULT_OPTIONS: PluginOptions = {
|
|||
sortPosts: 'descending',
|
||||
};
|
||||
|
||||
export const PluginOptionSchema = Joi.object<PluginOptions>({
|
||||
const PluginOptionSchema = Joi.object<PluginOptions>({
|
||||
path: Joi.string().default(DEFAULT_OPTIONS.path),
|
||||
archiveBasePath: Joi.string()
|
||||
.default(DEFAULT_OPTIONS.archiveBasePath)
|
||||
|
@ -125,4 +126,15 @@ export const PluginOptionSchema = Joi.object<PluginOptions>({
|
|||
sortPosts: Joi.string()
|
||||
.valid('descending', 'ascending')
|
||||
.default(DEFAULT_OPTIONS.sortPosts),
|
||||
});
|
||||
}).default(DEFAULT_OPTIONS);
|
||||
|
||||
export function validateOptions({
|
||||
validate,
|
||||
options,
|
||||
}: OptionValidationContext<Options, PluginOptions>): PluginOptions {
|
||||
const validatedOptions = validate(
|
||||
PluginOptionSchema,
|
||||
options,
|
||||
) as PluginOptions;
|
||||
return validatedOptions;
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue