@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
package/src/tests/modify.test.js
DELETED
|
@@ -1,939 +0,0 @@
|
|
|
1
|
-
import { modify } from '@/modify';
|
|
2
|
-
|
|
3
|
-
const schemaPet = {
|
|
4
|
-
type: 'object',
|
|
5
|
-
additionalProperties: false,
|
|
6
|
-
properties: {
|
|
7
|
-
has_pet: {
|
|
8
|
-
title: 'Has Pet',
|
|
9
|
-
description: 'Do you have a pet?',
|
|
10
|
-
oneOf: [
|
|
11
|
-
{
|
|
12
|
-
title: 'Yes',
|
|
13
|
-
const: 'yes',
|
|
14
|
-
},
|
|
15
|
-
{
|
|
16
|
-
title: 'No',
|
|
17
|
-
const: 'no',
|
|
18
|
-
},
|
|
19
|
-
],
|
|
20
|
-
'x-jsf-presentation': {
|
|
21
|
-
inputType: 'radio',
|
|
22
|
-
},
|
|
23
|
-
type: 'string',
|
|
24
|
-
},
|
|
25
|
-
pet_name: {
|
|
26
|
-
title: "Pet's name",
|
|
27
|
-
description: "What's your pet's name?",
|
|
28
|
-
'x-jsf-presentation': {
|
|
29
|
-
inputType: 'text',
|
|
30
|
-
},
|
|
31
|
-
type: 'string',
|
|
32
|
-
},
|
|
33
|
-
pet_age: {
|
|
34
|
-
title: "Pet's age in months",
|
|
35
|
-
maximum: 24,
|
|
36
|
-
'x-jsf-presentation': {
|
|
37
|
-
inputType: 'number',
|
|
38
|
-
},
|
|
39
|
-
'x-jsf-errorMessage': {
|
|
40
|
-
maximum: 'Your pet cannot be older than 24 months.',
|
|
41
|
-
},
|
|
42
|
-
type: 'integer',
|
|
43
|
-
},
|
|
44
|
-
pet_fat: {
|
|
45
|
-
title: 'Pet fat percentage',
|
|
46
|
-
'x-jsf-presentation': {
|
|
47
|
-
inputType: 'number',
|
|
48
|
-
percentage: true,
|
|
49
|
-
},
|
|
50
|
-
type: 'integer',
|
|
51
|
-
},
|
|
52
|
-
pet_address: {
|
|
53
|
-
properties: {
|
|
54
|
-
street: {
|
|
55
|
-
title: 'Street',
|
|
56
|
-
'x-jsf-presentation': {
|
|
57
|
-
inputType: 'text',
|
|
58
|
-
},
|
|
59
|
-
},
|
|
60
|
-
},
|
|
61
|
-
},
|
|
62
|
-
},
|
|
63
|
-
required: ['has_pet'],
|
|
64
|
-
'x-jsf-order': ['has_pet', 'pet_name', 'pet_age', 'pet_fat', 'pet_address'],
|
|
65
|
-
allOf: [
|
|
66
|
-
{
|
|
67
|
-
id: 'pet_conditional_id',
|
|
68
|
-
if: {
|
|
69
|
-
properties: {
|
|
70
|
-
has_pet: {
|
|
71
|
-
const: 'yes',
|
|
72
|
-
},
|
|
73
|
-
},
|
|
74
|
-
required: ['has_pet'],
|
|
75
|
-
},
|
|
76
|
-
then: {
|
|
77
|
-
required: ['pet_name', 'pet_age', 'pet_fat'],
|
|
78
|
-
},
|
|
79
|
-
else: {
|
|
80
|
-
properties: {
|
|
81
|
-
pet_name: false,
|
|
82
|
-
pet_age: false,
|
|
83
|
-
pet_fat: false,
|
|
84
|
-
},
|
|
85
|
-
},
|
|
86
|
-
},
|
|
87
|
-
],
|
|
88
|
-
};
|
|
89
|
-
|
|
90
|
-
const schemaAddress = {
|
|
91
|
-
properties: {
|
|
92
|
-
address: {
|
|
93
|
-
properties: {
|
|
94
|
-
street: {
|
|
95
|
-
title: 'Street',
|
|
96
|
-
},
|
|
97
|
-
number: {
|
|
98
|
-
title: 'Number',
|
|
99
|
-
},
|
|
100
|
-
city: {
|
|
101
|
-
title: 'City',
|
|
102
|
-
},
|
|
103
|
-
},
|
|
104
|
-
},
|
|
105
|
-
},
|
|
106
|
-
};
|
|
107
|
-
|
|
108
|
-
beforeAll(() => {
|
|
109
|
-
jest.spyOn(console, 'warn').mockImplementation(() => {});
|
|
110
|
-
});
|
|
111
|
-
|
|
112
|
-
afterAll(() => {
|
|
113
|
-
console.warn.mockRestore();
|
|
114
|
-
});
|
|
115
|
-
|
|
116
|
-
describe('modify() - warnings', () => {
|
|
117
|
-
it('logs a warning by default', () => {
|
|
118
|
-
const result = modify(schemaPet, {});
|
|
119
|
-
|
|
120
|
-
expect(console.warn).toBeCalledWith(
|
|
121
|
-
'json-schema-form modify(): We highly recommend you to handle/report the returned `warnings` as they highlight possible bugs in your modifications. To mute this log, pass `muteLogging: true` to the config.'
|
|
122
|
-
);
|
|
123
|
-
|
|
124
|
-
console.warn.mockClear();
|
|
125
|
-
expect(result.warnings).toEqual([]);
|
|
126
|
-
});
|
|
127
|
-
|
|
128
|
-
it('given muteLogging, it does not log the warning', () => {
|
|
129
|
-
const result = modify(schemaPet, {
|
|
130
|
-
muteLogging: true,
|
|
131
|
-
});
|
|
132
|
-
|
|
133
|
-
expect(console.warn).not.toBeCalled();
|
|
134
|
-
expect(result.warnings).toEqual([]);
|
|
135
|
-
});
|
|
136
|
-
});
|
|
137
|
-
|
|
138
|
-
describe('modify() - basic mutations', () => {
|
|
139
|
-
it('replace base field', () => {
|
|
140
|
-
const result = modify(schemaPet, {
|
|
141
|
-
fields: {
|
|
142
|
-
pet_name: {
|
|
143
|
-
title: 'Your pet name',
|
|
144
|
-
},
|
|
145
|
-
has_pet: (fieldAttrs) => {
|
|
146
|
-
const options = fieldAttrs.oneOf?.map(({ title }) => title).join(' or ') || '';
|
|
147
|
-
return {
|
|
148
|
-
title: 'Pet owner',
|
|
149
|
-
description: `Do you own a pet? ${options}?`, // "Do you own a pet? Yes or No?"
|
|
150
|
-
};
|
|
151
|
-
},
|
|
152
|
-
},
|
|
153
|
-
});
|
|
154
|
-
|
|
155
|
-
expect(result.schema).toMatchObject({
|
|
156
|
-
properties: {
|
|
157
|
-
pet_name: {
|
|
158
|
-
title: 'Your pet name',
|
|
159
|
-
},
|
|
160
|
-
has_pet: {
|
|
161
|
-
title: 'Pet owner',
|
|
162
|
-
description: 'Do you own a pet? Yes or No?',
|
|
163
|
-
},
|
|
164
|
-
},
|
|
165
|
-
});
|
|
166
|
-
});
|
|
167
|
-
|
|
168
|
-
it('replace nested field', () => {
|
|
169
|
-
const result = modify(schemaAddress, {
|
|
170
|
-
fields: {
|
|
171
|
-
// You can use dot notation
|
|
172
|
-
'address.street': {
|
|
173
|
-
title: 'Street name',
|
|
174
|
-
},
|
|
175
|
-
'address.city': () => ({
|
|
176
|
-
title: 'City name',
|
|
177
|
-
}),
|
|
178
|
-
// Or pass the native object
|
|
179
|
-
address: (fieldAttrs) => {
|
|
180
|
-
return {
|
|
181
|
-
properties: {
|
|
182
|
-
number: (nestedAttrs) => {
|
|
183
|
-
return {
|
|
184
|
-
'x-test-siblings': Object.keys(fieldAttrs.properties),
|
|
185
|
-
title: `Door ${nestedAttrs.title}`,
|
|
186
|
-
};
|
|
187
|
-
},
|
|
188
|
-
},
|
|
189
|
-
};
|
|
190
|
-
},
|
|
191
|
-
},
|
|
192
|
-
});
|
|
193
|
-
|
|
194
|
-
expect(result.schema).toMatchObject({
|
|
195
|
-
properties: {
|
|
196
|
-
address: {
|
|
197
|
-
properties: {
|
|
198
|
-
street: {
|
|
199
|
-
title: 'Street name',
|
|
200
|
-
},
|
|
201
|
-
number: {
|
|
202
|
-
title: 'Door Number',
|
|
203
|
-
'x-test-siblings': ['street', 'number', 'city'],
|
|
204
|
-
},
|
|
205
|
-
city: {
|
|
206
|
-
title: 'City name',
|
|
207
|
-
},
|
|
208
|
-
},
|
|
209
|
-
},
|
|
210
|
-
},
|
|
211
|
-
});
|
|
212
|
-
});
|
|
213
|
-
|
|
214
|
-
it('replace fields that dont exist gets ignored', () => {
|
|
215
|
-
// IMPORTANT NOTE on this behavior:
|
|
216
|
-
// Context: At Remote we have a lot of global customization that run equally across multiple different JSON Schemas.
|
|
217
|
-
// With this, we avoid applying customizations to non-existing fields. (aka create fields)
|
|
218
|
-
// That's why we have the "create" config, specific to create new fields.
|
|
219
|
-
const result = modify(schemaPet, {
|
|
220
|
-
fields: {
|
|
221
|
-
unknown_field: {
|
|
222
|
-
title: 'This field does not exist in the original schema',
|
|
223
|
-
},
|
|
224
|
-
'nested.field': {
|
|
225
|
-
title: 'Nop',
|
|
226
|
-
},
|
|
227
|
-
pet_name: {
|
|
228
|
-
title: 'New pet title',
|
|
229
|
-
},
|
|
230
|
-
},
|
|
231
|
-
});
|
|
232
|
-
|
|
233
|
-
expect(result.schema.properties.unknown_field).toBeUndefined();
|
|
234
|
-
expect(result.schema.properties.nested).toBeUndefined();
|
|
235
|
-
expect(result.schema.properties.pet_name).toEqual({
|
|
236
|
-
...schemaPet.properties.pet_name,
|
|
237
|
-
title: 'New pet title',
|
|
238
|
-
});
|
|
239
|
-
|
|
240
|
-
expect(result.warnings).toEqual([
|
|
241
|
-
{
|
|
242
|
-
type: 'FIELD_TO_CHANGE_NOT_FOUND',
|
|
243
|
-
message: 'Changing field "unknown_field" was ignored because it does not exist.',
|
|
244
|
-
},
|
|
245
|
-
{
|
|
246
|
-
type: 'FIELD_TO_CHANGE_NOT_FOUND',
|
|
247
|
-
message: 'Changing field "nested.field" was ignored because it does not exist.',
|
|
248
|
-
},
|
|
249
|
-
]);
|
|
250
|
-
});
|
|
251
|
-
|
|
252
|
-
it('replace all fields', () => {
|
|
253
|
-
const result = modify(schemaPet, {
|
|
254
|
-
allFields: (fieldName, fieldAttrs) => {
|
|
255
|
-
const { inputType, percentage } = fieldAttrs?.['x-jsf-presentation'] || {};
|
|
256
|
-
|
|
257
|
-
if (inputType === 'number' && percentage === true) {
|
|
258
|
-
return {
|
|
259
|
-
styleDecimals: 2,
|
|
260
|
-
};
|
|
261
|
-
}
|
|
262
|
-
|
|
263
|
-
return {
|
|
264
|
-
dataFoo: 'abc',
|
|
265
|
-
};
|
|
266
|
-
},
|
|
267
|
-
});
|
|
268
|
-
|
|
269
|
-
expect(result.schema).toMatchObject({
|
|
270
|
-
properties: {
|
|
271
|
-
has_pet: {
|
|
272
|
-
dataFoo: 'abc',
|
|
273
|
-
},
|
|
274
|
-
pet_name: {
|
|
275
|
-
dataFoo: 'abc',
|
|
276
|
-
},
|
|
277
|
-
pet_age: {
|
|
278
|
-
dataFoo: 'abc',
|
|
279
|
-
},
|
|
280
|
-
pet_fat: {
|
|
281
|
-
styleDecimals: 2,
|
|
282
|
-
},
|
|
283
|
-
pet_address: {
|
|
284
|
-
// assert recursivity
|
|
285
|
-
properties: {
|
|
286
|
-
street: {
|
|
287
|
-
dataFoo: 'abc',
|
|
288
|
-
},
|
|
289
|
-
},
|
|
290
|
-
},
|
|
291
|
-
},
|
|
292
|
-
});
|
|
293
|
-
});
|
|
294
|
-
|
|
295
|
-
it('replace field attrs that are arrays (partial)', () => {
|
|
296
|
-
const result = modify(schemaPet, {
|
|
297
|
-
fields: {
|
|
298
|
-
has_pet: (fieldAttrs) => {
|
|
299
|
-
const labelsMap = {
|
|
300
|
-
yes: 'Yes, I have',
|
|
301
|
-
};
|
|
302
|
-
|
|
303
|
-
return {
|
|
304
|
-
oneOf: fieldAttrs.oneOf.map((option) => {
|
|
305
|
-
const customTitle = labelsMap[option.const];
|
|
306
|
-
if (!customTitle) {
|
|
307
|
-
// TODO - test this
|
|
308
|
-
// console.error('The option is not handled.');
|
|
309
|
-
return option;
|
|
310
|
-
}
|
|
311
|
-
return {
|
|
312
|
-
...option,
|
|
313
|
-
title: customTitle,
|
|
314
|
-
};
|
|
315
|
-
}),
|
|
316
|
-
};
|
|
317
|
-
},
|
|
318
|
-
},
|
|
319
|
-
});
|
|
320
|
-
|
|
321
|
-
expect(result.schema).toMatchObject({
|
|
322
|
-
properties: {
|
|
323
|
-
has_pet: {
|
|
324
|
-
oneOf: [
|
|
325
|
-
{
|
|
326
|
-
title: 'Yes, I have',
|
|
327
|
-
const: 'yes',
|
|
328
|
-
},
|
|
329
|
-
{
|
|
330
|
-
title: 'No',
|
|
331
|
-
const: 'no',
|
|
332
|
-
},
|
|
333
|
-
],
|
|
334
|
-
},
|
|
335
|
-
},
|
|
336
|
-
});
|
|
337
|
-
});
|
|
338
|
-
|
|
339
|
-
it('replace field attrs that are arrays (full)', () => {
|
|
340
|
-
const result = modify(schemaPet, {
|
|
341
|
-
fields: {
|
|
342
|
-
has_pet: {
|
|
343
|
-
oneOf: [{ const: 'yaaas', title: 'YAAS!' }],
|
|
344
|
-
},
|
|
345
|
-
},
|
|
346
|
-
});
|
|
347
|
-
|
|
348
|
-
expect(result.schema).toMatchObject({
|
|
349
|
-
properties: {
|
|
350
|
-
has_pet: {
|
|
351
|
-
oneOf: [
|
|
352
|
-
{
|
|
353
|
-
const: 'yaaas',
|
|
354
|
-
title: 'YAAS!',
|
|
355
|
-
},
|
|
356
|
-
],
|
|
357
|
-
},
|
|
358
|
-
},
|
|
359
|
-
});
|
|
360
|
-
});
|
|
361
|
-
});
|
|
362
|
-
|
|
363
|
-
describe('supporting shorthands', () => {
|
|
364
|
-
const invoiceSchema = {
|
|
365
|
-
properties: {
|
|
366
|
-
title: {
|
|
367
|
-
title: 'Invoice title',
|
|
368
|
-
'x-jsf-presentation': {
|
|
369
|
-
inputType: 'text',
|
|
370
|
-
},
|
|
371
|
-
'x-jsf-errorMessage': {
|
|
372
|
-
required: 'Cannot be empty.',
|
|
373
|
-
},
|
|
374
|
-
type: 'string',
|
|
375
|
-
},
|
|
376
|
-
total: {
|
|
377
|
-
title: 'Invoice amount',
|
|
378
|
-
'x-jsf-presentation': {
|
|
379
|
-
inputType: 'money',
|
|
380
|
-
},
|
|
381
|
-
type: 'number',
|
|
382
|
-
},
|
|
383
|
-
taxes: {
|
|
384
|
-
title: 'Taxes details',
|
|
385
|
-
properties: {
|
|
386
|
-
country: {
|
|
387
|
-
title: 'Country',
|
|
388
|
-
'x-jsf-presentation': {
|
|
389
|
-
inputType: 'country',
|
|
390
|
-
},
|
|
391
|
-
type: 'string',
|
|
392
|
-
},
|
|
393
|
-
percentage: {
|
|
394
|
-
title: 'Percentage',
|
|
395
|
-
'x-jsf-presentation': {
|
|
396
|
-
inputType: 'number',
|
|
397
|
-
},
|
|
398
|
-
},
|
|
399
|
-
type: 'integer',
|
|
400
|
-
},
|
|
401
|
-
},
|
|
402
|
-
},
|
|
403
|
-
required: ['title'],
|
|
404
|
-
};
|
|
405
|
-
|
|
406
|
-
it('basic support for x-jsf-presentation and x-jsf-errorMessage in config.fields', () => {
|
|
407
|
-
const result = modify(invoiceSchema, {
|
|
408
|
-
fields: {
|
|
409
|
-
title: {
|
|
410
|
-
errorMessage: {
|
|
411
|
-
maxLength: 'Must be shorter.',
|
|
412
|
-
},
|
|
413
|
-
presentation: {
|
|
414
|
-
dataFoo: 123,
|
|
415
|
-
},
|
|
416
|
-
},
|
|
417
|
-
'taxes.country': {
|
|
418
|
-
errorMessage: {
|
|
419
|
-
required: 'The country is required.',
|
|
420
|
-
},
|
|
421
|
-
presentation: {
|
|
422
|
-
flags: true,
|
|
423
|
-
},
|
|
424
|
-
},
|
|
425
|
-
},
|
|
426
|
-
});
|
|
427
|
-
|
|
428
|
-
// Assert all the other propreties are kept
|
|
429
|
-
expect(result.schema).toMatchObject(invoiceSchema);
|
|
430
|
-
|
|
431
|
-
expect(result.schema).toMatchObject({
|
|
432
|
-
properties: {
|
|
433
|
-
title: {
|
|
434
|
-
'x-jsf-errorMessage': {
|
|
435
|
-
maxLength: 'Must be shorter.',
|
|
436
|
-
},
|
|
437
|
-
'x-jsf-presentation': {
|
|
438
|
-
dataFoo: 123,
|
|
439
|
-
},
|
|
440
|
-
},
|
|
441
|
-
taxes: {
|
|
442
|
-
properties: {
|
|
443
|
-
country: {
|
|
444
|
-
'x-jsf-errorMessage': {
|
|
445
|
-
required: 'The country is required.',
|
|
446
|
-
},
|
|
447
|
-
'x-jsf-presentation': {
|
|
448
|
-
flags: true,
|
|
449
|
-
},
|
|
450
|
-
},
|
|
451
|
-
},
|
|
452
|
-
},
|
|
453
|
-
},
|
|
454
|
-
});
|
|
455
|
-
});
|
|
456
|
-
|
|
457
|
-
it('shorthands work in config.allFields', () => {
|
|
458
|
-
const result = modify(invoiceSchema, {
|
|
459
|
-
allFields: (fieldName, attrs) => ({
|
|
460
|
-
errorMessage: {
|
|
461
|
-
required: `${attrs.title} cannot be empty.`,
|
|
462
|
-
},
|
|
463
|
-
presentation: {
|
|
464
|
-
dataName: fieldName,
|
|
465
|
-
},
|
|
466
|
-
}),
|
|
467
|
-
});
|
|
468
|
-
|
|
469
|
-
expect(result.schema).toMatchObject({
|
|
470
|
-
properties: {
|
|
471
|
-
title: {
|
|
472
|
-
'x-jsf-errorMessage': {
|
|
473
|
-
required: 'Invoice title cannot be empty.',
|
|
474
|
-
},
|
|
475
|
-
'x-jsf-presentation': {
|
|
476
|
-
dataName: 'title',
|
|
477
|
-
},
|
|
478
|
-
},
|
|
479
|
-
total: {
|
|
480
|
-
'x-jsf-errorMessage': {
|
|
481
|
-
required: 'Invoice amount cannot be empty.',
|
|
482
|
-
},
|
|
483
|
-
'x-jsf-presentation': {
|
|
484
|
-
dataName: 'total',
|
|
485
|
-
},
|
|
486
|
-
},
|
|
487
|
-
taxes: {
|
|
488
|
-
'x-jsf-errorMessage': {
|
|
489
|
-
required: 'Taxes details cannot be empty.',
|
|
490
|
-
},
|
|
491
|
-
'x-jsf-presentation': {
|
|
492
|
-
dataName: 'taxes',
|
|
493
|
-
},
|
|
494
|
-
properties: {
|
|
495
|
-
country: {
|
|
496
|
-
'x-jsf-errorMessage': {
|
|
497
|
-
required: 'Country cannot be empty.',
|
|
498
|
-
},
|
|
499
|
-
'x-jsf-presentation': {
|
|
500
|
-
dataName: 'taxes.country',
|
|
501
|
-
},
|
|
502
|
-
},
|
|
503
|
-
percentage: {
|
|
504
|
-
'x-jsf-errorMessage': {
|
|
505
|
-
required: 'Percentage cannot be empty.',
|
|
506
|
-
},
|
|
507
|
-
'x-jsf-presentation': {
|
|
508
|
-
dataName: 'taxes.percentage',
|
|
509
|
-
},
|
|
510
|
-
},
|
|
511
|
-
},
|
|
512
|
-
},
|
|
513
|
-
},
|
|
514
|
-
});
|
|
515
|
-
});
|
|
516
|
-
|
|
517
|
-
it('shorthands work in config.create', () => {
|
|
518
|
-
const result = modify(invoiceSchema, {
|
|
519
|
-
create: {
|
|
520
|
-
invoice_id: {
|
|
521
|
-
title: 'Invoice ID',
|
|
522
|
-
presentation: {
|
|
523
|
-
inputType: 'text',
|
|
524
|
-
},
|
|
525
|
-
errorMessage: {
|
|
526
|
-
pattern: 'Must have 5 characters.',
|
|
527
|
-
},
|
|
528
|
-
},
|
|
529
|
-
},
|
|
530
|
-
});
|
|
531
|
-
|
|
532
|
-
expect(result.schema).toMatchObject({
|
|
533
|
-
properties: {
|
|
534
|
-
invoice_id: {
|
|
535
|
-
title: 'Invoice ID',
|
|
536
|
-
'x-jsf-presentation': {
|
|
537
|
-
inputType: 'text',
|
|
538
|
-
},
|
|
539
|
-
'x-jsf-errorMessage': {
|
|
540
|
-
pattern: 'Must have 5 characters.',
|
|
541
|
-
},
|
|
542
|
-
},
|
|
543
|
-
},
|
|
544
|
-
});
|
|
545
|
-
});
|
|
546
|
-
});
|
|
547
|
-
|
|
548
|
-
const schemaTickets = {
|
|
549
|
-
properties: {
|
|
550
|
-
age: {
|
|
551
|
-
title: 'Age',
|
|
552
|
-
type: 'integer',
|
|
553
|
-
},
|
|
554
|
-
quantity: {
|
|
555
|
-
title: 'Quantity',
|
|
556
|
-
type: 'integer',
|
|
557
|
-
},
|
|
558
|
-
has_premium: {
|
|
559
|
-
title: 'Has premium',
|
|
560
|
-
type: 'string',
|
|
561
|
-
},
|
|
562
|
-
premium_id: {
|
|
563
|
-
title: 'Premium ID',
|
|
564
|
-
type: 'boolean',
|
|
565
|
-
},
|
|
566
|
-
reason: {
|
|
567
|
-
title: 'Why not premium?',
|
|
568
|
-
type: 'string',
|
|
569
|
-
},
|
|
570
|
-
},
|
|
571
|
-
'x-jsf-order': ['age', 'quantity', 'has_premium', 'premium_id', 'reason'],
|
|
572
|
-
allOf: [
|
|
573
|
-
{
|
|
574
|
-
// Empty conditional to sanity test empty cases
|
|
575
|
-
if: {},
|
|
576
|
-
then: {},
|
|
577
|
-
else: {},
|
|
578
|
-
},
|
|
579
|
-
// Create two conditionals to test both get matched
|
|
580
|
-
{
|
|
581
|
-
if: {
|
|
582
|
-
has_premium: {
|
|
583
|
-
const: 'yes',
|
|
584
|
-
},
|
|
585
|
-
required: ['has_premium'],
|
|
586
|
-
},
|
|
587
|
-
then: {
|
|
588
|
-
required: ['premium_id'],
|
|
589
|
-
},
|
|
590
|
-
else: {},
|
|
591
|
-
},
|
|
592
|
-
{
|
|
593
|
-
if: {
|
|
594
|
-
properties: {
|
|
595
|
-
has_premium: {
|
|
596
|
-
const: 'no',
|
|
597
|
-
},
|
|
598
|
-
},
|
|
599
|
-
required: ['has_premium'],
|
|
600
|
-
},
|
|
601
|
-
then: {
|
|
602
|
-
properties: {
|
|
603
|
-
reason: false,
|
|
604
|
-
},
|
|
605
|
-
},
|
|
606
|
-
else: {},
|
|
607
|
-
},
|
|
608
|
-
],
|
|
609
|
-
};
|
|
610
|
-
|
|
611
|
-
describe('modify() - reorder fields', () => {
|
|
612
|
-
it('reorder fields - basic usage', () => {
|
|
613
|
-
const baseExample = {
|
|
614
|
-
properties: {
|
|
615
|
-
/* does not matter */
|
|
616
|
-
},
|
|
617
|
-
'x-jsf-order': ['field_a', 'field_b', 'field_c', 'field_d'],
|
|
618
|
-
};
|
|
619
|
-
const result = modify(baseExample, {
|
|
620
|
-
orderRoot: ['field_c', 'field_b'],
|
|
621
|
-
});
|
|
622
|
-
|
|
623
|
-
// 💡 Note how the missing field (field_d) was added to the end as safety measure.
|
|
624
|
-
expect(result.schema).toMatchObject({
|
|
625
|
-
'x-jsf-order': ['field_c', 'field_b', 'field_a', 'field_d'],
|
|
626
|
-
});
|
|
627
|
-
|
|
628
|
-
expect(result.warnings).toMatchObject([
|
|
629
|
-
{
|
|
630
|
-
type: 'ORDER_MISSING_FIELDS',
|
|
631
|
-
message:
|
|
632
|
-
'Some fields got forgotten in the new order. They were automatically appended: field_a, field_d',
|
|
633
|
-
},
|
|
634
|
-
]);
|
|
635
|
-
});
|
|
636
|
-
|
|
637
|
-
it('reorder fields - basic usage fallback', () => {
|
|
638
|
-
const baseExample = {
|
|
639
|
-
properties: {
|
|
640
|
-
/* does not matter */
|
|
641
|
-
},
|
|
642
|
-
};
|
|
643
|
-
const result = modify(baseExample, {
|
|
644
|
-
orderRoot: ['field_c', 'field_b'],
|
|
645
|
-
});
|
|
646
|
-
|
|
647
|
-
// Does not explode if it doesn't have an original order.
|
|
648
|
-
expect(result.schema).toMatchObject({
|
|
649
|
-
'x-jsf-order': ['field_c', 'field_b'],
|
|
650
|
-
});
|
|
651
|
-
|
|
652
|
-
expect(result.warnings).toEqual([]);
|
|
653
|
-
});
|
|
654
|
-
|
|
655
|
-
it('reorder fields - as callback based on original order', () => {
|
|
656
|
-
const baseExample = {
|
|
657
|
-
properties: {
|
|
658
|
-
/* does not matter */
|
|
659
|
-
},
|
|
660
|
-
'x-jsf-order': ['field_a', 'field_b', 'field_c', 'field_d'],
|
|
661
|
-
};
|
|
662
|
-
const result = modify(baseExample, {
|
|
663
|
-
orderRoot: (original) => original.reverse(),
|
|
664
|
-
});
|
|
665
|
-
|
|
666
|
-
expect(result.schema).toMatchObject({
|
|
667
|
-
'x-jsf-order': ['field_d', 'field_c', 'field_b', 'field_a'],
|
|
668
|
-
});
|
|
669
|
-
});
|
|
670
|
-
|
|
671
|
-
it('reorder fields in fieldsets (through config.fields)', () => {
|
|
672
|
-
// NOTE: A better API is needed but we decided to not implement it yet
|
|
673
|
-
// as we didn't agreed on the best DX. Check PR #78 for proposed APIs.
|
|
674
|
-
// Until then this is the workaround.
|
|
675
|
-
// Note the warning "ORDER_MISSING_FIELDS" won't be added.
|
|
676
|
-
|
|
677
|
-
const baseExample = {
|
|
678
|
-
properties: {
|
|
679
|
-
address: {
|
|
680
|
-
properties: {
|
|
681
|
-
/* does not matter */
|
|
682
|
-
},
|
|
683
|
-
'x-jsf-order': ['first_line', 'zipcode', 'city'],
|
|
684
|
-
},
|
|
685
|
-
age: {
|
|
686
|
-
/* ... */
|
|
687
|
-
},
|
|
688
|
-
},
|
|
689
|
-
'x-jsf-order': ['address', 'age'],
|
|
690
|
-
};
|
|
691
|
-
|
|
692
|
-
const result = modify(baseExample, {
|
|
693
|
-
fields: {
|
|
694
|
-
address: (attrs) => {
|
|
695
|
-
// eslint-disable-next-line no-unused-vars
|
|
696
|
-
const [_firstLine, ...restOrder] = attrs['x-jsf-order'];
|
|
697
|
-
return { 'x-jsf-order': restOrder.reverse() }; // ['city', 'zipcode']
|
|
698
|
-
},
|
|
699
|
-
},
|
|
700
|
-
});
|
|
701
|
-
|
|
702
|
-
expect(result.schema).toMatchObject({
|
|
703
|
-
properties: {
|
|
704
|
-
address: {
|
|
705
|
-
// Note how first_line was NOT appended
|
|
706
|
-
'x-jsf-order': ['city', 'zipcode'],
|
|
707
|
-
},
|
|
708
|
-
},
|
|
709
|
-
});
|
|
710
|
-
});
|
|
711
|
-
});
|
|
712
|
-
|
|
713
|
-
describe('modify() - create fields', () => {
|
|
714
|
-
it('create base field', () => {
|
|
715
|
-
const result = modify(schemaAddress, {
|
|
716
|
-
create: {
|
|
717
|
-
new_field: {
|
|
718
|
-
title: 'New field',
|
|
719
|
-
type: 'string',
|
|
720
|
-
},
|
|
721
|
-
address: {
|
|
722
|
-
someAttr: 'foo',
|
|
723
|
-
},
|
|
724
|
-
},
|
|
725
|
-
});
|
|
726
|
-
|
|
727
|
-
expect(result.schema).toMatchObject({
|
|
728
|
-
properties: {
|
|
729
|
-
new_field: {
|
|
730
|
-
title: 'New field',
|
|
731
|
-
type: 'string',
|
|
732
|
-
},
|
|
733
|
-
address: schemaAddress.properties.address,
|
|
734
|
-
},
|
|
735
|
-
});
|
|
736
|
-
|
|
737
|
-
// this is ignored because the field already exists
|
|
738
|
-
expect(result.schema.properties.address.someAttr).toBe(undefined);
|
|
739
|
-
|
|
740
|
-
expect(result.warnings).toEqual([
|
|
741
|
-
{
|
|
742
|
-
type: 'FIELD_TO_CREATE_EXISTS',
|
|
743
|
-
message: 'Creating field "address" was ignored because it already exists.',
|
|
744
|
-
},
|
|
745
|
-
]);
|
|
746
|
-
});
|
|
747
|
-
|
|
748
|
-
it('create nested field', () => {
|
|
749
|
-
const result = modify(schemaAddress, {
|
|
750
|
-
create: {
|
|
751
|
-
// Pointer as string
|
|
752
|
-
'address.state': {
|
|
753
|
-
title: 'State',
|
|
754
|
-
},
|
|
755
|
-
// Pointer as object
|
|
756
|
-
address: {
|
|
757
|
-
someAttr: 'foo',
|
|
758
|
-
properties: {
|
|
759
|
-
district: {
|
|
760
|
-
title: 'District',
|
|
761
|
-
},
|
|
762
|
-
},
|
|
763
|
-
},
|
|
764
|
-
// Ignore field street because the field already exists [1]
|
|
765
|
-
'address.street': {
|
|
766
|
-
title: 'Foo',
|
|
767
|
-
},
|
|
768
|
-
},
|
|
769
|
-
});
|
|
770
|
-
|
|
771
|
-
expect(result.schema.properties.address.properties).toMatchObject({
|
|
772
|
-
...schemaAddress.properties.address.properties,
|
|
773
|
-
state: {
|
|
774
|
-
title: 'State',
|
|
775
|
-
},
|
|
776
|
-
district: {
|
|
777
|
-
title: 'District',
|
|
778
|
-
},
|
|
779
|
-
});
|
|
780
|
-
|
|
781
|
-
// Ignore address.someAttr because the address itself already exists.
|
|
782
|
-
expect(result.schema.properties.address.someAttr).toBeUndefined();
|
|
783
|
-
|
|
784
|
-
// Ignore field street because it already exists [1]
|
|
785
|
-
expect(result.schema.properties.address.properties.street.title).toBe('Street');
|
|
786
|
-
|
|
787
|
-
expect(result.warnings).toEqual([
|
|
788
|
-
{
|
|
789
|
-
type: 'FIELD_TO_CREATE_EXISTS',
|
|
790
|
-
message: 'Creating field "address" was ignored because it already exists.',
|
|
791
|
-
},
|
|
792
|
-
{
|
|
793
|
-
type: 'FIELD_TO_CREATE_EXISTS',
|
|
794
|
-
message: 'Creating field "address.street" was ignored because it already exists.',
|
|
795
|
-
},
|
|
796
|
-
]);
|
|
797
|
-
});
|
|
798
|
-
});
|
|
799
|
-
|
|
800
|
-
describe('modify() - pick fields', () => {
|
|
801
|
-
it('basic usage', () => {
|
|
802
|
-
const { schema, warnings } = modify(schemaTickets, {
|
|
803
|
-
pick: ['quantity'],
|
|
804
|
-
});
|
|
805
|
-
|
|
806
|
-
// Note how the other fields got removed from
|
|
807
|
-
// from the root properties, the "order" and "allOf".
|
|
808
|
-
expect(schema.properties).toEqual({
|
|
809
|
-
quantity: {
|
|
810
|
-
title: 'Quantity',
|
|
811
|
-
type: 'integer',
|
|
812
|
-
},
|
|
813
|
-
});
|
|
814
|
-
expect(schema.properties.age).toBeUndefined();
|
|
815
|
-
expect(schema.properties.has_premium).toBeUndefined();
|
|
816
|
-
expect(schema.properties.premium_id).toBeUndefined();
|
|
817
|
-
|
|
818
|
-
expect(schema['x-jsf-order']).toEqual(['quantity']);
|
|
819
|
-
expect(schema.allOf).toEqual([]); // conditional got removed.
|
|
820
|
-
|
|
821
|
-
expect(warnings).toHaveLength(0);
|
|
822
|
-
});
|
|
823
|
-
|
|
824
|
-
it('basic usage without conditionals', () => {
|
|
825
|
-
const schemaMinimal = {
|
|
826
|
-
properties: {
|
|
827
|
-
age: {
|
|
828
|
-
title: 'Age',
|
|
829
|
-
type: 'integer',
|
|
830
|
-
},
|
|
831
|
-
quantity: {
|
|
832
|
-
title: 'Quantity',
|
|
833
|
-
type: 'integer',
|
|
834
|
-
},
|
|
835
|
-
},
|
|
836
|
-
'x-jsf-order': ['age', 'quantity'],
|
|
837
|
-
};
|
|
838
|
-
const { schema, warnings } = modify(schemaMinimal, {
|
|
839
|
-
pick: ['quantity'],
|
|
840
|
-
});
|
|
841
|
-
|
|
842
|
-
expect(schema.properties.quantity).toBeDefined();
|
|
843
|
-
expect(schema.properties.age).toBeUndefined();
|
|
844
|
-
// `allOf` conditionals were not defined, and continue to be so.
|
|
845
|
-
// This test guards against a regression where lack of `allOf` caused a TypeError.
|
|
846
|
-
expect(schema.allOf).toBeUndefined();
|
|
847
|
-
expect(warnings).toHaveLength(0);
|
|
848
|
-
});
|
|
849
|
-
|
|
850
|
-
it('related conditionals are kept - (else)', () => {
|
|
851
|
-
const { schema, warnings } = modify(schemaTickets, {
|
|
852
|
-
pick: ['has_premium'],
|
|
853
|
-
});
|
|
854
|
-
|
|
855
|
-
expect(schema).toMatchObject({
|
|
856
|
-
properties: {
|
|
857
|
-
has_premium: {
|
|
858
|
-
title: 'Has premium',
|
|
859
|
-
},
|
|
860
|
-
premium_id: {
|
|
861
|
-
title: 'Premium ID',
|
|
862
|
-
},
|
|
863
|
-
reason: {
|
|
864
|
-
title: 'Why not premium?',
|
|
865
|
-
},
|
|
866
|
-
},
|
|
867
|
-
allOf: [schemaTickets.allOf[1], schemaTickets.allOf[2]],
|
|
868
|
-
});
|
|
869
|
-
|
|
870
|
-
expect(schema.properties.quantity).toBeUndefined();
|
|
871
|
-
expect(schema.properties.age).toBeUndefined();
|
|
872
|
-
expect(warnings).toEqual([
|
|
873
|
-
{
|
|
874
|
-
type: 'PICK_MISSED_FIELD',
|
|
875
|
-
message:
|
|
876
|
-
'The picked fields are in conditionals that refeer other fields. They added automatically: "premium_id", "reason". Check "meta" for more details.',
|
|
877
|
-
meta: { premium_id: { path: 'allOf[1].then' }, reason: { path: 'allOf[2].then' } },
|
|
878
|
-
},
|
|
879
|
-
]);
|
|
880
|
-
});
|
|
881
|
-
|
|
882
|
-
it('related conditionals are kept - (if)', () => {
|
|
883
|
-
const { schema, warnings } = modify(schemaTickets, {
|
|
884
|
-
pick: ['premium_id'],
|
|
885
|
-
});
|
|
886
|
-
|
|
887
|
-
expect(schema).toMatchObject({
|
|
888
|
-
properties: {
|
|
889
|
-
has_premium: {
|
|
890
|
-
title: 'Has premium',
|
|
891
|
-
},
|
|
892
|
-
premium_id: {
|
|
893
|
-
title: 'Premium ID',
|
|
894
|
-
},
|
|
895
|
-
},
|
|
896
|
-
allOf: [schemaTickets.allOf[0]],
|
|
897
|
-
});
|
|
898
|
-
|
|
899
|
-
expect(schema.properties.quantity).toBeUndefined();
|
|
900
|
-
expect(schema.properties.age).toBeUndefined();
|
|
901
|
-
expect(warnings).toEqual([
|
|
902
|
-
{
|
|
903
|
-
type: 'PICK_MISSED_FIELD',
|
|
904
|
-
message:
|
|
905
|
-
'The picked fields are in conditionals that refeer other fields. They added automatically: "has_premium". Check "meta" for more details.',
|
|
906
|
-
meta: { has_premium: { path: 'allOf[1].if' } },
|
|
907
|
-
},
|
|
908
|
-
]);
|
|
909
|
-
});
|
|
910
|
-
|
|
911
|
-
it('reorder only handles the picked fields', () => {
|
|
912
|
-
const { schema, warnings } = modify(schemaTickets, {
|
|
913
|
-
pick: ['age', 'quantity'],
|
|
914
|
-
orderRoot: (original) => original.reverse(),
|
|
915
|
-
});
|
|
916
|
-
|
|
917
|
-
// The order only includes those 2 fields
|
|
918
|
-
expect(schema['x-jsf-order']).toEqual(['quantity', 'age']);
|
|
919
|
-
// There are no warnings about forgotten fields.
|
|
920
|
-
expect(warnings).toHaveLength(0);
|
|
921
|
-
|
|
922
|
-
// Sanity check the result
|
|
923
|
-
expect(schema.properties.quantity).toBeDefined();
|
|
924
|
-
expect(schema.properties.age).toBeDefined();
|
|
925
|
-
expect(schema.properties.has_premium).toBeUndefined();
|
|
926
|
-
expect(schema.properties.premium_id).toBeUndefined();
|
|
927
|
-
expect(schema.allOf).toEqual([]);
|
|
928
|
-
});
|
|
929
|
-
|
|
930
|
-
// For later on when needed.
|
|
931
|
-
it.todo('ignore conditionals with unpicked fields');
|
|
932
|
-
|
|
933
|
-
it.todo('pick nested fields (fieldsets)');
|
|
934
|
-
/* Use cases:
|
|
935
|
-
- conditionals inside fieldstes. eg properties.family.allOf[0].if...
|
|
936
|
-
- conditional in the root pointing to nested fields: eg if properties.family.properties.simblings is 0 then hide properties.playTogether ...
|
|
937
|
-
- variations of each one of these similar to the existing tests.
|
|
938
|
-
*/
|
|
939
|
-
});
|