@remoteoss/json-schema-form 0.4.4-dev.20230830125207 → 0.4.6-dev.20230911130751

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@remoteoss/json-schema-form",
3
- "version": "0.4.4-dev.20230830125207",
3
+ "version": "0.4.6-dev.20230911130751",
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"
@@ -0,0 +1,98 @@
1
+ import { createHeadlessForm } from '../createHeadlessForm';
2
+
3
+ describe('validations: const', () => {
4
+ it('Should work for number', () => {
5
+ const { handleValidation } = createHeadlessForm(
6
+ {
7
+ properties: {
8
+ ten_only: { type: 'number', const: 10 },
9
+ },
10
+ },
11
+ { strictInputType: false }
12
+ );
13
+ expect(handleValidation({}).formErrors).toEqual(undefined);
14
+ expect(handleValidation({ ten_only: 1 }).formErrors).toEqual({
15
+ ten_only: 'The only accepted value is 10.',
16
+ });
17
+ expect(handleValidation({ ten_only: 10 }).formErrors).toBeUndefined();
18
+ // null is also considered valid until we fix @BUG RMT-518
19
+ // Expectation: To fail with error "The only accepted value is 10."
20
+ expect(handleValidation({ ten_only: null }).formErrors).toBeUndefined();
21
+ });
22
+
23
+ it('Should work for text', () => {
24
+ const { handleValidation } = createHeadlessForm(
25
+ {
26
+ properties: {
27
+ hello_only: { type: 'string', const: 'hello' },
28
+ },
29
+ },
30
+ { strictInputType: false }
31
+ );
32
+ expect(handleValidation({}).formErrors).toEqual(undefined);
33
+ expect(handleValidation({ hello_only: 'what' }).formErrors).toEqual({
34
+ hello_only: 'The only accepted value is hello.',
35
+ });
36
+ expect(handleValidation({ hello_only: 'hello' }).formErrors).toEqual(undefined);
37
+ });
38
+
39
+ it('Should work for a conditionally applied const', () => {
40
+ const { handleValidation } = createHeadlessForm(
41
+ {
42
+ properties: {
43
+ answer: { type: 'string', oneOf: [{ const: 'yes' }, { const: 'no' }] },
44
+ amount: {
45
+ description: 'If you select yes, this needs to be exactly 10.',
46
+ type: 'number',
47
+ },
48
+ },
49
+ allOf: [
50
+ {
51
+ if: { properties: { answer: { const: 'yes' } }, required: ['answer'] },
52
+ then: { properties: { amount: { const: 10 } }, required: ['amount'] },
53
+ },
54
+ ],
55
+ },
56
+ { strictInputType: false }
57
+ );
58
+ expect(handleValidation({}).formErrors).toEqual(undefined);
59
+ expect(handleValidation({ answer: 'no' }).formErrors).toEqual(undefined);
60
+ expect(handleValidation({ answer: 'yes' }).formErrors).toEqual({ amount: 'Required field' });
61
+ expect(handleValidation({ answer: 'yes', amount: 1 }).formErrors).toEqual({
62
+ amount: 'The only accepted value is 10.',
63
+ });
64
+ expect(handleValidation({ answer: 'yes', amount: 10 }).formErrors).toEqual(undefined);
65
+ });
66
+
67
+ it('Should show the custom error message', () => {
68
+ const { handleValidation } = createHeadlessForm(
69
+ {
70
+ properties: {
71
+ string: {
72
+ type: 'string',
73
+ const: 'hello',
74
+ 'x-jsf-errorMessage': { const: 'You must say hello!!!' },
75
+ },
76
+ },
77
+ },
78
+ { strictInputType: false }
79
+ );
80
+ expect(handleValidation({}).formErrors).toEqual(undefined);
81
+ expect(handleValidation({ string: 'hi' }).formErrors).toEqual({
82
+ string: 'You must say hello!!!',
83
+ });
84
+ expect(handleValidation({ string: 'hello' }).formErrors).toEqual(undefined);
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
+ });
98
+ });
@@ -207,7 +207,6 @@ export const schemaInputTypeHidden = {
207
207
  title: 'Select multi hidden',
208
208
  default: ['Albania, Algeria'],
209
209
  'x-jsf-presentation': { inputType: 'hidden' },
210
- const: ['Albania, Algeria'],
211
210
  type: 'array',
212
211
  },
213
212
  },
@@ -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
+ });