Last active
July 17, 2018 11:26
-
-
Save husa/42696ddd845486964dc7dc52971a08ac to your computer and use it in GitHub Desktop.
Formik validation
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
withFormik({ | |
// ... | |
validate: createValidator({ | |
title: ['required', rules.minLength(3), rules.maxLength(400), 'commonText'], | |
price: ['required', 'price', price => price === 1000 ? 'Price can not be 1000' : null] | |
}), | |
// ... | |
}) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
const isNotANumber = val => val && isNaN(Number(val)); | |
const isRequired = val => !val || (typeof val === 'string' && !val.trim()); | |
export const rules = { | |
required: { | |
predicate: val => isRequired(val), | |
message: 'The field is required.' | |
}, | |
number: { | |
predicate: val => isNotANumber(val), | |
message: 'Please, enter valid number' | |
}, | |
digits: { | |
predicate: val => val && !/^[0-9-]*$/.test(val), | |
message: 'Please, use digits only' | |
}, | |
alphanumeric: { | |
predicate: val => val && !/^[a-zA-Z0-9]*$/.test(val), | |
message: 'Please, use letters and digits only' | |
}, | |
lettersAndDigits: { | |
predicate: val => val && !/^[a-zA-Z0-9_.-]*$/.test(val), | |
message: 'Please, use letters and digits only' | |
}, | |
lettersDigitsAndSpaces: { | |
predicate: val => val && !/^[a-zA-Z0-9 _.-]*$/.test(val), | |
message: 'Please, use digits, numbers and spaces only' | |
}, | |
commonText: { | |
predicate: val => val && !/^[a-zA-Z0-9,:;*()$#@% _.-]*$/.test(val), | |
message: | |
'Please, use digits, numbers and spaces and following symbols (, : ; * () $ # @ %) only' | |
}, | |
price: { | |
predicate: val => | |
(val && !/^\d+(\.\d{1,2})?$/.test(val)) || | |
parseFloat(val) < 0.01 || | |
parseFloat(val) > 2000000, | |
message: | |
'Price is invalid. Please, enter a real number between 0.01 and 2 000 000 with 2 decimals after dot.\n' + | |
'Example: 555.05' | |
}, | |
minLength: len => val => | |
val && (val.length < len ? `Please, use minimum ${len} characters` : null), | |
maxLength: len => val => val && (val.length > len ? `Please, use up to ${len} characters.` : null) | |
}; | |
export const getMessage = (fieldRules, value, values) => { | |
for (const rule of fieldRules) { | |
if (typeof rule === 'string' && rules[rule]) { | |
if (rules[rule].predicate(value)) { | |
return rules[rule].message; | |
} | |
} | |
if (typeof rule === 'function') { | |
const msg = rule(value, values); | |
if (msg) return msg; | |
} | |
} | |
return null; | |
}; | |
const createValidator = (rulesMap = {}) => values => { | |
return Object.entries(rulesMap).reduce((acc, [name, fieldRules]) => { | |
const msg = getMessage(fieldRules, values[name], values); | |
if (msg) acc[name] = msg; | |
return acc; | |
}, {}); | |
}; | |
export default createValidator; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import createValidator, {rules} from '../validation'; | |
describe('Utils > validation', () => { | |
describe('createValidator', () => { | |
it('should always return function', () => { | |
expect(typeof createValidator()).toEqual('function'); | |
}); | |
describe('created validator', () => { | |
let validator; | |
it('should always return object', () => { | |
validator = createValidator(); | |
expect(validator()).toEqual({}); | |
validator = createValidator({}); | |
expect(validator({})).toEqual({}); | |
}); | |
it('should run default validation rules', () => { | |
const originalRequiredPredicate = rules.required.predicate; | |
const mockFn = jest.fn(); | |
rules.required.predicate = mockFn; | |
validator = createValidator({ | |
foo: ['required'] | |
}); | |
validator({ | |
foo: 'bar' | |
}); | |
expect(mockFn).toHaveBeenCalled(); | |
expect(mockFn).toHaveBeenCalledWith('bar'); | |
rules.required.predicate = originalRequiredPredicate; | |
}); | |
it('should run custom validation rules', () => { | |
const mockFn = jest.fn(); | |
validator = createValidator({ | |
foo: [mockFn] | |
}); | |
const rules = { | |
foo: 'bar' | |
}; | |
expect(validator(rules)); | |
expect(mockFn).toHaveBeenCalled(); | |
expect(mockFn).toHaveBeenCalledWith('bar', rules); | |
}); | |
it('should return first message found', () => { | |
const withMessage = jest.fn().mockImplementation(() => 'msg'); | |
const withOutMessage = jest.fn().mockImplementation(() => null); | |
validator = createValidator({ | |
foo: [withMessage, withOutMessage] | |
}); | |
expect( | |
validator({ | |
foo: 'bar' | |
}) | |
).toEqual({ | |
foo: 'msg' | |
}); | |
validator = createValidator({ | |
foo: [withOutMessage, withMessage] | |
}); | |
expect( | |
validator({ | |
foo: 'bar' | |
}) | |
).toEqual({ | |
foo: 'msg' | |
}); | |
}); | |
it('should return empty object if no message found', () => { | |
const mockFn = jest.fn(); | |
validator = createValidator({ | |
foo: [mockFn] | |
}); | |
expect( | |
validator({ | |
foo: 'bar' | |
}) | |
).toEqual({}); | |
}); | |
}); | |
}); | |
}); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment