@ukhomeoffice/cop-react-form-renderer 6.0.6-peter → 6.6.1

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 (80) hide show
  1. package/dist/components/CheckYourAnswers/CheckYourAnswers.scss +2 -2
  2. package/dist/components/CollectionPage/CollectionPage.js +8 -2
  3. package/dist/components/CollectionSummary/BannerStrip.js +3 -2
  4. package/dist/components/CollectionSummary/BannerStrip.scss +2 -2
  5. package/dist/components/CollectionSummary/BannerStrip.test.js +39 -4
  6. package/dist/components/CollectionSummary/CollectionSummary.js +82 -63
  7. package/dist/components/CollectionSummary/CollectionSummary.scss +1 -1
  8. package/dist/components/CollectionSummary/CollectionSummary.test.js +40 -80
  9. package/dist/components/CollectionSummary/Confirmation.scss +1 -1
  10. package/dist/components/CollectionSummary/RenderListView.js +23 -19
  11. package/dist/components/CollectionSummary/RenderListView.scss +11 -2
  12. package/dist/components/CollectionSummary/RenderListView.test.js +14 -4
  13. package/dist/components/CollectionSummary/SummaryCard.js +61 -40
  14. package/dist/components/CollectionSummary/SummaryCard.scss +2 -1
  15. package/dist/components/CollectionSummary/SummaryCard.test.js +193 -150
  16. package/dist/components/CollectionSummary/SummaryCardDetails.js +70 -13
  17. package/dist/components/CollectionSummary/SummaryCardDetails.scss +45 -8
  18. package/dist/components/CollectionSummary/SummaryCardDetails.test.js +174 -26
  19. package/dist/components/CollectionSummary/SummaryCardValidationContext.js +15 -5
  20. package/dist/components/CollectionSummary/SummaryCardValidationContext.test.js +5 -4
  21. package/dist/components/FormComponent/Collection.js +24 -17
  22. package/dist/components/FormComponent/Collection.test.js +138 -0
  23. package/dist/components/FormComponent/FormComponent.js +12 -0
  24. package/dist/components/FormPage/FormPage.scss +1 -1
  25. package/dist/components/FormRenderer/FormRenderer.js +7 -4
  26. package/dist/components/FormRenderer/FormRenderer.scss +1 -1
  27. package/dist/components/FormRenderer/helpers/index.js +1 -3
  28. package/dist/components/FormRenderer/onPageAction.js +7 -9
  29. package/dist/components/FormRenderer/onPageAction.test.js +18 -9
  30. package/dist/components/SummaryList/SummaryList.scss +2 -2
  31. package/dist/components/TaskList/TaskList.scss +1 -1
  32. package/dist/context/ValidationContext/ValidationContext.js +49 -5
  33. package/dist/context/ValidationContext/ValidationContext.test.js +16 -7
  34. package/dist/hooks/useRefData.js +1 -1
  35. package/dist/utils/CheckYourAnswers/showComponentCYA.js +1 -2
  36. package/dist/utils/CheckYourAnswers/showComponentCYA.test.js +5 -0
  37. package/dist/utils/CollectionPage/addCollectionPageEntry.js +1 -2
  38. package/dist/utils/CollectionPage/addCollectionPageEntry.test.js +4 -24
  39. package/dist/utils/CollectionPage/duplicateCollectionPageEntry.js +22 -2
  40. package/dist/utils/CollectionPage/duplicateCollectionPageEntry.test.js +39 -4
  41. package/dist/utils/CollectionPage/getErrorsForCollection.js +55 -0
  42. package/dist/utils/CollectionPage/getErrorsForCollection.test.js +155 -0
  43. package/dist/utils/CollectionPage/getQuickEditPage.js +14 -5
  44. package/dist/utils/CollectionPage/getQuickEditPage.test.js +14 -29
  45. package/dist/utils/CollectionPage/index.js +2 -0
  46. package/dist/utils/CollectionPage/mergeCollectionPages.js +0 -1
  47. package/dist/utils/CollectionPage/setCollectionPageData.js +9 -4
  48. package/dist/utils/CollectionPage/setCollectionPageData.test.js +18 -0
  49. package/dist/utils/Component/isEditable.js +1 -1
  50. package/dist/utils/Condition/meetsCondition.js +18 -0
  51. package/dist/utils/Condition/meetsCondition.test.js +100 -0
  52. package/dist/utils/Data/getOptions.js +10 -0
  53. package/dist/utils/Data/getOptions.test.js +73 -0
  54. package/dist/utils/Data/nestInRefdataOptions.js +49 -0
  55. package/dist/utils/Data/nestInRefdataOptions.test.js +236 -0
  56. package/dist/utils/Validate/additional/mustBeUniqueInCollection.js +4 -0
  57. package/dist/utils/Validate/additional/mustBeUniqueInCollection.test.js +36 -0
  58. package/dist/utils/Validate/validateContainer.js +3 -1
  59. package/dist/utils/Validate/validateContainer.test.js +33 -0
  60. package/dist/utils/Validate/validateEmail.js +1 -1
  61. package/dist/utils/Validate/validatePage.js +10 -1
  62. package/dist/utils/Validate/validatePage.test.js +69 -0
  63. package/package.json +4 -4
  64. package/dist/components/FormRenderer/clear-uncompleted-routes/test-data/component-used-in-multiple-pages-data.json +0 -4
  65. package/dist/components/FormRenderer/clear-uncompleted-routes/test-data/component-used-in-multiple-pages-form.json +0 -61
  66. package/dist/components/FormRenderer/clear-uncompleted-routes/test-data/data-with-collection-data-removed.json +0 -4
  67. package/dist/components/FormRenderer/clear-uncompleted-routes/test-data/data-with-collections.json +0 -8
  68. package/dist/components/FormRenderer/clear-uncompleted-routes/test-data/data-with-components-removed.json +0 -3
  69. package/dist/components/FormRenderer/clear-uncompleted-routes/test-data/data-with-components.json +0 -5
  70. package/dist/components/FormRenderer/clear-uncompleted-routes/test-data/data-with-entire-collection-removed.json +0 -3
  71. package/dist/components/FormRenderer/clear-uncompleted-routes/test-data/data-with-nested-component-removed.json +0 -10
  72. package/dist/components/FormRenderer/clear-uncompleted-routes/test-data/data-with-nested-components.json +0 -11
  73. package/dist/components/FormRenderer/clear-uncompleted-routes/test-data/form-for-nested-components.json +0 -96
  74. package/dist/components/FormRenderer/clear-uncompleted-routes/test-data/form-with-collections-delete-entire.json +0 -47
  75. package/dist/components/FormRenderer/clear-uncompleted-routes/test-data/form-with-collections.json +0 -46
  76. package/dist/components/FormRenderer/clear-uncompleted-routes/test-data/form-with-components.json +0 -48
  77. package/dist/components/FormRenderer/helpers/clearOutUncompletedRoutes.js +0 -175
  78. package/dist/components/FormRenderer/helpers/clearOutUncompletedRoutes.test.js +0 -113
  79. package/dist/components/FormRenderer/helpers/deleteNodeByPath.js +0 -20
  80. package/dist/components/FormRenderer/helpers/deleteNodeByPath.test.js +0 -56
@@ -90,22 +90,6 @@ var Collection = function Collection(_ref) {
90
90
  return _ref3.apply(this, arguments);
91
91
  };
92
92
  }();
93
- (0, _react.useEffect)(function () {
94
- if (config.focusOnAdd && Array.isArray(value) && value.length) {
95
- if (value.length > config.minimumEntries || !config.minimumEntries) {
96
- var _value2, _document$getElementB, _focusable$;
97
- var containerId = (_value2 = value[value.length - 1]) === null || _value2 === void 0 ? void 0 : _value2.id;
98
- var container = (_document$getElementB = document.getElementById(containerId)) === null || _document$getElementB === void 0 ? void 0 : _document$getElementB.childNodes[0];
99
- var focusable = container === null || container === void 0 ? void 0 : container.querySelectorAll('button, [href], input, select, textarea, [tabindex]:not([tabindex="-1"])');
100
- (_focusable$ = focusable[0]) === null || _focusable$ === void 0 || _focusable$.focus();
101
- }
102
- }
103
- }, [value.length, config.focusOnAdd, config.minimumEntries]);
104
- (0, _react.useEffect)(function () {
105
- if (config.minimumEntries && !value.length) {
106
- onAddAnother();
107
- }
108
- }, [config.minimumEntries, value.length]);
109
93
  var onRemoveItem = /*#__PURE__*/function () {
110
94
  var _ref4 = _asyncToGenerator( /*#__PURE__*/_regeneratorRuntime().mark(function _callee2(item) {
111
95
  return _regeneratorRuntime().wrap(function _callee2$(_context2) {
@@ -124,6 +108,28 @@ var Collection = function Collection(_ref) {
124
108
  return _ref4.apply(this, arguments);
125
109
  };
126
110
  }();
111
+ var getAddLabel = function getAddLabel(labels) {
112
+ if (labels.initial && !value.length) {
113
+ return labels.initial;
114
+ }
115
+ return labels.add;
116
+ };
117
+ (0, _react.useEffect)(function () {
118
+ if (config.focusOnAdd && Array.isArray(value) && value.length) {
119
+ if (value.length > config.minimumEntries || !config.minimumEntries) {
120
+ var _value2, _document$getElementB, _focusable$;
121
+ var containerId = (_value2 = value[value.length - 1]) === null || _value2 === void 0 ? void 0 : _value2.id;
122
+ var container = (_document$getElementB = document.getElementById(containerId)) === null || _document$getElementB === void 0 ? void 0 : _document$getElementB.childNodes[0];
123
+ var focusable = container === null || container === void 0 ? void 0 : container.querySelectorAll('button, [href], input, select, textarea, [tabindex]:not([tabindex="-1"])');
124
+ (_focusable$ = focusable[0]) === null || _focusable$ === void 0 || _focusable$.focus();
125
+ }
126
+ }
127
+ }, [value.length, config.focusOnAdd, config.minimumEntries]);
128
+ (0, _react.useEffect)(function () {
129
+ if (config.minimumEntries && !value.length) {
130
+ onAddAnother();
131
+ }
132
+ }, [config.minimumEntries, value.length]);
127
133
  var labels = _objectSpread(_objectSpread({}, _models.CollectionLabels), config.labels);
128
134
  var classes = _utils.default.classBuilder(DEFAULT_CLASS, [], config.className);
129
135
  return /*#__PURE__*/_react.default.createElement("div", {
@@ -203,7 +209,7 @@ var Collection = function Collection(_ref) {
203
209
  }), !config.disableAddAndRemove && /*#__PURE__*/_react.default.createElement(_copReactComponents.ButtonGroup, null, /*#__PURE__*/_react.default.createElement(_copReactComponents.Button, {
204
210
  onClick: onAddAnother,
205
211
  classModifiers: "secondary"
206
- }, labels.add)));
212
+ }, getAddLabel(labels))));
207
213
  };
208
214
  Collection.propTypes = {
209
215
  config: _propTypes.default.shape({
@@ -220,6 +226,7 @@ Collection.propTypes = {
220
226
  })),
221
227
  label: _propTypes.default.string,
222
228
  labels: _propTypes.default.shape({
229
+ initial: _propTypes.default.string,
223
230
  item: _propTypes.default.string,
224
231
  add: _propTypes.default.string,
225
232
  remove: _propTypes.default.string
@@ -964,4 +964,142 @@ describe('components.FormComponent.Collection', function () {
964
964
  }
965
965
  }, _callee18);
966
966
  })));
967
+ it('should display the initial cta add label and revert to default cta label on subsequent additions', /*#__PURE__*/_asyncToGenerator( /*#__PURE__*/_regeneratorRuntime().mark(function _callee20() {
968
+ var COLLECTION, container, c, addButton, item, label;
969
+ return _regeneratorRuntime().wrap(function _callee20$(_context20) {
970
+ while (1) switch (_context20.prev = _context20.next) {
971
+ case 0:
972
+ COLLECTION = {
973
+ id: ID,
974
+ fieldId: ID,
975
+ type: _models.ComponentTypes.COLLECTION,
976
+ item: [TEXT_COMPONENT],
977
+ // eslint-disable-next-line no-template-curly-in-string
978
+ labels: {
979
+ add: 'Add another item',
980
+ initial: 'Add an item',
981
+ item: 'Item ${index}'
982
+ }
983
+ };
984
+ container = document.createElement('div');
985
+ document.body.appendChild(container);
986
+ _context20.next = 5;
987
+ return (0, _testUtils.act)( /*#__PURE__*/_asyncToGenerator( /*#__PURE__*/_regeneratorRuntime().mark(function _callee19() {
988
+ return _regeneratorRuntime().wrap(function _callee19$(_context19) {
989
+ while (1) switch (_context19.prev = _context19.next) {
990
+ case 0:
991
+ (0, _setupTests.renderDomWithValidation)( /*#__PURE__*/_react2.default.createElement(_FormComponent.default, {
992
+ component: COLLECTION
993
+ }), container);
994
+ case 1:
995
+ case "end":
996
+ return _context19.stop();
997
+ }
998
+ }, _callee19);
999
+ })));
1000
+ case 5:
1001
+ // Check the container itself.
1002
+ c = container.childNodes[0];
1003
+ expect(c.tagName).toEqual('DIV');
1004
+ expect(c.classList).toContain(_Collection.DEFAULT_CLASS);
1005
+
1006
+ // And now make sure it has no children OTHER than the button to add an item.
1007
+ expect(c.childNodes.length).toEqual(1);
1008
+
1009
+ // Get hold of that "Add another" button and click it.
1010
+ addButton = c.childNodes[0].childNodes[0];
1011
+ expect(addButton.textContent).toContain('Add an item');
1012
+ _react.fireEvent.click(addButton, {});
1013
+
1014
+ // Make sure an item has been added.
1015
+ expect(c.childNodes.length).toEqual(2);
1016
+ item = c.childNodes[0];
1017
+ label = item.childNodes[0];
1018
+ expect(label.textContent).toContain(_utils.default.interpolateString(_models.CollectionLabels.item, {
1019
+ index: 1
1020
+ }));
1021
+
1022
+ // Check label of subsequent call to action button has updated
1023
+ expect(addButton.textContent).toContain('Add another item');
1024
+ case 17:
1025
+ case "end":
1026
+ return _context20.stop();
1027
+ }
1028
+ }, _callee20);
1029
+ })));
1030
+ it('should revert back to the initial cta add label on adding and removing an item', /*#__PURE__*/_asyncToGenerator( /*#__PURE__*/_regeneratorRuntime().mark(function _callee22() {
1031
+ var COLLECTION, container, c, addButton, item, label, removeButton;
1032
+ return _regeneratorRuntime().wrap(function _callee22$(_context22) {
1033
+ while (1) switch (_context22.prev = _context22.next) {
1034
+ case 0:
1035
+ COLLECTION = {
1036
+ id: ID,
1037
+ fieldId: ID,
1038
+ type: _models.ComponentTypes.COLLECTION,
1039
+ item: [TEXT_COMPONENT],
1040
+ // eslint-disable-next-line no-template-curly-in-string
1041
+ labels: {
1042
+ add: 'Add another item',
1043
+ initial: 'Add an item',
1044
+ item: 'Item ${index}'
1045
+ }
1046
+ };
1047
+ container = document.createElement('div');
1048
+ document.body.appendChild(container);
1049
+ _context22.next = 5;
1050
+ return (0, _testUtils.act)( /*#__PURE__*/_asyncToGenerator( /*#__PURE__*/_regeneratorRuntime().mark(function _callee21() {
1051
+ return _regeneratorRuntime().wrap(function _callee21$(_context21) {
1052
+ while (1) switch (_context21.prev = _context21.next) {
1053
+ case 0:
1054
+ (0, _setupTests.renderDomWithValidation)( /*#__PURE__*/_react2.default.createElement(_FormComponent.default, {
1055
+ component: COLLECTION
1056
+ }), container);
1057
+ case 1:
1058
+ case "end":
1059
+ return _context21.stop();
1060
+ }
1061
+ }, _callee21);
1062
+ })));
1063
+ case 5:
1064
+ // Check the container itself.
1065
+ c = container.childNodes[0];
1066
+ expect(c.tagName).toEqual('DIV');
1067
+ expect(c.classList).toContain(_Collection.DEFAULT_CLASS);
1068
+
1069
+ // And now make sure it has no children OTHER than the button to add an item.
1070
+ expect(c.childNodes.length).toEqual(1);
1071
+
1072
+ // Get hold of that "Add another" button and click it.
1073
+ addButton = c.childNodes[0].childNodes[0];
1074
+ expect(addButton.textContent).toContain('Add an item');
1075
+ _react.fireEvent.click(addButton, {});
1076
+
1077
+ // Make sure an item has been added.
1078
+ expect(c.childNodes.length).toEqual(2);
1079
+ item = c.childNodes[0];
1080
+ label = item.childNodes[0];
1081
+ expect(label.textContent).toContain(_utils.default.interpolateString(_models.CollectionLabels.item, {
1082
+ index: 1
1083
+ }));
1084
+
1085
+ // Check label of subsequent call to action button has updated
1086
+ expect(addButton.textContent).toContain('Add another item');
1087
+
1088
+ // Get hold of the newly-add item's "Remove" button.
1089
+ removeButton = label.childNodes[1];
1090
+ expect(removeButton.tagName).toEqual('BUTTON');
1091
+ expect(removeButton.classList).toContain('hods-button--secondary');
1092
+ expect(removeButton.textContent).toContain(_models.CollectionLabels.remove);
1093
+
1094
+ // Click the "Remove" button
1095
+ _react.fireEvent.click(removeButton, {});
1096
+
1097
+ // Check label of subsequent call to action button has updated
1098
+ expect(addButton.textContent).toContain('Add an item');
1099
+ case 23:
1100
+ case "end":
1101
+ return _context22.stop();
1102
+ }
1103
+ }, _callee22);
1104
+ })));
967
1105
  });
@@ -67,6 +67,17 @@ var FormComponent = function FormComponent(_ref) {
67
67
  // eslint-disable-next-line no-param-reassign
68
68
  target.value = target.value.trim();
69
69
  }
70
+ if (component.type === _models.ComponentTypes.CHECKBOXES && Array.isArray(target.value)) {
71
+ target.value.forEach(function (item) {
72
+ if (item.children && item.nested) {
73
+ // eslint-disable-next-line no-param-reassign
74
+ delete item.children;
75
+ // eslint-disable-next-line no-param-reassign
76
+ delete item.nested;
77
+ }
78
+ });
79
+ }
80
+
70
81
  // eslint-disable-next-line no-param-reassign
71
82
  target = (0, _helpers.addLabel)(target, component, data);
72
83
  onChange({
@@ -119,6 +130,7 @@ var FormComponent = function FormComponent(_ref) {
119
130
  formData: formData,
120
131
  onAction: onAction,
121
132
  onChange: onChange,
133
+ onTopLevelChange: onTopLevelChange,
122
134
  pages: pages
123
135
  })));
124
136
  }
@@ -1,4 +1,4 @@
1
- @import "node_modules/govuk-frontend/govuk/_base";
1
+ @import "govuk-frontend/dist/govuk/_base";
2
2
 
3
3
  .hods-form {
4
4
  &__page {
@@ -174,9 +174,9 @@ var InternalFormRenderer = function InternalFormRenderer(_ref2) {
174
174
  });
175
175
  });
176
176
  }
177
+ clearErrors();
177
178
  setGoingBack(true);
178
179
  hooks.onGoingBack(e.state ? e.state : null);
179
- clearErrors();
180
180
  if (components && pages && data && (_formState$page = formState.page) !== null && _formState$page !== void 0 && _formState$page.formData && pagePoint === undefined) {
181
181
  var submissionData = _utils.default.Format.form({
182
182
  pages: pages,
@@ -302,7 +302,7 @@ var InternalFormRenderer = function InternalFormRenderer(_ref2) {
302
302
  setData(newData);
303
303
  }
304
304
  }
305
- if (((_formState$page3 = formState.page) === null || _formState$page3 === void 0 ? void 0 : _formState$page3.type) === _models.FormPages.PARTIAL_CYA) {
305
+ if (((_formState$page3 = formState.page) === null || _formState$page3 === void 0 ? void 0 : _formState$page3.type) === _models.FormPages.PARTIAL_CYA && !cya.disableTaskSwitch) {
306
306
  hubDetails.sections.every(function (section) {
307
307
  return section.tasks.every(function (task) {
308
308
  if (task.pages.includes(page.pageId) && task.name !== currentTask.name) {
@@ -366,6 +366,7 @@ var InternalFormRenderer = function InternalFormRenderer(_ref2) {
366
366
  submitting: submitting
367
367
  }), formState.page && !formState.cya && formState.page.collection && /*#__PURE__*/_react.default.createElement(_CollectionPage.default, {
368
368
  page: formState.page,
369
+ pages: pages,
369
370
  onCollectionChange: onChange,
370
371
  onAction: function onAction(action, patch, patchLabel) {
371
372
  (0, _onPageAction.default)(action, patch, patchLabel, hooks, data, formState, validate, onPageChange, type, pages, components, pageId, setPagePoint, currentTask, setData, hubDetails, setSubmitted, addErrors, submitting, setSubmitting, errors);
@@ -382,7 +383,8 @@ var propTypes = {
382
383
  className: _propTypes.default.string,
383
384
  components: _propTypes.default.arrayOf(_propTypes.default.shape({})).isRequired,
384
385
  cya: _propTypes.default.shape({
385
- actions: _propTypes.default.arrayOf(_propTypes.default.oneOfType([_propTypes.default.shape({}), _propTypes.default.string]))
386
+ actions: _propTypes.default.arrayOf(_propTypes.default.oneOfType([_propTypes.default.shape({}), _propTypes.default.string])),
387
+ disableTaskSwitch: _propTypes.default.bool
386
388
  }),
387
389
  data: _propTypes.default.shape({
388
390
  formStatus: _propTypes.default.shape({
@@ -411,7 +413,8 @@ var defaultProps = {
411
413
  className: '',
412
414
  classModifiers: [],
413
415
  cya: {
414
- actions: []
416
+ actions: [],
417
+ disableTaskSwitch: false
415
418
  },
416
419
  data: null,
417
420
  hashLink: false,
@@ -1,4 +1,4 @@
1
- @import "node_modules/govuk-frontend/govuk/_base";
1
+ @import "govuk-frontend/dist/govuk/_base";
2
2
 
3
3
  .hods-form {
4
4
  display: block;
@@ -13,7 +13,6 @@ var _getNextPageId = _interopRequireDefault(require("./getNextPageId"));
13
13
  var _getPage = _interopRequireDefault(require("./getPage"));
14
14
  var _getSubmissionStatus = _interopRequireDefault(require("./getSubmissionStatus"));
15
15
  var _getUpdatedSectionStates = _interopRequireDefault(require("./getUpdatedSectionStates"));
16
- var _clearOutUncompletedRoutes = _interopRequireDefault(require("./clearOutUncompletedRoutes"));
17
16
  function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
18
17
  // Local imports
19
18
 
@@ -26,7 +25,6 @@ var helpers = {
26
25
  getNextPageId: _getNextPageId.default,
27
26
  getPage: _getPage.default,
28
27
  getSubmissionStatus: _getSubmissionStatus.default,
29
- getUpdatedSectionStates: _getUpdatedSectionStates.default,
30
- clearOutUncompletedRoutes: _clearOutUncompletedRoutes.default
28
+ getUpdatedSectionStates: _getUpdatedSectionStates.default
31
29
  };
32
30
  var _default = exports.default = helpers;
@@ -6,6 +6,7 @@ Object.defineProperty(exports, "__esModule", {
6
6
  exports.default = void 0;
7
7
  var _models = require("../../models");
8
8
  var _utils = _interopRequireDefault(require("../../utils"));
9
+ var _setCollectionPageData = _interopRequireDefault(require("../../utils/CollectionPage/setCollectionPageData"));
9
10
  var _handlers = _interopRequireDefault(require("./handlers"));
10
11
  var _helpers = _interopRequireDefault(require("./helpers"));
11
12
  function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
@@ -48,7 +49,11 @@ var onPageAction = function onPageAction(action, patch, patchLabel, hooks, data,
48
49
  if (action.addToFormData) {
49
50
  var operations = Array.isArray(action.addToFormData) ? action.addToFormData : [action.addToFormData];
50
51
  operations.forEach(function (op) {
51
- _utils.default.Data.setDataItem(formState.page.formData, op.field, op.value);
52
+ if (op.isCollection) {
53
+ (0, _setCollectionPageData.default)(op.field, op.value, formState.page.formData);
54
+ } else {
55
+ _utils.default.Data.setDataItem(formState.page.formData, op.field, op.value);
56
+ }
52
57
  });
53
58
  form.page.formData[action.addToFormData.field] = action.addToFormData.value;
54
59
  }
@@ -107,15 +112,8 @@ var onPageAction = function onPageAction(action, patch, patchLabel, hooks, data,
107
112
  setData(submissionData);
108
113
  }
109
114
  ;
110
- var formPagesAndComponents = {
111
- pages: pages,
112
- components: components
113
- };
114
- if (action.type === _models.PageAction.TYPES.SUBMIT) {
115
- _helpers.default.clearOutUncompletedRoutes(formPagesAndComponents, submissionData);
116
- }
117
115
 
118
- // In case of hub-and-spoke if patchLabel has changed then
116
+ // In case of hub-and-spoke if patchLabel has changed then
119
117
  // save name and value to variables for call to onSubmit hook
120
118
  var changedFieldName;
121
119
  var changedFieldValue;
@@ -50,11 +50,6 @@ jest.mock('./helpers', function () {
50
50
  this.cleanHiddenNestedDataCalls += 1;
51
51
  return patch;
52
52
  },
53
- clearOutUncompletedRoutesCalls: 0,
54
- clearOutUncompletedRoutes: function clearOutUncompletedRoutes(pages, allComponents, formData) {
55
- this.clearOutUncompletedRoutesCalls += 1;
56
- return formData;
57
- },
58
53
  getNextPageIdCalls: 0,
59
54
  getNextPageId: function getNextPageId() {
60
55
  this.getNextPageIdCalls += 1;
@@ -581,10 +576,16 @@ describe('components.FormRenderer.onPageAction', function () {
581
576
  var ACTION = {
582
577
  type: actionType,
583
578
  collection: 'testCollection',
584
- addToFormData: {
579
+ addToFormData: [{
585
580
  field: 'alpha',
586
581
  value: '123'
587
- }
582
+ }, {
583
+ field: 'parent.child',
584
+ value: [{
585
+ id: '12345'
586
+ }],
587
+ isCollection: true
588
+ }]
588
589
  };
589
590
  var CUSTOM_ARGS = _objectSpread(_objectSpread({}, ARGS), {}, {
590
591
  formState: FORM_STATE,
@@ -596,6 +597,7 @@ describe('components.FormRenderer.onPageAction', function () {
596
597
  expect(FORM_STATE.page.formData).toMatchObject({
597
598
  alpha: '123'
598
599
  });
600
+ expect(FORM_STATE.page.formData.parent[0].child[0].id).toEqual('12345');
599
601
  });
600
602
  });
601
603
  it("should work for the ".concat(_models.PageAction.TYPES.NAVIGATE, " action type"), function () {
@@ -609,10 +611,16 @@ describe('components.FormRenderer.onPageAction', function () {
609
611
  var ACTION = {
610
612
  type: _models.PageAction.TYPES.NAVIGATE,
611
613
  collection: 'testCollection',
612
- addToFormData: {
614
+ addToFormData: [{
613
615
  field: 'alpha',
614
616
  value: '123'
615
- }
617
+ }, {
618
+ field: 'parent.child',
619
+ value: [{
620
+ id: '12345'
621
+ }],
622
+ isCollection: true
623
+ }]
616
624
  };
617
625
  var CUSTOM_ARGS = _objectSpread(_objectSpread({}, ARGS), {}, {
618
626
  formState: FORM_STATE,
@@ -626,6 +634,7 @@ describe('components.FormRenderer.onPageAction', function () {
626
634
  expect(FORM_STATE.page.formData).toMatchObject({
627
635
  alpha: '123'
628
636
  });
637
+ expect(FORM_STATE.page.formData.parent[0].child[0].id).toEqual('12345');
629
638
  });
630
639
  it('should work for an array of formData', function () {
631
640
  var FORM_STATE = {
@@ -1,5 +1,5 @@
1
- @import 'node_modules/govuk-frontend/govuk/_base';
2
- @import 'node_modules/govuk-frontend/govuk/components/summary-list/_summary-list';
1
+ @import 'govuk-frontend/dist/govuk/_base';
2
+ @import 'govuk-frontend/dist/govuk/components/summary-list/_summary-list';
3
3
 
4
4
  .govuk-summary-list__title {
5
5
  width: 100%;
@@ -1,4 +1,4 @@
1
- @import 'node_modules/govuk-frontend/govuk/_base';
1
+ @import 'govuk-frontend/dist/govuk/_base';
2
2
 
3
3
  // Task list pattern
4
4
 
@@ -31,6 +31,45 @@ var ValidationContextProvider = function ValidationContextProvider(_ref) {
31
31
  _useState2 = _slicedToArray(_useState, 2),
32
32
  errors = _useState2[0],
33
33
  setErrors = _useState2[1];
34
+ var _useState3 = (0, _react.useState)([]),
35
+ _useState4 = _slicedToArray(_useState3, 2),
36
+ queuedErrors = _useState4[0],
37
+ setQueuedErrors = _useState4[1];
38
+
39
+ /**
40
+ * Queues errors to be displayed when the next validation takes place.
41
+ *
42
+ * @param {array} errors An array of errors to queue.
43
+ */
44
+ var enqueueErrors = function enqueueErrors(errors) {
45
+ setQueuedErrors(function (prev) {
46
+ return [].concat(prev, errors).filter(function (e) {
47
+ return !!e;
48
+ });
49
+ });
50
+ };
51
+
52
+ /**
53
+ * Removed errors from the queue if they match the given criteria.
54
+ *
55
+ * @param {function} matcher A function that should take an error and return true if that
56
+ * error should be removed from the queue.
57
+ */
58
+ var dequeueErrors = function dequeueErrors(matcher) {
59
+ if (typeof matcher !== 'function') {
60
+ return;
61
+ }
62
+ setQueuedErrors(function (prev) {
63
+ return prev.filter(function (e) {
64
+ return !matcher(e);
65
+ });
66
+ });
67
+ };
68
+ var clearQueuedErrors = function clearQueuedErrors() {
69
+ if (queuedErrors.length) {
70
+ setQueuedErrors([]);
71
+ }
72
+ };
34
73
  var addErrors = /*#__PURE__*/function () {
35
74
  var _ref2 = _asyncToGenerator( /*#__PURE__*/_regeneratorRuntime().mark(function _callee(errors) {
36
75
  return _regeneratorRuntime().wrap(function _callee$(_context) {
@@ -56,8 +95,9 @@ var ValidationContextProvider = function ValidationContextProvider(_ref) {
56
95
  return _regeneratorRuntime().wrap(function _callee2$(_context2) {
57
96
  while (1) switch (_context2.prev = _context2.next) {
58
97
  case 0:
98
+ setQueuedErrors([]);
59
99
  setErrors([]);
60
- case 1:
100
+ case 2:
61
101
  case "end":
62
102
  return _context2.stop();
63
103
  }
@@ -69,17 +109,17 @@ var ValidationContextProvider = function ValidationContextProvider(_ref) {
69
109
  }();
70
110
  var validate = {
71
111
  page: function page(_page) {
72
- var pageErrors = _utils.default.Validate.page(_page);
112
+ var pageErrors = _utils.default.Validate.page(_page, queuedErrors);
73
113
  var allErrors = hooks.onValidate(_page, pageErrors);
74
114
  setErrors(allErrors);
75
115
  return allErrors;
76
116
  },
77
117
  pages: function pages(_pages) {
78
- var allPagesErrors = _pages.map(function (page) {
79
- return _utils.default.Validate.page(page);
118
+ var pageErrors = _pages.map(function (page) {
119
+ return _utils.default.Validate.page(page, queuedErrors);
80
120
  });
81
121
  var allErrors = _pages.flatMap(function (page, index) {
82
- return hooks.onValidate(page, allPagesErrors[index]);
122
+ return hooks.onValidate(page, pageErrors[index]);
83
123
  });
84
124
  setErrors(allErrors);
85
125
  return allErrors;
@@ -90,6 +130,10 @@ var ValidationContextProvider = function ValidationContextProvider(_ref) {
90
130
  errors: errors,
91
131
  addErrors: addErrors,
92
132
  clearErrors: clearErrors,
133
+ queuedErrors: queuedErrors,
134
+ enqueueErrors: enqueueErrors,
135
+ dequeueErrors: dequeueErrors,
136
+ clearQueuedErrors: clearQueuedErrors,
93
137
  validate: validate
94
138
  }
95
139
  }, children);
@@ -6,8 +6,8 @@ var _copReactComponents = require("@ukhomeoffice/cop-react-components");
6
6
  var _react2 = _interopRequireWildcard(require("react"));
7
7
  var _propTypes = _interopRequireDefault(require("prop-types"));
8
8
  var _hooks = require("../../hooks");
9
- var _ValidationContext = _interopRequireDefault(require("./ValidationContext"));
10
9
  var _HooksContext = _interopRequireDefault(require("../HooksContext"));
10
+ var _ValidationContext = _interopRequireDefault(require("./ValidationContext"));
11
11
  function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
12
12
  function _getRequireWildcardCache(e) { if ("function" != typeof WeakMap) return null; var r = new WeakMap(), t = new WeakMap(); return (_getRequireWildcardCache = function _getRequireWildcardCache(e) { return e ? t : r; })(e); }
13
13
  function _interopRequireWildcard(e, r) { if (!r && e && e.__esModule) return e; if (null === e || "object" != _typeof(e) && "function" != typeof e) return { default: e }; var t = _getRequireWildcardCache(r); if (t && t.has(e)) return t.get(e); var n = { __proto__: null }, a = Object.defineProperty && Object.getOwnPropertyDescriptor; for (var u in e) if ("default" !== u && Object.prototype.hasOwnProperty.call(e, u)) { var i = a ? Object.getOwnPropertyDescriptor(e, u) : null; i && (i.get || i.set) ? Object.defineProperty(n, u, i) : n[u] = e[u]; } return n.default = e, t && t.set(e, n), n; }
@@ -20,6 +20,9 @@ var TestComponent = function TestComponent(_ref) {
20
20
  var _useValidation = (0, _hooks.useValidation)(),
21
21
  addErrors = _useValidation.addErrors,
22
22
  clearErrors = _useValidation.clearErrors,
23
+ enqueueErrors = _useValidation.enqueueErrors,
24
+ dequeueErrors = _useValidation.dequeueErrors,
25
+ clearQueuedErrors = _useValidation.clearQueuedErrors,
23
26
  errors = _useValidation.errors,
24
27
  validate = _useValidation.validate;
25
28
  (0, _react2.useEffect)(function () {
@@ -27,7 +30,7 @@ var TestComponent = function TestComponent(_ref) {
27
30
  addErrors(customErrors);
28
31
  }
29
32
  }, [customErrors]);
30
- return /*#__PURE__*/_react2.default.createElement(_react2.default.Fragment, null, typeof addErrors === 'function' && /*#__PURE__*/_react2.default.createElement("span", null, "addErrors is a function"), typeof clearErrors === 'function' && /*#__PURE__*/_react2.default.createElement("span", null, "clearErrors is a function"), typeof validate.page === 'function' && /*#__PURE__*/_react2.default.createElement("span", null, "validate.page is a function"), Array.isArray(errors) && /*#__PURE__*/_react2.default.createElement("span", null, "errors is an array of length ", errors.length), (errors === null || errors === void 0 ? void 0 : errors.length) > 0 && /*#__PURE__*/_react2.default.createElement(_copReactComponents.ErrorSummary, {
33
+ return /*#__PURE__*/_react2.default.createElement(_react2.default.Fragment, null, typeof addErrors === 'function' && /*#__PURE__*/_react2.default.createElement("span", null, "addErrors is a function"), typeof clearErrors === 'function' && /*#__PURE__*/_react2.default.createElement("span", null, "clearErrors is a function"), typeof enqueueErrors === 'function' && /*#__PURE__*/_react2.default.createElement("span", null, "enqueueErrors is a function"), typeof dequeueErrors === 'function' && /*#__PURE__*/_react2.default.createElement("span", null, "dequeueErrors is a function"), typeof clearQueuedErrors === 'function' && /*#__PURE__*/_react2.default.createElement("span", null, "clearQueuedErrors is a function"), typeof validate.page === 'function' && /*#__PURE__*/_react2.default.createElement("span", null, "validate.page is a function"), Array.isArray(errors) && /*#__PURE__*/_react2.default.createElement("span", null, "errors is an array of length ", errors.length), (errors === null || errors === void 0 ? void 0 : errors.length) > 0 && /*#__PURE__*/_react2.default.createElement(_copReactComponents.ErrorSummary, {
31
34
  errors: errors
32
35
  }));
33
36
  };
@@ -38,12 +41,15 @@ describe('context.ValidationContext', function () {
38
41
  while (1) switch (_context.prev = _context.next) {
39
42
  case 0:
40
43
  _render = (0, _react.render)( /*#__PURE__*/_react2.default.createElement(_HooksContext.default, null, /*#__PURE__*/_react2.default.createElement(_ValidationContext.default, null, /*#__PURE__*/_react2.default.createElement(TestComponent, null)))), container = _render.container;
41
- expect(container.childNodes.length).toEqual(4);
44
+ expect(container.childNodes.length).toEqual(7);
42
45
  expect(container.textContent).toContain('addErrors is a function');
43
46
  expect(container.textContent).toContain('clearErrors is a function');
47
+ expect(container.textContent).toContain('enqueueErrors is a function');
48
+ expect(container.textContent).toContain('dequeueErrors is a function');
49
+ expect(container.textContent).toContain('clearQueuedErrors is a function');
44
50
  expect(container.textContent).toContain('validate.page is a function');
45
51
  expect(container.textContent).toContain('errors is an array of length 0');
46
- case 6:
52
+ case 9:
47
53
  case "end":
48
54
  return _context.stop();
49
55
  }
@@ -61,16 +67,19 @@ describe('context.ValidationContext', function () {
61
67
  _render2 = (0, _react.render)( /*#__PURE__*/_react2.default.createElement(_HooksContext.default, null, /*#__PURE__*/_react2.default.createElement(_ValidationContext.default, null, /*#__PURE__*/_react2.default.createElement(TestComponent, {
62
68
  customErrors: CUSTOM_ERRORS
63
69
  })))), container = _render2.container;
64
- expect(container.childNodes.length).toEqual(5);
70
+ expect(container.childNodes.length).toEqual(8);
65
71
  expect(container.textContent).toContain('addErrors is a function');
66
72
  expect(container.textContent).toContain('clearErrors is a function');
73
+ expect(container.textContent).toContain('enqueueErrors is a function');
74
+ expect(container.textContent).toContain('dequeueErrors is a function');
75
+ expect(container.textContent).toContain('clearQueuedErrors is a function');
67
76
  expect(container.textContent).toContain('validate.page is a function');
68
77
  expect(container.textContent).toContain('errors is an array of length 1');
69
- errorSummary = container.childNodes[4];
78
+ errorSummary = container.childNodes[7];
70
79
  expect(errorSummary.tagName).toEqual('DIV');
71
80
  expect(errorSummary.classList).toContain('govuk-error-summary');
72
81
  expect(errorSummary.textContent).toContain(CUSTOM_ERRORS[0].error);
73
- case 11:
82
+ case 14:
74
83
  case "end":
75
84
  return _context2.stop();
76
85
  }
@@ -29,7 +29,7 @@ var STATUS_COMPLETE = exports.STATUS_COMPLETE = 'complete';
29
29
  var getRefDataUrl = function getRefDataUrl(component) {
30
30
  // eslint-disable-next-line prefer-destructuring
31
31
  var data = component.data;
32
- if (data && !data.options) {
32
+ if (data && !data.options || data !== null && data !== void 0 && data.url) {
33
33
  return _copReactComponents.Utils.interpolateString(data.url, component.formData);
34
34
  }
35
35
  return undefined;
@@ -9,10 +9,9 @@ var _Component = _interopRequireDefault(require("../Component"));
9
9
  var _Container = _interopRequireDefault(require("../Container"));
10
10
  var _getSourceData = _interopRequireDefault(require("../Data/getSourceData"));
11
11
  function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
12
- /* eslint-disable no-console */
13
12
  // Local imports
14
13
 
15
- var EXCLUDE_FROM_CYA = exports.EXCLUDE_FROM_CYA = [_models.ComponentTypes.ALERT, _models.ComponentTypes.COLLECTION_SUMMARY, _models.ComponentTypes.HEADING, _models.ComponentTypes.HTML, _models.ComponentTypes.INSET_TEXT];
14
+ var EXCLUDE_FROM_CYA = exports.EXCLUDE_FROM_CYA = [_models.ComponentTypes.ALERT, _models.ComponentTypes.COLLECTION_SUMMARY, _models.ComponentTypes.DETAILS, _models.ComponentTypes.HEADING, _models.ComponentTypes.HTML, _models.ComponentTypes.INSET_TEXT];
16
15
 
17
16
  /**
18
17
  * Determines whether a given component should display on the Check your answers screen.
@@ -31,6 +31,11 @@ describe('utils', function () {
31
31
  type: _models.ComponentTypes.INSET_TEXT
32
32
  }, null)).toBeFalsy();
33
33
  });
34
+ it('should not show when it is a details type', function () {
35
+ expect((0, _showComponentCYA.default)({
36
+ type: _models.ComponentTypes.DETAILS
37
+ }, null)).toBeFalsy();
38
+ });
34
39
  it('should not show when it hidden and disabled', function () {
35
40
  expect((0, _showComponentCYA.default)({
36
41
  hidden: true,
@@ -17,7 +17,6 @@ exports.default = void 0;
17
17
  */
18
18
  var addCollectionPageEntry = function addCollectionPageEntry(collectionName, formData) {
19
19
  var fieldName = collectionName.split('.').pop();
20
- formData["".concat(fieldName, "ActiveId")] = '0';
21
- formData["".concat(fieldName, "NumberedIndex")] = Number(formData["".concat(fieldName, "ActiveId")]) + 1;
20
+ formData["".concat(fieldName, "ActiveId")] = null;
22
21
  };
23
22
  var _default = exports.default = addCollectionPageEntry;