Skip to content

Commit

Permalink
feat(vest): add support for optional tests and "isValid" (#612)
Browse files Browse the repository at this point in the history
  • Loading branch information
ealush committed May 11, 2021
1 parent 73f2e35 commit 60099b3
Show file tree
Hide file tree
Showing 28 changed files with 329 additions and 44 deletions.
3 changes: 2 additions & 1 deletion jsconfig.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

51 changes: 51 additions & 0 deletions 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.
13 changes: 13 additions & 0 deletions packages/vest/docs/result.md
Expand Up @@ -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.
Expand Down
6 changes: 6 additions & 0 deletions packages/vest/src/__tests__/__snapshots__/infra.test.js.snap
Expand Up @@ -13,6 +13,7 @@ Object {
"hasErrorsByGroup": [Function],
"hasWarnings": [Function],
"hasWarningsByGroup": [Function],
"isValid": [Function],
"name": "molestias-veritatis-deserunt",
"testCount": 2,
"tests": Object {
Expand All @@ -39,6 +40,7 @@ Object {
"hasErrorsByGroup": [Function],
"hasWarnings": [Function],
"hasWarningsByGroup": [Function],
"isValid": [Function],
"name": "eveniet-maxime-ea",
"testCount": 2,
"tests": Object {
Expand Down Expand Up @@ -73,6 +75,7 @@ Object {
"hasErrorsByGroup": [Function],
"hasWarnings": [Function],
"hasWarningsByGroup": [Function],
"isValid": [Function],
"name": "inventore-quis-impedit",
"testCount": 1,
"tests": Object {
Expand Down Expand Up @@ -109,6 +112,7 @@ Object {
"hasErrorsByGroup": [Function],
"hasWarnings": [Function],
"hasWarningsByGroup": [Function],
"isValid": [Function],
"name": "corrupti-alias-autem",
"testCount": 1,
"tests": Object {
Expand Down Expand Up @@ -143,6 +147,7 @@ Object {
"hasErrorsByGroup": [Function],
"hasWarnings": [Function],
"hasWarningsByGroup": [Function],
"isValid": [Function],
"name": "corrupti-alias-autem",
"testCount": 2,
"tests": Object {
Expand Down Expand Up @@ -171,6 +176,7 @@ Object {
"enforce": [Function],
"group": [Function],
"only": [Function],
"optional": [Function],
"skip": [Function],
"skipWhen": [Function],
"test": [Function],
Expand Down
Expand Up @@ -13,6 +13,7 @@ Object {
"hasErrorsByGroup": [Function],
"hasWarnings": [Function],
"hasWarningsByGroup": [Function],
"isValid": [Function],
"name": "suite_name",
"testCount": 5,
"tests": Object {
Expand Down
Expand Up @@ -26,6 +26,7 @@ Object {
"hasErrorsByGroup": [Function],
"hasWarnings": [Function],
"hasWarningsByGroup": [Function],
"isValid": [Function],
"name": "suite_name",
"testCount": 3,
"tests": Object {
Expand Down Expand Up @@ -82,6 +83,7 @@ Object {
"hasErrorsByGroup": [Function],
"hasWarnings": [Function],
"hasWarningsByGroup": [Function],
"isValid": [Function],
"name": "suite_name",
"testCount": 3,
"tests": Object {
Expand Down Expand Up @@ -141,6 +143,7 @@ Object {
"hasErrorsByGroup": [Function],
"hasWarnings": [Function],
"hasWarningsByGroup": [Function],
"isValid": [Function],
"name": "suite_name",
"testCount": 7,
"tests": Object {
Expand Down
Expand Up @@ -13,6 +13,7 @@ Object {
"hasErrorsByGroup": [Function],
"hasWarnings": [Function],
"hasWarningsByGroup": [Function],
"isValid": [Function],
"name": "suite_name",
"testCount": 1,
"tests": Object {
Expand Down Expand Up @@ -62,6 +63,7 @@ Object {
"hasErrorsByGroup": [Function],
"hasWarnings": [Function],
"hasWarningsByGroup": [Function],
"isValid": [Function],
"name": "suite_name",
"testCount": 3,
"tests": Object {
Expand Down Expand Up @@ -115,6 +117,7 @@ Object {
"hasErrorsByGroup": [Function],
"hasWarnings": [Function],
"hasWarningsByGroup": [Function],
"isValid": [Function],
"name": "suite_name",
"testCount": 7,
"tests": Object {
Expand Down
14 changes: 9 additions & 5 deletions packages/vest/src/core/ctx.js
Expand Up @@ -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;
97 changes: 97 additions & 0 deletions 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);
});
});
});
2 changes: 1 addition & 1 deletion packages/vest/src/core/produce/hasFailuresByGroup.js
@@ -1,4 +1,4 @@
import { hasLogic } from 'hasFaillures';
import { hasLogic } from 'hasFailures';
import { useTestObjects } from 'stateHooks';

/**
Expand Down

0 comments on commit 60099b3

Please sign in to comment.