@remoteoss/json-schema-form 0.4.5-beta.0 → 0.5.0-beta.0
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/CHANGELOG.md +7 -0
- package/dist/index.cjs +197 -33
- package/dist/index.js +197 -33
- package/dist/standalone.js +575 -33
- package/package.json +2 -1
- package/src/tests/const.test.js +12 -0
- package/src/tests/jsonLogic.fixtures.js +162 -0
- package/src/tests/jsonLogic.test.js +230 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@remoteoss/json-schema-form",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.5.0-beta.0",
|
|
4
4
|
"description": "Headless UI form powered by JSON Schemas",
|
|
5
5
|
"author": "Remote.com <engineering@remote.com> (https://remote.com/)",
|
|
6
6
|
"license": "MIT",
|
|
@@ -47,6 +47,7 @@
|
|
|
47
47
|
]
|
|
48
48
|
},
|
|
49
49
|
"dependencies": {
|
|
50
|
+
"json-logic-js": "^2.0.2",
|
|
50
51
|
"lodash": "^4.17.21",
|
|
51
52
|
"randexp": "^0.5.3",
|
|
52
53
|
"yup": "^0.30.0"
|
package/src/tests/const.test.js
CHANGED
|
@@ -83,4 +83,16 @@ describe('validations: const', () => {
|
|
|
83
83
|
});
|
|
84
84
|
expect(handleValidation({ string: 'hello' }).formErrors).toEqual(undefined);
|
|
85
85
|
});
|
|
86
|
+
|
|
87
|
+
it('Should have value attribute for when const & default is present', () => {
|
|
88
|
+
const { fields } = createHeadlessForm(
|
|
89
|
+
{
|
|
90
|
+
properties: {
|
|
91
|
+
ten_only: { type: 'number', const: 10, default: 10 },
|
|
92
|
+
},
|
|
93
|
+
},
|
|
94
|
+
{ strictInputType: false }
|
|
95
|
+
);
|
|
96
|
+
expect(fields[0]).toMatchObject({ value: 10, const: 10, default: 10 });
|
|
97
|
+
});
|
|
86
98
|
});
|
|
@@ -0,0 +1,162 @@
|
|
|
1
|
+
export function createSchemaWithRulesOnFieldA(rules) {
|
|
2
|
+
return {
|
|
3
|
+
properties: {
|
|
4
|
+
field_a: {
|
|
5
|
+
type: 'number',
|
|
6
|
+
'x-jsf-logic-validations': Object.keys(rules),
|
|
7
|
+
},
|
|
8
|
+
field_b: {
|
|
9
|
+
type: 'number',
|
|
10
|
+
},
|
|
11
|
+
},
|
|
12
|
+
required: ['field_a', 'field_b'],
|
|
13
|
+
'x-jsf-logic': { validations: rules },
|
|
14
|
+
};
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export function createSchemaWithThreePropertiesWithRuleOnFieldA(rules) {
|
|
18
|
+
return {
|
|
19
|
+
properties: {
|
|
20
|
+
field_a: {
|
|
21
|
+
type: 'number',
|
|
22
|
+
'x-jsf-logic-validations': Object.keys(rules),
|
|
23
|
+
},
|
|
24
|
+
field_b: {
|
|
25
|
+
type: 'number',
|
|
26
|
+
},
|
|
27
|
+
field_c: {
|
|
28
|
+
type: 'number',
|
|
29
|
+
},
|
|
30
|
+
},
|
|
31
|
+
'x-jsf-logic': { validations: rules },
|
|
32
|
+
required: ['field_a', 'field_b', 'field_c'],
|
|
33
|
+
};
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export const schemaWithNonRequiredField = {
|
|
37
|
+
properties: {
|
|
38
|
+
field_a: {
|
|
39
|
+
type: 'number',
|
|
40
|
+
},
|
|
41
|
+
field_b: {
|
|
42
|
+
type: 'number',
|
|
43
|
+
'x-jsf-logic-validations': ['a_greater_than_field_b'],
|
|
44
|
+
},
|
|
45
|
+
},
|
|
46
|
+
'x-jsf-logic': {
|
|
47
|
+
validations: {
|
|
48
|
+
a_greater_than_field_b: {
|
|
49
|
+
errorMessage: 'Must be greater than field_a',
|
|
50
|
+
rule: {
|
|
51
|
+
'>': [{ var: 'field_a' }, { var: 'field_b' }],
|
|
52
|
+
},
|
|
53
|
+
},
|
|
54
|
+
},
|
|
55
|
+
},
|
|
56
|
+
required: [],
|
|
57
|
+
};
|
|
58
|
+
|
|
59
|
+
export const schemaWithNativeAndJSONLogicChecks = {
|
|
60
|
+
properties: {
|
|
61
|
+
field_a: {
|
|
62
|
+
type: 'number',
|
|
63
|
+
minimum: 100,
|
|
64
|
+
'x-jsf-logic-validations': ['a_multiple_of_ten'],
|
|
65
|
+
},
|
|
66
|
+
},
|
|
67
|
+
'x-jsf-logic': {
|
|
68
|
+
validations: {
|
|
69
|
+
a_multiple_of_ten: {
|
|
70
|
+
errorMessage: 'Must be a multiple of 10',
|
|
71
|
+
rule: {
|
|
72
|
+
'===': [{ '%': [{ var: 'field_a' }, 10] }, 0],
|
|
73
|
+
},
|
|
74
|
+
},
|
|
75
|
+
},
|
|
76
|
+
},
|
|
77
|
+
required: ['field_a'],
|
|
78
|
+
};
|
|
79
|
+
|
|
80
|
+
export const multiRuleSchema = {
|
|
81
|
+
properties: {
|
|
82
|
+
field_a: {
|
|
83
|
+
type: 'number',
|
|
84
|
+
'x-jsf-logic-validations': ['a_bigger_than_b', 'is_even_number'],
|
|
85
|
+
},
|
|
86
|
+
field_b: {
|
|
87
|
+
type: 'number',
|
|
88
|
+
},
|
|
89
|
+
},
|
|
90
|
+
required: ['field_a', 'field_b'],
|
|
91
|
+
'x-jsf-logic': {
|
|
92
|
+
validations: {
|
|
93
|
+
a_bigger_than_b: {
|
|
94
|
+
errorMessage: 'A must be bigger than B',
|
|
95
|
+
rule: {
|
|
96
|
+
'>': [{ var: 'field_a' }, { var: 'field_b' }],
|
|
97
|
+
},
|
|
98
|
+
},
|
|
99
|
+
is_even_number: {
|
|
100
|
+
errorMessage: 'A must be even',
|
|
101
|
+
rule: {
|
|
102
|
+
'===': [{ '%': [{ var: 'field_a' }, 2] }, 0],
|
|
103
|
+
},
|
|
104
|
+
},
|
|
105
|
+
},
|
|
106
|
+
},
|
|
107
|
+
};
|
|
108
|
+
|
|
109
|
+
export const schemaWithTwoRules = {
|
|
110
|
+
properties: {
|
|
111
|
+
field_a: {
|
|
112
|
+
type: 'number',
|
|
113
|
+
'x-jsf-logic-validations': ['a_bigger_than_b'],
|
|
114
|
+
},
|
|
115
|
+
field_b: {
|
|
116
|
+
type: 'number',
|
|
117
|
+
'x-jsf-logic-validations': ['is_even_number'],
|
|
118
|
+
},
|
|
119
|
+
},
|
|
120
|
+
required: ['field_a', 'field_b'],
|
|
121
|
+
'x-jsf-logic': {
|
|
122
|
+
validations: {
|
|
123
|
+
a_bigger_than_b: {
|
|
124
|
+
errorMessage: 'A must be bigger than B',
|
|
125
|
+
rule: {
|
|
126
|
+
'>': [{ var: 'field_a' }, { var: 'field_b' }],
|
|
127
|
+
},
|
|
128
|
+
},
|
|
129
|
+
is_even_number: {
|
|
130
|
+
errorMessage: 'B must be even',
|
|
131
|
+
rule: {
|
|
132
|
+
'===': [{ '%': [{ var: 'field_b' }, 2] }, 0],
|
|
133
|
+
},
|
|
134
|
+
},
|
|
135
|
+
},
|
|
136
|
+
},
|
|
137
|
+
};
|
|
138
|
+
|
|
139
|
+
export const schemaWithComputedAttributes = {
|
|
140
|
+
properties: {
|
|
141
|
+
field_a: {
|
|
142
|
+
type: 'number',
|
|
143
|
+
},
|
|
144
|
+
field_b: {
|
|
145
|
+
type: 'number',
|
|
146
|
+
'x-jsf-logic-computedAttrs': {
|
|
147
|
+
const: 'a_times_two',
|
|
148
|
+
default: 'a_times_two',
|
|
149
|
+
},
|
|
150
|
+
},
|
|
151
|
+
},
|
|
152
|
+
required: ['field_a', 'field_b'],
|
|
153
|
+
'x-jsf-logic': {
|
|
154
|
+
computedValues: {
|
|
155
|
+
a_times_two: {
|
|
156
|
+
rule: {
|
|
157
|
+
'*': [{ var: 'field_a' }, 2],
|
|
158
|
+
},
|
|
159
|
+
},
|
|
160
|
+
},
|
|
161
|
+
},
|
|
162
|
+
};
|
|
@@ -0,0 +1,230 @@
|
|
|
1
|
+
import { createHeadlessForm } from '../createHeadlessForm';
|
|
2
|
+
|
|
3
|
+
import {
|
|
4
|
+
createSchemaWithRulesOnFieldA,
|
|
5
|
+
createSchemaWithThreePropertiesWithRuleOnFieldA,
|
|
6
|
+
multiRuleSchema,
|
|
7
|
+
schemaWithComputedAttributes,
|
|
8
|
+
schemaWithNativeAndJSONLogicChecks,
|
|
9
|
+
schemaWithNonRequiredField,
|
|
10
|
+
schemaWithTwoRules,
|
|
11
|
+
} from './jsonLogic.fixtures';
|
|
12
|
+
|
|
13
|
+
describe('jsonLogic: cross-values validations', () => {
|
|
14
|
+
describe('Does not conflict with native JSON schema', () => {
|
|
15
|
+
it('Given an optional field and empty value, jsonLogic validations are ignored', () => {
|
|
16
|
+
const { handleValidation } = createHeadlessForm(schemaWithNonRequiredField, {
|
|
17
|
+
strictInputType: false,
|
|
18
|
+
});
|
|
19
|
+
expect(handleValidation({}).formErrors).toBeUndefined();
|
|
20
|
+
expect(handleValidation({ field_a: 0, field_b: 10 }).formErrors).toEqual({
|
|
21
|
+
field_b: 'Must be greater than field_a',
|
|
22
|
+
});
|
|
23
|
+
expect(handleValidation({ field_a: 'incorrect value' }).formErrors).toEqual({
|
|
24
|
+
field_a: 'The value must be a number',
|
|
25
|
+
});
|
|
26
|
+
expect(handleValidation({ field_a: 11 }).formErrors).toBeUndefined();
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
it('Native validations have higher precedence than jsonLogic validations', () => {
|
|
30
|
+
const { handleValidation } = createHeadlessForm(schemaWithNativeAndJSONLogicChecks, {
|
|
31
|
+
strictInputType: false,
|
|
32
|
+
});
|
|
33
|
+
expect(handleValidation({}).formErrors).toEqual({ field_a: 'Required field' });
|
|
34
|
+
expect(handleValidation({ field_a: 0 }).formErrors).toEqual({
|
|
35
|
+
field_a: 'Must be greater or equal to 100',
|
|
36
|
+
});
|
|
37
|
+
expect(handleValidation({ field_a: 101 }).formErrors).toEqual({
|
|
38
|
+
field_a: 'Must be a multiple of 10',
|
|
39
|
+
});
|
|
40
|
+
expect(handleValidation({ field_a: 110 }).formErrors).toBeUndefined();
|
|
41
|
+
});
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
describe('Relative: <, >, =', () => {
|
|
45
|
+
it('bigger: field_a > field_b', () => {
|
|
46
|
+
const schema = createSchemaWithRulesOnFieldA({
|
|
47
|
+
a_greater_than_b: {
|
|
48
|
+
errorMessage: 'Field A must be bigger than field B',
|
|
49
|
+
rule: { '>': [{ var: 'field_a' }, { var: 'field_b' }] },
|
|
50
|
+
},
|
|
51
|
+
});
|
|
52
|
+
const { handleValidation } = createHeadlessForm(schema, { strictInputType: false });
|
|
53
|
+
const { formErrors } = handleValidation({ field_a: 1, field_b: 2 });
|
|
54
|
+
expect(formErrors.field_a).toEqual('Field A must be bigger than field B');
|
|
55
|
+
expect(handleValidation({ field_a: 2, field_b: 0 }).formErrors).toEqual(undefined);
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
it('smaller: field_a < field_b', () => {
|
|
59
|
+
const schema = createSchemaWithRulesOnFieldA({
|
|
60
|
+
a_less_than_b: {
|
|
61
|
+
errorMessage: 'Field A must be smaller than field B',
|
|
62
|
+
rule: { '<': [{ var: 'field_a' }, { var: 'field_b' }] },
|
|
63
|
+
},
|
|
64
|
+
});
|
|
65
|
+
const { handleValidation } = createHeadlessForm(schema, { strictInputType: false });
|
|
66
|
+
const { formErrors } = handleValidation({ field_a: 2, field_b: 2 });
|
|
67
|
+
expect(formErrors.field_a).toEqual('Field A must be smaller than field B');
|
|
68
|
+
expect(handleValidation({ field_a: 0, field_b: 2 }).formErrors).toEqual(undefined);
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
it('equal: field_a = field_b', () => {
|
|
72
|
+
const schema = createSchemaWithRulesOnFieldA({
|
|
73
|
+
a_equals_b: {
|
|
74
|
+
errorMessage: 'Field A must equal field B',
|
|
75
|
+
rule: { '==': [{ var: 'field_a' }, { var: 'field_b' }] },
|
|
76
|
+
},
|
|
77
|
+
});
|
|
78
|
+
const { handleValidation } = createHeadlessForm(schema, { strictInputType: false });
|
|
79
|
+
const { formErrors } = handleValidation({ field_a: 3, field_b: 2 });
|
|
80
|
+
expect(formErrors.field_a).toEqual('Field A must equal field B');
|
|
81
|
+
expect(handleValidation({ field_a: 2, field_b: 2 }).formErrors).toEqual(undefined);
|
|
82
|
+
});
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
describe('Arithmetic: +, -, *, /', () => {
|
|
86
|
+
it('multiple: field_a > field_b * 2', () => {
|
|
87
|
+
const schema = createSchemaWithRulesOnFieldA({
|
|
88
|
+
a_greater_than_b_multiplied_by_2: {
|
|
89
|
+
errorMessage: 'Field A must be at least twice as big as field b',
|
|
90
|
+
rule: { '>': [{ var: 'field_a' }, { '*': [{ var: 'field_b' }, 2] }] },
|
|
91
|
+
},
|
|
92
|
+
});
|
|
93
|
+
const { handleValidation } = createHeadlessForm(schema, { strictInputType: false });
|
|
94
|
+
|
|
95
|
+
const { formErrors } = handleValidation({ field_a: 1, field_b: 4 });
|
|
96
|
+
expect(formErrors.field_a).toEqual('Field A must be at least twice as big as field b');
|
|
97
|
+
expect(handleValidation({ field_a: 3, field_b: 1 }).formErrors).toEqual(undefined);
|
|
98
|
+
});
|
|
99
|
+
|
|
100
|
+
it('divide: field_a > field_b / 2', () => {
|
|
101
|
+
const { handleValidation } = createHeadlessForm(
|
|
102
|
+
createSchemaWithRulesOnFieldA({
|
|
103
|
+
a_greater_than_b_divided_by_2: {
|
|
104
|
+
errorMessage: 'Field A must be greater than field_b / 2',
|
|
105
|
+
rule: { '>': [{ var: 'field_a' }, { '/': [{ var: 'field_b' }, 2] }] },
|
|
106
|
+
},
|
|
107
|
+
}),
|
|
108
|
+
{ strictInputType: false }
|
|
109
|
+
);
|
|
110
|
+
expect(handleValidation({ field_a: 2, field_b: 4 }).formErrors).toEqual({
|
|
111
|
+
field_a: 'Field A must be greater than field_b / 2',
|
|
112
|
+
});
|
|
113
|
+
expect(handleValidation({ field_a: 3, field_b: 5 }).formErrors).toEqual(undefined);
|
|
114
|
+
});
|
|
115
|
+
|
|
116
|
+
it('sum: field_a > field_b + field_c', () => {
|
|
117
|
+
const schema = createSchemaWithThreePropertiesWithRuleOnFieldA({
|
|
118
|
+
a_is_greater_than_b_plus_c: {
|
|
119
|
+
errorMessage: 'Field A must be greater than field_b and field_b added together',
|
|
120
|
+
rule: {
|
|
121
|
+
'>': [{ var: 'field_a' }, { '+': [{ var: 'field_b' }, { var: 'field_c' }] }],
|
|
122
|
+
},
|
|
123
|
+
},
|
|
124
|
+
});
|
|
125
|
+
const { handleValidation } = createHeadlessForm(schema, { strictInputType: false });
|
|
126
|
+
const { formErrors } = handleValidation({ field_a: 0, field_b: 1, field_c: 2 });
|
|
127
|
+
expect(formErrors.field_a).toEqual(
|
|
128
|
+
'Field A must be greater than field_b and field_b added together'
|
|
129
|
+
);
|
|
130
|
+
expect(handleValidation({ field_a: 4, field_b: 1, field_c: 2 }).formErrors).toEqual(
|
|
131
|
+
undefined
|
|
132
|
+
);
|
|
133
|
+
});
|
|
134
|
+
});
|
|
135
|
+
|
|
136
|
+
describe('Logical: ||, &&', () => {
|
|
137
|
+
it('AND: field_a > field_b && field_a > field_c (implicit with multiple rules in a single field)', () => {
|
|
138
|
+
const schema = createSchemaWithThreePropertiesWithRuleOnFieldA({
|
|
139
|
+
a_is_greater_than_b: {
|
|
140
|
+
errorMessage: 'Field A must be greater than field_b',
|
|
141
|
+
rule: {
|
|
142
|
+
'>': [{ var: 'field_a' }, { var: 'field_b' }],
|
|
143
|
+
},
|
|
144
|
+
},
|
|
145
|
+
a_is_greater_than_c: {
|
|
146
|
+
errorMessage: 'Field A must be greater than field_c',
|
|
147
|
+
rule: {
|
|
148
|
+
'>': [{ var: 'field_a' }, { var: 'field_c' }],
|
|
149
|
+
},
|
|
150
|
+
},
|
|
151
|
+
});
|
|
152
|
+
const { handleValidation } = createHeadlessForm(schema, { strictInputType: false });
|
|
153
|
+
expect(handleValidation({ field_a: 1, field_b: 10, field_c: 0 }).formErrors.field_a).toEqual(
|
|
154
|
+
'Field A must be greater than field_b'
|
|
155
|
+
);
|
|
156
|
+
expect(handleValidation({ field_a: 1, field_b: 0, field_c: 10 }).formErrors.field_a).toEqual(
|
|
157
|
+
'Field A must be greater than field_c'
|
|
158
|
+
);
|
|
159
|
+
expect(handleValidation({ field_a: 10, field_b: 5, field_c: 5 }).formErrors).toEqual(
|
|
160
|
+
undefined
|
|
161
|
+
);
|
|
162
|
+
});
|
|
163
|
+
|
|
164
|
+
it('OR: field_a > field_b or field_a > field_c', () => {
|
|
165
|
+
const schema = createSchemaWithThreePropertiesWithRuleOnFieldA({
|
|
166
|
+
field_a_is_greater_than_b_or_c: {
|
|
167
|
+
errorMessage: 'Field A must be greater than field_b or field_c',
|
|
168
|
+
rule: {
|
|
169
|
+
or: [
|
|
170
|
+
{ '>': [{ var: 'field_a' }, { var: 'field_b' }] },
|
|
171
|
+
{ '>': [{ var: 'field_a' }, { var: 'field_c' }] },
|
|
172
|
+
],
|
|
173
|
+
},
|
|
174
|
+
},
|
|
175
|
+
});
|
|
176
|
+
const { handleValidation } = createHeadlessForm(schema, { strictInputType: false });
|
|
177
|
+
expect(handleValidation({ field_a: 0, field_b: 10, field_c: 10 }).formErrors.field_a).toEqual(
|
|
178
|
+
'Field A must be greater than field_b or field_c'
|
|
179
|
+
);
|
|
180
|
+
expect(handleValidation({ field_a: 1, field_b: 0, field_c: 10 }).formErrors).toEqual(
|
|
181
|
+
undefined
|
|
182
|
+
);
|
|
183
|
+
expect(handleValidation({ field_a: 10, field_b: 5, field_c: 5 }).formErrors).toEqual(
|
|
184
|
+
undefined
|
|
185
|
+
);
|
|
186
|
+
});
|
|
187
|
+
});
|
|
188
|
+
|
|
189
|
+
describe('Multiple validations', () => {
|
|
190
|
+
it('two rules: A > B; A is even', () => {
|
|
191
|
+
const { handleValidation } = createHeadlessForm(multiRuleSchema, { strictInputType: false });
|
|
192
|
+
expect(handleValidation({ field_a: 1 }).formErrors).toEqual({
|
|
193
|
+
field_a: 'A must be even',
|
|
194
|
+
field_b: 'Required field',
|
|
195
|
+
});
|
|
196
|
+
expect(handleValidation({ field_a: 1, field_b: 2 }).formErrors).toEqual({
|
|
197
|
+
field_a: 'A must be bigger than B',
|
|
198
|
+
});
|
|
199
|
+
expect(handleValidation({ field_a: 3, field_b: 2 }).formErrors).toEqual({
|
|
200
|
+
field_a: 'A must be even',
|
|
201
|
+
});
|
|
202
|
+
expect(handleValidation({ field_a: 4, field_b: 2 }).formErrors).toEqual(undefined);
|
|
203
|
+
});
|
|
204
|
+
|
|
205
|
+
it('2 seperate fields with rules failing', () => {
|
|
206
|
+
const { handleValidation } = createHeadlessForm(schemaWithTwoRules, {
|
|
207
|
+
strictInputType: false,
|
|
208
|
+
});
|
|
209
|
+
expect(handleValidation({ field_a: 1, field_b: 3 }).formErrors).toEqual({
|
|
210
|
+
field_a: 'A must be bigger than B',
|
|
211
|
+
field_b: 'B must be even',
|
|
212
|
+
});
|
|
213
|
+
expect(handleValidation({ field_a: 4, field_b: 2 }).formErrors).toEqual(undefined);
|
|
214
|
+
});
|
|
215
|
+
});
|
|
216
|
+
|
|
217
|
+
describe('Derive values', () => {
|
|
218
|
+
it('field_b is field_a * 2', () => {
|
|
219
|
+
const { fields, handleValidation } = createHeadlessForm(schemaWithComputedAttributes, {
|
|
220
|
+
strictInputType: false,
|
|
221
|
+
initialValues: { field_a: 2 },
|
|
222
|
+
});
|
|
223
|
+
const fieldB = fields.find((i) => i.name === 'field_b');
|
|
224
|
+
expect(fieldB.default).toEqual(4);
|
|
225
|
+
expect(fieldB.value).toEqual(4);
|
|
226
|
+
handleValidation({ field_a: 4 });
|
|
227
|
+
expect(fieldB.default).toEqual(8);
|
|
228
|
+
});
|
|
229
|
+
});
|
|
230
|
+
});
|