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

feat(vest): add support for optional tests and "isValid" #612

Merged
merged 1 commit into from
Apr 28, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
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
Original file line number Diff line number Diff line change
@@ -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
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ Object {
"hasErrorsByGroup": [Function],
"hasWarnings": [Function],
"hasWarningsByGroup": [Function],
"isValid": [Function],
"name": "suite_name",
"testCount": 5,
"tests": Object {
Expand Down
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
@@ -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
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { hasLogic } from 'hasFaillures';
import { hasLogic } from 'hasFailures';
import { useTestObjects } from 'stateHooks';

/**
Expand Down