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' }} /> - +
+ + ); { + 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/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/Form.js b/src/Form.js index d8b82dc..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,13 +197,15 @@ 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; } 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) { @@ -246,9 +251,65 @@ 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 rules = ruleProp.split(',').map(rule => { + let deliminator =','; + let andCondition =true; + // set the deliminator + if(ruleProp.indexOf('|')>0){ + + deliminator='|'; + andCondition=false; + } + // Split and groups + let rules = ruleProp.split(deliminator).map(rule => { let params = rule.split(':'); let name = params.shift(); let inverse = name[0] === '!'; @@ -257,14 +318,14 @@ export default class Form extends InputContainer { 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 + '"'); @@ -275,6 +336,10 @@ export default class Form extends InputContainer { if (rule.inverse) { ruleResult = !ruleResult; } + if(!rule.andCondition){ + ruleResult = ruleResult || previousResult; + } + previousResult = ruleResult; if (result === true && ruleResult !== true) { result = getInputErrorMessage(input, rule.name) || 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;