Skip to content

Commit

Permalink
feat(cdk:forms): add validator: range, rangeLength and update messages (
Browse files Browse the repository at this point in the history
  • Loading branch information
danranVm authored Jun 30, 2022
1 parent 2a343c1 commit d52f864
Show file tree
Hide file tree
Showing 10 changed files with 187 additions and 62 deletions.
7 changes: 6 additions & 1 deletion packages/cdk/forms/__tests__/abstractControl.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,12 @@ describe('abstractControl.ts', () => {

expect(await control.validate()).toEqual({
email: { actual: 'test', message: zhCNMessages.email({ actual: 'test' }, control) },
minLength: { actual: 4, minLength: 5, message: zhCNMessages.minLength({ actual: 4, minLength: 5 }, control) },
minLength: {
actual: 4,
isArray: false,
minLength: 5,
message: zhCNMessages.minLength({ actual: 4, isArray: false, minLength: 5 }, control),
},
})
})

Expand Down
84 changes: 75 additions & 9 deletions packages/cdk/forms/__tests__/validators.spec.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { FormControl } from '../src/controls'
import { enUSMessages } from '../src/messages/en-US'
import { zhCNMessages } from '../src/messages/zh-CN'
import { AsyncValidatorFn, ValidateErrors, ValidateMessages, ValidatorFn } from '../src/types'
import { Validators } from '../src/validators'
Expand Down Expand Up @@ -86,6 +87,25 @@ describe('validators.ts', () => {
expect(maxOne('2', control)).toEqual(errorMessage('2'))
})

test('range work', () => {
const range3To9 = Validators.range(3, 9)

expect(range3To9('', control)).toBeUndefined()
expect(range3To9(undefined, control)).toBeUndefined()
expect(range3To9('test', control)).toBeUndefined()
expect(range3To9('3', control)).toBeUndefined()
expect(range3To9(3, control)).toBeUndefined()
expect(range3To9(9, control)).toBeUndefined()

const errorMessage = (actual: unknown) => ({
range: { actual, min: 3, max: 9, message: zhCNMessages.range({ actual, min: 3, max: 9 }, control) },
})
expect(range3To9(2, control)).toEqual(errorMessage(2))
expect(range3To9('2', control)).toEqual(errorMessage('2'))
expect(range3To9(10, control)).toEqual(errorMessage(10))
expect(range3To9('10', control)).toEqual(errorMessage('10'))
})

test('minLength work', () => {
const minLengthTwo = Validators.minLength(2)

Expand All @@ -98,11 +118,16 @@ describe('validators.ts', () => {
expect(minLengthTwo([1, 2], control)).toBeUndefined()
expect(minLengthTwo([1, 2, 3], control)).toBeUndefined()

const errorMessage = (actual: unknown) => ({
minLength: { actual, minLength: 2, message: zhCNMessages.minLength({ actual, minLength: 2 }, control) },
const errorMessage = (actual: unknown, isArray: boolean) => ({
minLength: {
actual,
isArray,
minLength: 2,
message: zhCNMessages.minLength({ actual, isArray, minLength: 2 }, control),
},
})
expect(minLengthTwo('t', control)).toEqual(errorMessage(1))
expect(minLengthTwo([1], control)).toEqual(errorMessage(1))
expect(minLengthTwo('t', control)).toEqual(errorMessage(1, false))
expect(minLengthTwo([1], control)).toEqual(errorMessage(1, true))
})

test('maxLength work', () => {
Expand All @@ -117,11 +142,41 @@ describe('validators.ts', () => {
expect(maxLengthTwo([1, 2], control)).toBeUndefined()
expect(maxLengthTwo([1], control)).toBeUndefined()

const errorMessage = (actual: unknown) => ({
maxLength: { actual, maxLength: 2, message: zhCNMessages.maxLength({ actual, maxLength: 2 }, control) },
const errorMessage = (actual: unknown, isArray: boolean) => ({
maxLength: {
actual,
isArray,
maxLength: 2,
message: zhCNMessages.maxLength({ actual, isArray, maxLength: 2 }, control),
},
})
expect(maxLengthTwo('test', control)).toEqual(errorMessage(4))
expect(maxLengthTwo([1, 2, 3], control)).toEqual(errorMessage(3))
expect(maxLengthTwo('test', control)).toEqual(errorMessage(4, false))
expect(maxLengthTwo([1, 2, 3], control)).toEqual(errorMessage(3, true))
})

test('rangeLength work', () => {
const rangeLength2To5 = Validators.rangeLength(2, 5)

expect(rangeLength2To5('', control)).toBeUndefined()
expect(rangeLength2To5(1, control)).toBeUndefined()
expect(rangeLength2To5(undefined, control)).toBeUndefined()
expect(rangeLength2To5('te', control)).toBeUndefined()
expect(rangeLength2To5('test1', control)).toBeUndefined()
expect(rangeLength2To5([], control)).toBeUndefined()
expect(rangeLength2To5([1, 2], control)).toBeUndefined()
expect(rangeLength2To5([1, 2, 3, 4, 5], control)).toBeUndefined()

const errorMessage = (actual: unknown, isArray: boolean) => ({
rangeLength: {
actual,
isArray,
minLength: 2,
maxLength: 5,
message: zhCNMessages.rangeLength({ actual, isArray, minLength: 2, maxLength: 5 }, control),
},
})
expect(rangeLength2To5('t', control)).toEqual(errorMessage(1, false))
expect(rangeLength2To5([1, 2, 3, 4, 5, 6], control)).toEqual(errorMessage(6, true))
})

test('pattern work', () => {
Expand Down Expand Up @@ -226,7 +281,18 @@ describe('validators.ts', () => {
expect(errors).toEqual({ a: message1 })
})

test('setMessages work', () => {
test('setMessages enUSMessages work', () => {
const { setMessages, required, requiredTrue } = Validators

setMessages(enUSMessages)

expect(required('', control)).toEqual({ required: { message: enUSMessages.required({}, control) } })
expect(requiredTrue(false, control)).toEqual({
requiredTrue: { message: enUSMessages.requiredTrue({ actual: false }, control), actual: false },
})
})

test('setMessages custom messages work', () => {
const { setMessages, required, requiredTrue } = Validators

const messages: ValidateMessages = { required: 'please input', requiredTrue: 'invalid input' }
Expand Down
7 changes: 5 additions & 2 deletions packages/cdk/forms/docs/Index.zh.md
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,7 @@ export function useFormControl<T>(
| --- | --- | --- | --- | --- |
| `disabled` | 默认禁用当前控件 | `boolean` | - | - |
| `name` | 控件的名称 | `string` | - | 通常用于自定义提示信息 |
| `example` | 控件的示例 | `string` | - | 通常用于自定义提示信息 |
| `trigger` | 验证器触发的时机 | `'change' \| 'blur' \| 'submit'` | `change` | - |
| `validators` | 一个同步验证器函数或数组 | `ValidatorFn \| ValidatorFn[]` | - | - |
| `asyncValidators` | 一个异步验证器函数或数组 | `AsyncValidatorFn \| AsyncValidatorFn[]` | - | - |
Expand All @@ -105,8 +106,10 @@ export function useFormControl<T>(
| `email` | 验证表单控件的值是否为 `email` | - | - | 验证失败返回 `{ email: { message: '', actual: value } }`|
| `min()` | 验证表单控件的值大于或等于指定的数字 | `number` | - | 验证失败返回 `{ min: { message: '', min, actual: value } }`|
| `max()` | 验证表单控件的值小于或等于指定的数字 | `number` | - | 验证失败返回 `{ max: { message: '', min, actual: value } }`|
| `minLength()` | 验证表单控件的值的长度大于或等于指定的数字 | `number` | - | 验证失败返回 `{ minLength: { message: '', minLength, actual: value.length } }`|
| `maxLength()` | 验证表单控件的值的长度小于或等于指定的数字 | `number` | - | 验证失败返回 `{ maxLength: { message: '', maxLength, actual: value.length } }`|
| `range()` | 验证表单控件的值的范围 | `number, number` | - | 验证失败返回 `{ range: { message: '', min, max, actual: value } }`|
| `minLength()` | 验证表单控件的值的长度大于或等于指定的数字 | `number` | - | 验证失败返回 `{ minLength: { message: '', minLength, actual: value.length, isArray } }`|
| `maxLength()` | 验证表单控件的值的长度小于或等于指定的数字 | `number` | - | 验证失败返回 `{ maxLength: { message: '', maxLength, actual: value.length, isArray } }`|
| `rangeLength()` | 验证表单控件的值的长度范围 | `number, number` | - | 验证失败返回 `{ rangeLength: { message: '', minLength, maxLength, actual: value.length, isArray } }`|
| `pattern()` | 验证表单控件的值匹配一个正则表达式 | `string \| RegExp` | - | 验证失败返回 `{ pattern: { message: '', pattern, actual: value } }`|
| `setMessages()` | 设置验证失败的提示信息 | `ValidateMessages` | - | 每次设置的 `messages` 会跟之前的进行合并, 默认的提示信息为 `zhCNMessages` |

Expand Down
2 changes: 2 additions & 0 deletions packages/cdk/forms/src/controls.ts
Original file line number Diff line number Diff line change
Expand Up @@ -157,6 +157,7 @@ export abstract class AbstractControl<T = any> {
}

name?: string
example?: string

protected _controls: Ref<any>
protected _valueRef!: Ref<T>
Expand Down Expand Up @@ -443,6 +444,7 @@ export abstract class AbstractControl<T = any> {
let disabled = false
if (isOptions(validatorOrOptions)) {
this.name = validatorOrOptions.name
this.example = validatorOrOptions.example
this._trigger = validatorOrOptions.trigger ?? this._trigger
this._trim = validatorOrOptions.trim ?? this._trim
this._validators = toValidator(validatorOrOptions.validators)
Expand Down
47 changes: 27 additions & 20 deletions packages/cdk/forms/src/messages/en-US.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,35 +19,42 @@ export const enUSMessages = {
return name ? `Validation error on field ${name}` : 'Validation error'
},
required: (_: Omit<ValidateError, 'message'>, control: AbstractControl): string => {
const name = control.name || defaultName
return `${name} is required`
const { name = defaultName, example } = control
return `Please enter ${name}${example ? ', for example: ' + example : ''}`
},
requiredTrue: (_: Omit<ValidateError, 'message'>, control: AbstractControl): string => {
const name = control.name || defaultName
const { name = defaultName } = control
return `${name} is must be 'true'`
},
email: (_: Omit<ValidateError, 'message'>, control: AbstractControl): string => {
const name = control.name || defaultName
return `${name} is not a valid email`
const { example } = control
return `Please enter your email address${example ? ', for example: ' + example : ''}`
},
min: (err: Omit<ValidateError, 'message'>, control: AbstractControl): string => {
const name = control.name || defaultName
return `${name} cannot be less than ${err.min}`
min: (err: Omit<ValidateError, 'message'>, __: AbstractControl): string => {
return `Please enter a number not less than ${err.min}`
},
max: (err: Omit<ValidateError, 'message'>, control: AbstractControl): string => {
const name = control.name
return `${name} cannot be greater than ${err.max}`
max: (err: Omit<ValidateError, 'message'>, __: AbstractControl): string => {
return `Please enter a number no greater than ${err.max}`
},
range: (err: Omit<ValidateError, 'message'>, __: AbstractControl): string => {
return `Please enter a number between ${err.min - 1}-${err.max + 1}`
},
minLength: (err: Omit<ValidateError, 'message'>, __: AbstractControl): string => {
const { minLength, isArray } = err
return isArray ? `Please select at least ${minLength} items` : `Please enter at least ${minLength} characters`
},
minLength: (err: Omit<ValidateError, 'message'>, control: AbstractControl): string => {
const name = control.name || defaultName
return `${name} cannot be less than ${err.minLength} in length, current is ${err.actual}`
maxLength: (err: Omit<ValidateError, 'message'>, __: AbstractControl): string => {
const { maxLength, isArray } = err
return isArray ? `Please select at most ${maxLength} items` : `Please enter at most ${maxLength} characters`
},
maxLength: (err: Omit<ValidateError, 'message'>, control: AbstractControl): string => {
const name = control.name || defaultName
return `${name} cannot be greater than ${err.minLength} in length, current is ${err.actual}`
rangeLength: (err: Omit<ValidateError, 'message'>, __: AbstractControl): string => {
const { minLength, maxLength, isArray } = err
return isArray
? `Please select ${minLength}-${maxLength} items`
: `Please enter ${minLength}-${maxLength} characters`
},
pattern: (err: Omit<ValidateError, 'message'>, control: AbstractControl): string => {
const name = control.name || defaultName
return `${name} does not match pattern '${err.pattern}'`
pattern: (_: Omit<ValidateError, 'message'>, control: AbstractControl): string => {
const { example } = control
return `Please enter the correct pattern${example ? ', for example: ' + example : ''}`
},
}
48 changes: 26 additions & 22 deletions packages/cdk/forms/src/messages/zh-CN.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,39 +15,43 @@ const defaultName = '此项'

export const zhCNMessages = {
default: (_: Omit<ValidateError, 'message'>, control: AbstractControl): string => {
const name = control.name
return `${name || ''}验证失败`
return `${control.name ?? ''}验证失败`
},
required: (_: Omit<ValidateError, 'message'>, control: AbstractControl): string => {
const name = control.name || defaultName
return `${name}必填`
const { name = defaultName, example } = control
return `请输入${name}${example ? ', 例: ' + example : ''}`
},
requiredTrue: (_: Omit<ValidateError, 'message'>, control: AbstractControl): string => {
const name = control.name || defaultName
const { name = defaultName } = control
return `${name}必须为 'true'`
},
email: (_: Omit<ValidateError, 'message'>, control: AbstractControl): string => {
const name = control.name || defaultName
return `${name}不是一个有效的邮箱地址`
const { example } = control
return `请输入正确的邮箱格式${example ? ', 例: ' + example : ''}`
},
min: (err: Omit<ValidateError, 'message'>, control: AbstractControl): string => {
const name = control.name || defaultName
return `${name}不能小于 ${err.min}`
min: (err: Omit<ValidateError, 'message'>, __: AbstractControl): string => {
return `请输入一个不小于 ${err.min} 的数字`
},
max: (err: Omit<ValidateError, 'message'>, control: AbstractControl): string => {
const name = control.name || defaultName
return `${name}不能大于 ${err.max}`
max: (err: Omit<ValidateError, 'message'>, __: AbstractControl): string => {
return `请输入一个不大于 ${err.max} 的数字`
},
minLength: (err: Omit<ValidateError, 'message'>, control: AbstractControl): string => {
const name = control.name || defaultName
return `${name}的长度不能小于 ${err.minLength}, 当前长度为 ${err.actual}`
range: (err: Omit<ValidateError, 'message'>, __: AbstractControl): string => {
return `请输入一个 ${err.min - 1}-${err.max + 1} 之间的数字`
},
maxLength: (err: Omit<ValidateError, 'message'>, control: AbstractControl): string => {
const name = control.name || defaultName
return `${name}的长度不能大于 ${err.maxLength}, 当前长度为 ${err.actual}`
minLength: (err: Omit<ValidateError, 'message'>, __: AbstractControl): string => {
const { minLength, isArray } = err
return isArray ? `请至少选择 ${minLength} 项` : `请至少输入 ${minLength} 个字符`
},
pattern: (err: Omit<ValidateError, 'message'>, control: AbstractControl): string => {
const name = control.name || defaultName
return `${name}不能匹配 '${err.pattern}'`
maxLength: (err: Omit<ValidateError, 'message'>, __: AbstractControl): string => {
const { maxLength, isArray } = err
return isArray ? `请至多选择 ${maxLength} 项` : `请至多输入 ${maxLength} 个字符`
},
rangeLength: (err: Omit<ValidateError, 'message'>, __: AbstractControl): string => {
const { minLength, maxLength, isArray } = err
return isArray ? `请选择 ${minLength}-${maxLength} 项` : `请输入 ${minLength}-${maxLength} 个字符`
},
pattern: (_: Omit<ValidateError, 'message'>, control: AbstractControl): string => {
const { example } = control
return `请输入正确的格式${example ? ', 例: ' + example : ''}`
},
}
1 change: 1 addition & 0 deletions packages/cdk/forms/src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ export type TriggerType = 'change' | 'blur' | 'submit'
export interface ValidatorOptions {
disabled?: boolean
name?: string
example?: string
trigger?: TriggerType
validators?: ValidatorFn | ValidatorFn[]
asyncValidators?: AsyncValidatorFn | AsyncValidatorFn[]
Expand Down
41 changes: 39 additions & 2 deletions packages/cdk/forms/src/validators.ts
Original file line number Diff line number Diff line change
Expand Up @@ -89,12 +89,27 @@ export class Validators {
}
}

static range(min: number, max: number): ValidatorFn {
return (value: any, control: AbstractControl): { range: ValidateError } | undefined => {
if (isEmpty(value) || !isNumeric(value) || (Number(value) >= min && Number(value) <= max)) {
return undefined
}
return { range: Validators.getError('range', control, { min, max, actual: value }) }
}
}

static minLength(minLength: number): ValidatorFn {
return (value: any, control: AbstractControl): { minLength: ValidateError } | undefined => {
if (isEmpty(value) || !hasLength(value) || value.length >= minLength) {
return undefined
}
return { minLength: Validators.getError('minLength', control, { minLength, actual: value.length }) }
return {
minLength: Validators.getError('minLength', control, {
minLength,
actual: value.length,
isArray: isArray(value),
}),
}
}
}

Expand All @@ -103,7 +118,29 @@ export class Validators {
if (isEmpty(value) || !hasLength(value) || value.length <= maxLength) {
return undefined
}
return { maxLength: Validators.getError('maxLength', control, { maxLength, actual: value.length }) }
return {
maxLength: Validators.getError('maxLength', control, {
maxLength,
actual: value.length,
isArray: isArray(value),
}),
}
}
}

static rangeLength(minLength: number, maxLength: number): ValidatorFn {
return (value: any, control: AbstractControl): { rangeLength: ValidateError } | undefined => {
if (isEmpty(value) || !hasLength(value) || (value.length >= minLength && value.length <= maxLength)) {
return undefined
}
return {
rangeLength: Validators.getError('rangeLength', control, {
minLength,
maxLength,
actual: value.length,
isArray: isArray(value),
}),
}
}
}

Expand Down
7 changes: 2 additions & 5 deletions packages/components/form/demo/Message.vue
Original file line number Diff line number Diff line change
Expand Up @@ -47,13 +47,10 @@
<script lang="ts">
import { defineComponent } from 'vue'
import { AbstractControl, ValidateErrors, Validators, enUSMessages, useFormGroup, zhCNMessages } from '@idux/cdk/forms'
import { AbstractControl, ValidateErrors, Validators, useFormGroup } from '@idux/cdk/forms'
Validators.setMessages({
required: '必填项/Input is required',
email: { 'zh-CN': zhCNMessages.email, 'en-US': enUSMessages.email },
minLength: { 'zh-CN': zhCNMessages.minLength, 'en-US': enUSMessages.minLength },
maxLength: { 'zh-CN': zhCNMessages.maxLength, 'en-US': enUSMessages.maxLength },
required: (_err, _control) => '必填项/Input is required',
passwordRequired: {
'zh-CN': '请确认你的密码',
'en-US': 'Please confirm your password',
Expand Down
Loading

0 comments on commit d52f864

Please sign in to comment.