From 284c5c02f5e5672a0b89275a6f98e8b333a43353 Mon Sep 17 00:00:00 2001 From: Michael Cornwell Date: Fri, 17 Sep 2021 15:52:12 -0400 Subject: [PATCH 1/3] Adds many more commonly used fields --- src/fields.js | 82 ++++++++++++- src/validation.js | 8 +- test/src/fields.test.js | 259 ++++++++++++++++++++++++++++++++++++++++ 3 files changed, 341 insertions(+), 8 deletions(-) diff --git a/src/fields.js b/src/fields.js index c4ec635..1a9f31a 100644 --- a/src/fields.js +++ b/src/fields.js @@ -1,8 +1,24 @@ const identity = require('lodash/identity') -const { createFieldValidator } = require('./validation') +const merge = require('lodash/merge') +const { + createFieldValidator, + emptyValidator, + maxTextLength, + minTextLength, + minNumber, + maxNumber, + isType, +} = require('./validation') const { createUuid } = require('./utils') const { lazyValue } = require('./lazy') +const _getValidatorFromConfigElseEmpty = (config, key, validatorGetter) => { + if (key in config) { + return validatorGetter(config[key]) + } + return emptyValidator +} + const field = (config = {}) => { const value = config.value || undefined const defaultValue = config.defaultValue || undefined @@ -105,10 +121,74 @@ const arrayField = (config = {}) => isArray: true, }) +const objectField = (config = {}) => + field( + merge(config, { + validators: [isType('object')], + }) + ) + +const textField = (config = {}) => + field( + merge(config, { + isString: true, + validators: [ + _getValidatorFromConfigElseEmpty(config, 'maxLength', value => + maxTextLength(value) + ), + _getValidatorFromConfigElseEmpty(config, 'minLength', value => + minTextLength(value) + ), + ], + }) + ) + +const integerField = (config = {}) => + field( + merge(config, { + isInteger: true, + validators: [ + _getValidatorFromConfigElseEmpty(config, 'minValue', value => + minNumber(value) + ), + _getValidatorFromConfigElseEmpty(config, 'maxValue', value => + maxNumber(value) + ), + ], + }) + ) + +const numberField = (config = {}) => + field( + merge(config, { + isNumber: true, + validators: [ + _getValidatorFromConfigElseEmpty(config, 'minValue', value => + minNumber(value) + ), + _getValidatorFromConfigElseEmpty(config, 'maxValue', value => + maxNumber(value) + ), + ], + }) + ) + +const constantValueField = (value, config = {}) => + textField( + merge(config, { + value, + }) + ) + module.exports = { field, uniqueId, dateField, arrayField, referenceField, + integerField, + textField, + constantValueField, + numberField, + objectField, } diff --git a/src/validation.js b/src/validation.js index 778dd62..4579a7b 100644 --- a/src/validation.js +++ b/src/validation.js @@ -28,13 +28,7 @@ const isType = type => value => { return _typeOrError(type, `Must be a ${type}`)(value) } const isNumber = isType('number') -const isInteger = _trueOrError(v => { - const numberError = isNumber(v) - if (numberError) { - return false - } - return Number.isNaN(parseInt(v, 10)) === false -}, 'Must be an integer') +const isInteger = _trueOrError(Number.isInteger, 'Must be an integer') const isBoolean = isType('boolean') const isString = isType('string') diff --git a/test/src/fields.test.js b/test/src/fields.test.js index ba8190c..0b5a2be 100644 --- a/test/src/fields.test.js +++ b/test/src/fields.test.js @@ -5,11 +5,270 @@ const { dateField, referenceField, arrayField, + constantValueField, + objectField, + numberField, + textField, + integerField, } = require('../../src/fields') const { TYPE_PRIMATIVES, arrayType } = require('../../src/validation') const { createModel } = require('../../src/models') describe('/src/fields.js', () => { + describe('#constantValueField()', () => { + describe('#createGetter()', () => { + it('should always have the value passed in', async () => { + const fieldInstance = constantValueField('constant') + const getter = fieldInstance.createGetter('changed') + const actual = await getter() + const expected = 'constant' + assert.deepEqual(actual, expected) + }) + }) + describe('#getValidator()', () => { + it('should return and validate successful with basic input', async () => { + const fieldInstance = constantValueField('constant') + const getter = fieldInstance.createGetter('changed') + const validator = fieldInstance.getValidator(getter) + const actual = await validator() + const expected = 0 + assert.equal(actual.length, expected) + }) + }) + }) + describe('#objectField()', () => { + describe('#createGetter()', () => { + it('should be able to create without a config', () => { + assert.doesNotThrow(() => { + objectField() + }) + }) + it('should be able to get the object passed in', async () => { + const fieldInstance = objectField({}) + const getter = fieldInstance.createGetter({ + my: 'object', + complex: { it: 'is' }, + }) + const actual = await getter() + const expected = { my: 'object', complex: { it: 'is' } } + assert.deepEqual(actual, expected) + }) + }) + describe('#getValidator()', () => { + it('should return and validate successful with basic input', async () => { + const fieldInstance = objectField({}) + const getter = fieldInstance.createGetter({ + my: 'object', + complex: { it: 'is' }, + }) + const validator = fieldInstance.getValidator(getter) + const actual = await validator() + const expected = 0 + assert.equal(actual.length, expected) + }) + }) + }) + + describe('#numberField()', () => { + describe('#createGetter()', () => { + it('should be able to create without a config', () => { + assert.doesNotThrow(() => { + numberField() + }) + }) + it('should be able to get the number passed in', async () => { + const fieldInstance = numberField({}) + const getter = fieldInstance.createGetter(5) + const actual = await getter() + const expected = 5 + assert.equal(actual, expected) + }) + it('should be able to get float passed in', async () => { + const fieldInstance = numberField({}) + const getter = fieldInstance.createGetter(5.123) + const actual = await getter() + const expected = 5.123 + assert.equal(actual, expected) + }) + }) + describe('#getValidator()', () => { + it('should return and validate successful with basic input', async () => { + const fieldInstance = numberField({}) + const getter = fieldInstance.createGetter(5) + const validator = fieldInstance.getValidator(getter) + const actual = await validator() + const expected = 0 + assert.equal(actual.length, expected) + }) + it('should return and validate successful with a basic float', async () => { + const fieldInstance = numberField({}) + const getter = fieldInstance.createGetter(5.123) + const validator = fieldInstance.getValidator(getter) + const actual = await validator() + const expected = 0 + assert.equal(actual.length, expected) + }) + it('should return with errors with a non integer input', async () => { + const fieldInstance = numberField({}) + const getter = fieldInstance.createGetter('string') + const validator = fieldInstance.getValidator(getter) + const actual = await validator() + const expected = 1 + assert.equal(actual.length, expected) + }) + it('should return with errors with a value=5 and maxValue=3', async () => { + const fieldInstance = numberField({ maxValue: 3 }) + const getter = fieldInstance.createGetter(5) + const validator = fieldInstance.getValidator(getter) + const actual = await validator() + const expected = 1 + assert.equal(actual.length, expected) + }) + it('should return with errors with a value=2 and minValue=3', async () => { + const fieldInstance = numberField({ minValue: 3 }) + const getter = fieldInstance.createGetter(2) + const validator = fieldInstance.getValidator(getter) + const actual = await validator() + const expected = 1 + assert.equal(actual.length, expected) + }) + it('should return with no errors with a value=3 and minValue=3 and maxValue=3', async () => { + const fieldInstance = numberField({ minValue: 3, maxValue: 3 }) + const getter = fieldInstance.createGetter(3) + const validator = fieldInstance.getValidator(getter) + const actual = await validator() + const expected = 0 + assert.equal(actual.length, expected) + }) + }) + }) + + describe('#integerField()', () => { + describe('#createGetter()', () => { + it('should be able to create without a config', () => { + assert.doesNotThrow(() => { + integerField() + }) + }) + it('should be able to get the number passed in', async () => { + const fieldInstance = integerField({}) + const getter = fieldInstance.createGetter(5) + const actual = await getter() + const expected = 5 + assert.equal(actual, expected) + }) + }) + describe('#getValidator()', () => { + it('should return and validate successful with basic input', async () => { + const fieldInstance = integerField({}) + const getter = fieldInstance.createGetter(5) + const validator = fieldInstance.getValidator(getter) + const actual = await validator() + const expected = 0 + assert.equal(actual.length, expected) + }) + it('should return errors with a basic float', async () => { + const fieldInstance = integerField({}) + const getter = fieldInstance.createGetter(5.123) + const validator = fieldInstance.getValidator(getter) + const actual = await validator() + const expected = 1 + assert.equal(actual.length, expected) + }) + it('should return with errors with a non integer input', async () => { + const fieldInstance = integerField({}) + const getter = fieldInstance.createGetter('string') + const validator = fieldInstance.getValidator(getter) + const actual = await validator() + const expected = 1 + assert.equal(actual.length, expected) + }) + it('should return with errors with a value=5 and maxValue=3', async () => { + const fieldInstance = integerField({ maxValue: 3 }) + const getter = fieldInstance.createGetter(5) + const validator = fieldInstance.getValidator(getter) + const actual = await validator() + const expected = 1 + assert.equal(actual.length, expected) + }) + it('should return with errors with a value=2 and minValue=3', async () => { + const fieldInstance = integerField({ minValue: 3 }) + const getter = fieldInstance.createGetter(2) + const validator = fieldInstance.getValidator(getter) + const actual = await validator() + const expected = 1 + assert.equal(actual.length, expected) + }) + it('should return with no errors with a value=3 and minValue=3 and maxValue=3', async () => { + const fieldInstance = integerField({ minValue: 3, maxValue: 3 }) + const getter = fieldInstance.createGetter(3) + const validator = fieldInstance.getValidator(getter) + const actual = await validator() + const expected = 0 + assert.equal(actual.length, expected) + }) + }) + }) + + describe('#textField()', () => { + describe('#createGetter()', () => { + it('should be able to create without a config', () => { + assert.doesNotThrow(() => { + textField() + }) + }) + it('should be able to get the value passed in', async () => { + const fieldInstance = textField({}) + const getter = fieldInstance.createGetter('basic input') + const actual = await getter() + const expected = 'basic input' + assert.equal(actual, expected) + }) + }) + describe('#getValidator()', () => { + it('should return and validate successful with basic input', async () => { + const fieldInstance = textField({}) + const getter = fieldInstance.createGetter('basic input') + const validator = fieldInstance.getValidator(getter) + const actual = await validator() + const expected = 0 + assert.equal(actual.length, expected) + }) + it('should return with errors with a value=5', async () => { + const fieldInstance = textField({}) + const getter = fieldInstance.createGetter(5) + const validator = fieldInstance.getValidator(getter) + const actual = await validator() + const expected = 1 + assert.equal(actual.length, expected) + }) + it('should return with errors with a value="hello" and maxLength=3', async () => { + const fieldInstance = textField({ maxLength: 3 }) + const getter = fieldInstance.createGetter('hello') + const validator = fieldInstance.getValidator(getter) + const actual = await validator() + const expected = 1 + assert.equal(actual.length, expected) + }) + it('should return with errors with a value="hello" and minLength=10', async () => { + const fieldInstance = textField({ minLength: 10 }) + const getter = fieldInstance.createGetter('hello') + const validator = fieldInstance.getValidator(getter) + const actual = await validator() + const expected = 1 + assert.equal(actual.length, expected) + }) + it('should return with no errors with a value="hello" and minLength=5 and maxLength=5', async () => { + const fieldInstance = textField({ minLength: 5, maxLength: 5 }) + const getter = fieldInstance.createGetter('hello') + const validator = fieldInstance.getValidator(getter) + const actual = await validator() + const expected = 0 + assert.equal(actual.length, expected) + }) + }) + }) + describe('#arrayField()', () => { describe('#createGetter()', () => { it('should return an array passed in without issue', async () => { From 165020d429102680db9d2bcfa1475112958d4b98 Mon Sep 17 00:00:00 2001 From: Michael Cornwell Date: Fri, 17 Sep 2021 15:59:57 -0400 Subject: [PATCH 2/3] Added email. --- src/fields.js | 12 ++++++++++++ test/src/fields.test.js | 27 +++++++++++++++++++++++++++ test/src/models.test.js | 2 -- 3 files changed, 39 insertions(+), 2 deletions(-) diff --git a/src/fields.js b/src/fields.js index 1a9f31a..f29e60d 100644 --- a/src/fields.js +++ b/src/fields.js @@ -8,10 +8,14 @@ const { minNumber, maxNumber, isType, + meetsRegex, } = require('./validation') const { createUuid } = require('./utils') const { lazyValue } = require('./lazy') +const EMAIL_REGEX = + /[a-z0-9!#$%&'*+/=?^_`{|}~-]+(?:\.[a-z0-9!#$%&'*+/=?^_`{|}~-]+)*@(?:[a-z0-9](?:[a-z0-9-]*[a-z0-9])?\.)+[a-z0-9](?:[a-z0-9-]*[a-z0-9])?/u + const _getValidatorFromConfigElseEmpty = (config, key, validatorGetter) => { if (key in config) { return validatorGetter(config[key]) @@ -180,6 +184,13 @@ const constantValueField = (value, config = {}) => }) ) +const emailField = (config = {}) => + textField( + merge(config, { + validators: [meetsRegex(EMAIL_REGEX)], + }) + ) + module.exports = { field, uniqueId, @@ -191,4 +202,5 @@ module.exports = { constantValueField, numberField, objectField, + emailField, } diff --git a/test/src/fields.test.js b/test/src/fields.test.js index 0b5a2be..9fb0d45 100644 --- a/test/src/fields.test.js +++ b/test/src/fields.test.js @@ -10,11 +10,38 @@ const { numberField, textField, integerField, + emailField, } = require('../../src/fields') const { TYPE_PRIMATIVES, arrayType } = require('../../src/validation') const { createModel } = require('../../src/models') describe('/src/fields.js', () => { + describe('#emailField()', () => { + describe('#createGetter()', () => { + it('should be able to create without a config', () => { + assert.doesNotThrow(() => { + emailField() + }) + }) + it('should always have the value passed in', async () => { + const fieldInstance = emailField({}) + const getter = fieldInstance.createGetter('testme@email.com') + const actual = await getter() + const expected = 'testme@email.com' + assert.deepEqual(actual, expected) + }) + }) + describe('#getValidator()', () => { + it('should return and validate successful with basic input', async () => { + const fieldInstance = emailField({}) + const getter = fieldInstance.createGetter('testme@email.com') + const validator = fieldInstance.getValidator(getter) + const actual = await validator() + const expected = 0 + assert.equal(actual.length, expected) + }) + }) + }) describe('#constantValueField()', () => { describe('#createGetter()', () => { it('should always have the value passed in', async () => { diff --git a/test/src/models.test.js b/test/src/models.test.js index e2ef08d..8ba9b5d 100644 --- a/test/src/models.test.js +++ b/test/src/models.test.js @@ -67,7 +67,6 @@ describe('/src/models.js', () => { } const model = createModel(input) const actual = model({ id: 'my-id', type: 'my-type' }) - console.log(actual) assert.isOk(actual.getId) assert.isOk(actual.getType) }) @@ -80,7 +79,6 @@ describe('/src/models.js', () => { const instance = model({ type: 'my-type' }) const actual = await instance.functions.validate.model() const expected = 1 - console.log(actual) assert.equal(Object.values(actual).length, expected) }) }) From e393112a3461413c52793bbe4785dd8173edd74d Mon Sep 17 00:00:00 2001 From: Michael Cornwell Date: Fri, 17 Sep 2021 16:00:12 -0400 Subject: [PATCH 3/3] Bump --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index ba62421..2556766 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "functional-models", - "version": "1.0.7", + "version": "1.0.8", "description": "A library for creating JavaScript function based models.", "main": "index.js", "scripts": {