@remoteoss/json-schema-form 0.7.2-beta.0 → 0.7.3-beta.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -1,3 +1,9 @@
1
+ #### 0.7.3-beta.0 (2023-11-07)
2
+
3
+ ##### Bug Fixes
4
+
5
+ * Remove conditional attributes after the condition is unmatched ([#57](https://github.com/remoteoss/json-schema-form/pull/57)) ([8bac7145](https://github.com/remoteoss/json-schema-form/commit/8bac7145dfdd4136c0613044389666a614eb12f7))
6
+
1
7
  #### 0.7.2-beta.0 (2023-11-06)
2
8
 
3
9
  ##### Bug Fixes
package/dist/index.cjs CHANGED
@@ -1,8 +1,8 @@
1
1
 
2
2
  /*!
3
3
  Copyright (c) 2023 Remote Technology, Inc.
4
- NPM Package: @remoteoss/json-schema-form@0.7.2-beta.0
5
- Generated: Mon, 06 Nov 2023 19:11:58 GMT
4
+ NPM Package: @remoteoss/json-schema-form@0.7.3-beta.0
5
+ Generated: Tue, 07 Nov 2023 09:29:14 GMT
6
6
 
7
7
  MIT License
8
8
 
@@ -1061,6 +1061,33 @@ function processJSONLogicNode({
1061
1061
  }
1062
1062
 
1063
1063
  // src/helpers.js
1064
+ var dynamicInternalJsfAttrs = [
1065
+ "isVisible",
1066
+ // Driven from conditionals state
1067
+ "fields",
1068
+ // driven from group-array
1069
+ "getComputedAttributes",
1070
+ // From json-logic
1071
+ "computedAttributes",
1072
+ // From json-logic
1073
+ "calculateConditionalProperties",
1074
+ // driven from conditionals
1075
+ "calculateCustomValidationProperties",
1076
+ // To be deprecated in favor of json-logic
1077
+ "scopedJsonSchema"
1078
+ // The respective JSON Schema
1079
+ ];
1080
+ var dynamicInternalJsfAttrsObj = Object.fromEntries(
1081
+ dynamicInternalJsfAttrs.map((k) => [k, true])
1082
+ );
1083
+ function removeConditionalStaleAttributes(field, conditionalAttrs, rootAttrs) {
1084
+ Object.keys(field).forEach((key) => {
1085
+ if (conditionalAttrs[key] === void 0 && rootAttrs[key] === void 0 && // Don't remove attrs that were declared in the root field.
1086
+ dynamicInternalJsfAttrsObj[key] === void 0) {
1087
+ field[key] = void 0;
1088
+ }
1089
+ });
1090
+ }
1064
1091
  function hasType(type, typeName) {
1065
1092
  return Array.isArray(type) ? type.includes(typeName) : type === typeName;
1066
1093
  }
@@ -1153,19 +1180,21 @@ function updateField(field, requiredFields, node, formValues, logic, config) {
1153
1180
  if (fieldIsRequired) {
1154
1181
  field.isVisible = true;
1155
1182
  }
1156
- const updateValues = (fieldValues) => Object.entries(fieldValues).forEach(([key, value]) => {
1157
- field[key] = typeof value === "function" ? value() : value;
1158
- if (key === "value") {
1159
- const readOnlyPropertyWasUpdated = typeof fieldValues.readOnly !== "undefined";
1160
- const isReadonlyByDefault = field.readOnly;
1161
- const isReadonly = readOnlyPropertyWasUpdated ? fieldValues.readOnly : isReadonlyByDefault;
1162
- if (!isReadonly && (value === null || field.inputType === "checkbox")) {
1163
- field.value = void 0;
1183
+ const updateAttributes = (fieldAttrs) => {
1184
+ Object.entries(fieldAttrs).forEach(([key, value]) => {
1185
+ field[key] = typeof value === "function" ? value() : value;
1186
+ if (key === "value") {
1187
+ const readOnlyPropertyWasUpdated = typeof fieldAttrs.readOnly !== "undefined";
1188
+ const isReadonlyByDefault = field.readOnly;
1189
+ const isReadonly = readOnlyPropertyWasUpdated ? fieldAttrs.readOnly : isReadonlyByDefault;
1190
+ if (!isReadonly && (value === null || field.inputType === "checkbox")) {
1191
+ field.value = void 0;
1192
+ }
1164
1193
  }
1165
- }
1166
- });
1194
+ });
1195
+ };
1167
1196
  if (field.getComputedAttributes) {
1168
- const computedFieldValues = field.getComputedAttributes({
1197
+ const newAttributes = field.getComputedAttributes({
1169
1198
  field,
1170
1199
  isRequired: fieldIsRequired,
1171
1200
  node,
@@ -1173,23 +1202,24 @@ function updateField(field, requiredFields, node, formValues, logic, config) {
1173
1202
  config,
1174
1203
  logic
1175
1204
  });
1176
- updateValues(computedFieldValues);
1205
+ updateAttributes(newAttributes);
1177
1206
  }
1178
1207
  if (field.calculateConditionalProperties) {
1179
- const newFieldValues = field.calculateConditionalProperties({
1208
+ const { rootFieldAttrs, newAttributes } = field.calculateConditionalProperties({
1180
1209
  isRequired: fieldIsRequired,
1181
1210
  conditionBranch: node,
1182
1211
  formValues
1183
1212
  });
1184
- updateValues(newFieldValues);
1213
+ updateAttributes(newAttributes);
1214
+ removeConditionalStaleAttributes(field, newAttributes, rootFieldAttrs);
1185
1215
  }
1186
1216
  if (field.calculateCustomValidationProperties) {
1187
- const newFieldValues = field.calculateCustomValidationProperties(
1217
+ const newAttributes = field.calculateCustomValidationProperties(
1188
1218
  fieldIsRequired,
1189
1219
  node,
1190
1220
  formValues
1191
1221
  );
1192
- updateValues(newFieldValues);
1222
+ updateAttributes(newAttributes);
1193
1223
  }
1194
1224
  }
1195
1225
  function processNode({
@@ -1531,17 +1561,23 @@ function calculateConditionalProperties({ fieldParams, customProperties, logic,
1531
1561
  logic
1532
1562
  )
1533
1563
  };
1534
- return (0, import_omit2.default)((0, import_merge2.default)(base, presentation, newFieldParams), ["inputType"]);
1564
+ return {
1565
+ rootFieldAttrs: fieldParams,
1566
+ newAttributes: (0, import_omit2.default)((0, import_merge2.default)(base, presentation, newFieldParams), ["inputType"])
1567
+ };
1535
1568
  }
1536
1569
  const isVisible = isRequired;
1537
1570
  return {
1538
- isVisible,
1539
- required: isRequired,
1540
- schema: buildYupSchema({
1541
- ...fieldParams,
1542
- ...extractParametersFromNode(conditionBranch),
1543
- required: isRequired
1544
- })
1571
+ rootFieldAttrs: fieldParams,
1572
+ newAttributes: {
1573
+ isVisible,
1574
+ required: isRequired,
1575
+ schema: buildYupSchema({
1576
+ ...fieldParams,
1577
+ ...extractParametersFromNode(conditionBranch),
1578
+ required: isRequired
1579
+ })
1580
+ }
1545
1581
  };
1546
1582
  };
1547
1583
  }
package/dist/index.js CHANGED
@@ -1,8 +1,8 @@
1
1
 
2
2
  /*!
3
3
  Copyright (c) 2023 Remote Technology, Inc.
4
- NPM Package: @remoteoss/json-schema-form@0.7.2-beta.0
5
- Generated: Mon, 06 Nov 2023 19:11:58 GMT
4
+ NPM Package: @remoteoss/json-schema-form@0.7.3-beta.0
5
+ Generated: Tue, 07 Nov 2023 09:29:14 GMT
6
6
 
7
7
  MIT License
8
8
 
@@ -1025,6 +1025,33 @@ function processJSONLogicNode({
1025
1025
  }
1026
1026
 
1027
1027
  // src/helpers.js
1028
+ var dynamicInternalJsfAttrs = [
1029
+ "isVisible",
1030
+ // Driven from conditionals state
1031
+ "fields",
1032
+ // driven from group-array
1033
+ "getComputedAttributes",
1034
+ // From json-logic
1035
+ "computedAttributes",
1036
+ // From json-logic
1037
+ "calculateConditionalProperties",
1038
+ // driven from conditionals
1039
+ "calculateCustomValidationProperties",
1040
+ // To be deprecated in favor of json-logic
1041
+ "scopedJsonSchema"
1042
+ // The respective JSON Schema
1043
+ ];
1044
+ var dynamicInternalJsfAttrsObj = Object.fromEntries(
1045
+ dynamicInternalJsfAttrs.map((k) => [k, true])
1046
+ );
1047
+ function removeConditionalStaleAttributes(field, conditionalAttrs, rootAttrs) {
1048
+ Object.keys(field).forEach((key) => {
1049
+ if (conditionalAttrs[key] === void 0 && rootAttrs[key] === void 0 && // Don't remove attrs that were declared in the root field.
1050
+ dynamicInternalJsfAttrsObj[key] === void 0) {
1051
+ field[key] = void 0;
1052
+ }
1053
+ });
1054
+ }
1028
1055
  function hasType(type, typeName) {
1029
1056
  return Array.isArray(type) ? type.includes(typeName) : type === typeName;
1030
1057
  }
@@ -1117,19 +1144,21 @@ function updateField(field, requiredFields, node, formValues, logic, config) {
1117
1144
  if (fieldIsRequired) {
1118
1145
  field.isVisible = true;
1119
1146
  }
1120
- const updateValues = (fieldValues) => Object.entries(fieldValues).forEach(([key, value]) => {
1121
- field[key] = typeof value === "function" ? value() : value;
1122
- if (key === "value") {
1123
- const readOnlyPropertyWasUpdated = typeof fieldValues.readOnly !== "undefined";
1124
- const isReadonlyByDefault = field.readOnly;
1125
- const isReadonly = readOnlyPropertyWasUpdated ? fieldValues.readOnly : isReadonlyByDefault;
1126
- if (!isReadonly && (value === null || field.inputType === "checkbox")) {
1127
- field.value = void 0;
1147
+ const updateAttributes = (fieldAttrs) => {
1148
+ Object.entries(fieldAttrs).forEach(([key, value]) => {
1149
+ field[key] = typeof value === "function" ? value() : value;
1150
+ if (key === "value") {
1151
+ const readOnlyPropertyWasUpdated = typeof fieldAttrs.readOnly !== "undefined";
1152
+ const isReadonlyByDefault = field.readOnly;
1153
+ const isReadonly = readOnlyPropertyWasUpdated ? fieldAttrs.readOnly : isReadonlyByDefault;
1154
+ if (!isReadonly && (value === null || field.inputType === "checkbox")) {
1155
+ field.value = void 0;
1156
+ }
1128
1157
  }
1129
- }
1130
- });
1158
+ });
1159
+ };
1131
1160
  if (field.getComputedAttributes) {
1132
- const computedFieldValues = field.getComputedAttributes({
1161
+ const newAttributes = field.getComputedAttributes({
1133
1162
  field,
1134
1163
  isRequired: fieldIsRequired,
1135
1164
  node,
@@ -1137,23 +1166,24 @@ function updateField(field, requiredFields, node, formValues, logic, config) {
1137
1166
  config,
1138
1167
  logic
1139
1168
  });
1140
- updateValues(computedFieldValues);
1169
+ updateAttributes(newAttributes);
1141
1170
  }
1142
1171
  if (field.calculateConditionalProperties) {
1143
- const newFieldValues = field.calculateConditionalProperties({
1172
+ const { rootFieldAttrs, newAttributes } = field.calculateConditionalProperties({
1144
1173
  isRequired: fieldIsRequired,
1145
1174
  conditionBranch: node,
1146
1175
  formValues
1147
1176
  });
1148
- updateValues(newFieldValues);
1177
+ updateAttributes(newAttributes);
1178
+ removeConditionalStaleAttributes(field, newAttributes, rootFieldAttrs);
1149
1179
  }
1150
1180
  if (field.calculateCustomValidationProperties) {
1151
- const newFieldValues = field.calculateCustomValidationProperties(
1181
+ const newAttributes = field.calculateCustomValidationProperties(
1152
1182
  fieldIsRequired,
1153
1183
  node,
1154
1184
  formValues
1155
1185
  );
1156
- updateValues(newFieldValues);
1186
+ updateAttributes(newAttributes);
1157
1187
  }
1158
1188
  }
1159
1189
  function processNode({
@@ -1495,17 +1525,23 @@ function calculateConditionalProperties({ fieldParams, customProperties, logic,
1495
1525
  logic
1496
1526
  )
1497
1527
  };
1498
- return omit2(merge2(base, presentation, newFieldParams), ["inputType"]);
1528
+ return {
1529
+ rootFieldAttrs: fieldParams,
1530
+ newAttributes: omit2(merge2(base, presentation, newFieldParams), ["inputType"])
1531
+ };
1499
1532
  }
1500
1533
  const isVisible = isRequired;
1501
1534
  return {
1502
- isVisible,
1503
- required: isRequired,
1504
- schema: buildYupSchema({
1505
- ...fieldParams,
1506
- ...extractParametersFromNode(conditionBranch),
1507
- required: isRequired
1508
- })
1535
+ rootFieldAttrs: fieldParams,
1536
+ newAttributes: {
1537
+ isVisible,
1538
+ required: isRequired,
1539
+ schema: buildYupSchema({
1540
+ ...fieldParams,
1541
+ ...extractParametersFromNode(conditionBranch),
1542
+ required: isRequired
1543
+ })
1544
+ }
1509
1545
  };
1510
1546
  };
1511
1547
  }
@@ -1,8 +1,8 @@
1
1
 
2
2
  /*!
3
3
  Copyright (c) 2023 Remote Technology, Inc.
4
- NPM Package: @remoteoss/json-schema-form@0.7.2-beta.0
5
- Generated: Mon, 06 Nov 2023 19:11:58 GMT
4
+ NPM Package: @remoteoss/json-schema-form@0.7.3-beta.0
5
+ Generated: Tue, 07 Nov 2023 09:29:14 GMT
6
6
 
7
7
  MIT License
8
8
 
@@ -12379,6 +12379,33 @@ function processJSONLogicNode({
12379
12379
  }
12380
12380
 
12381
12381
  // src/helpers.js
12382
+ var dynamicInternalJsfAttrs = [
12383
+ "isVisible",
12384
+ // Driven from conditionals state
12385
+ "fields",
12386
+ // driven from group-array
12387
+ "getComputedAttributes",
12388
+ // From json-logic
12389
+ "computedAttributes",
12390
+ // From json-logic
12391
+ "calculateConditionalProperties",
12392
+ // driven from conditionals
12393
+ "calculateCustomValidationProperties",
12394
+ // To be deprecated in favor of json-logic
12395
+ "scopedJsonSchema"
12396
+ // The respective JSON Schema
12397
+ ];
12398
+ var dynamicInternalJsfAttrsObj = Object.fromEntries(
12399
+ dynamicInternalJsfAttrs.map((k) => [k, true])
12400
+ );
12401
+ function removeConditionalStaleAttributes(field, conditionalAttrs, rootAttrs) {
12402
+ Object.keys(field).forEach((key) => {
12403
+ if (conditionalAttrs[key] === void 0 && rootAttrs[key] === void 0 && // Don't remove attrs that were declared in the root field.
12404
+ dynamicInternalJsfAttrsObj[key] === void 0) {
12405
+ field[key] = void 0;
12406
+ }
12407
+ });
12408
+ }
12382
12409
  function hasType(type, typeName) {
12383
12410
  return Array.isArray(type) ? type.includes(typeName) : type === typeName;
12384
12411
  }
@@ -12471,19 +12498,21 @@ function updateField(field, requiredFields, node, formValues, logic, config) {
12471
12498
  if (fieldIsRequired) {
12472
12499
  field.isVisible = true;
12473
12500
  }
12474
- const updateValues = (fieldValues) => Object.entries(fieldValues).forEach(([key, value]) => {
12475
- field[key] = typeof value === "function" ? value() : value;
12476
- if (key === "value") {
12477
- const readOnlyPropertyWasUpdated = typeof fieldValues.readOnly !== "undefined";
12478
- const isReadonlyByDefault = field.readOnly;
12479
- const isReadonly = readOnlyPropertyWasUpdated ? fieldValues.readOnly : isReadonlyByDefault;
12480
- if (!isReadonly && (value === null || field.inputType === "checkbox")) {
12481
- field.value = void 0;
12501
+ const updateAttributes = (fieldAttrs) => {
12502
+ Object.entries(fieldAttrs).forEach(([key, value]) => {
12503
+ field[key] = typeof value === "function" ? value() : value;
12504
+ if (key === "value") {
12505
+ const readOnlyPropertyWasUpdated = typeof fieldAttrs.readOnly !== "undefined";
12506
+ const isReadonlyByDefault = field.readOnly;
12507
+ const isReadonly = readOnlyPropertyWasUpdated ? fieldAttrs.readOnly : isReadonlyByDefault;
12508
+ if (!isReadonly && (value === null || field.inputType === "checkbox")) {
12509
+ field.value = void 0;
12510
+ }
12482
12511
  }
12483
- }
12484
- });
12512
+ });
12513
+ };
12485
12514
  if (field.getComputedAttributes) {
12486
- const computedFieldValues = field.getComputedAttributes({
12515
+ const newAttributes = field.getComputedAttributes({
12487
12516
  field,
12488
12517
  isRequired: fieldIsRequired,
12489
12518
  node,
@@ -12491,23 +12520,24 @@ function updateField(field, requiredFields, node, formValues, logic, config) {
12491
12520
  config,
12492
12521
  logic
12493
12522
  });
12494
- updateValues(computedFieldValues);
12523
+ updateAttributes(newAttributes);
12495
12524
  }
12496
12525
  if (field.calculateConditionalProperties) {
12497
- const newFieldValues = field.calculateConditionalProperties({
12526
+ const { rootFieldAttrs, newAttributes } = field.calculateConditionalProperties({
12498
12527
  isRequired: fieldIsRequired,
12499
12528
  conditionBranch: node,
12500
12529
  formValues
12501
12530
  });
12502
- updateValues(newFieldValues);
12531
+ updateAttributes(newAttributes);
12532
+ removeConditionalStaleAttributes(field, newAttributes, rootFieldAttrs);
12503
12533
  }
12504
12534
  if (field.calculateCustomValidationProperties) {
12505
- const newFieldValues = field.calculateCustomValidationProperties(
12535
+ const newAttributes = field.calculateCustomValidationProperties(
12506
12536
  fieldIsRequired,
12507
12537
  node,
12508
12538
  formValues
12509
12539
  );
12510
- updateValues(newFieldValues);
12540
+ updateAttributes(newAttributes);
12511
12541
  }
12512
12542
  }
12513
12543
  function processNode({
@@ -12849,17 +12879,23 @@ function calculateConditionalProperties({ fieldParams, customProperties, logic,
12849
12879
  logic
12850
12880
  )
12851
12881
  };
12852
- return (0, import_omit2.default)((0, import_merge2.default)(base, presentation, newFieldParams), ["inputType"]);
12882
+ return {
12883
+ rootFieldAttrs: fieldParams,
12884
+ newAttributes: (0, import_omit2.default)((0, import_merge2.default)(base, presentation, newFieldParams), ["inputType"])
12885
+ };
12853
12886
  }
12854
12887
  const isVisible = isRequired;
12855
12888
  return {
12856
- isVisible,
12857
- required: isRequired,
12858
- schema: buildYupSchema({
12859
- ...fieldParams,
12860
- ...extractParametersFromNode(conditionBranch),
12861
- required: isRequired
12862
- })
12889
+ rootFieldAttrs: fieldParams,
12890
+ newAttributes: {
12891
+ isVisible,
12892
+ required: isRequired,
12893
+ schema: buildYupSchema({
12894
+ ...fieldParams,
12895
+ ...extractParametersFromNode(conditionBranch),
12896
+ required: isRequired
12897
+ })
12898
+ }
12863
12899
  };
12864
12900
  };
12865
12901
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@remoteoss/json-schema-form",
3
- "version": "0.7.2-beta.0",
3
+ "version": "0.7.3-beta.0",
4
4
  "description": "Headless UI form powered by JSON Schemas",
5
5
  "author": "Remote.com <engineering@remote.com> (https://remote.com/)",
6
6
  "license": "MIT",
@@ -27,12 +27,7 @@ it('Should allow check of a nested property in a conditional', () => {
27
27
  additionalProperties: false,
28
28
  properties: {
29
29
  child: {
30
- oneOf: [
31
- {
32
- const: 'yes',
33
- },
34
- { const: 'no' },
35
- ],
30
+ oneOf: [{ const: 'yes' }, { const: 'no' }],
36
31
  type: 'string',
37
32
  },
38
33
  },
@@ -56,3 +51,311 @@ it('Should allow check of a nested property in a conditional', () => {
56
51
  undefined
57
52
  );
58
53
  });
54
+
55
+ describe('Conditional attributes updated', () => {
56
+ it('Update basic case with const, default, maximum', () => {
57
+ const { fields, handleValidation } = createHeadlessForm(
58
+ {
59
+ properties: {
60
+ is_full_time: { type: 'string', oneOf: [{ const: 'yes' }, { const: 'no' }] },
61
+ hours: { type: 'number' },
62
+ },
63
+ allOf: [
64
+ {
65
+ if: {
66
+ properties: { is_full_time: { const: 'yes' } },
67
+ required: ['is_full_time'],
68
+ },
69
+ then: {
70
+ properties: {
71
+ hours: {
72
+ const: 8,
73
+ default: 8,
74
+ },
75
+ },
76
+ },
77
+ else: {
78
+ properties: {
79
+ hours: {
80
+ maximum: 4,
81
+ },
82
+ },
83
+ },
84
+ },
85
+ ],
86
+ },
87
+ { strictInputType: false }
88
+ );
89
+
90
+ // Given "Yes" it applies "const" and "default"
91
+ expect(handleValidation({ is_full_time: 'yes', hours: 4 }).formErrors).toEqual({
92
+ hours: 'The only accepted value is 8.',
93
+ });
94
+ expect(fields[1]).toMatchObject({ const: 8, default: 8 });
95
+ expect(fields[1].maximum).toBeUndefined();
96
+
97
+ // Changing to "No", applies the "maximum" and cleans "const" and "default"
98
+ expect(handleValidation({ is_full_time: 'no', hours: 4 }).formErrors).toBeUndefined();
99
+ expect(fields[1]).toMatchObject({ maximum: 4 });
100
+ expect(fields[1].const).toBeUndefined();
101
+ expect(fields[1].default).toBeUndefined();
102
+
103
+ // Changing back to "Yes", it removes "maximum", and applies "const" and "default"
104
+ expect(handleValidation({}).formErrors).toBeUndefined();
105
+ expect(handleValidation({ is_full_time: 'yes', hours: 8 }).formErrors).toBeUndefined();
106
+ expect(fields[1].maximum).toBeUndefined();
107
+ expect(fields[1]).toMatchObject({ const: 8, default: 8 });
108
+ });
109
+
110
+ it('Update a new attribute (eg description)', () => {
111
+ const { fields, handleValidation } = createHeadlessForm(
112
+ {
113
+ properties: {
114
+ is_full_time: { type: 'string', oneOf: [{ const: 'yes' }, { const: 'no' }] },
115
+ hours: {
116
+ type: 'number',
117
+ },
118
+ },
119
+ allOf: [
120
+ {
121
+ if: {
122
+ properties: { is_full_time: { const: 'yes' } },
123
+ required: ['is_full_time'],
124
+ },
125
+ then: {
126
+ properties: {
127
+ hours: {
128
+ description: 'We recommend 8 hours.',
129
+ },
130
+ },
131
+ },
132
+ },
133
+ ],
134
+ },
135
+ { strictInputType: false }
136
+ );
137
+
138
+ // By default the attribute is not set.
139
+ expect(fields[1].description).toBeUndefined();
140
+
141
+ // Given "Yes" it applies the conditional attribute
142
+ expect(handleValidation({ is_full_time: 'yes' }).formErrors).toBeUndefined();
143
+ expect(fields[1].description).toBe('We recommend 8 hours.');
144
+
145
+ // Changing to "No", removes the description
146
+ expect(handleValidation({ is_full_time: 'no' }).formErrors).toBeUndefined();
147
+ expect(fields[1].description).toBeUndefined();
148
+
149
+ // Changing back to "Yes", it sets the attribute again
150
+ expect(handleValidation({ is_full_time: 'yes' }).formErrors).toBeUndefined();
151
+ expect(fields[1].description).toBe('We recommend 8 hours.');
152
+ });
153
+
154
+ it('Update an existing attribute (eg description)', () => {
155
+ const { fields, handleValidation } = createHeadlessForm(
156
+ {
157
+ properties: {
158
+ is_full_time: { type: 'string', oneOf: [{ const: 'yes' }, { const: 'no' }] },
159
+ hours: {
160
+ type: 'number',
161
+ description: 'Any value works.',
162
+ },
163
+ },
164
+ allOf: [
165
+ {
166
+ if: {
167
+ properties: { is_full_time: { const: 'yes' } },
168
+ required: ['is_full_time'],
169
+ },
170
+ then: {
171
+ properties: {
172
+ hours: {
173
+ description: 'We recommend 8 hours.',
174
+ },
175
+ },
176
+ },
177
+ },
178
+ ],
179
+ },
180
+ { strictInputType: false }
181
+ );
182
+
183
+ // By default the attribute is set the base value.
184
+ expect(fields[1].description).toBe('Any value works.');
185
+
186
+ // Given "Yes" it applies the conditional attribute
187
+ expect(handleValidation({ is_full_time: 'yes', hours: 4 }).formErrors).toBeUndefined();
188
+ expect(fields[1].description).toBe('We recommend 8 hours.');
189
+
190
+ // Changing to "No", it applies the base value again.
191
+ expect(handleValidation({ is_full_time: 'no', hours: 4 }).formErrors).toBeUndefined();
192
+ expect(fields[1].description).toBe('Any value works.');
193
+
194
+ // Changing back to "Yes", it sets the attribute again
195
+ expect(handleValidation({ is_full_time: 'yes', hours: 8 }).formErrors).toBeUndefined();
196
+ expect(fields[1].description).toBe('We recommend 8 hours.');
197
+ });
198
+
199
+ it('Update a nested attribute', () => {
200
+ const { fields, handleValidation } = createHeadlessForm(
201
+ {
202
+ properties: {
203
+ is_full_time: { type: 'string', oneOf: [{ const: 'yes' }, { const: 'no' }] },
204
+ hours: {
205
+ type: 'number',
206
+ presentation: {
207
+ inputType: 'number',
208
+ anything: 'info',
209
+ },
210
+ },
211
+ },
212
+ allOf: [
213
+ {
214
+ if: {
215
+ properties: { is_full_time: { const: 'yes' } },
216
+ required: ['is_full_time'],
217
+ },
218
+ then: {
219
+ properties: {
220
+ hours: {
221
+ presentation: {
222
+ anything: 'danger',
223
+ },
224
+ },
225
+ },
226
+ },
227
+ },
228
+ ],
229
+ },
230
+ { strictInputType: false }
231
+ );
232
+
233
+ // By default the attribute is set the base value.
234
+ expect(fields[1].anything).toBe('info');
235
+
236
+ // Given "Yes" it applies the conditional attribute
237
+ expect(handleValidation({ is_full_time: 'yes' }).formErrors).toBeUndefined();
238
+ expect(fields[1].anything).toBe('danger');
239
+
240
+ // Changing to "No", it applies the base value again.
241
+ expect(handleValidation({ is_full_time: 'no' }).formErrors).toBeUndefined();
242
+ expect(fields[1].anything).toBe('info');
243
+
244
+ // Changing back to "Yes", it sets the attribute again
245
+ expect(handleValidation({ is_full_time: 'yes' }).formErrors).toBeUndefined();
246
+ expect(fields[1].anything).toBe('danger');
247
+ });
248
+
249
+ it('Keeps existing attributes in matches that dont change the attr', () => {
250
+ const { fields, handleValidation } = createHeadlessForm(
251
+ {
252
+ properties: {
253
+ is_full_time: { type: 'string', oneOf: [{ const: 'yes' }, { const: 'no' }] },
254
+ hours: {
255
+ type: 'number',
256
+ description: 'Any value works.',
257
+ },
258
+ },
259
+ allOf: [
260
+ {
261
+ if: {
262
+ properties: { is_full_time: { const: 'yes' } },
263
+ required: ['is_full_time'],
264
+ },
265
+ then: {
266
+ required: ['hours'],
267
+ },
268
+ else: {
269
+ properties: {
270
+ hours: false,
271
+ },
272
+ },
273
+ },
274
+ ],
275
+ },
276
+ { strictInputType: false }
277
+ );
278
+
279
+ // By default the attribute is set the base value, even though the field is invisible.
280
+ expect(fields[1].description).toBe('Any value works.');
281
+ expect(fields[1].isVisible).toBe(false);
282
+
283
+ // Given "Yes" it keeps the base value
284
+ expect(handleValidation({ is_full_time: 'yes' }).formErrors).toEqual({
285
+ hours: 'Required field',
286
+ });
287
+ expect(fields[1].description).toBe('Any value works.');
288
+ expect(fields[1].isVisible).toBe(true);
289
+
290
+ // Changing to "No" it keeps the base value
291
+ expect(handleValidation({ is_full_time: 'no' }).formErrors).toBeUndefined();
292
+ expect(fields[1].description).toBe('Any value works.');
293
+ expect(fields[1].isVisible).toBe(false);
294
+ });
295
+
296
+ it('Keeps internal attributes (fieldAttrsFromJsf)', () => {
297
+ // This is necessary while we keep supporting "type", even if deprecated
298
+ // otherwise our Remote app will break because it didn't migrate
299
+ // from "type" to "inputType" yet.
300
+ const { fields, handleValidation } = createHeadlessForm(
301
+ {
302
+ properties: {
303
+ is_full_time: { type: 'string', oneOf: [{ const: 'yes' }, { const: 'no' }] },
304
+ salary_period: {
305
+ type: 'string',
306
+ title: 'Salary period',
307
+ oneOf: [
308
+ { title: 'Weekly', const: 'weekly' },
309
+ { title: 'Monthly', const: 'monthly' },
310
+ ],
311
+ },
312
+ },
313
+ allOf: [
314
+ {
315
+ if: {
316
+ properties: { is_full_time: { const: 'yes' } },
317
+ required: ['is_full_time'],
318
+ },
319
+ then: {
320
+ properties: {
321
+ salary_period: {
322
+ description: 'We recommend montlhy.',
323
+ },
324
+ },
325
+ },
326
+ },
327
+ ],
328
+ },
329
+ { strictInputType: false }
330
+ );
331
+
332
+ // Given "Yes" it keeps the "type"
333
+ handleValidation({ is_full_time: 'yes' });
334
+
335
+ // All the following attrs are never removed
336
+ // during conditionals because they are core.
337
+ expect(fields[1]).toMatchObject({
338
+ name: 'salary_period',
339
+ label: 'Salary period',
340
+ required: false,
341
+ type: 'radio',
342
+ inputType: 'radio',
343
+ jsonType: 'string',
344
+ computedAttributes: {},
345
+ calculateConditionalProperties: expect.any(Function),
346
+ schema: expect.any(Object),
347
+ scopedJsonSchema: expect.any(Object),
348
+ isVisible: true,
349
+ options: [
350
+ {
351
+ label: 'Weekly',
352
+ value: 'weekly',
353
+ },
354
+ {
355
+ label: 'Monthly',
356
+ value: 'monthly',
357
+ },
358
+ ],
359
+ });
360
+ });
361
+ });
@@ -446,7 +446,7 @@ describe('createHeadlessForm', () => {
446
446
  [fieldName]: 'The option "blah-blah" is not valid.',
447
447
  });
448
448
 
449
- // Given undefined, it says it's a required field.
449
+ // Given undefined, it says it's a required field.
450
450
  expect(validateForm({})).toEqual({
451
451
  [fieldName]: 'Required field',
452
452
  });
@@ -856,6 +856,7 @@ describe('createHeadlessForm', () => {
856
856
  has_car: 'yes',
857
857
  })
858
858
  ).toBeUndefined();
859
+
859
860
  expect(validateForm({})).toEqual({
860
861
  has_siblings: 'Required field',
861
862
  });
@@ -1419,6 +1420,50 @@ describe('createHeadlessForm', () => {
1419
1420
  ],
1420
1421
  });
1421
1422
  });
1423
+
1424
+ it('can be a conditional field', () => {
1425
+ const { fields, handleValidation } = createHeadlessForm({
1426
+ properties: {
1427
+ yes_or_no: {
1428
+ title: 'Show the dependents or not?',
1429
+ oneOf: [{ const: 'yes' }, { const: 'no' }],
1430
+ 'x-jsf-presentation': { inputType: 'radio' },
1431
+ },
1432
+ dependent_details: mockGroupArrayInput,
1433
+ },
1434
+ allOf: [
1435
+ {
1436
+ if: {
1437
+ properties: {
1438
+ yes_or_no: { const: 'yes' },
1439
+ },
1440
+ required: ['yes_or_no'],
1441
+ },
1442
+ then: {
1443
+ required: ['dependent_details'],
1444
+ },
1445
+ else: {
1446
+ properties: {
1447
+ dependent_details: false,
1448
+ },
1449
+ },
1450
+ },
1451
+ ],
1452
+ });
1453
+
1454
+ // By default is hidden but the fields are accessible
1455
+ expect(getField(fields, 'dependent_details').isVisible).toBe(false);
1456
+ expect(getField(fields, 'dependent_details').fields).toEqual(expect.any(Function));
1457
+
1458
+ // When the condition matches...
1459
+ const { formErrors } = handleValidation({ yes_or_no: 'yes' });
1460
+ expect(formErrors).toEqual({
1461
+ dependent_details: 'Required field',
1462
+ });
1463
+ // it gets visible with its inner fields.
1464
+ expect(getField(fields, 'dependent_details').isVisible).toBe(true);
1465
+ expect(getField(fields, 'dependent_details').fields).toEqual(expect.any(Function));
1466
+ });
1422
1467
  });
1423
1468
 
1424
1469
  it('supports "radio" field type with its "card" and "card-expandable" variants', () => {
@@ -1808,11 +1853,8 @@ describe('createHeadlessForm', () => {
1808
1853
  expect(foodField.options).toHaveLength(4);
1809
1854
  // ...Food description was back to the original
1810
1855
  expect(foodField.description).toBeUndefined();
1811
-
1812
- // @BUG RMT-58 PTO description should disappear, but it didn't.
1813
- expect(getField(fields, 'pto').description).toBe(
1814
- 'Above 30 hours, the PTO needs to be at least 20 days.'
1815
- );
1856
+ // ...PTO Description is removed too.
1857
+ expect(getField(fields, 'pto').description).toBeUndefined();
1816
1858
 
1817
1859
  // Given again "low perks", the form valid.
1818
1860
  expect(
@@ -250,7 +250,7 @@ export const schemaWithComputedAttributeThatDoesntExist = {
250
250
  field_a: {
251
251
  type: 'number',
252
252
  'x-jsf-logic-computedAttrs': {
253
- default: 'iDontExist',
253
+ minimum: 'iDontExist',
254
254
  },
255
255
  },
256
256
  },