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 +1 -0
- package/.github/workflows/feature.yml +26 -0
- package/.github/workflows/{ci.yml → ut.yml} +6 -7
- package/.prettierignore +1 -0
- package/README.md +4 -5
- package/features/stepDefinitions/steps.js +56 -0
- package/features/validation.feature +12 -0
- package/package.json +10 -3
- package/src/dates.js +1 -1
- package/src/objects.js +30 -4
- package/src/properties.js +7 -15
- package/src/utils.js +1 -0
- package/src/validation.js +165 -0
- package/test/src/objects.test.js +20 -0
- package/test/src/properties.test.js +6 -14
- package/test/src/references.test.js +2 -2
- package/test/src/validation.test.js +271 -0
package/.eslintignore
CHANGED
|
@@ -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:
|
|
1
|
+
name: Unit Tests
|
|
2
2
|
|
|
3
3
|
on:
|
|
4
4
|
push:
|
|
5
|
-
branches: [
|
|
5
|
+
branches: [master]
|
|
6
6
|
pull_request:
|
|
7
|
-
branches: [
|
|
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
|
-
-
|
|
27
|
-
|
|
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
package/README.md
CHANGED
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
# Functional Models
|
|
2
|
-
|
|
2
|
+
|
|
3
|
+

|
|
4
|
+

|
|
3
5
|
[](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.
|
|
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,
|
|
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) => (
|
|
14
|
+
? internals.reduce((acc, obj) => merge(acc, obj), {})
|
|
9
15
|
: internals
|
|
10
16
|
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
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,
|
|
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 =
|
|
21
|
-
const typed =
|
|
22
|
-
const uniqueId = (id = null) =>
|
|
23
|
-
|
|
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
|
@@ -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
|
+
}
|
package/test/src/objects.test.js
CHANGED
|
@@ -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'
|
|
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'
|
|
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
|
+
})
|