@ukhomeoffice/cop-react-form-renderer 3.8.0 → 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.
- package/dist/models/ComponentTypes.js +2 -0
- package/dist/utils/Component/getComponent.js +13 -0
- package/dist/utils/Component/getComponentTests/getComponent.calculation.test.js +269 -0
- package/dist/utils/Component/isEditable.js +1 -1
- package/dist/utils/Condition/meetsCondition.js +5 -0
- package/dist/utils/Condition/meetsCondition.test.js +89 -0
- package/dist/utils/Data/applyFormula.js +121 -0
- package/dist/utils/Data/applyFormula.test.js +264 -0
- package/dist/utils/Data/index.js +3 -0
- package/package.json +2 -2
|
@@ -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
|
+
});
|
package/dist/utils/Data/index.js
CHANGED
|
@@ -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,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ukhomeoffice/cop-react-form-renderer",
|
|
3
|
-
"version": "3.
|
|
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.
|
|
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",
|