Skip to content

Commit

Permalink
feat: add array.length() and treat empty arrays as valid for required()
Browse files Browse the repository at this point in the history
BREAKING CHANGE: array().required() will no longer consider an empty array missing and required checks will pass.

To maintain the old behavior change to:
```js
array().required().min(1)
```
  • Loading branch information
jquense committed Nov 19, 2020
1 parent 963d2e8 commit fbc158d
Show file tree
Hide file tree
Showing 3 changed files with 54 additions and 20 deletions.
24 changes: 13 additions & 11 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,6 @@ Yup's API is heavily inspired by [Joi](https://github.com/hapijs/joi), but leane
<!-- START doctoc generated TOC please keep comment here to allow auto update -->
<!-- DON'T EDIT THIS SECTION, INSTEAD RE-RUN doctoc TO UPDATE -->


- [Install](#install)
- [Usage](#usage)
- [Using a custom locale dictionary](#using-a-custom-locale-dictionary)
Expand Down Expand Up @@ -580,15 +579,19 @@ Indicates that `null` is a valid value for the schema. Without `nullable()`

#### `mixed.required(message?: string | function): Schema`

Mark the schema as required. All field values apart from `undefined` and `null` meet this requirement.
Mark the schema as required, which will not allow `undefined` or `null` as a value.
Note that unless a schema is marked as `nullable()` a `null` value is treated as a type error, not a missing value. Mark a schema as `mixed().nullable().required()` treat `null` as missing.

> Watch out! [`string().required`](#stringrequiredmessage-string--function-schema)) works a little
> different and additionally prevents empty string values (`''`) when required.
#### `mixed.notRequired(): Schema`
#### `mixed.notRequired(): Schema` Alias: `optional()`

Mark the schema as not required. Passing `undefined` as value will not fail validation.
Mark the schema as not required. Passing `undefined` (or `null` for nullable schema) as value will not fail validation.

#### `mixed.defined(): Schema`

Mark the schema as required but nullable. All field values apart from `undefined` meet this requirement.
Require a value for the schema. All field values apart from `undefined` meet this requirement.

#### `mixed.typeError(message: string): Schema`

Expand Down Expand Up @@ -723,7 +726,7 @@ await schema.isValid('john'); // => false
Test functions are called with a special context, or `this` value, that exposes some useful metadata
and functions. Older versions just expose the `this` context using `function ()`, not arrow-func,
but now it's exposed too as a second argument of the test functions. It's allow you decide which
approach you prefer.
approach you prefer.

- `this.path`: the string path of the current validation
- `this.schema`: the resolved schema object that the test is running against.
Expand Down Expand Up @@ -824,7 +827,7 @@ Failed casts return the input value.

#### `string.required(message?: string | function): Schema`

The same as the `mixed()` schema required, except that empty strings are also considered 'missing' values.
The same as the `mixed()` schema required, **except** that empty strings are also considered 'missing' values.

#### `string.length(limit: number | Ref, message?: string | function): Schema`

Expand Down Expand Up @@ -1016,9 +1019,9 @@ Failed casts return: `null`;
Specify the schema of array elements. `of()` is optional and when omitted the array schema will
not validate its contents.

#### `array.required(message?: string | function): Schema`
#### `array.length(length: number | Ref, message?: string | function): Schema`

The same as the `mixed()` schema required, except that empty arrays are also considered 'missing' values.
Set a specific length requirement for the array. The `${length}` interpolation can be used in the `message` argument.

#### `array.min(limit: number | Ref, message?: string | function): Schema`

Expand Down Expand Up @@ -1288,8 +1291,7 @@ const personSchema = yup.object({
.string()
// Here we use `defined` instead of `required` to more closely align with
// TypeScript. Both will have the same effect on the resulting type by
// excluding `undefined`, but `required` will also disallow other values
// such as empty strings.
// excluding `undefined`, but `required` will also disallow empty strings.
.defined(),
nickName: yup
.string()
Expand Down
19 changes: 13 additions & 6 deletions src/array.js
Original file line number Diff line number Diff line change
Expand Up @@ -127,12 +127,6 @@ inherits(ArraySchema, MixedSchema, {
);
},

_isPresent(value) {
return (
MixedSchema.prototype._isPresent.call(this, value) && value.length > 0
);
},

of(schema) {
var next = this.clone();

Expand Down Expand Up @@ -176,6 +170,19 @@ inherits(ArraySchema, MixedSchema, {
});
},

length(length, message) {
message = message || locale.length;
return this.test({
message,
name: 'length',
exclusive: true,
params: { length },
test(value) {
return isAbsent(value) || value.length === this.resolve(length);
},
});
},

ensure() {
return this.default(() => []).transform((val, original) => {
// We don't want to return `null` for nullable schema
Expand Down
31 changes: 28 additions & 3 deletions test/array.js
Original file line number Diff line number Diff line change
Expand Up @@ -67,14 +67,39 @@ describe('Array types', () => {
});

describe('validation', () => {
test.each([
['missing', undefined, array().defined()],
['required', undefined, array().required()],
['required', null, array().required()],
['null', null, array()],
['length', [1, 2, 3], array().length(2)],
])('Basic validations fail: %s %p', async (type, value, schema) => {
expect(await schema.isValid(value)).to.equal(false);
});

test.each([
['missing', [], array().defined()],
['required', [], array().required()],
['nullable', null, array().nullable()],
['length', [1, 2, 3], array().length(3)],
])('Basic validations pass: %s %p', async (type, value, schema) => {
expect(await schema.isValid(value)).to.equal(true);
});

it('should allow undefined', async () => {
await array().of(number().max(5)).isValid().should.become(true);
});

it('should not allow null when not nullable', async () => {
await array().isValid(null).should.become(false);
it('max should replace earlier tests', async () => {
expect(await array().max(4).max(10).isValid(Array(5).fill(0))).to.equal(
true,
);
});

await array().nullable().isValid(null).should.become(true);
it('min should replace earlier tests', async () => {
expect(await array().min(10).min(4).isValid(Array(5).fill(0))).to.equal(
true,
);
});

it('should respect subtype validations', async () => {
Expand Down

0 comments on commit fbc158d

Please sign in to comment.