From 1aefc98f6b204b94f9d4c8b91400593513796b18 Mon Sep 17 00:00:00 2001 From: Pojen Chen Date: Sat, 27 Apr 2019 22:04:21 +0900 Subject: [PATCH 01/22] Fix: check whether validator is a function to avoid type error --- packages/canner/src/hocs/validation.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/canner/src/hocs/validation.js b/packages/canner/src/hocs/validation.js index 5627773c..555dd7f3 100644 --- a/packages/canner/src/hocs/validation.js +++ b/packages/canner/src/hocs/validation.js @@ -3,7 +3,7 @@ import * as React from 'react'; import RefId from 'canner-ref-id'; import Ajv from 'ajv'; -import {isEmpty, isArray, isPlainObject, get} from 'lodash'; +import {isEmpty, isArray, isPlainObject, isFunction, get} from 'lodash'; import type {HOCProps} from './types'; type State = { @@ -49,7 +49,7 @@ export default function withValidation(Com: React.ComponentType<*>) { // custom validator const {validator, errorMessage} = validation; const reject = message => ({error: true, message}); - const validatorResult = validator && validator(value, reject); + const validatorResult = (validator && isFunction(validator) ) && validator(value, reject); let customValid = !(validatorResult && validatorResult.error); // if value is empty, should not validate with ajv From db82657cc8f9fe88cd77ea24329c9e72af73bfd2 Mon Sep 17 00:00:00 2001 From: Pojen Chen Date: Sat, 27 Apr 2019 22:07:42 +0900 Subject: [PATCH 02/22] Fix: typo `ComponentWithValidation` --- packages/canner/src/hocs/validation.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/canner/src/hocs/validation.js b/packages/canner/src/hocs/validation.js index 555dd7f3..81042b66 100644 --- a/packages/canner/src/hocs/validation.js +++ b/packages/canner/src/hocs/validation.js @@ -12,7 +12,7 @@ type State = { } export default function withValidation(Com: React.ComponentType<*>) { - return class ComponentWithValition extends React.Component { + return class ComponentWithValidation extends React.Component { key: string; id: ?string; callbackId: ?string; From 8c03926d17986a8fdb63a880140ab303bd06a200 Mon Sep 17 00:00:00 2001 From: Pojen Chen Date: Mon, 29 Apr 2019 14:17:09 +0900 Subject: [PATCH 03/22] Only pass schema property to avj.compile() --- packages/canner/src/hocs/validation.js | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/packages/canner/src/hocs/validation.js b/packages/canner/src/hocs/validation.js index 81042b66..1f3a3164 100644 --- a/packages/canner/src/hocs/validation.js +++ b/packages/canner/src/hocs/validation.js @@ -42,18 +42,22 @@ export default function withValidation(Com: React.ComponentType<*>) { const {value} = getValueAndPaths(result.data, paths); const isRequiredValid = required ? Boolean(value) : true; + const {schema, validator, errorMessage} = validation; + let validate = null + // Ajv validation - const ajv = new Ajv(); - const validate = ajv.compile(validation); - + if(schema && !isEmpty(schema)) { + const ajv = new Ajv(); + validate = ajv.compile(schema); + } // custom validator - const {validator, errorMessage} = validation; const reject = message => ({error: true, message}); const validatorResult = (validator && isFunction(validator) ) && validator(value, reject); let customValid = !(validatorResult && validatorResult.error); + // if value is empty, should not validate with ajv - if (customValid && isRequiredValid && (!value || validate(value))) { + if (customValid && isRequiredValid && (!(value && isFunction(validate)) || validate(value))) { this.setState({ error: false, errorInfo: [] From ae5d0ce72d8a8906a1f0dc27e2e577570aa2af44 Mon Sep 17 00:00:00 2001 From: Pojen Chen Date: Mon, 29 Apr 2019 14:36:57 +0900 Subject: [PATCH 04/22] Fix some coding style --- packages/canner/src/hocs/validation.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/canner/src/hocs/validation.js b/packages/canner/src/hocs/validation.js index 1f3a3164..13234988 100644 --- a/packages/canner/src/hocs/validation.js +++ b/packages/canner/src/hocs/validation.js @@ -43,7 +43,7 @@ export default function withValidation(Com: React.ComponentType<*>) { const isRequiredValid = required ? Boolean(value) : true; const {schema, validator, errorMessage} = validation; - let validate = null + let validate = null; // Ajv validation if(schema && !isEmpty(schema)) { @@ -52,7 +52,7 @@ export default function withValidation(Com: React.ComponentType<*>) { } // custom validator const reject = message => ({error: true, message}); - const validatorResult = (validator && isFunction(validator) ) && validator(value, reject); + const validatorResult = (validator && isFunction(validator)) && validator(value, reject); let customValid = !(validatorResult && validatorResult.error); From 991c04f135109f91843e4fcf66812482c3579cac Mon Sep 17 00:00:00 2001 From: Pojen Chen Date: Thu, 9 May 2019 22:32:07 +0900 Subject: [PATCH 05/22] Add async keyword to validate function --- packages/canner/src/hocs/validation.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/canner/src/hocs/validation.js b/packages/canner/src/hocs/validation.js index 13234988..33b9488e 100644 --- a/packages/canner/src/hocs/validation.js +++ b/packages/canner/src/hocs/validation.js @@ -35,7 +35,7 @@ export default function withValidation(Com: React.ComponentType<*>) { this.removeOnDeploy(); } - validate = (result: any) => { + validate = async (result: any) => { const {refId, validation = {}, required = false} = this.props; // required const paths = refId.getPathArr().slice(1); From 2f642e0cd9a8707c8579be63abe2fe74e1655462 Mon Sep 17 00:00:00 2001 From: Pojen Chen Date: Fri, 10 May 2019 08:55:21 +0900 Subject: [PATCH 06/22] Implement asynchronous pipline of validation --- packages/canner/src/hocs/validation.js | 133 ++++++++++++++++++------- 1 file changed, 96 insertions(+), 37 deletions(-) diff --git a/packages/canner/src/hocs/validation.js b/packages/canner/src/hocs/validation.js index 33b9488e..8a53425e 100644 --- a/packages/canner/src/hocs/validation.js +++ b/packages/canner/src/hocs/validation.js @@ -3,7 +3,7 @@ import * as React from 'react'; import RefId from 'canner-ref-id'; import Ajv from 'ajv'; -import {isEmpty, isArray, isPlainObject, isFunction, get} from 'lodash'; +import {isEmpty, isObject, isArray, isPlainObject, isFunction, get} from 'lodash'; import type {HOCProps} from './types'; type State = { @@ -11,6 +11,62 @@ type State = { errorInfo: Array } +const _isRequiredValidation = async (value) => { + const valid = Boolean(value) + return { + error: !valid, + errorInfo: !valid ? [{message: 'should be required'}] :[] + } +} + +const checkValidation = (validation) => { + return (isObject(validation) && !isEmpty(validation)) +} + +const checkSchema = (schema) => { + return (isObject(schema) && !isEmpty(schema) ) +} +const checkValidator = (validator) => { + return (isFunction(validator)) +} + +const _schemaValidation = (schema, errorMessage) => { + const ajv = new Ajv(); + const validate = ajv.compile(schema); + return async (value) => { + try { + const error = !validate(value) + const errorInfo = error ? [].concat( errorMessage ? {message: errorMessage} : validate.errors ) : [] + return { + error, + errorInfo + } + } + catch(err){ + return { + error: true, + errorInfo: [{message: err}] + } + } + + } +} +const _customizedValidator = (validator) => async (value) => { + try { + const errorMessage = await validator(value) + return { + error: Boolean(errorMessage), + errorInfo: [{message: errorMessage}] + } + } + catch(err) { + return { + error: true, + errorInfo: [{message: err}] + } + } +} + export default function withValidation(Com: React.ComponentType<*>) { return class ComponentWithValidation extends React.Component { key: string; @@ -35,52 +91,55 @@ export default function withValidation(Com: React.ComponentType<*>) { this.removeOnDeploy(); } + handleValidationResult = (results: any) => { + + let error = false; + let errorInfo = []; + + for(let index = 0; index < results.length; index++) { + error = error || results[index].error + errorInfo = errorInfo.concat(results[index].errorInfo); + } + + this.setState({ + error, + errorInfo + }); + + return { + error, + errorInfo + } + } + validate = async (result: any) => { - const {refId, validation = {}, required = false} = this.props; + const {refId, required = false, validation} = this.props; // required const paths = refId.getPathArr().slice(1); const {value} = getValueAndPaths(result.data, paths); - const isRequiredValid = required ? Boolean(value) : true; - - const {schema, validator, errorMessage} = validation; - let validate = null; + const promiseQueue = []; - // Ajv validation - if(schema && !isEmpty(schema)) { - const ajv = new Ajv(); - validate = ajv.compile(schema); + // check whether value is required in first step + if(required) { + promiseQueue.push(_isRequiredValidation(value)); } - // custom validator - const reject = message => ({error: true, message}); - const validatorResult = (validator && isFunction(validator)) && validator(value, reject); - - let customValid = !(validatorResult && validatorResult.error); - - // if value is empty, should not validate with ajv - if (customValid && isRequiredValid && (!(value && isFunction(validate)) || validate(value))) { - this.setState({ - error: false, - errorInfo: [] - }); - return result; + + // skip validation if object validation is undefined or empty + if(checkValidation(validation)) { + const {schema, errorMessage, validator} = validation; + if(value && checkSchema(schema)) { + promiseQueue.push(_schemaValidation(schema, errorMessage)(value)); + } + if(checkValidator(validator)) { + promiseQueue.push(_customizedValidator(validator)(value)); + } } - - - const errorInfo = [] - .concat(isRequiredValid ? [] : { - message: 'should be required' - }) - .concat(validate.errors ? (errorMessage ? {message: errorMessage} : validate.errors) : []) - .concat(customValid ? [] : validatorResult); - this.setState({ - error: true, - errorInfo: errorInfo - }); + const ValidationResult = await Promise.all(promiseQueue); + return { ...result, - error: true, - errorInfo: errorInfo + ...this.handleValidationResult(ValidationResult) } } From 763fde42f75f5b1547ec7cd1f84c51896ab83609 Mon Sep 17 00:00:00 2001 From: Pojen Chen Date: Tue, 14 May 2019 12:08:46 +0900 Subject: [PATCH 07/22] Fix typo: withValidation --- packages/canner/test/hocs/validation.test.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/canner/test/hocs/validation.test.js b/packages/canner/test/hocs/validation.test.js index 3db47d01..4da82ae1 100644 --- a/packages/canner/test/hocs/validation.test.js +++ b/packages/canner/test/hocs/validation.test.js @@ -1,7 +1,7 @@ import * as React from 'react'; import Enzyme, { mount } from 'enzyme'; import Adapter from 'enzyme-adapter-react-16'; -import withValidationn from '../../src/hocs/validation'; +import withValidation from '../../src/hocs/validation'; import RefId from 'canner-ref-id'; @@ -21,7 +21,7 @@ describe('withValidation', () => { onDeploy, removeOnDeploy } - WrapperComponent = withValidationn(MockComponent); + WrapperComponent = withValidation(MockComponent); }); it('should error state = false', () => { From 0d5146675f1c2bafd4b12b1e689270de7834146b Mon Sep 17 00:00:00 2001 From: Pojen Chen Date: Tue, 14 May 2019 23:38:07 +0900 Subject: [PATCH 08/22] Update validation test file because of object structure change --- packages/canner/test/hocs/validation.test.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/canner/test/hocs/validation.test.js b/packages/canner/test/hocs/validation.test.js index 4da82ae1..7389fdad 100644 --- a/packages/canner/test/hocs/validation.test.js +++ b/packages/canner/test/hocs/validation.test.js @@ -88,7 +88,7 @@ describe('withValidation', () => { {...props} onDeploy={jest.fn().mockImplementation((_, fn) => (fn(result)))} required - validation={{pattern: '^http://[.]+'}} + validation={{schema: {pattern: '^http://[.]+'}}} />); expect(wrapper.state()).toMatchObject({ error: true, @@ -109,7 +109,7 @@ describe('withValidation', () => { {...props} onDeploy={jest.fn().mockImplementation((_, fn) => (fn(result)))} required - validation={{pattern: '^http://[.]+', errorMessage}} + validation={{schema: {pattern: '^http://[.]+'}, errorMessage}} />); expect(wrapper.state()).toMatchObject({ error: true, @@ -128,7 +128,7 @@ describe('withValidation', () => { const wrapper = mount( (fn(result)))} - validation={{pattern: '^http://[.]+'}} + validation={{schema: {pattern: '^http://[.]+'}}} />); expect(wrapper.state()).toMatchObject({ error: false, From ff1312c1011d95b01f5dcfefad7fd5edeaf0ca89 Mon Sep 17 00:00:00 2001 From: Pojen Chen Date: Wed, 15 May 2019 06:18:43 +0900 Subject: [PATCH 09/22] Update validator test --- packages/canner/test/hocs/validation.test.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/canner/test/hocs/validation.test.js b/packages/canner/test/hocs/validation.test.js index 7389fdad..8d3267d6 100644 --- a/packages/canner/test/hocs/validation.test.js +++ b/packages/canner/test/hocs/validation.test.js @@ -147,9 +147,9 @@ describe('withValidation', () => { onDeploy={jest.fn().mockImplementation((_, fn) => (fn(result)))} validation={ { - validator: (content, reject) => { + validator: (content) => { if (!content) { - return reject('should be required'); + return 'should be required'; } } } From dfa6ce1598c3b865a24403cbd5e16fde54a7cfe7 Mon Sep 17 00:00:00 2001 From: Pojen Chen Date: Wed, 15 May 2019 20:49:20 +0900 Subject: [PATCH 10/22] Reform validation's test file with async/await, and remove unused props --- packages/canner/test/hocs/validation.test.js | 24 ++++++++++---------- 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/packages/canner/test/hocs/validation.test.js b/packages/canner/test/hocs/validation.test.js index 8d3267d6..690b76a7 100644 --- a/packages/canner/test/hocs/validation.test.js +++ b/packages/canner/test/hocs/validation.test.js @@ -42,7 +42,7 @@ describe('withValidation', () => { expect(onDeploy).toBeCalledWith('posts', wrapper.instance().validate); }); - it('should pass validation', () => { + it('should pass validation', async () => { const result = { data: { 0: { url: 'https://'} @@ -50,16 +50,16 @@ describe('withValidation', () => { }; const wrapper = mount( (fn(result)))} required />); + await wrapper.instance().validate(result) expect(wrapper.state()).toEqual({ error: false, errorInfo: [] }) }); - it('should not pass required validation', () => { + it('should not pass required validation', async () => { const result = { data: { 0: { url: ''} @@ -67,9 +67,9 @@ describe('withValidation', () => { }; const wrapper = mount( (fn(result)))} required />); + await wrapper.instance().validate(result) expect(wrapper.state()).toEqual({ error: true, errorInfo: [{ @@ -78,7 +78,7 @@ describe('withValidation', () => { }) }); - it('should not pass ajv validation', () => { + it('should not pass ajv validation', async () => { const result = { data: { 0: { url: 'imgurl.com'} @@ -86,10 +86,10 @@ describe('withValidation', () => { }; const wrapper = mount( (fn(result)))} required validation={{schema: {pattern: '^http://[.]+'}}} />); + await wrapper.instance().validate(result) expect(wrapper.state()).toMatchObject({ error: true, errorInfo: [{ @@ -98,7 +98,7 @@ describe('withValidation', () => { }) }); - it('should not pass ajv validation with custom error message', () => { + it('should not pass ajv validation with custom error message', async () => { const result = { data: { 0: { url: 'imgurl.com'} @@ -107,10 +107,10 @@ describe('withValidation', () => { const errorMessage = 'custom error'; const wrapper = mount( (fn(result)))} required validation={{schema: {pattern: '^http://[.]+'}, errorMessage}} />); + await wrapper.instance().validate(result) expect(wrapper.state()).toMatchObject({ error: true, errorInfo: [{ @@ -119,7 +119,7 @@ describe('withValidation', () => { }) }); - it('should pass ajv validation if empty', () => { + it('should pass ajv validation if empty', async () => { const result = { data: { 0: { url: ''} @@ -127,16 +127,16 @@ describe('withValidation', () => { }; const wrapper = mount( (fn(result)))} validation={{schema: {pattern: '^http://[.]+'}}} />); + await wrapper.instance().validate(result) expect(wrapper.state()).toMatchObject({ error: false, errorInfo: [] }) }); - it('should use custom validation', () => { + it('should use custom validation', async () => { const result = { data: { 0: { url: ''} @@ -144,7 +144,6 @@ describe('withValidation', () => { }; const wrapper = mount( (fn(result)))} validation={ { validator: (content) => { @@ -155,6 +154,7 @@ describe('withValidation', () => { } } />); + await wrapper.instance().validate(result) expect(wrapper.state()).toMatchObject({ error: true, errorInfo: [{ From c5711d04ad02e889cacefe0c8a64fdfb029d3516 Mon Sep 17 00:00:00 2001 From: Pojen Chen Date: Wed, 15 May 2019 20:58:41 +0900 Subject: [PATCH 11/22] Change the test label of customized validator --- packages/canner/test/hocs/validation.test.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/canner/test/hocs/validation.test.js b/packages/canner/test/hocs/validation.test.js index 690b76a7..840c9b1c 100644 --- a/packages/canner/test/hocs/validation.test.js +++ b/packages/canner/test/hocs/validation.test.js @@ -136,7 +136,7 @@ describe('withValidation', () => { }) }); - it('should use custom validation', async () => { + it('should use customized validator with error message', async () => { const result = { data: { 0: { url: ''} From 9e5748aef1e555c8392ae8b38a857de9b02a261c Mon Sep 17 00:00:00 2001 From: Pojen Chen Date: Wed, 15 May 2019 21:49:15 +0900 Subject: [PATCH 12/22] Create test cases of sync validator function --- packages/canner/test/hocs/validation.test.js | 51 +++++++++++++++++++- 1 file changed, 49 insertions(+), 2 deletions(-) diff --git a/packages/canner/test/hocs/validation.test.js b/packages/canner/test/hocs/validation.test.js index 840c9b1c..44f7b259 100644 --- a/packages/canner/test/hocs/validation.test.js +++ b/packages/canner/test/hocs/validation.test.js @@ -148,7 +148,7 @@ describe('withValidation', () => { { validator: (content) => { if (!content) { - return 'should be required'; + return 'error message as return value'; } } } @@ -158,11 +158,58 @@ describe('withValidation', () => { expect(wrapper.state()).toMatchObject({ error: true, errorInfo: [{ - message: 'should be required' + message: 'error message as return value' + }] + }) + }); + + it('should use customized validator with throwing error', async () => { + const result = { + data: { + 0: { url: ''} + } + }; + const wrapper = mount( { + throw 'Throw error' + } + } + } + />); + await wrapper.instance().validate(result) + expect(wrapper.state()).toMatchObject({ + error: true, + errorInfo: [{ + message: 'Throw error' }] }) }); + + it('should use customized validator with void return', async () => { + const result = { + data: { + 0: { url: ''} + } + }; + const wrapper = mount( {} + } + } + />); + await wrapper.instance().validate(result) + expect(wrapper.state()).toMatchObject({ + error: false, + errorInfo: [] + }) + }); + it('should removeOnDeploy not be called', () => { const wrapper = mount( Date: Wed, 15 May 2019 21:49:32 +0900 Subject: [PATCH 13/22] Correct fault in validation HoC --- packages/canner/src/hocs/validation.js | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/packages/canner/src/hocs/validation.js b/packages/canner/src/hocs/validation.js index 8a53425e..711620b5 100644 --- a/packages/canner/src/hocs/validation.js +++ b/packages/canner/src/hocs/validation.js @@ -3,7 +3,7 @@ import * as React from 'react'; import RefId from 'canner-ref-id'; import Ajv from 'ajv'; -import {isEmpty, isObject, isArray, isPlainObject, isFunction, get} from 'lodash'; +import {isEmpty, isObject, isArray, isPlainObject, isFunction, toString, get} from 'lodash'; import type {HOCProps} from './types'; type State = { @@ -35,8 +35,8 @@ const _schemaValidation = (schema, errorMessage) => { const validate = ajv.compile(schema); return async (value) => { try { - const error = !validate(value) - const errorInfo = error ? [].concat( errorMessage ? {message: errorMessage} : validate.errors ) : [] + const error = !validate(value); + const errorInfo = error ? [].concat( errorMessage ? {message: errorMessage} : validate.errors ) : []; return { error, errorInfo @@ -45,7 +45,7 @@ const _schemaValidation = (schema, errorMessage) => { catch(err){ return { error: true, - errorInfo: [{message: err}] + errorInfo: [{message: toString(err)}] } } @@ -53,16 +53,18 @@ const _schemaValidation = (schema, errorMessage) => { } const _customizedValidator = (validator) => async (value) => { try { - const errorMessage = await validator(value) + const errorMessage = await validator(value); + const error = Boolean(errorMessage); + const errorInfo = error ? [{message: errorMessage}] : [] return { - error: Boolean(errorMessage), - errorInfo: [{message: errorMessage}] + error, + errorInfo } } catch(err) { return { error: true, - errorInfo: [{message: err}] + errorInfo: [{message: toString(err)}] } } } From e10c57f66f291644e41aa75f346d63fb054f9f79 Mon Sep 17 00:00:00 2001 From: Pojen Chen Date: Wed, 15 May 2019 23:03:14 +0900 Subject: [PATCH 14/22] Create test cases with async-wait functions --- packages/canner/test/hocs/validation.test.js | 80 +++++++++++++++++++- 1 file changed, 79 insertions(+), 1 deletion(-) diff --git a/packages/canner/test/hocs/validation.test.js b/packages/canner/test/hocs/validation.test.js index 44f7b259..e1fbea2b 100644 --- a/packages/canner/test/hocs/validation.test.js +++ b/packages/canner/test/hocs/validation.test.js @@ -4,6 +4,7 @@ import Adapter from 'enzyme-adapter-react-16'; import withValidation from '../../src/hocs/validation'; import RefId from 'canner-ref-id'; +const sleep = ms => new Promise(resolve => setTimeout(resolve, ms)); Enzyme.configure({ adapter: new Adapter() }); @@ -136,6 +137,7 @@ describe('withValidation', () => { }) }); + // Synchronous functions it('should use customized validator with error message', async () => { const result = { data: { @@ -188,7 +190,6 @@ describe('withValidation', () => { }) }); - it('should use customized validator with void return', async () => { const result = { data: { @@ -210,6 +211,83 @@ describe('withValidation', () => { }) }); + // Async-await functions + it('should use customized async validator with error message', async () => { + const result = { + data: { + 0: { url: ''} + } + }; + const wrapper = mount( { + await sleep(5) + if (!content) { + return 'error message as return value'; + } + } + } + } + />); + await wrapper.instance().validate(result) + expect(wrapper.state()).toMatchObject({ + error: true, + errorInfo: [{ + message: 'error message as return value' + }] + }) + }); + + it('should use customized async validator with throwing error', async () => { + const result = { + data: { + 0: { url: ''} + } + }; + const wrapper = mount( { + await sleep(5) + throw 'Throw error' + } + } + } + />); + await wrapper.instance().validate(result) + expect(wrapper.state()).toMatchObject({ + error: true, + errorInfo: [{ + message: 'Throw error' + }] + }) + }); + + it('should use customized async validator with void return', async () => { + const result = { + data: { + 0: { url: ''} + } + }; + const wrapper = mount( { await sleep(5) } + } + } + />); + await wrapper.instance().validate(result) + expect(wrapper.state()).toMatchObject({ + error: false, + errorInfo: [] + }) + }); + + it('should removeOnDeploy not be called', () => { const wrapper = mount( Date: Wed, 15 May 2019 23:12:03 +0900 Subject: [PATCH 15/22] Create test cases with promise operations --- packages/canner/test/hocs/validation.test.js | 74 ++++++++++++++++++++ 1 file changed, 74 insertions(+) diff --git a/packages/canner/test/hocs/validation.test.js b/packages/canner/test/hocs/validation.test.js index e1fbea2b..85af8c2c 100644 --- a/packages/canner/test/hocs/validation.test.js +++ b/packages/canner/test/hocs/validation.test.js @@ -287,6 +287,80 @@ describe('withValidation', () => { }) }); + // Function with promise operation + it('should use customized validator with a Promise', async () => { + const result = { + data: { + 0: { url: ''} + } + }; + const wrapper = mount( { + return new Promise(resolve => resolve('error message as resolved value')); + } + } + } + />); + await wrapper.instance().validate(result) + expect(wrapper.state()).toMatchObject({ + error: true, + errorInfo: [{ + message: 'error message as resolved value' + }] + }) + }); + + it('should use customized validator with a rejected Promise', async () => { + const result = { + data: { + 0: { url: ''} + } + }; + const wrapper = mount( { + return new Promise((resolve, reject) => { + reject(new Error('Rejected promise')); + }) + } + } + } + />); + await wrapper.instance().validate(result) + expect(wrapper.state()).toMatchObject({ + error: true, + errorInfo: [{ + message: 'Error: Rejected promise' + }] + }) + }); + + it('should use customized validator with a Promise', async () => { + const result = { + data: { + 0: { url: ''} + } + }; + const wrapper = mount( { return new Promise(resolve => resolve())} + } + } + />); + await wrapper.instance().validate(result) + expect(wrapper.state()).toMatchObject({ + error: false, + errorInfo: [] + }) + }); + it('should removeOnDeploy not be called', () => { const wrapper = mount( Date: Thu, 16 May 2019 00:37:34 +0900 Subject: [PATCH 16/22] Add try-catch in validate to handle uncaught error --- packages/canner/src/hocs/validation.js | 47 +++++++++++++++----------- 1 file changed, 27 insertions(+), 20 deletions(-) diff --git a/packages/canner/src/hocs/validation.js b/packages/canner/src/hocs/validation.js index 711620b5..9e9ebe2e 100644 --- a/packages/canner/src/hocs/validation.js +++ b/packages/canner/src/hocs/validation.js @@ -116,32 +116,39 @@ export default function withValidation(Com: React.ComponentType<*>) { validate = async (result: any) => { const {refId, required = false, validation} = this.props; - // required const paths = refId.getPathArr().slice(1); const {value} = getValueAndPaths(result.data, paths); const promiseQueue = []; - - // check whether value is required in first step - if(required) { - promiseQueue.push(_isRequiredValidation(value)); - } - - // skip validation if object validation is undefined or empty - if(checkValidation(validation)) { - const {schema, errorMessage, validator} = validation; - if(value && checkSchema(schema)) { - promiseQueue.push(_schemaValidation(schema, errorMessage)(value)); + try{ + // check whether value is required in first step + if(required) { + promiseQueue.push(_isRequiredValidation(value)); + } + + // skip validation if object validation is undefined or empty + if(checkValidation(validation)) { + const {schema, errorMessage, validator} = validation; + if(value && checkSchema(schema)) { + promiseQueue.push(_schemaValidation(schema, errorMessage)(value)); + } + if(checkValidator(validator)) { + promiseQueue.push(_customizedValidator(validator)(value)); + } } - if(checkValidator(validator)) { - promiseQueue.push(_customizedValidator(validator)(value)); + + const ValidationResult = await Promise.all(promiseQueue); + + return { + ...result, + ...this.handleValidationResult(ValidationResult) } } - - const ValidationResult = await Promise.all(promiseQueue); - - return { - ...result, - ...this.handleValidationResult(ValidationResult) + catch(err){ + return { + ...result, + error: true, + errorInfo: [].concat({message: toString(err)}) + } } } From 096a86a374112b1ca08ac96c800764467024b73e Mon Sep 17 00:00:00 2001 From: Pojen Chen Date: Thu, 16 May 2019 07:28:43 +0900 Subject: [PATCH 17/22] Workaround for when validator is not a function --- packages/canner/src/hocs/validation.js | 14 +++++++++--- packages/canner/test/hocs/validation.test.js | 23 ++++++++++++++++++++ 2 files changed, 34 insertions(+), 3 deletions(-) diff --git a/packages/canner/src/hocs/validation.js b/packages/canner/src/hocs/validation.js index 9e9ebe2e..4363e553 100644 --- a/packages/canner/src/hocs/validation.js +++ b/packages/canner/src/hocs/validation.js @@ -131,9 +131,13 @@ export default function withValidation(Com: React.ComponentType<*>) { if(value && checkSchema(schema)) { promiseQueue.push(_schemaValidation(schema, errorMessage)(value)); } - if(checkValidator(validator)) { - promiseQueue.push(_customizedValidator(validator)(value)); - } + if(validator) { + if(checkValidator(validator)) { + promiseQueue.push(_customizedValidator(validator)(value)); + } else { + throw 'Validator should be a function' + } + } } const ValidationResult = await Promise.all(promiseQueue); @@ -144,6 +148,10 @@ export default function withValidation(Com: React.ComponentType<*>) { } } catch(err){ + this.setState({ + error: true, + errorInfo: [].concat({message: toString(err)}) + }); return { ...result, error: true, diff --git a/packages/canner/test/hocs/validation.test.js b/packages/canner/test/hocs/validation.test.js index 85af8c2c..582ed3f1 100644 --- a/packages/canner/test/hocs/validation.test.js +++ b/packages/canner/test/hocs/validation.test.js @@ -211,6 +211,29 @@ describe('withValidation', () => { }) }); + it('validator is not a function', async () => { + const result = { + data: { + 0: { url: ''} + } + }; + const wrapper = mount(); + await wrapper.instance().validate(result) + expect(wrapper.state()).toMatchObject({ + error: true, + errorInfo: [{ + message: 'Validator should be a function' + }] + }) + }); + // Async-await functions it('should use customized async validator with error message', async () => { const result = { From 91038ba63f6ab78e29b79a2de91b4496ede98d77 Mon Sep 17 00:00:00 2001 From: Pojen Chen Date: Thu, 16 May 2019 09:10:06 +0900 Subject: [PATCH 18/22] Reove `_ ` prefix of promise creator --- packages/canner/src/hocs/validation.js | 25 +++++++++++++------------ 1 file changed, 13 insertions(+), 12 deletions(-) diff --git a/packages/canner/src/hocs/validation.js b/packages/canner/src/hocs/validation.js index 4363e553..ad37dd56 100644 --- a/packages/canner/src/hocs/validation.js +++ b/packages/canner/src/hocs/validation.js @@ -11,13 +11,6 @@ type State = { errorInfo: Array } -const _isRequiredValidation = async (value) => { - const valid = Boolean(value) - return { - error: !valid, - errorInfo: !valid ? [{message: 'should be required'}] :[] - } -} const checkValidation = (validation) => { return (isObject(validation) && !isEmpty(validation)) @@ -30,7 +23,15 @@ const checkValidator = (validator) => { return (isFunction(validator)) } -const _schemaValidation = (schema, errorMessage) => { +const isRequiredValidation = async (value) => { + const valid = Boolean(value) + return { + error: !valid, + errorInfo: !valid ? [{message: 'should be required'}] :[] + } +} + +const schemaValidation = (schema, errorMessage) => { const ajv = new Ajv(); const validate = ajv.compile(schema); return async (value) => { @@ -51,7 +52,7 @@ const _schemaValidation = (schema, errorMessage) => { } } -const _customizedValidator = (validator) => async (value) => { +const customizedValidator = (validator) => async (value) => { try { const errorMessage = await validator(value); const error = Boolean(errorMessage); @@ -122,18 +123,18 @@ export default function withValidation(Com: React.ComponentType<*>) { try{ // check whether value is required in first step if(required) { - promiseQueue.push(_isRequiredValidation(value)); + promiseQueue.push(isRequiredValidation(value)); } // skip validation if object validation is undefined or empty if(checkValidation(validation)) { const {schema, errorMessage, validator} = validation; if(value && checkSchema(schema)) { - promiseQueue.push(_schemaValidation(schema, errorMessage)(value)); + promiseQueue.push(schemaValidation(schema, errorMessage)(value)); } if(validator) { if(checkValidator(validator)) { - promiseQueue.push(_customizedValidator(validator)(value)); + promiseQueue.push(customizedValidator(validator)(value)); } else { throw 'Validator should be a function' } From 831fd24ba9bbfb9c79a97b8c040cf3f57cfc38d2 Mon Sep 17 00:00:00 2001 From: Pojen Chen Date: Thu, 16 May 2019 09:20:51 +0900 Subject: [PATCH 19/22] Update type definition of validation --- packages/canner/src/hocs/types.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/canner/src/hocs/types.js b/packages/canner/src/hocs/types.js index 70a2ad00..a0a08d8d 100644 --- a/packages/canner/src/hocs/types.js +++ b/packages/canner/src/hocs/types.js @@ -1,4 +1,4 @@ -// @flow +g// @flow import type {Action, ActionType} from '../action/types'; import type {Query} from '../query'; import type RefId from 'canner-ref-id'; @@ -17,7 +17,7 @@ export type Subscribe = (key: string, callback: (data: any) => void) => Subscrip export type Deploy = (key: string, id?: string) => Promise<*>; export type OnDeploy = (key: string, callback: Function) => any; export type RemoveOnDeploy = (key: string, callbackId: string) => void; -export type Validation = Object; +export type Validation = {schema?: Object, erorrMessage?: string, validator?: (value: any) => string | Promise | Promise | void} export type UIParams = Object; export type Relation = Object; export type RenderChildren = any => React.Node From 471a87560db100d223413d1317124380dec65d84 Mon Sep 17 00:00:00 2001 From: Pojen Chen Date: Sat, 18 May 2019 15:05:56 +0900 Subject: [PATCH 20/22] Update the usage of customized validator in docs --- docs/schema/utils.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/schema/utils.js b/docs/schema/utils.js index 2409c462..9f8985cb 100644 --- a/docs/schema/utils.js +++ b/docs/schema/utils.js @@ -3,9 +3,9 @@ import {Tooltip, Icon} from 'antd'; import TableSelectColumn from "../components/columns/select"; export const galleryValidation = { - validator: (content, reject) => { + validator: (content) => { if (content.length === 0) { - return reject("should at least have one photo"); + return "should at least have one photo"; } } }; From 0993e84ed7a085445eb5b95e65808cb4ba4d8912 Mon Sep 17 00:00:00 2001 From: Pojen Chen Date: Sun, 19 May 2019 13:40:22 +0900 Subject: [PATCH 21/22] Use keep (verb) as prefix for promise creator --- packages/canner/src/hocs/validation.js | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/packages/canner/src/hocs/validation.js b/packages/canner/src/hocs/validation.js index ad37dd56..0e8fbf12 100644 --- a/packages/canner/src/hocs/validation.js +++ b/packages/canner/src/hocs/validation.js @@ -23,7 +23,7 @@ const checkValidator = (validator) => { return (isFunction(validator)) } -const isRequiredValidation = async (value) => { +const keepRequired = async (value) => { const valid = Boolean(value) return { error: !valid, @@ -31,7 +31,7 @@ const isRequiredValidation = async (value) => { } } -const schemaValidation = (schema, errorMessage) => { +const keepSchemaValidation = (schema, errorMessage) => { const ajv = new Ajv(); const validate = ajv.compile(schema); return async (value) => { @@ -52,7 +52,7 @@ const schemaValidation = (schema, errorMessage) => { } } -const customizedValidator = (validator) => async (value) => { +const keepCustomizedValidator = (validator) => async (value) => { try { const errorMessage = await validator(value); const error = Boolean(errorMessage); @@ -123,18 +123,18 @@ export default function withValidation(Com: React.ComponentType<*>) { try{ // check whether value is required in first step if(required) { - promiseQueue.push(isRequiredValidation(value)); + promiseQueue.push(keepRequired(value)); } // skip validation if object validation is undefined or empty if(checkValidation(validation)) { const {schema, errorMessage, validator} = validation; if(value && checkSchema(schema)) { - promiseQueue.push(schemaValidation(schema, errorMessage)(value)); + promiseQueue.push(keepSchemaValidation(schema, errorMessage)(value)); } if(validator) { if(checkValidator(validator)) { - promiseQueue.push(customizedValidator(validator)(value)); + promiseQueue.push(keepCustomizedValidator(validator)(value)); } else { throw 'Validator should be a function' } From 9157f2cf2c910cbe8926e269ecf8d4b9aab2fa5c Mon Sep 17 00:00:00 2001 From: Pojen Chen Date: Wed, 29 May 2019 18:57:56 +0900 Subject: [PATCH 22/22] Change `keep`(verb) with `promise`(verb) --- packages/canner/src/hocs/validation.js | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/packages/canner/src/hocs/validation.js b/packages/canner/src/hocs/validation.js index 0e8fbf12..baa3c1e6 100644 --- a/packages/canner/src/hocs/validation.js +++ b/packages/canner/src/hocs/validation.js @@ -23,7 +23,7 @@ const checkValidator = (validator) => { return (isFunction(validator)) } -const keepRequired = async (value) => { +const promiseRequired = async (value) => { const valid = Boolean(value) return { error: !valid, @@ -31,7 +31,7 @@ const keepRequired = async (value) => { } } -const keepSchemaValidation = (schema, errorMessage) => { +const promiseSchemaValidation = (schema, errorMessage) => { const ajv = new Ajv(); const validate = ajv.compile(schema); return async (value) => { @@ -52,7 +52,7 @@ const keepSchemaValidation = (schema, errorMessage) => { } } -const keepCustomizedValidator = (validator) => async (value) => { +const promiseCustomizedValidator = (validator) => async (value) => { try { const errorMessage = await validator(value); const error = Boolean(errorMessage); @@ -123,18 +123,18 @@ export default function withValidation(Com: React.ComponentType<*>) { try{ // check whether value is required in first step if(required) { - promiseQueue.push(keepRequired(value)); + promiseQueue.push(promiseRequired(value)); } // skip validation if object validation is undefined or empty if(checkValidation(validation)) { const {schema, errorMessage, validator} = validation; if(value && checkSchema(schema)) { - promiseQueue.push(keepSchemaValidation(schema, errorMessage)(value)); + promiseQueue.push(promiseSchemaValidation(schema, errorMessage)(value)); } if(validator) { if(checkValidator(validator)) { - promiseQueue.push(keepCustomizedValidator(validator)(value)); + promiseQueue.push(promiseCustomizedValidator(validator)(value)); } else { throw 'Validator should be a function' }