@ukhomeoffice/cop-react-form-renderer 4.49.0 → 4.51.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.
@@ -283,7 +283,7 @@ var InternalFormRenderer = function InternalFormRenderer(_ref2) {
283
283
 
284
284
  var tasks = (data === null || data === void 0 ? void 0 : (_data$formStatus2 = data.formStatus) === null || _data$formStatus2 === void 0 ? void 0 : _data$formStatus2.tasks) || {};
285
285
 
286
- var updatedSections = _helpers.default.getUpdatedSectionStates(theSections, tasks, nonSequential);
286
+ var updatedSections = _helpers.default.getUpdatedSectionStates(theSections, tasks, nonSequential, data);
287
287
 
288
288
  setHubDetails(function (prev) {
289
289
  return _objectSpread(_objectSpread({}, prev), {}, {
@@ -5,8 +5,12 @@ Object.defineProperty(exports, "__esModule", {
5
5
  });
6
6
  exports.default = void 0;
7
7
 
8
+ var _utils = _interopRequireDefault(require("../../../utils"));
9
+
8
10
  var _models = require("../../../models");
9
11
 
12
+ function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
13
+
10
14
  function ownKeys(object, enumerableOnly) { var keys = Object.keys(object); if (Object.getOwnPropertySymbols) { var symbols = Object.getOwnPropertySymbols(object); enumerableOnly && (symbols = symbols.filter(function (sym) { return Object.getOwnPropertyDescriptor(object, sym).enumerable; })), keys.push.apply(keys, symbols); } return keys; }
11
15
 
12
16
  function _objectSpread(target) { for (var i = 1; i < arguments.length; i++) { var source = null != arguments[i] ? arguments[i] : {}; i % 2 ? ownKeys(Object(source), !0).forEach(function (key) { _defineProperty(target, key, source[key]); }) : Object.getOwnPropertyDescriptors ? Object.defineProperties(target, Object.getOwnPropertyDescriptors(source)) : ownKeys(Object(source)).forEach(function (key) { Object.defineProperty(target, key, Object.getOwnPropertyDescriptor(source, key)); }); } return target; }
@@ -93,28 +97,66 @@ var updateTasks = function updateTasks(section, sectionIndex, tasks, nonSequenti
93
97
  return _objectSpread({}, a);
94
98
  });
95
99
  clone.forEach(function (task, taskIndex, taskList) {
96
- task.state = updateTaskState(task, taskIndex, taskList, tasks, nonSequential, allowFirst);
100
+ if (section.skipped) {
101
+ task.state = _models.TaskStates.TYPES.SKIPPED;
102
+ } else {
103
+ task.state = updateTaskState(task, taskIndex, taskList, tasks, nonSequential, allowFirst);
104
+ }
97
105
  });
98
106
  return clone;
99
107
  };
108
+ /**
109
+ * Checks if the previous section has been completed. If the last section
110
+ * was skipped then this function will check the section before that until
111
+ * the state of a non-skipped section can be found.
112
+ * @param {array} sections - array of sections
113
+ * @param {number} currentIndex - index of the current section
114
+ * @returns {boolean} true if the last non-skipped section is complete, false
115
+ * if not.
116
+ */
117
+
118
+
119
+ var isPreviousSectionComplete = function isPreviousSectionComplete(sections, currentIndex) {
120
+ var _sections, _sections$tasks$slice;
121
+
122
+ var lastTaskState = (_sections = sections[currentIndex - 1]) === null || _sections === void 0 ? void 0 : (_sections$tasks$slice = _sections.tasks.slice(-1)[0]) === null || _sections$tasks$slice === void 0 ? void 0 : _sections$tasks$slice.state;
123
+
124
+ if (lastTaskState === _models.TaskStates.TYPES.COMPLETE) {
125
+ return true;
126
+ }
127
+
128
+ if (lastTaskState === _models.TaskStates.TYPES.SKIPPED) {
129
+ return currentIndex - 1 === 0 ? true : isPreviousSectionComplete(sections, currentIndex - 1);
130
+ }
131
+
132
+ return false;
133
+ };
100
134
  /**
101
135
  * update task states in sections
102
136
  * @param {object[]} sections - array of sections
103
137
  * @param {object} tasks - update task states
104
138
  * @param {boolean} nonSequential - true if tasks can be performed non-sequentially
139
+ * @param {object} data - current form data
105
140
  * @returns {object[]} - updated sections - note this is a clone
106
141
  */
107
142
 
108
143
 
109
144
  var getUpdatedSectionStates = function getUpdatedSectionStates(sections, tasks) {
110
145
  var nonSequential = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : false;
146
+ var data = arguments.length > 3 && arguments[3] !== undefined ? arguments[3] : null;
111
147
  var clone = sections.map(function (a) {
112
148
  return _objectSpread({}, a);
113
149
  });
114
150
  clone.forEach(function (section, sectionIndex, sectionList) {
115
- var _sectionList, _sectionList$tasks$sl;
151
+ var _section$show_when;
152
+
153
+ var allowFirst = sectionIndex === 0 || isPreviousSectionComplete(sectionList, sectionIndex); // Support a single show_when check on a section. Support for an
154
+ // array of checks will have to be added if required.
155
+
156
+ if (data && !_utils.default.Condition.met(section.show_when, data[(_section$show_when = section.show_when) === null || _section$show_when === void 0 ? void 0 : _section$show_when.field])) {
157
+ section.skipped = true;
158
+ }
116
159
 
117
- var allowFirst = sectionIndex === 0 || ((_sectionList = sectionList[sectionIndex - 1]) === null || _sectionList === void 0 ? void 0 : (_sectionList$tasks$sl = _sectionList.tasks.slice(-1)[0]) === null || _sectionList$tasks$sl === void 0 ? void 0 : _sectionList$tasks$sl.state) === _models.TaskStates.TYPES.COMPLETE;
118
160
  section.tasks = updateTasks(section, sectionIndex, tasks, nonSequential, allowFirst);
119
161
  });
120
162
  return clone;
@@ -114,6 +114,85 @@ describe('components.FormRenderer.helpers.getUpdatedSectionStates', function ()
114
114
  expect(updatedSections[0].tasks[0].state).toEqual(_models.TaskStates.TYPES.IN_PROGRESS);
115
115
  expect(updatedSections[0].tasks[1].state).toEqual(_models.TaskStates.TYPES.CANNOT_START_YET);
116
116
  });
117
+ it("should set the status of any tasks in a section with a failed show_when to '".concat(_models.TaskStates.TYPES.SKIPPED, "'"), function () {
118
+ var SECTIONS = [{
119
+ name: 'Add event details',
120
+ tasks: [{
121
+ name: 'Date, location and mode details',
122
+ pages: ['eventDate', 'eventMode']
123
+ }, {
124
+ name: 'Officer and agency details',
125
+ pages: ['officeDetails']
126
+ }]
127
+ }, {
128
+ name: 'Add people details',
129
+ show_when: {
130
+ field: 'field',
131
+ op: '=',
132
+ value: 'showMe'
133
+ },
134
+ tasks: [{
135
+ name: 'People details',
136
+ pages: ['firstName', 'surname']
137
+ }, {
138
+ name: 'Immigration details',
139
+ pages: ['immigrationDate']
140
+ }]
141
+ }];
142
+ var TASKS = {
143
+ 'Date, location and mode details': {
144
+ complete: true
145
+ },
146
+ 'Officer and agency details': {
147
+ complete: true
148
+ }
149
+ };
150
+ var DATA = {
151
+ field: 'hideMe'
152
+ };
153
+ var updatedSections = (0, _getUpdatedSectionStates.default)(SECTIONS, TASKS, false, DATA);
154
+ expect(updatedSections[0].tasks[0].state).toEqual(_models.TaskStates.TYPES.COMPLETE);
155
+ expect(updatedSections[0].tasks[1].state).toEqual(_models.TaskStates.TYPES.COMPLETE);
156
+ expect(updatedSections[1].skipped).toEqual(true);
157
+ expect(updatedSections[1].tasks[0].state).toEqual(_models.TaskStates.TYPES.SKIPPED);
158
+ expect(updatedSections[1].tasks[1].state).toEqual(_models.TaskStates.TYPES.SKIPPED);
159
+ });
160
+ });
161
+ it("should set the status of any tasks to '".concat(_models.TaskStates.TYPES.NOT_STARTED, "' if previous tasks have been skipped"), function () {
162
+ var SECTIONS = [{
163
+ name: 'Add event details',
164
+ show_when: {
165
+ field: 'field',
166
+ op: '=',
167
+ value: 'showMe'
168
+ },
169
+ tasks: [{
170
+ name: 'Date, location and mode details',
171
+ pages: ['eventDate', 'eventMode']
172
+ }, {
173
+ name: 'Officer and agency details',
174
+ pages: ['officeDetails']
175
+ }]
176
+ }, {
177
+ name: 'Add people details',
178
+ tasks: [{
179
+ name: 'People details',
180
+ pages: ['firstName', 'surname']
181
+ }, {
182
+ name: 'Immigration details',
183
+ pages: ['immigrationDate']
184
+ }]
185
+ }];
186
+ var TASKS = {};
187
+ var DATA = {
188
+ field: 'hideMe'
189
+ };
190
+ var updatedSections = (0, _getUpdatedSectionStates.default)(SECTIONS, TASKS, false, DATA);
191
+ expect(updatedSections[0].skipped).toEqual(true);
192
+ expect(updatedSections[0].tasks[0].state).toEqual(_models.TaskStates.TYPES.SKIPPED);
193
+ expect(updatedSections[0].tasks[1].state).toEqual(_models.TaskStates.TYPES.SKIPPED);
194
+ expect(updatedSections[1].tasks[0].state).toEqual(_models.TaskStates.TYPES.NOT_STARTED);
195
+ expect(updatedSections[1].tasks[1].state).toEqual(_models.TaskStates.TYPES.CANNOT_START_YET);
117
196
  });
118
197
  describe('non-sequential tasks', function () {
119
198
  var NON_SEQUENTIAL = true;
@@ -114,7 +114,9 @@ var TaskList = function TaskList(_ref) {
114
114
  fieldId: notesId,
115
115
  readOnly: true,
116
116
  value: _copReactComponents.Utils.interpolateString(notesText, attrs.formData)
117
- })), sections.map(function (section, index) {
117
+ })), sections.filter(function (section) {
118
+ return !section.skipped;
119
+ }).map(function (section, index) {
118
120
  return /*#__PURE__*/_react.default.createElement(_react.Fragment, {
119
121
  key: "".concat(section.name)
120
122
  }, /*#__PURE__*/_react.default.createElement("h2", {
@@ -13,11 +13,13 @@ var TYPE_COMPLETE = 'complete';
13
13
  var TYPE_IN_PROGRESS = 'inProgress';
14
14
  var TYPE_NOT_STARTED = 'notStarted';
15
15
  var TYPE_CANNOT_START_YET = 'cannotStartYet';
16
+ var TYPE_SKIPPED = 'skipped';
16
17
  var StateTypes = {
17
18
  COMPLETE: TYPE_COMPLETE,
18
19
  IN_PROGRESS: TYPE_IN_PROGRESS,
19
20
  NOT_STARTED: TYPE_NOT_STARTED,
20
- CANNOT_START_YET: TYPE_CANNOT_START_YET
21
+ CANNOT_START_YET: TYPE_CANNOT_START_YET,
22
+ SKIPPED: TYPE_SKIPPED
21
23
  };
22
24
  exports.StateTypes = StateTypes;
23
25
  var StateDetails = (_StateDetails = {}, _defineProperty(_StateDetails, TYPE_COMPLETE, {
@@ -32,6 +34,9 @@ var StateDetails = (_StateDetails = {}, _defineProperty(_StateDetails, TYPE_COMP
32
34
  }), _defineProperty(_StateDetails, TYPE_CANNOT_START_YET, {
33
35
  label: 'Cannot Start Yet',
34
36
  colour: 'dark-grey'
37
+ }), _defineProperty(_StateDetails, TYPE_SKIPPED, {
38
+ label: 'Skipped',
39
+ colour: 'dark-grey'
35
40
  }), _StateDetails);
36
41
  var TaskStates = {
37
42
  TYPES: StateTypes,
@@ -0,0 +1,39 @@
1
+ "use strict";
2
+
3
+ Object.defineProperty(exports, "__esModule", {
4
+ value: true
5
+ });
6
+ exports.default = void 0;
7
+
8
+ var _copReactComponents = require("@ukhomeoffice/cop-react-components");
9
+
10
+ var _getSourceData = _interopRequireDefault(require("../Data/getSourceData"));
11
+
12
+ function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
13
+
14
+ // Global imports.
15
+ // Local imports.
16
+ var getFirstOf = function getFirstOf(config, data) {
17
+ var fieldValue;
18
+
19
+ if (config !== null && config !== void 0 && config.fields) {
20
+ config.fields.find(function (field) {
21
+ var fieldPath = _copReactComponents.Utils.interpolateString(field, data);
22
+
23
+ var foundValue = (0, _getSourceData.default)(data, fieldPath);
24
+
25
+ if (foundValue) {
26
+ fieldValue = foundValue;
27
+ return true;
28
+ }
29
+
30
+ return false;
31
+ });
32
+ return fieldValue || '';
33
+ }
34
+
35
+ return (config === null || config === void 0 ? void 0 : config.value) || null;
36
+ };
37
+
38
+ var _default = getFirstOf;
39
+ exports.default = _default;
@@ -0,0 +1,78 @@
1
+ "use strict";
2
+
3
+ var _getFirstOf = _interopRequireDefault(require("./getFirstOf"));
4
+
5
+ function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
6
+
7
+ describe('Utils.Operate.addToFormData', function () {
8
+ var DATA = {
9
+ name: 'sam',
10
+ id: 1,
11
+ test: ['2', '3'],
12
+ idName: 'passport',
13
+ passportNumber: 123,
14
+ otherIdDoc: '',
15
+ anotherMadeUpId: 789
16
+ };
17
+ it('Should handle interpolated field strings', function () {
18
+ // eslint-disable-next-line no-template-curly-in-string
19
+ var CONFIG = {
20
+ fields: ['test[${id}]']
21
+ };
22
+ var result = (0, _getFirstOf.default)(CONFIG, DATA);
23
+ expect(result).toEqual(DATA.test[1]);
24
+ });
25
+ it('Should return the value provided in config if no field is specified', function () {
26
+ var CONFIG = {
27
+ value: '2'
28
+ };
29
+ var result = (0, _getFirstOf.default)(CONFIG, DATA);
30
+ expect(result).toEqual(CONFIG.value);
31
+ });
32
+ it('Should return the value of the passportNumber field given in config', function () {
33
+ var CONFIG = {
34
+ fields: ['passportNumber', 'anotherMadeUpId', 'otherIdDoc']
35
+ };
36
+ var result = (0, _getFirstOf.default)(CONFIG, DATA);
37
+ expect(result).toEqual(DATA.passportNumber);
38
+ });
39
+ it('Should return the value of the anotherMadeUpId field given in config', function () {
40
+ var CONFIG = {
41
+ fields: ['anotherMadeUpId', 'passportNumber', 'otherIdDoc']
42
+ };
43
+ var result = (0, _getFirstOf.default)(CONFIG, DATA);
44
+ expect(result).toEqual(DATA.anotherMadeUpId);
45
+ });
46
+ it('Should return the value of the otherIdDoc field given in config', function () {
47
+ var CONFIG = {
48
+ fields: ['otherIdDoc', 'passportNumber', 'anotherMadeUpId']
49
+ };
50
+ var result = (0, _getFirstOf.default)(CONFIG, DATA);
51
+ expect(result).toEqual(DATA.passportNumber);
52
+ });
53
+ it('Should return the value of the otherIdDoc field given in config, if it exists, otherwise next field next in the array', function () {
54
+ var CONFIG = {
55
+ fields: ['otherIdDoc', 'passportNumber', 'anotherMadeUpId']
56
+ };
57
+ var result = (0, _getFirstOf.default)(CONFIG, DATA);
58
+ expect(result).toEqual(DATA.passportNumber);
59
+ });
60
+ it('Should return no value if elements in config.fields are not found in data', function () {
61
+ var CONFIG = {
62
+ fields: ['otherIdDoc1', 'passportNumber1', 'anotherMadeUpId1']
63
+ };
64
+ var result = (0, _getFirstOf.default)(CONFIG, DATA);
65
+ expect(result).toEqual('');
66
+ });
67
+ it('Should return null when an invalid config is used', function () {
68
+ var result = (0, _getFirstOf.default)(null, DATA);
69
+ expect(result).toEqual(null);
70
+ });
71
+ it('Should return null when invalid data is used', function () {
72
+ var CONFIG = {
73
+ field: 'a'
74
+ };
75
+ var result = (0, _getFirstOf.default)(CONFIG, null);
76
+ expect(result).toEqual(null);
77
+ });
78
+ });
@@ -19,6 +19,8 @@ var _shouldRun = _interopRequireDefault(require("./shouldRun"));
19
19
 
20
20
  var _setDataItem = _interopRequireDefault(require("../Data/setDataItem"));
21
21
 
22
+ var _getFirstOf = _interopRequireDefault(require("./getFirstOf"));
23
+
22
24
  function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
23
25
 
24
26
  // Global imports.
@@ -27,7 +29,8 @@ var functions = {
27
29
  checkValueIsTruthy: _checkValueIsTruthy.default,
28
30
  getIndexOfMatchingValueIn: _getIndexOfMatchingValueIn.default,
29
31
  persistValueInFormData: _persistValueInFormData.default,
30
- setValueInFormData: _setValueInFormData.default
32
+ setValueInFormData: _setValueInFormData.default,
33
+ getFirstOf: _getFirstOf.default
31
34
  };
32
35
 
33
36
  var doOperation = function doOperation(config, data, onChange) {
@@ -39,7 +39,7 @@ var validatePage = function validatePage(page) {
39
39
  }
40
40
  }
41
41
 
42
- if ((0, _showFormPage.default)(page, page.formData) && Array.isArray(page.components)) {
42
+ if ((0, _showFormPage.default)(page, data) && Array.isArray(page.components)) {
43
43
  var errs = page.components.reduce(function (errors, component) {
44
44
  var componentErrors = (0, _validateComponent.default)(component, data, data);
45
45
  return errors.concat(componentErrors).flat().map(function (err) {
@@ -73,6 +73,35 @@ describe('utils.Validate.Page', function () {
73
73
  error: 'Echo is required'
74
74
  });
75
75
  });
76
+ it('should return an error for each required component on a collection page', function () {
77
+ var COMPONENTS = [setup('a', _models.ComponentTypes.TEXT, 'Alpha', true), setup('b', _models.ComponentTypes.TEXT, 'Bravo', true), setup('c', _models.ComponentTypes.TEXT, 'Charlie', false), // The only unrequired one
78
+ setup('d', _models.ComponentTypes.TEXT, 'Delta', true), setup('e', _models.ComponentTypes.TEXT, 'Echo', true)];
79
+ var PAGE = {
80
+ components: COMPONENTS,
81
+ collection: {
82
+ name: 'testCollection'
83
+ },
84
+ formData: null
85
+ };
86
+ var RESULT = (0, _validatePage.default)(PAGE);
87
+ expect(RESULT.length).toEqual(4);
88
+ expect(RESULT[0]).toEqual({
89
+ id: 'a',
90
+ error: 'Alpha is required'
91
+ });
92
+ expect(RESULT[1]).toEqual({
93
+ id: 'b',
94
+ error: 'Bravo is required'
95
+ });
96
+ expect(RESULT[2]).toEqual({
97
+ id: 'd',
98
+ error: 'Delta is required'
99
+ });
100
+ expect(RESULT[3]).toEqual({
101
+ id: 'e',
102
+ error: 'Echo is required'
103
+ });
104
+ });
76
105
  it('should return an error for each required component with interpolated label', function () {
77
106
  var COMPONENTS = [// eslint-disable-next-line no-template-curly-in-string
78
107
  setup('a', _models.ComponentTypes.TEXT, 'Alpha ${tiger}', true), // eslint-disable-next-line no-template-curly-in-string
@@ -291,6 +320,70 @@ describe('utils.Validate.Page', function () {
291
320
  };
292
321
  expect((0, _validatePage.default)(PAGE).length).toEqual(0);
293
322
  });
323
+ it('should return an error for each required component on a collection page with a show_when that is true', function () {
324
+ var COMPONENTS = [setup('a', _models.ComponentTypes.TEXT, 'Alpha', true), setup('b', _models.ComponentTypes.TEXT, 'Bravo', true), setup('c', _models.ComponentTypes.TEXT, 'Charlie', false), // The only unrequired one
325
+ setup('d', _models.ComponentTypes.TEXT, 'Delta', true), setup('e', _models.ComponentTypes.TEXT, 'Echo', true)];
326
+ var PAGE = {
327
+ components: COMPONENTS,
328
+ collection: {
329
+ name: 'testCollection'
330
+ },
331
+ show_when: {
332
+ field: 'isShown',
333
+ op: '=',
334
+ value: true
335
+ },
336
+ formData: {
337
+ testCollectionActiveId: 123,
338
+ testCollection: [{
339
+ id: 123,
340
+ isShown: true
341
+ }]
342
+ }
343
+ };
344
+ var RESULT = (0, _validatePage.default)(PAGE);
345
+ expect(RESULT.length).toEqual(4);
346
+ expect(RESULT[0]).toEqual({
347
+ id: 'a',
348
+ error: 'Alpha is required'
349
+ });
350
+ expect(RESULT[1]).toEqual({
351
+ id: 'b',
352
+ error: 'Bravo is required'
353
+ });
354
+ expect(RESULT[2]).toEqual({
355
+ id: 'd',
356
+ error: 'Delta is required'
357
+ });
358
+ expect(RESULT[3]).toEqual({
359
+ id: 'e',
360
+ error: 'Echo is required'
361
+ });
362
+ });
363
+ it('should return no errors on a collection page with a show_when that is false', function () {
364
+ var COMPONENTS = [setup('a', _models.ComponentTypes.TEXT, 'Alpha', true), setup('b', _models.ComponentTypes.TEXT, 'Bravo', true), setup('c', _models.ComponentTypes.TEXT, 'Charlie', false), // The only unrequired one
365
+ setup('d', _models.ComponentTypes.TEXT, 'Delta', true), setup('e', _models.ComponentTypes.TEXT, 'Echo', true)];
366
+ var PAGE = {
367
+ components: COMPONENTS,
368
+ collection: {
369
+ name: 'testCollection'
370
+ },
371
+ show_when: {
372
+ field: 'isShown',
373
+ op: '=',
374
+ value: true
375
+ },
376
+ formData: {
377
+ testCollectionActiveId: 123,
378
+ testCollection: [{
379
+ id: 123,
380
+ isShown: false
381
+ }]
382
+ }
383
+ };
384
+ var RESULT = (0, _validatePage.default)(PAGE);
385
+ expect(RESULT.length).toEqual(0);
386
+ });
294
387
  });
295
388
  describe('when the form data has one field missing and includes an invalid email', function () {
296
389
  var _DATA2;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ukhomeoffice/cop-react-form-renderer",
3
- "version": "4.49.0",
3
+ "version": "4.51.1",
4
4
  "private": false,
5
5
  "scripts": {
6
6
  "clean": "rimraf dist",
@@ -97,4 +97,4 @@
97
97
  "last 1 safari version"
98
98
  ]
99
99
  }
100
- }
100
+ }