functional-models 1.0.2 → 1.0.3

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/.eslintignore CHANGED
@@ -1,3 +1,4 @@
1
1
  dist/
2
2
  node_modules/
3
3
  test/
4
+ features/
@@ -0,0 +1,26 @@
1
+ name: Feature Tests
2
+
3
+ on:
4
+ push:
5
+ branches: [master]
6
+ pull_request:
7
+ branches: [master]
8
+
9
+ jobs:
10
+ build:
11
+ runs-on: ubuntu-latest
12
+
13
+ strategy:
14
+ matrix:
15
+ node-version: [12.x, 14.x, 15.x]
16
+
17
+ steps:
18
+ - uses: actions/checkout@v2
19
+ - name: Use Node.js ${{ matrix.node-version }}
20
+ uses: actions/setup-node@v2
21
+ with:
22
+ node-version: ${{ matrix.node-version }}
23
+ - name: Install dependencies
24
+ run: npm install
25
+ - name: Run Cucumber Tests
26
+ run: npm run feature-tests
@@ -1,14 +1,13 @@
1
- name: CI
1
+ name: Unit Tests
2
2
 
3
3
  on:
4
4
  push:
5
- branches: [ master ]
5
+ branches: [master]
6
6
  pull_request:
7
- branches: [ master ]
7
+ branches: [master]
8
8
 
9
9
  jobs:
10
10
  build:
11
-
12
11
  runs-on: ubuntu-latest
13
12
 
14
13
  strategy:
@@ -23,11 +22,11 @@ jobs:
23
22
  node-version: ${{ matrix.node-version }}
24
23
  - name: Install dependencies
25
24
  run: npm install
26
- - run: npm test
27
- - run: npm run coverage
25
+ - name: Run Unit Tests
26
+ run: npm test
27
+ - run: npm run coverage
28
28
 
29
29
  - name: Coveralls
30
30
  uses: coverallsapp/github-action@master
31
31
  with:
32
32
  github-token: ${{ secrets.GITHUB_TOKEN }}
33
-
package/.prettierignore CHANGED
@@ -1,4 +1,5 @@
1
1
  node_modules/
2
+ *.json
2
3
  dist/
3
4
  *.html
4
5
  build
package/README.md CHANGED
@@ -1,11 +1,12 @@
1
1
  # Functional Models
2
- ![CI](https://github.com/monolithst/functional-models/actions/workflows/ci.yml/badge.svg)
2
+
3
+ ![Unit Tests](https://github.com/monolithst/functional-models/actions/workflows/ut.yml/badge.svg?branch=master)
4
+ ![Feature Tests](https://github.com/monolithst/functional-models/actions/workflows/feature.yml/badge.svg?branch=master)
3
5
  [![Coverage Status](https://coveralls.io/repos/github/monolithst/functional-models/badge.svg?branch=master)](https://coveralls.io/github/monolithst/functional-models?branch=master)
4
6
 
5
- Love functional javascript but still like composing objects/models? This is the library for you.
7
+ Love functional javascript but still like composing objects/models? This is the library for you.
6
8
  This library empowers the creation of pure JavaScript function based models that can be used on a client, a web frontend, and/or a backend all the same time. Use this library to create readable, read-only, models.
7
9
 
8
-
9
10
  ## Example Usage
10
11
 
11
12
  const {
@@ -51,5 +52,3 @@ This library empowers the creation of pure JavaScript function based models that
51
52
  console.log(sameTruck.getModel()) // 'F-150'
52
53
  console.log(sameTruck.getColor()) // 'White'
53
54
  console.log(sameTruck.getYear()) // 2013
54
-
55
-
@@ -0,0 +1,56 @@
1
+ const assert = require('chai').assert
2
+ const flatMap = require('lodash/flatMap')
3
+ const { Given, When, Then } = require('@cucumber/cucumber')
4
+
5
+ const { smartObject, property, named, typed } = require('../../index')
6
+
7
+ const MODEL_DEFINITIONS = {
8
+ TestModel1: ({ name, type, flag }) =>
9
+ smartObject([
10
+ named({ required: true })(name),
11
+ typed({ required: true, isString: 'true' })(type),
12
+ property('flag', { required: true, isNumber: true })(flag),
13
+ ]),
14
+ }
15
+
16
+ const MODEL_INPUT_VALUES = {
17
+ TestModel1a: {
18
+ name: 'my-name',
19
+ type: 1,
20
+ flag: '1',
21
+ },
22
+ TestModel1b: {
23
+ name: 'my-name',
24
+ type: 'a-type',
25
+ flag: 1,
26
+ },
27
+ }
28
+
29
+ Given(
30
+ 'the {word} has been created, with {word} inputs provided',
31
+ function (modelDefinition, modelInputValues) {
32
+ const def = MODEL_DEFINITIONS[modelDefinition]
33
+ const input = MODEL_INPUT_VALUES[modelInputValues]
34
+ if (!def) {
35
+ throw new Error(`${modelDefinition} did not result in a definition`)
36
+ }
37
+ if (!input) {
38
+ throw new Error(`${modelInputValues} did not result in an input`)
39
+ }
40
+ this.instance = def(input)
41
+ }
42
+ )
43
+
44
+ When('functions.validate is called', function () {
45
+ return this.instance.functions.validate.object().then(x => {
46
+ this.errors = x
47
+ })
48
+ })
49
+
50
+ Then('an array of {int} errors is shown', function (errorCount) {
51
+ const errors = flatMap(Object.values(this.errors))
52
+ if (errors.length !== errorCount) {
53
+ console.error(this.errors)
54
+ }
55
+ assert.equal(errors.length, errorCount)
56
+ })
@@ -0,0 +1,12 @@
1
+ Feature: Validation
2
+
3
+ Scenario: Creating TestModel1 with required arguments but not having them.
4
+ Given the TestModel1 has been created, with TestModel1a inputs provided
5
+ When functions.validate is called
6
+ Then an array of 2 errors is shown
7
+
8
+ Scenario: Creating TestModel1 with required arguments and having them.
9
+ Given the TestModel1 has been created, with TestModel1b inputs provided
10
+ When functions.validate is called
11
+ Then an array of 0 errors is shown
12
+
package/package.json CHANGED
@@ -1,10 +1,11 @@
1
1
  {
2
2
  "name": "functional-models",
3
- "version": "1.0.2",
3
+ "version": "1.0.3",
4
4
  "description": "A library for creating JavaScript function based models.",
5
5
  "main": "index.js",
6
6
  "scripts": {
7
7
  "test": "nyc --all mocha --recursive ./test/**/*.test.js",
8
+ "feature-tests": "./node_modules/@cucumber/cucumber/bin/cucumber-js",
8
9
  "coverage": "nyc --all --reporter=lcov npm test"
9
10
  },
10
11
  "repository": {
@@ -26,11 +27,16 @@
26
27
  },
27
28
  "homepage": "https://github.com/monolithst/functional-models#readme",
28
29
  "nyc": {
29
- "all": true
30
+ "all": true,
31
+ "exclude": [
32
+ "features/stepDefinitions/*",
33
+ "test/*"
34
+ ]
30
35
  },
31
36
  "devDependencies": {
32
37
  "babel-eslint": "^10.1.0",
33
38
  "chai": "^4.3.0",
39
+ "cucumber": "^7.0.0-rc.0",
34
40
  "eslint": "^7.19.0",
35
41
  "eslint-config-prettier": "^7.2.0",
36
42
  "eslint-plugin-functional": "^3.2.1",
@@ -42,6 +48,7 @@
42
48
  },
43
49
  "dependencies": {
44
50
  "get-random-values": "^1.2.2",
45
- "lazy-property": "^1.0.0"
51
+ "lazy-property": "^1.0.0",
52
+ "lodash": "^4.17.21"
46
53
  }
47
54
  }
package/src/dates.js CHANGED
@@ -2,7 +2,7 @@ const { property } = require('./properties')
2
2
 
3
3
  const _autonowDate = ({ key }) => (date = null, ...args) => {
4
4
  const theDate = date ? date : new Date()
5
- return property(key, theDate, ...args)
5
+ return property(key, ...args)(theDate)
6
6
  }
7
7
  const lastModifiedProperty = _autonowDate({ key: 'lastModified' })
8
8
  const lastUpdatedProperty = _autonowDate({ key: 'lastUpdated' })
package/src/objects.js CHANGED
@@ -1,23 +1,49 @@
1
+ const merge = require('lodash/merge')
2
+ const get = require('lodash/get')
1
3
  const { toJson } = require('./serialization')
2
4
 
5
+ const findValidateFunctions = smartObject => {
6
+ return Object.entries(get(smartObject, 'functions.validate', {}))
7
+ }
8
+
3
9
  const smartObject = (
4
10
  internals,
5
11
  { metaProperties = {}, functions = {} } = {}
6
12
  ) => {
7
13
  const realInternals = Array.isArray(internals)
8
- ? internals.reduce((acc, obj) => ({ ...acc, ...obj }), {})
14
+ ? internals.reduce((acc, obj) => merge(acc, obj), {})
9
15
  : internals
10
16
 
11
- return {
12
- ...(metaProperties ? { meta: { ...metaProperties } } : {}),
13
- ...realInternals,
17
+ const passedInData = merge(
18
+ metaProperties ? { meta: { ...metaProperties } } : {},
19
+ realInternals,
20
+ { functions }
21
+ )
22
+ const internalFunctions = {
14
23
  functions: {
15
24
  ...functions,
16
25
  toJson: toJson(realInternals),
26
+ validate: {
27
+ object: async () => {
28
+ const keysAndfunctions = findValidateFunctions(realInternals)
29
+ const data = await Promise.all(
30
+ keysAndfunctions.map(async ([key, validator]) => {
31
+ return [key, await validator()]
32
+ })
33
+ )
34
+ return data
35
+ .filter(([_, errors]) => Boolean(errors) && errors.length > 0)
36
+ .reduce((acc, [key, errors]) => {
37
+ return { ...acc, [key]: errors }
38
+ }, {})
39
+ },
40
+ },
17
41
  },
18
42
  }
43
+ return merge(passedInData, internalFunctions)
19
44
  }
20
45
 
21
46
  module.exports = {
22
47
  smartObject,
48
+ findValidateFunctions,
23
49
  }
package/src/properties.js CHANGED
@@ -1,27 +1,19 @@
1
1
  const { createPropertyTitle, createUuid } = require('./utils')
2
+ const { createPropertyValidate } = require('./validation')
2
3
 
3
- const property = (key, arg) => {
4
- if (typeof key === 'object') {
5
- return Object.entries(key).reduce((acc, [keyName, realValue]) => {
6
- const propertyKey = createPropertyTitle(keyName)
7
- return {
8
- ...acc,
9
- [propertyKey]: () => realValue,
10
- }
11
- }, {})
12
- }
4
+ const property = (key, config = {}) => arg => {
13
5
  const method = typeof arg === 'function' ? arg : () => arg
14
6
  const propertyKey = createPropertyTitle(key)
15
7
  return {
16
8
  [propertyKey]: method,
9
+ ...createPropertyValidate(key, config)(arg),
17
10
  }
18
11
  }
19
12
 
20
- const named = name => property('Name', name)
21
- const typed = type => property('Type', type)
22
- const uniqueId = (id = null) => {
23
- return property('id', id || createUuid())
24
- }
13
+ const named = config => property('Name', config)
14
+ const typed = config => property('Type', config)
15
+ const uniqueId = config => (id = null) =>
16
+ property('id', config)(id || createUuid(), config)
25
17
 
26
18
  module.exports = {
27
19
  property,
package/src/utils.js CHANGED
@@ -55,4 +55,5 @@ module.exports = {
55
55
  loweredTitleCase,
56
56
  lazyValue,
57
57
  createPropertyTitle,
58
+ toTitleCase,
58
59
  }
@@ -0,0 +1,165 @@
1
+ const isEmpty = require('lodash/isEmpty')
2
+ const flatMap = require('lodash/flatMap')
3
+
4
+ const _trueOrError = (method, error) => value => {
5
+ if (method(value) === false) {
6
+ return error
7
+ }
8
+ return undefined
9
+ }
10
+
11
+ const _typeOrError = (type, errorMessage) => value => {
12
+ if (typeof value !== type) {
13
+ return errorMessage
14
+ }
15
+ return undefined
16
+ }
17
+
18
+ const isType = type => value => {
19
+ return _typeOrError(type, `Must be a ${type}`)(value)
20
+ }
21
+ const isNumber = isType('number')
22
+ const isInteger = _trueOrError(v => {
23
+ const numberError = isNumber(v)
24
+ if (numberError) {
25
+ return false
26
+ }
27
+ return Number.isNaN(parseInt(v, 10)) === false
28
+ }, 'Must be an integer')
29
+
30
+ const isBoolean = isType('boolean')
31
+ const isString = isType('string')
32
+
33
+ const meetsRegex = (
34
+ regex,
35
+ flags,
36
+ errorMessage = 'Format was invalid'
37
+ ) => value => {
38
+ const reg = new RegExp(regex, flags)
39
+ return _trueOrError(v => reg.test(v), errorMessage)(value)
40
+ }
41
+
42
+ const choices = choiceArray => value => {
43
+ if (choiceArray.includes(value) === false) {
44
+ return 'Not a valid choice'
45
+ }
46
+ return undefined
47
+ }
48
+
49
+ const isRequired = value => {
50
+ if (value === true || value === false) {
51
+ return undefined
52
+ }
53
+ if (isNumber(value) === undefined) {
54
+ return undefined
55
+ }
56
+ return isEmpty(value) ? 'A value is required' : undefined
57
+ }
58
+
59
+ const maxNumber = max => value => {
60
+ const numberError = isNumber(value)
61
+ if (numberError) {
62
+ return numberError
63
+ }
64
+ if (value > max) {
65
+ return `The maximum is ${max}`
66
+ }
67
+ return undefined
68
+ }
69
+
70
+ const minNumber = min => value => {
71
+ const numberError = isNumber(value)
72
+ if (numberError) {
73
+ return numberError
74
+ }
75
+ if (value < min) {
76
+ return `The minimum is ${min}`
77
+ }
78
+ return undefined
79
+ }
80
+
81
+ const maxTextLength = max => value => {
82
+ const stringError = isString(value)
83
+ if (stringError) {
84
+ return stringError
85
+ }
86
+ if (value.length > max) {
87
+ return `The maximum length is ${max}`
88
+ }
89
+ return undefined
90
+ }
91
+
92
+ const minTextLength = min => value => {
93
+ const stringError = isString(value)
94
+ if (stringError) {
95
+ return stringError
96
+ }
97
+ if (value.length < min) {
98
+ return `The minimum length is ${min}`
99
+ }
100
+ return undefined
101
+ }
102
+
103
+ const aggregateValidator = methodOrMethods => async value => {
104
+ const toDo = Array.isArray(methodOrMethods)
105
+ ? methodOrMethods
106
+ : [methodOrMethods]
107
+ const values = await Promise.all(
108
+ toDo.map(method => {
109
+ return method(value)
110
+ })
111
+ )
112
+ return values.filter(x => x)
113
+ }
114
+
115
+ const emptyValidator = () => []
116
+
117
+ const _boolChoice = method => value => {
118
+ return value ? method : undefined
119
+ }
120
+
121
+ const CONFIG_TO_VALIDATE_METHOD = {
122
+ required: _boolChoice(isRequired),
123
+ isInteger: _boolChoice(isInteger),
124
+ isNumber: _boolChoice(isNumber),
125
+ isString: _boolChoice(isString),
126
+ }
127
+
128
+ const createPropertyValidate = (key, config) => value => {
129
+ const validators = [
130
+ ...Object.entries(config).map(([key, value]) => {
131
+ return (CONFIG_TO_VALIDATE_METHOD[key] || (() => undefined))(value)
132
+ }),
133
+ ...(config.validators ? config.validators : []),
134
+ ].filter(x => x)
135
+ const validator =
136
+ validators.length > 0 ? aggregateValidator(validators) : emptyValidator
137
+ return {
138
+ functions: {
139
+ validate: {
140
+ [key]: async () => {
141
+ const errors = await validator(value)
142
+ return flatMap(errors)
143
+ },
144
+ },
145
+ },
146
+ }
147
+ }
148
+
149
+ module.exports = {
150
+ isNumber,
151
+ isBoolean,
152
+ isString,
153
+ isInteger,
154
+ isType,
155
+ isRequired,
156
+ maxNumber,
157
+ minNumber,
158
+ choices,
159
+ maxTextLength,
160
+ minTextLength,
161
+ meetsRegex,
162
+ aggregateValidator,
163
+ emptyValidator,
164
+ createPropertyValidate,
165
+ }
@@ -3,6 +3,26 @@ const { smartObject } = require('../../src/objects')
3
3
 
4
4
  describe('/src/objects.js', () => {
5
5
  describe('#smartObject()', () => {
6
+ it('should use the functions.validation of two objects correctly', async () => {
7
+ const instance = smartObject([
8
+ { functions: { validate: { property1: () => ['failed1'] } } },
9
+ { functions: { validate: { property2: () => ['failed2'] } } },
10
+ ])
11
+ const actual = await instance.functions.validate.object()
12
+ const expected = {
13
+ property1: ['failed1'],
14
+ property2: ['failed2'],
15
+ }
16
+ assert.deepEqual(actual, expected)
17
+ })
18
+ it('should combine functions.validate of two objects correctly', () => {
19
+ const instance = smartObject([
20
+ { functions: { validate: { property1: () => {} } } },
21
+ { functions: { validate: { property2: () => {} } } },
22
+ ])
23
+ assert.isOk(instance.functions.validate.property1)
24
+ assert.isOk(instance.functions.validate.property2)
25
+ })
6
26
  it('should allow a single value for internals', async () => {
7
27
  const instance = smartObject({
8
28
  key: 'value',
@@ -5,41 +5,33 @@ describe('/src/properties.js', () => {
5
5
  describe('#uniqueId()', () => {
6
6
  describe('#getId()', () => {
7
7
  it('should use the id passed in', () => {
8
- const actual = uniqueId('passed-in').getId()
8
+ const actual = uniqueId()('passed-in').getId()
9
9
  const expected = 'passed-in'
10
10
  assert.equal(actual, expected)
11
11
  })
12
12
  it('should create an id if null is passed in', () => {
13
- const actual = uniqueId().getId()
13
+ const actual = uniqueId()().getId()
14
14
  assert.isOk(actual)
15
15
  })
16
16
  })
17
17
  })
18
18
  describe('#property()', () => {
19
19
  it('should take an arg that is a function', async () => {
20
- const instance = property('property', () => 'hello world')
20
+ const instance = property('property')(() => 'hello world')
21
21
  const actual = await instance.getProperty()
22
22
  })
23
23
  it('should return an object that has a key "getKey" that returns "value" when "Key" is passed in', () => {
24
- const actual = property('Key', 'value')
24
+ const actual = property('Key')('value')
25
25
  const [actualKey, actualValue] = Object.entries(actual)[0]
26
26
  const expectedKey = 'getKey'
27
27
  const expectedValue = 'value'
28
28
  assert.equal(actualKey, expectedKey)
29
29
  assert.equal(actualValue(), expectedValue)
30
30
  })
31
- it('should return an object that has a key "getUpperCased" that returns "value" when {upperCased: "value"} is passed in', () => {
32
- const actual = property({ upperCased: 'value' })
33
- const [actualKey, actualValue] = Object.entries(actual)[0]
34
- const expectedKey = 'getUpperCased'
35
- const expectedValue = 'value'
36
- assert.equal(actualKey, expectedKey)
37
- assert.equal(actualValue(), expectedValue)
38
- })
39
31
  })
40
32
  describe('#named()', () => {
41
33
  it('should return an object with a key "getName" that returns "name" when "name" is passed in', () => {
42
- const actual = named('name')
34
+ const actual = named()('name')
43
35
  const [actualKey, actualValue] = Object.entries(actual)[0]
44
36
  const expectedKey = 'getName'
45
37
  const expectedValue = 'name'
@@ -49,7 +41,7 @@ describe('/src/properties.js', () => {
49
41
  })
50
42
  describe('#typed()', () => {
51
43
  it('should return an object with a key "getType" that returns "theType" when "theType" is passed in', () => {
52
- const actual = typed('theType')
44
+ const actual = typed()('theType')
53
45
  const [actualKey, actualValue] = Object.entries(actual)[0]
54
46
  const expectedKey = 'getType'
55
47
  const expectedValue = 'theType'
@@ -55,7 +55,7 @@ describe('/src/references.js', () => {
55
55
  assert.deepEqual(actual, expected)
56
56
  })
57
57
  it('should take the smartObject as a value', async () => {
58
- const input = ['MyObject', smartObject([uniqueId('obj-id')])]
58
+ const input = ['MyObject', smartObject([uniqueId()('obj-id')])]
59
59
  const instance = smartObjectReference({})(...input)
60
60
  const classThing = await instance.getMyObject()
61
61
  const actual = await (await instance.getMyObject()).getId()
@@ -64,7 +64,7 @@ describe('/src/references.js', () => {
64
64
  })
65
65
  describe('#functions.toJson()', () => {
66
66
  it('should use the getId of the smartObject passed in when toJson is called', async () => {
67
- const input = ['MyObject', smartObject([uniqueId('obj-id')])]
67
+ const input = ['MyObject', smartObject([uniqueId()('obj-id')])]
68
68
  const instance = smartObjectReference({})(...input)
69
69
  const actual = await (await instance.getMyObject()).functions.toJson()
70
70
  const expected = 'obj-id'
@@ -0,0 +1,271 @@
1
+ const assert = require('chai').assert
2
+ const {
3
+ isNumber,
4
+ isBoolean,
5
+ isInteger,
6
+ isString,
7
+ isRequired,
8
+ maxNumber,
9
+ minNumber,
10
+ choices,
11
+ maxTextLength,
12
+ minTextLength,
13
+ meetsRegex,
14
+ aggregateValidator,
15
+ emptyValidator,
16
+ createPropertyValidate,
17
+ } = require('../../src/validation')
18
+
19
+ describe('/src/validation.js', () => {
20
+ describe('#isNumber()', () => {
21
+ it('should return an error when empty is passed', () => {
22
+ const actual = isNumber(null)
23
+ assert.isOk(actual)
24
+ })
25
+ it('should return an error when "asdf" is passed', () => {
26
+ const actual = isNumber('asdf')
27
+ assert.isOk(actual)
28
+ })
29
+ it('should return undefined when 1 is passed', () => {
30
+ const actual = isNumber(1)
31
+ assert.isUndefined(actual)
32
+ })
33
+ it('should return error when "1" is passed', () => {
34
+ const actual = isNumber('1')
35
+ assert.isOk(actual)
36
+ })
37
+ })
38
+ describe('#isString()', () => {
39
+ it('should return undefined when "1" is passed', () => {
40
+ const actual = isString('1')
41
+ assert.isUndefined(actual)
42
+ })
43
+ it('should return error when 1 is passed', () => {
44
+ const actual = isString(1)
45
+ assert.isOk(actual)
46
+ })
47
+ })
48
+ describe('#isRequired()', () => {
49
+ it('should return undefined when 1 is passed', () => {
50
+ const actual = isRequired(1)
51
+ assert.isUndefined(actual)
52
+ })
53
+ it('should return undefined when 0 is passed', () => {
54
+ const actual = isRequired(0)
55
+ assert.isUndefined(actual)
56
+ })
57
+ it('should return undefined when "something" is passed', () => {
58
+ const actual = isRequired('something')
59
+ assert.isUndefined(actual)
60
+ })
61
+ it('should return error when null is passed', () => {
62
+ const actual = isRequired(null)
63
+ assert.isOk(actual)
64
+ })
65
+ it('should return error when undefined is passed', () => {
66
+ const actual = isRequired(undefined)
67
+ assert.isOk(actual)
68
+ })
69
+ it('should return undefined when false is passed', () => {
70
+ const actual = isRequired(false)
71
+ assert.isUndefined(actual)
72
+ })
73
+ it('should return undefined when true is passed', () => {
74
+ const actual = isRequired(true)
75
+ assert.isUndefined(actual)
76
+ })
77
+ })
78
+ describe('#isBoolean()', () => {
79
+ it('should return error when "true" is passed"', () => {
80
+ const actual = isBoolean('true')
81
+ assert.isOk(actual)
82
+ })
83
+ it('should return an error when "false" is passed', () => {
84
+ const actual = isBoolean('false')
85
+ assert.isOk(actual)
86
+ })
87
+ it('should return undefined when true is passed"', () => {
88
+ const actual = isBoolean(true)
89
+ assert.isUndefined(actual)
90
+ })
91
+ it('should return undefined when false is passed', () => {
92
+ const actual = isBoolean(false)
93
+ assert.isUndefined(actual)
94
+ })
95
+ })
96
+ describe('#maxNumber()', () => {
97
+ it('should return error if max=5 and value="hello world"', () => {
98
+ const actual = maxNumber(5)('hello world')
99
+ assert.isOk(actual)
100
+ })
101
+ it('should return error if max=5 and value=6', () => {
102
+ const actual = maxNumber(5)(6)
103
+ assert.isOk(actual)
104
+ })
105
+ it('should return undefined if max=5 and value=5', () => {
106
+ const actual = maxNumber(5)(5)
107
+ assert.isUndefined(actual)
108
+ })
109
+ it('should return undefined if max=5 and value=4', () => {
110
+ const actual = maxNumber(5)(4)
111
+ assert.isUndefined(actual)
112
+ })
113
+ })
114
+ describe('#minNumber()', () => {
115
+ it('should return error if min=5 and value="hello world"', () => {
116
+ const actual = minNumber(5)('hello world')
117
+ assert.isOk(actual)
118
+ })
119
+ it('should return error if min=5 and value=4', () => {
120
+ const actual = minNumber(5)(4)
121
+ assert.isOk(actual)
122
+ })
123
+ it('should return undefined if min=5 and value=4', () => {
124
+ const actual = minNumber(5)(5)
125
+ assert.isUndefined(actual)
126
+ })
127
+ it('should return undefined if min=5 and value=6', () => {
128
+ const actual = minNumber(5)(6)
129
+ assert.isUndefined(actual)
130
+ })
131
+ })
132
+ describe('#choices()', () => {
133
+ it('should return an error if choices are [1,2,3] and value is 4', () => {
134
+ const actual = choices([1, 2, 3])(4)
135
+ assert.isOk(actual)
136
+ })
137
+ it('should return undefined if choices are [1,2,3] and value is 1', () => {
138
+ const actual = choices([1, 2, 3])(1)
139
+ assert.isUndefined(actual)
140
+ })
141
+ })
142
+ describe('#minTextLength()', () => {
143
+ it('should return error if min=5 and value=5', () => {
144
+ const actual = minTextLength(5)(5)
145
+ assert.isOk(actual)
146
+ })
147
+ it('should return error if length=5 and value="asdf"', () => {
148
+ const actual = minTextLength(5)('asdf')
149
+ assert.isOk(actual)
150
+ })
151
+ it('should return undefined if length=5 and value="hello"', () => {
152
+ const actual = minTextLength(5)('hello')
153
+ assert.isUndefined(actual)
154
+ })
155
+ it('should return undefined if length=5 and value="hello world"', () => {
156
+ const actual = minTextLength(5)('hello world')
157
+ assert.isUndefined(actual)
158
+ })
159
+ })
160
+ describe('#maxTextLength()', () => {
161
+ it('should return error if max=5 and value=5', () => {
162
+ const actual = maxTextLength(5)(5)
163
+ assert.isOk(actual)
164
+ })
165
+ it('should return error if length=5 and value="hello world"', () => {
166
+ const actual = maxTextLength(5)('hello world')
167
+ assert.isOk(actual)
168
+ })
169
+ it('should return undefined if length=5 and value="hello"', () => {
170
+ const actual = maxTextLength(5)('hello')
171
+ assert.isUndefined(actual)
172
+ })
173
+ it('should return undefined if length=5 and value="asdf"', () => {
174
+ const actual = maxTextLength(5)('asdf')
175
+ assert.isUndefined(actual)
176
+ })
177
+ })
178
+ describe('#meetsRegex()', () => {
179
+ it('should return an error with regex=/asdf/ flags="g" and value="hello world"', () => {
180
+ const actual = meetsRegex(/asdf/, 'g')('hello world')
181
+ assert.isOk(actual)
182
+ })
183
+ it('should return undefined with regex=/asdf/ flags="g" and value="hello asdf world"', () => {
184
+ const actual = meetsRegex(/asdf/, 'g')('asdf')
185
+ assert.isUndefined(actual)
186
+ })
187
+ })
188
+ describe('#aggregateValidator()', () => {
189
+ it('should return two errors when two validators are passed, and the value fails both', async () => {
190
+ const validators = [minTextLength(10), isNumber]
191
+ const value = 'asdf'
192
+ const actual = (await aggregateValidator(validators)('asdf')).length
193
+ const expected = 2
194
+ assert.equal(actual, expected)
195
+ })
196
+ it('should return one error when one validator is passed, and the value fails', async () => {
197
+ const validators = minTextLength(10)
198
+ const value = 'asdf'
199
+ const actual = (await aggregateValidator(validators)('asdf')).length
200
+ const expected = 1
201
+ assert.equal(actual, expected)
202
+ })
203
+ })
204
+ describe('#createPropertyValidate()', () => {
205
+ it('should result in {}.functions.validate.myProperty when key="myProperty"', () => {
206
+ const actual = createPropertyValidate('myProperty', {})('value')
207
+ assert.isOk(actual.functions.validate.myProperty)
208
+ })
209
+ it('should create a isRequired validator when config contains isRequired=true', async () => {
210
+ const property = createPropertyValidate('myProperty', { required: true })(
211
+ null
212
+ )
213
+ const actual = (await property.functions.validate.myProperty()).length
214
+ const expected = 1
215
+ assert.equal(actual, expected)
216
+ })
217
+ it('should not use isRequired validator when config contains isRequired=false', async () => {
218
+ const property = createPropertyValidate('myProperty', {
219
+ required: false,
220
+ })(null)
221
+ const actual = (await property.functions.validate.myProperty()).length
222
+ const expected = 0
223
+ assert.equal(actual, expected)
224
+ })
225
+ it('should use the validators passed in', async () => {
226
+ const property = createPropertyValidate('myProperty', {
227
+ validators: [maxTextLength(5)],
228
+ })('hello world')
229
+ const actual = (await property.functions.validate.myProperty()).length
230
+ const expected = 1
231
+ assert.equal(actual, expected)
232
+ })
233
+ })
234
+ describe('#emptyValidator()', () => {
235
+ it('should return an empty array with a value of 1', () => {
236
+ const actual = emptyValidator(1).length
237
+ const expected = 0
238
+ assert.equal(actual, expected)
239
+ })
240
+ it('should return an empty array with a value of "1"', () => {
241
+ const actual = emptyValidator('1').length
242
+ const expected = 0
243
+ assert.equal(actual, expected)
244
+ })
245
+ it('should return an empty array with a value of true', () => {
246
+ const actual = emptyValidator(true).length
247
+ const expected = 0
248
+ assert.equal(actual, expected)
249
+ })
250
+ it('should return an empty array with a value of false', () => {
251
+ const actual = emptyValidator(false).length
252
+ const expected = 0
253
+ assert.equal(actual, expected)
254
+ })
255
+ it('should return an empty array with a value of undefined', () => {
256
+ const actual = emptyValidator(undefined).length
257
+ const expected = 0
258
+ assert.equal(actual, expected)
259
+ })
260
+ })
261
+ describe('#isInteger()', () => {
262
+ it('should return an error with a value of "1"', () => {
263
+ const actual = isInteger('1')
264
+ assert.isOk(actual)
265
+ })
266
+ it('should return undefined with a value of 1', () => {
267
+ const actual = isInteger(1)
268
+ assert.isUndefined(actual)
269
+ })
270
+ })
271
+ })