diff --git a/packages/gatsby-plugin-mdx/__tests__/gatsby-node.js b/packages/gatsby-plugin-mdx/__tests__/gatsby-node.js index 21b3dc1eae8cf..18c798689f8c4 100644 --- a/packages/gatsby-plugin-mdx/__tests__/gatsby-node.js +++ b/packages/gatsby-plugin-mdx/__tests__/gatsby-node.js @@ -2,18 +2,22 @@ import { testPluginOptionsSchema } from "gatsby-plugin-utils" import { pluginOptionsSchema } from "../gatsby-node" describe(`pluginOptionsSchema`, () => { - it(`should provide meaningful errors when fields are invalid`, () => { + it(`should provide meaningful errors when fields are invalid`, async () => { const expectedErrors = [ - `"extensions" "[0]" must be a string. "[1]" must be a string. "[2]" must be a string`, + `"extensions[0]" must be a string`, + `"extensions[1]" must be a string`, + `"extensions[2]" must be a string`, `"defaultLayout" must be of type object`, - `"gatsbyRemarkPlugins" "[0]" does not match any of the allowed types. "[1]" does not match any of the allowed types`, + `"gatsbyRemarkPlugins[0]" does not match any of the allowed types`, + `"gatsbyRemarkPlugins[1]" does not match any of the allowed types`, `"remarkPlugins" must be an array`, `"rehypePlugins" must be an array`, - `"mediaTypes" "[0]" must be a string. "[1]" must be a string`, + `"mediaTypes[0]" must be a string`, + `"mediaTypes[1]" must be a string`, `"shouldBlockNodeFromTransformation" must have an arity lesser or equal to 1`, ] - const { errors } = testPluginOptionsSchema(pluginOptionsSchema, { + const { errors } = await testPluginOptionsSchema(pluginOptionsSchema, { extensions: [1, 2, 3], defaultLayout: `this should be an object`, gatsbyRemarkPlugins: [1, { not: `existing prop` }, `valid one`], @@ -26,8 +30,8 @@ describe(`pluginOptionsSchema`, () => { expect(errors).toEqual(expectedErrors) }) - it(`should validate the schema`, () => { - const { isValid } = testPluginOptionsSchema(pluginOptionsSchema, { + it(`should validate the schema`, async () => { + const { isValid } = await testPluginOptionsSchema(pluginOptionsSchema, { extensions: [`.mdx`, `.mdxx`], defaultLayout: { posts: `../post-layout.js`, diff --git a/packages/gatsby-plugin-netlify/src/__tests__/gatsby-node.js b/packages/gatsby-plugin-netlify/src/__tests__/gatsby-node.js index 8eb6b9a55da8a..89366303defaa 100644 --- a/packages/gatsby-plugin-netlify/src/__tests__/gatsby-node.js +++ b/packages/gatsby-plugin-netlify/src/__tests__/gatsby-node.js @@ -2,7 +2,7 @@ import { testPluginOptionsSchema } from "gatsby-plugin-utils" import { pluginOptionsSchema } from "../gatsby-node" describe(`gatsby-node.js`, () => { - it(`should provide meaningful errors when fields are invalid`, () => { + it(`should provide meaningful errors when fields are invalid`, async () => { const expectedErrors = [ `"headers" must be of type object`, `"allPageHeaders" must be an array`, @@ -13,7 +13,7 @@ describe(`gatsby-node.js`, () => { `"generateMatchPathRewrites" must be a boolean`, ] - const { errors } = testPluginOptionsSchema(pluginOptionsSchema, { + const { errors } = await testPluginOptionsSchema(pluginOptionsSchema, { headers: `this should be an object`, allPageHeaders: `this should be an array`, mergeSecurityHeaders: `this should be a boolean`, @@ -26,8 +26,8 @@ describe(`gatsby-node.js`, () => { expect(errors).toEqual(expectedErrors) }) - it(`should validate the schema`, () => { - const { isValid } = testPluginOptionsSchema(pluginOptionsSchema, { + it(`should validate the schema`, async () => { + const { isValid } = await testPluginOptionsSchema(pluginOptionsSchema, { headers: { "/some-page": [`Bearer: Some-Magic-Token`], "/some-other-page": [`some`, `great`, `headers`], diff --git a/packages/gatsby-plugin-offline/src/__tests__/gatsby-node.js b/packages/gatsby-plugin-offline/src/__tests__/gatsby-node.js index 31db000724069..d1403ffeccf46 100644 --- a/packages/gatsby-plugin-offline/src/__tests__/gatsby-node.js +++ b/packages/gatsby-plugin-offline/src/__tests__/gatsby-node.js @@ -111,15 +111,29 @@ describe(`onPostBuild`, () => { }) describe(`pluginOptionsSchema`, () => { - it(`should provide meaningful errors when fields are invalid`, () => { + it(`should provide meaningful errors when fields are invalid`, async () => { const expectedErrors = [ - `"precachePages" "[0]" must be a string. "[1]" must be a string. "[2]" must be a string`, + `"precachePages[0]" must be a string`, + `"precachePages[1]" must be a string`, + `"precachePages[2]" must be a string`, `"appendScript" must be a string`, `"debug" must be a boolean`, - `"workboxConfig" "importWorkboxFrom" must be a string. "globDirectory" must be a string. "globPatterns[0]" must be a string. "globPatterns[1]" must be a string. "globPatterns[2]" must be a string. "modifyURLPrefix./" must be a string. "cacheId" must be a string. "dontCacheBustURLsMatching" must be of type object. "runtimeCaching[0].handler" must be one of [StaleWhileRevalidate, CacheFirst, NetworkFirst, NetworkOnly, CacheOnly]. "runtimeCaching[1]" must be of type object. "runtimeCaching[2]" must be of type object. "skipWaiting" must be a boolean. "clientsClaim" must be a boolean`, + `"workboxConfig.importWorkboxFrom" must be a string`, + `"workboxConfig.globDirectory" must be a string`, + `"workboxConfig.globPatterns[0]" must be a string`, + `"workboxConfig.globPatterns[1]" must be a string`, + `"workboxConfig.globPatterns[2]" must be a string`, + `"workboxConfig.modifyURLPrefix./" must be a string`, + `"workboxConfig.cacheId" must be a string`, + `"workboxConfig.dontCacheBustURLsMatching" must be of type object`, + `"workboxConfig.runtimeCaching[0].handler" must be one of [StaleWhileRevalidate, CacheFirst, NetworkFirst, NetworkOnly, CacheOnly]`, + `"workboxConfig.runtimeCaching[1]" must be of type object`, + `"workboxConfig.runtimeCaching[2]" must be of type object`, + `"workboxConfig.skipWaiting" must be a boolean`, + `"workboxConfig.clientsClaim" must be a boolean`, ] - const { errors } = testPluginOptionsSchema(pluginOptionsSchema, { + const { errors } = await testPluginOptionsSchema(pluginOptionsSchema, { precachePages: [1, 2, 3], appendScript: 1223, debug: `This should be a boolean`, @@ -148,8 +162,8 @@ describe(`pluginOptionsSchema`, () => { expect(errors).toEqual(expectedErrors) }) - it(`should validate the schema`, () => { - const { isValid } = testPluginOptionsSchema(pluginOptionsSchema, { + it(`should validate the schema`, async () => { + const { isValid } = await testPluginOptionsSchema(pluginOptionsSchema, { precachePages: [`/about-us/`, `/projects/*`], appendScript: `src/custom-sw-code.js`, debug: true, diff --git a/packages/gatsby-plugin-sass/src/__tests__/gatsby-node.js b/packages/gatsby-plugin-sass/src/__tests__/gatsby-node.js index ce7dc55fd8182..ef75114d919b0 100644 --- a/packages/gatsby-plugin-sass/src/__tests__/gatsby-node.js +++ b/packages/gatsby-plugin-sass/src/__tests__/gatsby-node.js @@ -58,7 +58,7 @@ describe(`gatsby-plugin-sass`, () => { }) describe(`pluginOptionsSchema`, () => { - it(`should provide meaningful errors when fields are invalid`, () => { + it(`should provide meaningful errors when fields are invalid`, async () => { const expectedErrors = [ `"implementation" must be of type object`, `"postCssPlugins" must be an array`, @@ -85,7 +85,7 @@ describe(`pluginOptionsSchema`, () => { `"sourceMapRoot" must be a string`, ] - const { errors } = testPluginOptionsSchema(pluginOptionsSchema, { + const { errors } = await testPluginOptionsSchema(pluginOptionsSchema, { implementation: `This should be a require() thing`, postCssPlugins: `This should be an array of postCss plugins`, sassRuleTest: `This should be a regexp`, @@ -114,8 +114,8 @@ describe(`pluginOptionsSchema`, () => { expect(errors).toEqual(expectedErrors) }) - it(`should validate the schema`, () => { - const { isValid } = testPluginOptionsSchema(pluginOptionsSchema, { + it(`should validate the schema`, async () => { + const { isValid } = await testPluginOptionsSchema(pluginOptionsSchema, { implementation: require(`../gatsby-node.js`), postCssPlugins: [{ post: `CSS plugin` }], sassRuleTest: /\.global\.s(a|c)ss$/, diff --git a/packages/gatsby-plugin-typescript/src/__tests__/gatsby-node.js b/packages/gatsby-plugin-typescript/src/__tests__/gatsby-node.js index bf94356c433ed..1dce01b41caf1 100644 --- a/packages/gatsby-plugin-typescript/src/__tests__/gatsby-node.js +++ b/packages/gatsby-plugin-typescript/src/__tests__/gatsby-node.js @@ -74,14 +74,14 @@ describe(`gatsby-plugin-typescript`, () => { }) describe(`plugin schema`, () => { - it(`should provide meaningful errors when fields are invalid`, () => { + it(`should provide meaningful errors when fields are invalid`, async () => { const expectedErrors = [ `"isTSX" must be a boolean`, `"jsxPragma" must be a string`, `"allExtensions" must be a boolean`, ] - const { errors } = testPluginOptionsSchema(pluginOptionsSchema, { + const { errors } = await testPluginOptionsSchema(pluginOptionsSchema, { isTSX: `this should be a boolean`, jsxPragma: 123, allExtensions: `this should be a boolean`, @@ -90,8 +90,8 @@ describe(`gatsby-plugin-typescript`, () => { expect(errors).toEqual(expectedErrors) }) - it(`should validate the schema`, () => { - const { isValid } = testPluginOptionsSchema(pluginOptionsSchema, { + it(`should validate the schema`, async () => { + const { isValid } = await testPluginOptionsSchema(pluginOptionsSchema, { isTSX: false, jsxPragma: `ReactFunction`, allExtensions: false, @@ -99,5 +99,15 @@ describe(`gatsby-plugin-typescript`, () => { expect(isValid).toBe(true) }) + + it(`should break when isTSX doesn't match allExtensions`, async () => { + const { errors } = await testPluginOptionsSchema(pluginOptionsSchema, { + isTSX: true, + jsxPragma: `ReactFunction`, + allExtensions: false, + }) + + expect(errors).toEqual([`"allExtensions" must be [true]`]) + }) }) }) diff --git a/packages/gatsby-plugin-typescript/src/gatsby-node.js b/packages/gatsby-plugin-typescript/src/gatsby-node.js index 29bf30ea5bc28..f168344546ee4 100644 --- a/packages/gatsby-plugin-typescript/src/gatsby-node.js +++ b/packages/gatsby-plugin-typescript/src/gatsby-node.js @@ -46,7 +46,8 @@ if (process.env.GATSBY_EXPERIMENTAL_PLUGIN_OPTION_VALIDATION) { .default(`React`), allExtensions: Joi.boolean() .description(`Indicates that every file should be parsed as TS or TSX.`) - .default(false), + .default(false) + .when(`isTSX`, { is: true, then: Joi.valid(true) }), }) } diff --git a/packages/gatsby-plugin-utils/README.md b/packages/gatsby-plugin-utils/README.md index 5ef796fbfc7f7..b843edaf5f654 100644 --- a/packages/gatsby-plugin-utils/README.md +++ b/packages/gatsby-plugin-utils/README.md @@ -28,7 +28,7 @@ Utility to validate and test plugin options schemas. An example of a plugin opti // This is an example using Jest (https://jestjs.io/) import { testPluginOptionsSchema } from "gatsby-plugin-utils" -it(`should partially validate one value of a schema`, () => { +it(`should partially validate one value of a schema`, async () => { const pluginSchema = ({ Joi }) => Joi.object({ someOtherValue: Joi.string() @@ -36,7 +36,7 @@ it(`should partially validate one value of a schema`, () => { }) // Only the "toVerify" key of the schema will be verified in this test - const { isValid, errors } = testPluginOptionsSchema(pluginSchema, { + const { isValid, errors } = await testPluginOptionsSchema(pluginSchema, { toVerify: `abcd`, }) diff --git a/packages/gatsby-plugin-utils/src/__tests__/test-plugin-options-schema.ts b/packages/gatsby-plugin-utils/src/__tests__/test-plugin-options-schema.ts index c016f409e72fe..610947ea94025 100644 --- a/packages/gatsby-plugin-utils/src/__tests__/test-plugin-options-schema.ts +++ b/packages/gatsby-plugin-utils/src/__tests__/test-plugin-options-schema.ts @@ -2,7 +2,7 @@ import { testPluginOptionsSchema } from "../test-plugin-options-schema" import { ObjectSchema } from "../joi" describe(`testPluginOptionsSchema`, () => { - it(`should partially validate one value of a schema`, () => { + it(`should partially validate one value of a schema`, async () => { const pluginSchema = ({ Joi }): ObjectSchema => Joi.object({ str: Joi.string(), @@ -10,7 +10,7 @@ describe(`testPluginOptionsSchema`, () => { toVerify: Joi.boolean(), }) - const { isValid, errors } = testPluginOptionsSchema(pluginSchema, { + const { isValid, errors } = await testPluginOptionsSchema(pluginSchema, { toVerify: `abcd`, }) @@ -22,7 +22,7 @@ describe(`testPluginOptionsSchema`, () => { `) }) - it(`should partially validate multiples value of a schema`, () => { + it(`should partially validate multiples value of a schema`, async () => { const pluginSchema = ({ Joi }): ObjectSchema => Joi.object({ str: Joi.string(), @@ -30,7 +30,7 @@ describe(`testPluginOptionsSchema`, () => { toVerify: Joi.boolean(), }) - const { isValid, errors } = testPluginOptionsSchema(pluginSchema, { + const { isValid, errors } = await testPluginOptionsSchema(pluginSchema, { toVerify: `abcd`, nb: `invalid value`, }) @@ -38,13 +38,13 @@ describe(`testPluginOptionsSchema`, () => { expect(isValid).toBe(false) expect(errors).toMatchInlineSnapshot(` Array [ - "\\"toVerify\\" must be a boolean", "\\"nb\\" must be a number", + "\\"toVerify\\" must be a boolean", ] `) }) - it(`should validate half of a real world plugin schema`, () => { + it(`should validate half of a real world plugin schema`, async () => { const pluginSchema = ({ Joi }): ObjectSchema => Joi.object({ trackingId: Joi.string() @@ -85,7 +85,7 @@ describe(`testPluginOptionsSchema`, () => { cookieDomain: Joi.string(), }) - const { isValid, errors } = testPluginOptionsSchema(pluginSchema, { + const { isValid, errors } = await testPluginOptionsSchema(pluginSchema, { trackingId: undefined, head: `invalid boolean value`, anonymize: `invalid boolean value`, @@ -100,12 +100,14 @@ describe(`testPluginOptionsSchema`, () => { "\\"head\\" must be a boolean", "\\"anonymize\\" must be a boolean", "\\"respectDNT\\" must be a boolean", - "\\"exclude\\" \\"[0]\\" must be a string. \\"[1]\\" must be a string. \\"[2]\\" must be a string", + "\\"exclude[0]\\" must be a string", + "\\"exclude[1]\\" must be a string", + "\\"exclude[2]\\" must be a string", ] `) }) - it(`should validate an entire real world plugin schema`, () => { + it(`should validate an entire real world plugin schema`, async () => { const pluginSchema = ({ Joi }): ObjectSchema => Joi.object({ trackingId: Joi.string() @@ -146,7 +148,7 @@ describe(`testPluginOptionsSchema`, () => { cookieDomain: Joi.string(), }) - const { isValid, errors } = testPluginOptionsSchema(pluginSchema, { + const { isValid, errors } = await testPluginOptionsSchema(pluginSchema, { trackingId: undefined, head: `invalid boolean value`, anonymize: `invalid boolean value`, @@ -169,7 +171,9 @@ describe(`testPluginOptionsSchema`, () => { "\\"head\\" must be a boolean", "\\"anonymize\\" must be a boolean", "\\"respectDNT\\" must be a boolean", - "\\"exclude\\" \\"[0]\\" must be a string. \\"[1]\\" must be a string. \\"[2]\\" must be a string", + "\\"exclude[0]\\" must be a string", + "\\"exclude[1]\\" must be a string", + "\\"exclude[2]\\" must be a string", "\\"pageTransitionDelay\\" must be a number", "\\"optimizeId\\" must be a string", "\\"experimentId\\" must be a string", @@ -182,13 +186,13 @@ describe(`testPluginOptionsSchema`, () => { `) }) - it(`should check the validity of a schema`, () => { + it(`should check the validity of a schema`, async () => { const pluginSchema = ({ Joi }): ObjectSchema => Joi.object({ toVerify: Joi.boolean(), }) - const { isValid, errors } = testPluginOptionsSchema(pluginSchema, { + const { isValid, errors } = await testPluginOptionsSchema(pluginSchema, { toVerify: false, }) diff --git a/packages/gatsby-plugin-utils/src/test-plugin-options-schema.ts b/packages/gatsby-plugin-utils/src/test-plugin-options-schema.ts index 69c736cf4b038..8b72d6bcde628 100644 --- a/packages/gatsby-plugin-utils/src/test-plugin-options-schema.ts +++ b/packages/gatsby-plugin-utils/src/test-plugin-options-schema.ts @@ -1,37 +1,25 @@ import { Joi } from "./joi" import { GatsbyNode } from "gatsby" +import { validateOptionsSchema } from "./validate" +import { IPluginInfoOptions } from "gatsby" interface ITestPluginOptionsSchemaReturnType { errors: Array isValid: boolean } -export function testPluginOptionsSchema( +export async function testPluginOptionsSchema( pluginSchemaFunction: Exclude, - pluginOptions: PluginOptions -): ITestPluginOptionsSchemaReturnType { - const pluginOptionsNames = Object.keys(pluginOptions) + pluginOptions: IPluginInfoOptions +): Promise { const pluginSchema = pluginSchemaFunction({ Joi }) - const errors: Array = [] - pluginOptionsNames.forEach(pluginOptionName => { - const partialSchema = pluginSchema.extract(pluginOptionName) - const { error } = partialSchema.validate(pluginOptions[pluginOptionName], { - abortEarly: false, - }) + try { + await validateOptionsSchema(pluginSchema, pluginOptions) + } catch (e) { + const errors = e.details.map(detail => detail.message) + return { isValid: false, errors } + } - if (error) { - const errorMessage = error.message - - // In the case of an array, "value" does not exist in the error message - // and so we can't replace it with the plugin option name, we have to concat it - const message = errorMessage.includes(`"value"`) - ? errorMessage.replace(`"value"`, `"${pluginOptionName}"`) - : `"${pluginOptionName}" ${errorMessage}` - - errors.push(message) - } - }) - - return { isValid: errors.length === 0, errors } + return { isValid: true, errors: [] } } diff --git a/packages/gatsby-remark-images/src/__tests__/gatsby-node.js b/packages/gatsby-remark-images/src/__tests__/gatsby-node.js index bb0ad12982cd3..9a2dbf9a2b3a9 100644 --- a/packages/gatsby-remark-images/src/__tests__/gatsby-node.js +++ b/packages/gatsby-remark-images/src/__tests__/gatsby-node.js @@ -2,7 +2,7 @@ import { testPluginOptionsSchema } from "gatsby-plugin-utils" import { pluginOptionsSchema } from "../gatsby-node" describe(`pluginOptionsSchema`, () => { - it(`should provide meaningful errors when fields are invalid`, () => { + it(`should provide meaningful errors when fields are invalid`, async () => { const expectedErrors = [ `"maxWidth" must be a number`, `"linkImagesToOriginal" must be a boolean`, @@ -20,7 +20,7 @@ describe(`pluginOptionsSchema`, () => { `"srcSetBreakpoints" must be an array`, ] - const { errors } = testPluginOptionsSchema(pluginOptionsSchema, { + const { errors } = await testPluginOptionsSchema(pluginOptionsSchema, { maxWidth: `This should be a number`, linkImagesToOriginal: `This should be a boolean`, showCaptions: `This should be a boolean`, @@ -40,8 +40,8 @@ describe(`pluginOptionsSchema`, () => { expect(errors).toEqual(expectedErrors) }) - it(`should validate the schema`, () => { - const { isValid } = testPluginOptionsSchema(pluginOptionsSchema, { + it(`should validate the schema`, async () => { + const { isValid } = await testPluginOptionsSchema(pluginOptionsSchema, { maxWidth: 700, linkImagesToOriginal: false, showCaptions: true, diff --git a/packages/gatsby-transformer-remark/src/__tests__/gatsby-node.js b/packages/gatsby-transformer-remark/src/__tests__/gatsby-node.js index 6830026cfdec8..c6f5af36e83a2 100644 --- a/packages/gatsby-transformer-remark/src/__tests__/gatsby-node.js +++ b/packages/gatsby-transformer-remark/src/__tests__/gatsby-node.js @@ -2,7 +2,7 @@ import { testPluginOptionsSchema } from "gatsby-plugin-utils" import { pluginOptionsSchema } from "../gatsby-node" describe(`gatsby-node.js`, () => { - it(`should provide meaningful errors when fields are invalid`, () => { + it(`should provide meaningful errors when fields are invalid`, async () => { const expectedErrors = [ `"commonmark" must be a boolean`, `"footnotes" must be a boolean`, @@ -11,7 +11,7 @@ describe(`gatsby-node.js`, () => { `"plugins" must be an array`, ] - const { errors } = testPluginOptionsSchema(pluginOptionsSchema, { + const { errors } = await testPluginOptionsSchema(pluginOptionsSchema, { commonmark: `this should be a boolean`, footnotes: `this should be a boolean`, pedantic: `this should be a boolean`, @@ -22,8 +22,8 @@ describe(`gatsby-node.js`, () => { expect(errors).toEqual(expectedErrors) }) - it(`should validate the schema`, () => { - const { isValid } = testPluginOptionsSchema(pluginOptionsSchema, { + it(`should validate the schema`, async () => { + const { isValid } = await testPluginOptionsSchema(pluginOptionsSchema, { commonmark: false, footnotes: false, pedantic: false,