@ukhomeoffice/cop-react-form-renderer 3.0.3 → 3.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -67,15 +67,11 @@ var Collection = function Collection(_ref) {
67
67
  onChange = _ref.onChange,
68
68
  wrap = _ref.wrap;
69
69
 
70
- var _useState = (0, _react.useState)(),
70
+ var _useState = (0, _react.useState)(_value),
71
71
  _useState2 = _slicedToArray(_useState, 2),
72
72
  value = _useState2[0],
73
73
  setValue = _useState2[1];
74
74
 
75
- (0, _react.useEffect)(function () {
76
- setValue(_value || []);
77
- }, [_value, setValue]);
78
-
79
75
  var reportChange = function reportChange(newValue) {
80
76
  setValue(newValue);
81
77
 
@@ -0,0 +1,322 @@
1
+ "use strict";
2
+
3
+ var _react = require("@testing-library/react");
4
+
5
+ var _react2 = _interopRequireDefault(require("react"));
6
+
7
+ var _testUtils = require("react-dom/test-utils");
8
+
9
+ var _models = require("../../models");
10
+
11
+ var _setupTests = require("../../setupTests");
12
+
13
+ var _utils = _interopRequireDefault(require("../../utils"));
14
+
15
+ var _Collection = require("./Collection");
16
+
17
+ var _Container = require("./Container");
18
+
19
+ var _FormComponent = _interopRequireDefault(require("./FormComponent"));
20
+
21
+ function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
22
+
23
+ function _slicedToArray(arr, i) { return _arrayWithHoles(arr) || _iterableToArrayLimit(arr, i) || _unsupportedIterableToArray(arr, i) || _nonIterableRest(); }
24
+
25
+ function _nonIterableRest() { throw new TypeError("Invalid attempt to destructure non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method."); }
26
+
27
+ function _unsupportedIterableToArray(o, minLen) { if (!o) return; if (typeof o === "string") return _arrayLikeToArray(o, minLen); var n = Object.prototype.toString.call(o).slice(8, -1); if (n === "Object" && o.constructor) n = o.constructor.name; if (n === "Map" || n === "Set") return Array.from(o); if (n === "Arguments" || /^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(n)) return _arrayLikeToArray(o, minLen); }
28
+
29
+ function _arrayLikeToArray(arr, len) { if (len == null || len > arr.length) len = arr.length; for (var i = 0, arr2 = new Array(len); i < len; i++) { arr2[i] = arr[i]; } return arr2; }
30
+
31
+ function _iterableToArrayLimit(arr, i) { var _i = arr == null ? null : typeof Symbol !== "undefined" && arr[Symbol.iterator] || arr["@@iterator"]; if (_i == null) return; var _arr = []; var _n = true; var _d = false; var _s, _e; try { for (_i = _i.call(arr); !(_n = (_s = _i.next()).done); _n = true) { _arr.push(_s.value); if (i && _arr.length === i) break; } } catch (err) { _d = true; _e = err; } finally { try { if (!_n && _i["return"] != null) _i["return"](); } finally { if (_d) throw _e; } } return _arr; }
32
+
33
+ function _arrayWithHoles(arr) { if (Array.isArray(arr)) return arr; }
34
+
35
+ function asyncGeneratorStep(gen, resolve, reject, _next, _throw, key, arg) { try { var info = gen[key](arg); var value = info.value; } catch (error) { reject(error); return; } if (info.done) { resolve(value); } else { Promise.resolve(value).then(_next, _throw); } }
36
+
37
+ function _asyncToGenerator(fn) { return function () { var self = this, args = arguments; return new Promise(function (resolve, reject) { var gen = fn.apply(self, args); function _next(value) { asyncGeneratorStep(gen, resolve, reject, _next, _throw, "next", value); } function _throw(err) { asyncGeneratorStep(gen, resolve, reject, _next, _throw, "throw", err); } _next(undefined); }); }; }
38
+
39
+ function _defineProperty(obj, key, value) { if (key in obj) { Object.defineProperty(obj, key, { value: value, enumerable: true, configurable: true, writable: true }); } else { obj[key] = value; } return obj; }
40
+
41
+ describe('components.FormComponent.Collection', function () {
42
+ var ID = 'collection';
43
+ var TEXT_ID = 'text';
44
+ var TEXT_VALUE = 'alpha';
45
+
46
+ var FORM_DATA = _defineProperty({}, ID, [_defineProperty({
47
+ id: '1'
48
+ }, TEXT_ID, TEXT_VALUE)]);
49
+
50
+ var TEXT_COMPONENT = {
51
+ id: TEXT_ID,
52
+ fieldId: TEXT_ID,
53
+ type: _models.ComponentTypes.TEXT,
54
+ label: 'Text component',
55
+ hint: 'Text hint'
56
+ };
57
+ it('should render a collection component appropriately', /*#__PURE__*/_asyncToGenerator( /*#__PURE__*/regeneratorRuntime.mark(function _callee() {
58
+ var COLLECTION, _renderWithValidation, container, c, item, _item$childNodes, title, itemContainer, formGroup, label, hint, input;
59
+
60
+ return regeneratorRuntime.wrap(function _callee$(_context) {
61
+ while (1) {
62
+ switch (_context.prev = _context.next) {
63
+ case 0:
64
+ COLLECTION = {
65
+ id: ID,
66
+ fieldId: ID,
67
+ type: _models.ComponentTypes.COLLECTION,
68
+ item: [TEXT_COMPONENT]
69
+ };
70
+ _renderWithValidation = (0, _setupTests.renderWithValidation)( /*#__PURE__*/_react2.default.createElement(_FormComponent.default, {
71
+ component: COLLECTION,
72
+ value: FORM_DATA[ID],
73
+ formData: FORM_DATA
74
+ })), container = _renderWithValidation.container; // Check the container itself.
75
+
76
+ c = container.childNodes[0];
77
+ expect(c.tagName).toEqual('DIV');
78
+ expect(c.classList).toContain(_Collection.DEFAULT_CLASS); // And now check the single text component within it.
79
+
80
+ item = c.childNodes[0];
81
+ expect(item.tagName).toEqual('DIV');
82
+ expect(item.classList).toContain("".concat(_Collection.DEFAULT_CLASS, "__item"));
83
+ _item$childNodes = _slicedToArray(item.childNodes, 2), title = _item$childNodes[0], itemContainer = _item$childNodes[1];
84
+ expect(title.tagName).toEqual('LABEL');
85
+ expect(title.classList).toContain("".concat(_Collection.DEFAULT_CLASS, "__item-title"));
86
+ expect(itemContainer.tagName).toEqual('DIV');
87
+ expect(itemContainer.classList).toContain(_Container.DEFAULT_CLASS);
88
+ formGroup = itemContainer.childNodes[0];
89
+ expect(formGroup.tagName).toEqual('DIV');
90
+ expect(formGroup.classList).toContain('govuk-form-group');
91
+ label = formGroup.childNodes[0];
92
+ expect(label.tagName).toEqual('LABEL');
93
+ expect(label.classList).toContain('govuk-label');
94
+ expect(label.textContent).toEqual("".concat(TEXT_COMPONENT.label, " (optional)"));
95
+ expect(label.getAttribute('for')).toEqual("".concat(ID, "[0].").concat(TEXT_ID));
96
+ hint = formGroup.childNodes[1];
97
+ expect(hint.tagName).toEqual('SPAN');
98
+ expect(hint.classList).toContain('govuk-hint');
99
+ expect(hint.textContent).toEqual(TEXT_COMPONENT.hint);
100
+ input = formGroup.childNodes[2];
101
+ expect(input.tagName).toEqual('INPUT');
102
+ expect(input.classList).toContain('govuk-input');
103
+ expect(input.id).toEqual("".concat(ID, "[0].").concat(TEXT_ID));
104
+ expect(input.value).toEqual(TEXT_VALUE);
105
+
106
+ case 30:
107
+ case "end":
108
+ return _context.stop();
109
+ }
110
+ }
111
+ }, _callee);
112
+ })));
113
+ it('should handle a change to a component appropriately', /*#__PURE__*/_asyncToGenerator( /*#__PURE__*/regeneratorRuntime.mark(function _callee2() {
114
+ var ON_CHANGE_EVENTS, ON_CHANGE, COLLECTION, _renderWithValidation2, container, c, item, formGroup, input, NEW_TEXT_VALUE, EVENT;
115
+
116
+ return regeneratorRuntime.wrap(function _callee2$(_context2) {
117
+ while (1) {
118
+ switch (_context2.prev = _context2.next) {
119
+ case 0:
120
+ ON_CHANGE_EVENTS = [];
121
+
122
+ ON_CHANGE = function ON_CHANGE(e) {
123
+ ON_CHANGE_EVENTS.push(e);
124
+ };
125
+
126
+ COLLECTION = {
127
+ id: ID,
128
+ fieldId: ID,
129
+ type: _models.ComponentTypes.COLLECTION,
130
+ item: [{
131
+ id: TEXT_ID,
132
+ fieldId: TEXT_ID,
133
+ type: _models.ComponentTypes.TEXT,
134
+ label: 'Text component'
135
+ }]
136
+ };
137
+ _renderWithValidation2 = (0, _setupTests.renderWithValidation)( /*#__PURE__*/_react2.default.createElement(_FormComponent.default, {
138
+ component: COLLECTION,
139
+ value: FORM_DATA[ID],
140
+ formData: FORM_DATA,
141
+ onChange: ON_CHANGE
142
+ })), container = _renderWithValidation2.container; // Get hold of the text input.
143
+
144
+ c = container.childNodes[0];
145
+ item = c.childNodes[0];
146
+ formGroup = item.childNodes[1].childNodes[0];
147
+ input = formGroup.childNodes[2];
148
+ NEW_TEXT_VALUE = 'bravo';
149
+ EVENT = {
150
+ target: {
151
+ name: TEXT_ID,
152
+ value: NEW_TEXT_VALUE
153
+ }
154
+ };
155
+
156
+ _react.fireEvent.change(input, EVENT); // And confirm the formData has been changed.
157
+
158
+
159
+ expect(ON_CHANGE_EVENTS.length).toEqual(1);
160
+ expect(ON_CHANGE_EVENTS[0].target.name).toEqual(ID);
161
+ expect(ON_CHANGE_EVENTS[0].target.value[0][TEXT_ID]).toEqual(NEW_TEXT_VALUE);
162
+
163
+ case 14:
164
+ case "end":
165
+ return _context2.stop();
166
+ }
167
+ }
168
+ }, _callee2);
169
+ })));
170
+ it('should handle a null value appropriately', /*#__PURE__*/_asyncToGenerator( /*#__PURE__*/regeneratorRuntime.mark(function _callee3() {
171
+ var COLLECTION, _renderWithValidation3, container, c, buttonGroup, button;
172
+
173
+ return regeneratorRuntime.wrap(function _callee3$(_context3) {
174
+ while (1) {
175
+ switch (_context3.prev = _context3.next) {
176
+ case 0:
177
+ COLLECTION = {
178
+ id: ID,
179
+ fieldId: ID,
180
+ type: _models.ComponentTypes.COLLECTION,
181
+ item: [TEXT_COMPONENT]
182
+ };
183
+ _renderWithValidation3 = (0, _setupTests.renderWithValidation)( /*#__PURE__*/_react2.default.createElement(_FormComponent.default, {
184
+ component: COLLECTION
185
+ })), container = _renderWithValidation3.container; // Check the container itself.
186
+
187
+ c = container.childNodes[0];
188
+ expect(c.tagName).toEqual('DIV');
189
+ expect(c.classList).toContain(_Collection.DEFAULT_CLASS); // And now make sure it has no children OTHER than the button to add an item.
190
+
191
+ expect(c.childNodes.length).toEqual(1);
192
+ buttonGroup = c.childNodes[0];
193
+ expect(buttonGroup.tagName).toEqual('DIV');
194
+ expect(buttonGroup.classList).toContain('hods-button-group');
195
+ button = buttonGroup.childNodes[0];
196
+ expect(button.tagName).toEqual('BUTTON');
197
+ expect(button.classList).toContain('hods-button');
198
+ expect(button.classList).toContain('hods-button--secondary');
199
+ expect(button.textContent).toContain(_models.CollectionLabels.add);
200
+
201
+ case 14:
202
+ case "end":
203
+ return _context3.stop();
204
+ }
205
+ }
206
+ }, _callee3);
207
+ })));
208
+ it('should handle a collection label appropriately', /*#__PURE__*/_asyncToGenerator( /*#__PURE__*/regeneratorRuntime.mark(function _callee4() {
209
+ var LABEL, COLLECTION, _renderWithValidation4, container, c, _c$childNodes, title, buttonGroup, button;
210
+
211
+ return regeneratorRuntime.wrap(function _callee4$(_context4) {
212
+ while (1) {
213
+ switch (_context4.prev = _context4.next) {
214
+ case 0:
215
+ LABEL = 'Alpha Collection';
216
+ COLLECTION = {
217
+ id: ID,
218
+ fieldId: ID,
219
+ type: _models.ComponentTypes.COLLECTION,
220
+ item: [TEXT_COMPONENT],
221
+ label: LABEL
222
+ };
223
+ _renderWithValidation4 = (0, _setupTests.renderWithValidation)( /*#__PURE__*/_react2.default.createElement(_FormComponent.default, {
224
+ component: COLLECTION
225
+ })), container = _renderWithValidation4.container; // Check the container itself.
226
+
227
+ c = container.childNodes[0];
228
+ expect(c.tagName).toEqual('DIV');
229
+ expect(c.classList).toContain(_Collection.DEFAULT_CLASS); // And now make sure it has no children OTHER than the button to add an item.
230
+
231
+ expect(c.childNodes.length).toEqual(2);
232
+ _c$childNodes = _slicedToArray(c.childNodes, 2), title = _c$childNodes[0], buttonGroup = _c$childNodes[1];
233
+ expect(title.tagName).toEqual('LABEL');
234
+ expect(title.classList).toContain("".concat(_Collection.DEFAULT_CLASS, "__title"));
235
+ expect(title.getAttribute('for')).toEqual(ID);
236
+ expect(title.textContent).toContain(LABEL);
237
+ expect(buttonGroup.tagName).toEqual('DIV');
238
+ expect(buttonGroup.classList).toContain('hods-button-group');
239
+ button = buttonGroup.childNodes[0];
240
+ expect(button.tagName).toEqual('BUTTON');
241
+ expect(button.classList).toContain('hods-button');
242
+ expect(button.classList).toContain('hods-button--secondary');
243
+ expect(button.textContent).toContain(_models.CollectionLabels.add);
244
+
245
+ case 19:
246
+ case "end":
247
+ return _context4.stop();
248
+ }
249
+ }
250
+ }, _callee4);
251
+ })));
252
+ it('should handle the addition and removal of an item', /*#__PURE__*/_asyncToGenerator( /*#__PURE__*/regeneratorRuntime.mark(function _callee6() {
253
+ var COLLECTION, container, c, addButton, item, label, removeButton;
254
+ return regeneratorRuntime.wrap(function _callee6$(_context6) {
255
+ while (1) {
256
+ switch (_context6.prev = _context6.next) {
257
+ case 0:
258
+ COLLECTION = {
259
+ id: ID,
260
+ fieldId: ID,
261
+ type: _models.ComponentTypes.COLLECTION,
262
+ item: [TEXT_COMPONENT]
263
+ };
264
+ container = document.createElement('div');
265
+ document.body.appendChild(container);
266
+ _context6.next = 5;
267
+ return (0, _testUtils.act)( /*#__PURE__*/_asyncToGenerator( /*#__PURE__*/regeneratorRuntime.mark(function _callee5() {
268
+ return regeneratorRuntime.wrap(function _callee5$(_context5) {
269
+ while (1) {
270
+ switch (_context5.prev = _context5.next) {
271
+ case 0:
272
+ (0, _setupTests.renderDomWithValidation)( /*#__PURE__*/_react2.default.createElement(_FormComponent.default, {
273
+ component: COLLECTION
274
+ }), container);
275
+
276
+ case 1:
277
+ case "end":
278
+ return _context5.stop();
279
+ }
280
+ }
281
+ }, _callee5);
282
+ })));
283
+
284
+ case 5:
285
+ // Check the container itself.
286
+ c = container.childNodes[0];
287
+ expect(c.tagName).toEqual('DIV');
288
+ expect(c.classList).toContain(_Collection.DEFAULT_CLASS); // And now make sure it has no children OTHER than the button to add an item.
289
+
290
+ expect(c.childNodes.length).toEqual(1); // Get hold of that "Add another" button and click it.
291
+
292
+ addButton = c.childNodes[0].childNodes[0];
293
+
294
+ _react.fireEvent.click(addButton, {}); // Make sure an item has been added.
295
+
296
+
297
+ expect(c.childNodes.length).toEqual(2);
298
+ item = c.childNodes[0];
299
+ label = item.childNodes[0];
300
+ expect(label.textContent).toContain(_utils.default.interpolateString(_models.CollectionLabels.item, {
301
+ index: 1
302
+ })); // Get hold of the newly-add item's "Remove" button.
303
+
304
+ removeButton = label.childNodes[1];
305
+ expect(removeButton.tagName).toEqual('BUTTON');
306
+ expect(removeButton.classList).toContain('hods-button--secondary');
307
+ expect(removeButton.textContent).toContain(_models.CollectionLabels.remove); // Click the "Remove" button
308
+
309
+ _react.fireEvent.click(removeButton, {}); // Make sure the item has been removed.
310
+
311
+
312
+ expect(c.childNodes.length).toEqual(1);
313
+ expect(c.childNodes[0].classList).toContain('hods-button-group');
314
+
315
+ case 22:
316
+ case "end":
317
+ return _context6.stop();
318
+ }
319
+ }
320
+ }, _callee6);
321
+ })));
322
+ });
@@ -247,6 +247,8 @@ var InternalFormRenderer = function InternalFormRenderer(_ref2) {
247
247
  // Check to see whether the action is able to proceed, which in
248
248
  // in the case of a submission will validate the fields in the page.
249
249
  if (_helpers.default.canActionProceed(action, formState.page, validate.page)) {
250
+ patch = _helpers.default.cleanHiddenNestedData(patch, formState.page);
251
+
250
252
  if (action.type === _models.PageAction.TYPES.NAVIGATE) {
251
253
  _handlers.default.navigate(action, pageId, onPageChange);
252
254
  } else {
@@ -0,0 +1,38 @@
1
+ "use strict";
2
+
3
+ Object.defineProperty(exports, "__esModule", {
4
+ value: true
5
+ });
6
+ exports.default = void 0;
7
+ var parentComponents = ['radios'];
8
+
9
+ var cleanHiddenNestedData = function cleanHiddenNestedData(patch, page) {
10
+ page.components.forEach(function (component) {
11
+ if (!parentComponents.includes(component.type)) {
12
+ return;
13
+ }
14
+
15
+ var idsToDelete = getIdsToDelete(component, patch[component.id]);
16
+ idsToDelete.forEach(function (id) {
17
+ delete patch[id];
18
+ });
19
+ });
20
+ return patch;
21
+ };
22
+
23
+ function getIdsToDelete(component, selectedValue) {
24
+ var _component$data, _component$data$optio;
25
+
26
+ var idsToDelete = [];
27
+ component === null || component === void 0 ? void 0 : (_component$data = component.data) === null || _component$data === void 0 ? void 0 : (_component$data$optio = _component$data.options) === null || _component$data$optio === void 0 ? void 0 : _component$data$optio.forEach(function (option) {
28
+ if (option.value !== selectedValue && option.nested) {
29
+ option.nested.forEach(function (nested) {
30
+ idsToDelete.push(nested.id);
31
+ });
32
+ }
33
+ });
34
+ return idsToDelete;
35
+ }
36
+
37
+ var _default = cleanHiddenNestedData;
38
+ exports.default = _default;
@@ -0,0 +1,50 @@
1
+ "use strict";
2
+
3
+ var _cleanHiddenNestedData = _interopRequireDefault(require("./cleanHiddenNestedData"));
4
+
5
+ function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
6
+
7
+ // Local imports
8
+ describe('components', function () {
9
+ describe('FormRenderer', function () {
10
+ describe('helpers', function () {
11
+ describe('cleanHiddenNestedData', function () {
12
+ it('remove data corresponding to hidden nested components', function () {
13
+ var patch = {
14
+ parent: 'option2',
15
+ nested1: 'should not be included',
16
+ nested2: 'should be included'
17
+ };
18
+ var page = {
19
+ id: 'page',
20
+ name: 'page',
21
+ title: 'Page',
22
+ components: [{
23
+ id: 'parent',
24
+ fieldId: 'parent',
25
+ type: 'radios',
26
+ data: {
27
+ options: [{
28
+ value: 'option1',
29
+ nested: [{
30
+ id: 'nested1',
31
+ fieldId: 'nested1'
32
+ }]
33
+ }, {
34
+ value: 'option2',
35
+ nested: [{
36
+ id: 'nested2',
37
+ fieldId: 'nested2'
38
+ }]
39
+ }]
40
+ }
41
+ }]
42
+ };
43
+ var updatedPatch = (0, _cleanHiddenNestedData.default)(patch, page);
44
+ expect(updatedPatch['nested1']).toBeFalsy();
45
+ expect(updatedPatch['nested2']).toBeTruthy();
46
+ });
47
+ });
48
+ });
49
+ });
50
+ });
@@ -9,6 +9,8 @@ var _canActionProceed = _interopRequireDefault(require("./canActionProceed"));
9
9
 
10
10
  var _canCYASubmit = _interopRequireDefault(require("./canCYASubmit"));
11
11
 
12
+ var _cleanHiddenNestedData = _interopRequireDefault(require("./cleanHiddenNestedData"));
13
+
12
14
  var _getFormState = _interopRequireDefault(require("./getFormState"));
13
15
 
14
16
  var _getNextPageId = _interopRequireDefault(require("./getNextPageId"));
@@ -25,6 +27,7 @@ function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { de
25
27
  var helpers = {
26
28
  canActionProceed: _canActionProceed.default,
27
29
  canCYASubmit: _canCYASubmit.default,
30
+ cleanHiddenNestedData: _cleanHiddenNestedData.default,
28
31
  getFormState: _getFormState.default,
29
32
  getNextPageId: _getNextPageId.default,
30
33
  getPage: _getPage.default,
@@ -49,7 +49,9 @@ var setNestedValue = function setNestedValue(component, page) {
49
49
  (_component$data = component.data) === null || _component$data === void 0 ? void 0 : (_component$data$optio = _component$data.options) === null || _component$data$optio === void 0 ? void 0 : _component$data$optio.forEach(function (option, index) {
50
50
  //check if option is selected and has nested component
51
51
  if (page.formData[component.id] === option.value && option.nested) {
52
- component.data.options[index].nested.value = page.formData[option.nested.id];
52
+ option.nested.forEach(function (child) {
53
+ child.value = page.formData[child.id];
54
+ });
53
55
  }
54
56
  });
55
57
  };
@@ -162,14 +162,14 @@ describe('utils', function () {
162
162
  data: {
163
163
  options: [{
164
164
  value: SELECTED_VALUE,
165
- nested: {
165
+ nested: [{
166
166
  id: NESTED_ID
167
- }
167
+ }]
168
168
  }]
169
169
  }
170
170
  };
171
171
  var ROW = (0, _getCYARow.default)(PAGE, COMPONENT, function () {});
172
- expect(ROW.component.data.options[0].nested.value).toEqual(NESTED_VALUE);
172
+ expect(ROW.component.data.options[0].nested[0].value).toEqual(NESTED_VALUE);
173
173
  });
174
174
  });
175
175
  });
@@ -19,7 +19,7 @@ function _arrayWithoutHoles(arr) { if (Array.isArray(arr)) return _arrayLikeToAr
19
19
 
20
20
  function _arrayLikeToArray(arr, len) { if (len == null || len > arr.length) len = arr.length; for (var i = 0, arr2 = new Array(len); i < len; i++) { arr2[i] = arr[i]; } return arr2; }
21
21
 
22
- var JSON_ONLY_PROPERTIES = ['source', 'use', 'show_when', 'options', 'additionalValidation', 'full_path', 'shown', 'formData'];
22
+ var JSON_ONLY_PROPERTIES = ['source', 'use', 'show_when', 'options', 'additionalValidation', 'full_path', 'formData'];
23
23
  /**
24
24
  * This method removes and properties that are entirely specific to the JSON
25
25
  * configuration and that should not be used as HTML attributes.
@@ -3,7 +3,7 @@
3
3
  Object.defineProperty(exports, "__esModule", {
4
4
  value: true
5
5
  });
6
- exports.getNestedComponent = exports.default = void 0;
6
+ exports.getChildrenJsx = exports.default = void 0;
7
7
 
8
8
  var _copReactComponents = require("@ukhomeoffice/cop-react-components");
9
9
 
@@ -17,6 +17,8 @@ var _cleanAttributes = _interopRequireDefault(require("./cleanAttributes"));
17
17
 
18
18
  var _isEditable = _interopRequireDefault(require("./isEditable"));
19
19
 
20
+ var _FormComponent = _interopRequireDefault(require("../../components/FormComponent"));
21
+
20
22
  var _wrapInFormGroup = _interopRequireDefault(require("./wrapInFormGroup"));
21
23
 
22
24
  function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
@@ -85,11 +87,11 @@ var getRadios = function getRadios(config) {
85
87
  });
86
88
 
87
89
  options.forEach(function (option) {
88
- if (!option.nested) {
90
+ if (!Array.isArray(option.nested)) {
89
91
  return;
90
92
  }
91
93
 
92
- option.nestedJSX = getNestedComponent(config, option);
94
+ option.children = getChildrenJsx(config, option.nested);
93
95
  });
94
96
  var attrs = (0, _cleanAttributes.default)(config);
95
97
  return /*#__PURE__*/_react.default.createElement(_copReactComponents.Radios, _extends({}, attrs, {
@@ -156,32 +158,49 @@ var getComponentByType = function getComponentByType(config) {
156
158
  }
157
159
  };
158
160
  /**
159
- * Get nested component with form data
160
- * @param {*} parentConfig
161
- * @param {*} nestedConfig
161
+ * Get single child component for a parent
162
+ * @param {*} parent the parent configuration
163
+ * @param {*} child the child configuration
162
164
  */
163
165
 
164
166
 
165
- var getNestedComponent = function getNestedComponent(parentConfig, nestedConfig) {
166
- nestedConfig.nested.onChange = parentConfig.onChange;
167
-
168
- if (parentConfig.formData) {
169
- var _parentConfig$formDat;
167
+ var getChildJsx = function getChildJsx(parent, child) {
168
+ if (parent.formData) {
169
+ var _parent$formData;
170
170
 
171
- nestedConfig.nested.value = parentConfig !== null && parentConfig !== void 0 && (_parentConfig$formDat = parentConfig.formData) !== null && _parentConfig$formDat !== void 0 && _parentConfig$formDat[nestedConfig.nested.fieldId] ? parentConfig.formData[nestedConfig.nested.fieldId] : '';
171
+ child.value = parent !== null && parent !== void 0 && (_parent$formData = parent.formData) !== null && _parent$formData !== void 0 && _parent$formData[child.fieldId] ? parent.formData[child.fieldId] : '';
172
172
  }
173
173
 
174
- if ('readonly' in nestedConfig.nested) delete nestedConfig.nested.readonly;
174
+ if ('readonly' in child) delete child.readonly;
175
175
 
176
- if (parentConfig.readonly) {
177
- nestedConfig.nested.readonly = parentConfig.readonly;
178
- return getComponent(nestedConfig.nested, false);
176
+ if (parent.readonly) {
177
+ child.readonly = parent.readonly;
178
+ return /*#__PURE__*/_react.default.createElement("div", null, getComponent(child, false));
179
179
  }
180
180
 
181
- return getComponent(nestedConfig.nested);
181
+ return /*#__PURE__*/_react.default.createElement(_FormComponent.default, {
182
+ component: child,
183
+ value: child.value,
184
+ onChange: parent.onChange,
185
+ key: child.key
186
+ });
187
+ };
188
+ /**
189
+ * Convert chidlrenConfigs into components
190
+ * @param {*} parentConfig parent component which the childrenConfigs will be under
191
+ * @param {*} childrenConfigs array of configurations for the child components
192
+ */
193
+
194
+
195
+ var getChildrenJsx = function getChildrenJsx(parentConfig, childrenConfigs) {
196
+ return /*#__PURE__*/_react.default.createElement(_react.default.Fragment, null, childrenConfigs.map(function (config) {
197
+ return /*#__PURE__*/_react.default.createElement(_react.default.Fragment, {
198
+ key: config.id
199
+ }, getChildJsx(parentConfig, config));
200
+ }));
182
201
  };
183
202
 
184
- exports.getNestedComponent = getNestedComponent;
203
+ exports.getChildrenJsx = getChildrenJsx;
185
204
 
186
205
  /**
187
206
  * Get a renderable component, based on a configuration object.
@@ -4,6 +4,8 @@ var _react = require("@testing-library/react");
4
4
 
5
5
  var _getComponent = require("../getComponent");
6
6
 
7
+ var _setupTests = require("../../../setupTests");
8
+
7
9
  function _defineProperty(obj, key, value) { if (key in obj) { Object.defineProperty(obj, key, { value: value, enumerable: true, configurable: true, writable: true }); } else { obj[key] = value; } return obj; }
8
10
 
9
11
  function asyncGeneratorStep(gen, resolve, reject, _next, _throw, key, arg) { try { var info = gen[key](arg); var value = info.value; } catch (error) { reject(error); return; } if (info.done) { resolve(value); } else { Promise.resolve(value).then(_next, _throw); } }
@@ -11,8 +13,10 @@ function asyncGeneratorStep(gen, resolve, reject, _next, _throw, key, arg) { try
11
13
  function _asyncToGenerator(fn) { return function () { var self = this, args = arguments; return new Promise(function (resolve, reject) { var gen = fn.apply(self, args); function _next(value) { asyncGeneratorStep(gen, resolve, reject, _next, _throw, "next", value); } function _throw(err) { asyncGeneratorStep(gen, resolve, reject, _next, _throw, "throw", err); } _next(undefined); }); }; }
12
14
 
13
15
  describe('utils.Component.get', function () {
14
- it('should return a component that is nested within another component', /*#__PURE__*/_asyncToGenerator( /*#__PURE__*/regeneratorRuntime.mark(function _callee() {
15
- var ID, FIELD_ID, LABEL, VALUE, PARENT_CONFIG, NESTED_CONFIG, _render, container, child, label, input;
16
+ it('should return components that are nested within another component', /*#__PURE__*/_asyncToGenerator( /*#__PURE__*/regeneratorRuntime.mark(function _callee() {
17
+ var _formData;
18
+
19
+ var ID, FIELD_ID, LABEL, ID2, FIELD_ID2, LABEL2, VALUE, VALUE2, PARENT_CONFIG, NESTED_CONFIG, _renderWithValidation, container, child, label, input, child2, label2, input2;
16
20
 
17
21
  return regeneratorRuntime.wrap(function _callee$(_context) {
18
22
  while (1) {
@@ -21,32 +25,53 @@ describe('utils.Component.get', function () {
21
25
  ID = 'testId';
22
26
  FIELD_ID = 'fieldId';
23
27
  LABEL = 'Test label';
28
+ ID2 = 'testId2';
29
+ FIELD_ID2 = 'fieldId2';
30
+ LABEL2 = 'Test label 2';
24
31
  VALUE = 'nestedValue';
32
+ VALUE2 = '1-2-2022';
25
33
  PARENT_CONFIG = {
26
34
  onChange: function onChange() {},
27
- formData: _defineProperty({}, FIELD_ID, VALUE)
28
- };
29
- NESTED_CONFIG = {
30
- nested: {
31
- id: ID,
32
- fieldId: FIELD_ID,
33
- label: LABEL,
34
- type: 'text'
35
- }
35
+ formData: (_formData = {}, _defineProperty(_formData, FIELD_ID, VALUE), _defineProperty(_formData, FIELD_ID2, VALUE2), _formData)
36
36
  };
37
- _render = (0, _react.render)((0, _getComponent.getNestedComponent)(PARENT_CONFIG, NESTED_CONFIG)), container = _render.container;
37
+ NESTED_CONFIG = [{
38
+ id: ID,
39
+ fieldId: FIELD_ID,
40
+ label: LABEL,
41
+ type: 'text'
42
+ }, {
43
+ id: ID2,
44
+ fieldId: FIELD_ID2,
45
+ label: LABEL2,
46
+ type: 'date'
47
+ }];
48
+ _renderWithValidation = (0, _setupTests.renderWithValidation)((0, _getComponent.getChildrenJsx)(PARENT_CONFIG, NESTED_CONFIG)), container = _renderWithValidation.container;
38
49
  child = container.childNodes[0];
50
+ expect(child.childNodes.length).toEqual(3);
39
51
  expect(child.classList).toContain('govuk-form-group');
40
52
  label = child.childNodes[0];
41
53
  expect(label).toBeDefined();
42
54
  expect(label.innerHTML).toContain(LABEL);
43
- input = child.childNodes[1];
55
+ input = child.childNodes[2];
44
56
  expect(input.tagName).toEqual('INPUT');
45
57
  expect(input.classList).toContain('govuk-input');
46
58
  expect(input.id).toEqual(ID);
47
59
  expect(input.value).toEqual(VALUE);
60
+ child2 = container.childNodes[1];
61
+ expect(child2.childNodes.length).toEqual(3);
62
+ expect(child2.classList).toContain('govuk-form-group');
63
+ label2 = child2.childNodes[0];
64
+ expect(label2).toBeDefined();
65
+ expect(label2.innerHTML).toContain(LABEL2);
66
+ input2 = child2.childNodes[2];
67
+ expect(input2.tagName).toEqual('DIV');
68
+ expect(input2.classList).toContain('govuk-date-input');
69
+ expect(input2.id).toEqual(ID2);
70
+ expect(input2.childNodes[0].childNodes[1].value).toEqual('1');
71
+ expect(input2.childNodes[1].childNodes[1].value).toEqual('2');
72
+ expect(input2.childNodes[2].childNodes[1].value).toEqual('2022');
48
73
 
49
- case 17:
74
+ case 35:
50
75
  case "end":
51
76
  return _context.stop();
52
77
  }
@@ -54,7 +79,7 @@ describe('utils.Component.get', function () {
54
79
  }, _callee);
55
80
  })));
56
81
  it('should return a read only component that is nested within another readonly component', /*#__PURE__*/_asyncToGenerator( /*#__PURE__*/regeneratorRuntime.mark(function _callee2() {
57
- var ID, FIELD_ID, LABEL, VALUE, PARENT_CONFIG, NESTED_CONFIG, _render2, container, child;
82
+ var ID, FIELD_ID, LABEL, VALUE, PARENT_CONFIG, NESTED_CONFIG, _render, container, child;
58
83
 
59
84
  return regeneratorRuntime.wrap(function _callee2$(_context2) {
60
85
  while (1) {
@@ -69,17 +94,15 @@ describe('utils.Component.get', function () {
69
94
  formData: _defineProperty({}, FIELD_ID, VALUE),
70
95
  readonly: true
71
96
  };
72
- NESTED_CONFIG = {
73
- nested: {
74
- id: ID,
75
- fieldId: FIELD_ID,
76
- label: LABEL,
77
- type: 'text',
78
- readonly: true
79
- }
80
- };
81
- _render2 = (0, _react.render)((0, _getComponent.getNestedComponent)(PARENT_CONFIG, NESTED_CONFIG)), container = _render2.container;
82
- child = container.childNodes[0];
97
+ NESTED_CONFIG = [{
98
+ id: ID,
99
+ fieldId: FIELD_ID,
100
+ label: LABEL,
101
+ type: 'text',
102
+ readonly: true
103
+ }];
104
+ _render = (0, _react.render)((0, _getComponent.getChildrenJsx)(PARENT_CONFIG, NESTED_CONFIG)), container = _render.container;
105
+ child = container.childNodes[0].childNodes[0];
83
106
  expect(child.classList).toContain('hods-readonly');
84
107
 
85
108
  case 9:
@@ -50,7 +50,6 @@ var validateComponent = function validateComponent(component, outerData, formDat
50
50
  }
51
51
 
52
52
  var error = undefined;
53
- var nestedId = undefined;
54
53
  var properties = undefined;
55
54
  var data = outerData && _typeof(outerData) === 'object' ? outerData : {};
56
55
  var value = data[component.fieldId];
@@ -81,21 +80,15 @@ var validateComponent = function validateComponent(component, outerData, formDat
81
80
  break;
82
81
 
83
82
  case _models.ComponentTypes.RADIOS:
84
- (_component$data$optio = component.data.options) === null || _component$data$optio === void 0 ? void 0 : _component$data$optio.some(function (option) {
85
- var nestedError;
86
-
87
- if (option.nested && option.nested.shown) {
88
- nestedError = validateComponent(option.nested, outerData, fd);
89
-
90
- if (nestedError) {
91
- error = nestedError.error;
92
- nestedId = nestedError.id;
93
- }
83
+ var nestedErrors = [];
84
+ (_component$data$optio = component.data.options) === null || _component$data$optio === void 0 ? void 0 : _component$data$optio.forEach(function (option) {
85
+ if (option.nested && formData[component.id] === option.value) {
86
+ nestedErrors = nestedErrors.concat((0, _validateContainer.default)({
87
+ components: option.nested
88
+ }, formData));
94
89
  }
95
-
96
- return nestedError;
97
90
  });
98
- break;
91
+ return nestedErrors;
99
92
 
100
93
  default:
101
94
  break;
@@ -120,7 +113,7 @@ var validateComponent = function validateComponent(component, outerData, formDat
120
113
 
121
114
  if (error) {
122
115
  return {
123
- id: nestedId || component.full_path || component.id,
116
+ id: component.full_path || component.id,
124
117
  error: error,
125
118
  properties: properties
126
119
  };
@@ -211,17 +211,16 @@ describe('utils.Validate.Component', function () {
211
211
  id: 'a',
212
212
  data: {
213
213
  options: [{
214
- nested: {
214
+ nested: [{
215
215
  type: 'text',
216
216
  fieldId: NESTED_ID,
217
217
  id: NESTED_ID,
218
- required: true,
219
- shown: true
220
- }
218
+ required: true
219
+ }]
221
220
  }]
222
221
  }
223
222
  };
224
- expect((0, _validateComponent.default)(COMPONENT, FORMDATA)).toBeUndefined();
223
+ expect((0, _validateComponent.default)(COMPONENT, undefined, FORMDATA)).toEqual([]);
225
224
  });
226
225
  it('should return an error when the radio component contains nested components with errors', function () {
227
226
  var NESTED_ID = 'nestedId';
@@ -231,20 +230,19 @@ describe('utils.Validate.Component', function () {
231
230
  id: 'a',
232
231
  data: {
233
232
  options: [{
234
- nested: {
233
+ nested: [{
235
234
  type: 'text',
236
235
  fieldId: NESTED_ID,
237
236
  id: NESTED_ID,
238
- required: true,
239
- shown: true
240
- }
237
+ required: true
238
+ }]
241
239
  }]
242
240
  }
243
241
  };
244
- expect((0, _validateComponent.default)(COMPONENT, FORMDATA)).toEqual({
242
+ expect((0, _validateComponent.default)(COMPONENT, undefined, FORMDATA)).toEqual([{
245
243
  id: NESTED_ID,
246
244
  error: "Field is required"
247
- });
245
+ }]);
248
246
  });
249
247
  it('should return no error when a non selected radio component contains nested components with errors', function () {
250
248
  var NESTED_ID = 'nestedId';
@@ -253,16 +251,17 @@ describe('utils.Validate.Component', function () {
253
251
  id: 'a',
254
252
  data: {
255
253
  options: [{
256
- nested: {
254
+ value: 'optionValue',
255
+ nested: [{
257
256
  type: 'text',
258
257
  fieldId: NESTED_ID,
259
258
  id: NESTED_ID,
260
259
  required: true
261
- }
260
+ }]
262
261
  }]
263
262
  }
264
263
  };
265
- expect((0, _validateComponent.default)(COMPONENT, {})).toBeUndefined();
264
+ expect((0, _validateComponent.default)(COMPONENT, undefined, {})).toEqual([]);
266
265
  });
267
266
  });
268
267
  });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ukhomeoffice/cop-react-form-renderer",
3
- "version": "3.0.3",
3
+ "version": "3.1.0",
4
4
  "private": false,
5
5
  "scripts": {
6
6
  "clean": "rimraf dist",
@@ -16,7 +16,7 @@
16
16
  "post-compile": "rimraf dist/*.test.* dist/**/*.test.* dist/**/*.stories.* dist/docs dist/assets"
17
17
  },
18
18
  "dependencies": {
19
- "@ukhomeoffice/cop-react-components": "1.7.1",
19
+ "@ukhomeoffice/cop-react-components": "1.7.3",
20
20
  "axios": "^0.21.1",
21
21
  "dayjs": "^1.11.0",
22
22
  "govuk-frontend": "^3.13.0",