@ukhomeoffice/cop-react-form-renderer 3.7.0-alpha → 3.10.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.
@@ -5,6 +5,7 @@ Object.defineProperty(exports, "__esModule", {
5
5
  });
6
6
  exports.default = void 0;
7
7
  var TYPE_AUTOCOMPLETE = 'autocomplete';
8
+ var TYPE_CALCULATION = 'calculation';
8
9
  var TYPE_CHECKBOXES = 'checkboxes';
9
10
  var TYPE_COLLECTION = 'collection';
10
11
  var TYPE_CONTAINER = 'container';
@@ -24,6 +25,7 @@ var TYPE_TIME = 'time';
24
25
  var TYPE_WARNING = 'warning';
25
26
  var ComponentTypes = {
26
27
  AUTOCOMPLETE: TYPE_AUTOCOMPLETE,
28
+ CALCULATION: TYPE_CALCULATION,
27
29
  CHECKBOXES: TYPE_CHECKBOXES,
28
30
  COLLECTION: TYPE_COLLECTION,
29
31
  CONTAINER: TYPE_CONTAINER,
@@ -54,6 +54,16 @@ var getCheckboxes = function getCheckboxes(config) {
54
54
  }));
55
55
  };
56
56
 
57
+ var getCalculation = function getCalculation(config) {
58
+ var calculatedValue = _Data.default.applyFormula(config);
59
+
60
+ var attrs = (0, _cleanAttributes.default)(config, ['formula']);
61
+ return /*#__PURE__*/_react.default.createElement(_copReactComponents.TextInput, _extends({}, attrs, {
62
+ value: calculatedValue,
63
+ readOnly: true
64
+ }));
65
+ };
66
+
57
67
  var getDate = function getDate(config) {
58
68
  var attrs = (0, _cleanAttributes.default)(config);
59
69
  return /*#__PURE__*/_react.default.createElement(_copReactComponents.DateInput, attrs);
@@ -184,6 +194,9 @@ var getComponentByType = function getComponentByType(config) {
184
194
  case _models.ComponentTypes.DETAILS:
185
195
  return getDetails(config);
186
196
 
197
+ case _models.ComponentTypes.CALCULATION:
198
+ return getCalculation(config);
199
+
187
200
  default:
188
201
  {
189
202
  return null;
@@ -0,0 +1,269 @@
1
+ "use strict";
2
+
3
+ var _react = require("@testing-library/react");
4
+
5
+ var _models = require("../../../models");
6
+
7
+ var _getComponent = _interopRequireDefault(require("../getComponent"));
8
+
9
+ function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
10
+
11
+ function _slicedToArray(arr, i) { return _arrayWithHoles(arr) || _iterableToArrayLimit(arr, i) || _unsupportedIterableToArray(arr, i) || _nonIterableRest(); }
12
+
13
+ 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."); }
14
+
15
+ 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); }
16
+
17
+ 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; }
18
+
19
+ 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; }
20
+
21
+ function _arrayWithHoles(arr) { if (Array.isArray(arr)) return arr; }
22
+
23
+ function ownKeys(object, enumerableOnly) { var keys = Object.keys(object); if (Object.getOwnPropertySymbols) { var symbols = Object.getOwnPropertySymbols(object); if (enumerableOnly) { symbols = symbols.filter(function (sym) { return Object.getOwnPropertyDescriptor(object, sym).enumerable; }); } keys.push.apply(keys, symbols); } return keys; }
24
+
25
+ function _objectSpread(target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i] != null ? arguments[i] : {}; if (i % 2) { ownKeys(Object(source), true).forEach(function (key) { _defineProperty(target, key, source[key]); }); } else if (Object.getOwnPropertyDescriptors) { Object.defineProperties(target, Object.getOwnPropertyDescriptors(source)); } else { ownKeys(Object(source)).forEach(function (key) { Object.defineProperty(target, key, Object.getOwnPropertyDescriptor(source, key)); }); } } return target; }
26
+
27
+ 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; }
28
+
29
+ describe('utils.Component.get', function () {
30
+ var ID = 'test-id';
31
+ var FIELD_ID = 'field-id';
32
+ var LABEL = 'label';
33
+ var COMPONENT = {
34
+ type: _models.ComponentTypes.CALCULATION,
35
+ id: ID,
36
+ fieldId: FIELD_ID,
37
+ label: LABEL,
38
+ formData: {
39
+ totalPerson: '4',
40
+ personProcessed: '3'
41
+ },
42
+ 'data-testid': ID
43
+ };
44
+ var error = jest.spyOn(console, 'error').mockImplementation(function () {});
45
+ afterAll(function () {
46
+ error.mockReset();
47
+ });
48
+ afterEach(function () {
49
+ error.mockClear();
50
+ });
51
+ /**
52
+ * Iteratively test collction of positive test data objects each containing:
53
+ * config: object containing formula config
54
+ * result: the calculated formula(s) output value.
55
+ *
56
+ * Test record structure:
57
+ * ----------------------
58
+ * {
59
+ * config: {
60
+ * formula: { ... }
61
+ * },
62
+ * result: ''
63
+ * }
64
+ *
65
+ */
66
+
67
+ [{
68
+ config: {
69
+ formula: {
70
+ name: 'minus',
71
+ args: [{
72
+ field: 'totalPerson'
73
+ }, {
74
+ field: 'personProcessed'
75
+ }]
76
+ }
77
+ },
78
+ result: '1'
79
+ }, {
80
+ config: {
81
+ formula: {
82
+ name: 'plus',
83
+ args: [{
84
+ field: 'totalPerson'
85
+ }, {
86
+ field: 'personProcessed'
87
+ }]
88
+ }
89
+ },
90
+ result: '7'
91
+ }, {
92
+ config: {
93
+ formula: {
94
+ name: 'multiply',
95
+ args: [{
96
+ field: 'totalPerson'
97
+ }, {
98
+ field: 'personProcessed'
99
+ }]
100
+ }
101
+ },
102
+ result: '12'
103
+ }, {
104
+ config: {
105
+ formula: {
106
+ name: 'divide',
107
+ round: 2,
108
+ args: [{
109
+ field: 'totalPerson'
110
+ }, {
111
+ field: 'personProcessed'
112
+ }]
113
+ }
114
+ },
115
+ result: '1.33'
116
+ }, {
117
+ config: {
118
+ formula: {
119
+ name: 'multiply',
120
+ round: 2,
121
+ args: [{
122
+ formula: {
123
+ name: 'divide',
124
+ args: [{
125
+ field: 'totalPerson'
126
+ }, {
127
+ field: 'personProcessed'
128
+ }]
129
+ }
130
+ }, {
131
+ value: 200
132
+ }]
133
+ }
134
+ },
135
+ result: '266.67'
136
+ }].forEach(function (test) {
137
+ it("should render 'calculation' component with formula to given calculated value: ".concat(test.result), function () {
138
+ var _render = (0, _react.render)((0, _getComponent.default)(_objectSpread(_objectSpread({}, COMPONENT), test.config))),
139
+ container = _render.container;
140
+
141
+ var _getAllByTestId = (0, _react.getAllByTestId)(container, ID),
142
+ _getAllByTestId2 = _slicedToArray(_getAllByTestId, 2),
143
+ formGroup = _getAllByTestId2[0],
144
+ input = _getAllByTestId2[1];
145
+
146
+ expect(formGroup.tagName).toEqual('DIV');
147
+ expect(formGroup.classList).toContain('govuk-form-group');
148
+ var label = formGroup.childNodes[0];
149
+ expect(label.tagName).toEqual('LABEL');
150
+ expect(label.classList).toContain('govuk-label');
151
+ expect(label.textContent).toContain(LABEL);
152
+ expect(formGroup.classList).toContain('govuk-form-group');
153
+ expect(input.tagName).toEqual('INPUT');
154
+ expect(input.classList).toContain('govuk-input');
155
+ expect(input.id).toEqual(ID);
156
+ expect(input.getAttribute('readonly')).not.toBeNull();
157
+ expect(input.getAttribute('value')).toEqual(test.result);
158
+ });
159
+ });
160
+ /**
161
+ * Iteratively test collction of negative test data objects each containing:
162
+ * config: object containing invalid formula config
163
+ * result: the error to match.
164
+ *
165
+ * Test record structure:
166
+ * ----------------------
167
+ * {
168
+ * config: {
169
+ * formula: { ... }
170
+ * },
171
+ * error: 'error_message'
172
+ * }
173
+ *
174
+ */
175
+
176
+ [{
177
+ error: "Missing 'formula' definition"
178
+ }, {
179
+ config: {
180
+ formula: {}
181
+ },
182
+ error: "Calculation formula 'name' cannot be empty"
183
+ }, {
184
+ config: {
185
+ formula: {
186
+ name: 'tictactoe',
187
+ args: [{
188
+ field: 'totalPerson'
189
+ }, {
190
+ field: 'personProcessed'
191
+ }]
192
+ }
193
+ },
194
+ error: "Unsupported operation 'tictactoe'"
195
+ }, {
196
+ config: {
197
+ formula: {
198
+ name: 'minus',
199
+ args: [{
200
+ field: 'totalPerson',
201
+ value: 100
202
+ }, {
203
+ field: 'personProcessed'
204
+ }]
205
+ }
206
+ },
207
+ error: 'Argument cannot have more than one reference'
208
+ }, {
209
+ config: {
210
+ formula: {
211
+ name: 'minus',
212
+ args: [{
213
+ newToken: 'totalPerson'
214
+ }, {
215
+ field: 'personProcessed'
216
+ }]
217
+ }
218
+ },
219
+ error: 'Only accept following as argument field: {field, value, or formula}'
220
+ }, {
221
+ config: {
222
+ formula: {
223
+ name: 'minus',
224
+ args: [{
225
+ field: 'totalPerson'
226
+ }]
227
+ }
228
+ },
229
+ error: 'Requires more than one argument for calculation'
230
+ }, {
231
+ config: {
232
+ formula: {
233
+ name: 'multiply',
234
+ round: 2,
235
+ args: [{
236
+ value: 200,
237
+ field: 'totalPerson'
238
+ }, {
239
+ value: 200
240
+ }]
241
+ }
242
+ },
243
+ error: 'Argument cannot have more than one reference'
244
+ }].forEach(function (test) {
245
+ it("should return an appropriately rendered calculation component with message ".concat(test.result), function () {
246
+ var _render2 = (0, _react.render)((0, _getComponent.default)(_objectSpread(_objectSpread({}, COMPONENT), test.config))),
247
+ container = _render2.container;
248
+
249
+ var _getAllByTestId3 = (0, _react.getAllByTestId)(container, ID),
250
+ _getAllByTestId4 = _slicedToArray(_getAllByTestId3, 2),
251
+ formGroup = _getAllByTestId4[0],
252
+ input = _getAllByTestId4[1];
253
+
254
+ expect(formGroup.tagName).toEqual('DIV');
255
+ expect(formGroup.classList).toContain('govuk-form-group');
256
+ var label = formGroup.childNodes[0];
257
+ expect(label.tagName).toEqual('LABEL');
258
+ expect(label.classList).toContain('govuk-label');
259
+ expect(label.textContent).toContain(LABEL);
260
+ expect(formGroup.classList).toContain('govuk-form-group');
261
+ expect(input.tagName).toEqual('INPUT');
262
+ expect(input.classList).toContain('govuk-input');
263
+ expect(input.id).toEqual(ID);
264
+ expect(input.getAttribute('readonly')).not.toBeNull();
265
+ expect(error).toBeCalledTimes(1);
266
+ expect(error).toBeCalledWith(test.error);
267
+ });
268
+ });
269
+ });
@@ -8,7 +8,7 @@ exports.default = exports.EDITABLE_TYPES = void 0;
8
8
  var _models = require("../../models");
9
9
 
10
10
  // Local imports
11
- var EDITABLE_TYPES = [_models.ComponentTypes.AUTOCOMPLETE, _models.ComponentTypes.CHECKBOXES, _models.ComponentTypes.DATE, _models.ComponentTypes.EMAIL, _models.ComponentTypes.FILE, _models.ComponentTypes.PHONE_NUMBER, _models.ComponentTypes.RADIOS, _models.ComponentTypes.SELECT, _models.ComponentTypes.TEXT, _models.ComponentTypes.TEXT_AREA, _models.ComponentTypes.TIME];
11
+ var EDITABLE_TYPES = [_models.ComponentTypes.AUTOCOMPLETE, _models.ComponentTypes.CALCULATION, _models.ComponentTypes.CHECKBOXES, _models.ComponentTypes.DATE, _models.ComponentTypes.EMAIL, _models.ComponentTypes.FILE, _models.ComponentTypes.PHONE_NUMBER, _models.ComponentTypes.RADIOS, _models.ComponentTypes.SELECT, _models.ComponentTypes.TEXT, _models.ComponentTypes.TEXT_AREA, _models.ComponentTypes.TIME];
12
12
  exports.EDITABLE_TYPES = EDITABLE_TYPES;
13
13
 
14
14
  var isEditable = function isEditable(options) {
@@ -67,6 +67,11 @@ var meetsCondition = function meetsCondition(condition, value) {
67
67
  return true;
68
68
  }
69
69
 
70
+ case 'contains':
71
+ {
72
+ return value === null || value === void 0 ? void 0 : value.toString().toLowerCase().includes(compare); // If no value is provided, the field cannot contain it, so it must fail the condition.
73
+ }
74
+
70
75
  default:
71
76
  return false;
72
77
  }
@@ -261,6 +261,95 @@ describe('utils.Condition.meetsCondition', function () {
261
261
  });
262
262
  });
263
263
  });
264
+ describe('operator contains', function () {
265
+ var op = 'contains'; // Should match...
266
+
267
+ it('should match a string that is in the field array', function () {
268
+ var FIELD = ['alpha', 'bravo', 'charlie'];
269
+ var VALUE = 'alpha';
270
+ var CONDITION = getCondition(op, VALUE);
271
+ expect((0, _meetsCondition.default)(CONDITION, FIELD)).toBeTruthy();
272
+ });
273
+ it('should match a sub-string that is in the field array', function () {
274
+ var FIELD = ['alpha', 'bravo', 'charlie'];
275
+ var VALUE = 'alp';
276
+ var CONDITION = getCondition(op, VALUE);
277
+ expect((0, _meetsCondition.default)(CONDITION, FIELD)).toBeTruthy();
278
+ });
279
+ it('should match a number that is in the field array', function () {
280
+ var FIELD = [1, 2, 3];
281
+ var VALUE = 1;
282
+ var CONDITION = getCondition(op, VALUE);
283
+ expect((0, _meetsCondition.default)(CONDITION, FIELD)).toBeTruthy();
284
+ });
285
+ it('should match a sub-string that is in the field string', function () {
286
+ var FIELD = 'alphabravocharlie';
287
+ var VALUE = 'alpha';
288
+ var CONDITION = getCondition(op, VALUE);
289
+ expect((0, _meetsCondition.default)(CONDITION, FIELD)).toBeTruthy();
290
+ });
291
+ it('should match a string that is in the field array regardless of case', function () {
292
+ var FIELD = ['Alpha', 'bravo', 'charlie'];
293
+ var VALUE = 'alpha';
294
+ var CONDITION = getCondition(op, VALUE);
295
+ expect((0, _meetsCondition.default)(CONDITION, FIELD)).toBeTruthy();
296
+ }); // Should reject...
297
+
298
+ it('should reject a string that is missing from the field array', function () {
299
+ var FIELD = ['alpha', 'bravo', 'charlie'];
300
+ var VALUE = 'delta';
301
+ var CONDITION = getCondition(op, VALUE);
302
+ expect((0, _meetsCondition.default)(CONDITION, FIELD)).toBeFalsy();
303
+ });
304
+ it('should reject a number that is missing from the field array', function () {
305
+ var FIELD = [1, 2, 3];
306
+ var VALUE = 4;
307
+ var CONDITION = getCondition(op, VALUE);
308
+ expect((0, _meetsCondition.default)(CONDITION, FIELD)).toBeFalsy();
309
+ });
310
+ it('should reject a substring that is missing from the field string', function () {
311
+ var FIELD = 'alphabravocharlie';
312
+ var VALUE = 'delta';
313
+ var CONDITION = getCondition(op, VALUE);
314
+ expect((0, _meetsCondition.default)(CONDITION, FIELD)).toBeFalsy();
315
+ });
316
+ it('should reject any value when the field is an empty Array', function () {
317
+ var FIELD = [];
318
+ var VALUE = 'alpha';
319
+ var CONDITION = getCondition(op, VALUE);
320
+ expect((0, _meetsCondition.default)(CONDITION, FIELD)).toBeFalsy();
321
+ });
322
+ it('should reject any value when the field is an empty string', function () {
323
+ var FIELD = '';
324
+ var VALUE = 'alpha';
325
+ var CONDITION = getCondition(op, VALUE);
326
+ expect((0, _meetsCondition.default)(CONDITION, FIELD)).toBeFalsy();
327
+ });
328
+ it('should reject any field when the value is null', function () {
329
+ var FIELD = ['alpha', 'bravo', 'charlie'];
330
+ var VALUE = null;
331
+ var CONDITION = getCondition(op, VALUE);
332
+ expect((0, _meetsCondition.default)(CONDITION, FIELD)).toBeFalsy();
333
+ });
334
+ it('should reject any field when the value is undefined', function () {
335
+ var FIELD = ['alpha', 'bravo', 'charlie'];
336
+ var VALUE = undefined;
337
+ var CONDITION = getCondition(op, VALUE);
338
+ expect((0, _meetsCondition.default)(CONDITION, FIELD)).toBeFalsy();
339
+ });
340
+ it('should reject any value when the field is null', function () {
341
+ var FIELD = null;
342
+ var VALUE = 'alpha';
343
+ var CONDITION = getCondition(op, VALUE);
344
+ expect((0, _meetsCondition.default)(CONDITION, FIELD)).toBeFalsy();
345
+ });
346
+ it('should reject any value when the field is undefined', function () {
347
+ var FIELD = undefined;
348
+ var VALUE = 'alpha';
349
+ var CONDITION = getCondition(op, VALUE);
350
+ expect((0, _meetsCondition.default)(CONDITION, FIELD)).toBeFalsy();
351
+ });
352
+ });
264
353
  describe('unknown operator', function () {
265
354
  var op = 'definitely_not_a_real_operator';
266
355
  it('should reject anything regardless of the value', function () {
@@ -0,0 +1,121 @@
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
+ function ownKeys(object, enumerableOnly) { var keys = Object.keys(object); if (Object.getOwnPropertySymbols) { var symbols = Object.getOwnPropertySymbols(object); if (enumerableOnly) { symbols = symbols.filter(function (sym) { return Object.getOwnPropertyDescriptor(object, sym).enumerable; }); } keys.push.apply(keys, symbols); } return keys; }
11
+
12
+ function _objectSpread(target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i] != null ? arguments[i] : {}; if (i % 2) { ownKeys(Object(source), true).forEach(function (key) { _defineProperty(target, key, source[key]); }); } else if (Object.getOwnPropertyDescriptors) { Object.defineProperties(target, Object.getOwnPropertyDescriptors(source)); } else { ownKeys(Object(source)).forEach(function (key) { Object.defineProperty(target, key, Object.getOwnPropertyDescriptor(source, key)); }); } } return target; }
13
+
14
+ 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; }
15
+
16
+ var applyFormula = function applyFormula(config) {
17
+ try {
18
+ var result = simplify(config);
19
+ return !result && result !== 0 ? '' : result;
20
+ } catch (err) {
21
+ console.error(err.message);
22
+ }
23
+
24
+ return '';
25
+ };
26
+
27
+ var simplify = function simplify(config) {
28
+ if (!config || !config.formula) {
29
+ throw new Error("Missing 'formula' definition");
30
+ }
31
+
32
+ var _config$formula = _objectSpread({}, config.formula),
33
+ name = _config$formula.name;
34
+
35
+ switch (name) {
36
+ case 'multiply':
37
+ return reduceNumber(config, function (t, c) {
38
+ return t * c;
39
+ });
40
+
41
+ case 'divide':
42
+ return reduceNumber(config, function (t, c) {
43
+ return t / c;
44
+ });
45
+
46
+ case 'plus':
47
+ return reduceNumber(config, function (t, c) {
48
+ return t + c;
49
+ });
50
+
51
+ case 'minus':
52
+ return reduceNumber(config, function (t, c) {
53
+ return t - c;
54
+ });
55
+
56
+ default:
57
+ throw new Error(!name ? "Calculation formula 'name' cannot be empty" : "Unsupported operation '".concat(name, "'"));
58
+ }
59
+ };
60
+
61
+ var reduceNumber = function reduceNumber(config, reduction) {
62
+ var _config$formula2 = _objectSpread({}, config.formula),
63
+ args = _config$formula2.args;
64
+
65
+ if (args.length < 2) {
66
+ throw new Error('Requires more than one argument for calculation');
67
+ }
68
+
69
+ return round(args.map(function (a) {
70
+ return getValue(a, config.formData);
71
+ }).reduce(function (total, current, index) {
72
+ return index === 0 ? current : reduction(total, current);
73
+ }, 0), config);
74
+ };
75
+
76
+ var round = function round(number, config) {
77
+ var round = config.formula.round;
78
+ if (!round && round !== 0 || !number) return number;
79
+
80
+ if (round === 0) {
81
+ return parseInt(number);
82
+ } else {
83
+ var precisionScale = Math.pow(10, round);
84
+ return Math.round((number + Number.EPSILON) * precisionScale) / precisionScale;
85
+ }
86
+ };
87
+
88
+ var getValue = function getValue(arg, formData) {
89
+ var keys = Object.keys(arg);
90
+
91
+ if (keys.length === 1) {
92
+ var key = keys[0];
93
+ var val = arg[key];
94
+
95
+ switch (key) {
96
+ case 'field':
97
+ var fieldVal = fieldValue(val, formData);
98
+ return !fieldVal && fieldVal !== 0 ? NaN : parseFloat(fieldVal);
99
+
100
+ case 'value':
101
+ return parseFloat(val);
102
+
103
+ case 'formula':
104
+ return applyFormula(_objectSpread(_objectSpread({}, arg), {}, {
105
+ formData: formData
106
+ }));
107
+
108
+ default:
109
+ throw new Error('Only accept following as argument field: {field, value, or formula}');
110
+ }
111
+ } else {
112
+ throw new Error('Argument cannot have more than one reference');
113
+ }
114
+ };
115
+
116
+ var fieldValue = function fieldValue(field, data) {
117
+ return _copReactComponents.Utils.interpolateString('${' + field + '}', data);
118
+ };
119
+
120
+ var _default = applyFormula;
121
+ exports.default = _default;
@@ -0,0 +1,264 @@
1
+ "use strict";
2
+
3
+ var _applyFormula = _interopRequireDefault(require("./applyFormula"));
4
+
5
+ function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
6
+
7
+ // Local imports
8
+ describe('utils.Data.applyFormula', function () {
9
+ var error = jest.spyOn(console, 'error').mockImplementation(function () {});
10
+ afterAll(function () {
11
+ error.mockReset();
12
+ });
13
+ afterEach(function () {
14
+ error.mockClear();
15
+ });
16
+ it('should throw and handle exception for a null config', function () {
17
+ (0, _applyFormula.default)(null);
18
+ expect(error).toBeCalledWith("Missing 'formula' definition");
19
+ });
20
+ it("should throw and handle exception for config with missing 'formula'", function () {
21
+ (0, _applyFormula.default)({});
22
+ expect(error).toBeCalledWith("Missing 'formula' definition");
23
+ });
24
+ it("should throw and handle exception for config with 'formula' but missing 'name'", function () {
25
+ (0, _applyFormula.default)({
26
+ formula: {}
27
+ });
28
+ expect(error).toBeCalledWith("Calculation formula 'name' cannot be empty");
29
+ });
30
+ it("should throw and handle exception for config with 'formula' with unsupported operation 'name'", function () {
31
+ (0, _applyFormula.default)({
32
+ formula: {
33
+ name: "something"
34
+ }
35
+ });
36
+ expect(error).toBeCalledWith("Unsupported operation 'something'");
37
+ });
38
+ it("should throw and handle exception for 'formula' with wrong argument name", function () {
39
+ (0, _applyFormula.default)({
40
+ formula: {
41
+ name: 'plus',
42
+ args: [{
43
+ somthing: 1
44
+ }, {
45
+ somthing: 1
46
+ }]
47
+ }
48
+ });
49
+ expect(error).toBeCalledWith('Only accept following as argument field: {field, value, or formula}');
50
+ });
51
+ it("should throw and handle exception for 'formula' argument with more than one field", function () {
52
+ (0, _applyFormula.default)({
53
+ formula: {
54
+ name: 'plus',
55
+ args: [{
56
+ value: 1,
57
+ field: 'fieldA'
58
+ }, {
59
+ value: 1
60
+ }]
61
+ }
62
+ });
63
+ expect(error).toBeCalledWith('Argument cannot have more than one reference');
64
+ });
65
+ it("should throw and handle exception for 'formula' with single argument", function () {
66
+ (0, _applyFormula.default)({
67
+ formula: {
68
+ name: 'plus',
69
+ args: [{
70
+ value: 10
71
+ }]
72
+ }
73
+ });
74
+ expect(error).toBeCalledWith('Requires more than one argument for calculation');
75
+ });
76
+ var DATA = {
77
+ fieldA: '10',
78
+ fieldB: '20',
79
+ fieldC: 'abc',
80
+ fieldD: '0',
81
+ fieldE: '10'
82
+ };
83
+ [{
84
+ name: 'plus',
85
+ args: [{
86
+ field: 'fieldA'
87
+ }, {
88
+ field: 'fieldB'
89
+ }],
90
+ result: 30
91
+ }, {
92
+ name: 'minus',
93
+ args: [{
94
+ field: 'fieldB'
95
+ }, {
96
+ field: 'fieldA'
97
+ }],
98
+ result: 10
99
+ }, {
100
+ name: 'multiply',
101
+ args: [{
102
+ field: 'fieldA'
103
+ }, {
104
+ field: 'fieldB'
105
+ }],
106
+ result: 200
107
+ }, {
108
+ name: 'divide',
109
+ args: [{
110
+ field: 'fieldA'
111
+ }, {
112
+ field: 'fieldB'
113
+ }],
114
+ result: 0.5
115
+ }, {
116
+ name: 'divide',
117
+ args: [{
118
+ field: 'fieldA'
119
+ }, {
120
+ field: 'fieldD'
121
+ }],
122
+ result: Infinity
123
+ }, {
124
+ name: 'plus',
125
+ args: [{
126
+ field: 'fieldA'
127
+ }, {
128
+ field: 'fieldC'
129
+ }],
130
+ result: ''
131
+ }, {
132
+ name: 'multiply',
133
+ args: [{
134
+ field: 'fieldA'
135
+ }, {
136
+ field: 'fieldB'
137
+ }, {
138
+ field: 'fieldE'
139
+ }],
140
+ result: 2000
141
+ }].forEach(function (test) {
142
+ it("should calculate formula '".concat(test.name, "' correctly for field args to '").concat(test.result, "'"), function () {
143
+ var config = {
144
+ formData: DATA,
145
+ formula: {
146
+ name: test.name,
147
+ args: test.args
148
+ }
149
+ };
150
+ expect((0, _applyFormula.default)(config)).toEqual(test.result);
151
+ });
152
+ });
153
+ [{
154
+ fieldA: '1',
155
+ fieldB: '3',
156
+ round: 5,
157
+ result: 0.33333
158
+ }, {
159
+ fieldA: '2',
160
+ fieldB: '3',
161
+ round: 5,
162
+ result: 0.66667
163
+ }, {
164
+ fieldA: '1',
165
+ fieldB: '3',
166
+ round: 4,
167
+ result: 0.3333
168
+ }, {
169
+ fieldA: '2',
170
+ fieldB: '3',
171
+ round: 4,
172
+ result: 0.6667
173
+ }, {
174
+ fieldA: '1',
175
+ fieldB: '3',
176
+ round: 3,
177
+ result: 0.333
178
+ }, {
179
+ fieldA: '2',
180
+ fieldB: '3',
181
+ round: 3,
182
+ result: 0.667
183
+ }, {
184
+ fieldA: '1',
185
+ fieldB: '3',
186
+ round: 2,
187
+ result: 0.33
188
+ }, {
189
+ fieldA: '2',
190
+ fieldB: '3',
191
+ round: 2,
192
+ result: 0.67
193
+ }, {
194
+ fieldA: '1',
195
+ fieldB: '3',
196
+ round: 1,
197
+ result: 0.3
198
+ }, {
199
+ fieldA: '2',
200
+ fieldB: '3',
201
+ round: 1,
202
+ result: 0.7
203
+ }, {
204
+ fieldA: '1',
205
+ fieldB: '3',
206
+ round: 0,
207
+ result: 0
208
+ }, {
209
+ fieldA: '2',
210
+ fieldB: '3',
211
+ round: 0,
212
+ result: 0
213
+ }, {
214
+ fieldA: 'L',
215
+ fieldB: 'G3',
216
+ round: 1,
217
+ result: ''
218
+ }].forEach(function (test) {
219
+ it("should calculdate and round result to precision ".concat(test.round), function () {
220
+ var config = {
221
+ formData: {
222
+ fieldA: test.fieldA,
223
+ fieldB: test.fieldB
224
+ },
225
+ formula: {
226
+ name: 'divide',
227
+ round: test.round,
228
+ args: [{
229
+ field: 'fieldA'
230
+ }, {
231
+ field: 'fieldB'
232
+ }]
233
+ }
234
+ };
235
+ expect((0, _applyFormula.default)(config)).toEqual(test.result);
236
+ });
237
+ });
238
+ it("should calculate nested 'formula'", function () {
239
+ var config = {
240
+ formData: {
241
+ fieldA: '19',
242
+ fieldB: '66'
243
+ },
244
+ formula: {
245
+ name: 'multiply',
246
+ round: 2,
247
+ args: [{
248
+ formula: {
249
+ name: 'divide',
250
+ round: 4,
251
+ args: [{
252
+ field: 'fieldA'
253
+ }, {
254
+ field: 'fieldB'
255
+ }]
256
+ }
257
+ }, {
258
+ value: 100
259
+ }]
260
+ }
261
+ };
262
+ expect((0, _applyFormula.default)(config)).toEqual(28.79);
263
+ });
264
+ });
@@ -5,6 +5,8 @@ Object.defineProperty(exports, "__esModule", {
5
5
  });
6
6
  exports.default = void 0;
7
7
 
8
+ var _applyFormula = _interopRequireDefault(require("./applyFormula"));
9
+
8
10
  var _getAutocompleteSource = _interopRequireDefault(require("./getAutocompleteSource"));
9
11
 
10
12
  var _getDataPath = _interopRequireDefault(require("./getDataPath"));
@@ -25,6 +27,7 @@ function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { de
25
27
 
26
28
  // Local imports
27
29
  var Data = {
30
+ applyFormula: _applyFormula.default,
28
31
  getAutocompleteSource: _getAutocompleteSource.default,
29
32
  getDataPath: _getDataPath.default,
30
33
  getOptions: _getOptions.default,
@@ -5,6 +5,8 @@ Object.defineProperty(exports, "__esModule", {
5
5
  });
6
6
  exports.default = void 0;
7
7
 
8
+ var _dayjs = _interopRequireDefault(require("dayjs"));
9
+
8
10
  var _getSourceData = _interopRequireDefault(require("./getSourceData"));
9
11
 
10
12
  function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
@@ -15,6 +17,56 @@ function _objectSpread(target) { for (var i = 1; i < arguments.length; i++) { va
15
17
 
16
18
  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
19
 
20
+ var setDefaultDateValue = function setDefaultDateValue(date, data) {
21
+ if (date.defaultValue === 'today') {
22
+ data[date.fieldId] = (0, _dayjs.default)().format('DD-MM-YYYY');
23
+ } else {
24
+ data[date.fieldId] = date.defaultValue;
25
+ }
26
+ };
27
+ /**
28
+ * This currently will not work for collections or containers.
29
+ * If support is required it will need to be added.
30
+ */
31
+
32
+
33
+ var setupDefaultValue = function setupDefaultValue(component, data) {
34
+ if (component.defaultValue && !data[component.fieldId]) {
35
+ switch (component.type) {
36
+ case 'date':
37
+ setDefaultDateValue(component, data);
38
+ break;
39
+
40
+ default:
41
+ data[component.fieldId] = component.defaultValue;
42
+ }
43
+ }
44
+
45
+ if (component.defaultValue) {
46
+ // Some components will throw warnings when having
47
+ // both a 'value' and 'defaultValue' prop set.
48
+ // defaultValue is safe to delete once we've tried
49
+ // to use it.
50
+ delete component.defaultValue;
51
+ }
52
+ };
53
+
54
+ var setupDefaultValuesForComponents = function setupDefaultValuesForComponents(components, data) {
55
+ components.forEach(function (component) {
56
+ return setupDefaultValue(component, data);
57
+ });
58
+ };
59
+
60
+ var setupPageDefaultValues = function setupPageDefaultValues(pages, data) {
61
+ pages.forEach(function (page) {
62
+ page.components.filter(function (c) {
63
+ return !c.use;
64
+ }).forEach(function (component) {
65
+ return setupDefaultValue(component, data);
66
+ });
67
+ });
68
+ };
69
+
18
70
  var setupComponentSourceData = function setupComponentSourceData(component, data) {
19
71
  if (component.source) {
20
72
  data[component.fieldId] = (0, _getSourceData.default)(data, component.source.field);
@@ -37,8 +89,11 @@ var setupPageSourceData = function setupPageSourceData(pages, data) {
37
89
  });
38
90
  };
39
91
  /**
40
- * This populates an object with data from source fields.
41
- * Note that this doesn't currently support sequenced dependencies:
92
+ * This populates an object with data either from a specified default
93
+ * value or from a source field. If both are specified, data from a
94
+ * source field will take priority.
95
+ * Note that in the case of source fields, this doesn't currently
96
+ * support sequenced dependencies:
42
97
  * 0: fieldA: source.field = fieldB
43
98
  * 1: fieldB: source.field = fieldC
44
99
  * 2: fieldC: 'value'
@@ -54,6 +109,8 @@ var setupPageSourceData = function setupPageSourceData(pages, data) {
54
109
  var setupFormData = function setupFormData(pages, components, baseData) {
55
110
  var data = _objectSpread({}, baseData);
56
111
 
112
+ setupDefaultValuesForComponents(components, data);
113
+ setupPageDefaultValues(pages, data);
57
114
  setupSourceDataForComponents(components, data);
58
115
  setupPageSourceData(pages, data);
59
116
  return data;
@@ -211,6 +211,66 @@ describe('utils', function () {
211
211
  delegateEmails: _userProfileData.default.userDetails.delegateEmails
212
212
  }));
213
213
  });
214
+ it('should handle a default value field', function () {
215
+ var PAGES = [];
216
+ var COMPONENTS = [{
217
+ fieldId: 'testField',
218
+ type: 'text',
219
+ defaultValue: 'VALUE'
220
+ }];
221
+ var DATA = {};
222
+ var RESULT = (0, _setupFormData.default)(PAGES, COMPONENTS, DATA);
223
+ expect(RESULT).toEqual({
224
+ testField: 'VALUE'
225
+ });
226
+ });
227
+ it('should ignore a default value when a value already exists in data', function () {
228
+ var PAGES = [];
229
+ var COMPONENTS = [{
230
+ fieldId: 'testField',
231
+ type: 'text',
232
+ defaultValue: 'VALUE'
233
+ }];
234
+ var DATA = {
235
+ testField: 'EXISTING_VALUE'
236
+ };
237
+ var RESULT = (0, _setupFormData.default)(PAGES, COMPONENTS, DATA);
238
+ expect(RESULT).toEqual({
239
+ testField: 'EXISTING_VALUE'
240
+ });
241
+ });
242
+ it('should handle default values within pages', function () {
243
+ var PAGES = [{
244
+ components: [{
245
+ fieldId: 'pageField',
246
+ type: 'text',
247
+ defaultValue: 'VALUE'
248
+ }]
249
+ }];
250
+ var COMPONENTS = [];
251
+ var DATA = {};
252
+ var RESULT = (0, _setupFormData.default)(PAGES, COMPONENTS, DATA);
253
+ expect(RESULT).toEqual({
254
+ pageField: 'VALUE'
255
+ });
256
+ });
257
+ it('should ignore default values within pages when a value already exists in data', function () {
258
+ var PAGES = [{
259
+ components: [{
260
+ fieldId: 'pageField',
261
+ type: 'text',
262
+ defaultValue: 'VALUE'
263
+ }]
264
+ }];
265
+ var COMPONENTS = [];
266
+ var DATA = {
267
+ pageField: 'EXISTING_VALUE'
268
+ };
269
+ var RESULT = (0, _setupFormData.default)(PAGES, COMPONENTS, DATA);
270
+ expect(RESULT).toEqual({
271
+ pageField: 'EXISTING_VALUE'
272
+ });
273
+ });
214
274
  });
215
275
  });
216
276
  });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ukhomeoffice/cop-react-form-renderer",
3
- "version": "3.7.0-alpha",
3
+ "version": "3.10.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.9.5",
19
+ "@ukhomeoffice/cop-react-components": "1.10.0",
20
20
  "axios": "^0.23.0",
21
21
  "dayjs": "^1.11.0",
22
22
  "govuk-frontend": "^3.13.0",