Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix(v2): config validation fixes + add tests for config validation #3142

Merged
merged 3 commits into from
Jul 29, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP

exports[`throw Error in case of invalid feedtype 1`] = `[ValidationError: "feedOptions.type" does not match any of the allowed types]`;
exports[`should throw Error in case of invalid feedtype 1`] = `[ValidationError: "feedOptions.type" does not match any of the allowed types]`;

exports[`throw Error in case of invalid options 1`] = `[ValidationError: "postsPerPage" must be larger than or equal to 1]`;
exports[`should throw Error in case of invalid options 1`] = `[ValidationError: "postsPerPage" must be larger than or equal to 1]`;
Original file line number Diff line number Diff line change
Expand Up @@ -7,28 +7,47 @@

import {PluginOptionSchema, DEFAULT_OPTIONS} from '../pluginOptionSchema';

test('normalize options', () => {
// the type of remark/rehype plugins is function
const remarkRehypePluginStub = () => {};

test('should normalize options', () => {
const {value} = PluginOptionSchema.validate({});
expect(value).toEqual(DEFAULT_OPTIONS);
});

test('validate options', () => {
const {value} = PluginOptionSchema.validate({
test('should accept correctly defined user options', () => {
const userOptions = {
...DEFAULT_OPTIONS,
feedOptions: {type: 'rss', title: 'myTitle'},
path: 'not_blog',
routeBasePath: '',
postsPerPage: 5,
include: ['api/*', 'docs/*'],
routeBasePath: 'not_blog',
});
};
const {value} = PluginOptionSchema.validate(userOptions);
expect(value).toEqual({
...DEFAULT_OPTIONS,
postsPerPage: 5,
include: ['api/*', 'docs/*'],
routeBasePath: 'not_blog',
path: 'not_blog',
...userOptions,
feedOptions: {type: ['rss'], title: 'myTitle'},
});
});

test('throw Error in case of invalid options', () => {
test('should accept valid user options', async () => {
const userOptions = {
...DEFAULT_OPTIONS,
routebasePath: '',
beforeDefaultRemarkPlugins: [],
beforeDefaultRehypePlugins: [remarkRehypePluginStub],
remarkPlugins: [remarkRehypePluginStub, {option1: '42'}],
rehypePlugins: [
remarkRehypePluginStub,
[remarkRehypePluginStub, {option1: '42'}],
],
};
const {value} = await PluginOptionSchema.validate(userOptions);
expect(value).toEqual(userOptions);
});

test('should throw Error in case of invalid options', () => {
const {error} = PluginOptionSchema.validate({
path: 'not_blog',
postsPerPage: -1,
Expand All @@ -39,7 +58,7 @@ test('throw Error in case of invalid options', () => {
expect(error).toMatchSnapshot();
});

test('throw Error in case of invalid feedtype', () => {
test('should throw Error in case of invalid feedtype', () => {
const {error} = PluginOptionSchema.validate({
feedOptions: {
type: 'none',
Expand All @@ -49,7 +68,7 @@ test('throw Error in case of invalid feedtype', () => {
expect(error).toMatchSnapshot();
});

test('convert all feed type to array with other feed type', () => {
test('should convert all feed type to array with other feed type', () => {
const {value} = PluginOptionSchema.validate({
feedOptions: {type: 'all'},
});
Expand Down
18 changes: 14 additions & 4 deletions packages/docusaurus-plugin-content-blog/src/pluginOptionSchema.ts
Original file line number Diff line number Diff line change
Expand Up @@ -63,10 +63,20 @@ export const PluginOptionSchema = Joi.object({
truncateMarker: Joi.object().default(DEFAULT_OPTIONS.truncateMarker),
admonitions: Joi.object().default(DEFAULT_OPTIONS.admonitions),
beforeDefaultRemarkPlugins: Joi.array()
.items(Joi.object())
.items(
Joi.array()
.items(Joi.function().required(), Joi.object().required())
.length(2),
Joi.function(),
)
.default(DEFAULT_OPTIONS.beforeDefaultRemarkPlugins),
beforeDefaultRehypePlugins: Joi.array()
.items(Joi.object())
.items(
Joi.array()
.items(Joi.function().required(), Joi.object().required())
.length(2),
Joi.function(),
)
.default(DEFAULT_OPTIONS.beforeDefaultRehypePlugins),
feedOptions: Joi.object({
type: Joi.alternatives().conditional(
Expand All @@ -75,8 +85,8 @@ export const PluginOptionSchema = Joi.object({
then: Joi.custom((val) => (val === 'all' ? ['rss', 'atom'] : [val])),
},
),
title: Joi.string(),
description: Joi.string(),
title: Joi.string().allow(''),
description: Joi.string().allow(''),
copyright: Joi.string(),
language: Joi.string(),
}).default(DEFAULT_OPTIONS.feedOptions),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,9 @@ export default function normalizePluginOptions(options) {
}
}

// the type of remark/rehype plugins is function
const remarkRehypePluginStub = () => {};

describe('normalizeDocsPluginOptions', () => {
test('should return default options for undefined user options', async () => {
const {value} = await PluginOptionSchema.validate({});
Expand All @@ -34,14 +37,26 @@ describe('normalizeDocsPluginOptions', () => {
docLayoutComponent: '@theme/DocPage',
docItemComponent: '@theme/DocItem',
remarkPlugins: [],
rehypePlugins: [],
rehypePlugins: [remarkRehypePluginStub],
showLastUpdateTime: true,
showLastUpdateAuthor: true,
admonitions: {},
excludeNextVersionDocs: true,
disableVersioning: true,
};
const {value} = await PluginOptionSchema.validate(userOptions);
expect(value).toEqual(userOptions);
});

test('should accept correctly defined remark and rehype plugin options', async () => {
const userOptions = {
...DEFAULT_OPTIONS,
remarkPlugins: [remarkRehypePluginStub, {option1: '42'}],
rehypePlugins: [
remarkRehypePluginStub,
[remarkRehypePluginStub, {option1: '42'}],
],
};
const {value} = await PluginOptionSchema.validate(userOptions);
expect(value).toEqual(userOptions);
});
Expand Down
Original file line number Diff line number Diff line change
@@ -1,50 +1,51 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP

exports[`validateConfig throw error for baseUrl without trailing \`/\` 1`] = `
exports[`normalizeConfig should throw error for baseUrl without trailing \`/\` 1`] = `
"\\"baseUrl\\" must be a string with a trailing \`/\`
"
`;

exports[`validateConfig throw error for required fields 1`] = `
exports[`normalizeConfig should throw error for required fields 1`] = `
"\\"baseUrl\\" is required
\\"favicon\\" is required
\\"title\\" is required
\\"url\\" is required
\\"themes\\" must be an array
\\"presets\\" must be an array
\\"scripts\\" must be an array
\\"stylesheets\\" must be an array
These field(s) [\\"invalid\\",\\"preset\\",] are not recognized in docusaurus.config.js.
These field(s) [\\"invalidField\\",] are not recognized in docusaurus.config.js.
If you still want these fields to be in your configuration, put them in the 'customFields' attribute.
See https://v2.docusaurus.io/docs/docusaurus.config.js/#customfields"
`;

exports[`validateConfig throw error for unknown field 1`] = `
exports[`normalizeConfig should throw error for unknown field 1`] = `
"These field(s) [\\"invalid\\",] are not recognized in docusaurus.config.js.
If you still want these fields to be in your configuration, put them in the 'customFields' attribute.
See https://v2.docusaurus.io/docs/docusaurus.config.js/#customfields"
`;

exports[`validateConfig throw error if css doesn't have href 1`] = `
exports[`normalizeConfig should throw error if css doesn't have href 1`] = `
"\\"stylesheets[1]\\" does not match any of the allowed types
"
`;

exports[`validateConfig throw error if plugins is not array 1`] = `
exports[`normalizeConfig should throw error if plugins is not array 1`] = `
"\\"plugins\\" must be an array
"
`;

exports[`validateConfig throw error if presets is not array 1`] = `
exports[`normalizeConfig should throw error if presets is not array 1`] = `
"\\"presets\\" must be an array
"
`;

exports[`validateConfig throw error if scripts doesn't have src 1`] = `
exports[`normalizeConfig should throw error if scripts doesn't have src 1`] = `
"\\"scripts[1]\\" does not match any of the allowed types
"
`;

exports[`validateConfig throw error if themes is not array 1`] = `
exports[`normalizeConfig should throw error if themes is not array 1`] = `
"\\"themes\\" must be an array
"
`;
102 changes: 66 additions & 36 deletions packages/docusaurus/src/server/__tests__/configValidation.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,94 +15,124 @@ const baseConfig = {
url: 'https://mysite.com',
};

const testConfig = (config) => validateConfig({...baseConfig, ...config});
const normalizeConfig = (config) => validateConfig({...baseConfig, ...config});

describe('validateConfig', () => {
test('normalize config', () => {
const value = testConfig({});
describe('normalizeConfig', () => {
test('should normalize empty config', () => {
const value = normalizeConfig({});
expect(value).toEqual({
...DEFAULT_CONFIG,
...baseConfig,
});
});

test('throw error for unknown field', () => {
test('should accept correctly defined config options', () => {
const userConfig = {
...DEFAULT_CONFIG,
...baseConfig,
tagline: 'my awesome site',
organizationName: 'facebook',
projectName: 'docusaurus',
githubHost: 'github.com',
customFields: {
myCustomField: '42',
},
scripts: [
{
src: `/analytics.js`,
async: true,
defer: true,
},
],
stylesheets: [
{
href: '/katex/katex.min.css',
type: 'text/css',
crossorigin: 'anonymous',
},
],
};
const normalizedConfig = normalizeConfig(userConfig);
expect(normalizedConfig).toEqual(userConfig);
});

test('should accept custom field in config', () => {
const value = normalizeConfig({
customFields: {
author: 'anshul',
},
});
expect(value).toEqual({
...DEFAULT_CONFIG,
...baseConfig,
customFields: {
author: 'anshul',
},
});
});

test('should throw error for unknown field', () => {
expect(() => {
testConfig({
normalizeConfig({
invalid: true,
});
}).toThrowErrorMatchingSnapshot();
});

test('throw error for baseUrl without trailing `/`', () => {
test('should throw error for baseUrl without trailing `/`', () => {
expect(() => {
testConfig({
normalizeConfig({
baseUrl: 'noslash',
});
}).toThrowErrorMatchingSnapshot();
});

test('throw error if plugins is not array', () => {
test('should throw error if plugins is not array', () => {
expect(() => {
testConfig({
normalizeConfig({
plugins: {},
});
}).toThrowErrorMatchingSnapshot();
});

test('throw error if themes is not array', () => {
test('should throw error if themes is not array', () => {
expect(() => {
testConfig({
normalizeConfig({
themes: {},
});
}).toThrowErrorMatchingSnapshot();
});

test('throw error if presets is not array', () => {
test('should throw error if presets is not array', () => {
expect(() => {
testConfig({
normalizeConfig({
presets: {},
});
}).toThrowErrorMatchingSnapshot();
});

test("throw error if scripts doesn't have src", () => {
test("should throw error if scripts doesn't have src", () => {
expect(() => {
testConfig({
normalizeConfig({
scripts: ['https://some.com', {}],
});
}).toThrowErrorMatchingSnapshot();
});

test("throw error if css doesn't have href", () => {
test("should throw error if css doesn't have href", () => {
expect(() => {
testConfig({
normalizeConfig({
stylesheets: ['https://somescript.com', {type: 'text/css'}],
});
}).toThrowErrorMatchingSnapshot();
});

test('custom field in config', () => {
const value = testConfig({
customFields: {
author: 'anshul',
},
});
expect(value).toEqual({
...DEFAULT_CONFIG,
...baseConfig,
customFields: {
author: 'anshul',
},
});
});

test('throw error for required fields', () => {
test('should throw error for required fields', () => {
expect(
() =>
validateConfig(({
invalid: true,
preset: {},
invalidField: true,
presets: {},
stylesheets: {},
themes: {},
scripts: {},
Expand Down
6 changes: 3 additions & 3 deletions packages/docusaurus/src/server/configValidation.ts
Original file line number Diff line number Diff line change
Expand Up @@ -52,8 +52,8 @@ const ConfigSchema = Joi.object({
onBrokenLinks: Joi.string()
.equal('ignore', 'log', 'error', 'throw')
.default(DEFAULT_CONFIG.onBrokenLinks),
organizationName: Joi.string(),
projectName: Joi.string(),
organizationName: Joi.string().allow(''),
projectName: Joi.string().allow(''),
customFields: Joi.object().unknown().default(DEFAULT_CONFIG.customFields),
githubHost: Joi.string(),
plugins: Joi.array().items(PluginSchema).default(DEFAULT_CONFIG.plugins),
Expand All @@ -75,7 +75,7 @@ const ConfigSchema = Joi.object({
type: Joi.string().required(),
}).unknown(),
),
tagline: Joi.string(),
tagline: Joi.string().allow(''),
});

export function validateConfig(
Expand Down