Skip to content

Commit

Permalink
fix: Update plugin schema testing util and associated tests (#27574)
Browse files Browse the repository at this point in the history
* Fix default

* validate matching options, add associated test

* fix error test

* it's only the mismatch that matters

* remove test, the utility can't handle external calls yet

* use when instead of async

* remove keys now that we're not using external

* fix typescript

* fix expected errors in tests

* missed some tests
  • Loading branch information
LB committed Oct 21, 2020
1 parent dc0ce0c commit 6d81283
Show file tree
Hide file tree
Showing 11 changed files with 94 additions and 73 deletions.
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: [] }
}

0 comments on commit 6d81283

Please sign in to comment.