@ukhomeoffice/cop-react-form-renderer 6.15.13 → 6.16.0-alpha

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.
@@ -127,12 +127,16 @@ const CheckYourAnswers = _ref => {
127
127
  }
128
128
  })));
129
129
  };
130
+ const getDedupedErrors = () => {
131
+ const dedupedErrors = Array.from(new Set(errors.map(a => a.id))).map(id => errors.find(b => b.id === id));
132
+ return dedupedErrors;
133
+ };
130
134
  return /*#__PURE__*/_react.default.createElement("div", {
131
135
  className: DEFAULT_CLASS
132
136
  }, title && !hide_title && /*#__PURE__*/_react.default.createElement(_copReactComponents.LargeHeading, {
133
137
  key: "heading"
134
138
  }, title), errors && errors.length > 0 && /*#__PURE__*/_react.default.createElement(_copReactComponents.ErrorSummary, {
135
- errors: errors
139
+ errors: getDedupedErrors()
136
140
  }), type === _models.FormTypes.TASK_CYA && pages.length > 0 && sections && sections.map(section => section.tasks.filter(task => task.state !== _models.TaskStates.TYPES.SKIPPED).map((task, taskIndex) => {
137
141
  const filterPages = pages.filter(page => task.pages.some(p => p === page.name));
138
142
  return /*#__PURE__*/_react.default.createElement(_react.Fragment, {
@@ -72,6 +72,13 @@ const onPageAction = (action, patch, patchLabel, hooks, data, formState, validat
72
72
  break;
73
73
  case _models.PageAction.TYPES.COLLECTION_ADD:
74
74
  _utils.default.CollectionPage.addEntry(action.collection, form.page.formData);
75
+
76
+ // We need to delete the collection entry fields from formData as it will be holding previous values.
77
+ if (form.page.formData["".concat(action.collection)] && form.page.formData["".concat(action.collection)].length > 0) {
78
+ const lastIndex = form.page.formData["".concat(action.collection)].length - 1;
79
+ const fieldsToDelete = Object.keys(form.page.formData["".concat(action.collection)][lastIndex]).filter(k => k in form.page.formData && k !== 'id');
80
+ fieldsToDelete.forEach(i => delete form.page.formData[i]);
81
+ }
75
82
  pageUpdate = () => _handlers.default.navigate(action, pageId, onPageChange);
76
83
  break;
77
84
  case _models.PageAction.TYPES.COLLECTION_DUPLICATE:
@@ -86,6 +93,35 @@ const onPageAction = (action, patch, patchLabel, hooks, data, formState, validat
86
93
  if (action.recordRemoval) {
87
94
  form.page.formData["".concat(action.collection.split('.').pop(), "LastRemoved")] = _objectSpread({}, removedEntry);
88
95
  }
96
+
97
+ // The formData needs to be tidied up after removing the collection entry:
98
+
99
+ // 1. Retrieve the formData ID as it will be overwritten by a field with the same name in the collection entry.
100
+ const formDataId = form.page.formData.id;
101
+
102
+ // 2. Build the list of field names to delete and delete them from formData.
103
+ if (removedEntry !== null) {
104
+ const removedEntryFieldsToDelete = Object.keys(removedEntry).filter(k => k in form.page.formData);
105
+ removedEntryFieldsToDelete.forEach(i => delete form.page.formData[i]);
106
+ }
107
+
108
+ // 3. Retrieve the collection array from formData.
109
+ const collectionArray = form.page.formData[action.collection];
110
+
111
+ // 4. Assign the fields in the last collection entry to formData.
112
+ Object.assign(form.page.formData, collectionArray[collectionArray.length - 1]);
113
+
114
+ // 5. Assign the formData ID back to the formData.
115
+ form.page.formData.id = formDataId;
116
+
117
+ // 6. If the collection array has entries set the correct ActiveId
118
+ // otherwise delete it as well as the collection array.
119
+ if (collectionArray.length > 0) {
120
+ form.page.formData["".concat(action.collection, "ActiveId")] = collectionArray[collectionArray.length - 1].id;
121
+ } else {
122
+ delete form.page.formData["".concat(action.collection, "ActiveId")];
123
+ delete form.page.formData[action.collection];
124
+ }
89
125
  break;
90
126
  default:
91
127
  break;
@@ -380,6 +380,35 @@ describe('components.FormRenderer.onPageAction', () => {
380
380
  // that's called as part of the onSubmit hook's onSuccess callback.
381
381
  expect(_handlers.default.navigateCalls).toEqual(1);
382
382
  });
383
+ it("should clean previous collection entry fields at top level of formData", () => {
384
+ const FORM_STATE = {
385
+ page: {
386
+ formData: {
387
+ 'testCollection': [{
388
+ 'flightNumber': '1'
389
+ }],
390
+ 'flightNumber': '1'
391
+ }
392
+ }
393
+ };
394
+ const ACTION = {
395
+ type: _models.PageAction.TYPES.COLLECTION_ADD,
396
+ collection: 'testCollection'
397
+ };
398
+ const CUSTOM_ARGS = _objectSpread(_objectSpread({}, ARGS), {}, {
399
+ action: ACTION,
400
+ formState: FORM_STATE
401
+ });
402
+ _onPageAction.default.apply(void 0, Object.values(CUSTOM_ARGS));
403
+ preActionChecks();
404
+ expect(FORM_STATE.page.formData["".concat(ACTION.collection, "ActiveId")]).toBeDefined();
405
+ expect(FORM_STATE.page.formData.flightNumber).toBeUndefined();
406
+ postActionChecks(CUSTOM_ARGS);
407
+
408
+ // This action type also sets up a call to handlers.navigate
409
+ // that's called as part of the onSubmit hook's onSuccess callback.
410
+ expect(_handlers.default.navigateCalls).toEqual(1);
411
+ });
383
412
  describe("should handle the ".concat(_models.PageAction.TYPES.COLLECTION_DUPLICATE, " Page Action type"), () => {
384
413
  it("calling handlers.navigate if the duplication was successful", () => {
385
414
  const FORM_STATE = {
@@ -533,6 +562,90 @@ describe('components.FormRenderer.onPageAction', () => {
533
562
  expect(FORM_STATE.page.formData.testCollectionLastRemoved).toBeUndefined();
534
563
  postActionChecks(CUSTOM_ARGS);
535
564
 
565
+ // This action type uses the default onPageChange.
566
+ expect(onPageChangeCalls).toEqual(1);
567
+ });
568
+ it("should clean collection fields at top level of formData when entries remain", () => {
569
+ // After removing the active entry, the collection data is assigned to the
570
+ // top level of formData and the activeId is reset.
571
+ const FORM_STATE = {
572
+ page: {
573
+ formData: {
574
+ testCollectionActiveId: 'removeMe',
575
+ testCollection: [{
576
+ id: 'leaveMeAlone',
577
+ flightNumber: '1'
578
+ }, {
579
+ id: 'removeMe',
580
+ flightNumber: '2'
581
+ }],
582
+ flightNumber: '2',
583
+ testCollectionLastRemoved: 'previousOne'
584
+ }
585
+ }
586
+ };
587
+ const ACTION = {
588
+ type: _models.PageAction.TYPES.COLLECTION_REMOVE,
589
+ collection: 'testCollection',
590
+ recordRemoval: true
591
+ };
592
+ const CUSTOM_ARGS = _objectSpread(_objectSpread({}, ARGS), {}, {
593
+ action: ACTION,
594
+ formState: FORM_STATE
595
+ });
596
+ _onPageAction.default.apply(void 0, Object.values(CUSTOM_ARGS));
597
+ preActionChecks();
598
+ expect(FORM_STATE.page.formData.testCollection).toEqual([{
599
+ id: 'leaveMeAlone',
600
+ flightNumber: '1'
601
+ }]);
602
+ expect(FORM_STATE.page.formData.testCollectionLastRemoved).toEqual({
603
+ id: 'removeMe',
604
+ flightNumber: '2'
605
+ });
606
+ expect(FORM_STATE.page.formData.flightNumber).toEqual('1');
607
+ expect(FORM_STATE.page.formData.testCollectionActiveId).toEqual('leaveMeAlone');
608
+ postActionChecks(CUSTOM_ARGS);
609
+
610
+ // This action type uses the default onPageChange.
611
+ expect(onPageChangeCalls).toEqual(1);
612
+ });
613
+ it("should clean collection fields at top level of formData after last entry removed", () => {
614
+ // After removing the last active entry, the collection array and activeID are deleted,
615
+ // and the collection data is removed from top level of formData.
616
+ const FORM_STATE = {
617
+ page: {
618
+ formData: {
619
+ testCollectionActiveId: 'removeMe',
620
+ testCollection: [{
621
+ id: 'removeMe',
622
+ flightNumber: '1'
623
+ }],
624
+ flightNumber: '1',
625
+ testCollectionLastRemoved: 'previousOne'
626
+ }
627
+ }
628
+ };
629
+ const ACTION = {
630
+ type: _models.PageAction.TYPES.COLLECTION_REMOVE,
631
+ collection: 'testCollection',
632
+ recordRemoval: true
633
+ };
634
+ const CUSTOM_ARGS = _objectSpread(_objectSpread({}, ARGS), {}, {
635
+ action: ACTION,
636
+ formState: FORM_STATE
637
+ });
638
+ _onPageAction.default.apply(void 0, Object.values(CUSTOM_ARGS));
639
+ preActionChecks();
640
+ expect(FORM_STATE.page.formData.testCollection).toBeUndefined();
641
+ expect(FORM_STATE.page.formData.testCollectionLastRemoved).toEqual({
642
+ id: 'removeMe',
643
+ flightNumber: '1'
644
+ });
645
+ expect(FORM_STATE.page.formData.flightNumber).toBeUndefined();
646
+ expect(FORM_STATE.page.formData.testCollectionActiveId).toBeUndefined();
647
+ postActionChecks(CUSTOM_ARGS);
648
+
536
649
  // This action type uses the default onPageChange.
537
650
  expect(onPageChangeCalls).toEqual(1);
538
651
  });
@@ -18,6 +18,7 @@ var _validateRegex = _interopRequireDefault(require("./validateRegex"));
18
18
  var _validateRequired = _interopRequireDefault(require("./validateRequired"));
19
19
  var _validateTextArea = _interopRequireDefault(require("./validateTextArea"));
20
20
  var _validateTime = _interopRequireDefault(require("./validateTime"));
21
+ var _canOverrideFieldRequired = _interopRequireDefault(require("../canOverrideFieldRequired"));
21
22
  function _interopRequireDefault(e) { return e && e.__esModule ? e : { default: e }; }
22
23
  function ownKeys(e, r) { var t = Object.keys(e); if (Object.getOwnPropertySymbols) { var o = Object.getOwnPropertySymbols(e); r && (o = o.filter(function (r) { return Object.getOwnPropertyDescriptor(e, r).enumerable; })), t.push.apply(t, o); } return t; }
23
24
  function _objectSpread(e) { for (var r = 1; r < arguments.length; r++) { var t = null != arguments[r] ? arguments[r] : {}; r % 2 ? ownKeys(Object(t), !0).forEach(function (r) { _defineProperty(e, r, t[r]); }) : Object.getOwnPropertyDescriptors ? Object.defineProperties(e, Object.getOwnPropertyDescriptors(t)) : ownKeys(Object(t)).forEach(function (r) { Object.defineProperty(e, r, Object.getOwnPropertyDescriptor(t, r)); }); } return e; }
@@ -47,7 +48,9 @@ const validateComponent = (component, outerData, formData) => {
47
48
  value = (0, _getCollectionPageData.default)(component.collectionName, fd);
48
49
  }
49
50
  if (component.required) {
50
- error = (0, _validateRequired.default)(value, component.label, component.custom_errors);
51
+ if (!component.requiredOverrideConditions || !(0, _canOverrideFieldRequired.default)(component.requiredOverrideConditions, fd)) {
52
+ error = (0, _validateRequired.default)(value, component.label, component.custom_errors);
53
+ }
51
54
  }
52
55
  if (!error) {
53
56
  switch (component.type) {
@@ -0,0 +1,18 @@
1
+ "use strict";
2
+
3
+ Object.defineProperty(exports, "__esModule", {
4
+ value: true
5
+ });
6
+ exports.default = void 0;
7
+ /**
8
+ * Check to see if the field required status can be overridden.
9
+ * All conditions need to be met in order for the override to be permitted.
10
+ * @param {array} requiredOverrideConditions The conditions to evaluate.
11
+ * @param {object} data The form data to check.
12
+ * @returns true or false.
13
+ */
14
+ const canOverrideFieldRequired = (requiredOverrideConditions, data) => {
15
+ const conditionsNotMet = requiredOverrideConditions.filter(condition => data[condition.name] !== condition.value);
16
+ return conditionsNotMet.length === 0;
17
+ };
18
+ var _default = exports.default = canOverrideFieldRequired;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ukhomeoffice/cop-react-form-renderer",
3
- "version": "6.15.13",
3
+ "version": "6.16.0-alpha",
4
4
  "private": false,
5
5
  "scripts": {
6
6
  "clean": "rimraf dist",