@remoteoss/json-schema-form 0.11.11-dev.20250220174843 → 1.0.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/.nvmrc +1 -0
- package/.vscode/settings.json +51 -0
- package/README.md +29 -24
- package/babel.config.mjs +14 -0
- package/eslint.config.mjs +3 -0
- package/jest.config.mjs +52 -0
- package/package.json +27 -71
- package/src/form.ts +3 -0
- package/src/index.ts +1 -0
- package/src/utils.ts +24 -0
- package/test/form.test.ts +11 -0
- package/tsconfig.json +13 -0
- package/tsup.config.json +10 -0
- package/CHANGELOG.md +0 -320
- package/LICENSE +0 -21
- package/dist/index.cjs +0 -2171
- package/dist/index.js +0 -2140
- package/dist/standalone.js +0 -13656
- package/json-schema-form.d.ts +0 -175
- package/json-schema-form.schema.json +0 -188
- package/src/tests/checkIfConditionMatches.test.js +0 -65
- package/src/tests/conditions.test.js +0 -938
- package/src/tests/const.test.js +0 -236
- package/src/tests/createHeadlessForm.customValidations.test.js +0 -803
- package/src/tests/createHeadlessForm.test.js +0 -4324
- package/src/tests/helpers.custom.js +0 -222
- package/src/tests/helpers.js +0 -2385
- package/src/tests/internals.helpers.test.js +0 -175
- package/src/tests/jsonLogic.fixtures.js +0 -986
- package/src/tests/jsonLogic.test.js +0 -588
- package/src/tests/modify.test.js +0 -939
- package/src/tests/testUtils.js +0 -11
- package/src/tests/utils.test.jsx +0 -32
|
@@ -1,4324 +0,0 @@
|
|
|
1
|
-
import isNil from 'lodash/isNil';
|
|
2
|
-
import omitBy from 'lodash/omitBy';
|
|
3
|
-
import { object } from 'yup';
|
|
4
|
-
|
|
5
|
-
import {
|
|
6
|
-
JSONSchemaBuilder,
|
|
7
|
-
schemaInputTypeText,
|
|
8
|
-
schemaInputTypeRadioDeprecated,
|
|
9
|
-
schemaInputTypeRadioString,
|
|
10
|
-
schemaInputTypeRadioStringY,
|
|
11
|
-
schemaInputTypeRadioBoolean,
|
|
12
|
-
schemaInputTypeRadioNumber,
|
|
13
|
-
schemaInputTypeRadioRequiredAndOptional,
|
|
14
|
-
schemaInputRadioOptionalNull,
|
|
15
|
-
schemaInputRadioOptionalConventional,
|
|
16
|
-
schemaInputTypeRadioOptionsWithDetails,
|
|
17
|
-
schemaInputTypeRadioWithoutOptions,
|
|
18
|
-
schemaInputTypeSelectSoloDeprecated,
|
|
19
|
-
schemaInputTypeSelectSolo,
|
|
20
|
-
schemaInputTypeSelectMultipleDeprecated,
|
|
21
|
-
schemaInputTypeSelectMultiple,
|
|
22
|
-
schemaInputTypeSelectMultipleOptional,
|
|
23
|
-
schemaInputTypeFieldset,
|
|
24
|
-
schemaInputTypeIntegerNumber,
|
|
25
|
-
schemaInputTypeNumber,
|
|
26
|
-
schemaInputTypeNumberZeroMaximum,
|
|
27
|
-
schemaInputTypeDate,
|
|
28
|
-
schemaInputTypeEmail,
|
|
29
|
-
schemaInputWithStatement,
|
|
30
|
-
schemaInputTypeCheckbox,
|
|
31
|
-
schemaInputTypeCheckboxBooleans,
|
|
32
|
-
schemaInputTypeCheckboxBooleanConditional,
|
|
33
|
-
schemaInputTypeNull,
|
|
34
|
-
schemaWithOrderKeyword,
|
|
35
|
-
schemaWithPositionDeprecated,
|
|
36
|
-
schemaDynamicValidationConst,
|
|
37
|
-
schemaDynamicValidationMinimumMaximum,
|
|
38
|
-
schemaDynamicValidationMinLengthMaxLength,
|
|
39
|
-
schemaDynamicValidationContains,
|
|
40
|
-
schemaAnyOfValidation,
|
|
41
|
-
schemaWithoutInputTypes,
|
|
42
|
-
schemaWithoutTypes,
|
|
43
|
-
mockFileInput,
|
|
44
|
-
mockRadioCardInput,
|
|
45
|
-
mockRadioCardExpandableInput,
|
|
46
|
-
mockTelWithPattern,
|
|
47
|
-
mockTextInput,
|
|
48
|
-
mockTextInputDeprecated,
|
|
49
|
-
mockNumberInput,
|
|
50
|
-
mockNumberInputWithPercentageAndCustomRange,
|
|
51
|
-
mockTextPatternInput,
|
|
52
|
-
mockTextMaxLengthInput,
|
|
53
|
-
mockFieldset,
|
|
54
|
-
mockNestedFieldset,
|
|
55
|
-
mockGroupArrayInput,
|
|
56
|
-
schemaFieldsetScopedCondition,
|
|
57
|
-
schemaWithConditionalToFieldset,
|
|
58
|
-
schemaWithConditionalPresentationProperties,
|
|
59
|
-
schemaWithConditionalReadOnlyProperty,
|
|
60
|
-
schemaWithWrongConditional,
|
|
61
|
-
schemaWithConditionalAcknowledgementProperty,
|
|
62
|
-
schemaInputTypeNumberWithPercentage,
|
|
63
|
-
schemaForErrorMessageSpecificity,
|
|
64
|
-
jsfConfigForErrorMessageSpecificity,
|
|
65
|
-
schemaInputTypeFile,
|
|
66
|
-
nestedGroupArrayForm,
|
|
67
|
-
} from './helpers';
|
|
68
|
-
import { mockConsole, restoreConsoleAndEnsureItWasNotCalled } from './testUtils';
|
|
69
|
-
import { createHeadlessForm } from '@/createHeadlessForm';
|
|
70
|
-
|
|
71
|
-
function buildJSONSchemaInput({ presentationFields, inputFields = {}, required }) {
|
|
72
|
-
return {
|
|
73
|
-
type: 'object',
|
|
74
|
-
properties: {
|
|
75
|
-
test: {
|
|
76
|
-
description: 'Test description',
|
|
77
|
-
presentation: {
|
|
78
|
-
...presentationFields,
|
|
79
|
-
},
|
|
80
|
-
title: 'Test title',
|
|
81
|
-
type: 'number',
|
|
82
|
-
...inputFields,
|
|
83
|
-
},
|
|
84
|
-
},
|
|
85
|
-
required: required ? ['test'] : [],
|
|
86
|
-
};
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
function friendlyError({ formErrors }) {
|
|
90
|
-
// destruct the formErrors directly
|
|
91
|
-
return formErrors;
|
|
92
|
-
}
|
|
93
|
-
|
|
94
|
-
// Get a field by name recursively
|
|
95
|
-
// eg getField(demo, "age") -> returns "age" field
|
|
96
|
-
// eg getField(demo, child, name) -> returns "child.name" subfield
|
|
97
|
-
const getField = (fields, name, ...subNames) => {
|
|
98
|
-
const field = fields.find((f) => f.name === name);
|
|
99
|
-
if (subNames.length > 0) {
|
|
100
|
-
return getField(field.fields, ...subNames);
|
|
101
|
-
}
|
|
102
|
-
return field;
|
|
103
|
-
};
|
|
104
|
-
|
|
105
|
-
beforeEach(mockConsole);
|
|
106
|
-
afterEach(restoreConsoleAndEnsureItWasNotCalled);
|
|
107
|
-
|
|
108
|
-
describe('createHeadlessForm', () => {
|
|
109
|
-
it('returns empty result given no schema', () => {
|
|
110
|
-
const result = createHeadlessForm();
|
|
111
|
-
|
|
112
|
-
expect(result).toMatchObject({
|
|
113
|
-
fields: [],
|
|
114
|
-
});
|
|
115
|
-
expect(result.isError).toBe(false);
|
|
116
|
-
expect(result.error).toBeFalsy();
|
|
117
|
-
});
|
|
118
|
-
|
|
119
|
-
it('returns an error given invalid schema', () => {
|
|
120
|
-
const result = createHeadlessForm({ foo: 1 });
|
|
121
|
-
|
|
122
|
-
expect(result.fields).toHaveLength(0);
|
|
123
|
-
expect(result.isError).toBe(true);
|
|
124
|
-
|
|
125
|
-
expect(console.error).toHaveBeenCalledWith(`JSON Schema invalid!`, expect.any(Error));
|
|
126
|
-
console.error.mockClear();
|
|
127
|
-
|
|
128
|
-
expect(result.error.message).toBe(`Cannot convert undefined or null to object`);
|
|
129
|
-
});
|
|
130
|
-
|
|
131
|
-
describe('field support fallback', () => {
|
|
132
|
-
it('sets type from presentation.inputType', () => {
|
|
133
|
-
const { fields } = createHeadlessForm({
|
|
134
|
-
properties: {
|
|
135
|
-
age: {
|
|
136
|
-
title: 'Age',
|
|
137
|
-
presentation: { inputType: 'number' },
|
|
138
|
-
type: 'number',
|
|
139
|
-
},
|
|
140
|
-
starting_time: {
|
|
141
|
-
title: 'Starting time',
|
|
142
|
-
presentation: {
|
|
143
|
-
inputType: 'hour', // Arbitrary types are accepted
|
|
144
|
-
set: 'AM', // And even any arbitrary presentation keys
|
|
145
|
-
},
|
|
146
|
-
type: 'string',
|
|
147
|
-
},
|
|
148
|
-
},
|
|
149
|
-
});
|
|
150
|
-
|
|
151
|
-
const { schema: yupSchema1, ...fieldAge } = omitBy(fields[0], isNil);
|
|
152
|
-
const { schema: yupSchema2, ...fieldTime } = omitBy(fields[1], isNil);
|
|
153
|
-
|
|
154
|
-
expect(yupSchema1).toEqual(expect.any(Object));
|
|
155
|
-
expect(fieldAge).toMatchObject({
|
|
156
|
-
inputType: 'number',
|
|
157
|
-
jsonType: 'number',
|
|
158
|
-
type: 'number',
|
|
159
|
-
});
|
|
160
|
-
|
|
161
|
-
expect(yupSchema1).toEqual(expect.any(Object));
|
|
162
|
-
expect(fieldTime).toMatchObject({
|
|
163
|
-
inputType: 'hour',
|
|
164
|
-
jsonType: 'string',
|
|
165
|
-
name: 'starting_time',
|
|
166
|
-
type: 'hour',
|
|
167
|
-
set: 'AM',
|
|
168
|
-
});
|
|
169
|
-
});
|
|
170
|
-
|
|
171
|
-
it('fails given a json schema without inputType', () => {
|
|
172
|
-
const { fields, error } = createHeadlessForm({
|
|
173
|
-
properties: {
|
|
174
|
-
test: { type: 'string' },
|
|
175
|
-
},
|
|
176
|
-
});
|
|
177
|
-
|
|
178
|
-
expect(fields).toHaveLength(0);
|
|
179
|
-
expect(error.message).toContain('Strict error: Missing inputType to field "test"');
|
|
180
|
-
|
|
181
|
-
expect(console.error).toHaveBeenCalledWith(`JSON Schema invalid!`, expect.any(Error));
|
|
182
|
-
console.error.mockClear();
|
|
183
|
-
});
|
|
184
|
-
|
|
185
|
-
function extractTypeOnly(listOfFields) {
|
|
186
|
-
const list = Array.isArray(listOfFields) ? listOfFields : listOfFields?.(); // handle fieldset + group-array
|
|
187
|
-
return list?.map(
|
|
188
|
-
({ name, type, inputType, jsonType, label, options, fields: nestedFields }) => {
|
|
189
|
-
return omitBy(
|
|
190
|
-
{
|
|
191
|
-
name,
|
|
192
|
-
type, // @deprecated
|
|
193
|
-
inputType,
|
|
194
|
-
jsonType,
|
|
195
|
-
label,
|
|
196
|
-
options,
|
|
197
|
-
fields: extractTypeOnly(nestedFields),
|
|
198
|
-
},
|
|
199
|
-
isNil
|
|
200
|
-
);
|
|
201
|
-
}
|
|
202
|
-
);
|
|
203
|
-
}
|
|
204
|
-
|
|
205
|
-
it('given a json schema without inputType, sets type based on json type (when strictInputType:false)', () => {
|
|
206
|
-
const { fields } = createHeadlessForm(schemaWithoutInputTypes, {
|
|
207
|
-
strictInputType: false,
|
|
208
|
-
});
|
|
209
|
-
|
|
210
|
-
const fieldsByNameAndType = extractTypeOnly(fields);
|
|
211
|
-
expect(fieldsByNameAndType).toMatchInlineSnapshot(`
|
|
212
|
-
[
|
|
213
|
-
{
|
|
214
|
-
"inputType": "text",
|
|
215
|
-
"jsonType": "string",
|
|
216
|
-
"label": "A string -> text",
|
|
217
|
-
"name": "a_string",
|
|
218
|
-
"type": "text",
|
|
219
|
-
},
|
|
220
|
-
{
|
|
221
|
-
"inputType": "radio",
|
|
222
|
-
"jsonType": "string",
|
|
223
|
-
"label": "A string with oneOf -> radio",
|
|
224
|
-
"name": "a_string_oneOf",
|
|
225
|
-
"options": [
|
|
226
|
-
{
|
|
227
|
-
"label": "Yes",
|
|
228
|
-
"value": "yes",
|
|
229
|
-
},
|
|
230
|
-
{
|
|
231
|
-
"label": "No",
|
|
232
|
-
"value": "no",
|
|
233
|
-
},
|
|
234
|
-
],
|
|
235
|
-
"type": "radio",
|
|
236
|
-
},
|
|
237
|
-
{
|
|
238
|
-
"inputType": "email",
|
|
239
|
-
"jsonType": "string",
|
|
240
|
-
"label": "A string with format:email -> email",
|
|
241
|
-
"name": "a_string_email",
|
|
242
|
-
"type": "email",
|
|
243
|
-
},
|
|
244
|
-
{
|
|
245
|
-
"inputType": "date",
|
|
246
|
-
"jsonType": "string",
|
|
247
|
-
"label": "A string with format:email -> date",
|
|
248
|
-
"name": "a_string_date",
|
|
249
|
-
"type": "date",
|
|
250
|
-
},
|
|
251
|
-
{
|
|
252
|
-
"inputType": "file",
|
|
253
|
-
"jsonType": "string",
|
|
254
|
-
"label": "A string with format:data-url -> file",
|
|
255
|
-
"name": "a_string_file",
|
|
256
|
-
"type": "file",
|
|
257
|
-
},
|
|
258
|
-
{
|
|
259
|
-
"inputType": "number",
|
|
260
|
-
"jsonType": "number",
|
|
261
|
-
"label": "A number -> number",
|
|
262
|
-
"name": "a_number",
|
|
263
|
-
"type": "number",
|
|
264
|
-
},
|
|
265
|
-
{
|
|
266
|
-
"inputType": "number",
|
|
267
|
-
"jsonType": "integer",
|
|
268
|
-
"label": "A integer -> number",
|
|
269
|
-
"name": "a_integer",
|
|
270
|
-
"type": "number",
|
|
271
|
-
},
|
|
272
|
-
{
|
|
273
|
-
"inputType": "checkbox",
|
|
274
|
-
"jsonType": "boolean",
|
|
275
|
-
"label": "A boolean -> checkbox",
|
|
276
|
-
"name": "a_boolean",
|
|
277
|
-
"type": "checkbox",
|
|
278
|
-
},
|
|
279
|
-
{
|
|
280
|
-
"fields": [
|
|
281
|
-
{
|
|
282
|
-
"inputType": "text",
|
|
283
|
-
"jsonType": "string",
|
|
284
|
-
"name": "foo",
|
|
285
|
-
"type": "text",
|
|
286
|
-
},
|
|
287
|
-
{
|
|
288
|
-
"inputType": "text",
|
|
289
|
-
"jsonType": "string",
|
|
290
|
-
"name": "bar",
|
|
291
|
-
"type": "text",
|
|
292
|
-
},
|
|
293
|
-
],
|
|
294
|
-
"inputType": "fieldset",
|
|
295
|
-
"jsonType": "object",
|
|
296
|
-
"label": "An object -> fieldset",
|
|
297
|
-
"name": "a_object",
|
|
298
|
-
"type": "fieldset",
|
|
299
|
-
},
|
|
300
|
-
{
|
|
301
|
-
"inputType": "select",
|
|
302
|
-
"jsonType": "array",
|
|
303
|
-
"label": "An array items.anyOf -> select",
|
|
304
|
-
"name": "a_array_items",
|
|
305
|
-
"options": [
|
|
306
|
-
{
|
|
307
|
-
"label": "Chrome",
|
|
308
|
-
"value": "chr",
|
|
309
|
-
},
|
|
310
|
-
{
|
|
311
|
-
"label": "Firefox",
|
|
312
|
-
"value": "ff",
|
|
313
|
-
},
|
|
314
|
-
{
|
|
315
|
-
"label": "Internet Explorer",
|
|
316
|
-
"value": "ie",
|
|
317
|
-
},
|
|
318
|
-
],
|
|
319
|
-
"type": "select",
|
|
320
|
-
},
|
|
321
|
-
{
|
|
322
|
-
"fields": [
|
|
323
|
-
{
|
|
324
|
-
"inputType": "text",
|
|
325
|
-
"jsonType": "string",
|
|
326
|
-
"label": "Role",
|
|
327
|
-
"name": "role",
|
|
328
|
-
"type": "text",
|
|
329
|
-
},
|
|
330
|
-
{
|
|
331
|
-
"inputType": "number",
|
|
332
|
-
"jsonType": "number",
|
|
333
|
-
"label": "Years",
|
|
334
|
-
"name": "years",
|
|
335
|
-
"type": "number",
|
|
336
|
-
},
|
|
337
|
-
],
|
|
338
|
-
"inputType": "group-array",
|
|
339
|
-
"jsonType": "array",
|
|
340
|
-
"label": "An array items.properties -> group-array",
|
|
341
|
-
"name": "a_array_properties",
|
|
342
|
-
"type": "group-array",
|
|
343
|
-
},
|
|
344
|
-
{
|
|
345
|
-
"inputType": "text",
|
|
346
|
-
"label": "A void -> text",
|
|
347
|
-
"name": "a_void",
|
|
348
|
-
"type": "text",
|
|
349
|
-
},
|
|
350
|
-
]
|
|
351
|
-
`);
|
|
352
|
-
});
|
|
353
|
-
|
|
354
|
-
it('given a json schema without json type, sets type based on structure (when strictInputType:false)', () => {
|
|
355
|
-
const { fields } = createHeadlessForm(schemaWithoutTypes, {
|
|
356
|
-
strictInputType: false,
|
|
357
|
-
});
|
|
358
|
-
|
|
359
|
-
const fieldsByNameAndType = extractTypeOnly(fields);
|
|
360
|
-
expect(fieldsByNameAndType).toMatchInlineSnapshot(`
|
|
361
|
-
[
|
|
362
|
-
{
|
|
363
|
-
"inputType": "text",
|
|
364
|
-
"label": "Default -> text",
|
|
365
|
-
"name": "default",
|
|
366
|
-
"type": "text",
|
|
367
|
-
},
|
|
368
|
-
{
|
|
369
|
-
"inputType": "radio",
|
|
370
|
-
"label": "With oneOf -> radio",
|
|
371
|
-
"name": "with_oneOf",
|
|
372
|
-
"options": [
|
|
373
|
-
{
|
|
374
|
-
"label": "Yes",
|
|
375
|
-
"value": "yes",
|
|
376
|
-
},
|
|
377
|
-
{
|
|
378
|
-
"label": "No",
|
|
379
|
-
"value": "no",
|
|
380
|
-
},
|
|
381
|
-
],
|
|
382
|
-
"type": "radio",
|
|
383
|
-
},
|
|
384
|
-
{
|
|
385
|
-
"inputType": "email",
|
|
386
|
-
"label": "With format:email -> email",
|
|
387
|
-
"name": "with_email",
|
|
388
|
-
"type": "email",
|
|
389
|
-
},
|
|
390
|
-
{
|
|
391
|
-
"inputType": "select",
|
|
392
|
-
"label": "With properties -> fieldset",
|
|
393
|
-
"name": "with_object",
|
|
394
|
-
"type": "select",
|
|
395
|
-
},
|
|
396
|
-
{
|
|
397
|
-
"inputType": "text",
|
|
398
|
-
"label": "With items.anyOf -> select",
|
|
399
|
-
"name": "with_items_anyOf",
|
|
400
|
-
"options": [
|
|
401
|
-
{
|
|
402
|
-
"label": "Chrome",
|
|
403
|
-
"value": "chr",
|
|
404
|
-
},
|
|
405
|
-
{
|
|
406
|
-
"label": "Firefox",
|
|
407
|
-
"value": "ff",
|
|
408
|
-
},
|
|
409
|
-
{
|
|
410
|
-
"label": "Internet Explorer",
|
|
411
|
-
"value": "ie",
|
|
412
|
-
},
|
|
413
|
-
],
|
|
414
|
-
"type": "text",
|
|
415
|
-
},
|
|
416
|
-
{
|
|
417
|
-
"fields": [
|
|
418
|
-
{
|
|
419
|
-
"inputType": "text",
|
|
420
|
-
"label": "Role",
|
|
421
|
-
"name": "role",
|
|
422
|
-
"type": "text",
|
|
423
|
-
},
|
|
424
|
-
{
|
|
425
|
-
"inputType": "text",
|
|
426
|
-
"label": "Years",
|
|
427
|
-
"name": "years",
|
|
428
|
-
"type": "text",
|
|
429
|
-
},
|
|
430
|
-
],
|
|
431
|
-
"inputType": "group-array",
|
|
432
|
-
"label": "With items.properties -> group-array",
|
|
433
|
-
"name": "with_items_properties",
|
|
434
|
-
"type": "group-array",
|
|
435
|
-
},
|
|
436
|
-
]
|
|
437
|
-
`);
|
|
438
|
-
});
|
|
439
|
-
});
|
|
440
|
-
|
|
441
|
-
describe('field support', () => {
|
|
442
|
-
function assertOptionsAllowed({ handleValidation, fieldName, validOptions, type = 'string' }) {
|
|
443
|
-
const validateForm = (vals) => friendlyError(handleValidation(vals));
|
|
444
|
-
|
|
445
|
-
// All allowed options are valid
|
|
446
|
-
validOptions.forEach((value) => {
|
|
447
|
-
expect(validateForm({ [fieldName]: value })).toBeUndefined();
|
|
448
|
-
});
|
|
449
|
-
|
|
450
|
-
if (type === 'string') {
|
|
451
|
-
// Any other arbitrary value is not valid.
|
|
452
|
-
expect(validateForm({ [fieldName]: 'blah-blah' })).toEqual({
|
|
453
|
-
[fieldName]: 'The option "blah-blah" is not valid.',
|
|
454
|
-
});
|
|
455
|
-
|
|
456
|
-
// As required field, empty string ("") is also considered empty. @BUG RMT-518
|
|
457
|
-
// Expectation: The error to be "The option '' is not valid."
|
|
458
|
-
expect(validateForm({ [fieldName]: '' })).toEqual({
|
|
459
|
-
[fieldName]: 'Required field',
|
|
460
|
-
});
|
|
461
|
-
}
|
|
462
|
-
|
|
463
|
-
// Given undefined, it says it's a required field.
|
|
464
|
-
expect(validateForm({})).toEqual({
|
|
465
|
-
[fieldName]: 'Required field',
|
|
466
|
-
});
|
|
467
|
-
|
|
468
|
-
// As required field, null is also considered empty @BUG RMT-518
|
|
469
|
-
// Expectation: The error to be "The option null is not valid."
|
|
470
|
-
expect(validateForm({ [fieldName]: null })).toEqual({
|
|
471
|
-
[fieldName]: 'Required field',
|
|
472
|
-
});
|
|
473
|
-
}
|
|
474
|
-
|
|
475
|
-
it('support "text" field type', () => {
|
|
476
|
-
const { fields, handleValidation } = createHeadlessForm(schemaInputTypeText);
|
|
477
|
-
|
|
478
|
-
expect(fields[0]).toMatchObject({
|
|
479
|
-
description: 'Your username (max 10 characters)',
|
|
480
|
-
label: 'Username',
|
|
481
|
-
name: 'username',
|
|
482
|
-
required: true,
|
|
483
|
-
schema: expect.any(Object),
|
|
484
|
-
inputType: 'text',
|
|
485
|
-
jsonType: 'string',
|
|
486
|
-
maskSecret: 2,
|
|
487
|
-
maxLength: 10,
|
|
488
|
-
isVisible: true,
|
|
489
|
-
});
|
|
490
|
-
|
|
491
|
-
const fieldValidator = fields[0].schema;
|
|
492
|
-
expect(fieldValidator.isValidSync('CI007')).toBe(true);
|
|
493
|
-
expect(fieldValidator.isValidSync(true)).toBe(false);
|
|
494
|
-
expect(fieldValidator.isValidSync(1)).toBe(false);
|
|
495
|
-
expect(fieldValidator.isValidSync(0)).toBe(false);
|
|
496
|
-
|
|
497
|
-
expect(handleValidation({ username: 1 }).formErrors).toEqual({
|
|
498
|
-
username: 'username must be a `string` type, but the final value was: `1`.',
|
|
499
|
-
});
|
|
500
|
-
|
|
501
|
-
expect(() => fieldValidator.validateSync('')).toThrowError('Required field');
|
|
502
|
-
});
|
|
503
|
-
|
|
504
|
-
describe('support "select" field type', () => {
|
|
505
|
-
it('support "select" field type @deprecated', () => {
|
|
506
|
-
const { fields, handleValidation } = createHeadlessForm(
|
|
507
|
-
schemaInputTypeSelectSoloDeprecated
|
|
508
|
-
);
|
|
509
|
-
|
|
510
|
-
expect(fields).toMatchObject([
|
|
511
|
-
{
|
|
512
|
-
description: 'Life Insurance',
|
|
513
|
-
label: 'Benefits (solo)',
|
|
514
|
-
name: 'benefits',
|
|
515
|
-
placeholder: 'Select...',
|
|
516
|
-
type: 'select',
|
|
517
|
-
options: [
|
|
518
|
-
{
|
|
519
|
-
label: 'Medical Insurance',
|
|
520
|
-
value: 'Medical Insurance',
|
|
521
|
-
},
|
|
522
|
-
{
|
|
523
|
-
label: 'Health Insurance',
|
|
524
|
-
value: 'Health Insurance',
|
|
525
|
-
},
|
|
526
|
-
{
|
|
527
|
-
label: 'Travel Bonus',
|
|
528
|
-
value: 'Travel Bonus',
|
|
529
|
-
},
|
|
530
|
-
],
|
|
531
|
-
},
|
|
532
|
-
]);
|
|
533
|
-
|
|
534
|
-
assertOptionsAllowed({
|
|
535
|
-
handleValidation,
|
|
536
|
-
fieldName: 'benefits',
|
|
537
|
-
validOptions: ['Medical Insurance', 'Health Insurance', 'Travel Bonus'],
|
|
538
|
-
});
|
|
539
|
-
});
|
|
540
|
-
|
|
541
|
-
it('support "select" field type', () => {
|
|
542
|
-
const { fields, handleValidation } = createHeadlessForm(schemaInputTypeSelectSolo);
|
|
543
|
-
|
|
544
|
-
const fieldSelect = fields[0];
|
|
545
|
-
expect(fieldSelect).toMatchObject({
|
|
546
|
-
name: 'browsers',
|
|
547
|
-
label: 'Browsers (solo)',
|
|
548
|
-
description: 'This solo select also includes a disabled option.',
|
|
549
|
-
options: [
|
|
550
|
-
{
|
|
551
|
-
value: 'chr',
|
|
552
|
-
label: 'Chrome',
|
|
553
|
-
},
|
|
554
|
-
{
|
|
555
|
-
value: 'ff',
|
|
556
|
-
label: 'Firefox',
|
|
557
|
-
},
|
|
558
|
-
{
|
|
559
|
-
value: 'ie',
|
|
560
|
-
label: 'Internet Explorer',
|
|
561
|
-
disabled: true,
|
|
562
|
-
},
|
|
563
|
-
],
|
|
564
|
-
});
|
|
565
|
-
|
|
566
|
-
expect(fieldSelect).not.toHaveProperty('multiple');
|
|
567
|
-
|
|
568
|
-
assertOptionsAllowed({
|
|
569
|
-
handleValidation,
|
|
570
|
-
fieldName: 'browsers',
|
|
571
|
-
validOptions: ['chr', 'ff', 'ie'],
|
|
572
|
-
});
|
|
573
|
-
});
|
|
574
|
-
|
|
575
|
-
it('supports "select" field type with multiple options @deprecated', () => {
|
|
576
|
-
const result = createHeadlessForm(schemaInputTypeSelectMultipleDeprecated);
|
|
577
|
-
expect(result).toMatchObject({
|
|
578
|
-
fields: [
|
|
579
|
-
{
|
|
580
|
-
description: 'Life Insurance',
|
|
581
|
-
label: 'Benefits (multiple)',
|
|
582
|
-
name: 'benefits_multi',
|
|
583
|
-
placeholder: 'Select...',
|
|
584
|
-
type: 'select',
|
|
585
|
-
options: [
|
|
586
|
-
{
|
|
587
|
-
label: 'Medical Insurance',
|
|
588
|
-
value: 'Medical Insurance',
|
|
589
|
-
},
|
|
590
|
-
{
|
|
591
|
-
label: 'Health Insurance',
|
|
592
|
-
value: 'Health Insurance',
|
|
593
|
-
},
|
|
594
|
-
{
|
|
595
|
-
label: 'Travel Bonus',
|
|
596
|
-
value: 'Travel Bonus',
|
|
597
|
-
},
|
|
598
|
-
],
|
|
599
|
-
multiple: true,
|
|
600
|
-
},
|
|
601
|
-
],
|
|
602
|
-
});
|
|
603
|
-
});
|
|
604
|
-
it('supports "select" field type with multiple options', () => {
|
|
605
|
-
const result = createHeadlessForm(schemaInputTypeSelectMultiple);
|
|
606
|
-
expect(result).toMatchObject({
|
|
607
|
-
fields: [
|
|
608
|
-
{
|
|
609
|
-
name: 'browsers_multi',
|
|
610
|
-
label: 'Browsers (multiple)',
|
|
611
|
-
description: 'This multi-select also includes a disabled option.',
|
|
612
|
-
options: [
|
|
613
|
-
{
|
|
614
|
-
value: 'chr',
|
|
615
|
-
label: 'Chrome',
|
|
616
|
-
},
|
|
617
|
-
{
|
|
618
|
-
value: 'ff',
|
|
619
|
-
label: 'Firefox',
|
|
620
|
-
},
|
|
621
|
-
{
|
|
622
|
-
value: 'ie',
|
|
623
|
-
label: 'Internet Explorer',
|
|
624
|
-
disabled: true,
|
|
625
|
-
},
|
|
626
|
-
],
|
|
627
|
-
multiple: true,
|
|
628
|
-
},
|
|
629
|
-
],
|
|
630
|
-
});
|
|
631
|
-
});
|
|
632
|
-
|
|
633
|
-
it('supports "select" field type with multiple options and optional', () => {
|
|
634
|
-
const result = createHeadlessForm(schemaInputTypeSelectMultipleOptional);
|
|
635
|
-
expect(result).toMatchObject({
|
|
636
|
-
fields: [
|
|
637
|
-
{
|
|
638
|
-
name: 'browsers_multi_optional',
|
|
639
|
-
label: 'Browsers (multiple) (optional)',
|
|
640
|
-
description: 'This optional multi-select also includes a disabled option.',
|
|
641
|
-
options: [
|
|
642
|
-
{
|
|
643
|
-
value: 'chr',
|
|
644
|
-
label: 'Chrome',
|
|
645
|
-
},
|
|
646
|
-
{
|
|
647
|
-
value: 'ff',
|
|
648
|
-
label: 'Firefox',
|
|
649
|
-
},
|
|
650
|
-
{
|
|
651
|
-
value: 'ie',
|
|
652
|
-
label: 'Internet Explorer',
|
|
653
|
-
disabled: true,
|
|
654
|
-
},
|
|
655
|
-
],
|
|
656
|
-
multiple: true,
|
|
657
|
-
},
|
|
658
|
-
],
|
|
659
|
-
});
|
|
660
|
-
});
|
|
661
|
-
});
|
|
662
|
-
describe('support "radio" field type', () => {
|
|
663
|
-
it('support "radio" field type @deprecated', () => {
|
|
664
|
-
const { fields, handleValidation } = createHeadlessForm(schemaInputTypeRadioDeprecated);
|
|
665
|
-
|
|
666
|
-
expect(fields).toMatchObject([
|
|
667
|
-
{
|
|
668
|
-
description: 'Do you have any siblings?',
|
|
669
|
-
label: 'Has siblings',
|
|
670
|
-
name: 'has_siblings',
|
|
671
|
-
options: [
|
|
672
|
-
{
|
|
673
|
-
label: 'Yes',
|
|
674
|
-
value: 'yes',
|
|
675
|
-
},
|
|
676
|
-
{
|
|
677
|
-
label: 'No',
|
|
678
|
-
value: 'no',
|
|
679
|
-
},
|
|
680
|
-
],
|
|
681
|
-
required: true,
|
|
682
|
-
schema: expect.any(Object),
|
|
683
|
-
type: 'radio',
|
|
684
|
-
},
|
|
685
|
-
]);
|
|
686
|
-
|
|
687
|
-
assertOptionsAllowed({
|
|
688
|
-
handleValidation,
|
|
689
|
-
fieldName: 'has_siblings',
|
|
690
|
-
validOptions: ['yes', 'no'],
|
|
691
|
-
});
|
|
692
|
-
});
|
|
693
|
-
it('support "radio" field string type', () => {
|
|
694
|
-
const { fields, handleValidation } = createHeadlessForm(schemaInputTypeRadioString);
|
|
695
|
-
|
|
696
|
-
expect(fields).toMatchObject([
|
|
697
|
-
{
|
|
698
|
-
description: 'Do you have any siblings?',
|
|
699
|
-
label: 'Has siblings',
|
|
700
|
-
name: 'has_siblings',
|
|
701
|
-
options: [
|
|
702
|
-
{
|
|
703
|
-
label: 'Yes',
|
|
704
|
-
value: 'yes',
|
|
705
|
-
},
|
|
706
|
-
{
|
|
707
|
-
label: 'No',
|
|
708
|
-
value: 'no',
|
|
709
|
-
},
|
|
710
|
-
],
|
|
711
|
-
required: true,
|
|
712
|
-
schema: expect.any(Object),
|
|
713
|
-
type: 'radio',
|
|
714
|
-
},
|
|
715
|
-
]);
|
|
716
|
-
|
|
717
|
-
assertOptionsAllowed({
|
|
718
|
-
handleValidation,
|
|
719
|
-
fieldName: 'has_siblings',
|
|
720
|
-
validOptions: ['yes', 'no'],
|
|
721
|
-
});
|
|
722
|
-
});
|
|
723
|
-
|
|
724
|
-
it('support "radio" field boolean type', () => {
|
|
725
|
-
const { fields, handleValidation } = createHeadlessForm(schemaInputTypeRadioBoolean);
|
|
726
|
-
|
|
727
|
-
const validateForm = (vals) => friendlyError(handleValidation(vals));
|
|
728
|
-
|
|
729
|
-
expect(fields).toMatchObject([
|
|
730
|
-
{
|
|
731
|
-
description: 'Are you over 18 years old?',
|
|
732
|
-
label: 'Over 18',
|
|
733
|
-
name: 'over_18',
|
|
734
|
-
options: [
|
|
735
|
-
{
|
|
736
|
-
label: 'Yes',
|
|
737
|
-
value: true,
|
|
738
|
-
},
|
|
739
|
-
{
|
|
740
|
-
label: 'No',
|
|
741
|
-
value: false,
|
|
742
|
-
},
|
|
743
|
-
],
|
|
744
|
-
required: true,
|
|
745
|
-
schema: expect.any(Object),
|
|
746
|
-
type: 'radio',
|
|
747
|
-
},
|
|
748
|
-
]);
|
|
749
|
-
|
|
750
|
-
assertOptionsAllowed({
|
|
751
|
-
handleValidation,
|
|
752
|
-
fieldName: 'over_18',
|
|
753
|
-
validOptions: [true, false],
|
|
754
|
-
type: schemaInputTypeRadioBoolean.properties.over_18.type,
|
|
755
|
-
});
|
|
756
|
-
|
|
757
|
-
expect(validateForm({ over_18: 'true' })).toEqual({
|
|
758
|
-
over_18: 'The option "true" is not valid.',
|
|
759
|
-
});
|
|
760
|
-
});
|
|
761
|
-
|
|
762
|
-
it('supports "radio" field type with its "card" and "card-expandable" variants', () => {
|
|
763
|
-
const result = createHeadlessForm(
|
|
764
|
-
JSONSchemaBuilder()
|
|
765
|
-
.addInput({
|
|
766
|
-
experience_level: mockRadioCardExpandableInput,
|
|
767
|
-
payment_method: mockRadioCardInput,
|
|
768
|
-
})
|
|
769
|
-
.build()
|
|
770
|
-
);
|
|
771
|
-
|
|
772
|
-
expect(result).toMatchObject({
|
|
773
|
-
fields: [
|
|
774
|
-
{
|
|
775
|
-
description:
|
|
776
|
-
'Please select the experience level that aligns with this role based on the job description (not the employees overall experience)',
|
|
777
|
-
label: 'Experience level',
|
|
778
|
-
name: 'experience_level',
|
|
779
|
-
type: 'radio',
|
|
780
|
-
required: false,
|
|
781
|
-
variant: 'card-expandable',
|
|
782
|
-
options: [
|
|
783
|
-
{
|
|
784
|
-
label: 'Junior level',
|
|
785
|
-
value: 'junior',
|
|
786
|
-
description:
|
|
787
|
-
'Entry level employees who perform tasks under the supervision of a more experienced employee.',
|
|
788
|
-
},
|
|
789
|
-
{
|
|
790
|
-
label: 'Mid level',
|
|
791
|
-
value: 'mid',
|
|
792
|
-
description:
|
|
793
|
-
'Employees who perform tasks with a good degree of autonomy and/or with coordination and control functions.',
|
|
794
|
-
},
|
|
795
|
-
{
|
|
796
|
-
label: 'Senior level',
|
|
797
|
-
value: 'senior',
|
|
798
|
-
description:
|
|
799
|
-
'Employees who perform tasks with a high degree of autonomy and/or with coordination and control functions.',
|
|
800
|
-
},
|
|
801
|
-
],
|
|
802
|
-
},
|
|
803
|
-
{
|
|
804
|
-
description: 'Chose how you want to be paid',
|
|
805
|
-
label: 'Payment method',
|
|
806
|
-
name: 'payment_method',
|
|
807
|
-
type: 'radio',
|
|
808
|
-
variant: 'card',
|
|
809
|
-
required: false,
|
|
810
|
-
options: [
|
|
811
|
-
{
|
|
812
|
-
label: 'Credit Card',
|
|
813
|
-
value: 'cc',
|
|
814
|
-
description: 'Plastic money, which is still money',
|
|
815
|
-
},
|
|
816
|
-
{
|
|
817
|
-
label: 'Cash',
|
|
818
|
-
value: 'cash',
|
|
819
|
-
description: 'Rules Everything Around Me',
|
|
820
|
-
},
|
|
821
|
-
],
|
|
822
|
-
},
|
|
823
|
-
],
|
|
824
|
-
});
|
|
825
|
-
});
|
|
826
|
-
|
|
827
|
-
// @BUG COD-1859
|
|
828
|
-
// it should validate when type is string but value is not a boolean
|
|
829
|
-
it('support "radio" field string-y type', () => {
|
|
830
|
-
const { fields, handleValidation } = createHeadlessForm(schemaInputTypeRadioStringY);
|
|
831
|
-
|
|
832
|
-
const validateForm = (vals) => friendlyError(handleValidation(vals));
|
|
833
|
-
|
|
834
|
-
expect(fields).toMatchObject([
|
|
835
|
-
{
|
|
836
|
-
description: 'Do you have any siblings?',
|
|
837
|
-
label: 'Has siblings',
|
|
838
|
-
name: 'has_siblings',
|
|
839
|
-
options: [
|
|
840
|
-
{
|
|
841
|
-
label: 'Yes',
|
|
842
|
-
value: 'true',
|
|
843
|
-
},
|
|
844
|
-
{
|
|
845
|
-
label: 'No',
|
|
846
|
-
value: 'false',
|
|
847
|
-
},
|
|
848
|
-
],
|
|
849
|
-
required: true,
|
|
850
|
-
schema: expect.any(Object),
|
|
851
|
-
type: 'radio',
|
|
852
|
-
},
|
|
853
|
-
]);
|
|
854
|
-
|
|
855
|
-
assertOptionsAllowed({
|
|
856
|
-
handleValidation,
|
|
857
|
-
fieldName: 'has_siblings',
|
|
858
|
-
validOptions: ['true', 'false'],
|
|
859
|
-
});
|
|
860
|
-
|
|
861
|
-
expect(validateForm({ has_siblings: false })).toEqual({
|
|
862
|
-
has_siblings: 'The option "false" is not valid.',
|
|
863
|
-
});
|
|
864
|
-
});
|
|
865
|
-
|
|
866
|
-
it('support "radio" field number type', () => {
|
|
867
|
-
const { fields, handleValidation } = createHeadlessForm(schemaInputTypeRadioNumber);
|
|
868
|
-
|
|
869
|
-
const validateForm = (vals) => friendlyError(handleValidation(vals));
|
|
870
|
-
|
|
871
|
-
expect(fields).toMatchObject([
|
|
872
|
-
{
|
|
873
|
-
description: 'How many siblings do you have?',
|
|
874
|
-
label: 'Number of siblings',
|
|
875
|
-
name: 'siblings_count',
|
|
876
|
-
options: [
|
|
877
|
-
{
|
|
878
|
-
label: 'One',
|
|
879
|
-
value: 1,
|
|
880
|
-
},
|
|
881
|
-
{
|
|
882
|
-
label: 'Two',
|
|
883
|
-
value: 2,
|
|
884
|
-
},
|
|
885
|
-
{
|
|
886
|
-
label: 'Three',
|
|
887
|
-
value: 3,
|
|
888
|
-
},
|
|
889
|
-
],
|
|
890
|
-
required: true,
|
|
891
|
-
schema: expect.any(Object),
|
|
892
|
-
type: 'radio',
|
|
893
|
-
},
|
|
894
|
-
]);
|
|
895
|
-
|
|
896
|
-
assertOptionsAllowed({
|
|
897
|
-
handleValidation,
|
|
898
|
-
fieldName: 'siblings_count',
|
|
899
|
-
validOptions: [1, 2, 3],
|
|
900
|
-
type: schemaInputTypeRadioNumber.properties.siblings_count.type,
|
|
901
|
-
});
|
|
902
|
-
|
|
903
|
-
expect(validateForm({ siblings_count: '3' })).toEqual({
|
|
904
|
-
siblings_count: 'The option "3" is not valid.',
|
|
905
|
-
});
|
|
906
|
-
});
|
|
907
|
-
|
|
908
|
-
it('support "radio" optional field', () => {
|
|
909
|
-
const { fields, handleValidation } = createHeadlessForm(
|
|
910
|
-
schemaInputTypeRadioRequiredAndOptional
|
|
911
|
-
);
|
|
912
|
-
const validateForm = (vals) => friendlyError(handleValidation(vals));
|
|
913
|
-
|
|
914
|
-
expect(fields).toMatchObject([
|
|
915
|
-
{},
|
|
916
|
-
{
|
|
917
|
-
name: 'has_car',
|
|
918
|
-
label: 'Has car',
|
|
919
|
-
description: 'Do you have a car? (optional field, check oneOf)',
|
|
920
|
-
options: [
|
|
921
|
-
{
|
|
922
|
-
label: 'Yes',
|
|
923
|
-
value: 'yes',
|
|
924
|
-
},
|
|
925
|
-
{
|
|
926
|
-
label: 'No',
|
|
927
|
-
value: 'no',
|
|
928
|
-
},
|
|
929
|
-
],
|
|
930
|
-
required: false,
|
|
931
|
-
schema: expect.any(Object),
|
|
932
|
-
type: 'radio',
|
|
933
|
-
},
|
|
934
|
-
]);
|
|
935
|
-
|
|
936
|
-
expect(
|
|
937
|
-
validateForm({
|
|
938
|
-
has_siblings: 'yes',
|
|
939
|
-
has_car: 'yes',
|
|
940
|
-
})
|
|
941
|
-
).toBeUndefined();
|
|
942
|
-
|
|
943
|
-
expect(validateForm({})).toEqual({
|
|
944
|
-
has_siblings: 'Required field',
|
|
945
|
-
});
|
|
946
|
-
});
|
|
947
|
-
|
|
948
|
-
function assertCommonBehavior(validateForm) {
|
|
949
|
-
// Note: Very similar to assertOptionsAllowed()
|
|
950
|
-
// We could reuse it in a next iteration.
|
|
951
|
-
|
|
952
|
-
// Happy path
|
|
953
|
-
expect(validateForm({ has_car: 'yes' })).toBeUndefined();
|
|
954
|
-
|
|
955
|
-
// Accepts undefined field
|
|
956
|
-
expect(validateForm({})).toBeUndefined();
|
|
957
|
-
|
|
958
|
-
// Does not accept other values
|
|
959
|
-
expect(validateForm({ has_car: 'blah-blah' })).toEqual({
|
|
960
|
-
has_car: 'The option "blah-blah" is not valid.',
|
|
961
|
-
});
|
|
962
|
-
|
|
963
|
-
// Does not accept "null" as string
|
|
964
|
-
expect(validateForm({ has_car: 'null' })).toEqual({
|
|
965
|
-
has_car: 'The option "null" is not valid.',
|
|
966
|
-
});
|
|
967
|
-
|
|
968
|
-
// Accepts empty string ("") — @BUG RMT-518
|
|
969
|
-
// Expectation: Does not accept empty string ("")
|
|
970
|
-
expect(validateForm({ has_car: '' })).toBeUndefined();
|
|
971
|
-
}
|
|
972
|
-
|
|
973
|
-
it('support "radio" optional field - optional (conventional way) - @BUG RMT-518', () => {
|
|
974
|
-
const { handleValidation } = createHeadlessForm(schemaInputRadioOptionalConventional);
|
|
975
|
-
const validateForm = (vals) => friendlyError(handleValidation(vals));
|
|
976
|
-
|
|
977
|
-
assertCommonBehavior(validateForm);
|
|
978
|
-
|
|
979
|
-
// Accepts null, even though it shouldn't @BUG RMT-518
|
|
980
|
-
// This is for cases where we (Remote) still have incorrect
|
|
981
|
-
// JSON Schemas in our Platform.
|
|
982
|
-
expect(validateForm({ has_car: null })).toBeUndefined();
|
|
983
|
-
// Expected:
|
|
984
|
-
// // Does NOT accept null value
|
|
985
|
-
// expect(validateForm({ has_car: null })).toEqual({
|
|
986
|
-
// has_car: 'The option null is not valid.',
|
|
987
|
-
// });
|
|
988
|
-
});
|
|
989
|
-
|
|
990
|
-
it('support "radio" optional field - optional with null option (as Remote does) - @BUG RMT-518', () => {
|
|
991
|
-
const { handleValidation } = createHeadlessForm(schemaInputRadioOptionalNull);
|
|
992
|
-
const validateForm = (vals) => friendlyError(handleValidation(vals));
|
|
993
|
-
|
|
994
|
-
assertCommonBehavior(validateForm);
|
|
995
|
-
|
|
996
|
-
// Accepts null value
|
|
997
|
-
expect(validateForm({ has_car: null })).toBeUndefined();
|
|
998
|
-
});
|
|
999
|
-
|
|
1000
|
-
it('support "radio" field type with extra info inside each option', () => {
|
|
1001
|
-
const result = createHeadlessForm(schemaInputTypeRadioOptionsWithDetails);
|
|
1002
|
-
|
|
1003
|
-
expect(result.fields).toHaveLength(1);
|
|
1004
|
-
|
|
1005
|
-
const fieldOptions = result.fields[0].options;
|
|
1006
|
-
|
|
1007
|
-
// The x-jsf-presentation content was spread to the root:
|
|
1008
|
-
expect(fieldOptions[0]).not.toHaveProperty('x-jsf-presentation');
|
|
1009
|
-
expect(fieldOptions).toEqual([
|
|
1010
|
-
{
|
|
1011
|
-
label: 'Basic',
|
|
1012
|
-
value: 'basic',
|
|
1013
|
-
meta: {
|
|
1014
|
-
displayCost: '$30.00/mo',
|
|
1015
|
-
},
|
|
1016
|
-
// Other x-* keywords are kept as it is.
|
|
1017
|
-
'x-another': 'extra-thing',
|
|
1018
|
-
},
|
|
1019
|
-
{
|
|
1020
|
-
label: 'Standard',
|
|
1021
|
-
value: 'standard',
|
|
1022
|
-
meta: {
|
|
1023
|
-
displayCost: '$50.00/mo',
|
|
1024
|
-
},
|
|
1025
|
-
},
|
|
1026
|
-
]);
|
|
1027
|
-
});
|
|
1028
|
-
|
|
1029
|
-
it('supports oneOf pattern validation', () => {
|
|
1030
|
-
const result = createHeadlessForm(mockTelWithPattern);
|
|
1031
|
-
|
|
1032
|
-
expect(result).toMatchObject({
|
|
1033
|
-
fields: [
|
|
1034
|
-
{
|
|
1035
|
-
label: 'Phone number',
|
|
1036
|
-
name: 'phone_number',
|
|
1037
|
-
type: 'tel',
|
|
1038
|
-
required: false,
|
|
1039
|
-
options: [
|
|
1040
|
-
{
|
|
1041
|
-
label: 'Portugal',
|
|
1042
|
-
pattern: '^(\\+351)[0-9]{9,}$',
|
|
1043
|
-
},
|
|
1044
|
-
{
|
|
1045
|
-
label: 'United Kingdom (UK)',
|
|
1046
|
-
pattern: '^(\\+44)[0-9]{1,}$',
|
|
1047
|
-
},
|
|
1048
|
-
{
|
|
1049
|
-
label: 'Bolivia',
|
|
1050
|
-
pattern: '^(\\+591)[0-9]{9,}$',
|
|
1051
|
-
},
|
|
1052
|
-
{
|
|
1053
|
-
label: 'Canada',
|
|
1054
|
-
pattern: '^(\\+1)(206|224)[0-9]{1,}$',
|
|
1055
|
-
},
|
|
1056
|
-
{
|
|
1057
|
-
label: 'United States',
|
|
1058
|
-
pattern: '^(\\+1)[0-9]{1,}$',
|
|
1059
|
-
},
|
|
1060
|
-
],
|
|
1061
|
-
},
|
|
1062
|
-
],
|
|
1063
|
-
});
|
|
1064
|
-
|
|
1065
|
-
const fieldValidator = result.fields[0].schema;
|
|
1066
|
-
|
|
1067
|
-
expect(fieldValidator.isValidSync('+351123123123')).toBe(true);
|
|
1068
|
-
expect(() => fieldValidator.validateSync('+35100')).toThrowError(
|
|
1069
|
-
'The option "+35100" is not valid.'
|
|
1070
|
-
);
|
|
1071
|
-
expect(fieldValidator.isValidSync(undefined)).toBe(true);
|
|
1072
|
-
});
|
|
1073
|
-
|
|
1074
|
-
it('support "radio" field type without oneOf options', () => {
|
|
1075
|
-
const result = createHeadlessForm(schemaInputTypeRadioWithoutOptions);
|
|
1076
|
-
|
|
1077
|
-
expect(result.fields).toHaveLength(1);
|
|
1078
|
-
|
|
1079
|
-
const fieldOptions = result.fields[0].options;
|
|
1080
|
-
expect(fieldOptions).toEqual([]);
|
|
1081
|
-
});
|
|
1082
|
-
});
|
|
1083
|
-
|
|
1084
|
-
it('support "integer" field type', () => {
|
|
1085
|
-
const result = createHeadlessForm(schemaInputTypeIntegerNumber);
|
|
1086
|
-
expect(result).toMatchObject({
|
|
1087
|
-
fields: [
|
|
1088
|
-
{
|
|
1089
|
-
description: 'How many open tabs do you have?',
|
|
1090
|
-
label: 'Tabs',
|
|
1091
|
-
name: 'tabs',
|
|
1092
|
-
required: false,
|
|
1093
|
-
schema: expect.any(Object),
|
|
1094
|
-
type: 'number',
|
|
1095
|
-
jsonType: 'integer',
|
|
1096
|
-
inputType: 'number',
|
|
1097
|
-
minimum: 1,
|
|
1098
|
-
maximum: 10,
|
|
1099
|
-
},
|
|
1100
|
-
],
|
|
1101
|
-
});
|
|
1102
|
-
|
|
1103
|
-
const fieldValidator = result.fields[0].schema;
|
|
1104
|
-
expect(fieldValidator.isValidSync('0')).toBe(false);
|
|
1105
|
-
expect(fieldValidator.isValidSync('10')).toBe(true);
|
|
1106
|
-
expect(fieldValidator.isValidSync('11')).toBe(false);
|
|
1107
|
-
expect(fieldValidator.isValidSync('5.5')).toBe(false);
|
|
1108
|
-
expect(fieldValidator.isValidSync('1.0')).toBe(true);
|
|
1109
|
-
expect(fieldValidator.isValidSync('this is text with a number 1')).toBe(false);
|
|
1110
|
-
expect(() => fieldValidator.validateSync('5.5')).toThrowError(
|
|
1111
|
-
'Must not contain decimal points. E.g. 5 instead of 5.5'
|
|
1112
|
-
);
|
|
1113
|
-
expect(() => fieldValidator.validateSync('some text')).toThrowError(
|
|
1114
|
-
'The value must be a number'
|
|
1115
|
-
);
|
|
1116
|
-
expect(() => fieldValidator.validateSync('')).toThrowError('The value must be a number');
|
|
1117
|
-
});
|
|
1118
|
-
|
|
1119
|
-
it('support "number" field type', () => {
|
|
1120
|
-
const result = createHeadlessForm(schemaInputTypeNumber);
|
|
1121
|
-
expect(result).toMatchObject({
|
|
1122
|
-
fields: [
|
|
1123
|
-
{
|
|
1124
|
-
description: 'How many open tabs do you have?',
|
|
1125
|
-
label: 'Tabs',
|
|
1126
|
-
name: 'tabs',
|
|
1127
|
-
required: true,
|
|
1128
|
-
schema: expect.any(Object),
|
|
1129
|
-
type: 'number',
|
|
1130
|
-
minimum: 1,
|
|
1131
|
-
maximum: 10,
|
|
1132
|
-
},
|
|
1133
|
-
],
|
|
1134
|
-
});
|
|
1135
|
-
|
|
1136
|
-
const fieldValidator = result.fields[0].schema;
|
|
1137
|
-
expect(fieldValidator.isValidSync('0')).toBe(false);
|
|
1138
|
-
expect(fieldValidator.isValidSync('10')).toBe(true);
|
|
1139
|
-
expect(fieldValidator.isValidSync('11')).toBe(false);
|
|
1140
|
-
expect(fieldValidator.isValidSync('this is text with a number 1')).toBe(false);
|
|
1141
|
-
expect(() => fieldValidator.validateSync('some text')).toThrowError(
|
|
1142
|
-
'The value must be a number'
|
|
1143
|
-
);
|
|
1144
|
-
expect(() => fieldValidator.validateSync('')).toThrowError('The value must be a number');
|
|
1145
|
-
});
|
|
1146
|
-
|
|
1147
|
-
it('support "number" field type with the percentage attribute', () => {
|
|
1148
|
-
const result = createHeadlessForm(schemaInputTypeNumberWithPercentage);
|
|
1149
|
-
expect(result).toMatchObject({
|
|
1150
|
-
fields: [
|
|
1151
|
-
{
|
|
1152
|
-
description: 'What % of shares do you own?',
|
|
1153
|
-
label: 'Shares',
|
|
1154
|
-
name: 'shares',
|
|
1155
|
-
percentage: true,
|
|
1156
|
-
required: true,
|
|
1157
|
-
schema: expect.any(Object),
|
|
1158
|
-
type: 'number',
|
|
1159
|
-
minimum: 1,
|
|
1160
|
-
maximum: 100,
|
|
1161
|
-
},
|
|
1162
|
-
],
|
|
1163
|
-
});
|
|
1164
|
-
|
|
1165
|
-
const fieldValidator = result.fields[0].schema;
|
|
1166
|
-
const { percentage } = result.fields[0];
|
|
1167
|
-
expect(fieldValidator.isValidSync('0')).toBe(false);
|
|
1168
|
-
expect(fieldValidator.isValidSync('10')).toBe(true);
|
|
1169
|
-
expect(fieldValidator.isValidSync('101')).toBe(false);
|
|
1170
|
-
expect(fieldValidator.isValidSync('this is text with a number 1')).toBe(false);
|
|
1171
|
-
expect(() => fieldValidator.validateSync('some text')).toThrowError(
|
|
1172
|
-
'The value must be a number'
|
|
1173
|
-
);
|
|
1174
|
-
expect(() => fieldValidator.validateSync('')).toThrowError('The value must be a number');
|
|
1175
|
-
expect(percentage).toBe(true);
|
|
1176
|
-
});
|
|
1177
|
-
|
|
1178
|
-
it('support "number" field type with the percentage attribute and custom range values', () => {
|
|
1179
|
-
const result = createHeadlessForm(
|
|
1180
|
-
JSONSchemaBuilder()
|
|
1181
|
-
.addInput({
|
|
1182
|
-
shares: {
|
|
1183
|
-
...mockNumberInputWithPercentageAndCustomRange,
|
|
1184
|
-
},
|
|
1185
|
-
})
|
|
1186
|
-
.setRequiredFields(['shares'])
|
|
1187
|
-
.build()
|
|
1188
|
-
);
|
|
1189
|
-
|
|
1190
|
-
expect(result).toMatchObject({
|
|
1191
|
-
fields: [
|
|
1192
|
-
{
|
|
1193
|
-
description: 'What % of shares do you own?',
|
|
1194
|
-
label: 'Shares',
|
|
1195
|
-
name: 'shares',
|
|
1196
|
-
percentage: true,
|
|
1197
|
-
required: true,
|
|
1198
|
-
schema: expect.any(Object),
|
|
1199
|
-
type: 'number',
|
|
1200
|
-
minimum: 50,
|
|
1201
|
-
maximum: 70,
|
|
1202
|
-
},
|
|
1203
|
-
],
|
|
1204
|
-
});
|
|
1205
|
-
|
|
1206
|
-
const fieldValidatorCustom = result.fields[0].schema;
|
|
1207
|
-
const { percentage: percentageCustom } = result.fields[0];
|
|
1208
|
-
expect(fieldValidatorCustom.isValidSync('0')).toBe(false);
|
|
1209
|
-
expect(fieldValidatorCustom.isValidSync('49')).toBe(false);
|
|
1210
|
-
expect(fieldValidatorCustom.isValidSync('55')).toBe(true);
|
|
1211
|
-
expect(fieldValidatorCustom.isValidSync('70')).toBe(true);
|
|
1212
|
-
expect(fieldValidatorCustom.isValidSync('101')).toBe(false);
|
|
1213
|
-
expect(fieldValidatorCustom.isValidSync('this is text with a number 1')).toBe(false);
|
|
1214
|
-
expect(() => fieldValidatorCustom.validateSync('some text')).toThrowError(
|
|
1215
|
-
'The value must be a number'
|
|
1216
|
-
);
|
|
1217
|
-
expect(() => fieldValidatorCustom.validateSync('')).toThrowError(
|
|
1218
|
-
'The value must be a number'
|
|
1219
|
-
);
|
|
1220
|
-
expect(percentageCustom).toBe(true);
|
|
1221
|
-
});
|
|
1222
|
-
|
|
1223
|
-
it('support "date" field type', () => {
|
|
1224
|
-
const { fields, handleValidation } = createHeadlessForm(schemaInputTypeDate);
|
|
1225
|
-
|
|
1226
|
-
const validateForm = (vals) => friendlyError(handleValidation(vals));
|
|
1227
|
-
|
|
1228
|
-
expect(fields[0]).toMatchObject({
|
|
1229
|
-
label: 'Birthdate',
|
|
1230
|
-
name: 'birthdate',
|
|
1231
|
-
required: true,
|
|
1232
|
-
schema: expect.any(Object),
|
|
1233
|
-
type: 'date',
|
|
1234
|
-
minDate: '1922-03-01',
|
|
1235
|
-
maxDate: '2022-03-17',
|
|
1236
|
-
});
|
|
1237
|
-
|
|
1238
|
-
const todayDateHint = new Date().toISOString().substring(0, 10);
|
|
1239
|
-
|
|
1240
|
-
expect(validateForm({})).toEqual({
|
|
1241
|
-
birthdate: 'Required field',
|
|
1242
|
-
});
|
|
1243
|
-
|
|
1244
|
-
expect(validateForm({ birthdate: '2020-10-10' })).toBeUndefined();
|
|
1245
|
-
expect(validateForm({ birthdate: '2020-13-10' })).toEqual({
|
|
1246
|
-
birthdate: `Must be a valid date in yyyy-mm-dd format. e.g. ${todayDateHint}`,
|
|
1247
|
-
});
|
|
1248
|
-
});
|
|
1249
|
-
|
|
1250
|
-
describe('support "date" field type', () => {
|
|
1251
|
-
it('support "date" field type with a minDate', () => {
|
|
1252
|
-
const { fields, handleValidation } = createHeadlessForm(schemaInputTypeDate);
|
|
1253
|
-
|
|
1254
|
-
const validateForm = (vals) => friendlyError(handleValidation(vals));
|
|
1255
|
-
|
|
1256
|
-
expect(fields[0]).toMatchObject({
|
|
1257
|
-
label: 'Birthdate',
|
|
1258
|
-
name: 'birthdate',
|
|
1259
|
-
required: true,
|
|
1260
|
-
schema: expect.any(Object),
|
|
1261
|
-
type: 'date',
|
|
1262
|
-
minDate: '1922-03-01',
|
|
1263
|
-
maxDate: '2022-03-17',
|
|
1264
|
-
});
|
|
1265
|
-
|
|
1266
|
-
expect(validateForm({})).toEqual({
|
|
1267
|
-
birthdate: 'Required field',
|
|
1268
|
-
});
|
|
1269
|
-
|
|
1270
|
-
expect(validateForm({ birthdate: '' })).toEqual({
|
|
1271
|
-
birthdate: `Required field`,
|
|
1272
|
-
});
|
|
1273
|
-
|
|
1274
|
-
expect(validateForm({ birthdate: '1922-02-01' })).toEqual({
|
|
1275
|
-
birthdate: 'The date must be 1922-03-01 or after.',
|
|
1276
|
-
});
|
|
1277
|
-
|
|
1278
|
-
expect(validateForm({ birthdate: '1922-03-01' })).toBeUndefined();
|
|
1279
|
-
|
|
1280
|
-
expect(validateForm({ birthdate: '2021-03-01' })).toBeUndefined();
|
|
1281
|
-
});
|
|
1282
|
-
|
|
1283
|
-
it('support "date" field type with a maxDate', () => {
|
|
1284
|
-
const { fields, handleValidation } = createHeadlessForm(schemaInputTypeDate);
|
|
1285
|
-
|
|
1286
|
-
const validateForm = (vals) => friendlyError(handleValidation(vals));
|
|
1287
|
-
|
|
1288
|
-
expect(fields[0]).toMatchObject({
|
|
1289
|
-
label: 'Birthdate',
|
|
1290
|
-
name: 'birthdate',
|
|
1291
|
-
required: true,
|
|
1292
|
-
schema: expect.any(Object),
|
|
1293
|
-
type: 'date',
|
|
1294
|
-
minDate: '1922-03-01',
|
|
1295
|
-
maxDate: '2022-03-17',
|
|
1296
|
-
});
|
|
1297
|
-
|
|
1298
|
-
expect(validateForm({ birthdate: '' })).toEqual({
|
|
1299
|
-
birthdate: `Required field`,
|
|
1300
|
-
});
|
|
1301
|
-
|
|
1302
|
-
expect(validateForm({ birthdate: '2022-02-01' })).toBeUndefined();
|
|
1303
|
-
expect(validateForm({ birthdate: '2022-03-01' })).toBeUndefined();
|
|
1304
|
-
expect(validateForm({ birthdate: '2022-04-01' })).toEqual({
|
|
1305
|
-
birthdate: 'The date must be 2022-03-17 or before.',
|
|
1306
|
-
});
|
|
1307
|
-
});
|
|
1308
|
-
|
|
1309
|
-
it('support format date with minDate and maxDate', () => {
|
|
1310
|
-
const schemaFormatDate = {
|
|
1311
|
-
properties: {
|
|
1312
|
-
birthdate: {
|
|
1313
|
-
title: 'Birthdate',
|
|
1314
|
-
type: 'string',
|
|
1315
|
-
format: 'date',
|
|
1316
|
-
'x-jsf-presentation': {
|
|
1317
|
-
inputType: 'myDateType',
|
|
1318
|
-
maxDate: '2022-03-01',
|
|
1319
|
-
minDate: '1922-03-01',
|
|
1320
|
-
},
|
|
1321
|
-
},
|
|
1322
|
-
},
|
|
1323
|
-
};
|
|
1324
|
-
|
|
1325
|
-
const { handleValidation } = createHeadlessForm(schemaFormatDate);
|
|
1326
|
-
const validateForm = (vals) => friendlyError(handleValidation(vals));
|
|
1327
|
-
|
|
1328
|
-
expect(validateForm({ birthdate: '1922-02-01' })).toEqual({
|
|
1329
|
-
birthdate: 'The date must be 1922-03-01 or after.',
|
|
1330
|
-
});
|
|
1331
|
-
});
|
|
1332
|
-
});
|
|
1333
|
-
|
|
1334
|
-
describe('supports "file" field type', () => {
|
|
1335
|
-
it('supports "file" field type', () => {
|
|
1336
|
-
const result = createHeadlessForm(
|
|
1337
|
-
JSONSchemaBuilder()
|
|
1338
|
-
.addInput({
|
|
1339
|
-
fileInput: mockFileInput,
|
|
1340
|
-
})
|
|
1341
|
-
.build()
|
|
1342
|
-
);
|
|
1343
|
-
|
|
1344
|
-
expect(result).toMatchObject({
|
|
1345
|
-
fields: [
|
|
1346
|
-
{
|
|
1347
|
-
type: 'file',
|
|
1348
|
-
fileDownload: 'http://some.domain.com/file-name.pdf',
|
|
1349
|
-
description: 'File Input Description',
|
|
1350
|
-
fileName: 'My File',
|
|
1351
|
-
label: 'File Input',
|
|
1352
|
-
name: 'fileInput',
|
|
1353
|
-
required: false,
|
|
1354
|
-
accept: '.png,.jpg,.jpeg,.pdf',
|
|
1355
|
-
},
|
|
1356
|
-
],
|
|
1357
|
-
});
|
|
1358
|
-
});
|
|
1359
|
-
|
|
1360
|
-
describe('when a field has accepted extensions', () => {
|
|
1361
|
-
let fields;
|
|
1362
|
-
beforeEach(() => {
|
|
1363
|
-
const result = createHeadlessForm(
|
|
1364
|
-
JSONSchemaBuilder().addInput({ fileInput: mockFileInput }).build()
|
|
1365
|
-
);
|
|
1366
|
-
fields = result.fields;
|
|
1367
|
-
});
|
|
1368
|
-
|
|
1369
|
-
describe('and file is of incorrect format', () => {
|
|
1370
|
-
const file = new File(['foo'], 'file.txt', {
|
|
1371
|
-
type: 'text/plain',
|
|
1372
|
-
});
|
|
1373
|
-
|
|
1374
|
-
it('should throw an error', async () =>
|
|
1375
|
-
expect(
|
|
1376
|
-
object()
|
|
1377
|
-
.shape({
|
|
1378
|
-
fileInput: fields[0].schema,
|
|
1379
|
-
})
|
|
1380
|
-
.validate({ fileInput: [file] })
|
|
1381
|
-
).rejects.toMatchObject({
|
|
1382
|
-
errors: ['Unsupported file format. The acceptable formats are .png,.jpg,.jpeg,.pdf.'],
|
|
1383
|
-
}));
|
|
1384
|
-
});
|
|
1385
|
-
|
|
1386
|
-
describe('and file is of correct format', () => {
|
|
1387
|
-
const file = new File(['foo'], 'file.png', {
|
|
1388
|
-
type: 'image/png',
|
|
1389
|
-
});
|
|
1390
|
-
Object.defineProperty(file, 'size', { value: 1024 * 1024 });
|
|
1391
|
-
|
|
1392
|
-
const assertObj = { fileInput: [file] };
|
|
1393
|
-
it('should validate field', async () =>
|
|
1394
|
-
expect(
|
|
1395
|
-
object()
|
|
1396
|
-
.shape({
|
|
1397
|
-
fileInput: fields[0].schema,
|
|
1398
|
-
})
|
|
1399
|
-
.validate({ fileInput: [file] })
|
|
1400
|
-
).resolves.toEqual(assertObj));
|
|
1401
|
-
});
|
|
1402
|
-
|
|
1403
|
-
describe('and file is of correct but uppercase format ', () => {
|
|
1404
|
-
const file = new File(['foo'], 'file.PNG', {
|
|
1405
|
-
type: 'image/png',
|
|
1406
|
-
});
|
|
1407
|
-
Object.defineProperty(file, 'size', { value: 1024 * 1024 });
|
|
1408
|
-
|
|
1409
|
-
const assertObj = { fileInput: [file] };
|
|
1410
|
-
it('should validate field', async () =>
|
|
1411
|
-
expect(
|
|
1412
|
-
object()
|
|
1413
|
-
.shape({
|
|
1414
|
-
fileInput: fields[0].schema,
|
|
1415
|
-
})
|
|
1416
|
-
.validate({ fileInput: [file] })
|
|
1417
|
-
).resolves.toEqual(assertObj));
|
|
1418
|
-
});
|
|
1419
|
-
|
|
1420
|
-
describe('and file is not instance of a File', () => {
|
|
1421
|
-
it('accepts if file object has name property', async () => {
|
|
1422
|
-
expect(
|
|
1423
|
-
object()
|
|
1424
|
-
.shape({
|
|
1425
|
-
fileInput: fields[0].schema,
|
|
1426
|
-
})
|
|
1427
|
-
.validate({ fileInput: [{ name: 'foo.pdf' }] })
|
|
1428
|
-
).resolves.toEqual({ fileInput: [{ name: 'foo.pdf' }] });
|
|
1429
|
-
});
|
|
1430
|
-
|
|
1431
|
-
it('should validate format', async () =>
|
|
1432
|
-
expect(
|
|
1433
|
-
object()
|
|
1434
|
-
.shape({
|
|
1435
|
-
fileInput: fields[0].schema,
|
|
1436
|
-
})
|
|
1437
|
-
.validate({ fileInput: [{ name: 'foo.txt' }] })
|
|
1438
|
-
).rejects.toMatchObject({
|
|
1439
|
-
errors: ['Unsupported file format. The acceptable formats are .png,.jpg,.jpeg,.pdf.'],
|
|
1440
|
-
}));
|
|
1441
|
-
|
|
1442
|
-
it('should validate max size', async () =>
|
|
1443
|
-
expect(
|
|
1444
|
-
object()
|
|
1445
|
-
.shape({
|
|
1446
|
-
fileInput: fields[0].schema,
|
|
1447
|
-
})
|
|
1448
|
-
.validate({ fileInput: [{ name: 'foo.txt', size: 1024 * 1024 * 1024 }] })
|
|
1449
|
-
).rejects.toMatchObject({ errors: ['File size too large. The limit is 20 MB.'] }));
|
|
1450
|
-
|
|
1451
|
-
it('throw an error if invalid file object', async () =>
|
|
1452
|
-
expect(
|
|
1453
|
-
object()
|
|
1454
|
-
.shape({
|
|
1455
|
-
fileInput: fields[0].schema,
|
|
1456
|
-
})
|
|
1457
|
-
.validate({ fileInput: [{ path: 'foo.txt' }] })
|
|
1458
|
-
).rejects.toMatchObject({ errors: ['Not a valid file.'] }));
|
|
1459
|
-
});
|
|
1460
|
-
});
|
|
1461
|
-
|
|
1462
|
-
describe('when a field has max file size', () => {
|
|
1463
|
-
let fields;
|
|
1464
|
-
beforeEach(() => {
|
|
1465
|
-
const result = createHeadlessForm(
|
|
1466
|
-
JSONSchemaBuilder().addInput({ fileInput: mockFileInput }).build()
|
|
1467
|
-
);
|
|
1468
|
-
fields = result.fields;
|
|
1469
|
-
});
|
|
1470
|
-
describe('and file is greater than that', () => {
|
|
1471
|
-
const file = new File([''], 'file.png');
|
|
1472
|
-
Object.defineProperty(file, 'size', { value: 1024 * 1024 * 1024 });
|
|
1473
|
-
|
|
1474
|
-
it('should throw an error', async () =>
|
|
1475
|
-
expect(
|
|
1476
|
-
object()
|
|
1477
|
-
.shape({
|
|
1478
|
-
fileInput: fields[0].schema,
|
|
1479
|
-
})
|
|
1480
|
-
.validate({ fileInput: [file] })
|
|
1481
|
-
).rejects.toMatchObject({ errors: ['File size too large. The limit is 20 MB.'] }));
|
|
1482
|
-
});
|
|
1483
|
-
describe('and file is smaller than that', () => {
|
|
1484
|
-
const file = new File([''], 'file.png');
|
|
1485
|
-
Object.defineProperty(file, 'size', { value: 1024 * 1024 });
|
|
1486
|
-
|
|
1487
|
-
const assertObj = { fileInput: [file] };
|
|
1488
|
-
it('should validate field', async () =>
|
|
1489
|
-
expect(
|
|
1490
|
-
object()
|
|
1491
|
-
.shape({
|
|
1492
|
-
fileInput: fields[0].schema,
|
|
1493
|
-
})
|
|
1494
|
-
.validate({ fileInput: [file] })
|
|
1495
|
-
).resolves.toEqual(assertObj));
|
|
1496
|
-
});
|
|
1497
|
-
});
|
|
1498
|
-
|
|
1499
|
-
describe('when a field file is optional', () => {
|
|
1500
|
-
it('it accepts an empty array', () => {
|
|
1501
|
-
const result = createHeadlessForm(
|
|
1502
|
-
JSONSchemaBuilder().addInput({ fileInput: mockFileInput }).build()
|
|
1503
|
-
);
|
|
1504
|
-
const emptyFile = { fileInput: [] };
|
|
1505
|
-
expect(
|
|
1506
|
-
object()
|
|
1507
|
-
.shape({
|
|
1508
|
-
fileInput: result.fields[0].schema,
|
|
1509
|
-
})
|
|
1510
|
-
.validate(emptyFile)
|
|
1511
|
-
).resolves.toEqual(emptyFile);
|
|
1512
|
-
});
|
|
1513
|
-
|
|
1514
|
-
it('it validates missing file correctly', () => {
|
|
1515
|
-
const { handleValidation } = createHeadlessForm(
|
|
1516
|
-
JSONSchemaBuilder().addInput({ fileInput: mockFileInput }).build()
|
|
1517
|
-
);
|
|
1518
|
-
const validateForm = (vals) => friendlyError(handleValidation(vals));
|
|
1519
|
-
|
|
1520
|
-
expect(validateForm({})).toBeUndefined();
|
|
1521
|
-
expect(validateForm({ fileInput: null })).toBeUndefined();
|
|
1522
|
-
});
|
|
1523
|
-
});
|
|
1524
|
-
|
|
1525
|
-
describe('when a field file is required', () => {
|
|
1526
|
-
it('it validates missing file correctly', () => {
|
|
1527
|
-
const { handleValidation } = createHeadlessForm(schemaInputTypeFile);
|
|
1528
|
-
const validateForm = (vals) => friendlyError(handleValidation(vals));
|
|
1529
|
-
|
|
1530
|
-
expect(validateForm({})).toEqual({
|
|
1531
|
-
a_file: 'Required field',
|
|
1532
|
-
});
|
|
1533
|
-
|
|
1534
|
-
expect(
|
|
1535
|
-
validateForm({
|
|
1536
|
-
a_file: null,
|
|
1537
|
-
})
|
|
1538
|
-
).toEqual({
|
|
1539
|
-
a_file: 'Required field',
|
|
1540
|
-
});
|
|
1541
|
-
});
|
|
1542
|
-
});
|
|
1543
|
-
});
|
|
1544
|
-
|
|
1545
|
-
describe('supports "group-array" field type', () => {
|
|
1546
|
-
it('basic test', () => {
|
|
1547
|
-
const result = createHeadlessForm(
|
|
1548
|
-
JSONSchemaBuilder()
|
|
1549
|
-
.addInput({
|
|
1550
|
-
dependent_details: mockGroupArrayInput,
|
|
1551
|
-
})
|
|
1552
|
-
.build()
|
|
1553
|
-
);
|
|
1554
|
-
|
|
1555
|
-
expect(result).toMatchObject({
|
|
1556
|
-
fields: [
|
|
1557
|
-
{
|
|
1558
|
-
type: 'group-array',
|
|
1559
|
-
description: 'Add the dependents you claim below',
|
|
1560
|
-
label: 'Child details',
|
|
1561
|
-
name: 'dependent_details',
|
|
1562
|
-
required: false,
|
|
1563
|
-
fields: expect.any(Function),
|
|
1564
|
-
addFieldText: 'Add new field',
|
|
1565
|
-
},
|
|
1566
|
-
],
|
|
1567
|
-
});
|
|
1568
|
-
|
|
1569
|
-
// Validations
|
|
1570
|
-
const fieldValidator = result.fields[0].schema;
|
|
1571
|
-
// nthfields are required
|
|
1572
|
-
expect(
|
|
1573
|
-
fieldValidator.isValidSync([
|
|
1574
|
-
{
|
|
1575
|
-
birthdate: '',
|
|
1576
|
-
full_name: '',
|
|
1577
|
-
sex: '',
|
|
1578
|
-
},
|
|
1579
|
-
])
|
|
1580
|
-
).toBe(false);
|
|
1581
|
-
// date is invalid
|
|
1582
|
-
expect(
|
|
1583
|
-
fieldValidator.isValidSync([
|
|
1584
|
-
{
|
|
1585
|
-
birthdate: 'invalidate date',
|
|
1586
|
-
full_name: 'John Doe',
|
|
1587
|
-
sex: 'male',
|
|
1588
|
-
},
|
|
1589
|
-
])
|
|
1590
|
-
).toBe(false);
|
|
1591
|
-
// all good
|
|
1592
|
-
expect(
|
|
1593
|
-
fieldValidator.isValidSync([
|
|
1594
|
-
{
|
|
1595
|
-
birthdate: '2021-12-04',
|
|
1596
|
-
full_name: 'John Doe',
|
|
1597
|
-
sex: 'male',
|
|
1598
|
-
},
|
|
1599
|
-
])
|
|
1600
|
-
).toBe(true);
|
|
1601
|
-
|
|
1602
|
-
const nestedFieldsFromResult = result.fields[0].fields();
|
|
1603
|
-
expect(nestedFieldsFromResult).toMatchObject([
|
|
1604
|
-
{
|
|
1605
|
-
type: 'text',
|
|
1606
|
-
description: 'Enter your child’s full name',
|
|
1607
|
-
maxLength: 255,
|
|
1608
|
-
label: 'Child Full Name',
|
|
1609
|
-
name: 'full_name',
|
|
1610
|
-
required: true,
|
|
1611
|
-
},
|
|
1612
|
-
{
|
|
1613
|
-
type: 'date',
|
|
1614
|
-
name: 'birthdate',
|
|
1615
|
-
label: 'Child Birthdate',
|
|
1616
|
-
required: true,
|
|
1617
|
-
description: 'Enter your child’s date of birth',
|
|
1618
|
-
maxLength: 255,
|
|
1619
|
-
},
|
|
1620
|
-
{
|
|
1621
|
-
type: 'radio',
|
|
1622
|
-
name: 'sex',
|
|
1623
|
-
label: 'Child Sex',
|
|
1624
|
-
options: [
|
|
1625
|
-
{
|
|
1626
|
-
label: 'Male',
|
|
1627
|
-
value: 'male',
|
|
1628
|
-
},
|
|
1629
|
-
{
|
|
1630
|
-
label: 'Female',
|
|
1631
|
-
value: 'female',
|
|
1632
|
-
},
|
|
1633
|
-
],
|
|
1634
|
-
required: true,
|
|
1635
|
-
description:
|
|
1636
|
-
'We know sex is non-binary but for insurance and payroll purposes, we need to collect this information.',
|
|
1637
|
-
},
|
|
1638
|
-
]);
|
|
1639
|
-
});
|
|
1640
|
-
|
|
1641
|
-
it('nested fields (native, core and custom) has correct validations', () => {
|
|
1642
|
-
const { handleValidation } = createHeadlessForm({
|
|
1643
|
-
properties: {
|
|
1644
|
-
break_schedule: {
|
|
1645
|
-
title: 'Work schedule',
|
|
1646
|
-
type: 'array',
|
|
1647
|
-
presentation: {
|
|
1648
|
-
inputType: 'group-array',
|
|
1649
|
-
},
|
|
1650
|
-
items: {
|
|
1651
|
-
properties: {
|
|
1652
|
-
minutes_native: {
|
|
1653
|
-
title: 'Minutes of break (native)',
|
|
1654
|
-
type: 'integer',
|
|
1655
|
-
minimum: 60,
|
|
1656
|
-
// without presentation.inputType
|
|
1657
|
-
},
|
|
1658
|
-
minutes_core: {
|
|
1659
|
-
title: 'Minutes of break (core)',
|
|
1660
|
-
type: 'integer',
|
|
1661
|
-
minimum: 60,
|
|
1662
|
-
presentation: {
|
|
1663
|
-
inputType: 'number', // a core inputType
|
|
1664
|
-
},
|
|
1665
|
-
},
|
|
1666
|
-
minutes_custom: {
|
|
1667
|
-
title: 'Minutes of break (custom)',
|
|
1668
|
-
type: 'integer',
|
|
1669
|
-
minimum: 60,
|
|
1670
|
-
presentation: {
|
|
1671
|
-
inputType: 'hour', // a custom inputType
|
|
1672
|
-
},
|
|
1673
|
-
},
|
|
1674
|
-
},
|
|
1675
|
-
required: ['weekday', 'minutes_native', 'minutes_core', 'minutes_custom'],
|
|
1676
|
-
},
|
|
1677
|
-
},
|
|
1678
|
-
},
|
|
1679
|
-
required: ['break_schedule'],
|
|
1680
|
-
});
|
|
1681
|
-
const validateForm = (vals) => friendlyError(handleValidation(vals));
|
|
1682
|
-
|
|
1683
|
-
// Given empty, it says it's required
|
|
1684
|
-
expect(validateForm({})).toEqual({
|
|
1685
|
-
break_schedule: 'Required field',
|
|
1686
|
-
});
|
|
1687
|
-
|
|
1688
|
-
// Given empty fields, it mentions nested required fields
|
|
1689
|
-
expect(
|
|
1690
|
-
validateForm({
|
|
1691
|
-
break_schedule: [{}],
|
|
1692
|
-
})
|
|
1693
|
-
).toEqual({
|
|
1694
|
-
break_schedule: [
|
|
1695
|
-
{
|
|
1696
|
-
minutes_native: 'Required field',
|
|
1697
|
-
minutes_core: 'Required field',
|
|
1698
|
-
minutes_custom: 'Required field',
|
|
1699
|
-
},
|
|
1700
|
-
],
|
|
1701
|
-
});
|
|
1702
|
-
|
|
1703
|
-
// Given correct values, it's all valid.
|
|
1704
|
-
expect(
|
|
1705
|
-
validateForm({
|
|
1706
|
-
break_schedule: [
|
|
1707
|
-
{
|
|
1708
|
-
minutes_native: 60,
|
|
1709
|
-
minutes_core: 60,
|
|
1710
|
-
minutes_custom: 60,
|
|
1711
|
-
},
|
|
1712
|
-
],
|
|
1713
|
-
})
|
|
1714
|
-
).toBeUndefined();
|
|
1715
|
-
|
|
1716
|
-
// Given invalid values, the validation is triggered.
|
|
1717
|
-
expect(
|
|
1718
|
-
validateForm({
|
|
1719
|
-
break_schedule: [
|
|
1720
|
-
{
|
|
1721
|
-
minutes_native: 50,
|
|
1722
|
-
minutes_core: 50,
|
|
1723
|
-
minutes_custom: 50,
|
|
1724
|
-
},
|
|
1725
|
-
],
|
|
1726
|
-
})
|
|
1727
|
-
).toEqual({
|
|
1728
|
-
break_schedule: [
|
|
1729
|
-
{
|
|
1730
|
-
minutes_core: 'Must be greater or equal to 60',
|
|
1731
|
-
minutes_native: 'Must be greater or equal to 60',
|
|
1732
|
-
minutes_custom: 'Must be greater or equal to 60',
|
|
1733
|
-
},
|
|
1734
|
-
],
|
|
1735
|
-
});
|
|
1736
|
-
});
|
|
1737
|
-
|
|
1738
|
-
it('nested "group-array" fields', () => {
|
|
1739
|
-
const result = createHeadlessForm({
|
|
1740
|
-
properties: {
|
|
1741
|
-
nestedGroupArray: nestedGroupArrayForm,
|
|
1742
|
-
},
|
|
1743
|
-
});
|
|
1744
|
-
|
|
1745
|
-
expect(result.fields[0]).toMatchObject({
|
|
1746
|
-
label: 'Parent object',
|
|
1747
|
-
name: 'nestedGroupArray',
|
|
1748
|
-
required: false,
|
|
1749
|
-
type: 'group-array',
|
|
1750
|
-
inputType: 'group-array',
|
|
1751
|
-
jsonType: 'array',
|
|
1752
|
-
fields: expect.any(Function),
|
|
1753
|
-
});
|
|
1754
|
-
|
|
1755
|
-
expect(result.fields[0].fields()).toMatchObject([
|
|
1756
|
-
{
|
|
1757
|
-
type: 'text',
|
|
1758
|
-
description: 'Simple text field',
|
|
1759
|
-
maxLength: 255,
|
|
1760
|
-
label: 'Outer Field',
|
|
1761
|
-
name: 'notNested',
|
|
1762
|
-
required: true,
|
|
1763
|
-
},
|
|
1764
|
-
{
|
|
1765
|
-
label: 'Nested group-array',
|
|
1766
|
-
name: 'nested',
|
|
1767
|
-
required: true,
|
|
1768
|
-
type: 'group-array',
|
|
1769
|
-
inputType: 'group-array',
|
|
1770
|
-
jsonType: 'array',
|
|
1771
|
-
fields: expect.any(Function),
|
|
1772
|
-
},
|
|
1773
|
-
]);
|
|
1774
|
-
|
|
1775
|
-
expect(result.fields[0].fields()[1].fields()).toMatchObject([
|
|
1776
|
-
{
|
|
1777
|
-
type: 'text',
|
|
1778
|
-
description: 'First nested text field',
|
|
1779
|
-
maxLength: 255,
|
|
1780
|
-
label: 'Inner Field 1',
|
|
1781
|
-
name: 'nestedField1',
|
|
1782
|
-
required: true,
|
|
1783
|
-
},
|
|
1784
|
-
{
|
|
1785
|
-
type: 'text',
|
|
1786
|
-
description: 'Second nested text field',
|
|
1787
|
-
maxLength: 255,
|
|
1788
|
-
label: 'Inner Field 2',
|
|
1789
|
-
name: 'nestedField2',
|
|
1790
|
-
required: true,
|
|
1791
|
-
},
|
|
1792
|
-
]);
|
|
1793
|
-
});
|
|
1794
|
-
|
|
1795
|
-
it('can pass custom field attributes', () => {
|
|
1796
|
-
const result = createHeadlessForm(
|
|
1797
|
-
{
|
|
1798
|
-
properties: {
|
|
1799
|
-
children_basic: mockGroupArrayInput,
|
|
1800
|
-
children_custom: mockGroupArrayInput,
|
|
1801
|
-
},
|
|
1802
|
-
},
|
|
1803
|
-
{
|
|
1804
|
-
customProperties: {
|
|
1805
|
-
children_custom: {
|
|
1806
|
-
'data-foo': 'baz',
|
|
1807
|
-
},
|
|
1808
|
-
},
|
|
1809
|
-
}
|
|
1810
|
-
);
|
|
1811
|
-
|
|
1812
|
-
expect(result).toMatchObject({
|
|
1813
|
-
fields: [
|
|
1814
|
-
{
|
|
1815
|
-
label: 'Child details',
|
|
1816
|
-
name: 'children_basic',
|
|
1817
|
-
required: false,
|
|
1818
|
-
type: 'group-array',
|
|
1819
|
-
inputType: 'group-array',
|
|
1820
|
-
jsonType: 'array',
|
|
1821
|
-
fields: expect.any(Function), // This is what makes the field work
|
|
1822
|
-
},
|
|
1823
|
-
{
|
|
1824
|
-
label: 'Child details',
|
|
1825
|
-
name: 'children_custom',
|
|
1826
|
-
type: 'group-array',
|
|
1827
|
-
inputType: 'group-array',
|
|
1828
|
-
jsonType: 'array',
|
|
1829
|
-
required: false,
|
|
1830
|
-
'data-foo': 'baz', // check that custom property is properly propagated
|
|
1831
|
-
fields: expect.any(Function), // This is what makes the field work
|
|
1832
|
-
},
|
|
1833
|
-
],
|
|
1834
|
-
});
|
|
1835
|
-
});
|
|
1836
|
-
|
|
1837
|
-
it('can be a conditional field', () => {
|
|
1838
|
-
const { fields, handleValidation } = createHeadlessForm({
|
|
1839
|
-
properties: {
|
|
1840
|
-
yes_or_no: {
|
|
1841
|
-
title: 'Show the dependents or not?',
|
|
1842
|
-
oneOf: [{ const: 'yes' }, { const: 'no' }],
|
|
1843
|
-
'x-jsf-presentation': { inputType: 'radio' },
|
|
1844
|
-
},
|
|
1845
|
-
dependent_details: mockGroupArrayInput,
|
|
1846
|
-
},
|
|
1847
|
-
allOf: [
|
|
1848
|
-
{
|
|
1849
|
-
if: {
|
|
1850
|
-
properties: {
|
|
1851
|
-
yes_or_no: { const: 'yes' },
|
|
1852
|
-
},
|
|
1853
|
-
required: ['yes_or_no'],
|
|
1854
|
-
},
|
|
1855
|
-
then: {
|
|
1856
|
-
required: ['dependent_details'],
|
|
1857
|
-
},
|
|
1858
|
-
else: {
|
|
1859
|
-
properties: {
|
|
1860
|
-
dependent_details: false,
|
|
1861
|
-
},
|
|
1862
|
-
},
|
|
1863
|
-
},
|
|
1864
|
-
],
|
|
1865
|
-
});
|
|
1866
|
-
|
|
1867
|
-
// By default is hidden but the fields are accessible
|
|
1868
|
-
expect(getField(fields, 'dependent_details').isVisible).toBe(false);
|
|
1869
|
-
expect(getField(fields, 'dependent_details').fields).toEqual(expect.any(Function));
|
|
1870
|
-
|
|
1871
|
-
// When the condition matches...
|
|
1872
|
-
const { formErrors } = handleValidation({ yes_or_no: 'yes' });
|
|
1873
|
-
expect(formErrors).toEqual({
|
|
1874
|
-
dependent_details: 'Required field',
|
|
1875
|
-
});
|
|
1876
|
-
// it gets visible with its inner fields.
|
|
1877
|
-
expect(getField(fields, 'dependent_details').isVisible).toBe(true);
|
|
1878
|
-
expect(getField(fields, 'dependent_details').fields).toEqual(expect.any(Function));
|
|
1879
|
-
});
|
|
1880
|
-
});
|
|
1881
|
-
|
|
1882
|
-
it('supports "null" field type', () => {
|
|
1883
|
-
const { handleValidation, fields } = createHeadlessForm(schemaInputTypeNull, {
|
|
1884
|
-
strictInputType: false,
|
|
1885
|
-
});
|
|
1886
|
-
|
|
1887
|
-
expect(fields).toMatchObject([
|
|
1888
|
-
{
|
|
1889
|
-
name: 'name',
|
|
1890
|
-
label: '(Optional) Name',
|
|
1891
|
-
type: undefined,
|
|
1892
|
-
jsonType: 'null',
|
|
1893
|
-
schema: expect.any(Object),
|
|
1894
|
-
},
|
|
1895
|
-
{
|
|
1896
|
-
name: 'username',
|
|
1897
|
-
label: 'Username',
|
|
1898
|
-
type: 'text',
|
|
1899
|
-
jsonType: 'string',
|
|
1900
|
-
inputType: 'text',
|
|
1901
|
-
maxLength: 4,
|
|
1902
|
-
schema: expect.any(Object),
|
|
1903
|
-
},
|
|
1904
|
-
]);
|
|
1905
|
-
|
|
1906
|
-
// jsonType `null` fields do not have a corresponding inputType
|
|
1907
|
-
expect(fields[0].inputType).toBeUndefined();
|
|
1908
|
-
|
|
1909
|
-
const validateForm = (vals) => friendlyError(handleValidation(vals));
|
|
1910
|
-
|
|
1911
|
-
expect(validateForm({})).toEqual({
|
|
1912
|
-
username: 'Required field',
|
|
1913
|
-
});
|
|
1914
|
-
|
|
1915
|
-
expect(validateForm({ username: 'hello', name: 'John' })).toEqual({
|
|
1916
|
-
username: 'Please insert up to 4 characters',
|
|
1917
|
-
name: 'The value "John" is not valid.',
|
|
1918
|
-
});
|
|
1919
|
-
|
|
1920
|
-
expect(validateForm({ username: 'john' })).toBeUndefined();
|
|
1921
|
-
expect(validateForm({ username: 'john', name: null })).toBeUndefined();
|
|
1922
|
-
});
|
|
1923
|
-
|
|
1924
|
-
describe('supports "fieldset" field type', () => {
|
|
1925
|
-
it('supports basic case', () => {
|
|
1926
|
-
const result = createHeadlessForm({
|
|
1927
|
-
properties: {
|
|
1928
|
-
fieldset: mockFieldset,
|
|
1929
|
-
},
|
|
1930
|
-
});
|
|
1931
|
-
|
|
1932
|
-
expect(result).toMatchObject({
|
|
1933
|
-
fields: [
|
|
1934
|
-
{
|
|
1935
|
-
description: 'Fieldset description',
|
|
1936
|
-
label: 'Fieldset title',
|
|
1937
|
-
name: 'fieldset',
|
|
1938
|
-
type: 'fieldset',
|
|
1939
|
-
required: false,
|
|
1940
|
-
fields: [
|
|
1941
|
-
{
|
|
1942
|
-
description: 'Your username (max 10 characters)',
|
|
1943
|
-
label: 'Username',
|
|
1944
|
-
name: 'username',
|
|
1945
|
-
type: 'text',
|
|
1946
|
-
required: true,
|
|
1947
|
-
},
|
|
1948
|
-
{
|
|
1949
|
-
description: 'How many open tabs do you have?',
|
|
1950
|
-
label: 'Tabs',
|
|
1951
|
-
maximum: 10,
|
|
1952
|
-
minimum: 1,
|
|
1953
|
-
name: 'tabs',
|
|
1954
|
-
type: 'number',
|
|
1955
|
-
required: false,
|
|
1956
|
-
},
|
|
1957
|
-
],
|
|
1958
|
-
},
|
|
1959
|
-
],
|
|
1960
|
-
});
|
|
1961
|
-
});
|
|
1962
|
-
|
|
1963
|
-
it('supports nested fieldset (fieldset inside fieldset)', () => {
|
|
1964
|
-
const result = createHeadlessForm(
|
|
1965
|
-
JSONSchemaBuilder()
|
|
1966
|
-
.addInput({
|
|
1967
|
-
nestedFieldset: mockNestedFieldset,
|
|
1968
|
-
})
|
|
1969
|
-
.build()
|
|
1970
|
-
);
|
|
1971
|
-
|
|
1972
|
-
expect(result).toMatchObject({
|
|
1973
|
-
fields: [
|
|
1974
|
-
{
|
|
1975
|
-
label: 'Nested fieldset title',
|
|
1976
|
-
description: 'Nested fieldset description',
|
|
1977
|
-
name: 'nestedFieldset',
|
|
1978
|
-
type: 'fieldset',
|
|
1979
|
-
required: false,
|
|
1980
|
-
fields: [
|
|
1981
|
-
{
|
|
1982
|
-
description: 'Fieldset description',
|
|
1983
|
-
label: 'Fieldset title',
|
|
1984
|
-
name: 'innerFieldset',
|
|
1985
|
-
type: 'fieldset',
|
|
1986
|
-
required: false,
|
|
1987
|
-
fields: [
|
|
1988
|
-
{
|
|
1989
|
-
description: 'Your username (max 10 characters)',
|
|
1990
|
-
label: 'Username',
|
|
1991
|
-
name: 'username',
|
|
1992
|
-
type: 'text',
|
|
1993
|
-
required: true,
|
|
1994
|
-
},
|
|
1995
|
-
{
|
|
1996
|
-
description: 'How many open tabs do you have?',
|
|
1997
|
-
label: 'Tabs',
|
|
1998
|
-
maximum: 10,
|
|
1999
|
-
minimum: 1,
|
|
2000
|
-
name: 'tabs',
|
|
2001
|
-
type: 'number',
|
|
2002
|
-
required: false,
|
|
2003
|
-
},
|
|
2004
|
-
],
|
|
2005
|
-
},
|
|
2006
|
-
],
|
|
2007
|
-
},
|
|
2008
|
-
],
|
|
2009
|
-
});
|
|
2010
|
-
});
|
|
2011
|
-
|
|
2012
|
-
it('supported "fieldset" with scoped conditionals', () => {
|
|
2013
|
-
const { handleValidation } = createHeadlessForm(schemaFieldsetScopedCondition, {});
|
|
2014
|
-
const validateForm = (vals) => friendlyError(handleValidation(vals));
|
|
2015
|
-
|
|
2016
|
-
// The "child.has_child" is required
|
|
2017
|
-
expect(validateForm({})).toEqual({
|
|
2018
|
-
child: {
|
|
2019
|
-
has_child: 'Required field',
|
|
2020
|
-
},
|
|
2021
|
-
});
|
|
2022
|
-
|
|
2023
|
-
// The "child.no" is valid
|
|
2024
|
-
expect(
|
|
2025
|
-
validateForm({
|
|
2026
|
-
child: {
|
|
2027
|
-
has_child: 'no',
|
|
2028
|
-
},
|
|
2029
|
-
})
|
|
2030
|
-
).toBeUndefined();
|
|
2031
|
-
|
|
2032
|
-
// Invalid because it expect child.age too
|
|
2033
|
-
expect(
|
|
2034
|
-
validateForm({
|
|
2035
|
-
child: {
|
|
2036
|
-
has_child: 'yes',
|
|
2037
|
-
},
|
|
2038
|
-
})
|
|
2039
|
-
).toEqual({
|
|
2040
|
-
child: {
|
|
2041
|
-
age: 'Required field',
|
|
2042
|
-
},
|
|
2043
|
-
});
|
|
2044
|
-
|
|
2045
|
-
// Valid without optional child.passport_id
|
|
2046
|
-
expect(
|
|
2047
|
-
validateForm({
|
|
2048
|
-
child: {
|
|
2049
|
-
has_child: 'yes',
|
|
2050
|
-
age: 15,
|
|
2051
|
-
},
|
|
2052
|
-
})
|
|
2053
|
-
).toBeUndefined();
|
|
2054
|
-
|
|
2055
|
-
// Valid with optional child.passport_id
|
|
2056
|
-
expect(
|
|
2057
|
-
validateForm({
|
|
2058
|
-
child: {
|
|
2059
|
-
has_child: 'yes',
|
|
2060
|
-
age: 15,
|
|
2061
|
-
passport_id: 'asdf',
|
|
2062
|
-
},
|
|
2063
|
-
})
|
|
2064
|
-
).toBeUndefined();
|
|
2065
|
-
});
|
|
2066
|
-
|
|
2067
|
-
it('should set any nested "fieldset" form values to null when they are invisible', async () => {
|
|
2068
|
-
const { handleValidation } = createHeadlessForm(schemaFieldsetScopedCondition, {});
|
|
2069
|
-
const validateForm = (vals) => friendlyError(handleValidation(vals));
|
|
2070
|
-
|
|
2071
|
-
const formValues = {
|
|
2072
|
-
child: {
|
|
2073
|
-
has_child: 'yes',
|
|
2074
|
-
age: 15,
|
|
2075
|
-
},
|
|
2076
|
-
};
|
|
2077
|
-
|
|
2078
|
-
await expect(validateForm(formValues)).toBeUndefined();
|
|
2079
|
-
expect(formValues.child.age).toBe(15);
|
|
2080
|
-
|
|
2081
|
-
formValues.child.has_child = 'no';
|
|
2082
|
-
// form value updates re-validate; see computeYupSchema()
|
|
2083
|
-
await expect(validateForm(formValues)).toBeUndefined();
|
|
2084
|
-
|
|
2085
|
-
// when child.has_child is 'no' child.age is invisible
|
|
2086
|
-
expect(formValues.child.age).toBe(null);
|
|
2087
|
-
});
|
|
2088
|
-
|
|
2089
|
-
describe('supports conditionals to fieldsets', () => {
|
|
2090
|
-
// To not mix the concepts:
|
|
2091
|
-
// - Scoped conditionals: Conditionals written inside a fieldset
|
|
2092
|
-
// - Conditionals to fieldsets: Root conditionals that affect a fieldset
|
|
2093
|
-
|
|
2094
|
-
// This describe has sequential tests, covering the following:
|
|
2095
|
-
// If the working_hours > 30,
|
|
2096
|
-
// Then the fieldset perks.food changes (the "no" option gets removed)
|
|
2097
|
-
|
|
2098
|
-
// Setup (arrange)
|
|
2099
|
-
let validateForm;
|
|
2100
|
-
let fields;
|
|
2101
|
-
let originalFood;
|
|
2102
|
-
let perksForLowWorkHours;
|
|
2103
|
-
|
|
2104
|
-
beforeAll(() => {
|
|
2105
|
-
const form = createHeadlessForm(schemaWithConditionalToFieldset);
|
|
2106
|
-
fields = form.fields;
|
|
2107
|
-
validateForm = (vals) => friendlyError(form.handleValidation(vals));
|
|
2108
|
-
originalFood = getField(fields, 'perks', 'food');
|
|
2109
|
-
|
|
2110
|
-
perksForLowWorkHours = {
|
|
2111
|
-
food: 'no', // this option will be removed when the condition happens.
|
|
2112
|
-
retirement: 'basic',
|
|
2113
|
-
};
|
|
2114
|
-
});
|
|
2115
|
-
|
|
2116
|
-
it('by default, the Perks.food has 4 options', () => {
|
|
2117
|
-
expect(originalFood.options).toHaveLength(4);
|
|
2118
|
-
expect(originalFood.description).toBeUndefined();
|
|
2119
|
-
|
|
2120
|
-
// Ensure the perks are required
|
|
2121
|
-
expect(validateForm({})).toEqual({
|
|
2122
|
-
work_hours_per_week: 'Required field',
|
|
2123
|
-
perks: {
|
|
2124
|
-
food: 'Required field',
|
|
2125
|
-
retirement: 'Required field',
|
|
2126
|
-
},
|
|
2127
|
-
});
|
|
2128
|
-
|
|
2129
|
-
// Given low work hours, the form is valid.
|
|
2130
|
-
expect(
|
|
2131
|
-
validateForm({
|
|
2132
|
-
work_hours_per_week: 5,
|
|
2133
|
-
perks: perksForLowWorkHours,
|
|
2134
|
-
})
|
|
2135
|
-
).toBeUndefined();
|
|
2136
|
-
});
|
|
2137
|
-
|
|
2138
|
-
it('Given a lot work hours, the perks.food options change', () => {
|
|
2139
|
-
expect(
|
|
2140
|
-
validateForm({
|
|
2141
|
-
work_hours_per_week: 35,
|
|
2142
|
-
})
|
|
2143
|
-
).toEqual({
|
|
2144
|
-
pto: 'Required field', // Sanity-check - this field gets required too.
|
|
2145
|
-
perks: {
|
|
2146
|
-
food: 'Required field',
|
|
2147
|
-
retirement: 'Required field',
|
|
2148
|
-
},
|
|
2149
|
-
});
|
|
2150
|
-
|
|
2151
|
-
// The fieldset changed!
|
|
2152
|
-
const foodField = getField(fields, 'perks', 'food');
|
|
2153
|
-
// perks.food options changed ("No" was removed)
|
|
2154
|
-
expect(foodField.options).toHaveLength(3);
|
|
2155
|
-
|
|
2156
|
-
// Ensure the "no" option is no longer accepted:
|
|
2157
|
-
// This is a very important test in case the UI fails for some reason.
|
|
2158
|
-
expect(
|
|
2159
|
-
validateForm({
|
|
2160
|
-
work_hours_per_week: 35,
|
|
2161
|
-
pto: 20,
|
|
2162
|
-
perks: perksForLowWorkHours,
|
|
2163
|
-
})
|
|
2164
|
-
).toEqual({
|
|
2165
|
-
perks: {
|
|
2166
|
-
food: 'The option "no" is not valid.',
|
|
2167
|
-
},
|
|
2168
|
-
});
|
|
2169
|
-
|
|
2170
|
-
// perks.food has a new description
|
|
2171
|
-
expect(foodField.description).toBe("Above 30 hours, the 'no' option disappears.");
|
|
2172
|
-
// pto has a new description
|
|
2173
|
-
expect(getField(fields, 'pto').description).toBe(
|
|
2174
|
-
'Above 30 hours, the PTO needs to be at least 20 days.'
|
|
2175
|
-
);
|
|
2176
|
-
|
|
2177
|
-
// Sanity-check: Now the PTO also has a minimum value
|
|
2178
|
-
expect(
|
|
2179
|
-
validateForm({
|
|
2180
|
-
work_hours_per_week: 35,
|
|
2181
|
-
pto: 5, // too low
|
|
2182
|
-
perks: { food: 'lunch', retirement: 'basic' },
|
|
2183
|
-
})
|
|
2184
|
-
).toEqual({
|
|
2185
|
-
pto: 'Must be greater or equal to 20',
|
|
2186
|
-
});
|
|
2187
|
-
});
|
|
2188
|
-
|
|
2189
|
-
it('When changing back to low work hours, the perks.food goes back to the original state', () => {
|
|
2190
|
-
expect(
|
|
2191
|
-
validateForm({
|
|
2192
|
-
work_hours_per_week: 10,
|
|
2193
|
-
pto: 5,
|
|
2194
|
-
})
|
|
2195
|
-
).toEqual({
|
|
2196
|
-
perks: {
|
|
2197
|
-
food: 'Required field',
|
|
2198
|
-
retirement: 'Required field',
|
|
2199
|
-
},
|
|
2200
|
-
// ...pto is minimum error is gone! (sanity-check)
|
|
2201
|
-
});
|
|
2202
|
-
|
|
2203
|
-
const foodField = getField(fields, 'perks', 'food');
|
|
2204
|
-
// ...Number of perks.food options was back to the original (4)
|
|
2205
|
-
expect(foodField.options).toHaveLength(4);
|
|
2206
|
-
// ...Food description was back to the original
|
|
2207
|
-
expect(foodField.description).toBeUndefined();
|
|
2208
|
-
// ...PTO Description is removed too.
|
|
2209
|
-
expect(getField(fields, 'pto').description).toBeUndefined();
|
|
2210
|
-
|
|
2211
|
-
// Given again "low perks", the form valid.
|
|
2212
|
-
expect(
|
|
2213
|
-
validateForm({
|
|
2214
|
-
work_hours_per_week: 10,
|
|
2215
|
-
perks: perksForLowWorkHours,
|
|
2216
|
-
})
|
|
2217
|
-
).toBeUndefined();
|
|
2218
|
-
});
|
|
2219
|
-
});
|
|
2220
|
-
});
|
|
2221
|
-
|
|
2222
|
-
it('support "email" field type', () => {
|
|
2223
|
-
const result = createHeadlessForm(schemaInputTypeEmail);
|
|
2224
|
-
|
|
2225
|
-
expect(result).toMatchObject({
|
|
2226
|
-
fields: [
|
|
2227
|
-
{
|
|
2228
|
-
description: 'Enter your email address',
|
|
2229
|
-
label: 'Email address',
|
|
2230
|
-
name: 'email_address',
|
|
2231
|
-
required: true,
|
|
2232
|
-
schema: expect.any(Object),
|
|
2233
|
-
type: 'email',
|
|
2234
|
-
maxLength: 255,
|
|
2235
|
-
},
|
|
2236
|
-
],
|
|
2237
|
-
});
|
|
2238
|
-
|
|
2239
|
-
const fieldValidator = result.fields[0].schema;
|
|
2240
|
-
expect(fieldValidator.isValidSync('test@gmail.com')).toBe(true);
|
|
2241
|
-
expect(() => fieldValidator.validateSync('ffsdf')).toThrowError(
|
|
2242
|
-
'Please enter a valid email address'
|
|
2243
|
-
);
|
|
2244
|
-
expect(() => fieldValidator.validateSync(undefined)).toThrowError('Required field');
|
|
2245
|
-
});
|
|
2246
|
-
|
|
2247
|
-
describe('supports "checkbox" field type', () => {
|
|
2248
|
-
describe('checkbox as string', () => {
|
|
2249
|
-
it('required: only accept the value in "checkboxValue"', () => {
|
|
2250
|
-
const result = createHeadlessForm(schemaInputTypeCheckbox);
|
|
2251
|
-
const checkboxField = result.fields.find((field) => field.name === 'contract_duration');
|
|
2252
|
-
|
|
2253
|
-
expect(checkboxField).toMatchObject({
|
|
2254
|
-
description:
|
|
2255
|
-
'I acknowledge that all employees in France will be hired on indefinite contracts.',
|
|
2256
|
-
label: 'Contract duration',
|
|
2257
|
-
name: 'contract_duration',
|
|
2258
|
-
type: 'checkbox',
|
|
2259
|
-
checkboxValue: 'Permanent',
|
|
2260
|
-
});
|
|
2261
|
-
expect(checkboxField).not.toHaveProperty('default'); // ensure it's not checked by default.
|
|
2262
|
-
|
|
2263
|
-
const fieldValidator = checkboxField.schema;
|
|
2264
|
-
expect(fieldValidator.isValidSync('Permanent')).toBe(true);
|
|
2265
|
-
expect(() => fieldValidator.validateSync(undefined)).toThrowError(
|
|
2266
|
-
'Please acknowledge this field'
|
|
2267
|
-
);
|
|
2268
|
-
});
|
|
2269
|
-
|
|
2270
|
-
it('required checked: returns a default value', () => {
|
|
2271
|
-
const result = createHeadlessForm(schemaInputTypeCheckbox);
|
|
2272
|
-
const checkboxField = result.fields.find(
|
|
2273
|
-
(field) => field.name === 'contract_duration_checked'
|
|
2274
|
-
);
|
|
2275
|
-
|
|
2276
|
-
expect(checkboxField).toMatchObject({
|
|
2277
|
-
default: 'Permanent',
|
|
2278
|
-
checkboxValue: 'Permanent',
|
|
2279
|
-
});
|
|
2280
|
-
});
|
|
2281
|
-
});
|
|
2282
|
-
|
|
2283
|
-
describe('checkbox as boolean', () => {
|
|
2284
|
-
it('optional: Accepts true or false', () => {
|
|
2285
|
-
const result = createHeadlessForm(schemaInputTypeCheckboxBooleans);
|
|
2286
|
-
const checkboxField = result.fields.find((field) => field.name === 'boolean_empty');
|
|
2287
|
-
|
|
2288
|
-
expect(checkboxField).toMatchObject({
|
|
2289
|
-
checkboxValue: true,
|
|
2290
|
-
});
|
|
2291
|
-
expect(checkboxField).not.toHaveProperty('default'); // ensure it's not checked by default.
|
|
2292
|
-
|
|
2293
|
-
const fieldValidator = checkboxField.schema;
|
|
2294
|
-
expect(fieldValidator.isValidSync(true)).toBe(true);
|
|
2295
|
-
expect(fieldValidator.isValidSync(false)).toBe(true);
|
|
2296
|
-
expect(fieldValidator.isValidSync(undefined)).toBe(true);
|
|
2297
|
-
expect(() => fieldValidator.validateSync('foo')).toThrowError(
|
|
2298
|
-
'The value must be a boolean, but received "foo"'
|
|
2299
|
-
);
|
|
2300
|
-
});
|
|
2301
|
-
|
|
2302
|
-
it('required: Only accepts true', () => {
|
|
2303
|
-
const result = createHeadlessForm(schemaInputTypeCheckboxBooleans);
|
|
2304
|
-
const checkboxField = result.fields.find((field) => field.name === 'boolean_required');
|
|
2305
|
-
|
|
2306
|
-
expect(checkboxField).toMatchObject({
|
|
2307
|
-
checkboxValue: true,
|
|
2308
|
-
});
|
|
2309
|
-
|
|
2310
|
-
const fieldValidator = checkboxField.schema;
|
|
2311
|
-
expect(fieldValidator.isValidSync(true)).toBe(true);
|
|
2312
|
-
expect(() => fieldValidator.validateSync(false)).toThrowError(
|
|
2313
|
-
'Please acknowledge this field'
|
|
2314
|
-
);
|
|
2315
|
-
});
|
|
2316
|
-
|
|
2317
|
-
it('checked: returns default: true', () => {
|
|
2318
|
-
const result = createHeadlessForm(schemaInputTypeCheckboxBooleans);
|
|
2319
|
-
const checkboxField = result.fields.find((field) => field.name === 'boolean_checked');
|
|
2320
|
-
|
|
2321
|
-
expect(checkboxField).toMatchObject({
|
|
2322
|
-
checkboxValue: true,
|
|
2323
|
-
default: true,
|
|
2324
|
-
});
|
|
2325
|
-
});
|
|
2326
|
-
|
|
2327
|
-
it('conditional: it works as undefined value', () => {
|
|
2328
|
-
const { fields, handleValidation } = createHeadlessForm(
|
|
2329
|
-
schemaInputTypeCheckboxBooleanConditional
|
|
2330
|
-
);
|
|
2331
|
-
const checkboxField = fields.find((field) => field.name === 'pet_is_cat');
|
|
2332
|
-
|
|
2333
|
-
expect(handleValidation({ has_pet: false }).formErrors).toBeUndefined();
|
|
2334
|
-
expect(checkboxField.isVisible).toBe(false);
|
|
2335
|
-
|
|
2336
|
-
expect(handleValidation({ has_pet: true, pet_is_cat: true }).formErrors).toBeUndefined();
|
|
2337
|
-
expect(checkboxField.isVisible).toBe(true);
|
|
2338
|
-
|
|
2339
|
-
expect(handleValidation({ has_pet: true, pet_is_cat: 'foo' }).formErrors).toEqual({
|
|
2340
|
-
pet_is_cat: 'The value must be a boolean, but received "foo"',
|
|
2341
|
-
});
|
|
2342
|
-
|
|
2343
|
-
// Bug: It should throw an error saying pet_is_cat is not allowed, but it doesn't.
|
|
2344
|
-
// Explained at "Given values from hidden fields, it does not thrown an error"
|
|
2345
|
-
expect(handleValidation({ has_pet: false, pet_is_cat: true }).formErrors).toBeUndefined();
|
|
2346
|
-
});
|
|
2347
|
-
});
|
|
2348
|
-
});
|
|
2349
|
-
|
|
2350
|
-
describe('supports custom inputType (eg "hour")', () => {
|
|
2351
|
-
it('as required, optional, and mixed types', () => {
|
|
2352
|
-
const { fields, handleValidation } = createHeadlessForm(
|
|
2353
|
-
{
|
|
2354
|
-
properties: {
|
|
2355
|
-
start_time: {
|
|
2356
|
-
title: 'Starting time',
|
|
2357
|
-
type: 'string',
|
|
2358
|
-
presentation: {
|
|
2359
|
-
inputType: 'hour',
|
|
2360
|
-
},
|
|
2361
|
-
},
|
|
2362
|
-
pause: {
|
|
2363
|
-
title: 'Pause time (optional)',
|
|
2364
|
-
type: 'string',
|
|
2365
|
-
presentation: {
|
|
2366
|
-
inputType: 'hour',
|
|
2367
|
-
},
|
|
2368
|
-
},
|
|
2369
|
-
end_time: {
|
|
2370
|
-
title: 'Finishing time (optional)',
|
|
2371
|
-
type: ['null', 'string'], // ensure it supports mix types (array) (optional/null)
|
|
2372
|
-
presentation: {
|
|
2373
|
-
inputType: 'hour',
|
|
2374
|
-
},
|
|
2375
|
-
},
|
|
2376
|
-
},
|
|
2377
|
-
required: ['start_time'],
|
|
2378
|
-
},
|
|
2379
|
-
{
|
|
2380
|
-
strictInputType: false,
|
|
2381
|
-
}
|
|
2382
|
-
);
|
|
2383
|
-
const validateForm = (vals) => friendlyError(handleValidation(vals));
|
|
2384
|
-
|
|
2385
|
-
const commonAttrs = {
|
|
2386
|
-
type: 'hour',
|
|
2387
|
-
inputType: 'hour',
|
|
2388
|
-
jsonType: 'string',
|
|
2389
|
-
schema: expect.any(Object),
|
|
2390
|
-
};
|
|
2391
|
-
expect(fields).toMatchObject([
|
|
2392
|
-
{
|
|
2393
|
-
name: 'start_time',
|
|
2394
|
-
label: 'Starting time',
|
|
2395
|
-
...commonAttrs,
|
|
2396
|
-
},
|
|
2397
|
-
{
|
|
2398
|
-
name: 'pause',
|
|
2399
|
-
label: 'Pause time (optional)',
|
|
2400
|
-
...commonAttrs,
|
|
2401
|
-
},
|
|
2402
|
-
{
|
|
2403
|
-
name: 'end_time',
|
|
2404
|
-
label: 'Finishing time (optional)',
|
|
2405
|
-
...commonAttrs,
|
|
2406
|
-
jsonType: ['null', 'string'],
|
|
2407
|
-
},
|
|
2408
|
-
]);
|
|
2409
|
-
|
|
2410
|
-
expect(validateForm({})).toEqual({
|
|
2411
|
-
start_time: 'Required field',
|
|
2412
|
-
});
|
|
2413
|
-
|
|
2414
|
-
expect(validateForm({ start_time: '08:30' })).toBeUndefined();
|
|
2415
|
-
});
|
|
2416
|
-
});
|
|
2417
|
-
});
|
|
2418
|
-
|
|
2419
|
-
describe('validation options', () => {
|
|
2420
|
-
it('given invalid values it returns both yupError and formErrors', () => {
|
|
2421
|
-
const { handleValidation } = createHeadlessForm(schemaInputTypeText);
|
|
2422
|
-
|
|
2423
|
-
const { formErrors, yupError } = handleValidation({});
|
|
2424
|
-
|
|
2425
|
-
// Assert the yupError shape is really a YupError
|
|
2426
|
-
expect(yupError).toEqual(expect.any(Error));
|
|
2427
|
-
expect(yupError.inner[0].path).toBe('username');
|
|
2428
|
-
expect(yupError.inner[0].message).toBe('Required field');
|
|
2429
|
-
|
|
2430
|
-
// Assert the converted YupError to formErrors
|
|
2431
|
-
expect(formErrors).toEqual({
|
|
2432
|
-
username: 'Required field',
|
|
2433
|
-
});
|
|
2434
|
-
});
|
|
2435
|
-
});
|
|
2436
|
-
|
|
2437
|
-
it('supports oneOf number const', () => {
|
|
2438
|
-
const result = createHeadlessForm({
|
|
2439
|
-
type: 'object',
|
|
2440
|
-
additionalProperties: false,
|
|
2441
|
-
properties: {
|
|
2442
|
-
pets: {
|
|
2443
|
-
title: 'How many pets?',
|
|
2444
|
-
oneOf: [
|
|
2445
|
-
{
|
|
2446
|
-
title: 'One',
|
|
2447
|
-
const: 0,
|
|
2448
|
-
},
|
|
2449
|
-
{
|
|
2450
|
-
title: 'Two',
|
|
2451
|
-
const: 2,
|
|
2452
|
-
},
|
|
2453
|
-
{
|
|
2454
|
-
title: 'null',
|
|
2455
|
-
const: 1,
|
|
2456
|
-
},
|
|
2457
|
-
],
|
|
2458
|
-
'x-jsf-presentation': {
|
|
2459
|
-
inputType: 'select',
|
|
2460
|
-
},
|
|
2461
|
-
type: ['number', 'null'],
|
|
2462
|
-
},
|
|
2463
|
-
},
|
|
2464
|
-
required: [],
|
|
2465
|
-
'x-jsf-order': ['pets'],
|
|
2466
|
-
});
|
|
2467
|
-
|
|
2468
|
-
const fieldValidator = result.fields[0].schema;
|
|
2469
|
-
|
|
2470
|
-
expect(fieldValidator.isValidSync(0)).toBe(true);
|
|
2471
|
-
expect(fieldValidator.isValidSync(1)).toBe(true);
|
|
2472
|
-
expect(() => fieldValidator.validateSync('2')).toThrowError('The option "2" is not valid.');
|
|
2473
|
-
expect(fieldValidator.isValidSync(null)).toBe(true);
|
|
2474
|
-
});
|
|
2475
|
-
|
|
2476
|
-
describe('property misc attributes', () => {
|
|
2477
|
-
it('pass readOnly to field', () => {
|
|
2478
|
-
const result = createHeadlessForm({
|
|
2479
|
-
properties: {
|
|
2480
|
-
secret: {
|
|
2481
|
-
title: 'Secret code',
|
|
2482
|
-
readOnly: true,
|
|
2483
|
-
type: 'string',
|
|
2484
|
-
presentation: {
|
|
2485
|
-
inputType: 'text',
|
|
2486
|
-
},
|
|
2487
|
-
},
|
|
2488
|
-
},
|
|
2489
|
-
});
|
|
2490
|
-
|
|
2491
|
-
expect(result).toMatchObject({
|
|
2492
|
-
fields: [
|
|
2493
|
-
{
|
|
2494
|
-
name: 'secret',
|
|
2495
|
-
label: 'Secret code',
|
|
2496
|
-
schema: expect.any(Object),
|
|
2497
|
-
readOnly: true,
|
|
2498
|
-
},
|
|
2499
|
-
],
|
|
2500
|
-
});
|
|
2501
|
-
});
|
|
2502
|
-
|
|
2503
|
-
it('pass "deprecated" attributes to field', () => {
|
|
2504
|
-
const result = createHeadlessForm({
|
|
2505
|
-
properties: {
|
|
2506
|
-
secret: {
|
|
2507
|
-
title: 'Age',
|
|
2508
|
-
type: 'number',
|
|
2509
|
-
deprecated: true,
|
|
2510
|
-
presentation: {
|
|
2511
|
-
inputType: 'number',
|
|
2512
|
-
deprecated: {
|
|
2513
|
-
description: 'Deprecated in favor of "birthdate".',
|
|
2514
|
-
},
|
|
2515
|
-
},
|
|
2516
|
-
},
|
|
2517
|
-
},
|
|
2518
|
-
});
|
|
2519
|
-
|
|
2520
|
-
expect(result).toMatchObject({
|
|
2521
|
-
fields: [
|
|
2522
|
-
{
|
|
2523
|
-
type: 'number',
|
|
2524
|
-
name: 'secret',
|
|
2525
|
-
label: 'Age',
|
|
2526
|
-
schema: expect.any(Object),
|
|
2527
|
-
deprecated: {
|
|
2528
|
-
description: 'Deprecated in favor of "birthdate".',
|
|
2529
|
-
},
|
|
2530
|
-
},
|
|
2531
|
-
],
|
|
2532
|
-
});
|
|
2533
|
-
});
|
|
2534
|
-
|
|
2535
|
-
it('pass both root level "description" and "x-jsf-presentation.description"', () => {
|
|
2536
|
-
const resultsWithRootDescription = createHeadlessForm({
|
|
2537
|
-
properties: {
|
|
2538
|
-
username: mockTextInput,
|
|
2539
|
-
},
|
|
2540
|
-
required: ['username'],
|
|
2541
|
-
});
|
|
2542
|
-
|
|
2543
|
-
expect(resultsWithRootDescription.fields[0].description).toMatch(/your username/i);
|
|
2544
|
-
|
|
2545
|
-
const resultsWithPresentationDescription = createHeadlessForm({
|
|
2546
|
-
properties: {
|
|
2547
|
-
username: {
|
|
2548
|
-
...mockTextInput,
|
|
2549
|
-
'x-jsf-presentation': {
|
|
2550
|
-
inputType: 'text',
|
|
2551
|
-
// should override the root level description
|
|
2552
|
-
description: 'a different description with <span>markup</span>',
|
|
2553
|
-
},
|
|
2554
|
-
},
|
|
2555
|
-
},
|
|
2556
|
-
required: ['username'],
|
|
2557
|
-
});
|
|
2558
|
-
|
|
2559
|
-
expect(resultsWithPresentationDescription.fields[0].description).toMatch(
|
|
2560
|
-
/a different description /i
|
|
2561
|
-
);
|
|
2562
|
-
});
|
|
2563
|
-
|
|
2564
|
-
it('pass both root level "description" and "presentation.description" (deprecated)', () => {
|
|
2565
|
-
const resultsWithRootDescription = createHeadlessForm({
|
|
2566
|
-
properties: {
|
|
2567
|
-
username: mockTextInputDeprecated,
|
|
2568
|
-
},
|
|
2569
|
-
required: ['username'],
|
|
2570
|
-
});
|
|
2571
|
-
|
|
2572
|
-
expect(resultsWithRootDescription.fields[0].description).toMatch(/your username/i);
|
|
2573
|
-
|
|
2574
|
-
const resultsWithPresentationDescription = createHeadlessForm(
|
|
2575
|
-
JSONSchemaBuilder()
|
|
2576
|
-
.addInput({
|
|
2577
|
-
username: {
|
|
2578
|
-
...mockTextInputDeprecated,
|
|
2579
|
-
presentation: {
|
|
2580
|
-
inputType: 'text',
|
|
2581
|
-
maskSecret: 2,
|
|
2582
|
-
// should override the root level description
|
|
2583
|
-
description: 'a different description with <span>markup</span>',
|
|
2584
|
-
},
|
|
2585
|
-
},
|
|
2586
|
-
})
|
|
2587
|
-
.setRequiredFields(['username'])
|
|
2588
|
-
.build()
|
|
2589
|
-
);
|
|
2590
|
-
|
|
2591
|
-
expect(resultsWithPresentationDescription.fields[0].description).toMatch(
|
|
2592
|
-
/a different description /i
|
|
2593
|
-
);
|
|
2594
|
-
});
|
|
2595
|
-
|
|
2596
|
-
it('support field with "x-jsf-presentation.statement"', () => {
|
|
2597
|
-
const result = createHeadlessForm(schemaInputWithStatement);
|
|
2598
|
-
|
|
2599
|
-
expect(result).toMatchObject({
|
|
2600
|
-
fields: [
|
|
2601
|
-
{
|
|
2602
|
-
name: 'bonus',
|
|
2603
|
-
label: 'Bonus',
|
|
2604
|
-
type: 'text',
|
|
2605
|
-
statement: {
|
|
2606
|
-
description: 'This is a custom statement message.',
|
|
2607
|
-
inputType: 'statement',
|
|
2608
|
-
severity: 'info',
|
|
2609
|
-
},
|
|
2610
|
-
},
|
|
2611
|
-
{
|
|
2612
|
-
name: 'role',
|
|
2613
|
-
label: 'Role',
|
|
2614
|
-
type: 'text',
|
|
2615
|
-
statement: {
|
|
2616
|
-
description: 'This is another statement message, but more severe.',
|
|
2617
|
-
inputType: 'statement',
|
|
2618
|
-
severity: 'warning',
|
|
2619
|
-
},
|
|
2620
|
-
},
|
|
2621
|
-
],
|
|
2622
|
-
});
|
|
2623
|
-
});
|
|
2624
|
-
|
|
2625
|
-
it('pass custom attributes as function', () => {
|
|
2626
|
-
function FakeComponent(props) {
|
|
2627
|
-
const { label, description } = props;
|
|
2628
|
-
return `A React component with ${label} and ${description}`;
|
|
2629
|
-
}
|
|
2630
|
-
// Any custom attributes must be inside "x-jsf-presentation"
|
|
2631
|
-
const { fields, handleValidation } = createHeadlessForm({
|
|
2632
|
-
properties: {
|
|
2633
|
-
field_a: {
|
|
2634
|
-
title: 'Field A',
|
|
2635
|
-
'x-jsf-presentation': {
|
|
2636
|
-
inputType: 'text',
|
|
2637
|
-
MyComponent: FakeComponent,
|
|
2638
|
-
},
|
|
2639
|
-
},
|
|
2640
|
-
field_b: {
|
|
2641
|
-
title: 'Field B',
|
|
2642
|
-
'x-jsf-presentation': {
|
|
2643
|
-
inputType: 'text',
|
|
2644
|
-
MyComponent: FakeComponent,
|
|
2645
|
-
},
|
|
2646
|
-
},
|
|
2647
|
-
},
|
|
2648
|
-
allOf: [
|
|
2649
|
-
{
|
|
2650
|
-
if: {
|
|
2651
|
-
properties: {
|
|
2652
|
-
field_a: { const: 'yes' },
|
|
2653
|
-
},
|
|
2654
|
-
required: ['field_a'],
|
|
2655
|
-
},
|
|
2656
|
-
then: {
|
|
2657
|
-
required: ['field_b'],
|
|
2658
|
-
},
|
|
2659
|
-
},
|
|
2660
|
-
],
|
|
2661
|
-
});
|
|
2662
|
-
|
|
2663
|
-
const fieldA = getField(fields, 'field_a');
|
|
2664
|
-
expect(fieldA).toMatchObject({
|
|
2665
|
-
label: 'Field A',
|
|
2666
|
-
MyComponent: expect.any(Function),
|
|
2667
|
-
});
|
|
2668
|
-
|
|
2669
|
-
const fieldB = getField(fields, 'field_b');
|
|
2670
|
-
expect(fieldB).toMatchObject({
|
|
2671
|
-
label: 'Field B',
|
|
2672
|
-
required: false,
|
|
2673
|
-
MyComponent: expect.any(Function),
|
|
2674
|
-
});
|
|
2675
|
-
|
|
2676
|
-
const fakeProps = { label: 'Field B', description: 'fake description' };
|
|
2677
|
-
expect(fieldB.MyComponent(fakeProps)).toBe(
|
|
2678
|
-
'A React component with Field B and fake description'
|
|
2679
|
-
);
|
|
2680
|
-
|
|
2681
|
-
// Ensure "MyComponent" attribute still exsits after a validation cycle.
|
|
2682
|
-
// This covers the updateField(). Check PR for more context.
|
|
2683
|
-
handleValidation({ field_a: 'yes' });
|
|
2684
|
-
|
|
2685
|
-
expect(getField(fields, 'field_a')).toMatchObject({
|
|
2686
|
-
MyComponent: expect.any(Function),
|
|
2687
|
-
});
|
|
2688
|
-
expect(getField(fields, 'field_b')).toMatchObject({
|
|
2689
|
-
required: true,
|
|
2690
|
-
MyComponent: expect.any(Function),
|
|
2691
|
-
});
|
|
2692
|
-
});
|
|
2693
|
-
|
|
2694
|
-
it('pass scopedJsonSchema to each field', () => {
|
|
2695
|
-
const { fields } = createHeadlessForm(schemaWithoutInputTypes, {
|
|
2696
|
-
strictInputType: false,
|
|
2697
|
-
});
|
|
2698
|
-
|
|
2699
|
-
const aFieldInRoot = getField(fields, 'a_string');
|
|
2700
|
-
// It's the entire json schema
|
|
2701
|
-
expect(aFieldInRoot.scopedJsonSchema).toEqual(schemaWithoutInputTypes);
|
|
2702
|
-
|
|
2703
|
-
const aFieldset = getField(fields, 'a_object');
|
|
2704
|
-
const aFieldInTheFieldset = getField(aFieldset.fields, 'foo');
|
|
2705
|
-
|
|
2706
|
-
// It's only the json schema of that fieldset
|
|
2707
|
-
expect(aFieldInTheFieldset.scopedJsonSchema).toEqual(
|
|
2708
|
-
schemaWithoutInputTypes.properties.a_object
|
|
2709
|
-
);
|
|
2710
|
-
});
|
|
2711
|
-
|
|
2712
|
-
describe('Order of fields', () => {
|
|
2713
|
-
it('sorts fields based on presentation.position keyword (deprecated)', () => {
|
|
2714
|
-
const { fields } = createHeadlessForm(schemaWithPositionDeprecated);
|
|
2715
|
-
|
|
2716
|
-
// Assert the order from the original schema object
|
|
2717
|
-
expect(Object.keys(schemaWithPositionDeprecated.properties)).toEqual([
|
|
2718
|
-
'age',
|
|
2719
|
-
'street',
|
|
2720
|
-
'username',
|
|
2721
|
-
]);
|
|
2722
|
-
expect(Object.keys(schemaWithPositionDeprecated.properties.street.properties)).toEqual([
|
|
2723
|
-
'line_one',
|
|
2724
|
-
'postal_code',
|
|
2725
|
-
'number',
|
|
2726
|
-
]);
|
|
2727
|
-
|
|
2728
|
-
// Assert the Fields order
|
|
2729
|
-
const fieldsByName = fields.map((f) => f.name);
|
|
2730
|
-
expect(fieldsByName).toEqual(['username', 'age', 'street']);
|
|
2731
|
-
|
|
2732
|
-
const fieldsetByName = fields[2].fields.map((f) => f.name);
|
|
2733
|
-
expect(fieldsetByName).toEqual(['line_one', 'number', 'postal_code']);
|
|
2734
|
-
});
|
|
2735
|
-
|
|
2736
|
-
it('sorts fields based on x-jsf-order keyword', () => {
|
|
2737
|
-
const { fields } = createHeadlessForm(schemaWithOrderKeyword);
|
|
2738
|
-
|
|
2739
|
-
// Assert the order from the original schema object
|
|
2740
|
-
expect(Object.keys(schemaWithOrderKeyword.properties)).toEqual([
|
|
2741
|
-
'age',
|
|
2742
|
-
'street',
|
|
2743
|
-
'username',
|
|
2744
|
-
]);
|
|
2745
|
-
expect(Object.keys(schemaWithOrderKeyword.properties.street.properties)).toEqual([
|
|
2746
|
-
'line_one',
|
|
2747
|
-
'postal_code',
|
|
2748
|
-
'number',
|
|
2749
|
-
]);
|
|
2750
|
-
|
|
2751
|
-
// Assert the Fields order
|
|
2752
|
-
const fieldsByName = fields.map((f) => f.name);
|
|
2753
|
-
expect(fieldsByName).toEqual(['username', 'age', 'street']);
|
|
2754
|
-
|
|
2755
|
-
const fieldsetByName = fields[2].fields.map((f) => f.name);
|
|
2756
|
-
expect(fieldsetByName).toEqual(['line_one', 'number', 'postal_code']);
|
|
2757
|
-
});
|
|
2758
|
-
|
|
2759
|
-
it('sorts fields based on original properties (without x-jsf-order)', () => {
|
|
2760
|
-
// Assert the sample schema has x-jsf-order
|
|
2761
|
-
expect(schemaWithOrderKeyword['x-jsf-order']).toBeDefined();
|
|
2762
|
-
|
|
2763
|
-
const schemaWithoutOrder = {
|
|
2764
|
-
...schemaWithOrderKeyword,
|
|
2765
|
-
'x-jsf-order': undefined,
|
|
2766
|
-
};
|
|
2767
|
-
const { fields } = createHeadlessForm(schemaWithoutOrder);
|
|
2768
|
-
|
|
2769
|
-
const originalOrder = ['age', 'street', 'username'];
|
|
2770
|
-
// Assert the order from the original schema object
|
|
2771
|
-
expect(Object.keys(schemaWithoutOrder.properties)).toEqual(originalOrder);
|
|
2772
|
-
|
|
2773
|
-
// Assert the order of fields is the same as the original object
|
|
2774
|
-
const fieldsByName = fields.map((f) => f.name);
|
|
2775
|
-
expect(fieldsByName).toEqual(originalOrder);
|
|
2776
|
-
});
|
|
2777
|
-
});
|
|
2778
|
-
});
|
|
2779
|
-
|
|
2780
|
-
describe('more validations', () => {
|
|
2781
|
-
describe('when a field is required', () => {
|
|
2782
|
-
let fields;
|
|
2783
|
-
beforeEach(() => {
|
|
2784
|
-
const result = createHeadlessForm(
|
|
2785
|
-
buildJSONSchemaInput({ presentationFields: { inputType: 'text' }, required: true })
|
|
2786
|
-
);
|
|
2787
|
-
fields = result.fields;
|
|
2788
|
-
});
|
|
2789
|
-
describe('and value is empty', () => {
|
|
2790
|
-
it('should throw an error', async () =>
|
|
2791
|
-
expect(
|
|
2792
|
-
object()
|
|
2793
|
-
.shape({
|
|
2794
|
-
test: fields[0].schema,
|
|
2795
|
-
})
|
|
2796
|
-
.validate({ test: '' })
|
|
2797
|
-
).rejects.toMatchObject({ errors: ['Required field'] }));
|
|
2798
|
-
});
|
|
2799
|
-
describe('and value is defined', () => {
|
|
2800
|
-
it('should validate field', async () => {
|
|
2801
|
-
const assertObj = { test: 'Hello' };
|
|
2802
|
-
return expect(
|
|
2803
|
-
object()
|
|
2804
|
-
.shape({
|
|
2805
|
-
test: fields[0].schema,
|
|
2806
|
-
})
|
|
2807
|
-
.validate(assertObj)
|
|
2808
|
-
).resolves.toEqual(assertObj);
|
|
2809
|
-
});
|
|
2810
|
-
});
|
|
2811
|
-
});
|
|
2812
|
-
|
|
2813
|
-
describe('when a field is number', () => {
|
|
2814
|
-
let fields;
|
|
2815
|
-
beforeEach(() => {
|
|
2816
|
-
const result = createHeadlessForm(
|
|
2817
|
-
buildJSONSchemaInput({ presentationFields: { inputType: 'number' } })
|
|
2818
|
-
);
|
|
2819
|
-
fields = result.fields;
|
|
2820
|
-
});
|
|
2821
|
-
describe('and value is a string', () => {
|
|
2822
|
-
it('should throw an error', async () =>
|
|
2823
|
-
expect(
|
|
2824
|
-
object()
|
|
2825
|
-
.shape({
|
|
2826
|
-
test: fields[0].schema,
|
|
2827
|
-
})
|
|
2828
|
-
.validate({ test: 'Hello' })
|
|
2829
|
-
).rejects.toThrow());
|
|
2830
|
-
});
|
|
2831
|
-
describe('and value is a number', () => {
|
|
2832
|
-
it('should validate field', async () => {
|
|
2833
|
-
const assertObj = { test: 3 };
|
|
2834
|
-
return expect(
|
|
2835
|
-
object()
|
|
2836
|
-
.shape({
|
|
2837
|
-
test: fields[0].schema,
|
|
2838
|
-
})
|
|
2839
|
-
.validate(assertObj)
|
|
2840
|
-
).resolves.toEqual(assertObj);
|
|
2841
|
-
});
|
|
2842
|
-
});
|
|
2843
|
-
describe('and maximum is set to zero', () => {
|
|
2844
|
-
it('shows the correct validation', () => {
|
|
2845
|
-
const { handleValidation } = createHeadlessForm(schemaInputTypeNumberZeroMaximum);
|
|
2846
|
-
const validateForm = (vals) => friendlyError(handleValidation(vals));
|
|
2847
|
-
|
|
2848
|
-
expect(validateForm({ tabs: '0' })).toBeUndefined();
|
|
2849
|
-
expect(validateForm({ tabs: '-10' })).toBeUndefined();
|
|
2850
|
-
|
|
2851
|
-
expect(validateForm({ tabs: 1 })).toEqual({
|
|
2852
|
-
tabs: 'Must be smaller or equal to 0',
|
|
2853
|
-
});
|
|
2854
|
-
});
|
|
2855
|
-
});
|
|
2856
|
-
});
|
|
2857
|
-
|
|
2858
|
-
describe('when a field has a maxLength of 10', () => {
|
|
2859
|
-
let fields;
|
|
2860
|
-
beforeEach(() => {
|
|
2861
|
-
const result = createHeadlessForm(
|
|
2862
|
-
buildJSONSchemaInput({
|
|
2863
|
-
presentationFields: { inputType: 'text' },
|
|
2864
|
-
inputFields: { maxLength: 10 },
|
|
2865
|
-
})
|
|
2866
|
-
);
|
|
2867
|
-
fields = result.fields;
|
|
2868
|
-
});
|
|
2869
|
-
describe('and value is greater than that', () => {
|
|
2870
|
-
it('should throw an error', async () =>
|
|
2871
|
-
expect(
|
|
2872
|
-
object()
|
|
2873
|
-
.shape({
|
|
2874
|
-
test: fields[0].schema,
|
|
2875
|
-
})
|
|
2876
|
-
.validate({ test: 'Hello Mr John Doe' })
|
|
2877
|
-
).rejects.toMatchObject({ errors: ['Please insert up to 10 characters'] }));
|
|
2878
|
-
});
|
|
2879
|
-
describe('and value is less than that', () => {
|
|
2880
|
-
it('should validate field', async () => {
|
|
2881
|
-
const assertObj = { test: 'Hello John' };
|
|
2882
|
-
return expect(
|
|
2883
|
-
object()
|
|
2884
|
-
.shape({
|
|
2885
|
-
test: fields[0].schema,
|
|
2886
|
-
})
|
|
2887
|
-
.validate(assertObj)
|
|
2888
|
-
).resolves.toEqual(assertObj);
|
|
2889
|
-
});
|
|
2890
|
-
});
|
|
2891
|
-
});
|
|
2892
|
-
|
|
2893
|
-
describe('when a field has a minLength of 2', () => {
|
|
2894
|
-
let fields;
|
|
2895
|
-
beforeEach(() => {
|
|
2896
|
-
const result = createHeadlessForm(
|
|
2897
|
-
buildJSONSchemaInput({
|
|
2898
|
-
presentationFields: { inputType: 'text' },
|
|
2899
|
-
inputFields: { minLength: 2 },
|
|
2900
|
-
})
|
|
2901
|
-
);
|
|
2902
|
-
fields = result.fields;
|
|
2903
|
-
});
|
|
2904
|
-
describe('and value is smaller than that', () => {
|
|
2905
|
-
it('should throw an error', async () =>
|
|
2906
|
-
expect(
|
|
2907
|
-
object()
|
|
2908
|
-
.shape({
|
|
2909
|
-
test: fields[0].schema,
|
|
2910
|
-
})
|
|
2911
|
-
.validate({ test: 'H' })
|
|
2912
|
-
).rejects.toMatchObject({ errors: ['Please insert at least 2 characters'] }));
|
|
2913
|
-
});
|
|
2914
|
-
describe('and value is greater than that', () => {
|
|
2915
|
-
it('should validate field', async () => {
|
|
2916
|
-
const assertObj = { test: 'Hello John' };
|
|
2917
|
-
return expect(
|
|
2918
|
-
object()
|
|
2919
|
-
.shape({
|
|
2920
|
-
test: fields[0].schema,
|
|
2921
|
-
})
|
|
2922
|
-
.validate(assertObj)
|
|
2923
|
-
).resolves.toEqual(assertObj);
|
|
2924
|
-
});
|
|
2925
|
-
});
|
|
2926
|
-
});
|
|
2927
|
-
|
|
2928
|
-
describe('when a field has a minimum of 0', () => {
|
|
2929
|
-
let fields;
|
|
2930
|
-
beforeEach(() => {
|
|
2931
|
-
const result = createHeadlessForm(
|
|
2932
|
-
buildJSONSchemaInput({
|
|
2933
|
-
presentationFields: { inputType: 'number' },
|
|
2934
|
-
inputFields: { minimum: 0 },
|
|
2935
|
-
})
|
|
2936
|
-
);
|
|
2937
|
-
fields = result.fields;
|
|
2938
|
-
});
|
|
2939
|
-
|
|
2940
|
-
describe('and value is less than that', () => {
|
|
2941
|
-
it('should throw an error', async () =>
|
|
2942
|
-
expect(
|
|
2943
|
-
object()
|
|
2944
|
-
.shape({
|
|
2945
|
-
test: fields[0].schema,
|
|
2946
|
-
})
|
|
2947
|
-
.validate({ test: -1 })
|
|
2948
|
-
).rejects.toMatchObject({ errors: ['Must be greater or equal to 0'] }));
|
|
2949
|
-
});
|
|
2950
|
-
|
|
2951
|
-
describe('and value is greater than that', () => {
|
|
2952
|
-
it('should validate field', async () => {
|
|
2953
|
-
const assertObj = { test: 4 };
|
|
2954
|
-
return expect(
|
|
2955
|
-
object()
|
|
2956
|
-
.shape({
|
|
2957
|
-
test: fields[0].schema,
|
|
2958
|
-
})
|
|
2959
|
-
.validate(assertObj)
|
|
2960
|
-
).resolves.toEqual(assertObj);
|
|
2961
|
-
});
|
|
2962
|
-
});
|
|
2963
|
-
});
|
|
2964
|
-
|
|
2965
|
-
describe('when a field has a maximum of 10', () => {
|
|
2966
|
-
let fields;
|
|
2967
|
-
beforeEach(() => {
|
|
2968
|
-
const result = createHeadlessForm(
|
|
2969
|
-
buildJSONSchemaInput({
|
|
2970
|
-
presentationFields: { inputType: 'number' },
|
|
2971
|
-
inputFields: { maximum: 10 },
|
|
2972
|
-
})
|
|
2973
|
-
);
|
|
2974
|
-
fields = result.fields;
|
|
2975
|
-
});
|
|
2976
|
-
|
|
2977
|
-
describe('and value is greater than that', () => {
|
|
2978
|
-
it('should throw an error', async () =>
|
|
2979
|
-
expect(
|
|
2980
|
-
object()
|
|
2981
|
-
.shape({
|
|
2982
|
-
test: fields[0].schema,
|
|
2983
|
-
})
|
|
2984
|
-
.validate({ test: 11 })
|
|
2985
|
-
).rejects.toMatchObject({ errors: ['Must be smaller or equal to 10'] }));
|
|
2986
|
-
});
|
|
2987
|
-
|
|
2988
|
-
describe('and value is greater than that', () => {
|
|
2989
|
-
it('should validate field', async () => {
|
|
2990
|
-
const assertObj = { test: 4 };
|
|
2991
|
-
return expect(
|
|
2992
|
-
object()
|
|
2993
|
-
.shape({
|
|
2994
|
-
test: fields[0].schema,
|
|
2995
|
-
})
|
|
2996
|
-
.validate(assertObj)
|
|
2997
|
-
).resolves.toEqual(assertObj);
|
|
2998
|
-
});
|
|
2999
|
-
});
|
|
3000
|
-
});
|
|
3001
|
-
|
|
3002
|
-
describe('when a field has a pattern', () => {
|
|
3003
|
-
let fields;
|
|
3004
|
-
beforeEach(() => {
|
|
3005
|
-
const result = createHeadlessForm(
|
|
3006
|
-
buildJSONSchemaInput({
|
|
3007
|
-
presentationFields: { inputType: 'text' },
|
|
3008
|
-
inputFields: { pattern: '^[0-9]{3}-[0-9]{2}-(?!0{4})[0-9]{4}$' },
|
|
3009
|
-
})
|
|
3010
|
-
);
|
|
3011
|
-
fields = result.fields;
|
|
3012
|
-
});
|
|
3013
|
-
describe('and value does not match the pattern', () => {
|
|
3014
|
-
it('should throw an error', async () =>
|
|
3015
|
-
expect(
|
|
3016
|
-
object()
|
|
3017
|
-
.shape({
|
|
3018
|
-
test: fields[0].schema,
|
|
3019
|
-
})
|
|
3020
|
-
.validate({ test: 'Hello' })
|
|
3021
|
-
).rejects.toMatchObject({ errors: [expect.any(String)] }));
|
|
3022
|
-
});
|
|
3023
|
-
describe('and value matches the pattern', () => {
|
|
3024
|
-
it('should validate field', async () => {
|
|
3025
|
-
const assertObj = { test: '401-85-1950' };
|
|
3026
|
-
return expect(
|
|
3027
|
-
object()
|
|
3028
|
-
.shape({
|
|
3029
|
-
test: fields[0].schema,
|
|
3030
|
-
})
|
|
3031
|
-
.validate(assertObj)
|
|
3032
|
-
).resolves.toEqual(assertObj);
|
|
3033
|
-
});
|
|
3034
|
-
});
|
|
3035
|
-
});
|
|
3036
|
-
});
|
|
3037
|
-
|
|
3038
|
-
describe('even more validations', () => {
|
|
3039
|
-
describe('and all fields are optional', () => {
|
|
3040
|
-
let handleValidation;
|
|
3041
|
-
const validateForm = (vals) => friendlyError(handleValidation(vals));
|
|
3042
|
-
|
|
3043
|
-
beforeEach(() => {
|
|
3044
|
-
const result = {
|
|
3045
|
-
properties: {
|
|
3046
|
-
textInput: mockTextInput,
|
|
3047
|
-
numberInput: mockNumberInput,
|
|
3048
|
-
},
|
|
3049
|
-
};
|
|
3050
|
-
const { handleValidation: handleValidationEach } = createHeadlessForm(result);
|
|
3051
|
-
handleValidation = handleValidationEach;
|
|
3052
|
-
});
|
|
3053
|
-
|
|
3054
|
-
it.each([
|
|
3055
|
-
[
|
|
3056
|
-
'validation should return true when the object has empty values',
|
|
3057
|
-
{ textInput: '' },
|
|
3058
|
-
undefined,
|
|
3059
|
-
],
|
|
3060
|
-
[
|
|
3061
|
-
'validation should return true when object is valid',
|
|
3062
|
-
{ textInput: 'abcde', numberInput: 9 },
|
|
3063
|
-
undefined,
|
|
3064
|
-
],
|
|
3065
|
-
])('%s', (_, value, errors) => {
|
|
3066
|
-
const testValue = validateForm(value);
|
|
3067
|
-
if (errors) {
|
|
3068
|
-
expect(testValue).toEqual(errors);
|
|
3069
|
-
} else {
|
|
3070
|
-
expect(testValue).toBeUndefined();
|
|
3071
|
-
}
|
|
3072
|
-
});
|
|
3073
|
-
});
|
|
3074
|
-
|
|
3075
|
-
describe('and all fields are mandatory', () => {
|
|
3076
|
-
let handleValidation;
|
|
3077
|
-
const validateForm = (vals) => friendlyError(handleValidation(vals));
|
|
3078
|
-
|
|
3079
|
-
beforeEach(() => {
|
|
3080
|
-
const result = {
|
|
3081
|
-
properties: {
|
|
3082
|
-
textInput: mockTextInput,
|
|
3083
|
-
numberInput: mockNumberInput,
|
|
3084
|
-
},
|
|
3085
|
-
required: ['numberInput', 'textInput'],
|
|
3086
|
-
};
|
|
3087
|
-
const { handleValidation: handleValidationEach } = createHeadlessForm(result);
|
|
3088
|
-
handleValidation = handleValidationEach;
|
|
3089
|
-
});
|
|
3090
|
-
|
|
3091
|
-
it.each([
|
|
3092
|
-
[
|
|
3093
|
-
'validation should return false when value is an empty object',
|
|
3094
|
-
{},
|
|
3095
|
-
{
|
|
3096
|
-
numberInput: 'Required field',
|
|
3097
|
-
textInput: 'Required field',
|
|
3098
|
-
},
|
|
3099
|
-
],
|
|
3100
|
-
[
|
|
3101
|
-
'validation should return false when value is an object with null values',
|
|
3102
|
-
{ textInput: null, numberInput: null },
|
|
3103
|
-
{ numberInput: 'Required field', textInput: 'Required field' },
|
|
3104
|
-
],
|
|
3105
|
-
[
|
|
3106
|
-
'validation should return false when value is an object with empty values',
|
|
3107
|
-
{ textInput: '', numberInput: '' },
|
|
3108
|
-
{ numberInput: 'The value must be a number', textInput: 'Required field' },
|
|
3109
|
-
],
|
|
3110
|
-
[
|
|
3111
|
-
'validation should return false when one value is empty',
|
|
3112
|
-
{ textInput: '986-39-076', numberInput: '' },
|
|
3113
|
-
{ numberInput: 'The value must be a number' },
|
|
3114
|
-
],
|
|
3115
|
-
[
|
|
3116
|
-
'validation should return false a numeric field is not a number',
|
|
3117
|
-
{ textInput: '986-39-076', numberInput: 'not a number' },
|
|
3118
|
-
{ numberInput: 'The value must be a number' },
|
|
3119
|
-
],
|
|
3120
|
-
[
|
|
3121
|
-
'validation should return true when object is valid',
|
|
3122
|
-
{ textInput: 'abc-xy-asd', numberInput: 9 },
|
|
3123
|
-
undefined,
|
|
3124
|
-
],
|
|
3125
|
-
])('%s', (_, values, errors) => {
|
|
3126
|
-
const testValue = validateForm(values);
|
|
3127
|
-
if (errors) {
|
|
3128
|
-
expect(testValue).toEqual(errors);
|
|
3129
|
-
} else {
|
|
3130
|
-
expect(testValue).toBeUndefined();
|
|
3131
|
-
}
|
|
3132
|
-
});
|
|
3133
|
-
|
|
3134
|
-
describe('and one field has pattern validation', () => {
|
|
3135
|
-
beforeEach(() => {
|
|
3136
|
-
const result = JSONSchemaBuilder()
|
|
3137
|
-
.addInput({ patternTextInput: mockTextPatternInput })
|
|
3138
|
-
.setRequiredFields(['patternTextInput'])
|
|
3139
|
-
.build();
|
|
3140
|
-
const { handleValidation: handleValidationEach } = createHeadlessForm(result);
|
|
3141
|
-
handleValidation = handleValidationEach;
|
|
3142
|
-
});
|
|
3143
|
-
|
|
3144
|
-
it.each([
|
|
3145
|
-
[
|
|
3146
|
-
'validation should return false when a value does not match a pattern',
|
|
3147
|
-
{ patternTextInput: 'abc-xy-asd' },
|
|
3148
|
-
{ patternTextInput: expect.stringMatching(/Must have a valid format. E.g./i) },
|
|
3149
|
-
],
|
|
3150
|
-
[
|
|
3151
|
-
'validation should return true when value matches the pattern',
|
|
3152
|
-
{ patternTextInput: '986-39-0716' },
|
|
3153
|
-
undefined,
|
|
3154
|
-
],
|
|
3155
|
-
])('%s', (_, values, errors) => {
|
|
3156
|
-
const testValue = validateForm(values);
|
|
3157
|
-
if (errors) {
|
|
3158
|
-
expect(testValue).toEqual(errors);
|
|
3159
|
-
} else {
|
|
3160
|
-
expect(testValue).toBeUndefined();
|
|
3161
|
-
}
|
|
3162
|
-
});
|
|
3163
|
-
});
|
|
3164
|
-
|
|
3165
|
-
describe('and one field has max length validation', () => {
|
|
3166
|
-
beforeEach(() => {
|
|
3167
|
-
const result = JSONSchemaBuilder()
|
|
3168
|
-
.addInput({ maxLengthTextInput: mockTextMaxLengthInput })
|
|
3169
|
-
.setRequiredFields(['maxLengthTextInput'])
|
|
3170
|
-
.build();
|
|
3171
|
-
const { handleValidation: handleValidationEach } = createHeadlessForm(result);
|
|
3172
|
-
handleValidation = handleValidationEach;
|
|
3173
|
-
});
|
|
3174
|
-
|
|
3175
|
-
it.each([
|
|
3176
|
-
[
|
|
3177
|
-
'validation should return false when a value is greater than the limit',
|
|
3178
|
-
{ maxLengthTextInput: 'Hello John Dow' },
|
|
3179
|
-
{ maxLengthTextInput: 'Please insert up to 10 characters' },
|
|
3180
|
-
],
|
|
3181
|
-
[
|
|
3182
|
-
'validation should return true when value is within the limit',
|
|
3183
|
-
{ maxLengthTextInput: 'Hello John' },
|
|
3184
|
-
undefined,
|
|
3185
|
-
],
|
|
3186
|
-
])('%s', (_, values, errors) => {
|
|
3187
|
-
const testValue = validateForm(values);
|
|
3188
|
-
if (errors) {
|
|
3189
|
-
expect(testValue).toEqual(errors);
|
|
3190
|
-
} else {
|
|
3191
|
-
expect(testValue).toBeUndefined();
|
|
3192
|
-
}
|
|
3193
|
-
});
|
|
3194
|
-
});
|
|
3195
|
-
});
|
|
3196
|
-
|
|
3197
|
-
describe('and fields are dynamically required/optional', () => {
|
|
3198
|
-
it('applies correct validation for single-value based conditionals', async () => {
|
|
3199
|
-
const { fields, handleValidation } = createHeadlessForm(schemaDynamicValidationConst);
|
|
3200
|
-
const validateForm = (vals) => friendlyError(handleValidation(vals));
|
|
3201
|
-
|
|
3202
|
-
expect(
|
|
3203
|
-
validateForm({
|
|
3204
|
-
validate_tabs: 'no',
|
|
3205
|
-
a_fieldset: {
|
|
3206
|
-
username: 'abc',
|
|
3207
|
-
},
|
|
3208
|
-
mandatory_group_array: 'no',
|
|
3209
|
-
})
|
|
3210
|
-
).toBeUndefined();
|
|
3211
|
-
|
|
3212
|
-
const getTabsField = () =>
|
|
3213
|
-
fields.find((f) => f.name === 'a_fieldset').fields.find((f) => f.name === 'tabs');
|
|
3214
|
-
|
|
3215
|
-
expect(getTabsField().required).toBeFalsy();
|
|
3216
|
-
|
|
3217
|
-
expect(
|
|
3218
|
-
validateForm({
|
|
3219
|
-
validate_tabs: 'yes',
|
|
3220
|
-
a_fieldset: {
|
|
3221
|
-
username: 'abc',
|
|
3222
|
-
},
|
|
3223
|
-
mandatory_group_array: 'no',
|
|
3224
|
-
})
|
|
3225
|
-
).toEqual({
|
|
3226
|
-
a_fieldset: {
|
|
3227
|
-
tabs: 'Required field',
|
|
3228
|
-
},
|
|
3229
|
-
});
|
|
3230
|
-
|
|
3231
|
-
expect(getTabsField().required).toBeTruthy();
|
|
3232
|
-
|
|
3233
|
-
expect(
|
|
3234
|
-
validateForm({
|
|
3235
|
-
validate_tabs: 'yes',
|
|
3236
|
-
a_fieldset: {
|
|
3237
|
-
username: 'abc',
|
|
3238
|
-
},
|
|
3239
|
-
mandatory_group_array: 'yes',
|
|
3240
|
-
a_group_array: [{ full_name: 'adfs' }],
|
|
3241
|
-
})
|
|
3242
|
-
).toEqual({ a_fieldset: { tabs: 'Required field' } });
|
|
3243
|
-
|
|
3244
|
-
expect(
|
|
3245
|
-
validateForm({
|
|
3246
|
-
validate_tabs: 'yes',
|
|
3247
|
-
a_fieldset: {
|
|
3248
|
-
username: 'abc',
|
|
3249
|
-
tabs: 2,
|
|
3250
|
-
},
|
|
3251
|
-
mandatory_group_array: 'no',
|
|
3252
|
-
})
|
|
3253
|
-
).toBeUndefined();
|
|
3254
|
-
});
|
|
3255
|
-
|
|
3256
|
-
it('applies correct validation for minimum/maximum conditionals', async () => {
|
|
3257
|
-
const { handleValidation } = createHeadlessForm(schemaDynamicValidationMinimumMaximum);
|
|
3258
|
-
const validateForm = (vals) => friendlyError(handleValidation(vals));
|
|
3259
|
-
|
|
3260
|
-
// Check for minimum condition
|
|
3261
|
-
expect(
|
|
3262
|
-
validateForm({
|
|
3263
|
-
a_number: 0,
|
|
3264
|
-
})
|
|
3265
|
-
).toEqual({
|
|
3266
|
-
a_conditional_text: 'Required field',
|
|
3267
|
-
a_number: 'Must be greater or equal to 1',
|
|
3268
|
-
});
|
|
3269
|
-
|
|
3270
|
-
// Check for maximum condition
|
|
3271
|
-
expect(
|
|
3272
|
-
validateForm({
|
|
3273
|
-
a_number: 11,
|
|
3274
|
-
})
|
|
3275
|
-
).toEqual({
|
|
3276
|
-
a_conditional_text: 'Required field',
|
|
3277
|
-
a_number: 'Must be smaller or equal to 10',
|
|
3278
|
-
});
|
|
3279
|
-
|
|
3280
|
-
// Check for absence of a_number
|
|
3281
|
-
expect(validateForm({})).toEqual({
|
|
3282
|
-
a_conditional_text: 'Required field',
|
|
3283
|
-
});
|
|
3284
|
-
|
|
3285
|
-
// Check for number within range
|
|
3286
|
-
expect(
|
|
3287
|
-
validateForm({
|
|
3288
|
-
a_number: 5,
|
|
3289
|
-
})
|
|
3290
|
-
).toBeUndefined();
|
|
3291
|
-
});
|
|
3292
|
-
|
|
3293
|
-
it('applies correct validation for minLength/maxLength conditionals', async () => {
|
|
3294
|
-
const { handleValidation } = createHeadlessForm(schemaDynamicValidationMinLengthMaxLength);
|
|
3295
|
-
const validateForm = (vals) => friendlyError(handleValidation(vals));
|
|
3296
|
-
const formError = {
|
|
3297
|
-
a_conditional_text: 'Required field',
|
|
3298
|
-
};
|
|
3299
|
-
// By default a_conditional_text is required.
|
|
3300
|
-
expect(validateForm({})).toEqual(formError);
|
|
3301
|
-
|
|
3302
|
-
// Check for minimum length condition - a_text >= 3 chars
|
|
3303
|
-
expect(
|
|
3304
|
-
validateForm({
|
|
3305
|
-
a_text: 'Foo',
|
|
3306
|
-
})
|
|
3307
|
-
).toBeUndefined();
|
|
3308
|
-
|
|
3309
|
-
// Check for maximum length condition - a_text <= 5 chars
|
|
3310
|
-
expect(
|
|
3311
|
-
validateForm({
|
|
3312
|
-
a_text: 'Fooba',
|
|
3313
|
-
})
|
|
3314
|
-
).toBeUndefined();
|
|
3315
|
-
|
|
3316
|
-
// Check for text out of length range (7 chars)
|
|
3317
|
-
expect(
|
|
3318
|
-
validateForm({
|
|
3319
|
-
a_text: 'Foobaaz',
|
|
3320
|
-
})
|
|
3321
|
-
).toEqual(formError);
|
|
3322
|
-
|
|
3323
|
-
// Check for text out of length range (2 chars)
|
|
3324
|
-
expect(
|
|
3325
|
-
validateForm({
|
|
3326
|
-
a_text: 'Fe',
|
|
3327
|
-
})
|
|
3328
|
-
).toEqual(formError);
|
|
3329
|
-
});
|
|
3330
|
-
|
|
3331
|
-
it('applies correct validation for array-contain based conditionals', async () => {
|
|
3332
|
-
const { handleValidation } = createHeadlessForm(schemaDynamicValidationContains);
|
|
3333
|
-
const validateForm = (vals) => friendlyError(handleValidation(vals));
|
|
3334
|
-
|
|
3335
|
-
expect(
|
|
3336
|
-
validateForm({
|
|
3337
|
-
validate_fieldset: ['username'],
|
|
3338
|
-
a_fieldset: {
|
|
3339
|
-
username: 'abc',
|
|
3340
|
-
},
|
|
3341
|
-
})
|
|
3342
|
-
).toBeUndefined();
|
|
3343
|
-
|
|
3344
|
-
expect(
|
|
3345
|
-
validateForm({
|
|
3346
|
-
validate_fieldset: ['username', 'all'],
|
|
3347
|
-
a_fieldset: {
|
|
3348
|
-
username: 'abc',
|
|
3349
|
-
},
|
|
3350
|
-
})
|
|
3351
|
-
).toEqual({
|
|
3352
|
-
a_fieldset: {
|
|
3353
|
-
tabs: 'Required field',
|
|
3354
|
-
},
|
|
3355
|
-
});
|
|
3356
|
-
|
|
3357
|
-
expect(
|
|
3358
|
-
validateForm({
|
|
3359
|
-
validate_fieldset: ['username', 'all'],
|
|
3360
|
-
a_fieldset: {
|
|
3361
|
-
username: 'abc',
|
|
3362
|
-
tabs: 2,
|
|
3363
|
-
},
|
|
3364
|
-
})
|
|
3365
|
-
).toBeUndefined();
|
|
3366
|
-
});
|
|
3367
|
-
|
|
3368
|
-
it('applies correct validation for fieldset fields', async () => {
|
|
3369
|
-
const { handleValidation } = createHeadlessForm(schemaDynamicValidationContains);
|
|
3370
|
-
const validateForm = (vals) => friendlyError(handleValidation(vals));
|
|
3371
|
-
|
|
3372
|
-
expect(
|
|
3373
|
-
validateForm({
|
|
3374
|
-
validate_fieldset: ['username'],
|
|
3375
|
-
a_fieldset: {
|
|
3376
|
-
username: 'abc',
|
|
3377
|
-
},
|
|
3378
|
-
})
|
|
3379
|
-
).toBeUndefined();
|
|
3380
|
-
|
|
3381
|
-
expect(
|
|
3382
|
-
validateForm({
|
|
3383
|
-
validate_fieldset: ['username', 'all'],
|
|
3384
|
-
a_fieldset: {
|
|
3385
|
-
username: 'abc',
|
|
3386
|
-
},
|
|
3387
|
-
})
|
|
3388
|
-
).toEqual({
|
|
3389
|
-
a_fieldset: {
|
|
3390
|
-
tabs: 'Required field',
|
|
3391
|
-
},
|
|
3392
|
-
});
|
|
3393
|
-
|
|
3394
|
-
expect(
|
|
3395
|
-
validateForm({
|
|
3396
|
-
validate_fieldset: ['username', 'all'],
|
|
3397
|
-
a_fieldset: {
|
|
3398
|
-
username: 'abc',
|
|
3399
|
-
tabs: 2,
|
|
3400
|
-
},
|
|
3401
|
-
})
|
|
3402
|
-
).toBeUndefined();
|
|
3403
|
-
});
|
|
3404
|
-
|
|
3405
|
-
it('applies any of the validation alternatives in a anyOf branch', async () => {
|
|
3406
|
-
const { handleValidation } = createHeadlessForm(schemaAnyOfValidation);
|
|
3407
|
-
const validateForm = (vals) => friendlyError(handleValidation(vals));
|
|
3408
|
-
|
|
3409
|
-
expect(
|
|
3410
|
-
validateForm({
|
|
3411
|
-
field_a: '123',
|
|
3412
|
-
})
|
|
3413
|
-
).toBeUndefined();
|
|
3414
|
-
|
|
3415
|
-
expect(
|
|
3416
|
-
validateForm({
|
|
3417
|
-
field_b: '456',
|
|
3418
|
-
})
|
|
3419
|
-
).toEqual({ field_c: 'Required field' });
|
|
3420
|
-
|
|
3421
|
-
expect(
|
|
3422
|
-
validateForm({
|
|
3423
|
-
field_b: '456',
|
|
3424
|
-
field_c: '789',
|
|
3425
|
-
})
|
|
3426
|
-
).toBeUndefined();
|
|
3427
|
-
|
|
3428
|
-
expect(
|
|
3429
|
-
validateForm({
|
|
3430
|
-
field_a: '123',
|
|
3431
|
-
field_c: '789',
|
|
3432
|
-
})
|
|
3433
|
-
).toBeUndefined();
|
|
3434
|
-
|
|
3435
|
-
expect(
|
|
3436
|
-
validateForm({
|
|
3437
|
-
field_a: '123',
|
|
3438
|
-
field_b: '456',
|
|
3439
|
-
field_c: '789',
|
|
3440
|
-
})
|
|
3441
|
-
).toBeUndefined();
|
|
3442
|
-
});
|
|
3443
|
-
|
|
3444
|
-
describe('nested conditionals', () => {
|
|
3445
|
-
it('given empty values, runs "else" (gets hidden)', () => {
|
|
3446
|
-
const { fields } = createHeadlessForm(schemaWithConditionalReadOnlyProperty, {
|
|
3447
|
-
field_a: null,
|
|
3448
|
-
});
|
|
3449
|
-
expect(getField(fields, 'field_b').isVisible).toBe(false);
|
|
3450
|
-
});
|
|
3451
|
-
|
|
3452
|
-
it('given a match, runs "then" (turns visible and editable)', () => {
|
|
3453
|
-
const { fields } = createHeadlessForm(schemaWithConditionalReadOnlyProperty, {
|
|
3454
|
-
initialValues: { field_a: 'yes' },
|
|
3455
|
-
});
|
|
3456
|
-
expect(getField(fields, 'field_b').isVisible).toBe(true);
|
|
3457
|
-
expect(getField(fields, 'field_b').readOnly).toBe(false);
|
|
3458
|
-
});
|
|
3459
|
-
|
|
3460
|
-
it('given a nested match, runs "else-then" (turns visible but readOnly)', () => {
|
|
3461
|
-
const { fields } = createHeadlessForm(schemaWithConditionalReadOnlyProperty, {
|
|
3462
|
-
initialValues: { field_a: 'no' },
|
|
3463
|
-
});
|
|
3464
|
-
expect(getField(fields, 'field_b').isVisible).toBe(true);
|
|
3465
|
-
expect(getField(fields, 'field_b').readOnly).toBe(true);
|
|
3466
|
-
});
|
|
3467
|
-
});
|
|
3468
|
-
|
|
3469
|
-
describe('conditional fields (incorrectly done)', () => {
|
|
3470
|
-
// this catches the typical scenario where developers forget to set the if.required[]
|
|
3471
|
-
|
|
3472
|
-
it('given empty values, the incorrect conditional runs "then" instead of "else"', () => {
|
|
3473
|
-
const { fields: fieldsEmpty } = createHeadlessForm(schemaWithWrongConditional, {
|
|
3474
|
-
initialValues: { field_a: null, field_a_wrong: null },
|
|
3475
|
-
});
|
|
3476
|
-
// The dependent correct field gets hidden, but...
|
|
3477
|
-
expect(getField(fieldsEmpty, 'field_b').isVisible).toBe(false);
|
|
3478
|
-
// ...the dependent wrong field stays visible because the
|
|
3479
|
-
// conditional is wrong (it's missing the if.required[])
|
|
3480
|
-
expect(getField(fieldsEmpty, 'field_b_wrong').isVisible).toBe(true);
|
|
3481
|
-
});
|
|
3482
|
-
|
|
3483
|
-
it('given a match ("yes"), both runs "then" (turn visible)', () => {
|
|
3484
|
-
const { fields: fieldsVisible } = createHeadlessForm(schemaWithWrongConditional, {
|
|
3485
|
-
initialValues: { field_a: 'yes', field_a_wrong: 'yes' },
|
|
3486
|
-
});
|
|
3487
|
-
expect(getField(fieldsVisible, 'field_b').isVisible).toBe(true);
|
|
3488
|
-
expect(getField(fieldsVisible, 'field_b_wrong').isVisible).toBe(true);
|
|
3489
|
-
});
|
|
3490
|
-
|
|
3491
|
-
it('not given a match ("no"), both run else (stay hidden)', () => {
|
|
3492
|
-
const { fields: fieldsHidden } = createHeadlessForm(schemaWithWrongConditional, {
|
|
3493
|
-
initialValues: { field_a: 'no', field_a_wrong: 'no' },
|
|
3494
|
-
});
|
|
3495
|
-
expect(getField(fieldsHidden, 'field_b').isVisible).toBe(false);
|
|
3496
|
-
expect(getField(fieldsHidden, 'field_b_wrong').isVisible).toBe(false);
|
|
3497
|
-
});
|
|
3498
|
-
});
|
|
3499
|
-
|
|
3500
|
-
it('checkbox should have no initial value when its dynamically shown and invisible', () => {
|
|
3501
|
-
const { fields } = createHeadlessForm(schemaWithConditionalAcknowledgementProperty, {
|
|
3502
|
-
initialValues: {
|
|
3503
|
-
field_a: 'no',
|
|
3504
|
-
},
|
|
3505
|
-
});
|
|
3506
|
-
const dependentField = getField(fields, 'field_b');
|
|
3507
|
-
expect(dependentField.isVisible).toBe(false);
|
|
3508
|
-
expect(dependentField.value).toBe(undefined);
|
|
3509
|
-
});
|
|
3510
|
-
|
|
3511
|
-
it('checkbox should have no initial value when its dynamically shown and visible', () => {
|
|
3512
|
-
const { fields } = createHeadlessForm(schemaWithConditionalAcknowledgementProperty, {
|
|
3513
|
-
initialValues: {
|
|
3514
|
-
field_a: 'yes',
|
|
3515
|
-
},
|
|
3516
|
-
});
|
|
3517
|
-
const dependentField = getField(fields, 'field_b');
|
|
3518
|
-
expect(dependentField.isVisible).toBe(true);
|
|
3519
|
-
expect(dependentField.value).toBe(undefined);
|
|
3520
|
-
});
|
|
3521
|
-
});
|
|
3522
|
-
});
|
|
3523
|
-
|
|
3524
|
-
// TODO: delete after migration to x-jsf-errorMessage is completed
|
|
3525
|
-
describe('Throwing custom error messages using errorMessage (deprecated)', () => {
|
|
3526
|
-
it.each([
|
|
3527
|
-
[
|
|
3528
|
-
'type',
|
|
3529
|
-
JSONSchemaBuilder()
|
|
3530
|
-
.addInput({
|
|
3531
|
-
numberInput: {
|
|
3532
|
-
...mockNumberInput,
|
|
3533
|
-
errorMessage: { type: 'It has to be a number.' },
|
|
3534
|
-
},
|
|
3535
|
-
})
|
|
3536
|
-
.build(),
|
|
3537
|
-
{ numberInput: 'Two' },
|
|
3538
|
-
{
|
|
3539
|
-
numberInput: 'It has to be a number.',
|
|
3540
|
-
},
|
|
3541
|
-
false,
|
|
3542
|
-
],
|
|
3543
|
-
[
|
|
3544
|
-
'minimum',
|
|
3545
|
-
JSONSchemaBuilder()
|
|
3546
|
-
.addInput({
|
|
3547
|
-
numberInput: {
|
|
3548
|
-
...mockNumberInput,
|
|
3549
|
-
errorMessage: { minimum: 'I am a custom error message' },
|
|
3550
|
-
},
|
|
3551
|
-
})
|
|
3552
|
-
.build(),
|
|
3553
|
-
{ numberInput: -1 },
|
|
3554
|
-
{
|
|
3555
|
-
numberInput: 'I am a custom error message',
|
|
3556
|
-
},
|
|
3557
|
-
false,
|
|
3558
|
-
],
|
|
3559
|
-
[
|
|
3560
|
-
'required',
|
|
3561
|
-
JSONSchemaBuilder()
|
|
3562
|
-
.addInput({
|
|
3563
|
-
numberInput: {
|
|
3564
|
-
...mockNumberInput,
|
|
3565
|
-
errorMessage: { required: 'I am a custom error message' },
|
|
3566
|
-
},
|
|
3567
|
-
})
|
|
3568
|
-
.setRequiredFields(['numberInput'])
|
|
3569
|
-
.build(),
|
|
3570
|
-
{},
|
|
3571
|
-
{
|
|
3572
|
-
numberInput: 'I am a custom error message',
|
|
3573
|
-
},
|
|
3574
|
-
],
|
|
3575
|
-
[
|
|
3576
|
-
'required (ignored because it is optional)',
|
|
3577
|
-
JSONSchemaBuilder()
|
|
3578
|
-
.addInput({
|
|
3579
|
-
numberInput: {
|
|
3580
|
-
...mockNumberInput,
|
|
3581
|
-
errorMessage: { required: 'I am a custom error message' },
|
|
3582
|
-
},
|
|
3583
|
-
})
|
|
3584
|
-
.build(),
|
|
3585
|
-
{},
|
|
3586
|
-
undefined,
|
|
3587
|
-
],
|
|
3588
|
-
[
|
|
3589
|
-
'maximum',
|
|
3590
|
-
JSONSchemaBuilder()
|
|
3591
|
-
.addInput({
|
|
3592
|
-
numberInput: {
|
|
3593
|
-
...mockNumberInput,
|
|
3594
|
-
errorMessage: { maximum: 'I am a custom error message' },
|
|
3595
|
-
},
|
|
3596
|
-
})
|
|
3597
|
-
.build(),
|
|
3598
|
-
{ numberInput: 11 },
|
|
3599
|
-
{
|
|
3600
|
-
numberInput: 'I am a custom error message',
|
|
3601
|
-
},
|
|
3602
|
-
],
|
|
3603
|
-
[
|
|
3604
|
-
'minLength',
|
|
3605
|
-
JSONSchemaBuilder()
|
|
3606
|
-
.addInput({
|
|
3607
|
-
stringInput: {
|
|
3608
|
-
...mockTextInput,
|
|
3609
|
-
minLength: 3,
|
|
3610
|
-
errorMessage: { minLength: 'I am a custom error message' },
|
|
3611
|
-
},
|
|
3612
|
-
})
|
|
3613
|
-
.build(),
|
|
3614
|
-
{ stringInput: 'aa' },
|
|
3615
|
-
{
|
|
3616
|
-
stringInput: 'I am a custom error message',
|
|
3617
|
-
},
|
|
3618
|
-
],
|
|
3619
|
-
[
|
|
3620
|
-
'maxLength',
|
|
3621
|
-
JSONSchemaBuilder()
|
|
3622
|
-
.addInput({
|
|
3623
|
-
stringInput: {
|
|
3624
|
-
...mockTextInput,
|
|
3625
|
-
maxLength: 3,
|
|
3626
|
-
errorMessage: { maxLength: 'I am a custom error message' },
|
|
3627
|
-
},
|
|
3628
|
-
})
|
|
3629
|
-
.build(),
|
|
3630
|
-
{ stringInput: 'aaaa' },
|
|
3631
|
-
{
|
|
3632
|
-
stringInput: 'I am a custom error message',
|
|
3633
|
-
},
|
|
3634
|
-
],
|
|
3635
|
-
[
|
|
3636
|
-
'pattern',
|
|
3637
|
-
JSONSchemaBuilder()
|
|
3638
|
-
.addInput({
|
|
3639
|
-
stringInput: {
|
|
3640
|
-
...mockTextInput,
|
|
3641
|
-
pattern: '^(\\+|00)\\d*$',
|
|
3642
|
-
errorMessage: { pattern: 'I am a custom error message' },
|
|
3643
|
-
},
|
|
3644
|
-
})
|
|
3645
|
-
.build(),
|
|
3646
|
-
{ stringInput: 'aaaa' },
|
|
3647
|
-
{
|
|
3648
|
-
stringInput: 'I am a custom error message',
|
|
3649
|
-
},
|
|
3650
|
-
],
|
|
3651
|
-
[
|
|
3652
|
-
'maxFileSize',
|
|
3653
|
-
JSONSchemaBuilder()
|
|
3654
|
-
.addInput({
|
|
3655
|
-
fileInput: {
|
|
3656
|
-
...mockFileInput,
|
|
3657
|
-
'x-jsf-presentation': {
|
|
3658
|
-
...mockFileInput['x-jsf-presentation'],
|
|
3659
|
-
maxFileSize: 1000,
|
|
3660
|
-
},
|
|
3661
|
-
errorMessage: { maxFileSize: 'I am a custom error message' },
|
|
3662
|
-
},
|
|
3663
|
-
})
|
|
3664
|
-
.build(),
|
|
3665
|
-
{
|
|
3666
|
-
fileInput: [
|
|
3667
|
-
(() => {
|
|
3668
|
-
const file = new File([''], 'file.png');
|
|
3669
|
-
Object.defineProperty(file, 'size', { value: 1024 * 1024 * 1024 });
|
|
3670
|
-
return file;
|
|
3671
|
-
})(),
|
|
3672
|
-
],
|
|
3673
|
-
},
|
|
3674
|
-
{
|
|
3675
|
-
fileInput: 'I am a custom error message',
|
|
3676
|
-
},
|
|
3677
|
-
],
|
|
3678
|
-
[
|
|
3679
|
-
'accept',
|
|
3680
|
-
JSONSchemaBuilder()
|
|
3681
|
-
.addInput({
|
|
3682
|
-
fileInput: {
|
|
3683
|
-
...mockFileInput,
|
|
3684
|
-
accept: '.pdf',
|
|
3685
|
-
errorMessage: { accept: 'I am a custom error message' },
|
|
3686
|
-
},
|
|
3687
|
-
})
|
|
3688
|
-
.build(),
|
|
3689
|
-
{
|
|
3690
|
-
fileInput: [new File([''], 'file.docx')],
|
|
3691
|
-
},
|
|
3692
|
-
{
|
|
3693
|
-
fileInput: 'I am a custom error message',
|
|
3694
|
-
},
|
|
3695
|
-
],
|
|
3696
|
-
])('error message for property "%s"', (_, schema, input, errors) => {
|
|
3697
|
-
const { handleValidation } = createHeadlessForm(schema);
|
|
3698
|
-
const validateForm = (vals) => friendlyError(handleValidation(vals));
|
|
3699
|
-
|
|
3700
|
-
if (errors) {
|
|
3701
|
-
expect(validateForm(input)).toEqual(errors);
|
|
3702
|
-
} else {
|
|
3703
|
-
expect(validateForm(input)).toBeUndefined();
|
|
3704
|
-
}
|
|
3705
|
-
});
|
|
3706
|
-
});
|
|
3707
|
-
|
|
3708
|
-
describe('Custom error messages', () => {
|
|
3709
|
-
it.each([
|
|
3710
|
-
[
|
|
3711
|
-
'type',
|
|
3712
|
-
JSONSchemaBuilder()
|
|
3713
|
-
.addInput({
|
|
3714
|
-
numberInput: {
|
|
3715
|
-
...mockNumberInput,
|
|
3716
|
-
'x-jsf-errorMessage': { type: 'It has to be a number.' },
|
|
3717
|
-
},
|
|
3718
|
-
})
|
|
3719
|
-
.build(),
|
|
3720
|
-
{ numberInput: 'Two' },
|
|
3721
|
-
{
|
|
3722
|
-
numberInput: 'It has to be a number.',
|
|
3723
|
-
},
|
|
3724
|
-
false,
|
|
3725
|
-
],
|
|
3726
|
-
[
|
|
3727
|
-
'minimum',
|
|
3728
|
-
JSONSchemaBuilder()
|
|
3729
|
-
.addInput({
|
|
3730
|
-
numberInput: {
|
|
3731
|
-
...mockNumberInput,
|
|
3732
|
-
'x-jsf-errorMessage': { minimum: 'I am a custom error message' },
|
|
3733
|
-
},
|
|
3734
|
-
})
|
|
3735
|
-
.build(),
|
|
3736
|
-
{ numberInput: -1 },
|
|
3737
|
-
{
|
|
3738
|
-
numberInput: 'I am a custom error message',
|
|
3739
|
-
},
|
|
3740
|
-
false,
|
|
3741
|
-
],
|
|
3742
|
-
[
|
|
3743
|
-
'required',
|
|
3744
|
-
JSONSchemaBuilder()
|
|
3745
|
-
.addInput({
|
|
3746
|
-
numberInput: {
|
|
3747
|
-
...mockNumberInput,
|
|
3748
|
-
'x-jsf-errorMessage': { required: 'I am a custom error message' },
|
|
3749
|
-
},
|
|
3750
|
-
})
|
|
3751
|
-
.setRequiredFields(['numberInput'])
|
|
3752
|
-
.build(),
|
|
3753
|
-
{},
|
|
3754
|
-
{
|
|
3755
|
-
numberInput: 'I am a custom error message',
|
|
3756
|
-
},
|
|
3757
|
-
],
|
|
3758
|
-
[
|
|
3759
|
-
'required (ignored because it is optional)',
|
|
3760
|
-
JSONSchemaBuilder()
|
|
3761
|
-
.addInput({
|
|
3762
|
-
numberInput: {
|
|
3763
|
-
...mockNumberInput,
|
|
3764
|
-
'x-jsf-errorMessage': { required: 'I am a custom error message' },
|
|
3765
|
-
},
|
|
3766
|
-
})
|
|
3767
|
-
.build(),
|
|
3768
|
-
{},
|
|
3769
|
-
undefined,
|
|
3770
|
-
],
|
|
3771
|
-
[
|
|
3772
|
-
'maximum',
|
|
3773
|
-
JSONSchemaBuilder()
|
|
3774
|
-
.addInput({
|
|
3775
|
-
numberInput: {
|
|
3776
|
-
...mockNumberInput,
|
|
3777
|
-
'x-jsf-errorMessage': { maximum: 'I am a custom error message' },
|
|
3778
|
-
},
|
|
3779
|
-
})
|
|
3780
|
-
.build(),
|
|
3781
|
-
{ numberInput: 11 },
|
|
3782
|
-
{
|
|
3783
|
-
numberInput: 'I am a custom error message',
|
|
3784
|
-
},
|
|
3785
|
-
],
|
|
3786
|
-
[
|
|
3787
|
-
'minLength',
|
|
3788
|
-
JSONSchemaBuilder()
|
|
3789
|
-
.addInput({
|
|
3790
|
-
stringInput: {
|
|
3791
|
-
...mockTextInput,
|
|
3792
|
-
minLength: 3,
|
|
3793
|
-
'x-jsf-errorMessage': { minLength: 'I am a custom error message' },
|
|
3794
|
-
},
|
|
3795
|
-
})
|
|
3796
|
-
.build(),
|
|
3797
|
-
{ stringInput: 'aa' },
|
|
3798
|
-
{
|
|
3799
|
-
stringInput: 'I am a custom error message',
|
|
3800
|
-
},
|
|
3801
|
-
],
|
|
3802
|
-
[
|
|
3803
|
-
'maxLength',
|
|
3804
|
-
JSONSchemaBuilder()
|
|
3805
|
-
.addInput({
|
|
3806
|
-
stringInput: {
|
|
3807
|
-
...mockTextInput,
|
|
3808
|
-
maxLength: 3,
|
|
3809
|
-
'x-jsf-errorMessage': { maxLength: 'I am a custom error message' },
|
|
3810
|
-
},
|
|
3811
|
-
})
|
|
3812
|
-
.build(),
|
|
3813
|
-
{ stringInput: 'aaaa' },
|
|
3814
|
-
{
|
|
3815
|
-
stringInput: 'I am a custom error message',
|
|
3816
|
-
},
|
|
3817
|
-
],
|
|
3818
|
-
[
|
|
3819
|
-
'pattern',
|
|
3820
|
-
JSONSchemaBuilder()
|
|
3821
|
-
.addInput({
|
|
3822
|
-
stringInput: {
|
|
3823
|
-
...mockTextInput,
|
|
3824
|
-
pattern: '^(\\+|00)\\d*$',
|
|
3825
|
-
'x-jsf-errorMessage': { pattern: 'I am a custom error message' },
|
|
3826
|
-
},
|
|
3827
|
-
})
|
|
3828
|
-
.build(),
|
|
3829
|
-
{ stringInput: 'aaaa' },
|
|
3830
|
-
{
|
|
3831
|
-
stringInput: 'I am a custom error message',
|
|
3832
|
-
},
|
|
3833
|
-
],
|
|
3834
|
-
[
|
|
3835
|
-
'maxFileSize',
|
|
3836
|
-
JSONSchemaBuilder()
|
|
3837
|
-
.addInput({
|
|
3838
|
-
fileInput: {
|
|
3839
|
-
...mockFileInput,
|
|
3840
|
-
'x-jsf-presentation': {
|
|
3841
|
-
...mockFileInput['x-jsf-presentation'],
|
|
3842
|
-
maxFileSize: 1000,
|
|
3843
|
-
},
|
|
3844
|
-
'x-jsf-errorMessage': { maxFileSize: 'I am a custom error message' },
|
|
3845
|
-
},
|
|
3846
|
-
})
|
|
3847
|
-
.build(),
|
|
3848
|
-
{
|
|
3849
|
-
fileInput: [
|
|
3850
|
-
(() => {
|
|
3851
|
-
const file = new File([''], 'file.png');
|
|
3852
|
-
Object.defineProperty(file, 'size', { value: 1024 * 1024 * 1024 });
|
|
3853
|
-
return file;
|
|
3854
|
-
})(),
|
|
3855
|
-
],
|
|
3856
|
-
},
|
|
3857
|
-
{
|
|
3858
|
-
fileInput: 'I am a custom error message',
|
|
3859
|
-
},
|
|
3860
|
-
],
|
|
3861
|
-
[
|
|
3862
|
-
'accept',
|
|
3863
|
-
JSONSchemaBuilder()
|
|
3864
|
-
.addInput({
|
|
3865
|
-
fileInput: {
|
|
3866
|
-
...mockFileInput,
|
|
3867
|
-
accept: '.pdf',
|
|
3868
|
-
'x-jsf-errorMessage': { accept: 'I am a custom error message' },
|
|
3869
|
-
},
|
|
3870
|
-
})
|
|
3871
|
-
.build(),
|
|
3872
|
-
{
|
|
3873
|
-
fileInput: [new File([''], 'file.docx')],
|
|
3874
|
-
},
|
|
3875
|
-
{
|
|
3876
|
-
fileInput: 'I am a custom error message',
|
|
3877
|
-
},
|
|
3878
|
-
],
|
|
3879
|
-
])('error message for property "%s"', (_, schema, input, errors) => {
|
|
3880
|
-
const { handleValidation } = createHeadlessForm(schema);
|
|
3881
|
-
const validateForm = (vals) => friendlyError(handleValidation(vals));
|
|
3882
|
-
|
|
3883
|
-
if (errors) {
|
|
3884
|
-
expect(validateForm(input)).toEqual(errors);
|
|
3885
|
-
} else {
|
|
3886
|
-
expect(validateForm(input)).toBeUndefined();
|
|
3887
|
-
}
|
|
3888
|
-
});
|
|
3889
|
-
|
|
3890
|
-
it('accepts with options.inputType[].errorMessage', () => {
|
|
3891
|
-
// Sanity-check the default error message
|
|
3892
|
-
const resultDefault = createHeadlessForm(schemaForErrorMessageSpecificity);
|
|
3893
|
-
expect(resultDefault.handleValidation({}).formErrors).toEqual({
|
|
3894
|
-
weekday: 'Required field',
|
|
3895
|
-
day: 'Required field',
|
|
3896
|
-
month: 'Required field',
|
|
3897
|
-
year: 'The year is mandatory.', // from x-jsf-errorMessage
|
|
3898
|
-
});
|
|
3899
|
-
|
|
3900
|
-
// Assert the custom error message
|
|
3901
|
-
const resultCustom = createHeadlessForm(schemaForErrorMessageSpecificity, {
|
|
3902
|
-
...jsfConfigForErrorMessageSpecificity,
|
|
3903
|
-
});
|
|
3904
|
-
expect(resultCustom.handleValidation({}).formErrors).toEqual({
|
|
3905
|
-
weekday: 'Required field', // sanity-check that a different inputType keeps the default error msg.
|
|
3906
|
-
day: 'This cannot be empty.',
|
|
3907
|
-
month: 'This cannot be empty.',
|
|
3908
|
-
year: 'The year is mandatory.', // error specificity: schema's msg is higher than options' msg.
|
|
3909
|
-
});
|
|
3910
|
-
});
|
|
3911
|
-
});
|
|
3912
|
-
|
|
3913
|
-
describe('given default values', () => {
|
|
3914
|
-
describe('and a field with conditional presentation properties', () => {
|
|
3915
|
-
it('returns the nested properties when the conditional matches', () => {
|
|
3916
|
-
const { fields } = createHeadlessForm(schemaWithConditionalPresentationProperties, {
|
|
3917
|
-
initialValues: {
|
|
3918
|
-
// show the hidden statement
|
|
3919
|
-
mock_radio: 'no',
|
|
3920
|
-
},
|
|
3921
|
-
});
|
|
3922
|
-
|
|
3923
|
-
expect(fields[0].statement.description).toBe(`<a href="">conditional statement markup</a>`);
|
|
3924
|
-
});
|
|
3925
|
-
});
|
|
3926
|
-
describe('and "fieldset" has scoped conditionals', () => {
|
|
3927
|
-
it('should show conditionals fields when values fullfil conditions', () => {
|
|
3928
|
-
const result = createHeadlessForm(schemaFieldsetScopedCondition, {
|
|
3929
|
-
initialValues: { child: { has_child: 'yes' } },
|
|
3930
|
-
});
|
|
3931
|
-
|
|
3932
|
-
const fieldset = result.fields[0];
|
|
3933
|
-
|
|
3934
|
-
expect(fieldset).toMatchObject({
|
|
3935
|
-
fields: [
|
|
3936
|
-
{
|
|
3937
|
-
name: 'has_child',
|
|
3938
|
-
required: true,
|
|
3939
|
-
},
|
|
3940
|
-
{
|
|
3941
|
-
name: 'age',
|
|
3942
|
-
required: true,
|
|
3943
|
-
isVisible: true,
|
|
3944
|
-
},
|
|
3945
|
-
{
|
|
3946
|
-
name: 'passport_id',
|
|
3947
|
-
required: false,
|
|
3948
|
-
isVisible: true,
|
|
3949
|
-
},
|
|
3950
|
-
],
|
|
3951
|
-
});
|
|
3952
|
-
});
|
|
3953
|
-
|
|
3954
|
-
it('should hide conditionals fields when values do not fullfil conditions', () => {
|
|
3955
|
-
const result = createHeadlessForm(schemaFieldsetScopedCondition, {
|
|
3956
|
-
child: { has_child: 'no' },
|
|
3957
|
-
});
|
|
3958
|
-
|
|
3959
|
-
const fieldset = result.fields[0];
|
|
3960
|
-
|
|
3961
|
-
expect(fieldset).toMatchObject({
|
|
3962
|
-
fields: [
|
|
3963
|
-
{
|
|
3964
|
-
name: 'has_child',
|
|
3965
|
-
required: true,
|
|
3966
|
-
},
|
|
3967
|
-
{
|
|
3968
|
-
name: 'age',
|
|
3969
|
-
required: false,
|
|
3970
|
-
isVisible: false,
|
|
3971
|
-
},
|
|
3972
|
-
{
|
|
3973
|
-
name: 'passport_id',
|
|
3974
|
-
required: false,
|
|
3975
|
-
isVisible: false,
|
|
3976
|
-
},
|
|
3977
|
-
],
|
|
3978
|
-
});
|
|
3979
|
-
});
|
|
3980
|
-
|
|
3981
|
-
it('should ignore initial values that do not match the field type (eg string vs object)', () => {
|
|
3982
|
-
const result = createHeadlessForm(schemaInputTypeFieldset, {
|
|
3983
|
-
initialValues: {
|
|
3984
|
-
a_fieldset: 'foo', // should be an object instead of string
|
|
3985
|
-
},
|
|
3986
|
-
});
|
|
3987
|
-
|
|
3988
|
-
// It returns fields without errors
|
|
3989
|
-
expect(result.fields).toBeDefined();
|
|
3990
|
-
expect(result.fields[0].fields[0].name).toBe('username');
|
|
3991
|
-
expect(result.fields[0].fields[1].name).toBe('tabs');
|
|
3992
|
-
|
|
3993
|
-
// Warn about those missmatched values
|
|
3994
|
-
expect(console.warn).toHaveBeenCalledWith(
|
|
3995
|
-
`Field "a_fieldset"'s value is "foo", but should be type object.`
|
|
3996
|
-
);
|
|
3997
|
-
console.warn.mockClear();
|
|
3998
|
-
|
|
3999
|
-
expect(console.error).not.toHaveBeenCalled();
|
|
4000
|
-
});
|
|
4001
|
-
});
|
|
4002
|
-
});
|
|
4003
|
-
|
|
4004
|
-
describe('parser options', () => {
|
|
4005
|
-
it('should support any custom field attribute', () => {
|
|
4006
|
-
const customAttrs = {
|
|
4007
|
-
something: 'foo', // a misc attribute
|
|
4008
|
-
inputType: 'super', // overrides "textarea"
|
|
4009
|
-
falsy: false, // accepts falsy attributes
|
|
4010
|
-
};
|
|
4011
|
-
const result = createHeadlessForm(
|
|
4012
|
-
{
|
|
4013
|
-
properties: {
|
|
4014
|
-
feedback: {
|
|
4015
|
-
title: 'Your feedback',
|
|
4016
|
-
type: 'string',
|
|
4017
|
-
presentation: {
|
|
4018
|
-
inputType: 'textarea',
|
|
4019
|
-
},
|
|
4020
|
-
},
|
|
4021
|
-
},
|
|
4022
|
-
},
|
|
4023
|
-
{
|
|
4024
|
-
customProperties: {
|
|
4025
|
-
feedback: {
|
|
4026
|
-
...customAttrs,
|
|
4027
|
-
},
|
|
4028
|
-
},
|
|
4029
|
-
}
|
|
4030
|
-
);
|
|
4031
|
-
|
|
4032
|
-
expect(result).toMatchObject({
|
|
4033
|
-
fields: [
|
|
4034
|
-
{
|
|
4035
|
-
name: 'feedback',
|
|
4036
|
-
label: 'Your feedback',
|
|
4037
|
-
jsonType: 'string',
|
|
4038
|
-
...customAttrs,
|
|
4039
|
-
},
|
|
4040
|
-
],
|
|
4041
|
-
});
|
|
4042
|
-
});
|
|
4043
|
-
|
|
4044
|
-
it('should support custom description (checkbox)', () => {
|
|
4045
|
-
const result = createHeadlessForm(
|
|
4046
|
-
{
|
|
4047
|
-
properties: {
|
|
4048
|
-
terms: {
|
|
4049
|
-
const: 'Agreed',
|
|
4050
|
-
title: 'Terms',
|
|
4051
|
-
description: 'Accept terms.',
|
|
4052
|
-
type: 'string',
|
|
4053
|
-
presentation: { inputType: 'checkbox' },
|
|
4054
|
-
},
|
|
4055
|
-
},
|
|
4056
|
-
},
|
|
4057
|
-
{
|
|
4058
|
-
customProperties: {
|
|
4059
|
-
terms: {
|
|
4060
|
-
description: (text) => `Extra text before. ${text}`,
|
|
4061
|
-
},
|
|
4062
|
-
},
|
|
4063
|
-
}
|
|
4064
|
-
);
|
|
4065
|
-
|
|
4066
|
-
expect(result).toMatchObject({
|
|
4067
|
-
fields: [
|
|
4068
|
-
{
|
|
4069
|
-
label: 'Terms',
|
|
4070
|
-
description: 'Extra text before. Accept terms.', // ensure custom description works
|
|
4071
|
-
name: 'terms',
|
|
4072
|
-
required: false,
|
|
4073
|
-
inputType: 'checkbox',
|
|
4074
|
-
type: 'checkbox',
|
|
4075
|
-
jsonType: 'string',
|
|
4076
|
-
checkboxValue: 'Agreed', // ensure _composeFieldCheckbox(). transformations are passed.
|
|
4077
|
-
},
|
|
4078
|
-
],
|
|
4079
|
-
});
|
|
4080
|
-
|
|
4081
|
-
// ensure _composeFieldCheckbox() "value" destructure happens.
|
|
4082
|
-
expect(result.fields[0]).not.toHaveProperty('value');
|
|
4083
|
-
});
|
|
4084
|
-
|
|
4085
|
-
it('should ignore fields that are not present in the schema', () => {
|
|
4086
|
-
const schemaBase = {
|
|
4087
|
-
properties: {
|
|
4088
|
-
feedback: {
|
|
4089
|
-
title: 'Your feedback',
|
|
4090
|
-
type: 'string',
|
|
4091
|
-
presentation: {
|
|
4092
|
-
inputType: 'textarea',
|
|
4093
|
-
},
|
|
4094
|
-
},
|
|
4095
|
-
},
|
|
4096
|
-
};
|
|
4097
|
-
|
|
4098
|
-
const resultWithoutCustomProperties = createHeadlessForm(schemaBase);
|
|
4099
|
-
const resultWithInvalidCustomProperty = createHeadlessForm(schemaBase, {
|
|
4100
|
-
customProperties: {
|
|
4101
|
-
unknown: {
|
|
4102
|
-
'data-foo': 'baz',
|
|
4103
|
-
},
|
|
4104
|
-
},
|
|
4105
|
-
});
|
|
4106
|
-
|
|
4107
|
-
function assertResultHasNoCustomizations(result) {
|
|
4108
|
-
expect(result.fields).toHaveLength(1); // The "unknown" is not present
|
|
4109
|
-
expect(result.fields[0].name).toBe('feedback');
|
|
4110
|
-
expect(result.fields[0]).not.toHaveProperty('data-foo');
|
|
4111
|
-
}
|
|
4112
|
-
|
|
4113
|
-
assertResultHasNoCustomizations(resultWithoutCustomProperties);
|
|
4114
|
-
assertResultHasNoCustomizations(resultWithInvalidCustomProperty);
|
|
4115
|
-
});
|
|
4116
|
-
|
|
4117
|
-
it('should handle custom properties when inside fieldsets', () => {
|
|
4118
|
-
const result = createHeadlessForm(
|
|
4119
|
-
JSONSchemaBuilder()
|
|
4120
|
-
.addInput({
|
|
4121
|
-
id_number: mockNumberInput,
|
|
4122
|
-
})
|
|
4123
|
-
.addInput({
|
|
4124
|
-
fieldset: mockFieldset,
|
|
4125
|
-
})
|
|
4126
|
-
.addInput({ nestedFieldset: mockNestedFieldset })
|
|
4127
|
-
.build(),
|
|
4128
|
-
{
|
|
4129
|
-
customProperties: {
|
|
4130
|
-
id_number: { 'data-field': 'field' },
|
|
4131
|
-
fieldset: {
|
|
4132
|
-
customProperties: {
|
|
4133
|
-
username: { 'data-fieldset': 'fieldset' },
|
|
4134
|
-
},
|
|
4135
|
-
},
|
|
4136
|
-
nestedFieldset: {
|
|
4137
|
-
customProperties: {
|
|
4138
|
-
innerFieldset: {
|
|
4139
|
-
customProperties: {
|
|
4140
|
-
username: { 'data-nested-fieldset': 'nested-fieldset' },
|
|
4141
|
-
},
|
|
4142
|
-
},
|
|
4143
|
-
},
|
|
4144
|
-
},
|
|
4145
|
-
},
|
|
4146
|
-
}
|
|
4147
|
-
);
|
|
4148
|
-
|
|
4149
|
-
expect(result).toMatchObject({
|
|
4150
|
-
fields: [
|
|
4151
|
-
{
|
|
4152
|
-
'data-field': 'field',
|
|
4153
|
-
name: 'id_number',
|
|
4154
|
-
},
|
|
4155
|
-
{
|
|
4156
|
-
name: 'fieldset',
|
|
4157
|
-
fields: [
|
|
4158
|
-
{
|
|
4159
|
-
name: 'username',
|
|
4160
|
-
'data-fieldset': 'fieldset',
|
|
4161
|
-
},
|
|
4162
|
-
{
|
|
4163
|
-
name: 'tabs',
|
|
4164
|
-
},
|
|
4165
|
-
],
|
|
4166
|
-
},
|
|
4167
|
-
{
|
|
4168
|
-
name: 'nestedFieldset',
|
|
4169
|
-
fields: [
|
|
4170
|
-
{
|
|
4171
|
-
name: 'innerFieldset',
|
|
4172
|
-
fields: [
|
|
4173
|
-
{
|
|
4174
|
-
name: 'username',
|
|
4175
|
-
'data-nested-fieldset': 'nested-fieldset',
|
|
4176
|
-
},
|
|
4177
|
-
{
|
|
4178
|
-
name: 'tabs',
|
|
4179
|
-
},
|
|
4180
|
-
],
|
|
4181
|
-
},
|
|
4182
|
-
],
|
|
4183
|
-
},
|
|
4184
|
-
],
|
|
4185
|
-
});
|
|
4186
|
-
|
|
4187
|
-
const [fieldResult, fildsetResult, nestedFieldsetResult] = result.fields;
|
|
4188
|
-
|
|
4189
|
-
// Sanity check that custom attrs are not "leaked" into other fields
|
|
4190
|
-
// $.id_number
|
|
4191
|
-
expect(fieldResult).toHaveProperty('name', 'id_number');
|
|
4192
|
-
expect(fieldResult).toHaveProperty('data-field', 'field');
|
|
4193
|
-
expect(fieldResult).not.toHaveProperty('data-fieldset');
|
|
4194
|
-
expect(fieldResult).not.toHaveProperty('data-nested-fieldset');
|
|
4195
|
-
|
|
4196
|
-
// $.fieldset.username
|
|
4197
|
-
expect(fildsetResult.fields[0]).toHaveProperty('name', 'username');
|
|
4198
|
-
expect(fildsetResult.fields[0]).toHaveProperty('data-fieldset', 'fieldset');
|
|
4199
|
-
expect(fildsetResult.fields[0]).not.toHaveProperty('data-field');
|
|
4200
|
-
expect(fildsetResult.fields[0]).not.toHaveProperty('data-nested-fieldset');
|
|
4201
|
-
expect(fildsetResult.fields[1]).not.toHaveProperty('data-field');
|
|
4202
|
-
expect(fildsetResult.fields[1]).not.toHaveProperty('data-nested-fieldset');
|
|
4203
|
-
|
|
4204
|
-
// $.nestedFieldset.innerFieldset.id_number
|
|
4205
|
-
expect(nestedFieldsetResult.fields[0].fields[0]).toHaveProperty('name', 'username');
|
|
4206
|
-
expect(nestedFieldsetResult.fields[0].fields[0]).toHaveProperty(
|
|
4207
|
-
'data-nested-fieldset',
|
|
4208
|
-
'nested-fieldset'
|
|
4209
|
-
);
|
|
4210
|
-
expect(nestedFieldsetResult.fields[0].fields[0]).not.toHaveProperty('data-field');
|
|
4211
|
-
expect(nestedFieldsetResult.fields[0].fields[0]).not.toHaveProperty('data-fieldset');
|
|
4212
|
-
expect(nestedFieldsetResult.fields[0].fields[1]).not.toHaveProperty('data-field');
|
|
4213
|
-
expect(nestedFieldsetResult.fields[0].fields[1]).not.toHaveProperty('data-fieldset');
|
|
4214
|
-
});
|
|
4215
|
-
it('should handle custom properties when inside fieldsets for fields name clashing with reserved words', () => {
|
|
4216
|
-
const { fields } = createHeadlessForm(
|
|
4217
|
-
{
|
|
4218
|
-
properties: {
|
|
4219
|
-
dog: {
|
|
4220
|
-
title: 'Dog details',
|
|
4221
|
-
description: 'Fieldset description',
|
|
4222
|
-
'x-jsf-presentation': {
|
|
4223
|
-
inputType: 'fieldset',
|
|
4224
|
-
},
|
|
4225
|
-
properties: {
|
|
4226
|
-
name: {
|
|
4227
|
-
// This fieldName (name) clashs with the field specs "name"
|
|
4228
|
-
title: 'Dogs name',
|
|
4229
|
-
'x-jsf-presentation': {
|
|
4230
|
-
inputType: 'text',
|
|
4231
|
-
},
|
|
4232
|
-
type: 'string',
|
|
4233
|
-
},
|
|
4234
|
-
type: {
|
|
4235
|
-
// This field name (type) clashs with the field specs "type"
|
|
4236
|
-
title: 'Breed type',
|
|
4237
|
-
'x-jsf-presentation': {
|
|
4238
|
-
inputType: 'number',
|
|
4239
|
-
},
|
|
4240
|
-
type: 'string',
|
|
4241
|
-
},
|
|
4242
|
-
},
|
|
4243
|
-
required: ['name'],
|
|
4244
|
-
type: 'object',
|
|
4245
|
-
},
|
|
4246
|
-
},
|
|
4247
|
-
required: ['dog'],
|
|
4248
|
-
},
|
|
4249
|
-
{
|
|
4250
|
-
customProperties: {
|
|
4251
|
-
dog: {
|
|
4252
|
-
customProperties: {
|
|
4253
|
-
name: {
|
|
4254
|
-
description: "What's your dogs name",
|
|
4255
|
-
},
|
|
4256
|
-
},
|
|
4257
|
-
},
|
|
4258
|
-
},
|
|
4259
|
-
}
|
|
4260
|
-
);
|
|
4261
|
-
|
|
4262
|
-
expect(fields.length).toBe(1);
|
|
4263
|
-
expect(fields[0].fields.length).toBe(2);
|
|
4264
|
-
expect(fields[0].fields[0].name).toBe('name');
|
|
4265
|
-
expect(fields[0].fields[0].description).toBe("What's your dogs name");
|
|
4266
|
-
});
|
|
4267
|
-
});
|
|
4268
|
-
|
|
4269
|
-
describe('presentation (deprecated in favor of x-jsf-presentation)', () => {
|
|
4270
|
-
it('works well with position, description, inputType, and any other arbitrary attribute', () => {
|
|
4271
|
-
const { fields } = createHeadlessForm({
|
|
4272
|
-
properties: {
|
|
4273
|
-
day: {
|
|
4274
|
-
title: 'Date',
|
|
4275
|
-
presentation: {
|
|
4276
|
-
inputType: 'date',
|
|
4277
|
-
position: 1,
|
|
4278
|
-
foo: 'bar',
|
|
4279
|
-
statement: {
|
|
4280
|
-
description: 'ss',
|
|
4281
|
-
},
|
|
4282
|
-
},
|
|
4283
|
-
},
|
|
4284
|
-
time: {
|
|
4285
|
-
title: 'Time',
|
|
4286
|
-
presentation: {
|
|
4287
|
-
inputType: 'clock',
|
|
4288
|
-
description: 'Write in <b>hh:ss</b> format',
|
|
4289
|
-
position: 0,
|
|
4290
|
-
deprecated: {
|
|
4291
|
-
description: 'In favor of X',
|
|
4292
|
-
},
|
|
4293
|
-
},
|
|
4294
|
-
},
|
|
4295
|
-
},
|
|
4296
|
-
});
|
|
4297
|
-
|
|
4298
|
-
// Assert order from presentation.position
|
|
4299
|
-
expect(fields[0].name).toBe('time');
|
|
4300
|
-
expect(fields[1].name).toBe('day');
|
|
4301
|
-
|
|
4302
|
-
// Assert spreaded attributes
|
|
4303
|
-
expect(fields).toMatchObject([
|
|
4304
|
-
{
|
|
4305
|
-
name: 'time',
|
|
4306
|
-
description: 'Write in <b>hh:ss</b> format', // from presentation
|
|
4307
|
-
inputType: 'clock', // arbitrary type from presentation
|
|
4308
|
-
deprecated: {
|
|
4309
|
-
description: 'In favor of X', // from presentation
|
|
4310
|
-
},
|
|
4311
|
-
},
|
|
4312
|
-
{
|
|
4313
|
-
name: 'day',
|
|
4314
|
-
inputType: 'date', // arbitrary type from presentation
|
|
4315
|
-
foo: 'bar', // spread from presentation
|
|
4316
|
-
statement: {
|
|
4317
|
-
// from presentation
|
|
4318
|
-
description: 'ss',
|
|
4319
|
-
},
|
|
4320
|
-
},
|
|
4321
|
-
]);
|
|
4322
|
-
});
|
|
4323
|
-
});
|
|
4324
|
-
});
|