systemview 1.5.2 → 1.6.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/cli/appIsRunning.js +25 -0
- package/cli/index.js +8 -40
- package/cli/launchApp.js +48 -0
- package/cli/runTests.js +99 -0
- package/cli/startLineReader.js +27 -0
- package/cli/utils/log.js +2 -8
- package/package.json +3 -2
- package/testing-utilities/Argument.class.js +90 -0
- package/testing-utilities/FullTestController.js +78 -0
- package/testing-utilities/Test.class.js +162 -0
- package/testing-utilities/TestController.class.js +134 -0
- package/testing-utilities/test-helpers.js +148 -0
- package/testing-utilities/transformTests.js +56 -0
- package/testing-utilities/validators.js +228 -0
- package/testing-utilities/validtionMessages.js +45 -0
- package/cli/runTest.js +0 -0
- package/plugin/SystemViewModule.js +0 -97
- package/plugin/getAllTest.js +0 -21
- package/plugin/index.js +0 -40
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
const Test = require("./Test.class");
|
|
2
|
+
const { Argument, TargetValue } = require("./Argument.class");
|
|
3
|
+
const { getType } = require("./test-helpers");
|
|
4
|
+
|
|
5
|
+
module.exports = function TestController({
|
|
6
|
+
TestSection,
|
|
7
|
+
setState,
|
|
8
|
+
section,
|
|
9
|
+
FullTest,
|
|
10
|
+
connectedServices,
|
|
11
|
+
}) {
|
|
12
|
+
this.runTest = async (testIndex) => {
|
|
13
|
+
const test = TestSection[testIndex];
|
|
14
|
+
//run only one test
|
|
15
|
+
await test.runTest();
|
|
16
|
+
setState([...TestSection]);
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
this.updateNamespace = (index, namespace) => {
|
|
20
|
+
TestSection[index].namespace = namespace;
|
|
21
|
+
TestSection[index].getConnection(connectedServices);
|
|
22
|
+
setState([...TestSection]);
|
|
23
|
+
};
|
|
24
|
+
this.addTest = (namespace, args, title) => {
|
|
25
|
+
TestSection.push(new Test({ namespace, args, title, editMode: true }));
|
|
26
|
+
setState([...TestSection]);
|
|
27
|
+
if (namespace) this.updateNamespace(TestSection.length - 1, namespace);
|
|
28
|
+
};
|
|
29
|
+
this.deleteTest = (index) => {
|
|
30
|
+
TestSection.splice(index, 1);
|
|
31
|
+
setState([...TestSection]);
|
|
32
|
+
};
|
|
33
|
+
this.addArg = (index) => {
|
|
34
|
+
const name = "arg" + (TestSection[0].args.length + 1);
|
|
35
|
+
TestSection[index].args.push(new Argument(name, FullTest));
|
|
36
|
+
setState([...TestSection]);
|
|
37
|
+
};
|
|
38
|
+
this.deleteArg = (index, arg_index) => {
|
|
39
|
+
TestSection[index].args.splice(arg_index, 1);
|
|
40
|
+
setState([...TestSection]);
|
|
41
|
+
};
|
|
42
|
+
this.editArg = (index, arg_index, arg) => {
|
|
43
|
+
arg.data_type = getType(arg.input);
|
|
44
|
+
TestSection[index].args[arg_index] = arg;
|
|
45
|
+
setState([...TestSection]);
|
|
46
|
+
};
|
|
47
|
+
this.resetResults = (index) => {
|
|
48
|
+
TestSection[index].clearResults();
|
|
49
|
+
setState([...TestSection]);
|
|
50
|
+
};
|
|
51
|
+
|
|
52
|
+
this.addTargetValue = (
|
|
53
|
+
testIndex,
|
|
54
|
+
arg_index,
|
|
55
|
+
target_namespace,
|
|
56
|
+
source_map,
|
|
57
|
+
source_index
|
|
58
|
+
) => {
|
|
59
|
+
//check to see if target value already exists first
|
|
60
|
+
const arg = TestSection[testIndex].args[arg_index];
|
|
61
|
+
arg.addTargetValue(target_namespace, source_map, source_index);
|
|
62
|
+
setState([...TestSection]);
|
|
63
|
+
};
|
|
64
|
+
this.setTargetValue = (
|
|
65
|
+
testIndex,
|
|
66
|
+
arg_index,
|
|
67
|
+
target_index,
|
|
68
|
+
target_namespace,
|
|
69
|
+
source_map,
|
|
70
|
+
source_index
|
|
71
|
+
) => {
|
|
72
|
+
const arg = TestSection[testIndex].args[arg_index];
|
|
73
|
+
|
|
74
|
+
arg.targetValues[target_index] = new TargetValue(
|
|
75
|
+
target_namespace.trim(),
|
|
76
|
+
source_map,
|
|
77
|
+
source_index
|
|
78
|
+
);
|
|
79
|
+
setState([...TestSection]);
|
|
80
|
+
};
|
|
81
|
+
|
|
82
|
+
this.parseTargetValues = (testIndex, arg_index, input, source_map) => {
|
|
83
|
+
const arg = TestSection[testIndex].args[arg_index];
|
|
84
|
+
arg.parseTargetValues(input, source_map).checkTargetNamespaces();
|
|
85
|
+
setState([...TestSection]);
|
|
86
|
+
};
|
|
87
|
+
this.checkTargetValues = (testIndex, arg_index) => {
|
|
88
|
+
const arg = TestSection[testIndex].args[arg_index];
|
|
89
|
+
arg.checkTargetNamespaces();
|
|
90
|
+
setState([...TestSection]);
|
|
91
|
+
};
|
|
92
|
+
this.updateTitle = (testIndex, title) => {
|
|
93
|
+
TestSection[testIndex].title = title;
|
|
94
|
+
setState([...TestSection]);
|
|
95
|
+
};
|
|
96
|
+
this.updateEvaluations = (testIndex, evaluations) => {
|
|
97
|
+
TestSection[testIndex].evaluations = evaluations;
|
|
98
|
+
setState([...TestSection]);
|
|
99
|
+
};
|
|
100
|
+
this.updateTests = () => {
|
|
101
|
+
setState([...TestSection]);
|
|
102
|
+
};
|
|
103
|
+
this.updateValidationStatus = (testIndex) => {
|
|
104
|
+
if (section !== 1) {
|
|
105
|
+
TestSection[testIndex].shouldValidate = !TestSection[testIndex].shouldValidate;
|
|
106
|
+
if (TestSection[testIndex].shouldValidate) TestSection[testIndex].validate();
|
|
107
|
+
else TestSection[testIndex].evaluations = [];
|
|
108
|
+
setState([...TestSection]);
|
|
109
|
+
}
|
|
110
|
+
};
|
|
111
|
+
this.getTargetSuggestions = (testIndex) => {
|
|
112
|
+
//get target value suggestion (namespaces) for previous test including sub test
|
|
113
|
+
const suggestions = [];
|
|
114
|
+
const test_names = ["beforeTest", "mainTest", "afterTest"];
|
|
115
|
+
//exclude all test sections following current section
|
|
116
|
+
const targetTests = FullTest.slice(0, section + 1);
|
|
117
|
+
|
|
118
|
+
targetTests.forEach((test_section, sIndex) => {
|
|
119
|
+
//also exclude current test and the tests that follow by the suggestions
|
|
120
|
+
const count = sIndex === section ? testIndex : test_section.length;
|
|
121
|
+
for (let i = 0; i < count; i++) {
|
|
122
|
+
suggestions.push(
|
|
123
|
+
`${test_names[sIndex]}.${"Action" + (i + 1) + "."}${
|
|
124
|
+
test_section[i].response_type || "results"
|
|
125
|
+
}`
|
|
126
|
+
);
|
|
127
|
+
}
|
|
128
|
+
});
|
|
129
|
+
|
|
130
|
+
return suggestions;
|
|
131
|
+
};
|
|
132
|
+
|
|
133
|
+
return this;
|
|
134
|
+
};
|
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
const moment = require("moment");
|
|
2
|
+
const rnb = (min, max) => Math.floor(Math.random() * (max - min + 1) + min);
|
|
3
|
+
|
|
4
|
+
const isTargetNamespace = (str) =>
|
|
5
|
+
/^(?:before|main|after)Test\.Action\d+\.(?:error|results)(?:\.(?![0-9])[a-zA-Z0-9$_]+(?:\[\d\])*)*$/.test(
|
|
6
|
+
str
|
|
7
|
+
);
|
|
8
|
+
const targetValueFnRegex =
|
|
9
|
+
/tv\((?:before|main|after)Test\.Action\d+\.(?:error|results)(?:\.(?![0-9])[a-zA-Z0-9$_]+(?:\[\d\])*)*\)/g;
|
|
10
|
+
const isTargetValueFn = (str) =>
|
|
11
|
+
/^tv\((?:before|main|after)Test\.Action\d+\.(?:error|results)(?:\.(?![0-9])[a-zA-Z0-9$_]+(?:\[\d\])*)*\)$/.test(
|
|
12
|
+
str
|
|
13
|
+
);
|
|
14
|
+
const isEqualArrays = (a, b) => a.join(".") === b.join("."); //specifically for arrays of strings
|
|
15
|
+
const isValidNamespace = (str) => /^(?![0-9])[a-zA-Z0-9$_]+$/.test(str); //_id
|
|
16
|
+
const startsWithNameAndArray = (str) => /^\w+(\[\d+\])+/.test(str); //users[0]
|
|
17
|
+
const isNameAndArray = (str) => /^\w+(\[\d+\])+$/.test(str); //users[0]
|
|
18
|
+
const endsWithArrayIndex = (str) => /\w+(\[\d+\])+$/.test(str); //users[0].docs[3]...
|
|
19
|
+
const getLastArrayNamespace = (str) => (str.match(/(\w+(\[\d+\])+)$/) || [str])[0];
|
|
20
|
+
|
|
21
|
+
const parseIndex = (nsp) => parseInt((nsp.match(/\[(\d+)\]$/) || [null, "0"])[1]);
|
|
22
|
+
const replaceFirstIndex = (nsp, insert = "0") => nsp.replace(/(\[\d+\])/, `[${insert}]`);
|
|
23
|
+
const replaceLastIndex = (nsp, insert = "0") => nsp.replace(/(\[\d+\])$/, `[${insert}]`);
|
|
24
|
+
|
|
25
|
+
const replaceAllIndices = (nsp, insert = "0") => nsp.replace(/(\[\d+\])/g, `[${insert}]`);
|
|
26
|
+
|
|
27
|
+
const getArrayNamespaces = (str) =>
|
|
28
|
+
str
|
|
29
|
+
.split(/(\w+(\[\d+\])+)/)
|
|
30
|
+
.filter(isNameAndArray)
|
|
31
|
+
.reduce((sum, nsp) => {
|
|
32
|
+
//split by indexes in case of nested arrays
|
|
33
|
+
const indices = nsp.split(/(\[\d+\])/).filter((n) => n);
|
|
34
|
+
const [name] = indices.splice(0, 1);
|
|
35
|
+
return sum.concat(
|
|
36
|
+
indices.map((index, i) => {
|
|
37
|
+
return name + indices.slice(0, i + 1).join("");
|
|
38
|
+
}, [])
|
|
39
|
+
);
|
|
40
|
+
}, []);
|
|
41
|
+
const switchArrayIndices = (nsp, replace) => {
|
|
42
|
+
// normalize nsp and replace nsp
|
|
43
|
+
const n = replaceAllIndices(nsp);
|
|
44
|
+
const r = replaceAllIndices(replace);
|
|
45
|
+
// match the normalized namespaces
|
|
46
|
+
if (n.substr(0, r.length) === r) {
|
|
47
|
+
// create new namespace by concatenating
|
|
48
|
+
const n = nsp.split(".");
|
|
49
|
+
const r = replace.split(".");
|
|
50
|
+
n.splice(...[0, r.length, ...r]);
|
|
51
|
+
return n.join(".");
|
|
52
|
+
} else return nsp;
|
|
53
|
+
};
|
|
54
|
+
|
|
55
|
+
//separate prop names from other prop names and indices (ie. 'test.results[0][0]'...);
|
|
56
|
+
const mapNamespace = (nsp) =>
|
|
57
|
+
nsp
|
|
58
|
+
.replace(/(?:\.|\[|\])/g, " ")
|
|
59
|
+
.split(" ")
|
|
60
|
+
.reduce((sum, str) => sum.concat(str.trim() || []), []);
|
|
61
|
+
|
|
62
|
+
const obj = function ObjectParser(obj) {
|
|
63
|
+
const parser = this || {};
|
|
64
|
+
parser.parse = (map) =>
|
|
65
|
+
map.reduce(([placeholder], key) => [placeholder?.[key], placeholder, key], [obj]);
|
|
66
|
+
|
|
67
|
+
parser.valueAt = (map) => parser.parse(map)[0];
|
|
68
|
+
|
|
69
|
+
parser.valueAtNsp = (nsp) => parser.valueAt(mapNamespace(nsp));
|
|
70
|
+
|
|
71
|
+
parser.parseNsp = (nsp) => parser.parse(mapNamespace(nsp));
|
|
72
|
+
//using JSON to create a deep copy in order to lose refs to original
|
|
73
|
+
parser.clone = () => JSON.parse(JSON.stringify(obj));
|
|
74
|
+
|
|
75
|
+
parser.isEmpty = () => Object.getOwnPropertyNames(obj).length === 0;
|
|
76
|
+
|
|
77
|
+
return parser;
|
|
78
|
+
};
|
|
79
|
+
|
|
80
|
+
const arr = function ArrayParser(arr) {
|
|
81
|
+
const parser = this || {};
|
|
82
|
+
|
|
83
|
+
parser.randomIndex = () => rnb(0, arr.length - 1);
|
|
84
|
+
|
|
85
|
+
parser.randomItem = () => arr[parser.randomIndex()];
|
|
86
|
+
return parser;
|
|
87
|
+
};
|
|
88
|
+
|
|
89
|
+
const isFn = /^\w+\(([^,)]*(,[^,)]*)*)\)$/;
|
|
90
|
+
const parseArgs = (str) => str.split(",").map((value) => value.trim());
|
|
91
|
+
const isFunction = (str) => isFn.test(str);
|
|
92
|
+
const isDateFunction = (str) => /^[dD]ate\(([^,)]*(,[^,)]*)*)\)$/.test(str);
|
|
93
|
+
|
|
94
|
+
const strFn = (str) => {
|
|
95
|
+
if (isDateFunction(str)) {
|
|
96
|
+
const [fullStr, args] = str.match(isFn);
|
|
97
|
+
return moment(args).toJSON();
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
return str;
|
|
101
|
+
};
|
|
102
|
+
function getType(value) {
|
|
103
|
+
switch (true) {
|
|
104
|
+
case typeof value === "object":
|
|
105
|
+
if (!value) return "null";
|
|
106
|
+
else if (Array.isArray(value)) return "array";
|
|
107
|
+
else return "object";
|
|
108
|
+
case typeof value === "string":
|
|
109
|
+
if (moment(value).isValid()) return "date";
|
|
110
|
+
else return "string";
|
|
111
|
+
case typeof value === "number":
|
|
112
|
+
return "number";
|
|
113
|
+
case typeof value === "boolean":
|
|
114
|
+
return "boolean";
|
|
115
|
+
case typeof value === "undefined":
|
|
116
|
+
return "undefined";
|
|
117
|
+
default:
|
|
118
|
+
return "?";
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
const isObjectLike = (value) =>
|
|
122
|
+
["object", "array", "string"].indexOf(getType(value)) > -1;
|
|
123
|
+
module.exports = {
|
|
124
|
+
rnb,
|
|
125
|
+
isObjectLike,
|
|
126
|
+
isTargetNamespace,
|
|
127
|
+
targetValueFnRegex,
|
|
128
|
+
isTargetValueFn,
|
|
129
|
+
isEqualArrays,
|
|
130
|
+
isValidNamespace,
|
|
131
|
+
startsWithNameAndArray,
|
|
132
|
+
isNameAndArray,
|
|
133
|
+
endsWithArrayIndex,
|
|
134
|
+
getLastArrayNamespace,
|
|
135
|
+
parseIndex,
|
|
136
|
+
replaceFirstIndex,
|
|
137
|
+
replaceLastIndex,
|
|
138
|
+
replaceAllIndices,
|
|
139
|
+
getArrayNamespaces,
|
|
140
|
+
switchArrayIndices,
|
|
141
|
+
mapNamespace,
|
|
142
|
+
obj,
|
|
143
|
+
arr,
|
|
144
|
+
isFunction,
|
|
145
|
+
isDateFunction,
|
|
146
|
+
strFn,
|
|
147
|
+
getType,
|
|
148
|
+
};
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
const { Argument } = require("./Argument.class");
|
|
2
|
+
const Test = require("./Test.class");
|
|
3
|
+
|
|
4
|
+
function initializeSavedTests(savedTests, connectedServices) {
|
|
5
|
+
return savedTests.map((ft) => {
|
|
6
|
+
// context matters
|
|
7
|
+
const newTests = [];
|
|
8
|
+
const { title, namespace } = ft;
|
|
9
|
+
|
|
10
|
+
const Before = ft.Before.map((test) =>
|
|
11
|
+
resetTestClass(test, newTests, connectedServices, false)
|
|
12
|
+
);
|
|
13
|
+
const Main = ft.Main.map((test) =>
|
|
14
|
+
resetTestClass(test, newTests, connectedServices, false)
|
|
15
|
+
);
|
|
16
|
+
const Events = ft.Events.map((test) =>
|
|
17
|
+
resetTestClass(test, newTests, connectedServices, false)
|
|
18
|
+
);
|
|
19
|
+
const After = ft.After.map((test) =>
|
|
20
|
+
resetTestClass(test, newTests, connectedServices, false)
|
|
21
|
+
);
|
|
22
|
+
newTests.push(Before);
|
|
23
|
+
newTests.push(Main);
|
|
24
|
+
newTests.push(Events);
|
|
25
|
+
newTests.push(After);
|
|
26
|
+
return { Before, Main, Events, After, title, namespace };
|
|
27
|
+
});
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
const resetTestClass = (test, FullTest, connectedServices, editMode) => {
|
|
31
|
+
return new Test({
|
|
32
|
+
...test,
|
|
33
|
+
args: test.args.map(
|
|
34
|
+
(arg) =>
|
|
35
|
+
new Argument(arg.name, FullTest, arg.input_type, arg.input, arg.targetValues)
|
|
36
|
+
),
|
|
37
|
+
editMode,
|
|
38
|
+
}).getConnection(connectedServices);
|
|
39
|
+
};
|
|
40
|
+
|
|
41
|
+
const resetFullTest = (FullTest, connectedServices, editMode) => {
|
|
42
|
+
//context matters
|
|
43
|
+
const newTests = [[], [], [], []];
|
|
44
|
+
return FullTest.map((section, i) => {
|
|
45
|
+
return section.map((test) => {
|
|
46
|
+
const newTest = resetTestClass(test, newTests, connectedServices, editMode);
|
|
47
|
+
newTests[i].push(newTest);
|
|
48
|
+
return newTest;
|
|
49
|
+
});
|
|
50
|
+
});
|
|
51
|
+
};
|
|
52
|
+
module.exports = {
|
|
53
|
+
initializeSavedTests,
|
|
54
|
+
resetTestClass,
|
|
55
|
+
resetFullTest,
|
|
56
|
+
};
|
|
@@ -0,0 +1,228 @@
|
|
|
1
|
+
const moment = require("moment");
|
|
2
|
+
const {
|
|
3
|
+
arr,
|
|
4
|
+
obj,
|
|
5
|
+
parseIndex,
|
|
6
|
+
replaceLastIndex,
|
|
7
|
+
switchArrayIndices,
|
|
8
|
+
getType,
|
|
9
|
+
} = require("./test-helpers");
|
|
10
|
+
|
|
11
|
+
function evaluate(value, namespace, savedEval = {}, shouldSave) {
|
|
12
|
+
const type = getType(value);
|
|
13
|
+
const validations = savedEval.validations || [];
|
|
14
|
+
const expected_type = savedEval.expected_type || type;
|
|
15
|
+
const save = shouldSave || !!savedEval.save;
|
|
16
|
+
const indexed = savedEval.indexed;
|
|
17
|
+
const errors = getErrors({ type, value, validations, expected_type });
|
|
18
|
+
return {
|
|
19
|
+
namespace,
|
|
20
|
+
expected_type,
|
|
21
|
+
validations,
|
|
22
|
+
save,
|
|
23
|
+
indexed,
|
|
24
|
+
type,
|
|
25
|
+
value,
|
|
26
|
+
errors,
|
|
27
|
+
};
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
function validateResults() {
|
|
31
|
+
const { results, response_type, savedEvaluations, editMode } = this;
|
|
32
|
+
const savedEvalClone = [...savedEvaluations];
|
|
33
|
+
const shouldSave = !savedEvaluations.length;
|
|
34
|
+
const evaluations = [];
|
|
35
|
+
const errors = [];
|
|
36
|
+
|
|
37
|
+
function getSavedIndices(data, nsp) {
|
|
38
|
+
const randomIndex = () => {
|
|
39
|
+
// get all matching indices and rename them
|
|
40
|
+
// so they can be found later during getSavedEval
|
|
41
|
+
const index = arr(data).randomIndex();
|
|
42
|
+
const new_nsp = replaceLastIndex(nsp, index);
|
|
43
|
+
savedEvalClone.forEach((e) => {
|
|
44
|
+
if (!e.indexed) e.namespace = switchArrayIndices(e.namespace, new_nsp);
|
|
45
|
+
});
|
|
46
|
+
return index;
|
|
47
|
+
};
|
|
48
|
+
const savedIndices = savedEvalClone
|
|
49
|
+
.filter(({ namespace }) => {
|
|
50
|
+
return replaceLastIndex(namespace) === nsp;
|
|
51
|
+
})
|
|
52
|
+
.map((e) => {
|
|
53
|
+
if (e.indexed) {
|
|
54
|
+
return parseIndex(e.namespace);
|
|
55
|
+
} else {
|
|
56
|
+
return randomIndex();
|
|
57
|
+
}
|
|
58
|
+
});
|
|
59
|
+
return savedIndices.length ? savedIndices : [randomIndex()];
|
|
60
|
+
}
|
|
61
|
+
const getSavedEval = (nsp) => {
|
|
62
|
+
const i = savedEvalClone.findIndex(({ namespace }) => {
|
|
63
|
+
return replaceLastIndex(namespace) === replaceLastIndex(nsp);
|
|
64
|
+
});
|
|
65
|
+
return i > -1 ? savedEvalClone.splice(i, 1)[0] : {};
|
|
66
|
+
};
|
|
67
|
+
const addEvaluation = (evaluation) => {
|
|
68
|
+
evaluation.errors.forEach(
|
|
69
|
+
(e) => evaluation.save && errors.push({ ...e, namespace: evaluation.namespace })
|
|
70
|
+
);
|
|
71
|
+
evaluations.push(evaluation);
|
|
72
|
+
};
|
|
73
|
+
|
|
74
|
+
//evaluate based on the result only in edit mode
|
|
75
|
+
if (editMode)
|
|
76
|
+
(function recursiveEval(data, namespace) {
|
|
77
|
+
const evaluation = evaluate(data, namespace, getSavedEval(namespace), shouldSave);
|
|
78
|
+
addEvaluation(evaluation);
|
|
79
|
+
if (evaluation.type === "object") {
|
|
80
|
+
Object.getOwnPropertyNames(data).forEach((prop) => {
|
|
81
|
+
recursiveEval(data[prop], `${namespace}.${prop}`);
|
|
82
|
+
});
|
|
83
|
+
} else if (evaluation.type === "array") {
|
|
84
|
+
const indices = getSavedIndices(data, `${namespace}[0]`);
|
|
85
|
+
indices.forEach((index) => recursiveEval(data[index], `${namespace}[${index}]`));
|
|
86
|
+
}
|
|
87
|
+
})(results, response_type);
|
|
88
|
+
|
|
89
|
+
//evaluate based on the saved evaluations
|
|
90
|
+
const objParser = new obj({ [response_type]: results });
|
|
91
|
+
savedEvalClone.forEach(({ namespace, ...e }) => {
|
|
92
|
+
const value = objParser.valueAtNsp(namespace);
|
|
93
|
+
if (e.save) addEvaluation(evaluate(value, namespace, e));
|
|
94
|
+
});
|
|
95
|
+
|
|
96
|
+
Object.assign(this, {
|
|
97
|
+
evaluations: evaluations.sort((e1, e2) => e1.namespace.localeCompare(e2.namespace)),
|
|
98
|
+
errors,
|
|
99
|
+
});
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
function getErrors({ type, value, validations, expected_type }) {
|
|
103
|
+
if (type !== expected_type && expected_type !== "mixed")
|
|
104
|
+
return [{ name: "typeError", expected: expected_type, received: type }];
|
|
105
|
+
|
|
106
|
+
switch (type) {
|
|
107
|
+
case "number":
|
|
108
|
+
return validateNumber(value, validations);
|
|
109
|
+
case "date":
|
|
110
|
+
return validateDate(value, validations);
|
|
111
|
+
case "string":
|
|
112
|
+
return validateString(value, validations);
|
|
113
|
+
case "array":
|
|
114
|
+
return validateArray(value, validations);
|
|
115
|
+
case "boolean":
|
|
116
|
+
return validateBoolean(value, validations);
|
|
117
|
+
case "null":
|
|
118
|
+
case "undefined":
|
|
119
|
+
default:
|
|
120
|
+
return [];
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
const defaultValue = (data_type) => {
|
|
125
|
+
switch (data_type) {
|
|
126
|
+
case "string":
|
|
127
|
+
return "";
|
|
128
|
+
case "number":
|
|
129
|
+
return 0;
|
|
130
|
+
case "date":
|
|
131
|
+
return moment().toJSON();
|
|
132
|
+
case "boolean":
|
|
133
|
+
return false;
|
|
134
|
+
case "array":
|
|
135
|
+
return [];
|
|
136
|
+
case "object":
|
|
137
|
+
return {};
|
|
138
|
+
case "null":
|
|
139
|
+
return null;
|
|
140
|
+
case "target":
|
|
141
|
+
return "";
|
|
142
|
+
case "undefined":
|
|
143
|
+
default:
|
|
144
|
+
return undefined;
|
|
145
|
+
}
|
|
146
|
+
};
|
|
147
|
+
|
|
148
|
+
const validateLength = (item, validations) =>
|
|
149
|
+
validations.reduce((errors, { name, value }) => {
|
|
150
|
+
if (name === "lengthEquals" && item.length !== value)
|
|
151
|
+
return errors.concat({ name, expected: value, received: item.length });
|
|
152
|
+
if (name === "maxLength" && item.length > value)
|
|
153
|
+
return errors.concat({ name, expected: value, received: item.length });
|
|
154
|
+
if (name === "minLength" && item.length < value)
|
|
155
|
+
return errors.concat({ name, expected: value, received: item.length });
|
|
156
|
+
return errors;
|
|
157
|
+
}, []);
|
|
158
|
+
|
|
159
|
+
const validateArray = (arr, validations) =>
|
|
160
|
+
validations.reduce((errors, { name, value }) => {
|
|
161
|
+
if (name === "includes" && !arr.includes(value))
|
|
162
|
+
return errors.concat({ name, expected: value, received: arr });
|
|
163
|
+
return errors;
|
|
164
|
+
}, validateLength(arr, validations));
|
|
165
|
+
|
|
166
|
+
const validateString = (str, validations) =>
|
|
167
|
+
validations.reduce((errors, { name, value }) => {
|
|
168
|
+
if (name === "strEquals" && str !== value)
|
|
169
|
+
return errors.concat({ name, expected: value, received: str });
|
|
170
|
+
//str.match() returns null when there is no match
|
|
171
|
+
if ((name === "isLike") & !str.match(new RegExp(value, "gi")))
|
|
172
|
+
return errors.concat({ name, expected: value, received: str });
|
|
173
|
+
if (
|
|
174
|
+
name === "isOneOf" &&
|
|
175
|
+
typeof value === "string" &&
|
|
176
|
+
!value
|
|
177
|
+
.split(",")
|
|
178
|
+
.map((v) => v.trim())
|
|
179
|
+
.includes(str)
|
|
180
|
+
)
|
|
181
|
+
return errors.concat({ name, expected: value, received: str });
|
|
182
|
+
return errors;
|
|
183
|
+
}, validateLength(str, validations));
|
|
184
|
+
|
|
185
|
+
const validateNumber = (num, validations) =>
|
|
186
|
+
validations.reduce((errors, { name, value }) => {
|
|
187
|
+
if (name === "numEquals" && num !== value)
|
|
188
|
+
return errors.concat({ name, expected: value, received: num });
|
|
189
|
+
if (name === "max" && num > value)
|
|
190
|
+
return errors.concat({ name, expected: value, received: num });
|
|
191
|
+
if (name === "min" && num < value)
|
|
192
|
+
return errors.concat({ name, expected: value, received: num });
|
|
193
|
+
if (
|
|
194
|
+
name === "isOneOf" &&
|
|
195
|
+
typeof value === "string" &&
|
|
196
|
+
!value
|
|
197
|
+
.split(",")
|
|
198
|
+
.map((v) => parseInt(v))
|
|
199
|
+
.includes(num)
|
|
200
|
+
)
|
|
201
|
+
return errors.concat({ name, expected: value, received: num });
|
|
202
|
+
return errors;
|
|
203
|
+
}, []);
|
|
204
|
+
|
|
205
|
+
const validateBoolean = (bool, validations) =>
|
|
206
|
+
validations.reduce((errors, { name, value }) => {
|
|
207
|
+
if (name === "boolEquals" && bool !== value)
|
|
208
|
+
return errors.concat({ name, expected: value, received: bool });
|
|
209
|
+
return errors;
|
|
210
|
+
}, []);
|
|
211
|
+
|
|
212
|
+
const validateDate = (datetime, validations) =>
|
|
213
|
+
validations.reduce((errors, { name, value }) => {
|
|
214
|
+
if (name === "dateEquals" && !moment(datetime).isSame(value))
|
|
215
|
+
return errors.concat({ name, expected: value, received: datetime });
|
|
216
|
+
if (name === "maxDate" && moment(datetime).isAfter(value))
|
|
217
|
+
return errors.concat({ name, expected: value, received: datetime });
|
|
218
|
+
if (name === "minDate" && moment(datetime).isBefore(value))
|
|
219
|
+
return errors.concat({ name, expected: value, received: datetime });
|
|
220
|
+
return errors;
|
|
221
|
+
}, []);
|
|
222
|
+
|
|
223
|
+
module.exports = {
|
|
224
|
+
evaluate,
|
|
225
|
+
validateResults,
|
|
226
|
+
getErrors,
|
|
227
|
+
defaultValue,
|
|
228
|
+
};
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
const moment = require("moment/moment");
|
|
2
|
+
|
|
3
|
+
const vowels = ["a", "e", "i", "o", "u"];
|
|
4
|
+
const isVowel = (str) => vowels.includes((str + "").toLowerCase());
|
|
5
|
+
const an = (word) =>
|
|
6
|
+
word + "" === "undefined" ? "" : isVowel((word + "")[0]) ? "an" : "a";
|
|
7
|
+
|
|
8
|
+
module.exports = function validationMessage({ name, namespace, expected, received }) {
|
|
9
|
+
return errorMessages[name](namespace, expected, received);
|
|
10
|
+
};
|
|
11
|
+
const errorMessages = {
|
|
12
|
+
typeError: (namespace, expected, received) => {
|
|
13
|
+
const a = isVowel(expected) ? "an" : "a";
|
|
14
|
+
return `Expected ${namespace}to be ${a} ${expected}, received ${received}`;
|
|
15
|
+
},
|
|
16
|
+
|
|
17
|
+
lengthEquals: (namespace, expected, received) =>
|
|
18
|
+
`Expected ${namespace} to have a length of ${expected}, received ${received}`,
|
|
19
|
+
maxLength: (namespace, expected, received) =>
|
|
20
|
+
`Expected ${namespace} to have a maximum length of ${expected}, received ${received}`,
|
|
21
|
+
minLength: (namespace, expected, received) =>
|
|
22
|
+
`Expected ${namespace} to have a minimum length of ${expected}, received ${received}`,
|
|
23
|
+
includes: (namespace, expected, received) =>
|
|
24
|
+
`Expected ${namespace} to include the following value: ${expected}, received ${received}`,
|
|
25
|
+
isLike: (namespace, expected, received) =>
|
|
26
|
+
`Expected ${namespace} to be like the following expression: ${expected}, received ${received}`,
|
|
27
|
+
isOneOf: (namespace, expected, received) =>
|
|
28
|
+
`Expected ${namespace} to be one of the following values: ${expected}, received ${received}`,
|
|
29
|
+
strEquals: (namespace, expected, received) =>
|
|
30
|
+
`Expected ${namespace} to equal "${expected}"`,
|
|
31
|
+
numEquals: (namespace, expected, received) =>
|
|
32
|
+
`Expected ${namespace} to equal ${expected}, received ${received}`,
|
|
33
|
+
max: (namespace, expected, received) =>
|
|
34
|
+
`Expected ${namespace} to be less than ${expected}, received ${received}`,
|
|
35
|
+
min: (namespace, expected, received) =>
|
|
36
|
+
`Expected ${namespace} to be greater than ${expected}, received ${received}`,
|
|
37
|
+
boolEquals: (namespace, expected, received) =>
|
|
38
|
+
`Expected ${namespace} to be ${expected.toString()}`,
|
|
39
|
+
dateEquals: (namespace, expected, received) =>
|
|
40
|
+
`Expected ${namespace} to be ${moment(expected).format()}`,
|
|
41
|
+
minDate: (namespace, expected, received) =>
|
|
42
|
+
`Expected ${namespace} to be a date/time later than ${expected}, received ${received}`,
|
|
43
|
+
maxDate: (namespace, expected, received) =>
|
|
44
|
+
`Expected ${namespace} to be a date/time earlier than ${expected}, received ${received}`,
|
|
45
|
+
};
|
package/cli/runTest.js
DELETED
|
File without changes
|