From f42263837a9856468713c840cf850aff76e95d09 Mon Sep 17 00:00:00 2001 From: Jelf <353742991@qq.com> Date: Tue, 31 May 2022 20:49:57 +0800 Subject: [PATCH] feat(useAsyncValidator): new function (#1497) Co-authored-by: Anthony Fu --- packages/add-ons.md | 1 + packages/integrations/README.md | 1 + packages/integrations/index.ts | 1 + packages/integrations/package.json | 15 ++ .../useAsyncValidator/component.ts | 26 ++++ .../integrations/useAsyncValidator/demo.vue | 75 ++++++++++ .../integrations/useAsyncValidator/index.md | 19 +++ .../useAsyncValidator/index.test.ts | 135 ++++++++++++++++++ .../integrations/useAsyncValidator/index.ts | 73 ++++++++++ pnpm-lock.yaml | 6 + 10 files changed, 352 insertions(+) create mode 100644 packages/integrations/useAsyncValidator/component.ts create mode 100644 packages/integrations/useAsyncValidator/demo.vue create mode 100644 packages/integrations/useAsyncValidator/index.md create mode 100644 packages/integrations/useAsyncValidator/index.test.ts create mode 100644 packages/integrations/useAsyncValidator/index.ts diff --git a/packages/add-ons.md b/packages/add-ons.md index 88152070a04..e041014dfdb 100644 --- a/packages/add-ons.md +++ b/packages/add-ons.md @@ -69,6 +69,7 @@ Utilities for vue-router ## Integrations - [`@vueuse/integrations`](https://vueuse.org/integrations/README.html) Integration wrappers for utility libraries + - [`useAsyncValidator`](https://vueuse.org/integrations/useAsyncValidator/) — wrapper for [`async-validator`](https://github.com/yiminghe/async-validator) - [`useAxios`](https://vueuse.org/integrations/useAxios/) — wrapper for [`axios`](https://github.com/axios/axios) - [`useChangeCase`](https://vueuse.org/integrations/useChangeCase/) — wrapper for [`change-case`](https://github.com/blakeembrey/change-case) - [`useCookies`](https://vueuse.org/integrations/useCookies/) — wrapper for [`universal-cookie`](https://www.npmjs.com/package/universal-cookie) diff --git a/packages/integrations/README.md b/packages/integrations/README.md index d56bf62f68d..094e71b7bd2 100644 --- a/packages/integrations/README.md +++ b/packages/integrations/README.md @@ -14,6 +14,7 @@ npm i @vueuse/integrations + - [`useAsyncValidator`](https://vueuse.org/integrations/useAsyncValidator/) — wrapper for [`async-validator`](https://github.com/yiminghe/async-validator) - [`useAxios`](https://vueuse.org/integrations/useAxios/) — wrapper for [`axios`](https://github.com/axios/axios) - [`useChangeCase`](https://vueuse.org/integrations/useChangeCase/) — wrapper for [`change-case`](https://github.com/blakeembrey/change-case) - [`useCookies`](https://vueuse.org/integrations/useCookies/) — wrapper for [`universal-cookie`](https://www.npmjs.com/package/universal-cookie) diff --git a/packages/integrations/index.ts b/packages/integrations/index.ts index 03f46943d6e..9e0711e2fb6 100644 --- a/packages/integrations/index.ts +++ b/packages/integrations/index.ts @@ -1,3 +1,4 @@ +export * from './useAsyncValidator' export * from './useAxios' export * from './useChangeCase' export * from './useCookies' diff --git a/packages/integrations/package.json b/packages/integrations/package.json index a4222803724..1c1b568f1c1 100644 --- a/packages/integrations/package.json +++ b/packages/integrations/package.json @@ -27,6 +27,11 @@ "require": "./index.cjs" }, "./*": "./*", + "./useAsyncValidator": { + "types": "./useAsyncValidator.d.ts", + "import": "./useAsyncValidator.mjs", + "require": "./useAsyncValidator.cjs" + }, "./useAxios": { "types": "./useAxios.d.ts", "import": "./useAxios.mjs", @@ -76,6 +81,11 @@ "types": "./useChangeCase.d.ts", "import": "./useChangeCase.mjs", "require": "./useChangeCase.cjs" + }, + "./useAsyncValidator/component": { + "types": "./useAsyncValidator/component.d.ts", + "import": "./useAsyncValidator/component.mjs", + "require": "./useAsyncValidator/component.cjs" } }, "main": "./index.cjs", @@ -84,6 +94,7 @@ "jsdelivr": "./index.iife.min.js", "types": "./index.d.ts", "peerDependencies": { + "async-validator": "*", "axios": "*", "change-case": "*", "drauu": "*", @@ -98,6 +109,9 @@ "axios": { "optional": true }, + "async-validator": { + "optional": true + }, "change-case": { "optional": true }, @@ -131,6 +145,7 @@ "devDependencies": { "@types/nprogress": "^0.2.0", "@types/qrcode": "^1.4.2", + "async-validator": "^4.0.7", "axios": "^0.27.2", "change-case": "^4.1.2", "drauu": "^0.3.0", diff --git a/packages/integrations/useAsyncValidator/component.ts b/packages/integrations/useAsyncValidator/component.ts new file mode 100644 index 00000000000..df95e9a4359 --- /dev/null +++ b/packages/integrations/useAsyncValidator/component.ts @@ -0,0 +1,26 @@ +import type { PropType } from 'vue-demi' +import { defineComponent, reactive } from 'vue-demi' +import { useAsyncValidator } from '@vueuse/integrations' +import type { Rules } from 'async-validator' + +export const UseAsyncValidator = defineComponent({ + name: 'UseAsyncValidator', + props: { + form: { + type: Object as PropType>, + required: true, + }, + rules: { + type: Object as PropType, + required: true, + }, + }, + setup(props, { slots }) { + const data = reactive(useAsyncValidator(props.form, props.rules)) + + return () => { + if (slots.default) + return slots.default(data) + } + }, +}) diff --git a/packages/integrations/useAsyncValidator/demo.vue b/packages/integrations/useAsyncValidator/demo.vue new file mode 100644 index 00000000000..9fcc4aeecc7 --- /dev/null +++ b/packages/integrations/useAsyncValidator/demo.vue @@ -0,0 +1,75 @@ + + + diff --git a/packages/integrations/useAsyncValidator/index.md b/packages/integrations/useAsyncValidator/index.md new file mode 100644 index 00000000000..ac3c9633e8f --- /dev/null +++ b/packages/integrations/useAsyncValidator/index.md @@ -0,0 +1,19 @@ +--- +category: '@Integrations' +--- + +# useAsyncValidator + +wrapper for [`async-validator`](https://github.com/yiminghe/async-validator) + +## Install + +```bash +npm i async-validator +``` + +## Usage + +```ts +import { useAsyncValidator } from '@vueuse/integrations/useAsyncValidator' +``` diff --git a/packages/integrations/useAsyncValidator/index.test.ts b/packages/integrations/useAsyncValidator/index.test.ts new file mode 100644 index 00000000000..38a3cd9ce75 --- /dev/null +++ b/packages/integrations/useAsyncValidator/index.test.ts @@ -0,0 +1,135 @@ +import type { Rules } from 'async-validator' +import type { Ref } from 'vue-demi' +import { ref } from 'vue-demi' +import { useAsyncValidator } from '.' + +describe('useAsyncValidator', () => { + let form: { + name: string + age: number + } + + beforeEach(() => { + form = { + name: 'jelf', + age: 24, + } + }) + + it('should be defined', () => { + expect(useAsyncValidator).toBeDefined() + }) + + it('should pass', () => { + const rules: Rules = { + name: { + type: 'string', + }, + age: { + type: 'number', + }, + } + const { pass, errors, isFinished, then } = useAsyncValidator(form, rules) + then(() => { + expect(isFinished.value).toBe(true) + expect(pass.value).toBe(true) + expect(errors.value).toMatchObject([]) + }) + }) + + it('should async', async () => { + const rules: Rules = { + name: { + type: 'string', + }, + age: { + type: 'number', + }, + } + const { pass, errors, isFinished, then } = useAsyncValidator(form, rules) + expect(isFinished.value).toBe(false) + expect(pass.value).toBe(false) + expect(errors.value).toMatchObject([]) + + then(() => { + expect(isFinished.value).toBe(true) + expect(pass.value).toBe(true) + expect(errors.value).toMatchObject([]) + }) + }) + + it('should can be await', async () => { + const rules: Rules = { + name: { + type: 'string', + }, + age: { + type: 'number', + }, + } + const { pass, errors, isFinished } = await useAsyncValidator(form, rules) + expect(isFinished.value).toBe(true) + expect(pass.value).toBe(true) + expect(errors.value).toMatchObject([]) + }) + + it('should fail to validate', async () => { + const rules: Rules = { + name: { + type: 'string', + min: 5, + max: 20, + message: 'name length must be 5-20', + }, + age: { + type: 'number', + }, + } + const { pass, errors, isFinished } = await useAsyncValidator(form, rules) + expect(isFinished.value).toBe(true) + expect(pass.value).toBe(false) + expect(errors.value).toMatchInlineSnapshot(` + [ + { + "field": "name", + "fieldValue": "jelf", + "message": "name length must be 5-20", + }, + ] + `) + }) + + it('should reactive', async () => { + const form = ref({ + name: 'jelf', + age: 24, + }) + + const rules = ref({ + name: { + type: 'string', + min: 5, + max: 20, + message: 'name length must be 5-20', + }, + age: { + type: 'number', + }, + }) as Ref + + const { pass, errors, isFinished } = await useAsyncValidator(form, rules) + expect(isFinished.value).toBe(true) + expect(pass.value).toBe(false) + expect(errors.value).toMatchInlineSnapshot(` + [ + { + "field": "name", + "fieldValue": "jelf", + "message": "name length must be 5-20", + }, + ] + `) + + form.value.name = 'okxiaoliang4' + }) +}) diff --git a/packages/integrations/useAsyncValidator/index.ts b/packages/integrations/useAsyncValidator/index.ts new file mode 100644 index 00000000000..5f89111dd65 --- /dev/null +++ b/packages/integrations/useAsyncValidator/index.ts @@ -0,0 +1,73 @@ +import type { MaybeRef } from '@vueuse/shared' +import { until } from '@vueuse/shared' +import Schema from 'async-validator' +import type { Rules, ValidateError } from 'async-validator' +import type { Ref } from 'vue-demi' +import { computed, ref, unref, watchEffect } from 'vue-demi' + +type AsyncValidatorError = Error & { + errors: ValidateError[] + fields: Record +} + +interface UseAsyncValidatorReturn { + pass: Ref + errorInfo: Ref + isFinished: Ref + errors: Ref + errorFields: Ref +} + +/** + * Wrapper for async-validator. + * + * @see https://vueuse.org/useAsyncValidator + */ +export function useAsyncValidator(value: MaybeRef>, rules: MaybeRef): UseAsyncValidatorReturn & PromiseLike { + const errorInfo = ref() + const isFinished = ref(false) + const pass = ref(false) + const errors = computed(() => errorInfo.value?.errors || []) + const errorFields = computed(() => errorInfo.value?.fields || {}) + + watchEffect(async () => { + isFinished.value = false + pass.value = false + const validator = new Schema(unref(rules)) + try { + await validator.validate(unref(value)) + pass.value = true + errorInfo.value = null + } + catch (err) { + errorInfo.value = err as AsyncValidatorError + } + finally { + isFinished.value = true + } + }) + + const shell = { + pass, + isFinished, + errorInfo, + errors, + errorFields, + } as UseAsyncValidatorReturn + + function waitUntilFinished() { + return new Promise((resolve, reject) => { + until(isFinished).toBe(true) + .then(() => resolve(shell)) + .catch(error => reject(error)) + }) + } + + return { + ...shell, + then(onFulfilled, onRejected) { + return waitUntilFinished() + .then(onFulfilled, onRejected) + }, + } +} diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 2eb2da5bb3c..b7690849fbb 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -183,6 +183,7 @@ importers: '@types/qrcode': ^1.4.2 '@vueuse/core': workspace:* '@vueuse/shared': workspace:* + async-validator: ^4.0.7 axios: ^0.27.2 change-case: ^4.1.2 drauu: ^0.3.0 @@ -200,6 +201,7 @@ importers: devDependencies: '@types/nprogress': 0.2.0 '@types/qrcode': 1.4.2 + async-validator: 4.0.7 axios: 0.27.2 change-case: 4.1.2 drauu: 0.3.0 @@ -3284,6 +3286,10 @@ packages: engines: {node: '>=8'} dev: true + /async-validator/4.0.7: + resolution: {integrity: sha512-Pj2IR7u8hmUEDOwB++su6baaRi+QvsgajuFB9j95foM1N2gy5HM4z60hfusIO0fBPG5uLAEl6yCJr1jNSVugEQ==} + dev: true + /async/0.9.2: resolution: {integrity: sha1-rqdNXmHB+JlhO/ZL2mbUx48v0X0=} dev: true