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: Update plugin schema testing util and associated tests #27574

Merged
merged 11 commits into from
Oct 21, 2020
18 changes: 11 additions & 7 deletions packages/gatsby-plugin-mdx/__tests__/gatsby-node.js
Expand Up @@ -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`],
Expand All @@ -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`,
Expand Down
8 changes: 4 additions & 4 deletions packages/gatsby-plugin-netlify/src/__tests__/gatsby-node.js
Expand Up @@ -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`,
Expand All @@ -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`,
Expand All @@ -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`],
Expand Down
26 changes: 20 additions & 6 deletions packages/gatsby-plugin-offline/src/__tests__/gatsby-node.js
Expand Up @@ -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`,
Expand Down Expand Up @@ -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,
Expand Down
8 changes: 4 additions & 4 deletions packages/gatsby-plugin-sass/src/__tests__/gatsby-node.js
Expand Up @@ -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`,
Expand All @@ -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`,
Expand Down Expand Up @@ -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$/,
Expand Down
18 changes: 14 additions & 4 deletions packages/gatsby-plugin-typescript/src/__tests__/gatsby-node.js
Expand Up @@ -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`,
Expand All @@ -90,14 +90,24 @@ 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,
})

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]`])
})
})
})
3 changes: 2 additions & 1 deletion packages/gatsby-plugin-typescript/src/gatsby-node.js
Expand Up @@ -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) }),
})
}

Expand Down
4 changes: 2 additions & 2 deletions packages/gatsby-plugin-utils/README.md
Expand Up @@ -28,15 +28,15 @@ 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()
toVerify: Joi.boolean(),
})

// 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`,
})

Expand Down
Expand Up @@ -2,15 +2,15 @@ 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(),
nb: Joi.number(),
toVerify: Joi.boolean(),
})

const { isValid, errors } = testPluginOptionsSchema(pluginSchema, {
const { isValid, errors } = await testPluginOptionsSchema(pluginSchema, {
toVerify: `abcd`,
})

Expand All @@ -22,29 +22,29 @@ 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(),
nb: Joi.number(),
toVerify: Joi.boolean(),
})

const { isValid, errors } = testPluginOptionsSchema(pluginSchema, {
const { isValid, errors } = await testPluginOptionsSchema(pluginSchema, {
toVerify: `abcd`,
nb: `invalid value`,
})

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()
Expand Down Expand Up @@ -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`,
Expand All @@ -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()
Expand Down Expand Up @@ -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`,
Expand All @@ -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",
Expand All @@ -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,
})

Expand Down
36 changes: 12 additions & 24 deletions 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<string>
isValid: boolean
}

export function testPluginOptionsSchema<PluginOptions = object>(
export async function testPluginOptionsSchema(
pluginSchemaFunction: Exclude<GatsbyNode["pluginOptionsSchema"], undefined>,
pluginOptions: PluginOptions
): ITestPluginOptionsSchemaReturnType {
const pluginOptionsNames = Object.keys(pluginOptions)
pluginOptions: IPluginInfoOptions
): Promise<ITestPluginOptionsSchemaReturnType> {
const pluginSchema = pluginSchemaFunction({ Joi })
const errors: Array<string> = []

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: [] }
}