functional-models 1.0.6 → 1.0.10

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -10,29 +10,36 @@ This library empowers the creation of pure JavaScript function based models that
10
10
  ## Example Usage
11
11
 
12
12
  const {
13
- smartObject,
14
- property,
15
- typed,
13
+ field,
14
+ constantValueField,
15
+ textField,
16
+ dateField,
17
+ integerField,
16
18
  uniqueId,
19
+ createModel,
20
+ validation,
17
21
  }= require('functional-models')
18
22
 
19
- const Truck = ({ id, make, model, color, year }) => smartObject([
20
- uniqueId(id),
21
- typed('truck'),
22
- property('make', make),
23
- property('model', model),
24
- property('color', color),
25
- property('year', year),
26
- ])
23
+ const Truck = createModel({
24
+ type: constantValueField('truck'),
25
+ id: uniqueId({required: true}),
26
+ make: textField({ maxLength: 20, minLength: 3, required: true}),
27
+ model: textField({ maxLength: 20, minLength: 3, required: true}),
28
+ color: textField({ maxLength: 10, minLength: 3, validators: [
29
+ validation.meetsRegex(/Red/),
30
+ ]}),
31
+ year: integerField({ maxValue: 2500, minValue: 1900}),
32
+ lastModified: dateField({ autoNow: true}),
33
+ })
27
34
 
28
35
 
29
36
  const myTruck = Truck({ make: 'Ford', model: 'F-150', color: 'White', year: 2013})
30
37
 
31
- console.log(myTruck.getId()) // a random uuid
32
- console.log(myTruck.getMake()) // 'Ford'
33
- console.log(myTruck.getModel()) // 'F-150'
34
- console.log(myTruck.getColor()) // 'White'
35
- console.log(myTruck.getYear()) // 2013
38
+ console.log(await myTruck.getId()) // a random uuid
39
+ console.log(await myTruck.getMake()) // 'Ford'
40
+ console.log(await myTruck.getModel()) // 'F-150'
41
+ console.log(await myTruck.getColor()) // 'White'
42
+ console.log(await myTruck.getYear()) // 2013
36
43
 
37
44
  const asJson = await myTruck.functions.toJson()
38
45
  console.log(asJson)
@@ -47,8 +54,19 @@ This library empowers the creation of pure JavaScript function based models that
47
54
  */
48
55
 
49
56
  const sameTruck = Truck(asJson)
50
- console.log(sameTruck.getId()) // same uuid as above
51
- console.log(sameTruck.getMake()) // 'Ford'
52
- console.log(sameTruck.getModel()) // 'F-150'
53
- console.log(sameTruck.getColor()) // 'White'
54
- console.log(sameTruck.getYear()) // 2013
57
+ console.log(await sameTruck.getId()) // same uuid as above
58
+ console.log(await sameTruck.getMake()) // 'Ford'
59
+ console.log(await sameTruck.getModel()) // 'F-150'
60
+ console.log(await sameTruck.getColor()) // 'White'
61
+ console.log(await sameTruck.getYear()) // 2013
62
+
63
+ // Validation
64
+ const errors = await sameTruck.functions.validate.model() // {}
65
+
66
+ const newTruck = Truck({ make: 'Ford', model: 'F-150', color: 'White', year: 20130})
67
+ const errors2 = await newTruck.functions.validate.model()
68
+ console.log(errors2)
69
+ // {"year": 'Value is too long'}
70
+
71
+
72
+
@@ -36,3 +36,17 @@ Feature: Array Fields
36
36
  Then the getArrayField field is called on the model
37
37
  Then functions.validate is called
38
38
  Then an array of 0 errors is shown
39
+
40
+ Scenario: A model that uses the arrayField with the choice validator should pass validation with no errors.
41
+ Given ArrayModel4 model is used
42
+ When ArrayModelData5 data is inserted
43
+ Then the getArrayField field is called on the model
44
+ Then functions.validate is called
45
+ Then an array of 0 errors is shown
46
+
47
+ Scenario: A model that uses the arrayField with the choice validator should fail validation when one value is outside the choices
48
+ Given ArrayModel4 model is used
49
+ When ArrayModelData6 data is inserted
50
+ Then the getArrayField field is called on the model
51
+ Then functions.validate is called
52
+ Then an array of 1 errors is shown
@@ -4,23 +4,29 @@ const { Given, When, Then } = require('@cucumber/cucumber')
4
4
  const { createModel, field, arrayField, validation } = require('../../index')
5
5
 
6
6
  const MODEL_DEFINITIONS = {
7
- TestModel1: createModel({
7
+ TestModel1: createModel("TestModel1", {
8
8
  name: field({ required: true }),
9
9
  type: field({ required: true, isString: true }),
10
10
  flag: field({ required: true, isNumber: true }),
11
11
  }),
12
- ArrayModel1: createModel({
12
+ ArrayModel1: createModel("ArrayModel1", {
13
13
  arrayField: field({
14
14
  isArray: true,
15
15
  validators: [validation.arrayType(validation.TYPE_PRIMATIVES.integer)],
16
16
  }),
17
17
  }),
18
- ArrayModel2: createModel({
18
+ ArrayModel2: createModel("ArrayModel2", {
19
19
  arrayField: field({ isArray: true }),
20
20
  }),
21
- ArrayModel3: createModel({
21
+ ArrayModel3: createModel("ArrayModel3", {
22
22
  arrayField: arrayField({}),
23
23
  }),
24
+ ArrayModel4: createModel("ArrayModel4", {
25
+ arrayField: arrayField({
26
+ choices: [4, 5, 6],
27
+ validators: [validation.arrayType(validation.TYPE_PRIMATIVES.integer)],
28
+ }),
29
+ }),
24
30
  }
25
31
 
26
32
  const MODEL_INPUT_VALUES = {
@@ -46,6 +52,12 @@ const MODEL_INPUT_VALUES = {
46
52
  ArrayModelData4: {
47
53
  arrayField: ['a-string', 1, {}, true],
48
54
  },
55
+ ArrayModelData5: {
56
+ arrayField: [4, 5, 5, 5, 6],
57
+ },
58
+ ArrayModelData6: {
59
+ arrayField: [4, 5, 5, 5, 6, 1],
60
+ },
49
61
  }
50
62
 
51
63
  const EXPECTED_FIELDS = {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "functional-models",
3
- "version": "1.0.6",
3
+ "version": "1.0.10",
4
4
  "description": "A library for creating JavaScript function based models.",
5
5
  "main": "index.js",
6
6
  "scripts": {
@@ -29,6 +29,7 @@
29
29
  "nyc": {
30
30
  "all": true,
31
31
  "exclude": [
32
+ "coverage/",
32
33
  "features/stepDefinitions/*",
33
34
  "test/*"
34
35
  ]
package/src/fields.js CHANGED
@@ -1,8 +1,28 @@
1
1
  const identity = require('lodash/identity')
2
- const { createFieldValidator } = require('./validation')
2
+ const merge = require('lodash/merge')
3
+ const {
4
+ createFieldValidator,
5
+ emptyValidator,
6
+ maxTextLength,
7
+ minTextLength,
8
+ minNumber,
9
+ maxNumber,
10
+ isType,
11
+ meetsRegex,
12
+ } = require('./validation')
3
13
  const { createUuid } = require('./utils')
4
14
  const { lazyValue } = require('./lazy')
5
15
 
16
+ const EMAIL_REGEX =
17
+ /[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
18
+
19
+ const _getValidatorFromConfigElseEmpty = (config, key, validatorGetter) => {
20
+ if (key in config) {
21
+ return validatorGetter(config[key])
22
+ }
23
+ return emptyValidator
24
+ }
25
+
6
26
  const field = (config = {}) => {
7
27
  const value = config.value || undefined
8
28
  const defaultValue = config.defaultValue || undefined
@@ -33,9 +53,11 @@ const field = (config = {}) => {
33
53
  }
34
54
  },
35
55
  getValidator: valueGetter => {
36
- return async () => {
37
- return createFieldValidator(config)(await valueGetter())
56
+ const validator = createFieldValidator(config)
57
+ const _fieldValidatorWrapper = async () => {
58
+ return validator(await valueGetter())
38
59
  }
60
+ return _fieldValidatorWrapper
39
61
  },
40
62
  }
41
63
  }
@@ -81,7 +103,7 @@ const referenceField = config => {
81
103
  ...objToUse,
82
104
  functions: {
83
105
  ...(objToUse.functions ? objToUse.functions : {}),
84
- toJson: _getId,
106
+ toObj: _getId,
85
107
  },
86
108
  }
87
109
  }
@@ -105,10 +127,82 @@ const arrayField = (config = {}) =>
105
127
  isArray: true,
106
128
  })
107
129
 
130
+ const objectField = (config = {}) =>
131
+ field(
132
+ merge(config, {
133
+ validators: [isType('object')],
134
+ })
135
+ )
136
+
137
+ const textField = (config = {}) =>
138
+ field(
139
+ merge(config, {
140
+ isString: true,
141
+ validators: [
142
+ _getValidatorFromConfigElseEmpty(config, 'maxLength', value =>
143
+ maxTextLength(value)
144
+ ),
145
+ _getValidatorFromConfigElseEmpty(config, 'minLength', value =>
146
+ minTextLength(value)
147
+ ),
148
+ ],
149
+ })
150
+ )
151
+
152
+ const integerField = (config = {}) =>
153
+ field(
154
+ merge(config, {
155
+ isInteger: true,
156
+ validators: [
157
+ _getValidatorFromConfigElseEmpty(config, 'minValue', value =>
158
+ minNumber(value)
159
+ ),
160
+ _getValidatorFromConfigElseEmpty(config, 'maxValue', value =>
161
+ maxNumber(value)
162
+ ),
163
+ ],
164
+ })
165
+ )
166
+
167
+ const numberField = (config = {}) =>
168
+ field(
169
+ merge(config, {
170
+ isNumber: true,
171
+ validators: [
172
+ _getValidatorFromConfigElseEmpty(config, 'minValue', value =>
173
+ minNumber(value)
174
+ ),
175
+ _getValidatorFromConfigElseEmpty(config, 'maxValue', value =>
176
+ maxNumber(value)
177
+ ),
178
+ ],
179
+ })
180
+ )
181
+
182
+ const constantValueField = (value, config = {}) =>
183
+ textField(
184
+ merge(config, {
185
+ value,
186
+ })
187
+ )
188
+
189
+ const emailField = (config = {}) =>
190
+ textField(
191
+ merge(config, {
192
+ validators: [meetsRegex(EMAIL_REGEX)],
193
+ })
194
+ )
195
+
108
196
  module.exports = {
109
197
  field,
110
198
  uniqueId,
111
199
  dateField,
112
200
  arrayField,
113
201
  referenceField,
202
+ integerField,
203
+ textField,
204
+ constantValueField,
205
+ numberField,
206
+ objectField,
207
+ emailField,
114
208
  }
package/src/models.js CHANGED
@@ -1,29 +1,36 @@
1
1
  const merge = require('lodash/merge')
2
- const get = require('lodash/get')
3
- const { toJson } = require('./serialization')
2
+ const pickBy = require('lodash/pickBy')
3
+ const { toObj } = require('./serialization')
4
4
  const { createPropertyTitle } = require('./utils')
5
5
  const { createModelValidator } = require('./validation')
6
6
 
7
- const SYSTEM_KEYS = ['meta', 'functions']
8
-
7
+ const MODEL_DEF_KEYS = ['meta', 'functions']
9
8
  const PROTECTED_KEYS = ['model']
10
9
 
11
- const createModel = keyToField => {
10
+ const createModel = (modelName, keyToField) => {
12
11
  PROTECTED_KEYS.forEach(key => {
13
12
  if (key in keyToField) {
14
13
  throw new Error(`Cannot use ${key}. This is a protected value.`)
15
14
  }
16
15
  })
17
- const systemProperties = SYSTEM_KEYS.reduce((acc, key) => {
18
- const value = get(keyToField, key, {})
19
- return { ...acc, [key]: value }
16
+ const fieldProperties = Object.entries(keyToField).filter(
17
+ ([key, _]) => !(key in MODEL_DEF_KEYS)
18
+ )
19
+ const fields = fieldProperties.reduce((acc, [key, field]) => {
20
+ return { ...acc, [key]: field }
20
21
  }, {})
21
- const nonSystemProperties = Object.entries(keyToField).filter(
22
- ([key, _]) => !(key in SYSTEM_KEYS)
22
+ const modelDefProperties = merge(
23
+ pickBy(keyToField, (value, key) => MODEL_DEF_KEYS.includes(key)),
24
+ {
25
+ meta: {
26
+ fields,
27
+ modelName,
28
+ },
29
+ }
23
30
  )
24
31
 
25
- return instanceValues => {
26
- const loadedInternals = nonSystemProperties.reduce((acc, [key, field]) => {
32
+ return (instanceValues = {}) => {
33
+ const loadedInternals = fieldProperties.reduce((acc, [key, field]) => {
27
34
  const fieldGetter = field.createGetter(instanceValues[key])
28
35
  const fieldValidator = field.getValidator(fieldGetter)
29
36
  const getFieldKey = createPropertyTitle(key)
@@ -37,16 +44,15 @@ const createModel = keyToField => {
37
44
  }
38
45
  return merge(acc, fleshedOutField)
39
46
  }, {})
40
- const allUserData = merge(systemProperties, loadedInternals)
41
- const internalFunctions = {
47
+ const frameworkProperties = {
42
48
  functions: {
43
- toJson: toJson(loadedInternals),
49
+ toObj: toObj(loadedInternals),
44
50
  validate: {
45
51
  model: createModelValidator(loadedInternals),
46
52
  },
47
53
  },
48
54
  }
49
- return merge(allUserData, internalFunctions)
55
+ return merge(loadedInternals, modelDefProperties, frameworkProperties)
50
56
  }
51
57
  }
52
58
 
@@ -12,8 +12,8 @@ const _getValue = async value => {
12
12
  return _getValue(await value())
13
13
  }
14
14
  // Nested Json
15
- if (type === 'object' && value.functions && value.functions.toJson) {
16
- return _getValue(await value.functions.toJson())
15
+ if (type === 'object' && value.functions && value.functions.toObj) {
16
+ return _getValue(await value.functions.toObj())
17
17
  }
18
18
  // Dates
19
19
  if (type === 'object' && value.toISOString) {
@@ -30,7 +30,7 @@ const _shouldIgnoreKey = key => {
30
30
  return IGNORABLE_KEYS.includes(key)
31
31
  }
32
32
 
33
- const toJson = keyToFunc => async () => {
33
+ const toObj = keyToFunc => async () => {
34
34
  return Object.entries(keyToFunc).reduce(async (acc, [key, value]) => {
35
35
  const realAcc = await acc
36
36
  if (_shouldIgnoreKey(key)) {
@@ -43,5 +43,5 @@ const toJson = keyToFunc => async () => {
43
43
  }
44
44
 
45
45
  module.exports = {
46
- toJson,
46
+ toObj,
47
47
  }
package/src/utils.js CHANGED
@@ -6,7 +6,7 @@ const toTitleCase = string => {
6
6
  return `${string.slice(0, 1).toUpperCase()}${string.slice(1)}`
7
7
  }
8
8
 
9
- const createPropertyTitle = key => {
9
+ const createFieldTitle = key => {
10
10
  const goodName = toTitleCase(key)
11
11
  return `get${goodName}`
12
12
  }
@@ -37,6 +37,6 @@ const loweredTitleCase = string => {
37
37
  module.exports = {
38
38
  createUuid,
39
39
  loweredTitleCase,
40
- createPropertyTitle,
40
+ createPropertyTitle: createFieldTitle,
41
41
  toTitleCase,
42
42
  }
package/src/validation.js CHANGED
@@ -28,13 +28,7 @@ const isType = type => value => {
28
28
  return _typeOrError(type, `Must be a ${type}`)(value)
29
29
  }
30
30
  const isNumber = isType('number')
31
- const isInteger = _trueOrError(v => {
32
- const numberError = isNumber(v)
33
- if (numberError) {
34
- return false
35
- }
36
- return Number.isNaN(parseInt(v, 10)) === false
37
- }, 'Must be an integer')
31
+ const isInteger = _trueOrError(Number.isInteger, 'Must be an integer')
38
32
 
39
33
  const isBoolean = isType('boolean')
40
34
  const isString = isType('string')
@@ -69,8 +63,15 @@ const meetsRegex =
69
63
  }
70
64
 
71
65
  const choices = choiceArray => value => {
72
- if (choiceArray.includes(value) === false) {
73
- return 'Not a valid choice'
66
+ if (Array.isArray(value)) {
67
+ const bad = value.find(v => !choiceArray.includes(v))
68
+ if (bad) {
69
+ return `${bad} is not a valid choice`
70
+ }
71
+ } else {
72
+ if (choiceArray.includes(value) === false) {
73
+ return `${value} is not a valid choice`
74
+ }
74
75
  }
75
76
  return undefined
76
77
  }
@@ -129,16 +130,20 @@ const minTextLength = min => value => {
129
130
  return undefined
130
131
  }
131
132
 
132
- const aggregateValidator = methodOrMethods => async value => {
133
+ const aggregateValidator = methodOrMethods => {
133
134
  const toDo = Array.isArray(methodOrMethods)
134
135
  ? methodOrMethods
135
136
  : [methodOrMethods]
136
- const values = await Promise.all(
137
- toDo.map(method => {
138
- return method(value)
139
- })
140
- )
141
- return values.filter(x => x)
137
+
138
+ const _aggregativeValidator = async value => {
139
+ const values = await Promise.all(
140
+ toDo.map(method => {
141
+ return method(value)
142
+ })
143
+ )
144
+ return values.filter(x => x)
145
+ }
146
+ return _aggregativeValidator
142
147
  }
143
148
 
144
149
  const emptyValidator = () => []
@@ -153,6 +158,7 @@ const CONFIG_TO_VALIDATE_METHOD = {
153
158
  isNumber: _boolChoice(isNumber),
154
159
  isString: _boolChoice(isString),
155
160
  isArray: _boolChoice(isArray),
161
+ choices,
156
162
  }
157
163
 
158
164
  const createFieldValidator = config => {
@@ -164,24 +170,33 @@ const createFieldValidator = config => {
164
170
  ].filter(x => x)
165
171
  const validator =
166
172
  validators.length > 0 ? aggregateValidator(validators) : emptyValidator
167
- return async value => {
173
+ const _fieldValidator = async value => {
168
174
  const errors = await validator(value)
169
175
  return [...new Set(flatMap(errors))]
170
176
  }
171
- }
172
-
173
- const createModelValidator = fields => async () => {
174
- const keysAndFunctions = Object.entries(get(fields, 'functions.validate', {}))
175
- const data = await Promise.all(
176
- keysAndFunctions.map(async ([key, validator]) => {
177
- return [key, await validator()]
178
- })
179
- )
180
- return data
181
- .filter(([_, errors]) => Boolean(errors) && errors.length > 0)
182
- .reduce((acc, [key, errors]) => {
183
- return { ...acc, [key]: errors }
184
- }, {})
177
+ return _fieldValidator
178
+ }
179
+
180
+ const createModelValidator = fields => {
181
+ const _modelValidator = async () => {
182
+ const keysAndFunctions = Object.entries(
183
+ get(fields, 'functions.validate', {})
184
+ )
185
+ const data = await Promise.all(
186
+ keysAndFunctions.map(async ([key, validator]) => {
187
+ if (key === 'model') {
188
+ return [key, []]
189
+ }
190
+ return [key, await validator()]
191
+ })
192
+ )
193
+ return data
194
+ .filter(([_, errors]) => Boolean(errors) && errors.length > 0)
195
+ .reduce((acc, [key, errors]) => {
196
+ return { ...acc, [key]: errors }
197
+ }, {})
198
+ }
199
+ return _modelValidator
185
200
  }
186
201
 
187
202
  module.exports = {
@@ -5,10 +5,297 @@ const {
5
5
  dateField,
6
6
  referenceField,
7
7
  arrayField,
8
+ constantValueField,
9
+ objectField,
10
+ numberField,
11
+ textField,
12
+ integerField,
13
+ emailField,
8
14
  } = require('../../src/fields')
15
+ const { TYPE_PRIMATIVES, arrayType } = require('../../src/validation')
9
16
  const { createModel } = require('../../src/models')
10
17
 
11
18
  describe('/src/fields.js', () => {
19
+ describe('#emailField()', () => {
20
+ describe('#createGetter()', () => {
21
+ it('should be able to create without a config', () => {
22
+ assert.doesNotThrow(() => {
23
+ emailField()
24
+ })
25
+ })
26
+ it('should always have the value passed in', async () => {
27
+ const fieldInstance = emailField({})
28
+ const getter = fieldInstance.createGetter('testme@email.com')
29
+ const actual = await getter()
30
+ const expected = 'testme@email.com'
31
+ assert.deepEqual(actual, expected)
32
+ })
33
+ })
34
+ describe('#getValidator()', () => {
35
+ it('should return and validate successful with basic input', async () => {
36
+ const fieldInstance = emailField({})
37
+ const getter = fieldInstance.createGetter('testme@email.com')
38
+ const validator = fieldInstance.getValidator(getter)
39
+ const actual = await validator()
40
+ const expected = 0
41
+ assert.equal(actual.length, expected)
42
+ })
43
+ })
44
+ })
45
+ describe('#constantValueField()', () => {
46
+ describe('#createGetter()', () => {
47
+ it('should always have the value passed in', async () => {
48
+ const fieldInstance = constantValueField('constant')
49
+ const getter = fieldInstance.createGetter('changed')
50
+ const actual = await getter()
51
+ const expected = 'constant'
52
+ assert.deepEqual(actual, expected)
53
+ })
54
+ })
55
+ describe('#getValidator()', () => {
56
+ it('should return and validate successful with basic input', async () => {
57
+ const fieldInstance = constantValueField('constant')
58
+ const getter = fieldInstance.createGetter('changed')
59
+ const validator = fieldInstance.getValidator(getter)
60
+ const actual = await validator()
61
+ const expected = 0
62
+ assert.equal(actual.length, expected)
63
+ })
64
+ })
65
+ })
66
+ describe('#objectField()', () => {
67
+ describe('#createGetter()', () => {
68
+ it('should be able to create without a config', () => {
69
+ assert.doesNotThrow(() => {
70
+ objectField()
71
+ })
72
+ })
73
+ it('should be able to get the object passed in', async () => {
74
+ const fieldInstance = objectField({})
75
+ const getter = fieldInstance.createGetter({
76
+ my: 'object',
77
+ complex: { it: 'is' },
78
+ })
79
+ const actual = await getter()
80
+ const expected = { my: 'object', complex: { it: 'is' } }
81
+ assert.deepEqual(actual, expected)
82
+ })
83
+ })
84
+ describe('#getValidator()', () => {
85
+ it('should return and validate successful with basic input', async () => {
86
+ const fieldInstance = objectField({})
87
+ const getter = fieldInstance.createGetter({
88
+ my: 'object',
89
+ complex: { it: 'is' },
90
+ })
91
+ const validator = fieldInstance.getValidator(getter)
92
+ const actual = await validator()
93
+ const expected = 0
94
+ assert.equal(actual.length, expected)
95
+ })
96
+ })
97
+ })
98
+
99
+ describe('#numberField()', () => {
100
+ describe('#createGetter()', () => {
101
+ it('should be able to create without a config', () => {
102
+ assert.doesNotThrow(() => {
103
+ numberField()
104
+ })
105
+ })
106
+ it('should be able to get the number passed in', async () => {
107
+ const fieldInstance = numberField({})
108
+ const getter = fieldInstance.createGetter(5)
109
+ const actual = await getter()
110
+ const expected = 5
111
+ assert.equal(actual, expected)
112
+ })
113
+ it('should be able to get float passed in', async () => {
114
+ const fieldInstance = numberField({})
115
+ const getter = fieldInstance.createGetter(5.123)
116
+ const actual = await getter()
117
+ const expected = 5.123
118
+ assert.equal(actual, expected)
119
+ })
120
+ })
121
+ describe('#getValidator()', () => {
122
+ it('should return and validate successful with basic input', async () => {
123
+ const fieldInstance = numberField({})
124
+ const getter = fieldInstance.createGetter(5)
125
+ const validator = fieldInstance.getValidator(getter)
126
+ const actual = await validator()
127
+ const expected = 0
128
+ assert.equal(actual.length, expected)
129
+ })
130
+ it('should return and validate successful with a basic float', async () => {
131
+ const fieldInstance = numberField({})
132
+ const getter = fieldInstance.createGetter(5.123)
133
+ const validator = fieldInstance.getValidator(getter)
134
+ const actual = await validator()
135
+ const expected = 0
136
+ assert.equal(actual.length, expected)
137
+ })
138
+ it('should return with errors with a non integer input', async () => {
139
+ const fieldInstance = numberField({})
140
+ const getter = fieldInstance.createGetter('string')
141
+ const validator = fieldInstance.getValidator(getter)
142
+ const actual = await validator()
143
+ const expected = 1
144
+ assert.equal(actual.length, expected)
145
+ })
146
+ it('should return with errors with a value=5 and maxValue=3', async () => {
147
+ const fieldInstance = numberField({ maxValue: 3 })
148
+ const getter = fieldInstance.createGetter(5)
149
+ const validator = fieldInstance.getValidator(getter)
150
+ const actual = await validator()
151
+ const expected = 1
152
+ assert.equal(actual.length, expected)
153
+ })
154
+ it('should return with errors with a value=2 and minValue=3', async () => {
155
+ const fieldInstance = numberField({ minValue: 3 })
156
+ const getter = fieldInstance.createGetter(2)
157
+ const validator = fieldInstance.getValidator(getter)
158
+ const actual = await validator()
159
+ const expected = 1
160
+ assert.equal(actual.length, expected)
161
+ })
162
+ it('should return with no errors with a value=3 and minValue=3 and maxValue=3', async () => {
163
+ const fieldInstance = numberField({ minValue: 3, maxValue: 3 })
164
+ const getter = fieldInstance.createGetter(3)
165
+ const validator = fieldInstance.getValidator(getter)
166
+ const actual = await validator()
167
+ const expected = 0
168
+ assert.equal(actual.length, expected)
169
+ })
170
+ })
171
+ })
172
+
173
+ describe('#integerField()', () => {
174
+ describe('#createGetter()', () => {
175
+ it('should be able to create without a config', () => {
176
+ assert.doesNotThrow(() => {
177
+ integerField()
178
+ })
179
+ })
180
+ it('should be able to get the number passed in', async () => {
181
+ const fieldInstance = integerField({})
182
+ const getter = fieldInstance.createGetter(5)
183
+ const actual = await getter()
184
+ const expected = 5
185
+ assert.equal(actual, expected)
186
+ })
187
+ })
188
+ describe('#getValidator()', () => {
189
+ it('should return and validate successful with basic input', async () => {
190
+ const fieldInstance = integerField({})
191
+ const getter = fieldInstance.createGetter(5)
192
+ const validator = fieldInstance.getValidator(getter)
193
+ const actual = await validator()
194
+ const expected = 0
195
+ assert.equal(actual.length, expected)
196
+ })
197
+ it('should return errors with a basic float', async () => {
198
+ const fieldInstance = integerField({})
199
+ const getter = fieldInstance.createGetter(5.123)
200
+ const validator = fieldInstance.getValidator(getter)
201
+ const actual = await validator()
202
+ const expected = 1
203
+ assert.equal(actual.length, expected)
204
+ })
205
+ it('should return with errors with a non integer input', async () => {
206
+ const fieldInstance = integerField({})
207
+ const getter = fieldInstance.createGetter('string')
208
+ const validator = fieldInstance.getValidator(getter)
209
+ const actual = await validator()
210
+ const expected = 1
211
+ assert.equal(actual.length, expected)
212
+ })
213
+ it('should return with errors with a value=5 and maxValue=3', async () => {
214
+ const fieldInstance = integerField({ maxValue: 3 })
215
+ const getter = fieldInstance.createGetter(5)
216
+ const validator = fieldInstance.getValidator(getter)
217
+ const actual = await validator()
218
+ const expected = 1
219
+ assert.equal(actual.length, expected)
220
+ })
221
+ it('should return with errors with a value=2 and minValue=3', async () => {
222
+ const fieldInstance = integerField({ minValue: 3 })
223
+ const getter = fieldInstance.createGetter(2)
224
+ const validator = fieldInstance.getValidator(getter)
225
+ const actual = await validator()
226
+ const expected = 1
227
+ assert.equal(actual.length, expected)
228
+ })
229
+ it('should return with no errors with a value=3 and minValue=3 and maxValue=3', async () => {
230
+ const fieldInstance = integerField({ minValue: 3, maxValue: 3 })
231
+ const getter = fieldInstance.createGetter(3)
232
+ const validator = fieldInstance.getValidator(getter)
233
+ const actual = await validator()
234
+ const expected = 0
235
+ assert.equal(actual.length, expected)
236
+ })
237
+ })
238
+ })
239
+
240
+ describe('#textField()', () => {
241
+ describe('#createGetter()', () => {
242
+ it('should be able to create without a config', () => {
243
+ assert.doesNotThrow(() => {
244
+ textField()
245
+ })
246
+ })
247
+ it('should be able to get the value passed in', async () => {
248
+ const fieldInstance = textField({})
249
+ const getter = fieldInstance.createGetter('basic input')
250
+ const actual = await getter()
251
+ const expected = 'basic input'
252
+ assert.equal(actual, expected)
253
+ })
254
+ })
255
+ describe('#getValidator()', () => {
256
+ it('should return and validate successful with basic input', async () => {
257
+ const fieldInstance = textField({})
258
+ const getter = fieldInstance.createGetter('basic input')
259
+ const validator = fieldInstance.getValidator(getter)
260
+ const actual = await validator()
261
+ const expected = 0
262
+ assert.equal(actual.length, expected)
263
+ })
264
+ it('should return with errors with a value=5', async () => {
265
+ const fieldInstance = textField({})
266
+ const getter = fieldInstance.createGetter(5)
267
+ const validator = fieldInstance.getValidator(getter)
268
+ const actual = await validator()
269
+ const expected = 1
270
+ assert.equal(actual.length, expected)
271
+ })
272
+ it('should return with errors with a value="hello" and maxLength=3', async () => {
273
+ const fieldInstance = textField({ maxLength: 3 })
274
+ const getter = fieldInstance.createGetter('hello')
275
+ const validator = fieldInstance.getValidator(getter)
276
+ const actual = await validator()
277
+ const expected = 1
278
+ assert.equal(actual.length, expected)
279
+ })
280
+ it('should return with errors with a value="hello" and minLength=10', async () => {
281
+ const fieldInstance = textField({ minLength: 10 })
282
+ const getter = fieldInstance.createGetter('hello')
283
+ const validator = fieldInstance.getValidator(getter)
284
+ const actual = await validator()
285
+ const expected = 1
286
+ assert.equal(actual.length, expected)
287
+ })
288
+ it('should return with no errors with a value="hello" and minLength=5 and maxLength=5', async () => {
289
+ const fieldInstance = textField({ minLength: 5, maxLength: 5 })
290
+ const getter = fieldInstance.createGetter('hello')
291
+ const validator = fieldInstance.getValidator(getter)
292
+ const actual = await validator()
293
+ const expected = 0
294
+ assert.equal(actual.length, expected)
295
+ })
296
+ })
297
+ })
298
+
12
299
  describe('#arrayField()', () => {
13
300
  describe('#createGetter()', () => {
14
301
  it('should return an array passed in without issue', async () => {
@@ -49,6 +336,32 @@ describe('/src/fields.js', () => {
49
336
  const expected = []
50
337
  assert.deepEqual(actual, expected)
51
338
  })
339
+ it('should error an array passed in when it doesnt have the right types', async () => {
340
+ const theField = arrayField({
341
+ validators: [arrayType(TYPE_PRIMATIVES.integer)],
342
+ })
343
+ const getter = theField.createGetter([1, 'string', 3])
344
+ const validator = theField.getValidator(getter)
345
+ const actual = await validator()
346
+ const expected = 1
347
+ assert.deepEqual(actual.length, expected)
348
+ })
349
+ it('should validate an array with [4,4,5,5,6,6] when choices are [4,5,6]', async () => {
350
+ const theField = arrayField({ choices: [4, 5, 6] })
351
+ const getter = theField.createGetter([4, 4, 5, 5, 6, 6])
352
+ const validator = theField.getValidator(getter)
353
+ const actual = await validator()
354
+ const expected = []
355
+ assert.deepEqual(actual, expected)
356
+ })
357
+ it('should return errors when an array with [4,4,3,5,5,6,6] when choices are [4,5,6]', async () => {
358
+ const theField = arrayField({ choices: [4, 5, 6] })
359
+ const getter = theField.createGetter([4, 4, 3, 5, 5, 6, 6])
360
+ const validator = theField.getValidator(getter)
361
+ const actual = await validator()
362
+ const expected = 1
363
+ assert.equal(actual.length, expected)
364
+ })
52
365
  })
53
366
  })
54
367
  describe('#field()', () => {
@@ -168,7 +481,7 @@ describe('/src/fields.js', () => {
168
481
  assert.deepEqual(actual, expected)
169
482
  })
170
483
  it('should take the smartObject as a value', async () => {
171
- const proto = createModel({
484
+ const proto = createModel('name', {
172
485
  id: uniqueId({ value: 'obj-id' }),
173
486
  })
174
487
  const input = [proto({ id: 'obj-id' })]
@@ -177,23 +490,23 @@ describe('/src/fields.js', () => {
177
490
  const expected = 'obj-id'
178
491
  assert.deepEqual(actual, expected)
179
492
  })
180
- describe('#functions.toJson()', () => {
181
- it('should use the getId of the smartObject passed in when toJson is called', async () => {
182
- const proto = createModel({
493
+ describe('#functions.toObj()', () => {
494
+ it('should use the getId of the smartObject passed in when toObj is called', async () => {
495
+ const proto = createModel('name', {
183
496
  id: uniqueId({ value: 'obj-id' }),
184
497
  })
185
498
  const input = [proto({ id: 'obj-id' })]
186
499
  const instance = await referenceField({}).createGetter(...input)()
187
- const actual = await instance.functions.toJson()
500
+ const actual = await instance.functions.toObj()
188
501
  const expected = 'obj-id'
189
502
  assert.deepEqual(actual, expected)
190
503
  })
191
- it('should return "obj-id" when switch-a-roo fetcher is used and toJson is called', async () => {
504
+ it('should return "obj-id" when switch-a-roo fetcher is used and toObj is called', async () => {
192
505
  const input = ['obj-id']
193
506
  const instance = await referenceField({
194
507
  fetcher: () => ({ id: 'obj-id', prop: 'switch-a-roo' }),
195
508
  }).createGetter(...input)()
196
- const actual = await instance.functions.toJson()
509
+ const actual = await instance.functions.toObj()
197
510
  const expected = 'obj-id'
198
511
  assert.deepEqual(actual, expected)
199
512
  })
@@ -1,3 +1,4 @@
1
+ const _ = require('lodash')
1
2
  const assert = require('chai').assert
2
3
  const { createModel } = require('../../src/models')
3
4
  const { field } = require('../../src/fields')
@@ -5,16 +6,53 @@ const { field } = require('../../src/fields')
5
6
  describe('/src/models.js', () => {
6
7
  describe('#createModel()', () => {
7
8
  it('should return a function when called once with valid data', () => {
8
- const actual = createModel({})
9
+ const actual = createModel('name', {})
9
10
  const expected = 'function'
10
11
  assert.isFunction(actual)
11
12
  })
12
13
  describe('#()', () => {
14
+ it('should not throw an exception if nothing is passed into function', () => {
15
+ const input = {
16
+ myField: field({ required: true }),
17
+ }
18
+ const model = createModel('name', input)
19
+ assert.doesNotThrow(() => {
20
+ model()
21
+ })
22
+ })
23
+ it('should return an object that contains meta.fields.myField', () => {
24
+ const input = {
25
+ myField: field({ required: true }),
26
+ }
27
+ const model = createModel('name', input)
28
+ const instance = model({ myField: 'value' })
29
+ const actual = _.get(instance, 'meta.fields.myField')
30
+ assert.isOk(actual)
31
+ })
32
+ it('should return an object that contains meta.modelName===test-the-name', () => {
33
+ const input = {
34
+ myField: field({ required: true }),
35
+ }
36
+ const model = createModel('test-the-name', input)
37
+ const instance = model({ myField: 'value' })
38
+ const actual = _.get(instance, 'meta.modelName')
39
+ const expected = 'test-the-name'
40
+ assert.deepEqual(actual, expected)
41
+ })
42
+ it('should return an object that contains meta.fields.myField', () => {
43
+ const input = {
44
+ myField: field({ required: true }),
45
+ }
46
+ const model = createModel('name', input)
47
+ const instance = model({ myField: 'value' })
48
+ const actual = _.get(instance, 'meta.fields.myField')
49
+ assert.isOk(actual)
50
+ })
13
51
  it('should use the value passed in when field.defaultValue and field.value are not set', async () => {
14
52
  const input = {
15
53
  myField: field({ required: true }),
16
54
  }
17
- const model = createModel(input)
55
+ const model = createModel('name', input)
18
56
  const instance = model({ myField: 'passed-in' })
19
57
  const actual = await instance.getMyField()
20
58
  const expected = 'passed-in'
@@ -24,7 +62,7 @@ describe('/src/models.js', () => {
24
62
  const input = {
25
63
  myField: field({ value: 'value', defaultValue: 'default-value' }),
26
64
  }
27
- const model = createModel(input)
65
+ const model = createModel('name', input)
28
66
  const instance = model({ myField: 'passed-in' })
29
67
  const actual = await instance.getMyField()
30
68
  const expected = 'value'
@@ -34,7 +72,7 @@ describe('/src/models.js', () => {
34
72
  const input = {
35
73
  myField: field({ value: 'value' }),
36
74
  }
37
- const model = createModel(input)
75
+ const model = createModel('name', input)
38
76
  const instance = model({ myField: 'passed-in' })
39
77
  const actual = await instance.getMyField()
40
78
  const expected = 'value'
@@ -44,7 +82,7 @@ describe('/src/models.js', () => {
44
82
  const input = {
45
83
  myField: field({ defaultValue: 'defaultValue' }),
46
84
  }
47
- const model = createModel(input)
85
+ const model = createModel('name', input)
48
86
  const instance = model({})
49
87
  const actual = await instance.getMyField()
50
88
  const expected = 'defaultValue'
@@ -54,7 +92,7 @@ describe('/src/models.js', () => {
54
92
  const input = {
55
93
  myField: field({ defaultValue: 'defaultValue' }),
56
94
  }
57
- const model = createModel(input)
95
+ const model = createModel('name', input)
58
96
  const instance = model({ myField: null })
59
97
  const actual = await instance.getMyField()
60
98
  const expected = 'defaultValue'
@@ -65,9 +103,8 @@ describe('/src/models.js', () => {
65
103
  id: field({ required: true }),
66
104
  type: field(),
67
105
  }
68
- const model = createModel(input)
106
+ const model = createModel('name', input)
69
107
  const actual = model({ id: 'my-id', type: 'my-type' })
70
- console.log(actual)
71
108
  assert.isOk(actual.getId)
72
109
  assert.isOk(actual.getType)
73
110
  })
@@ -76,21 +113,20 @@ describe('/src/models.js', () => {
76
113
  id: field({ required: true }),
77
114
  type: field(),
78
115
  }
79
- const model = createModel(input)
116
+ const model = createModel('name', input)
80
117
  const instance = model({ type: 'my-type' })
81
118
  const actual = await instance.functions.validate.model()
82
119
  const expected = 1
83
- console.log(actual)
84
120
  assert.equal(Object.values(actual).length, expected)
85
121
  })
86
122
  })
87
123
  it('should return a function when called once with valid data', () => {
88
- const actual = createModel({})
124
+ const actual = createModel('name', {})
89
125
  assert.isFunction(actual)
90
126
  })
91
127
  it('should throw an exception if a key "model" is passed in', () => {
92
128
  assert.throws(() => {
93
- createModel({ model: 'weeee' })
129
+ createModel('name', { model: 'weeee' })
94
130
  })
95
131
  })
96
132
  })
@@ -1,10 +1,10 @@
1
1
  const assert = require('chai').assert
2
- const { toJson } = require('../../src/serialization')
2
+ const { toObj } = require('../../src/serialization')
3
3
 
4
4
  describe('/src/serialization.js', () => {
5
- describe('#toJson()', () => {
5
+ describe('#toObj()', () => {
6
6
  it('serialize a very basic input of key-value', async () => {
7
- const actual = await toJson({
7
+ const actual = await toObj({
8
8
  key: 'value',
9
9
  key2: 'value2',
10
10
  })()
@@ -15,7 +15,7 @@ describe('/src/serialization.js', () => {
15
15
  assert.deepEqual(actual, expected)
16
16
  })
17
17
  it('should ignore "meta" properties', async () => {
18
- const actual = await toJson({
18
+ const actual = await toObj({
19
19
  key: 'value',
20
20
  key2: 'value2',
21
21
  meta: {
@@ -29,7 +29,7 @@ describe('/src/serialization.js', () => {
29
29
  assert.deepEqual(actual, expected)
30
30
  })
31
31
  it('should ignore "functions" properties', async () => {
32
- const actual = await toJson({
32
+ const actual = await toObj({
33
33
  key: 'value',
34
34
  key2: 'value2',
35
35
  functions: {
@@ -44,13 +44,13 @@ describe('/src/serialization.js', () => {
44
44
  }
45
45
  assert.deepEqual(actual, expected)
46
46
  })
47
- it('should call "functions.toJson" on nested objects', async () => {
48
- const actual = await toJson({
47
+ it('should call "functions.toObj" on nested objects', async () => {
48
+ const actual = await toObj({
49
49
  key: 'value',
50
50
  key2: {
51
51
  complex: () => ({ func: 'func' }),
52
52
  functions: {
53
- toJson: () => ({ func: 'value' }),
53
+ toObj: () => ({ func: 'value' }),
54
54
  },
55
55
  },
56
56
  })()
@@ -62,12 +62,12 @@ describe('/src/serialization.js', () => {
62
62
  }
63
63
  assert.deepEqual(actual, expected)
64
64
  })
65
- it('should call "toJson" on very nested objects', async () => {
66
- const actual = await toJson({
65
+ it('should call "toObj" on very nested objects', async () => {
66
+ const actual = await toObj({
67
67
  key: 'value',
68
68
  key2: {
69
69
  functions: {
70
- toJson: () => ({ func: 'value' }),
70
+ toObj: () => ({ func: 'value' }),
71
71
  },
72
72
  },
73
73
  })()
@@ -80,7 +80,7 @@ describe('/src/serialization.js', () => {
80
80
  assert.deepEqual(actual, expected)
81
81
  })
82
82
  it('should set an undefined property to null', async () => {
83
- const actual = await toJson({
83
+ const actual = await toObj({
84
84
  key: 'value',
85
85
  key2: undefined,
86
86
  })()
@@ -91,7 +91,7 @@ describe('/src/serialization.js', () => {
91
91
  assert.deepEqual(actual, expected)
92
92
  })
93
93
  it('should get the value of a function', async () => {
94
- const actual = await toJson({
94
+ const actual = await toObj({
95
95
  key: 'value',
96
96
  key2: () => {
97
97
  return 'funcvalue'
@@ -104,7 +104,7 @@ describe('/src/serialization.js', () => {
104
104
  assert.deepEqual(actual, expected)
105
105
  })
106
106
  it('should change property getTheValue to "theValue"', async () => {
107
- const actual = await toJson({
107
+ const actual = await toObj({
108
108
  key: 'value',
109
109
  getTheValue: () => 'funcvalue',
110
110
  })()
@@ -115,7 +115,7 @@ describe('/src/serialization.js', () => {
115
115
  assert.deepEqual(actual, expected)
116
116
  })
117
117
  it('should return "2021-09-16T21:51:56.039Z" for the set date.', async () => {
118
- const actual = await toJson({
118
+ const actual = await toObj({
119
119
  myDate: new Date('2021-09-16T21:51:56.039Z'),
120
120
  })()
121
121
  const expected = {
@@ -258,6 +258,20 @@ describe('/src/validation.js', () => {
258
258
  sinon.assert.calledOnce(fields.functions.validate.id)
259
259
  sinon.assert.calledOnce(fields.functions.validate.type)
260
260
  })
261
+ it('should not run a validate.model function', async () => {
262
+ const fields = {
263
+ functions: {
264
+ validate: {
265
+ id: sinon.stub().returns(undefined),
266
+ type: sinon.stub().returns(undefined),
267
+ model: sinon.stub().returns(undefined),
268
+ },
269
+ },
270
+ }
271
+ const validator = createModelValidator(fields)
272
+ await validator()
273
+ sinon.assert.notCalled(fields.functions.validate.model)
274
+ })
261
275
  it('should combine results for both functions.validate for two objects that error', async () => {
262
276
  const fields = {
263
277
  functions: {