diff --git a/package-lock.json b/package-lock.json index 0a394071..9e49bc5f 100644 --- a/package-lock.json +++ b/package-lock.json @@ -12,9 +12,9 @@ "dependencies": { "@sindresorhus/is": "4.6.0", "chalk": "4.1.2", - "joi": "17.4.2", "json-stringify-pretty-compact": "3.0.0", - "moment": "2.29.1" + "moment": "2.29.1", + "zod": "3.11.6" }, "devDependencies": { "@commitlint/cli": "16.2.3", @@ -924,17 +924,13 @@ "node": "^12.22.0 || ^14.17.0 || >=16.0.0" } }, - "node_modules/@hapi/hoek": { - "version": "9.2.1", - "resolved": "https://registry.npmjs.org/@hapi/hoek/-/hoek-9.2.1.tgz", - "integrity": "sha512-gfta+H8aziZsm8pZa0vj04KO6biEiisppNgA1kbJvFrrWu9Vm7eaUEy76DIxsuTaWvti5fkJVhllWc6ZTE+Mdw==" - }, - "node_modules/@hapi/topo": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/@hapi/topo/-/topo-5.1.0.tgz", - "integrity": "sha512-foQZKJig7Ob0BMAYBfcJk8d77QtOe7Wo4ox7ff1lQYoNNAb6jwcY1ncdoy2e9wQZzvNy7ODZCYJkK8kzmcAnAg==", - "dependencies": { - "@hapi/hoek": "^9.0.0" + "node_moxdules/@eslint/eslintrc/node_modules/ignore": { + "version": "4.0.6", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-4.0.6.tgz", + "integrity": "sha512-cyFDKrqc/YdcWFniJhzI42+AzS+gNwmUzOSFcRCQYwySuBBBy/KjuxWLZ/FHEH6Moq1NizMOBWyTcv8O4OZIMg==", + "dev": true, + "engines": { + "node": ">= 4" } }, "node_modules/@humanwhocodes/config-array": { @@ -1754,24 +1750,6 @@ "semantic-release": ">=18.0.0-beta.1" } }, - "node_modules/@sideway/address": { - "version": "4.1.3", - "resolved": "https://registry.npmjs.org/@sideway/address/-/address-4.1.3.tgz", - "integrity": "sha512-8ncEUtmnTsMmL7z1YPB47kPUq7LpKWJNFPsRzHiIajGC5uXlWGn+AmkYPcHNl8S4tcEGx+cnORnNYaw2wvL+LQ==", - "dependencies": { - "@hapi/hoek": "^9.0.0" - } - }, - "node_modules/@sideway/formula": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/@sideway/formula/-/formula-3.0.0.tgz", - "integrity": "sha512-vHe7wZ4NOXVfkoRb8T5otiENVlT7a3IAiw7H5M2+GO+9CDgcVUUsX1zalAztCmwyOr2RUTGJdgB+ZvSVqmdHmg==" - }, - "node_modules/@sideway/pinpoint": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/@sideway/pinpoint/-/pinpoint-2.0.0.tgz", - "integrity": "sha512-RNiOoTPkptFtSVzQevY/yWtZwf/RxyVnPy/OcA9HBM3MlGDnBEYL5B41H0MTn0Uec8Hi+2qUtTfG2WWZBmMejQ==" - }, "node_modules/@sindresorhus/is": { "version": "4.6.0", "resolved": "https://registry.npmjs.org/@sindresorhus/is/-/is-4.6.0.tgz", @@ -5887,18 +5865,6 @@ "url": "https://github.com/chalk/supports-color?sponsor=1" } }, - "node_modules/joi": { - "version": "17.4.2", - "resolved": "https://registry.npmjs.org/joi/-/joi-17.4.2.tgz", - "integrity": "sha512-Lm56PP+n0+Z2A2rfRvsfWVDXGEWjXxatPopkQ8qQ5mxCEhwHG+Ettgg5o98FFaxilOxozoa14cFhrE/hOzh/Nw==", - "dependencies": { - "@hapi/hoek": "^9.0.0", - "@hapi/topo": "^5.0.0", - "@sideway/address": "^4.1.0", - "@sideway/formula": "^3.0.0", - "@sideway/pinpoint": "^2.0.0" - } - }, "node_modules/js-tokens": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", @@ -12198,6 +12164,14 @@ "funding": { "url": "https://github.com/sponsors/sindresorhus" } + }, + "node_modules/zod": { + "version": "3.11.6", + "resolved": "https://registry.npmjs.org/zod/-/zod-3.11.6.tgz", + "integrity": "sha512-daZ80A81I3/9lIydI44motWe6n59kRBfNzTuS2bfzVh1nAXi667TOTWWtatxyG+fwgNUiagSj/CWZwRRbevJIg==", + "funding": { + "url": "https://github.com/sponsors/colinhacks" + } } }, "dependencies": { @@ -12865,19 +12839,6 @@ "strip-json-comments": "^3.1.1" } }, - "@hapi/hoek": { - "version": "9.2.1", - "resolved": "https://registry.npmjs.org/@hapi/hoek/-/hoek-9.2.1.tgz", - "integrity": "sha512-gfta+H8aziZsm8pZa0vj04KO6biEiisppNgA1kbJvFrrWu9Vm7eaUEy76DIxsuTaWvti5fkJVhllWc6ZTE+Mdw==" - }, - "@hapi/topo": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/@hapi/topo/-/topo-5.1.0.tgz", - "integrity": "sha512-foQZKJig7Ob0BMAYBfcJk8d77QtOe7Wo4ox7ff1lQYoNNAb6jwcY1ncdoy2e9wQZzvNy7ODZCYJkK8kzmcAnAg==", - "requires": { - "@hapi/hoek": "^9.0.0" - } - }, "@humanwhocodes/config-array": { "version": "0.9.5", "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.9.5.tgz", @@ -13546,24 +13507,6 @@ "read-pkg-up": "^7.0.0" } }, - "@sideway/address": { - "version": "4.1.3", - "resolved": "https://registry.npmjs.org/@sideway/address/-/address-4.1.3.tgz", - "integrity": "sha512-8ncEUtmnTsMmL7z1YPB47kPUq7LpKWJNFPsRzHiIajGC5uXlWGn+AmkYPcHNl8S4tcEGx+cnORnNYaw2wvL+LQ==", - "requires": { - "@hapi/hoek": "^9.0.0" - } - }, - "@sideway/formula": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/@sideway/formula/-/formula-3.0.0.tgz", - "integrity": "sha512-vHe7wZ4NOXVfkoRb8T5otiENVlT7a3IAiw7H5M2+GO+9CDgcVUUsX1zalAztCmwyOr2RUTGJdgB+ZvSVqmdHmg==" - }, - "@sideway/pinpoint": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/@sideway/pinpoint/-/pinpoint-2.0.0.tgz", - "integrity": "sha512-RNiOoTPkptFtSVzQevY/yWtZwf/RxyVnPy/OcA9HBM3MlGDnBEYL5B41H0MTn0Uec8Hi+2qUtTfG2WWZBmMejQ==" - }, "@sindresorhus/is": { "version": "4.6.0", "resolved": "https://registry.npmjs.org/@sindresorhus/is/-/is-4.6.0.tgz", @@ -16640,18 +16583,6 @@ } } }, - "joi": { - "version": "17.4.2", - "resolved": "https://registry.npmjs.org/joi/-/joi-17.4.2.tgz", - "integrity": "sha512-Lm56PP+n0+Z2A2rfRvsfWVDXGEWjXxatPopkQ8qQ5mxCEhwHG+Ettgg5o98FFaxilOxozoa14cFhrE/hOzh/Nw==", - "requires": { - "@hapi/hoek": "^9.0.0", - "@hapi/topo": "^5.0.0", - "@sideway/address": "^4.1.0", - "@sideway/formula": "^3.0.0", - "@sideway/pinpoint": "^2.0.0" - } - }, "js-tokens": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", @@ -21279,6 +21210,11 @@ "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", "dev": true + }, + "zod": { + "version": "3.11.6", + "resolved": "https://registry.npmjs.org/zod/-/zod-3.11.6.tgz", + "integrity": "sha512-daZ80A81I3/9lIydI44motWe6n59kRBfNzTuS2bfzVh1nAXi667TOTWWtatxyG+fwgNUiagSj/CWZwRRbevJIg==" } } } diff --git a/package.json b/package.json index 730ab00a..44051596 100644 --- a/package.json +++ b/package.json @@ -91,8 +91,8 @@ "dependencies": { "@sindresorhus/is": "4.6.0", "chalk": "4.1.2", - "joi": "17.4.2", "json-stringify-pretty-compact": "3.0.0", - "moment": "2.29.1" + "moment": "2.29.1", + "zod": "3.11.6" } } diff --git a/src/bunyan-pretty-stream.ts b/src/bunyan-pretty-stream.ts index 853460ae..2e442ec7 100644 --- a/src/bunyan-pretty-stream.ts +++ b/src/bunyan-pretty-stream.ts @@ -1,9 +1,8 @@ -import { MergedOptions, Options, schema } from './options'; +import { Options, schema } from './options'; import { Transform, TransformCallback } from 'stream'; import { fromString, isBunyanRecord } from './bunyan-record'; import { Formatter } from './formatter'; import is from '@sindresorhus/is'; -import { joi } from './helpers'; class PrettyStream extends Transform { private _formatter: Formatter; @@ -11,12 +10,7 @@ class PrettyStream extends Transform { constructor(options: Options = {}) { super({ objectMode: true }); - const validation = schema.validate(options); - if (!joi.isValid(validation, validation.value)) { - throw validation.error; - } - - this._formatter = new Formatter(validation.value); + this._formatter = new Formatter(schema.parse(options)); } _transform( diff --git a/src/formatter.ts b/src/formatter.ts index f6fc637d..8a84765e 100644 --- a/src/formatter.ts +++ b/src/formatter.ts @@ -79,17 +79,18 @@ class Formatter { return parsed; } - const extras = parsed.details[this._options.extrasKey]; - if (this._options.extrasKey !== '' && is.nonEmptyObject(extras)) { - Object.entries(parsed.details[this._options.extrasKey]).forEach( - ([key, value]) => { - if (this.isExtra(value)) { - parsed.extras[key] = value; - delete parsed.details[this._options.extrasKey][key]; - } - }, - ); - } else if (this._options.extrasKey === '') { + const extras = is.undefined(this._options.extrasKey) + ? {} + : parsed.details[this._options.extrasKey]; + if (this._options.extrasKey !== undefined && is.nonEmptyObject(extras)) { + const extrasKey = this._options.extrasKey; + Object.entries(parsed.details[extrasKey]).forEach(([key, value]) => { + if (this.isExtra(value)) { + parsed.extras[key] = value; + delete parsed.details[extrasKey][key]; + } + }); + } else if (this._options.extrasKey === undefined) { Object.entries(parsed.details).forEach(([key, value]) => { if (this.isExtra(value)) { parsed.extras[key] = value; diff --git a/src/helpers/index.ts b/src/helpers/index.ts deleted file mode 100644 index 83925e1b..00000000 --- a/src/helpers/index.ts +++ /dev/null @@ -1,3 +0,0 @@ -import { Joi, joi } from './joi'; - -export { Joi, joi }; diff --git a/src/helpers/joi.ts b/src/helpers/joi.ts deleted file mode 100644 index 8b9e61a4..00000000 --- a/src/helpers/joi.ts +++ /dev/null @@ -1,16 +0,0 @@ -import BaseJoi from 'joi'; - -function isValid( - validation: BaseJoi.ValidationResult, - value: unknown, -): value is T { - return validation.error === undefined; -} - -interface Joi extends BaseJoi.Root { - isValid(validation: BaseJoi.ValidationResult, value: unknown): value is T; -} - -const joi: Joi = { ...BaseJoi, isValid }; - -export { joi, BaseJoi as Joi }; diff --git a/src/helpers/joi.unit.test.ts b/src/helpers/joi.unit.test.ts deleted file mode 100644 index 65ff0662..00000000 --- a/src/helpers/joi.unit.test.ts +++ /dev/null @@ -1,39 +0,0 @@ -import { Joi, joi } from './joi'; -import { describe, expect, it } from '@jest/globals'; -import is from '@sindresorhus/is'; - -describe('isValid', () => { - it('is a function', () => { - expect(is.function_(joi.isValid)).toBe(true); - }); - - it('returns true when there are no validation errors', () => { - expect(joi.isValid({ value: {}, error: undefined }, null)).toBe(true); - }); - - it('returns false when there are validation errors', () => { - expect( - joi.isValid( - { - value: {}, - error: new joi.ValidationError('test error', null, null), - }, - null, - ), - ).toBe(false); - }); - - it('narrows the value type', () => { - const quote = - 'Beneath this mask there is an idea. And ideas are bulletproof.'; - const validation: Readonly = { - value: { quote }, - error: undefined, - }; - - expect(joi.isValid(validation, null)).toBe(true); - if (joi.isValid<{ quote: string }>(validation, validation.value)) { - expect(validation.value.quote).toEqual(quote); - } - }); -}); diff --git a/src/options.ts b/src/options.ts index 5769bb45..f1442e21 100644 --- a/src/options.ts +++ b/src/options.ts @@ -1,95 +1,76 @@ import { coreFields as bunyanCoreFields } from './bunyan-record'; -import { joi } from './helpers'; +import { z } from 'zod'; -interface Options { - enable?: { - time?: boolean; - name?: boolean; - hostname?: boolean; - pid?: boolean; - source?: boolean; - extras?: boolean; - }; +const options = z + .object({ + enable: z + .object({ + time: z.boolean().default(true), + name: z.boolean().default(false), + hostname: z.boolean().default(false), + pid: z.boolean().default(false), + source: z.boolean().default(false), + extras: z.boolean().default(true), + }) + .strict() + .default({}), + extrasKey: z + .string() + .min(1) + .regex(new RegExp(`^((?!(${bunyanCoreFields().join('|')})).)*$`)) + .optional(), // Exlcude bunyan core fields. + indent: z.number().int().nonnegative().default(4), + jsonIndent: z.number().int().nonnegative().default(2), + basePath: z.string().min(1).default('/'), + newLineCharacter: z.enum(['\r', '\n', '\r\n']).default('\n'), + time: z + .object({ + type: z.enum(['short', 'long', 'format']).default('long'), + /** + * Display local time instead of UTC. + */ + local: z.boolean().default(false), + /** + * Time format as specified by the `Moment.js` [format options]( + * https://momentjs.com/docs/#/displaying/format/). + * + * @note The time zone, `Z` or `ZZ`, should be omitted as `Z` is + * automatically to the format if the time is UTC. + * @note The `Z` display suffix for UTC times is automatically added to + * the format and should be omitted. + */ + format: z.string().min(1).default('YYYY-MM-DD[T]HH:mm:ss.SSS'), + }) + .strict() + .default({}), + }) + .strict(); - extrasKey?: string; - - indent?: number; - jsonIndent?: number; - basePath?: string; - newLineCharacter?: '\r' | '\n' | '\r\n'; - time?: { - type?: 'short' | 'long' | 'format'; - - /** - * Display local time instead of UTC. - */ - local?: boolean; - - /** - * Time format as specified by the `Moment.js` [format options]( - * https://momentjs.com/docs/#/displaying/format/). - * - * @note The time zone, `Z` or `ZZ`, should be omitted as `Z` is - * automatically to the format if the time is UTC. - * @note The `Z` display suffix for UTC times is automatically added to the - * format and should be omitted. - */ - format?: string; - }; -} - -interface InternalOptions { - extrasMaxValueLength: number; - time: { - formats: { - short: string; - long: string; - }; - }; -} - -type DeepRequired = { - [P in keyof T]-?: DeepRequired; -}; +const mergedOptions = options.merge( + z + .object({ + extrasMaxValueLength: z.number().int().positive().default(50), + time: z + .intersection( + options.shape.time, + z.object({ + formats: z + .object({ + short: z.string().default('HH:mm:ss.SSS'), + long: z.string().default('YYYY-MM-DD[T]HH:mm:ss.SSS'), + }) + .strict() + .default({}), + }), + ) + .default({}), + }) + .strict(), +); -type MergedOptions = DeepRequired; +type Options = z.input; +type MergedOptions = z.infer; -const schema = joi.object().keys({ - enable: joi - .object() - .keys({ - time: joi.boolean().default(true), - name: joi.boolean().default(false), - hostname: joi.boolean().default(false), - pid: joi.boolean().default(false), - source: joi.boolean().default(false), - extras: joi.boolean().default(true), - }) - .default(), - extrasKey: joi - .string() - .disallow(...bunyanCoreFields(), '') - .default(''), - indent: joi.number().integer().min(0).default(4), - jsonIndent: joi.number().integer().min(0).default(2), - basePath: joi.string().default('/'), - newLineCharacter: joi.string().valid('\r', '\n', '\r\n').default('\n'), - extrasMaxValueLength: joi.number().integer().positive().default(50), - time: joi - .object() - .keys({ - local: joi.boolean().default(false), - type: joi.string().valid('short', 'long', 'format').default('long'), - format: joi.string().default('YYYY-MM-DD[T]HH:mm:ss.SSS'), - formats: joi - .object() - .keys({ - short: joi.string().default('HH:mm:ss.SSS'), - long: joi.string().default('YYYY-MM-DD[T]HH:mm:ss.SSS'), - }) - .default(), - }) - .default(), -}); +const schema = mergedOptions; -export { Options, InternalOptions, MergedOptions, schema }; +export { Options, MergedOptions, schema }; diff --git a/src/options.unit.test.ts b/src/options.unit.test.ts index 9044b1c9..c3eb4539 100644 --- a/src/options.unit.test.ts +++ b/src/options.unit.test.ts @@ -1,4 +1,4 @@ -import { InternalOptions, Options, schema } from './options'; +import { Options, schema } from './options'; import { describe, expect, it } from '@jest/globals'; import clone from 'clone'; import { coreFields } from './bunyan-record'; @@ -6,6 +6,9 @@ import dotProp from 'dot-prop'; import is from '@sindresorhus/is'; function stringify(value: unknown): string { + if (is.undefined(value)) { + return 'undefined'; + } const stringifiedValue = is.string(value) ? value : JSON.stringify(value); return stringifiedValue.replace(/\r/g, '\\r').replace(/\n/g, '\\n'); @@ -14,7 +17,6 @@ function stringify(value: unknown): string { describe('schema', () => { const defaults: Readonly<{ options: Options; - internalOptions: InternalOptions; }> = { options: { enable: { @@ -36,56 +38,77 @@ describe('schema', () => { format: 'YYYY-MM-DD[T]HH:mm:ss.SSS', }, }, - internalOptions: { - extrasMaxValueLength: 50, - time: { - formats: { - short: 'HH:mm:ss.SSS', - long: 'YYYY-MM-DD[T]HH:mm:ss.SSS', - }, - }, - }, + // internalOptions: { + // extrasMaxValueLength: 50, + // time: { + // formats: { + // short: 'HH:mm:ss.SSS', + // long: 'YYYY-MM-DD[T]HH:mm:ss.SSS', + // }, + // }, + // }, }; describe.each([ - ['enable.time', 'a boolean', true], - ['enable.name', 'a boolean', false], - ['enable.hostname', 'a boolean', false], - ['enable.pid', 'a boolean', false], - ['enable.source', 'a boolean', false], - ['enable.extras', 'a boolean', true], - ['extrasKey', 'a string', ''], - ['indent', 'a number', 4], - ['jsonIndent', 'a number', 2], - ['basePath', 'a string', '/'], - ['newLineCharacter', 'one of [\r, \n, \r\n]', '\n'], - ['extrasMaxValueLength', 'a number', 50], - ['time.local', 'a boolean', false], + ['enable.time', 'boolean', true], + ['enable.name', 'boolean', false], + ['enable.hostname', 'boolean', false], + ['enable.pid', 'boolean', false], + ['enable.source', 'boolean', false], + ['enable.extras', 'boolean', true], + ['extrasKey', 'string', undefined], + ['indent', 'number', 4], + ['jsonIndent', 'number', 2], + ['basePath', 'string', '/'], + ['newLineCharacter', '\r | \n | \r\n', '\n'], + ['extrasMaxValueLength', 'number', 50], + ['time.local', 'boolean', false], ['time.type', 'one of [short, long, format]', 'long'], - ['time.format', 'a string', 'YYYY-MM-DD[T]HH:mm:ss.SSS'], - ['time.formats.short', 'a string', 'HH:mm:ss.SSS'], - ['time.formats.long', 'a string', 'YYYY-MM-DD[T]HH:mm:ss.SSS'], + ['time.format', 'string', 'YYYY-MM-DD[T]HH:mm:ss.SSS'], + ['time.formats.short', 'string', 'HH:mm:ss.SSS'], + ['time.formats.long', 'string', 'YYYY-MM-DD[T]HH:mm:ss.SSS'], ])('%s', (path: string, type: string, defaultValue: unknown) => { it(`MUST be ${stringify(type)}`, () => { const options = clone(defaults.options); - dotProp.set(options, path, is.number(defaultValue) ? 'abc' : 123); - const validation = schema.validate(options); - - expect(validation.error).toBeDefined(); - expect(validation.error?.isJoi).toBe(true); - const typeRe = type.replace(/\[/g, '\\[').replace(/\]/g, '\\]'); - expect(validation.error?.message).toMatch( - new RegExp(`^"${path}" must be ${typeRe}$`), - ); + if (is.undefined(defaultValue)) { + dotProp.set(options, path, 123); + } else { + dotProp.set(options, path, is.number(defaultValue) ? 'abc' : 123); + } + + const parsed = schema.safeParse(options); + + // const validation = schema.validate(options); + + // expect(validation.error).toBeDefined(); + // expect(validation.error?.isJoi).toBe(true); + expect(parsed.success).toBe(false); + if (parsed.success === false) { + // const typeRe = type.replace(/\[/g, '\\[').replace(/\]/g, '\\]'); + // expect(validation.error?.message).toMatch( + // new RegExp(`^"${path}" must be ${typeRe}$`), + // ); + expect(parsed.error.message).toMatch( + new RegExp( + `"(Expected ${type}, received (number|string|123)"|Invalid enum value. Expected)`, + ), + ); + } }); it(`defaults to "${stringify(defaultValue)}"`, () => { const options = clone(defaults.options); dotProp.delete(options, path); - const validation = schema.validate(options); + // const validation = schema.validate(options); + const parsed = schema.safeParse(options); + + expect(parsed.success).toBe(true); + if (parsed.success === true) { + expect(dotProp.get(parsed.data, path)).toEqual(defaultValue); + } - expect(validation.error).toBeUndefined(); - expect(dotProp.get(validation.value, path)).toEqual(defaultValue); + // expect(validation.error).toBeUndefined(); + // expect(dotProp.get(validation.value, path)).toEqual(defaultValue); }); }); @@ -97,33 +120,54 @@ describe('schema', () => { it('MUST be an integer', () => { const options = clone(defaults.options); dotProp.set(options, path, 0.5); - const validation = schema.validate(options); - - expect(validation.error).toBeDefined(); - expect(validation.error?.isJoi).toBe(true); - expect(validation.error?.message).toEqual(`"${path}" must be an integer`); + const parsed = schema.safeParse(options); + // const validation = schema.validate(options); + + // expect(validation.error).toBeDefined(); + // expect(validation.error?.isJoi).toBe(true); + // expect(validation.error?.message).toEqual(`"${path}" must be an integer`); + expect(parsed.success).toBe(false); + if (parsed.success === false) { + expect(parsed.error.message).toMatch( + /"Expected integer, received float"/, + ); + } }); if (type === 'a positive number') { it('MUST NOT be "0"', () => { const options = clone(defaults.options); dotProp.set(options, path, -1); - const validation = schema.validate(options); - - expect(validation.error).toBeDefined(); - expect(validation.error?.isJoi).toBe(true); - expect(validation.error?.message).toEqual(`"${path}" must be ${type}`); + const parsed = schema.safeParse(options); + // const validation = schema.validate(options); + + // expect(validation.error).toBeDefined(); + // expect(validation.error?.isJoi).toBe(true); + // expect(validation.error?.message).toEqual(`"${path}" must be ${type}`); + expect(parsed.success).toBe(false); + if (parsed.success === false) { + expect(parsed.error.message).toMatch( + /"Value should be greater than( or equal to)? 0"/, + ); + } }); } it('MUST NOT be negative', () => { const options = clone(defaults.options); dotProp.set(options, path, -1); - const validation = schema.validate(options); - - expect(validation.error).toBeDefined(); - expect(validation.error?.isJoi).toBe(true); - expect(validation.error?.message).toEqual(`"${path}" must be ${type}`); + const parsed = schema.safeParse(options); + // const validation = schema.validate(options); + + // expect(validation.error).toBeDefined(); + // expect(validation.error?.isJoi).toBe(true); + // expect(validation.error?.message).toEqual(`"${path}" must be ${type}`); + expect(parsed.success).toBe(false); + if (parsed.success === false) { + expect(parsed.error.message).toMatch( + /"Value should be greater than( or equal to)? 0"/, + ); + } }); }); @@ -134,13 +178,20 @@ describe('schema', () => { ])('disallows %s "%s"', (_, value: string) => { const options = clone(defaults.options); dotProp.set(options, 'extrasKey', value); - const validation = schema.validate(options); - - expect(validation.error).toBeDefined(); - expect(validation.error?.isJoi).toBe(true); - expect(validation.error?.message).toBe( - '"extrasKey" contains an invalid value', - ); + const parsed = schema.safeParse(options); + // const validation = schema.validate(options); + + // expect(validation.error).toBeDefined(); + // expect(validation.error?.isJoi).toBe(true); + // expect(validation.error?.message).toBe( + // '"extrasKey" contains an invalid value', + // ); + expect(parsed.success).toBe(false); + if (parsed.success === false) { + expect(parsed.error.message).toMatch( + /"(Invalid|Should be at least 1 characters)"/, + ); + } }); }); @@ -152,10 +203,15 @@ describe('schema', () => { ])('allows "%s"', (_, value: string) => { const options = clone(defaults.options); dotProp.set(options, 'newLineCharacter', value); - const validation = schema.validate(options); - - expect(validation.error).toBeUndefined(); - expect(validation.value.newLineCharacter).toEqual(value); + const parsed = schema.safeParse(options); + // const validation = schema.validate(options); + + // expect(validation.error).toBeUndefined(); + // expect(validation.value.newLineCharacter).toEqual(value); + expect(parsed.success).toBe(true); + if (parsed.success === true) { + expect(parsed.data.newLineCharacter).toEqual(value); + } }); it.each([ @@ -165,24 +221,34 @@ describe('schema', () => { ])('disallows %s', (_, value: string) => { const options = clone(defaults.options); dotProp.set(options, 'newLineCharacter', value); - const validation = schema.validate(options); - - expect(validation.error).toBeDefined(); - expect(validation.error?.isJoi).toBe(true); - expect(validation.error?.message).toMatch( - /^"newLineCharacter" must be one of /, - ); + const parsed = schema.safeParse(options); + // const validation = schema.validate(options); + + // expect(validation.error).toBeDefined(); + // expect(validation.error?.isJoi).toBe(true); + // expect(validation.error?.message).toMatch( + // /^"newLineCharacter" must be one of /, + // ); + expect(parsed.success).toBe(false); + if (parsed.success === false) { + expect(parsed.error.message).toMatch(/"Invalid enum value. Expected /); + } }); - }); - describe('time.type', () => { - it.each(['short', 'long', 'format'])('allows "%s"', (value: string) => { - const options = clone(defaults.options); - dotProp.set(options, 'time.type', value); - const validation = schema.validate(options); - - expect(validation.error).toBeUndefined(); - expect(validation.value.time.type).toEqual(value); + describe('time.type', () => { + it.each(['short', 'long', 'format'])('allows "%s"', (value: string) => { + const options = clone(defaults.options); + dotProp.set(options, 'time.type', value); + const parsed = schema.safeParse(options); + // const validation = schema.validate(options); + + // expect(validation.error).toBeUndefined(); + // expect(validation.value.time.type).toEqual(value); + expect(parsed.success).toBe(true); + if (parsed.success === true) { + expect(parsed.data.time.type).toEqual(value); + } + }); }); }); });