diff --git a/jsconfig.json b/jsconfig.json index b5d5608c4..343c2a7e7 100644 --- a/jsconfig.json +++ b/jsconfig.json @@ -102,7 +102,7 @@ "getFailuresByGroup": [ "./packages/vest/src/core/produce/getFailuresByGroup.js" ], - "hasFaillures": ["./packages/vest/src/core/produce/hasFaillures.js"], + "hasFailures": ["./packages/vest/src/core/produce/hasFailures.js"], "hasFailuresByGroup": [ "./packages/vest/src/core/produce/hasFailuresByGroup.js" ], @@ -134,6 +134,7 @@ "group": ["./packages/vest/src/hooks/group.js"], "hookErrors": ["./packages/vest/src/hooks/hookErrors.js"], "hooks": ["./packages/vest/src/hooks/hooks.js"], + "optionalTests": ["./packages/vest/src/hooks/optionalTests.js"], "warn": ["./packages/vest/src/hooks/warn.js"], "cache": ["./packages/vest/src/lib/cache.js"], "callEach": ["./packages/vest/src/lib/callEach.js"], diff --git a/packages/vest/docs/optional.md b/packages/vest/docs/optional.md new file mode 100644 index 000000000..634379431 --- /dev/null +++ b/packages/vest/docs/optional.md @@ -0,0 +1,51 @@ +# optional fields + +> Since 3.2.0 + +It is possible to mark fields in your suite as optional fields. This means that when they are skipped, the suite may still be considered as valid. +All fields are by default required, unless explicitly marked as optional using the `optional` function. + +## Usage + +`optional` can take a field name as its argument, or an array of field names. + +```js +import vest, { optional, only, test, enforce } from 'vest'; + +const suite = vest.create('RegisterPet', (data, currentField) => { + only(currentField); // only validate this specified field + + optional(['pet_color', 'pet_age']); + /** Equivalent to: + * optional('pet_color') + * optional('pet_age') + **/ + + test('pet_name', 'Pet Name is required', () => { + enforce(data.name).isNotEmpty(); + }); + + test('pet_color', 'If provided, pet color must be a string', () => { + enforce(data.color).isString(); + }); + + test('pet_age', 'If provided, pet age must be numeric', () => { + enforce(data.age).isNumeric(); + }); +}); + +suite({ name: 'Indie' }, /* -> only validate pet_name */ 'pet_name').isValid(); +// ✅ Since pet_color and pet_age are optional, the suite may still be valid + +suite({ age: 'Five' }, /* -> only validate pet_age */ 'pet_age').isValid(); +// 🚨 When erroring, optional fields still make the suite invalid +``` + +## Difference between `optional` and `warn` + +While on its surface, optional might seem similar to warn, they are quite different. +optional, like "only" and "skip" is set on the field level, which means that when set - all tests of an optional field are considered optional. Warn, on the other hand - is set on the test level, so the only tests affected are the tests that have the "warn" option applied within them. + +Another distinction is that warning tests cannot set the suite to be invalid. + +There may be rare occasions in which you have an optional and a warning only field, in which case, you may combine the two. diff --git a/packages/vest/docs/result.md b/packages/vest/docs/result.md index f31498240..ec1d78519 100644 --- a/packages/vest/docs/result.md +++ b/packages/vest/docs/result.md @@ -61,6 +61,19 @@ const suite = vest.create('my_form', data => { Along with these values, the result object exposes the following methods: +## `isValid` function + +`isValid` returns whether the validation suite as a whole is valid or not. + +A suite is considered valid if both conditions are met: + +- There are no errors (`hasErrors() === false`) in the suite - warnings are not counted as errors. +- All non optional fields have passing tests. + +```js +resultObject.isValid(); +``` + ## `hasErrors` and `hasWarnings` functions If you only need to know if a certain field has validation errors or warnings but don't really care which they are, you can use `hasErrors` or `hasWarnings` functions. diff --git a/packages/vest/src/__tests__/__snapshots__/infra.test.js.snap b/packages/vest/src/__tests__/__snapshots__/infra.test.js.snap index 2c8623e37..fe0aded88 100644 --- a/packages/vest/src/__tests__/__snapshots__/infra.test.js.snap +++ b/packages/vest/src/__tests__/__snapshots__/infra.test.js.snap @@ -13,6 +13,7 @@ Object { "hasErrorsByGroup": [Function], "hasWarnings": [Function], "hasWarningsByGroup": [Function], + "isValid": [Function], "name": "molestias-veritatis-deserunt", "testCount": 2, "tests": Object { @@ -39,6 +40,7 @@ Object { "hasErrorsByGroup": [Function], "hasWarnings": [Function], "hasWarningsByGroup": [Function], + "isValid": [Function], "name": "eveniet-maxime-ea", "testCount": 2, "tests": Object { @@ -73,6 +75,7 @@ Object { "hasErrorsByGroup": [Function], "hasWarnings": [Function], "hasWarningsByGroup": [Function], + "isValid": [Function], "name": "inventore-quis-impedit", "testCount": 1, "tests": Object { @@ -109,6 +112,7 @@ Object { "hasErrorsByGroup": [Function], "hasWarnings": [Function], "hasWarningsByGroup": [Function], + "isValid": [Function], "name": "corrupti-alias-autem", "testCount": 1, "tests": Object { @@ -143,6 +147,7 @@ Object { "hasErrorsByGroup": [Function], "hasWarnings": [Function], "hasWarningsByGroup": [Function], + "isValid": [Function], "name": "corrupti-alias-autem", "testCount": 2, "tests": Object { @@ -171,6 +176,7 @@ Object { "enforce": [Function], "group": [Function], "only": [Function], + "optional": [Function], "skip": [Function], "skipWhen": [Function], "test": [Function], diff --git a/packages/vest/src/__tests__/__snapshots__/integration.base.test.js.snap b/packages/vest/src/__tests__/__snapshots__/integration.base.test.js.snap index 34128076b..3710a1b79 100644 --- a/packages/vest/src/__tests__/__snapshots__/integration.base.test.js.snap +++ b/packages/vest/src/__tests__/__snapshots__/integration.base.test.js.snap @@ -13,6 +13,7 @@ Object { "hasErrorsByGroup": [Function], "hasWarnings": [Function], "hasWarningsByGroup": [Function], + "isValid": [Function], "name": "suite_name", "testCount": 5, "tests": Object { diff --git a/packages/vest/src/__tests__/__snapshots__/integration.stateful-async.test.js.snap b/packages/vest/src/__tests__/__snapshots__/integration.stateful-async.test.js.snap index 658994125..9650fadaa 100644 --- a/packages/vest/src/__tests__/__snapshots__/integration.stateful-async.test.js.snap +++ b/packages/vest/src/__tests__/__snapshots__/integration.stateful-async.test.js.snap @@ -26,6 +26,7 @@ Object { "hasErrorsByGroup": [Function], "hasWarnings": [Function], "hasWarningsByGroup": [Function], + "isValid": [Function], "name": "suite_name", "testCount": 3, "tests": Object { @@ -82,6 +83,7 @@ Object { "hasErrorsByGroup": [Function], "hasWarnings": [Function], "hasWarningsByGroup": [Function], + "isValid": [Function], "name": "suite_name", "testCount": 3, "tests": Object { @@ -141,6 +143,7 @@ Object { "hasErrorsByGroup": [Function], "hasWarnings": [Function], "hasWarningsByGroup": [Function], + "isValid": [Function], "name": "suite_name", "testCount": 7, "tests": Object { diff --git a/packages/vest/src/__tests__/__snapshots__/integration.stateful-tests.test.js.snap b/packages/vest/src/__tests__/__snapshots__/integration.stateful-tests.test.js.snap index da9e4672a..2b05f423a 100644 --- a/packages/vest/src/__tests__/__snapshots__/integration.stateful-tests.test.js.snap +++ b/packages/vest/src/__tests__/__snapshots__/integration.stateful-tests.test.js.snap @@ -13,6 +13,7 @@ Object { "hasErrorsByGroup": [Function], "hasWarnings": [Function], "hasWarningsByGroup": [Function], + "isValid": [Function], "name": "suite_name", "testCount": 1, "tests": Object { @@ -62,6 +63,7 @@ Object { "hasErrorsByGroup": [Function], "hasWarnings": [Function], "hasWarningsByGroup": [Function], + "isValid": [Function], "name": "suite_name", "testCount": 3, "tests": Object { @@ -115,6 +117,7 @@ Object { "hasErrorsByGroup": [Function], "hasWarnings": [Function], "hasWarningsByGroup": [Function], + "isValid": [Function], "name": "suite_name", "testCount": 7, "tests": Object { diff --git a/packages/vest/src/core/ctx.js b/packages/vest/src/core/ctx.js index acb2e35a7..678e917e7 100644 --- a/packages/vest/src/core/ctx.js +++ b/packages/vest/src/core/ctx.js @@ -8,12 +8,16 @@ import { const context = createContext((ctxRef, parentContext) => parentContext ? null - : Object.assign({}, ctxRef, { - exclusion: { - [EXCLUSION_ITEM_TYPE_TESTS]: {}, - [EXCLUSION_ITEM_TYPE_GROUPS]: {}, + : Object.assign( + {}, + { + exclusion: { + [EXCLUSION_ITEM_TYPE_TESTS]: {}, + [EXCLUSION_ITEM_TYPE_GROUPS]: {}, + }, }, - }) + ctxRef + ) ); export default context; diff --git a/packages/vest/src/core/produce/__tests__/isValid.test.js b/packages/vest/src/core/produce/__tests__/isValid.test.js new file mode 100644 index 000000000..aae6c0290 --- /dev/null +++ b/packages/vest/src/core/produce/__tests__/isValid.test.js @@ -0,0 +1,97 @@ +import vest, { test, optional } from 'vest'; + +describe('isValid', () => { + describe('Before any test ran', () => { + it('Should return false', () => { + const suite = vest.create(() => { + test('field_1', () => false); + }); + + expect(suite.get().isValid()).toBe(false); + }); + }); + + describe('When there are errors in the suite', () => { + let suite; + + beforeEach(() => { + suite = vest.create(skip => { + vest.skip(skip); + optional('field_1'); + + test('field_1', () => false); + test('field_2', () => false); + test('sanity', () => true); + }); + }); + + it('Should return false when an optional test has errors', () => { + expect(suite('field_2').isValid()).toBe(false); + }); + it('Should return false when a required test has errors', () => { + expect(suite('field_1').isValid()).toBe(false); + }); + }); + + describe('When there are warnings in the suite', () => { + let suite; + + beforeEach(() => { + suite = vest.create(() => { + test('field_1', () => { + vest.warn(); + return false; + }); + }); + }); + it('Should return true when a required test has warnings', () => { + expect(suite().isValid()).toBe(true); + }); + }); + + describe('When a non optional field is skipped', () => { + let suite; + + beforeEach(() => { + suite = vest.create(skip => { + vest.skip(skip); + test('field_1', () => { + return false; + }); + test('field_2', () => { + return true; + }); + test('field_3', () => { + return true; + }); + }); + }); + it('Should return false', () => { + expect(suite('field_1').isValid()).toBe(false); + }); + it('Should return false', () => { + expect(suite(['field_2', 'field_3']).isValid()).toBe(false); + }); + }); + + describe('When a all required fields are passing', () => { + let suite; + + beforeEach(() => { + suite = vest.create(() => { + test('field_1', () => { + return true; + }); + test('field_2', () => { + return true; + }); + test('field_3', () => { + return true; + }); + }); + }); + it('Should return true', () => { + expect(suite('field_1').isValid()).toBe(true); + }); + }); +}); diff --git a/packages/vest/src/core/produce/hasFaillures.js b/packages/vest/src/core/produce/hasFailures.js similarity index 100% rename from packages/vest/src/core/produce/hasFaillures.js rename to packages/vest/src/core/produce/hasFailures.js diff --git a/packages/vest/src/core/produce/hasFailuresByGroup.js b/packages/vest/src/core/produce/hasFailuresByGroup.js index 5b5b9a8e6..4347786e7 100644 --- a/packages/vest/src/core/produce/hasFailuresByGroup.js +++ b/packages/vest/src/core/produce/hasFailuresByGroup.js @@ -1,4 +1,4 @@ -import { hasLogic } from 'hasFaillures'; +import { hasLogic } from 'hasFailures'; import { useTestObjects } from 'stateHooks'; /** diff --git a/packages/vest/src/core/produce/produce.js b/packages/vest/src/core/produce/produce.js index 2441f2b41..2148a5db9 100644 --- a/packages/vest/src/core/produce/produce.js +++ b/packages/vest/src/core/produce/produce.js @@ -3,24 +3,65 @@ import context from 'ctx'; import genTestsSummary from 'genTestsSummary'; import getFailures from 'getFailures'; import getFailuresByGroup from 'getFailuresByGroup'; -import hasFaillures from 'hasFaillures'; +import hasFailures from 'hasFailures'; import hasFailuresByGroup from 'hasFailuresByGroup'; import hasRemainingTests from 'hasRemainingTests'; import isFunction from 'isFunction'; import { SEVERITY_GROUP_ERROR, SEVERITY_GROUP_WARN } from 'resultKeys'; import { HAS_WARNINGS, HAS_ERRORS } from 'sharedKeys'; -import { useTestCallbacks, useTestObjects } from 'stateHooks'; +import { + useTestCallbacks, + useTestObjects, + useOptionalFields, +} from 'stateHooks'; import withArgs from 'withArgs'; const cache = createCache(20); +/** + * @param {boolean} [isDraft] + * @returns Vest output object. + */ +const produce = isDraft => { + const { stateRef, optional } = context.use(); + const [testObjects] = useTestObjects(); + + const ctxRef = { stateRef, optional }; + + return cache( + [testObjects, isDraft], + context.bind(ctxRef, () => + [ + [HAS_ERRORS, hasFailures, SEVERITY_GROUP_ERROR], + [HAS_WARNINGS, hasFailures, SEVERITY_GROUP_WARN], + ['getErrors', getFailures, SEVERITY_GROUP_ERROR], + ['getWarnings', getFailures, SEVERITY_GROUP_WARN], + ['hasErrorsByGroup', hasFailuresByGroup, SEVERITY_GROUP_ERROR], + ['hasWarningsByGroup', hasFailuresByGroup, SEVERITY_GROUP_WARN], + ['getErrorsByGroup', getFailuresByGroup, SEVERITY_GROUP_ERROR], + ['getWarningsByGroup', getFailuresByGroup, SEVERITY_GROUP_WARN], + ] + .concat( + [['isValid', isValid]], + isDraft ? [] : [['done', withArgs(done)]] + ) + .reduce((properties, [name, fn, severityKey]) => { + properties[name] = context.bind(ctxRef, fn, severityKey); + return properties; + }, genTestsSummary()) + ) + ); +}; + +export default produce; + /** * Registers done callbacks. * @param {string} [fieldName] * @param {Function} doneCallback * @register {Object} Vest output object. */ -const done = withArgs(args => { +function done(args) { const [callback, fieldName] = args.reverse(); const { stateRef } = context.use(); @@ -61,38 +102,28 @@ const done = withArgs(args => { }); return output; -}); +} + +function isValid() { + const result = produce(); + + if (result.hasErrors()) { + return false; + } -/** - * @param {boolean} [isDraft] - * @returns Vest output object. - */ -const produce = isDraft => { - const { stateRef } = context.use(); const [testObjects] = useTestObjects(); - const ctxRef = { stateRef }; + if (testObjects.length === 0) { + return false; + } - return cache( - [testObjects, isDraft], - context.bind(ctxRef, () => - [ - [HAS_ERRORS, hasFaillures, SEVERITY_GROUP_ERROR], - [HAS_WARNINGS, hasFaillures, SEVERITY_GROUP_WARN], - ['getErrors', getFailures, SEVERITY_GROUP_ERROR], - ['getWarnings', getFailures, SEVERITY_GROUP_WARN], - ['hasErrorsByGroup', hasFailuresByGroup, SEVERITY_GROUP_ERROR], - ['hasWarningsByGroup', hasFailuresByGroup, SEVERITY_GROUP_WARN], - ['getErrorsByGroup', getFailuresByGroup, SEVERITY_GROUP_ERROR], - ['getWarningsByGroup', getFailuresByGroup, SEVERITY_GROUP_WARN], - ] - .concat(isDraft ? [] : [['done', done]]) - .reduce((properties, [name, fn, severityKey]) => { - properties[name] = context.bind(ctxRef, fn, severityKey); - return properties; - }, genTestsSummary()) - ) - ); -}; + const [optionalFields] = useOptionalFields(); -export default produce; + for (const test in result.tests) { + if (!optionalFields[test] && result.tests[test].testCount === 0) { + return false; + } + } + + return true; +} diff --git a/packages/vest/src/core/state/createStateRef.js b/packages/vest/src/core/state/createStateRef.js index 68fe57ce1..dba31f564 100644 --- a/packages/vest/src/core/state/createStateRef.js +++ b/packages/vest/src/core/state/createStateRef.js @@ -1,5 +1,6 @@ export default function createStateRef(state, { suiteId, name }) { return { + optionalFields: state.registerStateKey(() => ({})), pending: state.registerStateKey(() => ({ pending: [], lagging: [], diff --git a/packages/vest/src/core/state/stateHooks.js b/packages/vest/src/core/state/stateHooks.js index 07ce1281f..0e67f44cc 100644 --- a/packages/vest/src/core/state/stateHooks.js +++ b/packages/vest/src/core/state/stateHooks.js @@ -17,3 +17,6 @@ export function useTestObjects() { export function useSkippedTests() { return getStateRef().skippedTests(); } +export function useOptionalFields() { + return getStateRef().optionalFields(); +} diff --git a/packages/vest/src/core/suite/__tests__/__snapshots__/createSuite.test.js.snap b/packages/vest/src/core/suite/__tests__/__snapshots__/createSuite.test.js.snap index 59d33d42c..ae616811d 100644 --- a/packages/vest/src/core/suite/__tests__/__snapshots__/createSuite.test.js.snap +++ b/packages/vest/src/core/suite/__tests__/__snapshots__/createSuite.test.js.snap @@ -12,6 +12,7 @@ Object { "hasErrorsByGroup": [Function], "hasWarnings": [Function], "hasWarningsByGroup": [Function], + "isValid": [Function], "name": "test_get_suite", "testCount": 0, "tests": Object {}, @@ -31,6 +32,7 @@ Object { "hasErrorsByGroup": [Function], "hasWarnings": [Function], "hasWarningsByGroup": [Function], + "isValid": [Function], "name": "initial_run_spec", "testCount": 0, "tests": Object {}, @@ -50,6 +52,7 @@ Object { "hasErrorsByGroup": [Function], "hasWarnings": [Function], "hasWarningsByGroup": [Function], + "isValid": [Function], "name": undefined, "testCount": 0, "tests": Object {}, diff --git a/packages/vest/src/core/suite/__tests__/__snapshots__/subscribe.test.js.snap b/packages/vest/src/core/suite/__tests__/__snapshots__/subscribe.test.js.snap index 940c36f88..0f6374972 100644 --- a/packages/vest/src/core/suite/__tests__/__snapshots__/subscribe.test.js.snap +++ b/packages/vest/src/core/suite/__tests__/__snapshots__/subscribe.test.js.snap @@ -3,6 +3,7 @@ exports[`suite.subscribe Should call handler on suite subscription initialization 1`] = ` Object { "suiteState": Object { + "optionalFields": [Function], "pending": [Function], "skippedTests": [Function], "suiteId": [Function], diff --git a/packages/vest/src/core/test/__tests__/__snapshots__/memo.test.js.snap b/packages/vest/src/core/test/__tests__/__snapshots__/memo.test.js.snap index ec22b754a..89ef40c2c 100644 --- a/packages/vest/src/core/test/__tests__/__snapshots__/memo.test.js.snap +++ b/packages/vest/src/core/test/__tests__/__snapshots__/memo.test.js.snap @@ -12,6 +12,7 @@ Object { "hasErrorsByGroup": [Function], "hasWarnings": [Function], "hasWarningsByGroup": [Function], + "isValid": [Function], "name": "suite_name", "testCount": 1, "tests": Object { @@ -38,6 +39,7 @@ Object { "hasErrorsByGroup": [Function], "hasWarnings": [Function], "hasWarningsByGroup": [Function], + "isValid": [Function], "name": "suite_name", "testCount": 4, "tests": Object { @@ -88,6 +90,7 @@ Object { "hasErrorsByGroup": [Function], "hasWarnings": [Function], "hasWarningsByGroup": [Function], + "isValid": [Function], "name": "suite_name", "testCount": 4, "tests": Object { @@ -138,6 +141,7 @@ Object { "hasErrorsByGroup": [Function], "hasWarnings": [Function], "hasWarningsByGroup": [Function], + "isValid": [Function], "name": "suite_name", "testCount": 4, "tests": Object { diff --git a/packages/vest/src/core/test/__tests__/__snapshots__/runAsyncTest.test.js.snap b/packages/vest/src/core/test/__tests__/__snapshots__/runAsyncTest.test.js.snap index be63d4748..2f0a6e971 100644 --- a/packages/vest/src/core/test/__tests__/__snapshots__/runAsyncTest.test.js.snap +++ b/packages/vest/src/core/test/__tests__/__snapshots__/runAsyncTest.test.js.snap @@ -2,6 +2,7 @@ exports[`runAsyncTest: passing State updates Initial state matches snapshot (sanity) 1`] = ` Object { + "optionalFields": Object {}, "pending": Object { "lagging": Array [], "pending": Array [ diff --git a/packages/vest/src/core/test/lib/__tests__/__snapshots__/mergeExcludedTests.test.js.snap b/packages/vest/src/core/test/lib/__tests__/__snapshots__/mergeExcludedTests.test.js.snap index e3f975974..af8a75b6e 100644 --- a/packages/vest/src/core/test/lib/__tests__/__snapshots__/mergeExcludedTests.test.js.snap +++ b/packages/vest/src/core/test/lib/__tests__/__snapshots__/mergeExcludedTests.test.js.snap @@ -85,6 +85,7 @@ Object { "hasErrorsByGroup": [Function], "hasWarnings": [Function], "hasWarningsByGroup": [Function], + "isValid": [Function], "name": "suite_1", "testCount": 10, "tests": Object { @@ -242,6 +243,7 @@ Object { "hasErrorsByGroup": [Function], "hasWarnings": [Function], "hasWarningsByGroup": [Function], + "isValid": [Function], "name": "suite_1", "testCount": 16, "tests": Object { @@ -410,6 +412,7 @@ Object { "hasErrorsByGroup": [Function], "hasWarnings": [Function], "hasWarningsByGroup": [Function], + "isValid": [Function], "name": "suite_1", "testCount": 16, "tests": Object { @@ -554,6 +557,7 @@ Object { "hasErrorsByGroup": [Function], "hasWarnings": [Function], "hasWarningsByGroup": [Function], + "isValid": [Function], "name": "suite_1", "testCount": 4, "tests": Object { diff --git a/packages/vest/src/core/test/lib/__tests__/__snapshots__/pending.test.js.snap b/packages/vest/src/core/test/lib/__tests__/__snapshots__/pending.test.js.snap index 2d110bb0b..87e5d9243 100644 --- a/packages/vest/src/core/test/lib/__tests__/__snapshots__/pending.test.js.snap +++ b/packages/vest/src/core/test/lib/__tests__/__snapshots__/pending.test.js.snap @@ -2,6 +2,7 @@ exports[`module: pending export: removePending When testObject is either pending or lagging When in lagging Should remove test from lagging 1`] = ` Object { + "optionalFields": Object {}, "pending": Object { "lagging": Array [], "pending": Array [], @@ -21,6 +22,7 @@ Object { exports[`module: pending export: removePending When testObject is either pending or lagging When in pending Should remove test from pending 1`] = ` Object { + "optionalFields": Object {}, "pending": Object { "lagging": Array [], "pending": Array [], @@ -40,6 +42,7 @@ Object { exports[`module: pending export: setPending Should set supplied test object as pending 1`] = ` Object { + "optionalFields": Object {}, "pending": Object { "lagging": Array [], "pending": Array [ @@ -69,6 +72,7 @@ Object { exports[`module: pending export: setPending When a field of the same profile is in lagging array Should remove test from lagging array 1`] = ` Object { + "optionalFields": Object {}, "pending": Object { "lagging": Array [ VestTest { diff --git a/packages/vest/src/hooks/__tests__/__snapshots__/exclusive.test.js.snap b/packages/vest/src/hooks/__tests__/__snapshots__/exclusive.test.js.snap index 77a07734b..9b8a04a7c 100644 --- a/packages/vest/src/hooks/__tests__/__snapshots__/exclusive.test.js.snap +++ b/packages/vest/src/hooks/__tests__/__snapshots__/exclusive.test.js.snap @@ -21,6 +21,7 @@ Object { "hasErrorsByGroup": [Function], "hasWarnings": [Function], "hasWarningsByGroup": [Function], + "isValid": [Function], "name": undefined, "testCount": 3, "tests": Object { diff --git a/packages/vest/src/hooks/__tests__/optional.test.js b/packages/vest/src/hooks/__tests__/optional.test.js new file mode 100644 index 000000000..030b3d93e --- /dev/null +++ b/packages/vest/src/hooks/__tests__/optional.test.js @@ -0,0 +1,28 @@ +import { useOptionalFields } from 'stateHooks'; +import vest, { optional } from 'vest'; + +describe('optional hook', () => { + it('Should add optional fields to state', () => { + return new Promise(done => { + vest.create(() => { + expect(useOptionalFields()[0]).toMatchInlineSnapshot(`Object {}`); + optional('field_1'); + expect(useOptionalFields()[0]).toMatchInlineSnapshot(` + Object { + "field_1": true, + } + `); + optional(['field_2', 'field_3']); + expect(useOptionalFields()[0]).toMatchInlineSnapshot(` + Object { + "field_1": true, + "field_2": true, + "field_3": true, + } + `); + })(); + + done(); + }); + }); +}); diff --git a/packages/vest/src/hooks/hooks.js b/packages/vest/src/hooks/hooks.js index 0a6299ff1..b847d438d 100644 --- a/packages/vest/src/hooks/hooks.js +++ b/packages/vest/src/hooks/hooks.js @@ -1,3 +1,4 @@ export { only, skip, skipWhen } from 'exclusive'; export { default as warn } from 'warn'; export { default as group } from 'group'; +export { default as optional } from 'optionalTests'; diff --git a/packages/vest/src/hooks/optionalTests.js b/packages/vest/src/hooks/optionalTests.js new file mode 100644 index 000000000..d8716500b --- /dev/null +++ b/packages/vest/src/hooks/optionalTests.js @@ -0,0 +1,14 @@ +import asArray from 'asArray'; +import { useOptionalFields } from 'stateHooks'; + +export default function optional(optionals) { + const [, setOptionalFields] = useOptionalFields(); + + setOptionalFields(state => { + asArray(optionals).forEach(optionalField => { + state[optionalField] = true; + }); + + return state; + }); +} diff --git a/packages/vest/src/typings/vest.d.ts b/packages/vest/src/typings/vest.d.ts index 3518a3848..88d0ffda0 100644 --- a/packages/vest/src/typings/vest.d.ts +++ b/packages/vest/src/typings/vest.d.ts @@ -199,6 +199,8 @@ declare namespace vest { const skip: ISkip; const skipWhen: ISkipWhen; + function optional(optionalFields: string | string[]): boolean; + /** * Runs a stateful validation suite. * @param [suiteName] Unique suite name. diff --git a/packages/vest/src/typings/vestResult.d.ts b/packages/vest/src/typings/vestResult.d.ts index 1052cf21c..ada64205f 100644 --- a/packages/vest/src/typings/vestResult.d.ts +++ b/packages/vest/src/typings/vestResult.d.ts @@ -25,6 +25,12 @@ export type DraftResult = { }; }; }; + /** + * Returns whether the suite as a whole is valid. + * Determined if there are no errors, and if no + * required fields are skipped. + */ + isValid: () => boolean; /** * Returns whether the specified field has errors */ diff --git a/packages/vest/src/vest.js b/packages/vest/src/vest.js index 91023f40a..cd5731067 100644 --- a/packages/vest/src/vest.js +++ b/packages/vest/src/vest.js @@ -1,6 +1,6 @@ import create from 'createSuite'; import enforce from 'enforce'; -import { only, skip, warn, group, skipWhen } from 'hooks'; +import { only, skip, warn, group, skipWhen, optional } from 'hooks'; import test from 'test'; const VERSION = __LIB_VERSION__; @@ -11,6 +11,7 @@ export default { enforce, group, only, + optional, skip, skipWhen, test, diff --git a/packages/vest/src/vest.mjs.js b/packages/vest/src/vest.mjs.js index 3b16d6dfd..8efe4d9bb 100644 --- a/packages/vest/src/vest.mjs.js +++ b/packages/vest/src/vest.mjs.js @@ -1,6 +1,6 @@ -import create from 'createSuite'; + import create from 'createSuite'; import enforce from 'enforce'; -import { only, skip, warn, group, skipWhen } from 'hooks'; +import { only, skip, warn, group, skipWhen, optional } from 'hooks'; import test from 'test'; const VERSION = __LIB_VERSION__; @@ -11,10 +11,11 @@ export default { enforce, group, only, + optional, skip, skipWhen, test, warn, }; -export { VERSION, create, enforce, group, only, skip, test, warn }; +export { VERSION, create, enforce, group, only, optional, skip, test, warn };