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

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.
@@ -94,8 +94,8 @@ describe('utils.Component.get', function () {
94
94
  }
95
95
  });
96
96
 
97
- expect(ON_CHANGE_CALLS.length).toEqual(2);
98
- expect(ON_CHANGE_CALLS[1]).toMatchObject({
97
+ expect(ON_CHANGE_CALLS.length).toEqual(1);
98
+ expect(ON_CHANGE_CALLS[0]).toMatchObject({
99
99
  name: FIELD_ID,
100
100
  value: '5--'
101
101
  }); // And now the month...
@@ -107,8 +107,8 @@ describe('utils.Component.get', function () {
107
107
  }
108
108
  });
109
109
 
110
- expect(ON_CHANGE_CALLS.length).toEqual(3);
111
- expect(ON_CHANGE_CALLS[2]).toMatchObject({
110
+ expect(ON_CHANGE_CALLS.length).toEqual(2);
111
+ expect(ON_CHANGE_CALLS[1]).toMatchObject({
112
112
  name: FIELD_ID,
113
113
  value: '5-11-'
114
114
  }); // And finally the year.
@@ -120,8 +120,8 @@ describe('utils.Component.get', function () {
120
120
  }
121
121
  });
122
122
 
123
- expect(ON_CHANGE_CALLS.length).toEqual(4);
124
- expect(ON_CHANGE_CALLS[3]).toMatchObject({
123
+ expect(ON_CHANGE_CALLS.length).toEqual(3);
124
+ expect(ON_CHANGE_CALLS[2]).toMatchObject({
125
125
  name: FIELD_ID,
126
126
  value: '5-11-2022'
127
127
  });
@@ -5,6 +5,8 @@ Object.defineProperty(exports, "__esModule", {
5
5
  });
6
6
  exports.default = void 0;
7
7
 
8
+ var _copReactComponents = require("@ukhomeoffice/cop-react-components");
9
+
8
10
  var _Container = _interopRequireDefault(require("../Container"));
9
11
 
10
12
  var _Data = _interopRequireDefault(require("../Data"));
@@ -35,6 +37,8 @@ var getFormPage = function getFormPage(pageOptions, formComponents, formData) {
35
37
  return null;
36
38
  }
37
39
 
40
+ pageOptions = interpolatePageOptions(pageOptions, formData);
41
+ formComponents = interpolateFormComponents(formComponents, formData);
38
42
  var components = pageOptions.components.map(function (componentOptions) {
39
43
  if (typeof componentOptions === 'string') {
40
44
  return (0, _getParagraphFromText.default)(componentOptions);
@@ -57,6 +61,42 @@ var getFormPage = function getFormPage(pageOptions, formComponents, formData) {
57
61
  actions: actions
58
62
  }));
59
63
  };
64
+ /**
65
+ * Interpolate 'Variable Expression' using formData - for local use only.
66
+ * @param {object} pageOptions The JSON page.
67
+ * @param {object} formData The top-level form data, used for setting up components.
68
+ * @returns interpolated pageOptions.
69
+ */
70
+
71
+
72
+ var interpolatePageOptions = function interpolatePageOptions(pageOptions, formData) {
73
+ return JSON.parse(_copReactComponents.Utils.interpolateString(JSON.stringify(pageOptions), formData));
74
+ };
75
+ /**
76
+ * Interpolate 'Variable Expression' using formData excluding each component's data block - for local use only.
77
+ * @param {Array} formComponents The components defined at the top-level of the form.
78
+ * @param {object} formData The top-level form data, used for setting up components.
79
+ * @returns interpolated formComponents
80
+ */
81
+
82
+
83
+ var interpolateFormComponents = function interpolateFormComponents(formComponents, formData) {
84
+ return formComponents.map(function (formComponent) {
85
+ var formComponentDataUrl = undefined;
86
+
87
+ if (formComponent.data && formComponent.data.url) {
88
+ formComponentDataUrl = formComponent.data.url;
89
+ }
90
+
91
+ var interpolatedFormComponent = JSON.parse(_copReactComponents.Utils.interpolateString(JSON.stringify(formComponent), formData));
92
+
93
+ if (formComponentDataUrl) {
94
+ interpolatedFormComponent.data.url = formComponentDataUrl;
95
+ }
96
+
97
+ return interpolatedFormComponent;
98
+ });
99
+ };
60
100
 
61
101
  var _default = getFormPage;
62
102
  exports.default = _default;
@@ -32,6 +32,18 @@ describe('utils', function () {
32
32
  data: {
33
33
  url: '${urls.refData}/v3/charlies'
34
34
  }
35
+ }, // eslint-disable-next-line no-template-curly-in-string
36
+ {
37
+ id: 'd',
38
+ fieldId: 'd',
39
+ label: 'Roger ${currentUser.firstname}',
40
+ type: 'text'
41
+ }, // eslint-disable-next-line no-template-curly-in-string
42
+ {
43
+ id: 'e',
44
+ fieldId: 'e',
45
+ label: 'Bravo ${currentUser.surname}',
46
+ type: 'text'
35
47
  }];
36
48
  var FORM_DATA = {
37
49
  urls: {
@@ -165,6 +177,59 @@ describe('utils', function () {
165
177
  var C = FORM_COMPONENTS[2];
166
178
  expect((0, _getFormPage.default)(PAGE, FORM_COMPONENTS, {})).toEqual({
167
179
  title: PAGE.title,
180
+ components: [{
181
+ type: 'html',
182
+ tagName: 'p',
183
+ content: PAGE.components[0]
184
+ }, _objectSpread(_objectSpread({}, PAGE.components[1]), {}, {
185
+ full_path: PAGE.components[1].fieldId
186
+ }), {
187
+ type: 'html',
188
+ tagName: 'p',
189
+ content: PAGE.components[2]
190
+ }, // eslint-disable-next-line no-template-curly-in-string
191
+ _objectSpread(_objectSpread({
192
+ use: 'c'
193
+ }, C), {}, {
194
+ cya_label: C.label,
195
+ full_path: C.fieldId,
196
+ data: {
197
+ url: '${urls.refData}/v3/charlies'
198
+ }
199
+ })],
200
+ formData: {}
201
+ });
202
+ });
203
+ it('should interpolate and handle a page that references a form-level component with formData', function () {
204
+ var PAGE = {
205
+ // eslint-disable-next-line no-template-curly-in-string
206
+ title: 'Page ${postFixTitle}',
207
+ components: ["Opening paragraph", {
208
+ type: 'heading',
209
+ size: 'l',
210
+ content: 'Page heading'
211
+ }, "Closing paragraph", {
212
+ use: 'c'
213
+ }, {
214
+ use: 'd'
215
+ }, "Kevin", {
216
+ use: 'e'
217
+ }]
218
+ };
219
+
220
+ var DATA = _objectSpread(_objectSpread({}, FORM_DATA), {}, {
221
+ currentUser: {
222
+ firstname: 'Bob',
223
+ surname: 'Kevin'
224
+ },
225
+ postFixTitle: 'Everyone'
226
+ });
227
+
228
+ var C = FORM_COMPONENTS[2];
229
+ var D = FORM_COMPONENTS[3];
230
+ var E = FORM_COMPONENTS[4];
231
+ expect((0, _getFormPage.default)(PAGE, FORM_COMPONENTS, DATA)).toEqual({
232
+ title: 'Page Everyone',
168
233
  components: [{
169
234
  type: 'html',
170
235
  tagName: 'p',
@@ -179,9 +244,28 @@ describe('utils', function () {
179
244
  use: 'c'
180
245
  }, C), {}, {
181
246
  cya_label: C.label,
247
+ data: {
248
+ url: "".concat(FORM_DATA.urls.refData, "/v3/charlies")
249
+ },
182
250
  full_path: C.fieldId
251
+ }), _objectSpread(_objectSpread({
252
+ use: 'd'
253
+ }, D), {}, {
254
+ label: 'Roger ' + DATA.currentUser.firstname,
255
+ cya_label: 'Roger ' + DATA.currentUser.firstname,
256
+ full_path: D.fieldId
257
+ }), {
258
+ type: 'html',
259
+ tagName: 'p',
260
+ content: PAGE.components[5]
261
+ }, _objectSpread(_objectSpread({
262
+ use: 'e'
263
+ }, E), {}, {
264
+ label: 'Bravo ' + DATA.currentUser.surname,
265
+ cya_label: 'Bravo ' + DATA.currentUser.surname,
266
+ full_path: E.fieldId
183
267
  })],
184
- formData: {}
268
+ formData: DATA
185
269
  });
186
270
  });
187
271
  });
@@ -13,6 +13,8 @@ var _validateEmail = _interopRequireDefault(require("./validateEmail"));
13
13
 
14
14
  var _validatePage = _interopRequireDefault(require("./validatePage"));
15
15
 
16
+ var _validateRegex = _interopRequireDefault(require("./validateRegex"));
17
+
16
18
  var _validateRequired = _interopRequireDefault(require("./validateRequired"));
17
19
 
18
20
  var _validateTime = _interopRequireDefault(require("./validateTime"));
@@ -25,6 +27,7 @@ var Validate = {
25
27
  email: _validateEmail.default,
26
28
  date: _validateDate.default,
27
29
  page: _validatePage.default,
30
+ regex: _validateRegex.default,
28
31
  required: _validateRequired.default,
29
32
  time: _validateTime.default
30
33
  };
@@ -15,7 +15,14 @@ function _objectSpread(target) { for (var i = 1; i < arguments.length; i++) { va
15
15
 
16
16
  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; }
17
17
 
18
- var validateCollection = function validateCollection(collection, items) {
18
+ /**
19
+ * Validates all of the items within a collection.
20
+ * @param {object} collection The collection to validate.
21
+ * @param {object} items The data items in the collection.
22
+ * @param {object} formData The data at the top level of the form.
23
+ * @returns Errors for all components for all items within the collection.
24
+ */
25
+ var validateCollection = function validateCollection(collection, items, formData) {
19
26
  var errors = [];
20
27
 
21
28
  if (collection && Array.isArray(collection.item) && Array.isArray(items)) {
@@ -29,7 +36,7 @@ var validateCollection = function validateCollection(collection, items) {
29
36
  });
30
37
  })
31
38
  };
32
- errors.push((0, _validateContainer.default)(container, item));
39
+ errors.push((0, _validateContainer.default)(container, item, formData));
33
40
  });
34
41
  }
35
42
 
@@ -19,6 +19,8 @@ var _validateDate = _interopRequireDefault(require("./validateDate"));
19
19
 
20
20
  var _validateEmail = _interopRequireDefault(require("./validateEmail"));
21
21
 
22
+ var _validateRegex = _interopRequireDefault(require("./validateRegex"));
23
+
22
24
  var _validateRequired = _interopRequireDefault(require("./validateRequired"));
23
25
 
24
26
  var _validateTime = _interopRequireDefault(require("./validateTime"));
@@ -30,25 +32,27 @@ function _typeof(obj) { "@babel/helpers - typeof"; if (typeof Symbol === "functi
30
32
  /**
31
33
  * Validates a single component.
32
34
  * @param {object} component The component to validate.
33
- * @param {object} formData The data to use that holds this component's value.
35
+ * @param {object} outerData The data to use that holds this component's value.
36
+ * @param {object} formData The data at the top level of the form.
34
37
  * @returns The first encountered error with the component.
35
38
  */
36
- var validateComponent = function validateComponent(component, formData) {
39
+ var validateComponent = function validateComponent(component, outerData, formData) {
37
40
  var _component$data$optio;
38
41
 
39
- if (!(0, _showComponent.default)(component, formData)) {
42
+ var fd = formData || outerData;
43
+
44
+ if (!(0, _showComponent.default)(component, fd)) {
40
45
  return undefined;
41
46
  }
42
47
 
43
- var error = undefined;
44
- var nestedId = undefined;
45
- var propertiesInError = undefined;
46
- var data = formData && _typeof(formData) === 'object' ? formData : {};
47
-
48
48
  if (component.type === _models.ComponentTypes.CONTAINER) {
49
- return (0, _validateContainer.default)(component, formData);
49
+ return (0, _validateContainer.default)(component, outerData, fd);
50
50
  }
51
51
 
52
+ var error = undefined;
53
+ var nestedId = undefined;
54
+ var properties = undefined;
55
+ var data = outerData && _typeof(outerData) === 'object' ? outerData : {};
52
56
  var value = data[component.fieldId];
53
57
 
54
58
  if (component.required) {
@@ -58,7 +62,7 @@ var validateComponent = function validateComponent(component, formData) {
58
62
  if (!error) {
59
63
  switch (component.type) {
60
64
  case _models.ComponentTypes.COLLECTION:
61
- return (0, _validateCollection.default)(component, value);
65
+ return (0, _validateCollection.default)(component, value, fd);
62
66
 
63
67
  case _models.ComponentTypes.EMAIL:
64
68
  error = (0, _validateEmail.default)(value, component.label);
@@ -72,7 +76,7 @@ var validateComponent = function validateComponent(component, formData) {
72
76
  message = _validator.message,
73
77
  propsInError = _validator.propsInError;
74
78
 
75
- propertiesInError = propsInError;
79
+ properties = propsInError;
76
80
  error = message;
77
81
  break;
78
82
 
@@ -81,12 +85,10 @@ var validateComponent = function validateComponent(component, formData) {
81
85
  var nestedError;
82
86
 
83
87
  if (option.nested && option.nested.shown) {
84
- var _nestedError;
85
-
86
- nestedError = validateComponent(option.nested, formData);
87
- error = (_nestedError = nestedError) === null || _nestedError === void 0 ? void 0 : _nestedError.error;
88
+ nestedError = validateComponent(option.nested, outerData, fd);
88
89
 
89
90
  if (nestedError) {
91
+ error = nestedError.error;
90
92
  nestedId = nestedError.id;
91
93
  }
92
94
  }
@@ -99,11 +101,15 @@ var validateComponent = function validateComponent(component, formData) {
99
101
  break;
100
102
  }
101
103
 
104
+ if (!error && component.pattern) {
105
+ error = (0, _validateRegex.default)(value, component.label, component.pattern, component.custom_errors);
106
+ }
107
+
102
108
  if (!error && component.additionalValidation) {
103
109
  error = (0, _additional.default)(component, value);
104
110
 
105
111
  if (component.type === _models.ComponentTypes.DATE && error) {
106
- propertiesInError = {
112
+ properties = {
107
113
  day: true,
108
114
  month: true,
109
115
  year: true
@@ -116,7 +122,7 @@ var validateComponent = function validateComponent(component, formData) {
116
122
  return {
117
123
  id: nestedId || component.full_path || component.id,
118
124
  error: error,
119
- properties: propertiesInError
125
+ properties: properties
120
126
  };
121
127
  }
122
128
 
@@ -10,13 +10,22 @@ var _validateComponent = _interopRequireDefault(require("./validateComponent"));
10
10
  function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
11
11
 
12
12
  // Local imports
13
- var validateContainer = function validateContainer(container, formData) {
13
+
14
+ /**
15
+ * Validates all components within a container.
16
+ * @param {object} container The container to validate.
17
+ * @param {object} outerData The data to use that holds this component's value.
18
+ * @param {object} formData The data at the top level of the form.
19
+ * @returns Errors for all components within the container.
20
+ */
21
+ var validateContainer = function validateContainer(container, outerData, formData) {
22
+ var fd = formData || outerData;
14
23
  var errors = [];
15
24
 
16
25
  if (container && Array.isArray(container.components)) {
17
- var fd = formData && container.fieldId ? formData[container.fieldId] : formData;
26
+ var containerData = outerData && container.fieldId ? outerData[container.fieldId] : outerData;
18
27
  container.components.forEach(function (component) {
19
- errors.push((0, _validateComponent.default)(component, fd));
28
+ errors.push((0, _validateComponent.default)(component, containerData, fd));
20
29
  });
21
30
  }
22
31
 
@@ -21,7 +21,7 @@ function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { de
21
21
  var validatePage = function validatePage(page) {
22
22
  if ((0, _showFormPage.default)(page, page.formData) && Array.isArray(page.components)) {
23
23
  return page.components.reduce(function (errors, component) {
24
- return errors.concat((0, _validateComponent.default)(component, page.formData));
24
+ return errors.concat((0, _validateComponent.default)(component, page.formData, page.formData));
25
25
  }, []).filter(function (e) {
26
26
  return !!e;
27
27
  }).flat();
@@ -0,0 +1,56 @@
1
+ "use strict";
2
+
3
+ Object.defineProperty(exports, "__esModule", {
4
+ value: true
5
+ });
6
+ exports.default = void 0;
7
+
8
+ /**
9
+ * Validates an components value against a given regex pattern.
10
+ *
11
+ * An empty string for value is considered valid as not all components
12
+ * with pattern validation have to be required.
13
+ * @param {*} value The value to validate.
14
+ * @param {string} label The label to use in the default error message.
15
+ * @param {string} pattern The regex pattern to validate against.
16
+ * @param {array} customErrors The array of custom errors for the component.
17
+ * @returns An error if the value doesn't match the regex pattern.
18
+ */
19
+ var validateRegex = function validateRegex(value) {
20
+ var label = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : '';
21
+ var pattern = arguments.length > 2 ? arguments[2] : undefined;
22
+ var customErrors = arguments.length > 3 ? arguments[3] : undefined;
23
+
24
+ if (!value) {
25
+ return undefined;
26
+ }
27
+
28
+ if (typeof value === 'string') {
29
+ var regex = new RegExp(pattern);
30
+
31
+ if (regex.test(value)) {
32
+ return undefined;
33
+ }
34
+
35
+ if (Array.isArray(customErrors)) {
36
+ var _result$;
37
+
38
+ var result = customErrors.filter(function (error) {
39
+ return error.type === 'pattern';
40
+ });
41
+
42
+ if (result !== null && result !== void 0 && (_result$ = result[0]) !== null && _result$ !== void 0 && _result$.message) {
43
+ return result[0].message;
44
+ }
45
+ }
46
+ }
47
+
48
+ if (label === '') {
49
+ return 'Component failed regex validation';
50
+ }
51
+
52
+ return "".concat(label, " failed regex validation");
53
+ };
54
+
55
+ var _default = validateRegex;
56
+ exports.default = _default;
@@ -0,0 +1,41 @@
1
+ "use strict";
2
+
3
+ var _validateRegex = _interopRequireDefault(require("./validateRegex"));
4
+
5
+ function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
6
+
7
+ // Local imports
8
+ describe('utils', function () {
9
+ describe('Validate', function () {
10
+ describe('regex', function () {
11
+ var GOOD_VALUE = 'hello';
12
+ var BAD_VALUE = 'h3llo';
13
+ var LABEL = 'Component';
14
+ var PATTERN = '^[a-z]*$';
15
+ var CUSTOM_ERRORS = [{
16
+ "type": "pattern",
17
+ "message": "Regex validation failed for ".concat(LABEL)
18
+ }];
19
+ var DEFAULT_ERROR = [{
20
+ "type": "pattern"
21
+ }]; // Valid values
22
+
23
+ it('should return no error when the value matches the regex pattern', function () {
24
+ expect((0, _validateRegex.default)(GOOD_VALUE, LABEL, PATTERN, CUSTOM_ERRORS)).toBeUndefined();
25
+ });
26
+ it('should return no error when the value is an empty string', function () {
27
+ expect((0, _validateRegex.default)('', LABEL, PATTERN, CUSTOM_ERRORS)).toBeUndefined();
28
+ }); // Invalid values
29
+
30
+ it('should return a custom error when the value does not match the regex pattern and one is specified', function () {
31
+ expect((0, _validateRegex.default)(BAD_VALUE, LABEL, PATTERN, CUSTOM_ERRORS)).toEqual(CUSTOM_ERRORS[0].message);
32
+ });
33
+ it('should return an error using label when the value does not match the regex pattern and a custom error is not specified', function () {
34
+ expect((0, _validateRegex.default)(BAD_VALUE, LABEL, PATTERN, DEFAULT_ERROR)).toEqual("".concat(LABEL, " failed regex validation"));
35
+ });
36
+ it('should return a default error when the value does not match the regex pattern and both a custom error and label are not specified', function () {
37
+ expect((0, _validateRegex.default)(BAD_VALUE, '', PATTERN, DEFAULT_ERROR)).toEqual('Component failed regex validation');
38
+ });
39
+ });
40
+ });
41
+ });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ukhomeoffice/cop-react-form-renderer",
3
- "version": "3.0.0",
3
+ "version": "3.0.3",
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.6.0",
19
+ "@ukhomeoffice/cop-react-components": "1.7.1",
20
20
  "axios": "^0.21.1",
21
21
  "dayjs": "^1.11.0",
22
22
  "govuk-frontend": "^3.13.0",