@remoteoss/json-schema-form 0.4.3-beta.0 → 0.4.4-dev.20230829101351

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.
@@ -0,0 +1,814 @@
1
+ import { createHeadlessForm } from '../createHeadlessForm';
2
+
3
+ import {
4
+ createSchemaWithRulesOnFieldA,
5
+ multiRuleSchema,
6
+ schemaWithComputedAttributesAndErrorMessages,
7
+ schemaWithNonRequiredField,
8
+ aConditionallyAppliedComputedAttributeMinimumAndMaximum,
9
+ aConditionallyAppliedComputedAttributeValue,
10
+ createSchemaWithThreePropertiesWithRuleOnFieldA,
11
+ fieldsetWithAConditionalToApplyExtraValidations,
12
+ fieldsetWithComputedAttributes,
13
+ ifConditionWithMissingComputedValue,
14
+ ifConditionWithMissingValidation,
15
+ nestedFieldsetWithValidationSchema,
16
+ schemaSelfContainedValueForMaximumMinimumValues,
17
+ schemaSelfContainedValueForTitleWithNoTemplate,
18
+ schemaWhereValidationAndComputedValueIsAppliedOnNormalThenStatement,
19
+ schemaWithChecksAndThenValidationsOnThen,
20
+ schemaWithComputedAttributeThatDoesntExist,
21
+ schemaWithComputedAttributeThatDoesntExistDescription,
22
+ schemaWithComputedAttributeThatDoesntExistTitle,
23
+ schemaWithComputedAttributes,
24
+ schemaWithComputedValueChecksInIf,
25
+ schemaWithDeepVarThatDoesNotExist,
26
+ schemaWithDeepVarThatDoesNotExistOnFieldset,
27
+ schemaWithGreaterThanChecksForThreeFields,
28
+ schemaWithIfStatementWithComputedValuesAndValidationChecks,
29
+ schemaWithInlineMultipleRulesForComputedAttributes,
30
+ schemaWithInlineRuleForComputedAttributeInConditionallyAppliedSchema,
31
+ schemaWithInlineRuleForComputedAttributeWithCopy,
32
+ schemaWithInlinedRuleOnComputedAttributeThatReferencesUnknownVar,
33
+ schemaWithJSFLogicAndInlineRule,
34
+ schemaWithMissingComputedValue,
35
+ schemaWithMissingRule,
36
+ schemaWithMissingValueInlineRule,
37
+ schemaWithMultipleComputedValueChecks,
38
+ schemaWithNativeAndJSONLogicChecks,
39
+ schemaWithPropertiesCheckAndValidationsInAIf,
40
+ schemaWithPropertyThatDoesNotExistInThatLevelButDoesInFieldset,
41
+ schemaWithTwoRules,
42
+ schemaWithTwoValidationsWhereOneOfThemIsAppliedConditionally,
43
+ schemaWithValidationThatDoesNotExistOnProperty,
44
+ schemaWithVarThatDoesNotExist,
45
+ simpleArrayValidationSchema,
46
+ twoLevelsOfJSONLogicSchema,
47
+ validatingASingleItemInTheArray,
48
+ validatingTwoNestedFieldsSchema,
49
+ } from './jsonLogicFixtures';
50
+
51
+ describe('cross-value validations', () => {
52
+ describe('Does not conflict with native JSON schema', () => {
53
+ it('When a field is not required, validations should not block submitting when its an empty value', () => {
54
+ const { handleValidation } = createHeadlessForm(schemaWithNonRequiredField, {
55
+ strictInputType: false,
56
+ });
57
+ expect(handleValidation({}).formErrors).toEqual(undefined);
58
+ expect(handleValidation({ field_a: 0 }).formErrors).toEqual({
59
+ field_a: 'Must be greater than 10',
60
+ });
61
+ expect(handleValidation({ field_a: 'incorrect value' }).formErrors).toEqual({
62
+ field_a: 'The value must be a number',
63
+ });
64
+ expect(handleValidation({ field_a: 11 }).formErrors).toEqual(undefined);
65
+ });
66
+
67
+ it('Native validations always appear first', () => {
68
+ const { handleValidation } = createHeadlessForm(schemaWithNativeAndJSONLogicChecks, {
69
+ strictInputType: false,
70
+ });
71
+ expect(handleValidation({}).formErrors).toEqual({ field_a: 'Required field' });
72
+ expect(handleValidation({ field_a: 0 }).formErrors).toEqual({
73
+ field_a: 'Must be greater or equal to 5',
74
+ });
75
+ expect(handleValidation({ field_a: 5 }).formErrors).toEqual({
76
+ field_a: 'Must be greater than 10',
77
+ });
78
+ });
79
+ });
80
+
81
+ describe('Relative: <, >, =', () => {
82
+ it('bigger: field_a > field_b', () => {
83
+ const schema = createSchemaWithRulesOnFieldA({
84
+ a_greater_than_b: {
85
+ errorMessage: 'Field A must be bigger than field B',
86
+ rule: { '>': [{ var: 'field_a' }, { var: 'field_b' }] },
87
+ },
88
+ });
89
+ const { handleValidation } = createHeadlessForm(schema, { strictInputType: false });
90
+ const { formErrors } = handleValidation({ field_a: 1, field_b: 2 });
91
+ expect(formErrors.field_a).toEqual('Field A must be bigger than field B');
92
+ expect(handleValidation({ field_a: 2, field_b: 0 }).formErrors).toEqual(undefined);
93
+ });
94
+
95
+ it('smaller: field_a < field_b', () => {
96
+ const schema = createSchemaWithRulesOnFieldA({
97
+ a_less_than_b: {
98
+ errorMessage: 'Field A must be smaller than field B',
99
+ rule: { '<': [{ var: 'field_a' }, { var: 'field_b' }] },
100
+ },
101
+ });
102
+ const { handleValidation } = createHeadlessForm(schema, { strictInputType: false });
103
+ const { formErrors } = handleValidation({ field_a: 2, field_b: 2 });
104
+ expect(formErrors.field_a).toEqual('Field A must be smaller than field B');
105
+ expect(handleValidation({ field_a: 0, field_b: 2 }).formErrors).toEqual(undefined);
106
+ });
107
+
108
+ it('equal: field_a = field_b', () => {
109
+ const schema = createSchemaWithRulesOnFieldA({
110
+ a_equals_b: {
111
+ errorMessage: 'Field A must equal field B',
112
+ rule: { '==': [{ var: 'field_a' }, { var: 'field_b' }] },
113
+ },
114
+ });
115
+ const { handleValidation } = createHeadlessForm(schema, { strictInputType: false });
116
+ const { formErrors } = handleValidation({ field_a: 3, field_b: 2 });
117
+ expect(formErrors.field_a).toEqual('Field A must equal field B');
118
+ expect(handleValidation({ field_a: 2, field_b: 2 }).formErrors).toEqual(undefined);
119
+ });
120
+ });
121
+
122
+ describe('Incorrectly written schemas', () => {
123
+ beforeEach(() => {
124
+ jest.spyOn(console, 'error').mockImplementation(() => {});
125
+ });
126
+
127
+ afterEach(() => {
128
+ console.error.mockRestore();
129
+ });
130
+
131
+ it('Should throw when theres a missing rule', () => {
132
+ createHeadlessForm(schemaWithMissingRule, { strictInputType: false });
133
+ expect(console.error).toHaveBeenCalledWith(
134
+ 'JSON Schema invalid!',
135
+ Error('Missing rule for validation with id of: "a_greater_than_ten".')
136
+ );
137
+ });
138
+
139
+ it('Should throw when theres a missing computed value', () => {
140
+ createHeadlessForm(schemaWithMissingComputedValue, { strictInputType: false });
141
+ expect(console.error).toHaveBeenCalledWith(
142
+ 'JSON Schema invalid!',
143
+ Error('Missing rule for computedValue with id of: "a_plus_ten".')
144
+ );
145
+ });
146
+
147
+ it('Should throw when a var does not exist in a rule.', () => {
148
+ createHeadlessForm(schemaWithVarThatDoesNotExist, { strictInputType: false });
149
+ expect(console.error).toHaveBeenCalledWith(
150
+ 'JSON Schema invalid!',
151
+ Error('"field_b" in rule "a_greater_than_ten" does not exist as a JSON schema property.')
152
+ );
153
+ });
154
+
155
+ it('Should throw when theres an inline computed ruleset with no value.', () => {
156
+ createHeadlessForm(schemaWithMissingValueInlineRule, { strictInputType: false });
157
+ expect(console.error).toHaveBeenCalledWith(
158
+ 'JSON Schema invalid!',
159
+ Error('Cannot define multiple rules without a template string with key `value`.')
160
+ );
161
+ });
162
+
163
+ it('On x-jsf-logic-computedAttrs, error if theres a value that does not exist.', () => {
164
+ createHeadlessForm(schemaWithComputedAttributeThatDoesntExist, {
165
+ strictInputType: false,
166
+ });
167
+ expect(console.error).toHaveBeenCalledWith(
168
+ 'JSON Schema invalid!',
169
+ Error(`"iDontExist" computedValue in field "field_a" doesn't exist.`)
170
+ );
171
+ });
172
+
173
+ it('On x-jsf-logic-computedAttrs, error if theres a value that does not exist on a title.', () => {
174
+ createHeadlessForm(schemaWithComputedAttributeThatDoesntExistTitle, {
175
+ strictInputType: false,
176
+ });
177
+ expect(console.error).toHaveBeenCalledWith(
178
+ 'JSON Schema invalid!',
179
+ Error(`"iDontExist" computedValue in field "field_a" doesn't exist.`)
180
+ );
181
+ });
182
+
183
+ it('On x-jsf-logic-computedAttrs, error if theres a value that does not exist on a description.', () => {
184
+ createHeadlessForm(schemaWithComputedAttributeThatDoesntExistDescription, {
185
+ strictInputType: false,
186
+ });
187
+ expect(console.error).toHaveBeenCalledWith(
188
+ 'JSON Schema invalid!',
189
+ Error(`"iDontExist" computedValue in field "field_a" doesn't exist.`)
190
+ );
191
+ });
192
+
193
+ it('Should throw when a var does not exist in a deeply nested rule', () => {
194
+ createHeadlessForm(schemaWithDeepVarThatDoesNotExist, { strictInputType: false });
195
+ expect(console.error).toHaveBeenCalledWith(
196
+ 'JSON Schema invalid!',
197
+ Error('"field_b" in rule "a_greater_than_ten" does not exist as a JSON schema property.')
198
+ );
199
+ });
200
+
201
+ it('Should throw when a var does not exist in a fieldset.', () => {
202
+ createHeadlessForm(schemaWithDeepVarThatDoesNotExistOnFieldset, { strictInputType: false });
203
+ expect(console.error).toHaveBeenCalledWith(
204
+ 'JSON Schema invalid!',
205
+ Error('"field_a" in rule "a_greater_than_ten" does not exist as a JSON schema property.')
206
+ );
207
+ });
208
+
209
+ it('On a property, it should throw an error for a requiredValidation that does not exist', () => {
210
+ createHeadlessForm(schemaWithValidationThatDoesNotExistOnProperty, {
211
+ strictInputType: false,
212
+ });
213
+ expect(console.error).toHaveBeenCalledWith(
214
+ 'JSON Schema invalid!',
215
+ Error(`Validation "iDontExist" required for "field_a" doesn't exist.`)
216
+ );
217
+ });
218
+
219
+ it('A top level logic keyword will not be able to reference fieldset properties', () => {
220
+ createHeadlessForm(schemaWithPropertyThatDoesNotExistInThatLevelButDoesInFieldset, {
221
+ strictInputType: false,
222
+ });
223
+ expect(console.error).toHaveBeenCalledWith(
224
+ 'JSON Schema invalid!',
225
+ Error('"child" in rule "validation_parent" does not exist as a JSON schema property.')
226
+ );
227
+ });
228
+
229
+ it('Error for a missing computed value in an if', () => {
230
+ createHeadlessForm(ifConditionWithMissingComputedValue, {
231
+ strictInputType: false,
232
+ });
233
+ expect(console.error).toHaveBeenCalledWith(
234
+ 'JSON Schema invalid!',
235
+ Error(`"iDontExist" computedValue in if condition doesn't exist.`)
236
+ );
237
+ });
238
+
239
+ it('Error for a missing validation in an if', () => {
240
+ createHeadlessForm(ifConditionWithMissingValidation, {
241
+ strictInputType: false,
242
+ });
243
+ expect(console.error).toHaveBeenCalledWith(
244
+ 'JSON Schema invalid!',
245
+ Error(`"iDontExist" validation in if condition doesn't exist.`)
246
+ );
247
+ });
248
+
249
+ it('On an inline rule for a computedAttribute, error if theres a value referenced that does not exist', () => {
250
+ createHeadlessForm(schemaWithInlinedRuleOnComputedAttributeThatReferencesUnknownVar, {
251
+ strictInputType: false,
252
+ });
253
+ expect(console.error).toHaveBeenCalledWith(
254
+ 'JSON Schema invalid!',
255
+ Error(
256
+ '"IdontExist" in inline rule in property "field_a.x-jsf-logic-computedAttrs.title" does not exist as a JSON schema property.'
257
+ )
258
+ );
259
+ });
260
+ });
261
+
262
+ describe('Arithmetic: +, -, *, /', () => {
263
+ it('multiple: field_a > field_b * 2', () => {
264
+ const schema = createSchemaWithRulesOnFieldA({
265
+ a_greater_than_b_multiplied_by_2: {
266
+ errorMessage: 'Field A must be at least twice as big as field b',
267
+ rule: { '>': [{ var: 'field_a' }, { '*': [{ var: 'field_b' }, 2] }] },
268
+ },
269
+ });
270
+ const { handleValidation } = createHeadlessForm(schema, { strictInputType: false });
271
+
272
+ const { formErrors } = handleValidation({ field_a: 1, field_b: 4 });
273
+ expect(formErrors.field_a).toEqual('Field A must be at least twice as big as field b');
274
+ expect(handleValidation({ field_a: 3, field_b: 1 }).formErrors).toEqual(undefined);
275
+ });
276
+
277
+ it('divide: field_a > field_b / 2', () => {
278
+ const { handleValidation } = createHeadlessForm(
279
+ createSchemaWithRulesOnFieldA({
280
+ a_greater_than_b_divided_by_2: {
281
+ errorMessage: 'Field A must be greater than field_b / 2',
282
+ rule: { '>': [{ var: 'field_a' }, { '/': [{ var: 'field_b' }, 2] }] },
283
+ },
284
+ }),
285
+ { strictInputType: false }
286
+ );
287
+ const { formErrors } = handleValidation({ field_a: 2, field_b: 4 });
288
+ expect(formErrors.field_a).toEqual('Field A must be greater than field_b / 2');
289
+ expect(handleValidation({ field_a: 3, field_b: 5 }).formErrors).toEqual(undefined);
290
+ });
291
+
292
+ it('sum: field_a > field_b + field_c', () => {
293
+ const schema = createSchemaWithThreePropertiesWithRuleOnFieldA({
294
+ a_is_greater_than_b_plus_c: {
295
+ errorMessage: 'Field A must be greater than field_b and field_b added together',
296
+ rule: {
297
+ '>': [{ var: 'field_a' }, { '+': [{ var: 'field_b' }, { var: 'field_c' }] }],
298
+ },
299
+ },
300
+ });
301
+ const { handleValidation } = createHeadlessForm(schema, { strictInputType: false });
302
+ const { formErrors } = handleValidation({ field_a: 0, field_b: 1, field_c: 2 });
303
+ expect(formErrors.field_a).toEqual(
304
+ 'Field A must be greater than field_b and field_b added together'
305
+ );
306
+ expect(handleValidation({ field_a: 4, field_b: 1, field_c: 2 }).formErrors).toEqual(
307
+ undefined
308
+ );
309
+ });
310
+ });
311
+
312
+ describe('Logical: ||, &&', () => {
313
+ it('AND: field_a > field_b && field_a > field_c (implicit with multiple rules in a single field)', () => {
314
+ const schema = createSchemaWithThreePropertiesWithRuleOnFieldA({
315
+ a_is_greater_than_b: {
316
+ errorMessage: 'Field A must be greater than field_b',
317
+ rule: {
318
+ '>': [{ var: 'field_a' }, { var: 'field_b' }],
319
+ },
320
+ },
321
+ a_is_greater_than_c: {
322
+ errorMessage: 'Field A must be greater than field_c',
323
+ rule: {
324
+ '>': [{ var: 'field_a' }, { var: 'field_c' }],
325
+ },
326
+ },
327
+ });
328
+ const { handleValidation } = createHeadlessForm(schema, { strictInputType: false });
329
+ expect(handleValidation({ field_a: 1, field_b: 10, field_c: 0 }).formErrors.field_a).toEqual(
330
+ 'Field A must be greater than field_b'
331
+ );
332
+ expect(handleValidation({ field_a: 1, field_b: 0, field_c: 10 }).formErrors.field_a).toEqual(
333
+ 'Field A must be greater than field_c'
334
+ );
335
+ expect(handleValidation({ field_a: 10, field_b: 5, field_c: 5 }).formErrors).toEqual(
336
+ undefined
337
+ );
338
+ });
339
+
340
+ it('OR: field_a > field_b or field_a > field_c', () => {
341
+ const schema = createSchemaWithThreePropertiesWithRuleOnFieldA({
342
+ field_a_is_greater_than_b_or_c: {
343
+ errorMessage: 'Field A must be greater than field_b or field_c',
344
+ rule: {
345
+ or: [
346
+ { '>': [{ var: 'field_a' }, { var: 'field_b' }] },
347
+ { '>': [{ var: 'field_a' }, { var: 'field_c' }] },
348
+ ],
349
+ },
350
+ },
351
+ });
352
+ const { handleValidation } = createHeadlessForm(schema, { strictInputType: false });
353
+ expect(handleValidation({ field_a: 0, field_b: 10, field_c: 10 }).formErrors.field_a).toEqual(
354
+ 'Field A must be greater than field_b or field_c'
355
+ );
356
+ expect(handleValidation({ field_a: 1, field_b: 0, field_c: 10 }).formErrors).toEqual(
357
+ undefined
358
+ );
359
+ expect(handleValidation({ field_a: 10, field_b: 5, field_c: 5 }).formErrors).toEqual(
360
+ undefined
361
+ );
362
+ });
363
+ });
364
+
365
+ describe('Conditionals', () => {
366
+ it('when field_a > field_b, show field_c', () => {
367
+ const { fields, handleValidation } = createHeadlessForm(
368
+ schemaWithGreaterThanChecksForThreeFields,
369
+ { strictInputType: false }
370
+ );
371
+ expect(fields.find((i) => i.name === 'field_c').isVisible).toEqual(false);
372
+
373
+ expect(handleValidation({ field_a: 1, field_b: 3 }).formErrors).toEqual(undefined);
374
+ expect(handleValidation({ field_a: 1, field_b: null }).formErrors).toEqual({
375
+ field_b: 'Required field',
376
+ });
377
+ expect(handleValidation({ field_a: 10, field_b: 3 }).formErrors).toEqual({
378
+ field_c: 'Required field',
379
+ });
380
+ expect(handleValidation({ field_a: 10, field_b: 3, field_c: 0 }).formErrors).toEqual(
381
+ undefined
382
+ );
383
+ });
384
+
385
+ it('A schema with both a `x-jsf-validations` and `properties` check', () => {
386
+ const { handleValidation } = createHeadlessForm(
387
+ schemaWithPropertiesCheckAndValidationsInAIf,
388
+ { strictInputType: false }
389
+ );
390
+ expect(handleValidation({ field_a: 1, field_b: 3 }).formErrors).toEqual(undefined);
391
+ expect(handleValidation({ field_a: 10, field_b: 3 }).formErrors).toEqual({
392
+ field_c: 'Required field',
393
+ });
394
+ expect(handleValidation({ field_a: 5, field_b: 3 }).formErrors).toEqual(undefined);
395
+ });
396
+
397
+ it('Conditionally apply a validation on a property depending on values', () => {
398
+ const { fields, handleValidation } = createHeadlessForm(
399
+ schemaWithChecksAndThenValidationsOnThen,
400
+ { strictInputType: false }
401
+ );
402
+ const cField = fields.find((i) => i.name === 'field_c');
403
+ expect(cField.isVisible).toEqual(false);
404
+ expect(cField.description).toEqual(undefined);
405
+ expect(handleValidation({ field_a: 10, field_b: 5 }).formErrors).toEqual({
406
+ field_c: 'Required field',
407
+ });
408
+ expect(handleValidation({ field_a: 10, field_b: 5, field_c: 0 }).formErrors).toEqual({
409
+ field_c: 'Needs more numbers',
410
+ });
411
+ expect(cField.description).toBe('I am a description!');
412
+ expect(handleValidation({ field_a: 10, field_b: 5, field_c: 201 }).formErrors).toEqual(
413
+ undefined
414
+ );
415
+ });
416
+
417
+ it('Should apply a conditional based on a true computedValue', () => {
418
+ const { fields, handleValidation } = createHeadlessForm(schemaWithComputedValueChecksInIf, {
419
+ strictInputType: false,
420
+ });
421
+ const cField = fields.find((i) => i.name === 'field_c');
422
+ expect(cField.isVisible).toEqual(false);
423
+ expect(cField.description).toEqual(undefined);
424
+ expect(handleValidation({ field_a: 10, field_b: 5 }).formErrors).toEqual({
425
+ field_c: 'Required field',
426
+ });
427
+ expect(handleValidation({ field_a: 10, field_b: 5, field_c: 201 }).formErrors).toEqual(
428
+ undefined
429
+ );
430
+ });
431
+
432
+ it('Handle multiple computedValue checks by ANDing them together', () => {
433
+ const { handleValidation } = createHeadlessForm(schemaWithMultipleComputedValueChecks, {
434
+ strictInputType: false,
435
+ });
436
+ expect(handleValidation({}).formErrors).toEqual({
437
+ field_a: 'Required field',
438
+ field_b: 'Required field',
439
+ });
440
+ expect(handleValidation({ field_a: 10, field_b: 8 }).formErrors).toEqual({
441
+ field_c: 'Required field',
442
+ });
443
+ expect(handleValidation({ field_a: 10, field_b: 8, field_c: 0 }).formErrors).toEqual({
444
+ field_c: 'Must be two times B',
445
+ });
446
+ expect(handleValidation({ field_a: 10, field_b: 8, field_c: 17 }).formErrors).toEqual(
447
+ undefined
448
+ );
449
+ });
450
+
451
+ it('Handle having a true condition with both validations and computedValue checks', () => {
452
+ const { handleValidation } = createHeadlessForm(
453
+ schemaWithIfStatementWithComputedValuesAndValidationChecks,
454
+ { strictInputType: false }
455
+ );
456
+ expect(handleValidation({ field_a: 1, field_b: 1 }).formErrors).toEqual(undefined);
457
+ expect(handleValidation({ field_a: 10, field_b: 20 }).formErrors).toEqual(undefined);
458
+ expect(handleValidation({ field_a: 10, field_b: 9 }).formErrors).toEqual({
459
+ field_c: 'Required field',
460
+ });
461
+ expect(handleValidation({ field_a: 10, field_b: 9, field_c: 10 }).formErrors).toEqual(
462
+ undefined
463
+ );
464
+ });
465
+
466
+ it('Apply validations and computed values on normal if statement.', () => {
467
+ const { fields, handleValidation } = createHeadlessForm(
468
+ schemaWhereValidationAndComputedValueIsAppliedOnNormalThenStatement,
469
+ { strictInputType: false }
470
+ );
471
+ expect(handleValidation({ field_a: 0, field_b: 0 }).formErrors).toEqual(undefined);
472
+ expect(handleValidation({ field_a: 20, field_b: 0 }).formErrors).toEqual({
473
+ field_b: 'Must be greater than Field A + 10',
474
+ });
475
+ const [, fieldB] = fields;
476
+ expect(fieldB.label).toEqual('Must be greater than 30.');
477
+ expect(handleValidation({ field_a: 20, field_b: 31 }).formErrors).toEqual(undefined);
478
+ });
479
+
480
+ it('When we have a required validation on a top level property and another validation is added, both should be accounted for.', () => {
481
+ const { handleValidation } = createHeadlessForm(
482
+ schemaWithTwoValidationsWhereOneOfThemIsAppliedConditionally,
483
+ { strictInputType: false }
484
+ );
485
+ expect(handleValidation({ field_a: 10, field_b: 0 }).formErrors).toEqual({
486
+ field_b: 'Must be greater than A',
487
+ });
488
+ expect(handleValidation({ field_a: 10, field_b: 20 }).formErrors).toEqual(undefined);
489
+ expect(handleValidation({ field_a: 20, field_b: 10 }).formErrors).toEqual({
490
+ field_b: 'Must be greater than A',
491
+ });
492
+ expect(handleValidation({ field_a: 20, field_b: 21 }).formErrors).toEqual({
493
+ field_b: 'Must be greater than two times A',
494
+ });
495
+ expect(handleValidation({ field_a: 20, field_b: 41 }).formErrors).toEqual();
496
+ });
497
+ });
498
+
499
+ describe('Multiple validations', () => {
500
+ it('2 rules where A must be bigger than B and not an even number in another rule', () => {
501
+ const { handleValidation } = createHeadlessForm(multiRuleSchema, { strictInputType: false });
502
+ expect(handleValidation({ field_a: 1 }).formErrors).toEqual({
503
+ field_a: 'A must be even',
504
+ field_b: 'Required field',
505
+ });
506
+ expect(handleValidation({ field_a: 1, field_b: 2 }).formErrors).toEqual({
507
+ field_a: 'A must be bigger than B',
508
+ });
509
+ expect(handleValidation({ field_a: 3, field_b: 2 }).formErrors).toEqual({
510
+ field_a: 'A must be even',
511
+ });
512
+ expect(handleValidation({ field_a: 4, field_b: 2 }).formErrors).toEqual(undefined);
513
+ });
514
+
515
+ it('2 seperate fields with rules failing', () => {
516
+ const { handleValidation } = createHeadlessForm(schemaWithTwoRules, {
517
+ strictInputType: false,
518
+ });
519
+ expect(handleValidation({ field_a: 1, field_b: 3 }).formErrors).toEqual({
520
+ field_a: 'A must be bigger than B',
521
+ field_b: 'B must be even',
522
+ });
523
+ expect(handleValidation({ field_a: 4, field_b: 2 }).formErrors).toEqual(undefined);
524
+ });
525
+ });
526
+
527
+ describe('Derive values', () => {
528
+ it('field_b is field_a * 2', () => {
529
+ const { fields } = createHeadlessForm(schemaWithComputedAttributes, {
530
+ strictInputType: false,
531
+ initialValues: { field_a: 2 },
532
+ });
533
+ const fieldB = fields.find((i) => i.name === 'field_b');
534
+ expect(fieldB.description).toEqual(
535
+ 'This field is 2 times bigger than field_a with value of 4.'
536
+ );
537
+ expect(fieldB.default).toEqual(4);
538
+ expect(fieldB.value).toEqual(4);
539
+ expect(fieldB.label).toEqual('This is 4!');
540
+ });
541
+
542
+ it('Derived errorMessages and statements work', () => {
543
+ const { fields, handleValidation } = createHeadlessForm(
544
+ schemaWithComputedAttributesAndErrorMessages,
545
+ { strictInputType: false }
546
+ );
547
+ const fieldB = fields.find((i) => i.name === 'field_b');
548
+ expect(handleValidation({ field_a: 2, field_b: 0 }).formErrors).toEqual({
549
+ field_b: 'Must be bigger than 4',
550
+ });
551
+ expect(handleValidation({ field_a: 2, field_b: 100 }).formErrors).toEqual({
552
+ field_b: 'Must be smaller than 8',
553
+ });
554
+ expect(fieldB.minimum).toEqual(4);
555
+ expect(fieldB.maximum).toEqual(8);
556
+ expect(fieldB.statement).toEqual({ description: 'Must be bigger than 4 and smaller than 8' });
557
+ });
558
+
559
+ it('computedAttribute test that minimum, maximum, errorMessages.minimum, errorMessage.maximum is working', () => {
560
+ const { handleValidation } = createHeadlessForm(
561
+ aConditionallyAppliedComputedAttributeMinimumAndMaximum,
562
+ {
563
+ strictInputType: false,
564
+ }
565
+ );
566
+ expect(handleValidation({ field_a: 20, field_b: 1 }).formErrors).toEqual({
567
+ field_b: 'use 10 or more',
568
+ });
569
+ expect(handleValidation({ field_a: 20, field_b: 60 }).formErrors).toEqual({
570
+ field_b: 'use less than 40',
571
+ });
572
+ expect(handleValidation({ field_a: 20, field_b: 30 }).formErrors).toEqual(undefined);
573
+ });
574
+
575
+ it('Apply a conditional computed attribute value', () => {
576
+ const { fields, handleValidation } = createHeadlessForm(
577
+ aConditionallyAppliedComputedAttributeValue,
578
+ {
579
+ strictInputType: false,
580
+ }
581
+ );
582
+
583
+ expect(handleValidation({ field_a: 20, field_b: 1 }).formErrors).toEqual({
584
+ field_b: 'The only accepted value is 10.',
585
+ });
586
+
587
+ const [, fieldB] = fields;
588
+ expect(fieldB.value).toEqual(10);
589
+ expect(handleValidation({ field_a: 10, field_b: 1 }).formErrors).toEqual();
590
+ expect(fieldB.value).toEqual(undefined);
591
+ });
592
+
593
+ it('Use a self contained rule in a schema for a title attribute', () => {
594
+ const { fields, handleValidation } = createHeadlessForm(
595
+ schemaWithInlineRuleForComputedAttributeWithCopy,
596
+ {
597
+ strictInputType: false,
598
+ }
599
+ );
600
+ const [, fieldB] = fields;
601
+ expect(handleValidation({ field_a: 0, field_b: null }).formErrors).toEqual(undefined);
602
+ expect(fieldB.label).toEqual('I need this to work using the 10.');
603
+ expect(handleValidation({ field_a: 10 }).formErrors).toEqual(undefined);
604
+ expect(fieldB.label).toEqual('I need this to work using the 20.');
605
+ });
606
+
607
+ it('Use multiple inline rules with different identifiers', () => {
608
+ const { fields, handleValidation } = createHeadlessForm(
609
+ schemaWithInlineMultipleRulesForComputedAttributes,
610
+ {
611
+ strictInputType: false,
612
+ }
613
+ );
614
+ const [, fieldB] = fields;
615
+ expect(handleValidation({ field_a: 10, field_b: null }).formErrors).toEqual(undefined);
616
+ expect(fieldB.description).toEqual('Must be between 5 and 20.');
617
+ });
618
+
619
+ it('Use a self contained rule in a schema for a title but it just uses the value', () => {
620
+ const { fields, handleValidation } = createHeadlessForm(
621
+ schemaSelfContainedValueForTitleWithNoTemplate,
622
+ {
623
+ strictInputType: false,
624
+ }
625
+ );
626
+ const [, fieldB] = fields;
627
+ expect(handleValidation({ field_a: 10, field_b: null }).formErrors).toEqual(undefined);
628
+ expect(fieldB.label).toEqual('20');
629
+ });
630
+
631
+ it('Use a self contained rule for a minimum, maximum value', () => {
632
+ const { handleValidation } = createHeadlessForm(
633
+ schemaSelfContainedValueForMaximumMinimumValues,
634
+ {
635
+ strictInputType: false,
636
+ }
637
+ );
638
+ expect(handleValidation({ field_a: 10, field_b: null }).formErrors).toEqual(undefined);
639
+ expect(handleValidation({ field_a: 50, field_b: 20 }).formErrors).toEqual({
640
+ field_b: 'Must be greater or equal to 40',
641
+ });
642
+ expect(handleValidation({ field_a: 50, field_b: 70 }).formErrors).toEqual({
643
+ field_b: 'Must be smaller or equal to 60',
644
+ });
645
+ expect(handleValidation({ field_a: 50, field_b: 50 }).formErrors).toEqual(undefined);
646
+ });
647
+
648
+ it('Use a self contained rule for a conditionally applied schema', () => {
649
+ const { fields, handleValidation } = createHeadlessForm(
650
+ schemaWithInlineRuleForComputedAttributeInConditionallyAppliedSchema,
651
+ {
652
+ strictInputType: false,
653
+ }
654
+ );
655
+ const [, fieldB] = fields;
656
+ expect(fieldB.description).toEqual('Hello world');
657
+ handleValidation({ field_a: 20, field_b: 0 });
658
+ expect(fieldB.description).toEqual('Must be between 10 and 40.');
659
+ });
660
+
661
+ it('Mix use of multiple inline rules and an external rule', () => {
662
+ const { fields, handleValidation } = createHeadlessForm(schemaWithJSFLogicAndInlineRule, {
663
+ strictInputType: false,
664
+ });
665
+ handleValidation({ field_a: 10 });
666
+ const [, fieldB] = fields;
667
+ expect(fieldB.label).toEqual('Going to use 20 and 4');
668
+ });
669
+ });
670
+
671
+ describe('Nested fieldsets', () => {
672
+ it('Basic nested validation works', () => {
673
+ const { handleValidation } = createHeadlessForm(nestedFieldsetWithValidationSchema, {
674
+ strictInputType: false,
675
+ });
676
+ expect(handleValidation({}).formErrors).toEqual({ field_a: { child: 'Required field' } });
677
+ expect(handleValidation({ field_a: { child: 0 } }).formErrors).toEqual({
678
+ field_a: { child: 'Must be greater than 10!' },
679
+ });
680
+ expect(handleValidation({ field_a: { child: 11 } }).formErrors).toEqual(undefined);
681
+ });
682
+
683
+ it('Validating two nested fields together', () => {
684
+ const { handleValidation } = createHeadlessForm(validatingTwoNestedFieldsSchema, {
685
+ strictInputType: false,
686
+ });
687
+ expect(handleValidation({}).formErrors).toEqual({
688
+ field_a: { child: 'Required field', other_child: 'Required field' },
689
+ });
690
+ expect(handleValidation({ field_a: { child: 0, other_child: 0 } }).formErrors).toEqual({
691
+ field_a: { child: 'Must be greater than 10!', other_child: 'Must be greater than child' },
692
+ });
693
+ expect(handleValidation({ field_a: { child: 11, other_child: 12 } }).formErrors).toEqual(
694
+ undefined
695
+ );
696
+ });
697
+
698
+ it('Validate a field and a nested field together', () => {
699
+ const { handleValidation } = createHeadlessForm(twoLevelsOfJSONLogicSchema, {
700
+ strictInputType: false,
701
+ });
702
+ expect(handleValidation({}).formErrors).toEqual({
703
+ field_a: { child: 'Required field' },
704
+ field_b: 'Required field',
705
+ });
706
+ expect(handleValidation({ field_a: { child: 0 }, field_b: 0 }).formErrors).toEqual({
707
+ field_a: { child: 'Must be greater than 10!' },
708
+ field_b: 'Must be greater than 10!',
709
+ });
710
+ expect(handleValidation({ field_a: { child: 11 }, field_b: 11 }).formErrors).toEqual({
711
+ field_b: 'child must be greater than 15!',
712
+ });
713
+ expect(handleValidation({ field_a: { child: 16 }, field_b: 11 }).formErrors).toEqual(
714
+ undefined
715
+ );
716
+ });
717
+
718
+ it('compute a nested field attribute', () => {
719
+ const { fields, handleValidation } = createHeadlessForm(fieldsetWithComputedAttributes, {
720
+ strictInputType: false,
721
+ });
722
+ const [fieldA] = fields;
723
+ const [, computedField] = fieldA.fields;
724
+ expect(handleValidation({}).formErrors).toEqual({
725
+ field_a: { child: 'Required field' },
726
+ });
727
+ expect(computedField.value).toEqual(NaN);
728
+
729
+ expect(handleValidation({ field_a: { child: 10 } }).formErrors).toEqual(undefined);
730
+ expect(computedField.value).toEqual(100);
731
+ expect(computedField.description).toEqual('this is 100');
732
+
733
+ expect(handleValidation({ field_a: { child: 11 } }).formErrors).toEqual(undefined);
734
+ expect(computedField.value).toEqual(110);
735
+ expect(computedField.description).toEqual('this is 110');
736
+ });
737
+
738
+ it('Apply a conditional value in a nested field with a conditional extra validation.', () => {
739
+ const { fields, handleValidation } = createHeadlessForm(
740
+ fieldsetWithAConditionalToApplyExtraValidations,
741
+ {
742
+ strictInputType: false,
743
+ }
744
+ );
745
+ const [fieldA] = fields;
746
+ const [, , thirdChild] = fieldA.fields;
747
+ expect(thirdChild.isVisible).toEqual(false);
748
+ expect(thirdChild.required).toEqual(false);
749
+
750
+ expect(handleValidation({ field_a: {} }).formErrors).toEqual({
751
+ field_a: { child: 'Required field', other_child: 'Required field' },
752
+ });
753
+ expect(handleValidation({ field_a: { child: 0, other_child: 0 } }).formErrors).toEqual(
754
+ undefined
755
+ );
756
+ expect(handleValidation({ field_a: { child: 10, other_child: 0 } }).formErrors).toEqual(
757
+ undefined
758
+ );
759
+ expect(handleValidation({ field_a: { child: 10, other_child: 20 } }).formErrors).toEqual({
760
+ field_a: { third_child: 'Required field' },
761
+ });
762
+ expect(thirdChild.isVisible).toEqual(true);
763
+ expect(thirdChild.required).toEqual(true);
764
+
765
+ expect(
766
+ handleValidation({ field_a: { child: 10, other_child: 20, third_child: 10 } }).formErrors
767
+ ).toEqual({
768
+ field_a: { third_child: 'Must be greater than other child.' },
769
+ });
770
+
771
+ expect(
772
+ handleValidation({ field_a: { child: 10, other_child: 20, third_child: 30 } }).formErrors
773
+ ).toEqual(undefined);
774
+ });
775
+ });
776
+
777
+ describe('Array validation', () => {
778
+ it('Should apply the json logic on an individual array item', () => {
779
+ const { handleValidation } = createHeadlessForm(simpleArrayValidationSchema, {
780
+ strictInputType: false,
781
+ });
782
+ expect(handleValidation({ field_array: [] }).formErrors).toEqual(undefined);
783
+ expect(handleValidation({ field_array: [{}] }).formErrors).toEqual({
784
+ field_array: [{ array_item: 'Required field' }],
785
+ });
786
+ expect(handleValidation({ field_array: [{ array_item: 1 }] }).formErrors).toEqual({
787
+ field_array: [{ array_item: 'Must be divisible by two' }],
788
+ });
789
+ expect(handleValidation({ field_array: [{ array_item: 2 }] }).formErrors).toEqual(undefined);
790
+ expect(
791
+ handleValidation({ field_array: [{ array_item: 2 }, { array_item: 1 }] }).formErrors
792
+ ).toEqual({
793
+ field_array: [undefined, { array_item: 'Must be divisible by two' }],
794
+ });
795
+ expect(
796
+ handleValidation({ field_array: [{ array_item: 2 }, { array_item: 2 }] }).formErrors
797
+ ).toEqual(undefined);
798
+ });
799
+
800
+ it('Validating a single item in an array should work', () => {
801
+ const { handleValidation } = createHeadlessForm(validatingASingleItemInTheArray, {
802
+ strictInputType: false,
803
+ });
804
+ expect(handleValidation({ field_array: [] }).formErrors).toEqual(undefined);
805
+ expect(handleValidation({ field_array: [{ item: 0 }] }).formErrors).toEqual(undefined);
806
+ expect(handleValidation({ field_array: [{ item: 0 }, { item: 3 }] }).formErrors).toEqual({
807
+ field_array: 'Second item in array must be divisible by 4',
808
+ });
809
+ });
810
+
811
+ // FIXME: This doesn't work because conditionals in items are not supported.
812
+ it.todo('Should be able to use conditionals in items');
813
+ });
814
+ });