diff --git a/src/required.js b/src/required.js index e98207d..582d932 100644 --- a/src/required.js +++ b/src/required.js @@ -6,27 +6,32 @@ export default function required(props) { return function validate(value) { let isValid = true; - if (value === null || value === notDefined) { + const parse = typeof props.parseValue === 'function' ? + props.parseValue : parseValue; + + const parsedValue = parse(value); + + if (parsedValue === null || parsedValue === notDefined) { isValid = false; - } else if (typeof value === 'string') { - if (props.trim !== false) { - if (typeof props.trim === 'function') { - value = props.trim(value); - } else { - value = value.trim(); - } - } - - isValid = !!value.length; - } else if (typeof value === 'boolean') { - // By supporting required on boolean values where false is invalid - // we open up scenarios for required checkboxes - isValid = value; + } else if (typeof parsedValue === 'string') { + isValid = !!parsedValue.length; + } else if (typeof parsedValue === 'boolean') { + isValid = parsedValue; } return { ...props, - isValid + isValid, + value, + parsedValue }; } } + +function parseValue(value) { + if (typeof value === 'string') { + return value.trim(); + } + + return value; +} diff --git a/test/required.spec.js b/test/required.spec.js index 219ffc3..d41a897 100644 --- a/test/required.spec.js +++ b/test/required.spec.js @@ -22,17 +22,12 @@ describe('required', () => { expect(result.isValid).toBe(false); }); - it('is not valid for all spaces', () => { - const result = validate(' '); - expect(result.isValid).toBe(false); - }); - it('is valid for non-empty strings', () => { const result = validate('not empty'); expect(result.isValid).toBe(true); }); - it('is valid for the number 0', () => { + it('is valid for the number 0 (because 0 is indeed a supplied number)', () => { const result = validate(0); expect(result.isValid).toBe(true); }); @@ -47,10 +42,27 @@ describe('required', () => { expect(result.isValid).toBe(true); }); - it('is not valid for boolean false', () => { + it('is not valid for boolean false (supporting scenarios like required checkboxes)', () => { const result = validate(false); expect(result.isValid).toBe(false); }); + + it('trims string values so that an all spaces value is not valid', () => { + const result = validate(' '); + expect(result.isValid).toBe(false); + }); + + describe('exposes values on the result object', () => { + it('for the original value', () => { + const result = validate(' Original '); + expect(result.value).toBe(' Original '); + }); + + it('for the parsed (trimmed) value', () => { + const result = validate(' Original '); + expect(result.parsedValue).toBe('Original'); + }); + }); }); describe('with props', () => { @@ -68,7 +80,7 @@ describe('required', () => { }); }); - describe('overrides isValid prop', () => { + describe('overrides isValid prop with the validation result', () => { it('when valid', () => { const validate = required({isValid: false}); const result = validate('Valid'); @@ -82,4 +94,104 @@ describe('required', () => { }); }); }); + + describe('value parsing can be overridden with a parseValue prop', () => { + it('affecting validity to make an invalid value valid', () => { + const parseValue = () => 'Valid'; + const validate = required({parseValue}); + + const result = validate(''); + expect(result.isValid).toBe(true); + }); + + it('affecting validity to make a valid value invalid', () => { + const parseValue = () => ''; + const validate = required({parseValue}); + + const result = validate('Valid'); + expect(result.isValid).toBe(false); + }); + + it('with the original value supplied to the parseValue function', () => { + let suppliedValue; + + function parseValue(value) { + suppliedValue = value; + return value; + } + + const validate = required({parseValue}); + const result = validate('Original'); + + expect(suppliedValue).toBe('Original'); + }); + + it('with the parsed value exposed as the parsedValue prop', () => { + const parseValue = (value) => 'Parsed: ' + value; + const validate = required({parseValue}); + + const result = validate('Value'); + expect(result.parsedValue).toBe('Parsed: Value'); + }); + + it('bypassing string trimming', () => { + const parseValue = (value) => value; + const validate = required({parseValue}); + + const result = validate(' '); + expect(result.parsedValue).toBe(' '); + }); + + describe('converting a value', () => { + it('from null to a string validates the string', () => { + const parseValue = () => 'Valid'; + const validate = required({parseValue}); + + const result = validate(null); + expect(result.isValid).toBe(true); + }); + + describe('from a string to a boolean validates the boolean', () => { + it('that is valid', () => { + const parseValue = () => true; + const validate = required({parseValue}); + + const result = validate(' '); + expect(result.isValid).toBe(true); + }); + + it('that is invalid', () => { + const parseValue = () => false; + const validate = required({parseValue}); + + const result = validate(' '); + expect(result.isValid).toBe(false); + }); + }); + + it('from a string to null', () => { + const parseValue = () => null; + const validate = required({parseValue}); + + const result = validate('Valid'); + expect(result.isValid).toBe(false); + }); + + it('from a string to undefined', () => { + const parseValue = () => {}; + const validate = required({parseValue}); + + const result = validate('Valid'); + expect(result.isValid).toBe(false); + }); + + it('from a string to a number', () => { + const parseValue = () => 0; + const validate = required({parseValue}); + + const result = validate(''); + expect(result.isValid).toBe(true); + }); + }); + }); }); diff --git a/test/strickland.spec.js b/test/strickland.spec.js index 5711302..879037f 100644 --- a/test/strickland.spec.js +++ b/test/strickland.spec.js @@ -116,7 +116,7 @@ describe('validate', () => { expect(isValid(result)).toBe(false); }); - it('returns an object with an isValid prop set to false if the object does not specify isValid', () => { + it('returns an object with isValid = false if the object does not specify isValid', () => { const ruleResult = { message: 'That is not valid' }; @@ -145,7 +145,7 @@ describe('validate', () => { }); }); - it('returns an object with an isValid prop set to true if the object has isValid set to a truthy value other than true', () => { + it('returns an object with isValid = true if the object has isValid set to a truthy value other than true', () => { const ruleResult = { message: 'That is not valid', isValid: 'Yep' @@ -169,7 +169,7 @@ describe('validate', () => { expect(isValid(result)).toBe(false); }); - it('returns an object with an isValid prop set to false if the object has isValid set to a falsy value other than false', () => { + it('returns an object with isValid = false if the object has isValid set to a falsy value other than false', () => { const ruleResult = { message: 'That is not valid', isValid: 0