Skip to content

Commit

Permalink
feat: add support for label method (#77)
Browse files Browse the repository at this point in the history
* feat: add support for `label` method

* test: update tests and docs
  • Loading branch information
simonguo committed Apr 9, 2024
1 parent e315aec commit 9ff16c3
Show file tree
Hide file tree
Showing 3 changed files with 163 additions and 45 deletions.
66 changes: 48 additions & 18 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ Schema for data modeling & validation
- [`when(condition: (schemaSpec: SchemaDeclaration<DataType, ErrorMsgType>) => Type)`](#whencondition-schemaspec-schemadeclarationdatatype-errormsgtype--type)
- [`check(value: ValueType, data?: DataType):CheckResult`](#checkvalue-valuetype-data-datatypecheckresult)
- [`checkAsync(value: ValueType, data?: DataType):Promise<CheckResult>`](#checkasyncvalue-valuetype-data-datatypepromisecheckresult)
- [`label(label: string)`](#labellabel-string)
- [StringType(errorMessage?: string)](#stringtypeerrormessage-string)
- [`isEmail(errorMessage?: string)`](#isemailerrormessage-string)
- [`isURL(errorMessage?: string)`](#isurlerrormessage-string)
Expand Down Expand Up @@ -445,37 +446,49 @@ MixedType().addAsyncRule((value, data) => {

#### `when(condition: (schemaSpec: SchemaDeclaration<DataType, ErrorMsgType>) => Type)`

Define data verification rules based on conditions.
Conditional validation, the return value is a new type.

```js
```ts
const model = SchemaModel({
age: NumberType().min(18, 'error'),
contact: MixedType().when(schema => {
const checkResult = schema.age.check();
return checkResult.hasError
? StringType().isRequired('Please provide contact information')
: StringType();
option: StringType().isOneOf(['a', 'b', 'other']),
other: StringType().when(schema => {
const { value } = schema.option;
return value === 'other' ? StringType().isRequired('Other required') : StringType();
})
});

/**
{
age: { hasError: false },
contact: { hasError: false }
{
option: { hasError: false },
other: { hasError: false }
}
*/
model.check({ age: 18, contact: '' });
model.check({ option: 'a', other: '' });

/*
{
age: { hasError: true, errorMessage: 'error' },
contact: {
hasError: true,
errorMessage: 'Please provide contact information'
}
option: { hasError: false },
other: { hasError: true, errorMessage: 'Other required' }
}
*/
model.check({ age: 17, contact: '' });
model.check({ option: 'other', other: '' });
```

Check whether a field passes the validation to determine the validation rules of another field.

```js
const model = SchemaModel({
password: StringType().isRequired('Password required'),
confirmPassword: StringType().when(schema => {
const { hasError } = schema.password.check();
return hasError
? StringType()
: StringType().addRule(
value => value === schema.password.value,
'The passwords are inconsistent twice'
);
})
});
```

#### `check(value: ValueType, data?: DataType):CheckResult`
Expand Down Expand Up @@ -515,6 +528,23 @@ type.checkAsync(1).then(checkResult => {
});
```

#### `label(label: string)`

Overrides the key name in error messages.

```js
MixedType().label('Username');
```

Eg:

```js
SchemaModel({
first_name: StringType().label('First name'),
age: NumberType().label('Age')
});
```

### StringType(errorMessage?: string)

Define a string type. Supports all the same methods as [MixedType](#mixedtype).
Expand Down
17 changes: 15 additions & 2 deletions src/MixedType.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ export class MixedType<ValueType = any, DataType = any, E = ErrorMessageType, L
protected emptyAllowed = false;
protected rules: RuleType<ValueType, DataType, E | string>[] = [];
protected priorityRules: RuleType<ValueType, DataType, E | string>[] = [];
protected fieldLabel?: string;

schemaSpec: SchemaDeclaration<DataType, E>;
value: any;
Expand All @@ -43,7 +44,9 @@ export class MixedType<ValueType = any, DataType = any, E = ErrorMessageType, L
if (this.required && !checkRequired(value, this.trim, this.emptyAllowed)) {
return {
hasError: true,
errorMessage: formatErrorMessage(this.requiredMessage, { name: fieldName })
errorMessage: formatErrorMessage(this.requiredMessage, {
name: this.fieldLabel || fieldName
})
};
}

Expand All @@ -70,7 +73,9 @@ export class MixedType<ValueType = any, DataType = any, E = ErrorMessageType, L
if (this.required && !checkRequired(value, this.trim, this.emptyAllowed)) {
return Promise.resolve({
hasError: true,
errorMessage: formatErrorMessage(this.requiredMessage, { name: fieldName })
errorMessage: formatErrorMessage(this.requiredMessage, {
name: this.fieldLabel || fieldName
})
});
}

Expand Down Expand Up @@ -160,6 +165,14 @@ export class MixedType<ValueType = any, DataType = any, E = ErrorMessageType, L
);
return this;
}

/**
* Overrides the key name in error messages.
*/
label(label: string) {
this.fieldLabel = label;
return this;
}
}

export default function getMixedType<DataType = any, E = ErrorMessageType>() {
Expand Down
125 changes: 100 additions & 25 deletions test/MixedTypeSpec.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
/* eslint-disable @typescript-eslint/no-var-requires */
const chai = require('chai');
const schema = require('../src');
import chai, { expect } from 'chai';
import * as schema from '../src';

chai.should();

const { StringType, SchemaModel, NumberType, ArrayType, MixedType } = schema;

describe('#MixedType', () => {
Expand Down Expand Up @@ -364,42 +365,103 @@ describe('#MixedType', () => {

const checkResult1 = model.check({ field1: 20, field2: 2 });

checkResult1.field1.hasError.should.equal(false);
checkResult1.field2.hasError.should.equal(false);
expect(checkResult1).to.deep.equal({
field1: { hasError: false },
field2: { hasError: false }
});

const checkResult2 = model.check({ field1: 1, field2: 1 });

checkResult2.field1.hasError.should.equal(true);
checkResult2.field2.hasError.should.equal(true);
checkResult2.field2.errorMessage.should.equal('error1');
expect(checkResult2).to.deep.equal({
field1: { hasError: true, errorMessage: 'field1 must be greater than or equal to 10' },
field2: { hasError: true, errorMessage: 'error1' }
});

const checkResult3 = model.check({ field1: 10, field2: 1 });

checkResult3.field1.hasError.should.equal(false);
checkResult3.field2.hasError.should.equal(true);
checkResult3.field2.errorMessage.should.equal('error2');
expect(checkResult3).to.deep.equal({
field1: { hasError: false },
field2: { hasError: true, errorMessage: 'error2' }
});

const checkResult4 = model.checkForField('field2', { field1: 20, field2: 1 });
checkResult4.errorMessage.should.equal('error2');

expect(checkResult4).to.deep.equal({ hasError: true, errorMessage: 'error2' });

const checkResult5 = model.checkForField('field2', { field1: 9, field2: 1 });
checkResult5.errorMessage.should.equal('error1');

expect(checkResult5).to.deep.equal({ hasError: true, errorMessage: 'error1' });
});

it('Should be high priority even if it is empty', () => {
it('Should type be changed by condition', () => {
const model = SchemaModel({
age: NumberType().min(18, 'error1'),
contact: StringType().when(schema => {
const checkResult = schema.age.check();
return checkResult.hasError ? StringType().isRequired('error2') : StringType();
option: StringType().isOneOf(['a', 'b', 'other']),
other: StringType().when(schema => {
const { value } = schema.option;
return value === 'other' ? StringType().isRequired('Other required') : StringType();
})
});

const checkResult = model.check({ age: 17, contact: '' });
const checkResult = model.check({ option: 'a', other: '' });

expect(checkResult).to.deep.equal({
option: { hasError: false },
other: { hasError: false }
});

checkResult.age.hasError.should.equal(true);
checkResult.contact.hasError.should.equal(true);
checkResult.contact.errorMessage.should.equal('error2');
const checkResult2 = model.check({ option: 'other', other: '' });

expect(checkResult2).to.deep.equal({
option: { hasError: false },
other: { hasError: true, errorMessage: 'Other required' }
});
});

it('Should type be changed by condition', () => {
const model = SchemaModel({
password: StringType().isRequired('Password required'),
confirmPassword: StringType().when(schema => {
const { hasError } = schema.password.check();
return hasError
? StringType()
: StringType()
.addRule(
value => value === schema.password.value,
'The passwords are inconsistent twice'
)
.isRequired()
.label('Confirm password');
})
});

const checkResult = model.check({ password: '', confirmPassword: '123' });

expect(checkResult).to.deep.equal({
password: { hasError: true, errorMessage: 'Password required' },
confirmPassword: { hasError: false }
});

const checkResult2 = model.check({ password: '123', confirmPassword: '123' });

expect(checkResult2).to.deep.equal({
password: { hasError: false },
confirmPassword: { hasError: false }
});

const checkResult3 = model.check({ password: '123', confirmPassword: '1234' });

expect(checkResult3).to.deep.equal({
password: { hasError: false },
confirmPassword: { hasError: true, errorMessage: 'The passwords are inconsistent twice' }
});

const checkResult4 = model.check({ password: '123', confirmPassword: '' });

expect(checkResult4).to.deep.equal({
password: { hasError: false },
confirmPassword: { hasError: true, errorMessage: 'Confirm password is a required field' }
});
});

it('should error when an async rule is executed by the sync validator', () => {
Expand Down Expand Up @@ -450,14 +512,27 @@ describe('#MixedType', () => {
.isRequired('error2');
setTimeout(() => {
try {
chai.expect(called).to.eq(false);
chai.expect(type.check('').hasError).to.eq(true);
chai.expect(type.check('1').hasError).to.eq(true);
chai.expect(type.check(1).hasError).to.eq(false);
expect(called).to.eq(false);
expect(type.check('').hasError).to.eq(true);
expect(type.check('1').hasError).to.eq(true);
expect(type.check(1).hasError).to.eq(false);

done();
} catch (e) {
done(e);
}
}, 100);
});

it('Should use label to override the field name in the error message', () => {
const schema = SchemaModel({
first_name: StringType().label('First Name').isRequired(),
age: NumberType().label('Age').isRequired()
});

expect(schema.check({})).to.deep.equal({
first_name: { hasError: true, errorMessage: 'First Name is a required field' },
age: { hasError: true, errorMessage: 'Age is a required field' }
});
});
});

0 comments on commit 9ff16c3

Please sign in to comment.