Skip to content

Commit

Permalink
feat(cdk:forms): add default messages (#689)
Browse files Browse the repository at this point in the history
fix #684
  • Loading branch information
danranVm committed Dec 30, 2021
1 parent e26e548 commit 22d45d6
Show file tree
Hide file tree
Showing 20 changed files with 248 additions and 89 deletions.
1 change: 1 addition & 0 deletions .ls-lint.yml
Original file line number Diff line number Diff line change
Expand Up @@ -26,5 +26,6 @@ ignore:
- packages/components/node_modules
- packages/pro/node_modules
- packages/site/node_modules
- packages/cdk/forms/src/messages
- packages/components/i18n/src/locales
- packages/site/components.d.ts
2 changes: 1 addition & 1 deletion azure-pipelines.yml
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ stages:
- checkout: none
- task: NodeTool@0
inputs:
versionSpec: '14.x'
versionSpec: '16.x'
displayName: 'Install Node.js'

- stage: lint
Expand Down
15 changes: 8 additions & 7 deletions packages/cdk/forms/__tests__/abstractControl.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { flushPromises } from '@vue/test-utils'
import { Ref, ref, watch } from 'vue'

import { AbstractControl } from '../src/controls'
import { zhCNMessages } from '../src/messages/zh-CN'
import { AsyncValidatorFn, ValidateErrors, ValidatorFn, ValidatorOptions } from '../src/types'
import { Validators } from '../src/validators'

Expand Down Expand Up @@ -42,7 +43,7 @@ class Control<T = unknown> extends AbstractControl<T> {

describe('abstractControl.ts', () => {
describe('basic work', () => {
let control: Control
let control: AbstractControl

beforeEach(() => {
control = new Control()
Expand Down Expand Up @@ -71,14 +72,14 @@ describe('abstractControl.ts', () => {
const { required, minLength, email } = Validators
control.setValidator(required)

expect(await control.validate()).toEqual({ required: { message: undefined } })
expect(await control.validate()).toEqual({ required: { message: zhCNMessages.required({}, control) } })

control.setValidator([email, minLength(5)])
control.setValue('test')

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

Expand Down Expand Up @@ -195,12 +196,12 @@ describe('abstractControl.ts', () => {
})

describe('convert options work', () => {
let control: Control
let control: AbstractControl

test('options work', async () => {
control = new Control({ validators: Validators.required })

expect(await control.validate()).toEqual({ required: { message: undefined } })
expect(await control.validate()).toEqual({ required: { message: zhCNMessages.required({}, control) } })

expect(control.trigger).toEqual('change')

Expand All @@ -213,7 +214,7 @@ describe('abstractControl.ts', () => {
const _asyncValidator = (_: unknown) => Promise.resolve({ async: { message: 'async' } } as ValidateErrors)

control = new Control(Validators.required, _asyncValidator)
expect(await control.validate()).toEqual({ required: { message: undefined } })
expect(await control.validate()).toEqual({ required: { message: zhCNMessages.required({}, control) } })

control.setValue('test')
control.validate()
Expand Down
3 changes: 2 additions & 1 deletion packages/cdk/forms/__tests__/formControl.spec.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { flushPromises } from '@vue/test-utils'

import { FormControl } from '../src/controls'
import { zhCNMessages } from '../src/messages/zh-CN'
import { ValidateErrors } from '../src/types'
import { Validators } from '../src/validators'

Expand Down Expand Up @@ -67,7 +68,7 @@ describe('formControl.ts', () => {

control.setValidator(Validators.required)

expect(await control.validate()).toEqual({ required: { message: undefined } })
expect(await control.validate()).toEqual({ required: { message: zhCNMessages.required({}, control) } })
})
})

Expand Down
65 changes: 50 additions & 15 deletions packages/cdk/forms/__tests__/validators.spec.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
import { AbstractControl } from '../src/controls'
import { FormControl } from '../src/controls'
import { zhCNMessages } from '../src/messages/zh-CN'
import { AsyncValidatorFn, ValidateErrors, ValidateMessages, ValidatorFn } from '../src/types'
import { Validators } from '../src/validators'

describe('validators.ts', () => {
const control = undefined as unknown as AbstractControl
const control = new FormControl()

test('required work', () => {
const required = Validators.required
Expand All @@ -12,7 +13,9 @@ describe('validators.ts', () => {
expect(required('value', control)).toBeUndefined()
expect(required([1, 2], control)).toBeUndefined()

const errorMessage = { required: { message: undefined } }
const errorMessage = {
required: { message: zhCNMessages.required({}, control) },
}
expect(required(undefined, control)).toEqual(errorMessage)
expect(required(undefined, control)).toEqual(errorMessage)
expect(required('', control)).toEqual(errorMessage)
Expand All @@ -24,7 +27,9 @@ describe('validators.ts', () => {

expect(requiredTrue(true, control)).toBeUndefined()

const errorMessage = (actual: unknown) => ({ requiredTrue: { message: undefined, actual } })
const errorMessage = (actual: unknown) => ({
requiredTrue: { actual, message: zhCNMessages.requiredTrue({ actual }, control) },
})
expect(requiredTrue(undefined, control)).toEqual(errorMessage(undefined))
expect(requiredTrue(undefined, control)).toEqual(errorMessage(undefined))
expect(requiredTrue('', control)).toEqual(errorMessage(''))
Expand All @@ -40,7 +45,9 @@ describe('validators.ts', () => {
expect(email(undefined, control)).toBeUndefined()
expect(email('test@gmail.com', control)).toBeUndefined()

const errorMessage = (actual: unknown) => ({ email: { message: undefined, actual } })
const errorMessage = (actual: unknown) => ({
email: { actual, message: zhCNMessages.email({ actual }, control) },
})
expect(email({}, control)).toEqual(errorMessage({}))
expect(email('test', control)).toEqual(errorMessage('test'))
})
Expand All @@ -55,7 +62,9 @@ describe('validators.ts', () => {
expect(minOne(1, control)).toBeUndefined()
expect(minOne(2, control)).toBeUndefined()

const errorMessage = (actual: unknown) => ({ min: { message: undefined, min: 1, actual } })
const errorMessage = (actual: unknown) => ({
min: { actual, min: 1, message: zhCNMessages.min({ actual, min: 1 }, control) },
})
expect(minOne(0, control)).toEqual(errorMessage(0))
expect(minOne('0', control)).toEqual(errorMessage('0'))
})
Expand All @@ -70,7 +79,9 @@ describe('validators.ts', () => {
expect(maxOne(1, control)).toBeUndefined()
expect(maxOne(0, control)).toBeUndefined()

const errorMessage = (actual: unknown) => ({ max: { message: undefined, max: 1, actual } })
const errorMessage = (actual: unknown) => ({
max: { actual, max: 1, message: zhCNMessages.max({ actual, max: 1 }, control) },
})
expect(maxOne(2, control)).toEqual(errorMessage(2))
expect(maxOne('2', control)).toEqual(errorMessage('2'))
})
Expand All @@ -87,7 +98,9 @@ describe('validators.ts', () => {
expect(minLengthTwo([1, 2], control)).toBeUndefined()
expect(minLengthTwo([1, 2, 3], control)).toBeUndefined()

const errorMessage = (actual: unknown) => ({ minLength: { message: undefined, minLength: 2, actual } })
const errorMessage = (actual: unknown) => ({
minLength: { actual, minLength: 2, message: zhCNMessages.minLength({ actual, minLength: 2 }, control) },
})
expect(minLengthTwo('t', control)).toEqual(errorMessage(1))
expect(minLengthTwo([1], control)).toEqual(errorMessage(1))
})
Expand All @@ -104,7 +117,9 @@ describe('validators.ts', () => {
expect(maxLengthTwo([1, 2], control)).toBeUndefined()
expect(maxLengthTwo([1], control)).toBeUndefined()

const errorMessage = (actual: unknown) => ({ maxLength: { message: undefined, maxLength: 2, actual } })
const errorMessage = (actual: unknown) => ({
maxLength: { actual, maxLength: 2, message: zhCNMessages.maxLength({ actual, maxLength: 2 }, control) },
})
expect(maxLengthTwo('test', control)).toEqual(errorMessage(4))
expect(maxLengthTwo([1, 2, 3], control)).toEqual(errorMessage(3))
})
Expand All @@ -118,7 +133,13 @@ describe('validators.ts', () => {
expect(stringPattern(undefined, control)).toBeUndefined()
expect(stringPattern('test', control)).toBeUndefined()

let errorMessage = (actual: unknown) => ({ pattern: { message: undefined, pattern: '^[a-zA-Z]+$', actual } })
let errorMessage = (actual: unknown) => ({
pattern: {
actual,
pattern: '^[a-zA-Z]+$',
message: zhCNMessages.pattern({ actual, pattern: '^[a-zA-Z]+$' }, control),
},
})
expect(stringPattern('test1', control)).toEqual(errorMessage('test1'))
expect(stringPattern(1, control)).toEqual(errorMessage(1))

Expand All @@ -133,14 +154,26 @@ describe('validators.ts', () => {
expect(regExpPattern('test', control)).toBeUndefined()
expect(regExpPattern('test1', control)).toBeUndefined()

errorMessage = (actual: unknown) => ({ pattern: { message: undefined, pattern: '/[a-zA-Z]+/', actual } })
errorMessage = (actual: unknown) => ({
pattern: {
actual,
pattern: '/[a-zA-Z]+/',
message: zhCNMessages.pattern({ actual, pattern: '/[a-zA-Z]+/' }, control),
},
})
expect(regExpPattern(1, control)).toEqual(errorMessage(1))

const regExpPattern2 = Validators.pattern(new RegExp('^[a-zA-Z]+$'))

expect(regExpPattern2('test', control)).toBeUndefined()

errorMessage = (actual: unknown) => ({ pattern: { message: undefined, pattern: '/^[a-zA-Z]+$/', actual } })
errorMessage = (actual: unknown) => ({
pattern: {
actual,
pattern: '/^[a-zA-Z]+$/',
message: zhCNMessages.pattern({ actual, pattern: '/^[a-zA-Z]+$/' }, control),
},
})
expect(regExpPattern2('test1', control)).toEqual(errorMessage('test1'))
expect(regExpPattern2(1, control)).toEqual(errorMessage(1))
})
Expand All @@ -164,7 +197,9 @@ describe('validators.ts', () => {
b: message2,
})
expect(compose([_validator('a', message1), _validator('a', message2)])!('test', control)).toEqual({ a: message2 })
expect(compose([undefined, nullValidator, required])!('', control)).toEqual({ required: { message: undefined } })
expect(compose([undefined, nullValidator, required])!('', control)).toEqual({
required: { message: zhCNMessages.required({}, control) },
})
})

test('composeAsync work', async () => {
Expand Down Expand Up @@ -194,11 +229,11 @@ describe('validators.ts', () => {
test('setMessages work', () => {
const { setMessages, required, requiredTrue } = Validators

const messages: ValidateMessages = { default: 'invalid input', required: 'please input' }
const messages: ValidateMessages = { required: 'please input', requiredTrue: 'invalid input' }
setMessages(messages)

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

setMessages({ requiredTrue: () => 'please input true' })

Expand Down
3 changes: 1 addition & 2 deletions packages/cdk/forms/demo/Basic.vue
Original file line number Diff line number Diff line change
Expand Up @@ -15,9 +15,8 @@
</template>

<script setup lang="ts">
import { Validators, useFormGroup } from '@idux/cdk/forms'
import { Validators, useFormArray, useFormGroup } from '@idux/cdk/forms'
import { useFormArray } from '../src/useForms'
import CustomForm from './CustomForm.vue'
import CustomInput from './CustomInput.vue'
Expand Down
5 changes: 4 additions & 1 deletion packages/cdk/forms/docs/Index.zh.md
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,7 @@ export function useFormControl<T>(
| 名称 | 说明 | 类型 | 默认值 | 备注 |
| --- | --- | --- | --- | --- |
| `disabled` | 默认禁用当前控件 | `boolean` | - | - |
| `name` | 控件的名称 | `string` | - | 通常用于自定义提示信息 |
| `trigger` | 验证器触发的时机 | `'change' \| 'blur' \| 'submit'` | `change` | - |
| `validators` | 一个同步验证器函数或数组 | `ValidatorFn \| ValidatorFn[]` | - | - |
| `asyncValidator` | 一个异步验证器函数或数组 | `AsyncValidatorFn \| AsyncValidatorFn[]` | - | - |
Expand All @@ -107,7 +108,9 @@ export function useFormControl<T>(
| `minLength()` | 验证表单控件的值的长度大于或等于指定的数字 | `number` | - | 验证失败返回 `{ minLength: { message: '', minLength, actual: value.length } }`|
| `maxLength()` | 验证表单控件的值的长度小于或等于指定的数字 | `number` | - | 验证失败返回 `{ maxLength: { message: '', maxLength, actual: value.length } }`|
| `pattern()` | 验证表单控件的值匹配一个正则表达式 | `string \| RegExp` | - | 验证失败返回 `{ pattern: { message: '', pattern, actual: value } }`|
| `setMessages()` | 设置验证失败的提示信息 | `ValidateMessages` | - | 每次设置的 `messages` 会跟之前的进行合并 |
| `setMessages()` | 设置验证失败的提示信息 | `ValidateMessages` | - | 每次设置的 `messages` 会跟之前的进行合并, 默认的提示信息为 `zhCNMessages` |

更多默认的提示信息,参见 [messages](https://github.com/IDuxFE/idux/tree/main/packages/cdk/forms/src/messages)

### useValueControl

Expand Down
7 changes: 3 additions & 4 deletions packages/cdk/forms/docs/Overview.zh.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,8 @@

- `AbstractControl`: 它提供了一些所有控件和控件组共有的行为,比如运行验证器、计算状态和重置状态,还定义了一些所有子类共享的属性,如 `value``status`。它是 `FormControl``FormGroup``FormArray` 的基类,不允许直接实例化它。
- `FormControl`: 它用于跟踪独立表单控件的值和验证状态,实现了关于访问值、验证状态、用户交互和事件的大部分基本功能。
- `FormGroup`: 它用于跟踪一组控件实例的值和有效性状态,它把每个子控件的值聚合进一个数组,它的 `index` 是每个控件的名字。它通过归集其子控件的状态值来计算出自己的状态。 比如,如果组中的任何一个控件是无效的,那么整个组就是无效的。
- `AbstractControl`: 它用于跟踪一个控件数组实例的值和有效性状态,它把每个子控件的值聚合进一个对象,它的 `key` 是每个控件的名字。它通过归集其子控件的状态值来计算出自己的状态。 比如,如果数组中的任何一个控件是无效的,那么整个数组就是无效的。
- `FormGroup`: 它用于跟踪一组控件实例的值和有效性状态,它把每个子控件的值聚合进一个对象。它通过归集其子控件的状态值来计算出自己的状态。 比如,如果组中的任何一个控件是无效的,那么整个组就是无效的。
- `FormArray`: 它用于跟踪一个控件数组实例的值和有效性状态,它把每个子控件的值聚合进一个数组。它通过归集其子控件的状态值来计算出自己的状态。 比如,如果数组中的任何一个控件是无效的,那么整个数组就是无效的。

### 实现支持控制器的输入控件

Expand Down Expand Up @@ -344,8 +344,7 @@ export declare abstract class AbstractControl<T = any> {
* @param options Configuration options that emits events when the value changes.
* * `dirty`: Marks it dirty, default is false.
*/
abstract setValue(value: T, options?: { dirty?: boolean }): void;
abstract setValue(value: Partial<T>, options?: { dirty?: boolean }): void;
abstract setValue(value: T | Partial<T>, options?: { dirty?: boolean }): void;
/**
* The aggregate value of the control.
*/
Expand Down
5 changes: 5 additions & 0 deletions packages/cdk/forms/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,3 +11,8 @@ export * from './src/types'
export * from './src/useForms'
export * from './src/utils'
export * from './src/validators'

// messages

export * from './src/messages/zh-CN'
export * from './src/messages/en-US'
11 changes: 7 additions & 4 deletions packages/cdk/forms/src/controls.ts
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ export abstract class AbstractControl<T = any> {
/**
* A collection of child controls.
*/
readonly controls!: ComputedRef<GroupControls<T> | AbstractControl<ArrayElement<T>>[] | undefined>
readonly controls!: ComputedRef<any>

/**
* The ref value for the control.
Expand Down Expand Up @@ -145,7 +145,9 @@ export abstract class AbstractControl<T = any> {
return this._trigger ?? this._parent?.trigger ?? 'change'
}

protected _controls: Ref<GroupControls<T> | AbstractControl<ArrayElement<T>>[] | undefined>
name?: string

protected _controls: Ref<any>
protected _valueRef!: Ref<T>
protected _status!: Ref<ValidateStatus>
protected _controlsStatus!: Ref<ValidateStatus>
Expand Down Expand Up @@ -179,8 +181,7 @@ export abstract class AbstractControl<T = any> {
* @param options Configuration options that emits events when the value changes.
* * `dirty`: Marks it dirty, default is false.
*/
abstract setValue(value: T, options?: { dirty?: boolean }): void
abstract setValue(value: Partial<T>, options?: { dirty?: boolean }): void
abstract setValue(value: T | Partial<T>, options?: { dirty?: boolean }): void

/**
* The aggregate value of the control.
Expand Down Expand Up @@ -425,6 +426,7 @@ export abstract class AbstractControl<T = any> {
) {
let disabled = false
if (isOptions(validatorOrOptions)) {
this.name = validatorOrOptions.name
this._trigger = validatorOrOptions.trigger ?? this._trigger
this._validators = toValidator(validatorOrOptions.validators)
this._asyncValidators = toAsyncValidator(validatorOrOptions.asyncValidators)
Expand Down Expand Up @@ -511,6 +513,7 @@ export abstract class AbstractControl<T = any> {
}

export class FormControl<T = any> extends AbstractControl<T> {
readonly controls!: ComputedRef<undefined>
constructor(
private _initValue?: T,
validatorOrOptions?: ValidatorFn | ValidatorFn[] | ValidatorOptions,
Expand Down
Loading

0 comments on commit 22d45d6

Please sign in to comment.