@ukhomeoffice/cop-react-form-renderer 6.15.8-alpha → 6.15.8

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.
Files changed (36) hide show
  1. package/dist/components/CollectionSummary/BannerStrip.scss +4 -0
  2. package/dist/components/FormComponent/FormComponent.js +6 -0
  3. package/dist/components/FormComponent/helpers/addLabel.js +3 -2
  4. package/dist/components/FormPage/FormPage.js +28 -17
  5. package/dist/components/FormPage/FormPage.test.js +53 -0
  6. package/dist/components/FormRenderer/FormRenderer.js +5 -10
  7. package/dist/components/FormRenderer/clear-uncompleted-routes/test-data/forms/cop-reassign-task-to-rcc.json +445 -0
  8. package/dist/components/FormRenderer/clear-uncompleted-routes/test-data/forms/form-page-nested-radio-component.json +200 -0
  9. package/dist/components/FormRenderer/clear-uncompleted-routes/test-data/forms/form-page-same-component-reused-one-shown.json +74 -0
  10. package/dist/components/FormRenderer/clear-uncompleted-routes/test-data/input/cop-airpax-carrier.json +407 -0
  11. package/dist/components/FormRenderer/clear-uncompleted-routes/test-data/input/data-page-nested-radio-component.json +45 -0
  12. package/dist/components/FormRenderer/clear-uncompleted-routes/test-data/input/data-page-same-component-reused-one-shown.json +8 -0
  13. package/dist/components/FormRenderer/clear-uncompleted-routes/test-data/input/reassign-to-rcc.json +72 -0
  14. package/dist/components/FormRenderer/clear-uncompleted-routes/test-data/output/data-page-nested-radio-component-removed.json +45 -0
  15. package/dist/components/FormRenderer/clear-uncompleted-routes/test-data/output/data-page-same-component-reused-one-shown-removed.json +7 -0
  16. package/dist/components/FormRenderer/helpers/clearOutUncompletedRoutes.js +248 -171
  17. package/dist/components/FormRenderer/helpers/clearOutUncompletedRoutes.test.js +43 -7
  18. package/dist/components/FormRenderer/helpers/clearOutUncompletedRoutesUtils.js +139 -40
  19. package/dist/components/FormRenderer/helpers/clearOutUncompletedRoutesUtils.test.js +64 -7
  20. package/dist/components/FormRenderer/onCYAAction.js +0 -2
  21. package/dist/components/FormRenderer/onCYAAction.test.js +5 -0
  22. package/dist/components/FormRenderer/onPageAction.js +0 -1
  23. package/dist/hooks/useGetRequest.js +15 -15
  24. package/dist/hooks/useRefData.js +3 -2
  25. package/dist/utils/Component/getComponentTests/getComponent.multifile.test.js +2 -1
  26. package/dist/utils/Component/getDefaultValueFromConfig.js +2 -1
  27. package/dist/utils/Condition/meetsCondition.js +26 -12
  28. package/dist/utils/Condition/meetsCondition.test.js +21 -0
  29. package/dist/utils/Data/getAutocompleteSource.js +68 -51
  30. package/dist/utils/Data/getAutocompleteSource.test.js +31 -18
  31. package/dist/utils/Operate/doesContainValue.js +34 -0
  32. package/dist/utils/Operate/doesContainValue.test.js +75 -0
  33. package/dist/utils/Operate/runPageOperations.js +2 -0
  34. package/dist/utils/Validate/validateOnPageLoad.js +23 -0
  35. package/dist/utils/Validate/validateOnPageLoad.test.js +88 -0
  36. package/package.json +5 -4
@@ -3,28 +3,43 @@
3
3
  Object.defineProperty(exports, "__esModule", {
4
4
  value: true
5
5
  });
6
- exports.removeObjectWithSingleIdFieldInPlace = exports.removeEmptyArraysAndUnusedCollectionIDs = exports.isShowEntity = exports.getNestedQuestionPath = exports.getImmediateParent = exports.getDependencyObjectFromPath = exports.getDependencies = exports.findComponentDefinitionInForm = exports.deleteNodeByPath = exports.deleteCorrespondingMetaInfo = void 0;
6
+ exports.toArray = exports.removeObjectWithOnlySingleIdField = exports.removeEmptyArraysAndUnusedCollectionIDs = exports.pruneCollectionEntry = exports.isShowEntity = exports.getNestedQuestionPath = exports.getImmediateParent = exports.getDependencyObjectFromPath = exports.getDependencies = exports.findComponentDefinitionInForm = exports.deleteNodeByPath = exports.deleteNodeAndOptions = exports.deleteCorrespondingMetaInfo = exports.deleteComponentData = exports.addValue = void 0;
7
7
  var _Condition = _interopRequireDefault(require("../../../utils/Condition"));
8
8
  function _interopRequireDefault(e) { return e && e.__esModule ? e : { default: e }; }
9
+ /* eslint-disable no-param-reassign */
10
+
9
11
  /**
10
- * Given a path, remove a node from this path within an object.
12
+ * Add a value to a map of arrays. If the key exists, append the value to the array.
13
+ * If not, create the map entry with that key.
11
14
  *
15
+ * @param {*} key
16
+ * @param {*} value
17
+ * @param {*} multiMap
18
+ */
19
+ const addValue = (key, value, multiMap) => {
20
+ if (!multiMap.has(key)) {
21
+ multiMap.set(key, []);
22
+ }
23
+ multiMap.get(key).push(value);
24
+ };
25
+
26
+ /**
27
+ * Given a path, remove a node from this path within an object.
12
28
  *
13
- * @param {Object} obj Javascript object from which the node will be deleted. Updated by the method.
29
+ * @param {Object} payload Javascript object from which the node will be deleted. Updated by the method.
14
30
  * @param {String} path A string containing a decimal point delimited path specifying the node to delete.
15
- * @return Nothing, obj above updated.
31
+ * @return {void}, obj above updated.
16
32
  */
17
-
18
- /* eslint-disable consistent-return, no-param-reassign */
19
- const deleteNodeByPath = (obj, path) => {
20
- if (Array.isArray(obj)) {
21
- // If obj is an array, recursively call deleteNodeByPath on each element
22
- for (let i = 0; i < obj.length; i += 1) {
23
- deleteNodeByPath(obj[i], path);
33
+ exports.addValue = addValue;
34
+ const deleteNodeByPath = (payload, path) => {
35
+ if (Array.isArray(payload)) {
36
+ // If payload is an array, recursively call deleteNodeByPath on each element
37
+ for (let i = 0; i < payload.length; i += 1) {
38
+ deleteNodeByPath(payload[i], path);
24
39
  }
25
40
  }
26
41
  const keys = path.split('.');
27
- let current = obj;
42
+ let current = payload;
28
43
  for (let i = 0; i < keys.length - 1; i += 1) {
29
44
  current = current[keys[i]];
30
45
  if (current === undefined) {
@@ -32,27 +47,8 @@ const deleteNodeByPath = (obj, path) => {
32
47
  }
33
48
  }
34
49
  if (current[keys[keys.length - 1]]) {
35
- console.log("Deleting element : ".concat(keys[keys.length - 1]));
36
50
  delete current[keys[keys.length - 1]];
37
51
  }
38
- return obj;
39
- };
40
-
41
- /**
42
- * Pruning a collection payload may have resulted in objects that are only left with their 'id' field, which isn't data
43
- * but added by the renderer to find the activeId. If so, remove these objects entirely as they have been pruned.
44
- *
45
- * @param {Array} array Array of objects. Each object which has only 1 remaining field called 'id' should be removed.
46
- * @return Nothing, array above updated.
47
- */
48
- exports.deleteNodeByPath = deleteNodeByPath;
49
- const removeObjectWithSingleIdFieldInPlace = array => {
50
- for (let i = array.length - 1; i >= 0; i -= 1) {
51
- const obj = array[i];
52
- if (Object.keys(obj).length === 1 && Object.keys(obj)[0] === 'id') {
53
- array.splice(i, 1);
54
- }
55
- }
56
52
  };
57
53
 
58
54
  /**
@@ -63,7 +59,7 @@ const removeObjectWithSingleIdFieldInPlace = array => {
63
59
  * @param {String} path Decimal point delimited path.
64
60
  * @returns {String} Immediate parent of the path passed in.
65
61
  */
66
- exports.removeObjectWithSingleIdFieldInPlace = removeObjectWithSingleIdFieldInPlace;
62
+ exports.deleteNodeByPath = deleteNodeByPath;
67
63
  const getImmediateParent = path => {
68
64
  if (typeof path !== 'string' || !path.includes('.')) {
69
65
  return null;
@@ -89,6 +85,53 @@ const getNestedQuestionPath = (optionPath, nestedFieldId) => {
89
85
  return parentPath ? "".concat(parentPath, ".").concat(nestedFieldId) : nestedFieldId;
90
86
  };
91
87
 
88
+ /**
89
+ * If the component has options, go through each option removing the data for any nested fields.
90
+ * Required as nested options are in the payload at the same heirarchical level.
91
+ *
92
+ * There are occasions when a field shouldn't be removed from the payload, eg if a form is being used for 2 purposes
93
+ * eg the cop-reassign-task-to-rcc.json, and the field is only being hidden for display purposes. Allow these fields
94
+ * to be preserved with a property on the component.
95
+
96
+ * @param {Object} payload Javascript object from which the node will be deleted. Updated by the method.
97
+ * @param {String} path A string containing a decimal point delimited path specifying the node to delete.
98
+ * @param {Object} component The form component representing the path being deleted
99
+ * @return {void}, obj above updated.
100
+ */
101
+ exports.getNestedQuestionPath = getNestedQuestionPath;
102
+ const deleteNodeAndOptions = (payload, path, component) => {
103
+ var _component$data;
104
+ if (component.preserveInPayload) return;
105
+ deleteNodeByPath(payload, path);
106
+ // If the component has options, go through each option removing the data for any nested fields. Required as nested options are in the payload at the same heirarchical level.
107
+ if (component !== null && component !== void 0 && (_component$data = component.data) !== null && _component$data !== void 0 && _component$data.options) {
108
+ var _component$data2;
109
+ component === null || component === void 0 || (_component$data2 = component.data) === null || _component$data2 === void 0 || (_component$data2 = _component$data2.options) === null || _component$data2 === void 0 || _component$data2.forEach(option => {
110
+ var _option$nested;
111
+ (_option$nested = option.nested) === null || _option$nested === void 0 || _option$nested.forEach(nested => {
112
+ deleteNodeByPath(payload, getNestedQuestionPath(path, nested.fieldId));
113
+ });
114
+ });
115
+ }
116
+ };
117
+
118
+ /**
119
+ * Pruning a collection payload may have resulted in objects that are only left with their 'id' field, which isn't data
120
+ * but added by the renderer to find the activeId. If so, remove these objects entirely as they have been pruned.
121
+ *
122
+ * @param {Array} array Array of objects. Each object which has only 1 remaining field called 'id' should be removed.
123
+ * @return {void}, array above updated.
124
+ */
125
+ exports.deleteNodeAndOptions = deleteNodeAndOptions;
126
+ const removeObjectWithOnlySingleIdField = array => {
127
+ for (let i = array.length - 1; i >= 0; i -= 1) {
128
+ const obj = array[i];
129
+ if (Object.keys(obj).length === 1 && Object.keys(obj)[0] === 'id') {
130
+ array.splice(i, 1);
131
+ }
132
+ }
133
+ };
134
+
92
135
  /**
93
136
  * Helper method to establish all the payload paths (dependencies) that an entity (page or component) is
94
137
  * dependent on through its show_when rule.
@@ -102,7 +145,7 @@ const getNestedQuestionPath = (optionPath, nestedFieldId) => {
102
145
  * @param {Object} entity Entity whose show_when rule is to be searched for 'field' data items within.
103
146
  * @returns {Set} Set of payload paths that the entity (page or component) is dependent on.
104
147
  */
105
- exports.getNestedQuestionPath = getNestedQuestionPath;
148
+ exports.removeObjectWithOnlySingleIdField = removeObjectWithOnlySingleIdField;
106
149
  const getDependencies = entity => {
107
150
  const findShowWhenFields = (showWhenObject, showWhenFields) => {
108
151
  if (typeof showWhenObject === 'object' && showWhenObject !== null) {
@@ -200,11 +243,13 @@ exports.isShowEntity = isShowEntity;
200
243
  const findComponentDefinitionInForm = (pageComponentDef, componentByIdMap, componentByFieldIdMap) => {
201
244
  var _ref, _componentByIdMap$get;
202
245
  const componentInForm = (_ref = (_componentByIdMap$get = componentByIdMap.get(pageComponentDef.use)) !== null && _componentByIdMap$get !== void 0 ? _componentByIdMap$get : componentByFieldIdMap.get(pageComponentDef.use)) !== null && _ref !== void 0 ? _ref : pageComponentDef;
246
+
247
+ // Create clone of component, so processing can make changes to it without changing the form
248
+ const componentInFormClone = JSON.parse(JSON.stringify(componentInForm));
203
249
  if (pageComponentDef.use && pageComponentDef.show_when) {
204
- componentInForm.show_when = pageComponentDef.show_when;
250
+ componentInFormClone.show_when = pageComponentDef.show_when;
205
251
  }
206
- // Retun clone of component, so subsequent processing can make changes to it without changing the form
207
- return JSON.parse(JSON.stringify(componentInForm));
252
+ return componentInFormClone;
208
253
  };
209
254
 
210
255
  /**
@@ -216,7 +261,7 @@ const findComponentDefinitionInForm = (pageComponentDef, componentByIdMap, compo
216
261
  * @param {*} component The component definition being deleted that needs corresponding meta data also deleted
217
262
  * @param {*} collectionDataObject The payload containing the file data being deleted (for which the corresponding meta needs deleting)
218
263
  * @param {*} formData The entire payload, which will include the meta section
219
- * @returns Nothing, as the formData will be updated in situ
264
+ * @returns {void}, as the formData will be updated in situ
220
265
  */
221
266
  exports.findComponentDefinitionInForm = findComponentDefinitionInForm;
222
267
  const deleteCorrespondingMetaInfo = (component, collectionDataObject, formData) => {
@@ -236,6 +281,7 @@ const deleteCorrespondingMetaInfo = (component, collectionDataObject, formData)
236
281
  }
237
282
  }
238
283
  };
284
+
239
285
  /**
240
286
  * After the payload has been cleansed of individual data items, empty arrays and objects may remain.
241
287
  * Removing an array (when the payload for a collection) may leave a redundant activeId field. The active Id
@@ -252,6 +298,7 @@ const deleteCorrespondingMetaInfo = (component, collectionDataObject, formData)
252
298
  * 3. The function operates recursively to handle deeply nested structures.
253
299
  *
254
300
  * @param {any} payload - The input data structure, which can be an array or an object.
301
+ *
255
302
  */
256
303
  exports.deleteCorrespondingMetaInfo = deleteCorrespondingMetaInfo;
257
304
  const removeEmptyArraysAndUnusedCollectionIDs = payload => {
@@ -274,12 +321,10 @@ const removeEmptyArraysAndUnusedCollectionIDs = payload => {
274
321
  // If the array being removed has an activeId associated with it, remove it
275
322
  if (payload["".concat(key, "ActiveId")]) {
276
323
  delete payload["".concat(key, "ActiveId")];
277
- console.log("Deleted element : ".concat(key, "ActiveId}"));
278
324
  }
279
325
  delete payload["".concat(key, "ActiveId")];
280
326
  if (payload[key]) {
281
327
  delete payload[key];
282
- console.log("Deleted element : ".concat(key));
283
328
  }
284
329
  } else {
285
330
  removeEmptyArraysAndUnusedCollectionIDs(payload[key]); // Recurse for nested structures
@@ -287,4 +332,58 @@ const removeEmptyArraysAndUnusedCollectionIDs = payload => {
287
332
  });
288
333
  }
289
334
  };
290
- exports.removeEmptyArraysAndUnusedCollectionIDs = removeEmptyArraysAndUnusedCollectionIDs;
335
+
336
+ /**
337
+ * Delete a component's payload item from the overall payload.
338
+ * A component can be defined in >1 places in the form spec. To cater for this,
339
+ * if the componentsToKeep counter tells us that there is > 1 uses of this component
340
+ * still unaccounted for in the form then don't delete, but reduce the count by 1.
341
+ *
342
+ * When the counter reaches 1 we know all other occurences of the component have been resolved
343
+ * so it is safe to delete.
344
+ *
345
+ *
346
+ * @param {*} payload The form payload from which to delete the component data
347
+ * @param {*} path The payload path of the component
348
+ * @param {*} component The component whose data we should attempt to delete
349
+ * @param {*} componentsToKeep A list of all components with a count of their number of uses in the form
350
+ */
351
+ exports.removeEmptyArraysAndUnusedCollectionIDs = removeEmptyArraysAndUnusedCollectionIDs;
352
+ const deleteComponentData = (payload, path, component, componentsToKeep) => {
353
+ if (componentsToKeep[path] > 1) {
354
+ componentsToKeep[path] -= 1;
355
+ } else {
356
+ deleteNodeAndOptions(payload, path, component);
357
+ }
358
+ };
359
+
360
+ /**
361
+ *
362
+ * Takes a single page collection payload object and removes the payload items specified in the componentsToPrune list
363
+ * as long as they don't appear in the componentsToKeep list.
364
+ * Additionally, if the component type is multifile, remove the corresponding data entries that will have been created
365
+ * in the meta section of the payload by the form renderer.
366
+ *
367
+ * @param {Set} pathsToKeep paths that we cannot delete from the collectionDataObject
368
+ * @param {Map} componentsToPrune paths that we should delete, as long as they are not in the pathsToKeep
369
+ * @param {Object} collectionDataObject the payload from which to delete the paths
370
+ * @param {Object} formData The form data, whose meta section may include corresponding documents entries for multifile entries
371
+ */
372
+ exports.deleteComponentData = deleteComponentData;
373
+ const pruneCollectionEntry = (pathsToKeep, componentsToPrune, collectionDataObject, formData) => {
374
+ componentsToPrune.forEach(component => {
375
+ if (!pathsToKeep.has(component.fieldId)) {
376
+ if (component.type === "multifile") {
377
+ deleteCorrespondingMetaInfo(component, collectionDataObject, formData);
378
+ }
379
+ deleteNodeAndOptions(collectionDataObject, component.fieldId, component);
380
+ }
381
+ });
382
+ };
383
+
384
+ /*
385
+ * Converts an object to an array if it isn't already, for use when combining show when rules.
386
+ */
387
+ exports.pruneCollectionEntry = pruneCollectionEntry;
388
+ const toArray = value => Array.isArray(value) ? value : [value];
389
+ exports.toArray = toArray;
@@ -19,8 +19,8 @@ describe('deleteNodeByPath', () => {
19
19
  epsilon: "epsilon"
20
20
  };
21
21
  const path = 'type';
22
- const result = Utils.deleteNodeByPath(nested, path);
23
- expect(result).toEqual({
22
+ Utils.deleteNodeByPath(nested, path);
23
+ expect(nested).toEqual({
24
24
  alpha: "alpha",
25
25
  bravo: "bravo",
26
26
  person: {
@@ -44,8 +44,8 @@ describe('deleteNodeByPath', () => {
44
44
  epsilon: "epsilon"
45
45
  };
46
46
  const path = 'person.charlie';
47
- const result = Utils.deleteNodeByPath(nested, path);
48
- expect(result).toEqual({
47
+ Utils.deleteNodeByPath(nested, path);
48
+ expect(nested).toEqual({
49
49
  type: "Example form",
50
50
  alpha: "alpha",
51
51
  bravo: "bravo",
@@ -57,7 +57,7 @@ describe('deleteNodeByPath', () => {
57
57
  });
58
58
  });
59
59
  });
60
- describe('removeObjectWithSingleIdFieldInPlace', () => {
60
+ describe('removeObjectWithOnlySingleIdField', () => {
61
61
  it('removes objects with only the "id" field', () => {
62
62
  const inputArray = [{
63
63
  id: 1
@@ -73,7 +73,7 @@ describe('removeObjectWithSingleIdFieldInPlace', () => {
73
73
  id: 4,
74
74
  name: 'John'
75
75
  }];
76
- Utils.removeObjectWithSingleIdFieldInPlace(inputArray);
76
+ Utils.removeObjectWithOnlySingleIdField(inputArray);
77
77
  expect(inputArray).toEqual([{
78
78
  name: 'John'
79
79
  }, {
@@ -90,7 +90,7 @@ describe('removeObjectWithSingleIdFieldInPlace', () => {
90
90
  age: 30
91
91
  }];
92
92
  const originalArray = [].concat(inputArray);
93
- Utils.removeObjectWithSingleIdFieldInPlace(inputArray);
93
+ Utils.removeObjectWithOnlySingleIdField(inputArray);
94
94
  expect(inputArray).toEqual(originalArray);
95
95
  });
96
96
  });
@@ -499,4 +499,61 @@ describe("removeEmptyArraysAndUnusedCollectionIDs", () => {
499
499
  nested: {}
500
500
  }]);
501
501
  });
502
+ });
503
+ describe("pruneCollectionEntry", () => {
504
+ let pathsToKeep;
505
+ let componentsToPrune;
506
+ let collectionDataObject;
507
+ let formData;
508
+ let expectedFormData;
509
+ beforeEach(() => {
510
+ pathsToKeep = new Set(['keepThis']);
511
+ componentsToPrune = [{
512
+ fieldId: 'removeThis',
513
+ type: 'text'
514
+ }, {
515
+ fieldId: 'keepThis',
516
+ type: 'text'
517
+ }, {
518
+ fieldId: 'removeMultifile',
519
+ type: 'multifile'
520
+ }];
521
+ collectionDataObject = {
522
+ 'removeThis': 'value',
523
+ 'keepThis': 'value',
524
+ 'removeMultifile': {
525
+ 'url': 'https://example.com/file1.pdf'
526
+ }
527
+ };
528
+ formData = {
529
+ meta: {
530
+ documents: [{
531
+ url: 'https://example.com/file1.pdf'
532
+ }, {
533
+ url: 'https://example.com/file2.pdf'
534
+ }, {
535
+ url: 'https://example.com/file3.pdf'
536
+ }]
537
+ }
538
+ };
539
+ expectedFormData = {
540
+ meta: {
541
+ documents: [{
542
+ url: 'https://example.com/file2.pdf'
543
+ }, {
544
+ url: 'https://example.com/file3.pdf'
545
+ }]
546
+ }
547
+ };
548
+ });
549
+ it('should remove components not in pathsToKeep', () => {
550
+ Utils.pruneCollectionEntry(pathsToKeep, componentsToPrune, collectionDataObject, formData);
551
+ expect(collectionDataObject).toEqual({
552
+ 'keepThis': 'value'
553
+ });
554
+ });
555
+ it('should delete meta.documents for multifile components', () => {
556
+ Utils.pruneCollectionEntry(pathsToKeep, componentsToPrune, collectionDataObject, formData);
557
+ expect(formData).toEqual(expectedFormData);
558
+ });
502
559
  });
@@ -36,7 +36,6 @@ const onCYAAction = (setPagePoint, action, pages, validate, components, data, se
36
36
  pages,
37
37
  components
38
38
  };
39
- console.log("Invoking clearOutUncompletedRoutes");
40
39
  _helpers.default.clearOutUncompletedRoutes(formPagesAndComponents, submissionData);
41
40
  submissionData.formStatus = _helpers.default.getSubmissionStatus(type, pages, pageId, action, submissionData, currentTask, true);
42
41
  setData(submissionData);
@@ -81,7 +80,6 @@ const onCYAAction = (setPagePoint, action, pages, validate, components, data, se
81
80
  pages,
82
81
  components
83
82
  };
84
- console.log("Invoking clearOutUncompletedRoutes");
85
83
  _helpers.default.clearOutUncompletedRoutes(formPagesAndComponents, submissionData);
86
84
  submissionData.formStatus = _helpers.default.getSubmissionStatus(type, pages, pageId, action, submissionData, currentTask, true);
87
85
  setData(submissionData);
@@ -55,6 +55,11 @@ jest.mock('./helpers', () => ({
55
55
  this.getNextPageIdCalls = 0;
56
56
  this.getFormStateCalls = 0;
57
57
  this.getSubmissionStatusCalls = 0;
58
+ },
59
+ clearOutUncompletedRoutesCalls: 0,
60
+ clearOutUncompletedRoutes(formPagesAndComponents, formData) {
61
+ this.clearOutUncompletedRoutesCalls += 1;
62
+ return formData;
58
63
  }
59
64
  }));
60
65
  jest.mock('../../utils', () => ({
@@ -112,7 +112,6 @@ const onPageAction = (action, patch, patchLabel, hooks, data, formState, validat
112
112
  components
113
113
  };
114
114
  if (action.type === _models.PageAction.TYPES.SUBMIT) {
115
- console.log("Invoking clearOutUncompletedRoutes");
116
115
  _helpers.default.clearOutUncompletedRoutes(formPagesAndComponents, submissionData);
117
116
  }
118
117
 
@@ -8,10 +8,6 @@ var _axios = _interopRequireDefault(require("axios"));
8
8
  var _react = require("react");
9
9
  var _useAxios = _interopRequireDefault(require("./useAxios"));
10
10
  function _interopRequireDefault(e) { return e && e.__esModule ? e : { default: e }; }
11
- // Global imports
12
-
13
- // Local imports
14
-
15
11
  // Caches for responses and errors.
16
12
  const cache = {};
17
13
  const errorCache = {};
@@ -31,15 +27,18 @@ const STATUS_IDLE = exports.STATUS_IDLE = 'idle';
31
27
  const STATUS_FETCHING = exports.STATUS_FETCHING = 'fetching';
32
28
  const STATUS_FETCHED = exports.STATUS_FETCHED = 'fetched';
33
29
  const STATUS_ERROR = exports.STATUS_ERROR = 'error';
34
- const useGetRequest = url => {
30
+ const useGetRequest = function (url) {
31
+ let caching = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : true;
35
32
  const axiosInstance = (0, _useAxios.default)();
36
- const cancelToken = _axios.default.CancelToken.source();
37
- const cancelRequests = () => {
38
- if (cancelToken) cancelToken.cancel();
39
- };
33
+ const cancelTokenRef = (0, _react.useRef)(_axios.default.CancelToken.source());
40
34
  const [status, setStatus] = (0, _react.useState)(STATUS_IDLE);
41
35
  const [error, setError] = (0, _react.useState)(null);
42
36
  const [data, setData] = (0, _react.useState)(null);
37
+ const cancelRequests = () => {
38
+ if (cancelTokenRef.current) {
39
+ cancelTokenRef.current.cancel();
40
+ }
41
+ };
43
42
  (0, _react.useEffect)(() => {
44
43
  if (!url || !axiosInstance) return;
45
44
  const fetchData = async () => {
@@ -47,9 +46,9 @@ const useGetRequest = url => {
47
46
  setError(null);
48
47
  setStatus(STATUS_FETCHING);
49
48
  let fetchedData;
50
- if (cache[url]) {
49
+ if (caching && cache[url]) {
51
50
  fetchedData = cache[url];
52
- } else if (errorCache[url]) {
51
+ } else if (caching && errorCache[url]) {
53
52
  /**
54
53
  * This logic is intended to stop multiple requests being made in succession
55
54
  * that all fail. Presently, this will only allow the first request to be
@@ -60,24 +59,25 @@ const useGetRequest = url => {
60
59
  throw errorCache[url];
61
60
  } else {
62
61
  const response = await axiosInstance.get(url, {
63
- cancelToken: cancelToken.token
62
+ cancelToken: cancelTokenRef.current.token
64
63
  }).catch(e => {
64
+ setError(e);
65
65
  throw e;
66
66
  });
67
67
  fetchedData = response.data;
68
- cache[url] = fetchedData;
68
+ if (caching) cache[url] = fetchedData;
69
69
  }
70
70
  setData(fetchedData);
71
71
  setStatus(STATUS_FETCHED);
72
72
  } catch (e) {
73
- errorCache[url] = e;
73
+ if (caching) errorCache[url] = e;
74
74
  setError(e);
75
75
  setData(null);
76
76
  setStatus(STATUS_ERROR);
77
77
  }
78
78
  };
79
79
  fetchData();
80
- }, [axiosInstance, url, cancelToken.token]);
80
+ }, [axiosInstance, url, caching]);
81
81
  return {
82
82
  status,
83
83
  error,
@@ -28,13 +28,14 @@ const getRefDataUrl = component => {
28
28
  return undefined;
29
29
  };
30
30
  const useRefData = (component, formData) => {
31
+ var _component$data;
31
32
  const url = getRefDataUrl(_objectSpread(_objectSpread({}, component), {}, {
32
33
  formData
33
34
  }));
34
35
  const {
35
36
  status: _status,
36
37
  data: _data
37
- } = (0, _useGetRequest.default)(url);
38
+ } = (0, _useGetRequest.default)(url, component === null || component === void 0 || (_component$data = component.data) === null || _component$data === void 0 ? void 0 : _component$data.useCache);
38
39
  const [data, setData] = (0, _react.useState)([]);
39
40
  const [status, setStatus] = (0, _react.useState)(STATUS_LOADING);
40
41
  (0, _react.useEffect)(() => {
@@ -52,7 +53,7 @@ const useRefData = (component, formData) => {
52
53
  setData([]);
53
54
  setStatus(STATUS_COMPLETE);
54
55
  }
55
- }, [component, _status, _data, url, setData, setStatus]);
56
+ }, [component.id, _status, _data, url]);
56
57
  return {
57
58
  data,
58
59
  status
@@ -23,7 +23,8 @@ describe('utils.Component.get', () => {
23
23
  fieldId: FIELD_ID,
24
24
  label: LABEL,
25
25
  onChange: ON_CHANGE,
26
- 'data-testid': ID
26
+ 'data-testid': ID,
27
+ allowedTypes: []
27
28
  };
28
29
  const {
29
30
  container
@@ -75,7 +75,8 @@ const setupDefaultObjectValue = (defaultObject, data) => {
75
75
  // defaultObject.sourced is an object with values that should
76
76
  // be used as field names to get values from data.
77
77
  if (defaultObj.sourced) {
78
- result = result !== null && result !== void 0 ? result : {};
78
+ var _result;
79
+ result = (_result = result) !== null && _result !== void 0 ? _result : {};
79
80
  Object.keys(defaultObj.sourced).every(key => {
80
81
  const sourcedValue = typeof defaultObj.sourced[key] === 'string' && (0, _getSourceData.default)(data, defaultObj.sourced[key]);
81
82
  if (sourcedValue === undefined && defaultObj.skipIfSourceInvalid) {
@@ -20,6 +20,26 @@ const getComparisonValue = (condition, data) => {
20
20
  return condition.value;
21
21
  };
22
22
 
23
+ /**
24
+ * Processes a value based on its type.
25
+ * - If it's an array, returns its length.
26
+ * - If it's a string, parses it as a float.
27
+ * - If it's a number, returns it as is.
28
+ * - Returns `undefined` for other types.
29
+ *
30
+ * @param {any} value - The value to process.
31
+ * @returns {number | undefined} - The processed value or `undefined` if not applicable.
32
+ */
33
+ const getProcessedValue = obj => {
34
+ if (Array.isArray(obj)) return obj.length;
35
+ if (typeof obj === "string") {
36
+ const cleaned = obj.replace(/,/g, ''); // Remove all commas
37
+ return Number.isNaN(cleaned) ? null : parseFloat(cleaned);
38
+ }
39
+ if (typeof obj === "number") return obj;
40
+ return null;
41
+ };
42
+
23
43
  /**
24
44
  * Looks at a condition object to see if the supplied value meets it.
25
45
  * The condition object contains an `op` property, along with a comparator
@@ -66,21 +86,15 @@ const meetsCondition = (condition, value, data) => {
66
86
  }
67
87
  case '<':
68
88
  {
69
- if (!value || !compare) {
70
- return false;
71
- }
72
- const valFloat = parseFloat(value.replace(',', ''));
73
- const compareFloat = parseFloat(compare.replace(',', ''));
74
- return valFloat < compareFloat;
89
+ const valFloat = getProcessedValue(value);
90
+ const compareFloat = getProcessedValue(compare);
91
+ return valFloat != null && compareFloat != null && valFloat < compareFloat;
75
92
  }
76
93
  case '>':
77
94
  {
78
- if (!value || !compare) {
79
- return false;
80
- }
81
- const valFloat = parseFloat(value.replace(/,/g, ''));
82
- const compareFloat = parseFloat(compare.replace(/,/g, ''));
83
- return valFloat > compareFloat;
95
+ const valFloat = getProcessedValue(value);
96
+ const compareFloat = getProcessedValue(compare);
97
+ return valFloat != null && compareFloat != null && valFloat > compareFloat;
84
98
  }
85
99
  case 'contains':
86
100
  {
@@ -551,6 +551,27 @@ describe('utils.Condition.meetsCondition', () => {
551
551
  };
552
552
  expect((0, _meetsCondition.default)(CONDITION, '10,000,000')).toBeTruthy();
553
553
  });
554
+ it('greater than should handle value is 0', () => {
555
+ const CONDITION = {
556
+ op: '>',
557
+ value: 0
558
+ };
559
+ expect((0, _meetsCondition.default)(CONDITION, 1)).toBeTruthy();
560
+ });
561
+ it('greater than should handle compear is 0', () => {
562
+ const CONDITION = {
563
+ op: '<',
564
+ value: 1
565
+ };
566
+ expect((0, _meetsCondition.default)(CONDITION, 0)).toBeTruthy();
567
+ });
568
+ it('greater than should handle array is 0', () => {
569
+ const CONDITION = {
570
+ op: '>',
571
+ value: 1
572
+ };
573
+ expect((0, _meetsCondition.default)(CONDITION, ["A", "B", "C"])).toBeTruthy();
574
+ });
554
575
  });
555
576
  describe('operator includes', () => {
556
577
  it('should accept a string that exists within the value', () => {