From ebfc46d82cf3c4e0e4ae7b486108028ca98bc7e4 Mon Sep 17 00:00:00 2001 From: Jason Turner Date: Mon, 25 Apr 2016 21:22:49 +1000 Subject: [PATCH 1/4] Upgrade to latest validator version. --- package.json | 2 +- src/Validator.js | 8 +++++--- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/package.json b/package.json index e31f51d..e535292 100644 --- a/package.json +++ b/package.json @@ -63,7 +63,7 @@ "babel-runtime": "^5.1.10", "classnames": "^2.0.0", "react-bootstrap": ">=0.23.0", - "validator": "^3.41.3", + "validator": "^5.2.0", "react-addons-create-fragment": "^0.14.7" } } diff --git a/src/Validator.js b/src/Validator.js index 0e267e9..17286fe 100644 --- a/src/Validator.js +++ b/src/Validator.js @@ -6,19 +6,21 @@ import validator from 'validator'; * @params {String} val * @returns {Boolean} */ -validator.extend('required', val => !validator.isNull(val)); +validator.required = (val) => !validator.isNull(val); +validator.isEmpty = (val) => validator.isNull(val); + /** * Returns true if the value is boolean true * * @params {String} val * @returns {Boolean} */ -validator.extend('isChecked', val => { +validator.isChecked = (val) => { // compare it against string representation of a bool value, because // validator ensures all incoming values are coerced to strings // https://github.com/chriso/validator.js#strings-only return val === 'true'; -}); +}; export default validator; From 189331f5665a652821109185244dacae94a0ee06 Mon Sep 17 00:00:00 2001 From: Jason Turner Date: Mon, 25 Apr 2016 23:58:28 +1000 Subject: [PATCH 2/4] Updated to work with new syntax for passing parameters. --- __tests__/ValidatedInputTests.js | 159 +++++++++++++++++++++++++++++++ src/Form.js | 33 +++++-- 2 files changed, 186 insertions(+), 6 deletions(-) create mode 100644 __tests__/ValidatedInputTests.js diff --git a/__tests__/ValidatedInputTests.js b/__tests__/ValidatedInputTests.js new file mode 100644 index 0000000..f94e625 --- /dev/null +++ b/__tests__/ValidatedInputTests.js @@ -0,0 +1,159 @@ +'use strict'; +import {ValidatedInput,Form} from '../src'; + +// Note: THere is an issue with react-bootstrap and mocking. For now the whole node_modules directory has been unmocked. +describe('React bootstrap ValidatedInput test', () => { + var React = require('react'); + var TestUtils = require('react-addons-test-utils'); + beforeEach(function() { + }); + it('Validates a number with minimum.', () => { + // Render into document + var submittedObject = null; + var validSubmit = function(event){ + submittedObject = event; + }; + + let item = TestUtils.renderIntoDocument( +
+ + ); + + let input = TestUtils.scryRenderedDOMComponentsWithClass(item, 'testInput')[1]; + input.value='not a number'; + TestUtils.Simulate.change(input); + TestUtils.Simulate.submit(item.refs.form); + // test for error messages next to the item + expect(submittedObject).toBe(null); + + input.value='9'; + TestUtils.Simulate.change(input); + TestUtils.Simulate.submit(item.refs.form); + // test for error messages next to the item + expect(submittedObject).toBe(null); + + input.value='19'; + TestUtils.Simulate.change(input); + TestUtils.Simulate.submit(item.refs.form); + // test for error messages next to the item + expect(submittedObject.number).toBe('19'); + + + }); + + it('Validates an empty field.', () => { + // Render into document + var submittedObject = null; + var validSubmit = function(event){ + submittedObject = event; + }; + + let item = TestUtils.renderIntoDocument( +
+ + ); + + let input = TestUtils.scryRenderedDOMComponentsWithClass(item, 'testInput')[1]; + input.value='aaaa'; + TestUtils.Simulate.change(input); + TestUtils.Simulate.submit(item.refs.form); + // test for error messages next to the item + expect(submittedObject).toBe(null); + input.value=''; + TestUtils.Simulate.change(input); + TestUtils.Simulate.submit(item.refs.form); + // test for error messages next to the item + expect(submittedObject.email).toBe(''); + }); + + it('Validates a number.', () => { + // Render into document + var submittedObject = null; + var validSubmit = function(event){ + submittedObject = event; + }; + + let item = TestUtils.renderIntoDocument( +
+ + ); + + let input = TestUtils.scryRenderedDOMComponentsWithClass(item, 'testInput')[1]; + input.value='not a number'; + TestUtils.Simulate.change(input); + TestUtils.Simulate.submit(item.refs.form); + // test for error messages next to the item + expect(submittedObject).toBe(null); + + input.value='23'; + TestUtils.Simulate.change(input); + TestUtils.Simulate.submit(item.refs.form); + // test for error messages next to the item + expect(submittedObject.number).toBe('23'); + }); + + it('Validates an email.', () => { + // Render into document + var submittedObject = null; + var validSubmit = function(event){ + submittedObject = event; + }; + + let item = TestUtils.renderIntoDocument( +
+ + ); + + let input = TestUtils.scryRenderedDOMComponentsWithClass(item, 'testInput')[1]; + input.value='notavlidaemailaddress'; + TestUtils.Simulate.change(input); + TestUtils.Simulate.submit(item.refs.form); + // test for error messages next to the item + expect(submittedObject).toBe(null); + + input.value='test@nowhere.com'; + TestUtils.Simulate.change(input); + TestUtils.Simulate.submit(item.refs.form); + console.log(submittedObject); + expect(submittedObject.email).toBe('test@nowhere.com'); + }); + +}); diff --git a/src/Form.js b/src/Form.js index d8b82dc..8bd86a2 100644 --- a/src/Form.js +++ b/src/Form.js @@ -200,7 +200,7 @@ export default class Form extends InputContainer { if (typeof this.props.validateOne === 'function') { result = this.props.validateOne(iptName, value, context, result); - } + } // if result is !== true, it is considered an error // it can be either bool or string error if (result !== true) { @@ -248,10 +248,31 @@ export default class Form extends InputContainer { } _compileValidationRules(input, ruleProp) { - let rules = ruleProp.split(',').map(rule => { - let params = rule.split(':'); - let name = params.shift(); - let inverse = name[0] === '!'; + + let deliminator =','; + let andCondition =true; + // set the deliminator + if(ruleProp.indexOf('|')>0){ + + deliminator='|'; + andCondition=false; + } + // Split and groups + let regex = /([!\w]+()\([^\)]*\)|[!\w]+)/g; + // Note: need to test against negation as well.. + let tmp = ruleProp.match(regex);//.map(rule => { + let rules = ruleProp.match(regex).map(rule => { + let params = rule.match(/(\{[^}]*})/g); + if(!params){ + params = []; + } else{ + params = params.map(param =>{ + return JSON.parse(param); + }); + params = params[0]; + } + let name = rule.split('(').shift(); + let inverse = name[0] === '!'; if (inverse) { name = name.substr(1); @@ -270,7 +291,7 @@ export default class Form extends InputContainer { throw new Error('Invalid input validation rule "' + rule.name + '"'); } - let ruleResult = validator[rule.name](val, ...rule.params); + let ruleResult = validator[rule.name](val, rule.params); if (rule.inverse) { ruleResult = !ruleResult; From c33f7fed907612cff2bbf477db70535fa653cd8f Mon Sep 17 00:00:00 2001 From: Jason Turner Date: Tue, 26 Apr 2016 22:38:49 +1000 Subject: [PATCH 3/4] Moved to more specific validation rules. Removed regexes. --- __tests__/ValidatedInputTests.js | 6 ++- src/Form.js | 82 ++++++++++++++++++++++++-------- 2 files changed, 68 insertions(+), 20 deletions(-) diff --git a/__tests__/ValidatedInputTests.js b/__tests__/ValidatedInputTests.js index f94e625..8f1e836 100644 --- a/__tests__/ValidatedInputTests.js +++ b/__tests__/ValidatedInputTests.js @@ -21,7 +21,11 @@ describe('React bootstrap ValidatedInput test', () => { label='Number' name='number' className='testInput' - validate='isInt({ "min": 10, "max": 99 })' + validate={[{ + "name" :"isInt", + "params": { "min": 10, "max": 99 }, + "condition": "and" + }]} errorHelp={{ isInt: 'Must be a whole number' }} diff --git a/src/Form.js b/src/Form.js index 8bd86a2..7625daa 100644 --- a/src/Form.js +++ b/src/Form.js @@ -38,7 +38,10 @@ export default class Form extends InputContainer { if (typeof input.props.validate === 'string') { this._validators[input.props.name] = this._compileValidationRules(input, input.props.validate); + } else if(input.props.validate){ + this._validators[input.props.name] = this._compileValidationRulesSpecial(input, input.props.validate); } + } unregisterInput(input) { @@ -194,6 +197,8 @@ export default class Form extends InputContainer { result = validate(value, context); } else if (typeof validate === 'string') { result = this._validators[iptName](value); + } else if(typeof validate === 'object'){ + result = this._validators[iptName](value); } else { result = true; } @@ -246,9 +251,55 @@ export default class Form extends InputContainer { errors: errors }; } + _compileValidationRulesSpecial(input, ruleProp) { + + // Note: need to test against negation as well.. + let rules = ruleProp.map(rule => { + /* + { + "name" :"isInt", + "params": { "min": 10, "max": 99 }, + "condition": "and", + "inverse": false + } + */ + let params = []; + + if(rule.params){ + params = rule.params; + } + + let inverse = rule.inverse; + return { "name":rule.name, "inverse":rule.inverse, params, "condition":rule.condition }; + }); + let validator = (input.props && input.props.type) === 'file' ? FileValidator : Validator; + + return val => { + let result = true; + let previousResult = true; + rules.forEach(rule => { + if (typeof validator[rule.name] !== 'function') { + throw new Error('Invalid input validation rule "' + rule.name + '"'); + } + let ruleResult = validator[rule.name](val, rule.params); + + if (rule.inverse) { + ruleResult = !ruleResult; + } + if(rule.condition && rule.condition === "or"){ + ruleResult = ruleResult || previousResult; + } + previousResult = ruleResult; + if (result === true && ruleResult !== true) { + result = getInputErrorMessage(input, rule.name) || + getInputErrorMessage(this, rule.name) || false; + } + }); + return result; + }; + } _compileValidationRules(input, ruleProp) { - let deliminator =','; let andCondition =true; // set the deliminator @@ -258,44 +309,37 @@ export default class Form extends InputContainer { andCondition=false; } // Split and groups - let regex = /([!\w]+()\([^\)]*\)|[!\w]+)/g; - // Note: need to test against negation as well.. - let tmp = ruleProp.match(regex);//.map(rule => { - let rules = ruleProp.match(regex).map(rule => { - let params = rule.match(/(\{[^}]*})/g); - if(!params){ - params = []; - } else{ - params = params.map(param =>{ - return JSON.parse(param); - }); - params = params[0]; - } - let name = rule.split('(').shift(); - let inverse = name[0] === '!'; + let rules = ruleProp.split(deliminator).map(rule => { + let params = rule.split(':'); + let name = params.shift(); + let inverse = name[0] === '!'; if (inverse) { name = name.substr(1); } - return { name, inverse, params }; + return { name, inverse, params,andCondition:andCondition }; }); let validator = (input.props && input.props.type) === 'file' ? FileValidator : Validator; return val => { let result = true; - + let previousResult = true; rules.forEach(rule => { if (typeof validator[rule.name] !== 'function') { throw new Error('Invalid input validation rule "' + rule.name + '"'); } - let ruleResult = validator[rule.name](val, rule.params); + let ruleResult = validator[rule.name](val, ...rule.params); if (rule.inverse) { ruleResult = !ruleResult; } + if(!rule.andCondition){ + ruleResult = ruleResult || previousResult; + } + previousResult = ruleResult; if (result === true && ruleResult !== true) { result = getInputErrorMessage(input, rule.name) || From c39e2b595fea008a93043b8b8f33cb0e8889ea15 Mon Sep 17 00:00:00 2001 From: Jason Turner Date: Tue, 26 Apr 2016 22:52:16 +1000 Subject: [PATCH 4/4] Update documentation with new syntax. --- README.md | 18 +++++++++++++++++- 1 file changed, 17 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 82e5381..d3df254 100644 --- a/README.md +++ b/README.md @@ -43,7 +43,23 @@ class MyRegistrationForm extends React.Component { isEmail: 'Email is invalid' }} /> - +
+ + );