refactor(client-redirects): migrate validation to validateOptions lifecycle (#6924)

This commit is contained in:
Joshua Chen 2022-03-17 00:24:01 +08:00 committed by GitHub
parent 68aaf9201f
commit da9f38b748
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
8 changed files with 86 additions and 113 deletions

View file

@ -154,7 +154,9 @@ declare module '@docusaurus/Link' {
readonly href?: string;
readonly autoAddBaseUrl?: boolean;
// escape hatch in case broken links check is annoying for a specific link
/**
* escape hatch in case broken links check is annoying for a specific link
*/
readonly 'data-noBrokenLinkCheck'?: boolean;
};
export default function Link(props: Props): JSX.Element;

View file

@ -1,35 +0,0 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`normalizePluginOptions rejects bad createRedirects user inputs 1`] = `
"Invalid @docusaurus/plugin-client-redirects options: \\"createRedirects\\" must be of type function
{
\\"createRedirects\\": [
\\"bad\\",
\\"value\\"
]
}"
`;
exports[`normalizePluginOptions rejects bad fromExtensions user inputs 1`] = `
"Invalid @docusaurus/plugin-client-redirects options: \\"fromExtensions[0]\\" contains an invalid value
{
\\"fromExtensions\\": [
null,
null,
123,
true
]
}"
`;
exports[`normalizePluginOptions rejects bad toExtensions user inputs 1`] = `
"Invalid @docusaurus/plugin-client-redirects options: \\"toExtensions[0]\\" contains an invalid value
{
\\"toExtensions\\": [
null,
null,
123,
true
]
}"
`;

View file

@ -0,0 +1,7 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`normalizePluginOptions rejects bad createRedirects user inputs 1`] = `"\\"createRedirects\\" must be of type function"`;
exports[`normalizePluginOptions rejects bad fromExtensions user inputs 1`] = `"\\"fromExtensions[0]\\" contains an invalid value"`;
exports[`normalizePluginOptions rejects bad toExtensions user inputs 1`] = `"\\"toExtensions[0]\\" contains an invalid value"`;

View file

@ -7,8 +7,9 @@
import type {PluginContext} from '../types';
import collectRedirects from '../collectRedirects';
import normalizePluginOptions from '../normalizePluginOptions';
import {validateOptions} from '../options';
import {removeTrailingSlash} from '@docusaurus/utils';
import {normalizePluginOptions} from '@docusaurus/utils-validation';
import type {Options} from '@docusaurus/plugin-client-redirects';
function createTestPluginContext(
@ -19,7 +20,7 @@ function createTestPluginContext(
outDir: '/tmp',
baseUrl: 'https://docusaurus.io',
relativeRoutesPaths,
options: normalizePluginOptions(options),
options: validateOptions({validate: normalizePluginOptions, options}),
};
}

View file

@ -5,32 +5,38 @@
* LICENSE file in the root directory of this source tree.
*/
import normalizePluginOptions, {
DefaultPluginOptions,
} from '../normalizePluginOptions';
import type {CreateRedirectsFnOption} from '@docusaurus/plugin-client-redirects';
import {validateOptions, DEFAULT_OPTIONS} from '../options';
import {normalizePluginOptions} from '@docusaurus/utils-validation';
import type {
CreateRedirectsFnOption,
Options,
} from '@docusaurus/plugin-client-redirects';
function testValidate(options: Options) {
return validateOptions({validate: normalizePluginOptions, options});
}
describe('normalizePluginOptions', () => {
it('returns default options for undefined user options', () => {
expect(normalizePluginOptions()).toEqual(DefaultPluginOptions);
expect(testValidate(undefined)).toEqual(DEFAULT_OPTIONS);
});
it('returns default options for empty user options', () => {
expect(normalizePluginOptions()).toEqual(DefaultPluginOptions);
expect(testValidate(undefined)).toEqual(DEFAULT_OPTIONS);
});
it('overrides one default options with valid user options', () => {
expect(
normalizePluginOptions({
testValidate({
toExtensions: ['html'],
}),
).toEqual({...DefaultPluginOptions, toExtensions: ['html']});
).toEqual({...DEFAULT_OPTIONS, id: 'default', toExtensions: ['html']});
});
it('overrides all default options with valid user options', () => {
const createRedirects: CreateRedirectsFnOption = (_routePath: string) => [];
expect(
normalizePluginOptions({
testValidate({
fromExtensions: ['exe', 'zip'],
toExtensions: ['html'],
createRedirects,
@ -47,7 +53,7 @@ describe('normalizePluginOptions', () => {
it('rejects bad fromExtensions user inputs', () => {
expect(() =>
normalizePluginOptions({
testValidate({
fromExtensions: [null, undefined, 123, true] as unknown as string[],
}),
).toThrowErrorMatchingSnapshot();
@ -55,7 +61,7 @@ describe('normalizePluginOptions', () => {
it('rejects bad toExtensions user inputs', () => {
expect(() =>
normalizePluginOptions({
testValidate({
toExtensions: [null, undefined, 123, true] as unknown as string[],
}),
).toThrowErrorMatchingSnapshot();
@ -63,7 +69,7 @@ describe('normalizePluginOptions', () => {
it('rejects bad createRedirects user inputs', () => {
expect(() =>
normalizePluginOptions({
testValidate({
createRedirects: ['bad', 'value'] as unknown as CreateRedirectsFnOption,
}),
).toThrowErrorMatchingSnapshot();

View file

@ -9,7 +9,6 @@ import type {LoadContext, Plugin, Props} from '@docusaurus/types';
import type {PluginContext, RedirectMetadata} from './types';
import type {PluginOptions} from '@docusaurus/plugin-client-redirects';
import normalizePluginOptions from './normalizePluginOptions';
import collectRedirects from './collectRedirects';
import writeRedirectFiles, {
toRedirectFilesMetadata,
@ -19,12 +18,10 @@ import {removePrefix, addLeadingSlash} from '@docusaurus/utils';
export default function pluginClientRedirectsPages(
context: LoadContext,
opts: PluginOptions,
options: PluginOptions,
): Plugin<unknown> {
const {trailingSlash} = context.siteConfig;
const options = normalizePluginOptions(opts);
return {
name: 'docusaurus-plugin-client-redirects',
async postBuild(props: Props) {
@ -53,3 +50,5 @@ export default function pluginClientRedirectsPages(
},
};
}
export {validateOptions} from './options';

View file

@ -1,59 +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 type {
PluginOptions,
Options as UserPluginOptions,
RedirectOption,
} from '@docusaurus/plugin-client-redirects';
import {Joi, PathnameSchema} from '@docusaurus/utils-validation';
import {DEFAULT_PLUGIN_ID} from '@docusaurus/utils';
export const DefaultPluginOptions: PluginOptions = {
id: DEFAULT_PLUGIN_ID, // TODO temporary
fromExtensions: [],
toExtensions: [],
redirects: [],
};
const RedirectPluginOptionValidation = Joi.object<RedirectOption>({
to: PathnameSchema.required(),
from: Joi.alternatives().try(
PathnameSchema.required(),
Joi.array().items(PathnameSchema.required()),
),
});
const isString = Joi.string().required().not(null);
const UserOptionsSchema = Joi.object<UserPluginOptions>({
id: Joi.string().optional(), // TODO remove once validation migrated to new system
fromExtensions: Joi.array().items(isString),
toExtensions: Joi.array().items(isString),
redirects: Joi.array().items(RedirectPluginOptionValidation),
createRedirects: Joi.function().arity(1),
});
function validateUserOptions(userOptions: UserPluginOptions) {
const {error} = UserOptionsSchema.validate(userOptions, {
abortEarly: true,
allowUnknown: false,
});
if (error) {
throw new Error(
`Invalid @docusaurus/plugin-client-redirects options: ${error.message}
${JSON.stringify(userOptions, null, 2)}`,
);
}
}
export default function normalizePluginOptions(
userPluginOptions: UserPluginOptions = {},
): PluginOptions {
validateUserOptions(userPluginOptions);
return {...DefaultPluginOptions, ...userPluginOptions};
}

View file

@ -0,0 +1,52 @@
/**
* 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 type {
PluginOptions,
RedirectOption,
} from '@docusaurus/plugin-client-redirects';
import type {
OptionValidationContext,
ValidationResult,
} from '@docusaurus/types';
import {Joi, PathnameSchema} from '@docusaurus/utils-validation';
export const DEFAULT_OPTIONS: Partial<PluginOptions> = {
fromExtensions: [],
toExtensions: [],
redirects: [],
};
const RedirectPluginOptionValidation = Joi.object<RedirectOption>({
to: PathnameSchema.required(),
from: Joi.alternatives().try(
PathnameSchema.required(),
Joi.array().items(PathnameSchema.required()),
),
});
const isString = Joi.string().required().not(null);
const UserOptionsSchema = Joi.object<PluginOptions>({
fromExtensions: Joi.array()
.items(isString)
.default(DEFAULT_OPTIONS.fromExtensions),
toExtensions: Joi.array()
.items(isString)
.default(DEFAULT_OPTIONS.toExtensions),
redirects: Joi.array()
.items(RedirectPluginOptionValidation)
.default(DEFAULT_OPTIONS.redirects),
createRedirects: Joi.function().arity(1),
}).default(DEFAULT_OPTIONS);
export function validateOptions({
validate,
options: userOptions,
}: OptionValidationContext<PluginOptions>): ValidationResult<PluginOptions> {
return validate(UserOptionsSchema, userOptions);
}