@ukhomeoffice/cop-react-form-renderer 6.15.0-alpha → 6.15.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/components/CollectionSummary/BannerStrip.scss +4 -0
- package/dist/components/FormPage/FormPage.js +8 -10
- package/dist/components/FormRenderer/FormRenderer.js +5 -10
- package/dist/components/FormRenderer/clear-uncompleted-routes/test-data/forms/form-hidden-component-show-when-in-component-and-page.json +62 -0
- package/dist/components/FormRenderer/clear-uncompleted-routes/test-data/forms/form-hidden-page-same-component-reused.json +61 -0
- package/dist/components/FormRenderer/clear-uncompleted-routes/test-data/forms/form-page-same-component-reused-one-shown.json +74 -0
- package/dist/components/FormRenderer/clear-uncompleted-routes/test-data/input/cop-airpax-change-what-happened-before.json +300 -0
- package/dist/components/FormRenderer/clear-uncompleted-routes/test-data/input/cop-mandec-remove-unspent-convictions-before.json +143 -0
- package/dist/components/FormRenderer/clear-uncompleted-routes/test-data/input/data-hidden-component-show-when-in-component-and-page.json +6 -0
- package/dist/components/FormRenderer/clear-uncompleted-routes/test-data/input/data-hidden-page-same-component-reused.json +6 -0
- package/dist/components/FormRenderer/clear-uncompleted-routes/test-data/input/data-page-same-component-reused-one-shown.json +8 -0
- package/dist/components/FormRenderer/clear-uncompleted-routes/test-data/output/cop-airpax-change-what-happened-after.json +280 -0
- package/dist/components/FormRenderer/clear-uncompleted-routes/test-data/output/cop-mandec-remove-unspent-convictions-after.json +127 -0
- package/dist/components/FormRenderer/clear-uncompleted-routes/test-data/output/data-hidden-component-show-when-in-component-and-page-removed.json +5 -0
- package/dist/components/FormRenderer/clear-uncompleted-routes/test-data/output/data-hidden-page-same-component-reused-removed.json +5 -0
- package/dist/components/FormRenderer/clear-uncompleted-routes/test-data/output/data-page-same-component-reused-one-shown-removed.json +7 -0
- package/dist/components/FormRenderer/helpers/clearOutUncompletedRoutes.js +259 -282
- package/dist/components/FormRenderer/helpers/clearOutUncompletedRoutes.test.js +77 -26
- package/dist/components/FormRenderer/helpers/clearOutUncompletedRoutesUtils.js +381 -0
- package/dist/components/FormRenderer/helpers/clearOutUncompletedRoutesUtils.test.js +559 -0
- package/dist/components/FormRenderer/onCYAAction.js +12 -0
- package/dist/components/FormRenderer/onCYAAction.test.js +5 -0
- package/dist/hooks/useGetRequest.js +15 -15
- package/dist/hooks/useRefData.js +3 -2
- package/dist/utils/Component/getComponentTests/getComponent.multifile.test.js +2 -1
- package/dist/utils/Component/getDefaultValueFromConfig.js +2 -1
- package/dist/utils/Condition/meetsCondition.js +26 -12
- package/dist/utils/Condition/meetsCondition.test.js +21 -0
- package/dist/utils/Data/getAutocompleteSource.js +68 -51
- package/dist/utils/Data/getAutocompleteSource.test.js +31 -18
- package/dist/utils/Operate/doesContainValue.js +34 -0
- package/dist/utils/Operate/doesContainValue.test.js +75 -0
- package/dist/utils/Operate/runPageOperations.js +2 -0
- package/dist/utils/Validate/validateOnPageLoad.js +23 -0
- package/dist/utils/Validate/validateOnPageLoad.test.js +88 -0
- package/package.json +4 -4
- package/dist/components/FormRenderer/helpers/deleteNodeByPath.js +0 -26
- package/dist/components/FormRenderer/helpers/deleteNodeByPath.test.js +0 -56
|
@@ -0,0 +1,559 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
var Utils = _interopRequireWildcard(require("./clearOutUncompletedRoutesUtils"));
|
|
4
|
+
var _Condition = _interopRequireDefault(require("../../../utils/Condition"));
|
|
5
|
+
function _interopRequireDefault(e) { return e && e.__esModule ? e : { default: e }; }
|
|
6
|
+
function _getRequireWildcardCache(e) { if ("function" != typeof WeakMap) return null; var r = new WeakMap(), t = new WeakMap(); return (_getRequireWildcardCache = function (e) { return e ? t : r; })(e); }
|
|
7
|
+
function _interopRequireWildcard(e, r) { if (!r && e && e.__esModule) return e; if (null === e || "object" != typeof e && "function" != typeof e) return { default: e }; var t = _getRequireWildcardCache(r); if (t && t.has(e)) return t.get(e); var n = { __proto__: null }, a = Object.defineProperty && Object.getOwnPropertyDescriptor; for (var u in e) if ("default" !== u && {}.hasOwnProperty.call(e, u)) { var i = a ? Object.getOwnPropertyDescriptor(e, u) : null; i && (i.get || i.set) ? Object.defineProperty(n, u, i) : n[u] = e[u]; } return n.default = e, t && t.set(e, n), n; }
|
|
8
|
+
describe('deleteNodeByPath', () => {
|
|
9
|
+
it('delete where node is at parent level', () => {
|
|
10
|
+
const nested = {
|
|
11
|
+
type: "Example form",
|
|
12
|
+
alpha: "alpha",
|
|
13
|
+
bravo: "bravo",
|
|
14
|
+
person: {
|
|
15
|
+
charlie: "Smith",
|
|
16
|
+
delta: "Acme Inc"
|
|
17
|
+
},
|
|
18
|
+
gamma: "gamma",
|
|
19
|
+
epsilon: "epsilon"
|
|
20
|
+
};
|
|
21
|
+
const path = 'type';
|
|
22
|
+
Utils.deleteNodeByPath(nested, path);
|
|
23
|
+
expect(nested).toEqual({
|
|
24
|
+
alpha: "alpha",
|
|
25
|
+
bravo: "bravo",
|
|
26
|
+
person: {
|
|
27
|
+
charlie: "Smith",
|
|
28
|
+
delta: "Acme Inc"
|
|
29
|
+
},
|
|
30
|
+
gamma: "gamma",
|
|
31
|
+
epsilon: "epsilon"
|
|
32
|
+
});
|
|
33
|
+
});
|
|
34
|
+
it('delete where node is at child level', () => {
|
|
35
|
+
const nested = {
|
|
36
|
+
type: "Example form",
|
|
37
|
+
alpha: "alpha",
|
|
38
|
+
bravo: "bravo",
|
|
39
|
+
person: {
|
|
40
|
+
charlie: "Smith",
|
|
41
|
+
delta: "Acme Inc"
|
|
42
|
+
},
|
|
43
|
+
gamma: "gamma",
|
|
44
|
+
epsilon: "epsilon"
|
|
45
|
+
};
|
|
46
|
+
const path = 'person.charlie';
|
|
47
|
+
Utils.deleteNodeByPath(nested, path);
|
|
48
|
+
expect(nested).toEqual({
|
|
49
|
+
type: "Example form",
|
|
50
|
+
alpha: "alpha",
|
|
51
|
+
bravo: "bravo",
|
|
52
|
+
person: {
|
|
53
|
+
delta: "Acme Inc"
|
|
54
|
+
},
|
|
55
|
+
gamma: "gamma",
|
|
56
|
+
epsilon: "epsilon"
|
|
57
|
+
});
|
|
58
|
+
});
|
|
59
|
+
});
|
|
60
|
+
describe('removeObjectWithOnlySingleIdField', () => {
|
|
61
|
+
it('removes objects with only the "id" field', () => {
|
|
62
|
+
const inputArray = [{
|
|
63
|
+
id: 1
|
|
64
|
+
}, {
|
|
65
|
+
name: 'John'
|
|
66
|
+
}, {
|
|
67
|
+
id: 2
|
|
68
|
+
}, {
|
|
69
|
+
age: 30
|
|
70
|
+
}, {
|
|
71
|
+
id: 3
|
|
72
|
+
}, {
|
|
73
|
+
id: 4,
|
|
74
|
+
name: 'John'
|
|
75
|
+
}];
|
|
76
|
+
Utils.removeObjectWithOnlySingleIdField(inputArray);
|
|
77
|
+
expect(inputArray).toEqual([{
|
|
78
|
+
name: 'John'
|
|
79
|
+
}, {
|
|
80
|
+
age: 30
|
|
81
|
+
}, {
|
|
82
|
+
id: 4,
|
|
83
|
+
name: 'John'
|
|
84
|
+
}]);
|
|
85
|
+
});
|
|
86
|
+
it('does not mutate the array if no elements are removed', () => {
|
|
87
|
+
const inputArray = [{
|
|
88
|
+
name: 'John'
|
|
89
|
+
}, {
|
|
90
|
+
age: 30
|
|
91
|
+
}];
|
|
92
|
+
const originalArray = [].concat(inputArray);
|
|
93
|
+
Utils.removeObjectWithOnlySingleIdField(inputArray);
|
|
94
|
+
expect(inputArray).toEqual(originalArray);
|
|
95
|
+
});
|
|
96
|
+
});
|
|
97
|
+
describe('getImmediateParent', () => {
|
|
98
|
+
it('returns the immediate parent path when a valid path is provided', () => {
|
|
99
|
+
const path = 'user.profile.name';
|
|
100
|
+
const result = Utils.getImmediateParent(path);
|
|
101
|
+
expect(result).toBe('user.profile');
|
|
102
|
+
});
|
|
103
|
+
it('returns null if the path is an empty string', () => {
|
|
104
|
+
const result = Utils.getImmediateParent('');
|
|
105
|
+
expect(result).toBeNull();
|
|
106
|
+
});
|
|
107
|
+
it('returns the immediate parent when there is only one part in the path', () => {
|
|
108
|
+
const path = 'user.';
|
|
109
|
+
const result = Utils.getImmediateParent(path);
|
|
110
|
+
expect(result).toBe('user');
|
|
111
|
+
});
|
|
112
|
+
});
|
|
113
|
+
describe('getNestedQuestionPath', () => {
|
|
114
|
+
it('returns the correct nested path when the parent path is found', () => {
|
|
115
|
+
const result = Utils.getNestedQuestionPath('user.profile.name', 'age');
|
|
116
|
+
expect(result).toBe('user.profile.age');
|
|
117
|
+
});
|
|
118
|
+
it('returns the nestedFieldId when the parent path is not found', () => {
|
|
119
|
+
const result = Utils.getNestedQuestionPath('name', 'age');
|
|
120
|
+
expect(result).toBe('age');
|
|
121
|
+
});
|
|
122
|
+
it('handles an empty parent path correctly', () => {
|
|
123
|
+
const result = Utils.getNestedQuestionPath('', 'age');
|
|
124
|
+
expect(result).toBe('age');
|
|
125
|
+
});
|
|
126
|
+
it('handles nested paths with multiple parts correctly', () => {
|
|
127
|
+
const result = Utils.getNestedQuestionPath('user.profile.settings.theme', 'darkMode');
|
|
128
|
+
expect(result).toBe('user.profile.settings.darkMode');
|
|
129
|
+
});
|
|
130
|
+
});
|
|
131
|
+
describe('getDependencies', () => {
|
|
132
|
+
it('should extract "field" values from deeply nested structures', () => {
|
|
133
|
+
const input = {
|
|
134
|
+
"id": "firearms",
|
|
135
|
+
"show_when": [{
|
|
136
|
+
"type": "or",
|
|
137
|
+
"conditions": [{
|
|
138
|
+
"type": "and",
|
|
139
|
+
"conditions": [{
|
|
140
|
+
"op": "=",
|
|
141
|
+
"field": "itemCategory.category",
|
|
142
|
+
"value": "Weapons"
|
|
143
|
+
}, {
|
|
144
|
+
"op": "in",
|
|
145
|
+
"field": "itemSubCategory.epmscategory",
|
|
146
|
+
"values": ["ARM", "ARP"]
|
|
147
|
+
}]
|
|
148
|
+
}, {
|
|
149
|
+
"type": "and",
|
|
150
|
+
"conditions": [{
|
|
151
|
+
"op": "=",
|
|
152
|
+
"field": "itemCategory.category",
|
|
153
|
+
"value": "Firearms"
|
|
154
|
+
}, {
|
|
155
|
+
"op": "!=",
|
|
156
|
+
"field": "itemSubCategory.epmscategory",
|
|
157
|
+
"value": "AMM"
|
|
158
|
+
}]
|
|
159
|
+
}]
|
|
160
|
+
}]
|
|
161
|
+
};
|
|
162
|
+
expect(Utils.getDependencies(input)).toEqual(new Set(['itemCategory.category', 'itemSubCategory.epmscategory', 'itemCategory.category', 'itemSubCategory.epmscategory']));
|
|
163
|
+
});
|
|
164
|
+
it('should return null when no show_when', () => {
|
|
165
|
+
const input = {
|
|
166
|
+
"id": "firearms"
|
|
167
|
+
};
|
|
168
|
+
expect(Utils.getDependencies(input)).toEqual(null);
|
|
169
|
+
});
|
|
170
|
+
});
|
|
171
|
+
describe('getDependencyObjectFromPath', () => {
|
|
172
|
+
let allComponents;
|
|
173
|
+
beforeEach(() => {
|
|
174
|
+
allComponents = new Map([['whatHappened', {
|
|
175
|
+
id: 'whatHappened',
|
|
176
|
+
value: 'whatHappenedComponent'
|
|
177
|
+
}], ['modeOfTransport', {
|
|
178
|
+
id: 'modeOfTransport',
|
|
179
|
+
value: 'modeOfTransportComponent'
|
|
180
|
+
}]]);
|
|
181
|
+
});
|
|
182
|
+
it('should return the exact match from the map', () => {
|
|
183
|
+
expect(Utils.getDependencyObjectFromPath('whatHappened', allComponents)).toEqual({
|
|
184
|
+
id: 'whatHappened',
|
|
185
|
+
value: 'whatHappenedComponent'
|
|
186
|
+
});
|
|
187
|
+
});
|
|
188
|
+
it('should return the nearest parent found in the allComponents map', () => {
|
|
189
|
+
expect(Utils.getDependencyObjectFromPath('modeOfTransport.id', allComponents)).toEqual({
|
|
190
|
+
id: 'modeOfTransport',
|
|
191
|
+
value: 'modeOfTransportComponent'
|
|
192
|
+
});
|
|
193
|
+
});
|
|
194
|
+
it('should return the first matched component even when > 1 layers down', () => {
|
|
195
|
+
expect(Utils.getDependencyObjectFromPath('modeOfTransport.types.id', allComponents)).toEqual({
|
|
196
|
+
id: 'modeOfTransport',
|
|
197
|
+
value: 'modeOfTransportComponent'
|
|
198
|
+
});
|
|
199
|
+
});
|
|
200
|
+
it('should return null if no matching path is found', () => {
|
|
201
|
+
expect(Utils.getDependencyObjectFromPath('unknown.path', allComponents)).toBeNull();
|
|
202
|
+
});
|
|
203
|
+
});
|
|
204
|
+
jest.mock('../../../utils/Condition', () => ({
|
|
205
|
+
meetsOne: jest.fn(),
|
|
206
|
+
meetsAll: jest.fn()
|
|
207
|
+
}));
|
|
208
|
+
describe('isShowEntity', () => {
|
|
209
|
+
afterEach(() => {
|
|
210
|
+
jest.clearAllMocks(); // Reset mocks after each test
|
|
211
|
+
});
|
|
212
|
+
it('should return true if entity.show_when is not set', () => {
|
|
213
|
+
const entity = {}; // No show_when field
|
|
214
|
+
const data = {};
|
|
215
|
+
expect(Utils.isShowEntity(entity, data)).toBe(true);
|
|
216
|
+
});
|
|
217
|
+
it('should call meetsOne if show_when.type is "or"', () => {
|
|
218
|
+
const entity = {
|
|
219
|
+
show_when: {
|
|
220
|
+
type: 'or'
|
|
221
|
+
}
|
|
222
|
+
};
|
|
223
|
+
const data = {};
|
|
224
|
+
_Condition.default.meetsOne.mockReturnValue(true); // Mock meetsOne to return true
|
|
225
|
+
|
|
226
|
+
expect(Utils.isShowEntity(entity, data)).toBe(true);
|
|
227
|
+
expect(_Condition.default.meetsOne).toHaveBeenCalledWith(entity, data);
|
|
228
|
+
expect(_Condition.default.meetsAll).not.toHaveBeenCalled();
|
|
229
|
+
});
|
|
230
|
+
it('should call meetsAll if show_when.type is not "or"', () => {
|
|
231
|
+
const entity = {
|
|
232
|
+
show_when: {
|
|
233
|
+
type: 'and'
|
|
234
|
+
}
|
|
235
|
+
};
|
|
236
|
+
const data = {};
|
|
237
|
+
_Condition.default.meetsAll.mockReturnValue(false); // Mock meetsAll to return false
|
|
238
|
+
|
|
239
|
+
expect(Utils.isShowEntity(entity, data)).toBe(false);
|
|
240
|
+
expect(_Condition.default.meetsAll).toHaveBeenCalledWith(entity, data);
|
|
241
|
+
expect(_Condition.default.meetsOne).not.toHaveBeenCalled();
|
|
242
|
+
});
|
|
243
|
+
it('should call meetsAll if show_when.type is missing', () => {
|
|
244
|
+
const entity = {
|
|
245
|
+
show_when: {}
|
|
246
|
+
}; // No type specified
|
|
247
|
+
const data = {};
|
|
248
|
+
_Condition.default.meetsAll.mockReturnValue(true);
|
|
249
|
+
expect(Utils.isShowEntity(entity, data)).toBe(true);
|
|
250
|
+
expect(_Condition.default.meetsAll).toHaveBeenCalledWith(entity, data);
|
|
251
|
+
expect(_Condition.default.meetsOne).not.toHaveBeenCalled();
|
|
252
|
+
});
|
|
253
|
+
});
|
|
254
|
+
describe('findComponentDefinitionInForm', () => {
|
|
255
|
+
let componentByIdMap;
|
|
256
|
+
let componentByFieldIdMap;
|
|
257
|
+
beforeEach(() => {
|
|
258
|
+
componentByIdMap = new Map();
|
|
259
|
+
componentByFieldIdMap = new Map();
|
|
260
|
+
});
|
|
261
|
+
test('returns component from componentByIdMap when found', () => {
|
|
262
|
+
const component = {
|
|
263
|
+
id: 'comp1',
|
|
264
|
+
type: 'button'
|
|
265
|
+
};
|
|
266
|
+
componentByIdMap.set('comp1', component);
|
|
267
|
+
const pageComponentDef = {
|
|
268
|
+
use: 'comp1'
|
|
269
|
+
};
|
|
270
|
+
const result = Utils.findComponentDefinitionInForm(pageComponentDef, componentByIdMap, componentByFieldIdMap);
|
|
271
|
+
expect(result).toEqual(component);
|
|
272
|
+
expect(result).not.toBe(component); // Ensure it is a clone
|
|
273
|
+
});
|
|
274
|
+
test('returns component from componentByIdMap when found, but applies show_when from pageComponentDef', () => {
|
|
275
|
+
const component = {
|
|
276
|
+
id: 'comp1',
|
|
277
|
+
type: 'button',
|
|
278
|
+
show_when: 'rule1'
|
|
279
|
+
};
|
|
280
|
+
componentByIdMap.set('comp1', component);
|
|
281
|
+
const pageComponentDef = {
|
|
282
|
+
use: 'comp1',
|
|
283
|
+
show_when: 'rule2'
|
|
284
|
+
};
|
|
285
|
+
const result = Utils.findComponentDefinitionInForm(pageComponentDef, componentByIdMap, componentByFieldIdMap);
|
|
286
|
+
expect(result).toEqual({
|
|
287
|
+
id: 'comp1',
|
|
288
|
+
type: 'button',
|
|
289
|
+
show_when: 'rule2'
|
|
290
|
+
});
|
|
291
|
+
});
|
|
292
|
+
test('returns component from componentByFieldIdMap if not in componentByIdMap', () => {
|
|
293
|
+
const component = {
|
|
294
|
+
id: 'comp2',
|
|
295
|
+
type: 'input'
|
|
296
|
+
};
|
|
297
|
+
componentByFieldIdMap.set('comp2', component);
|
|
298
|
+
const pageComponentDef = {
|
|
299
|
+
use: 'comp2'
|
|
300
|
+
};
|
|
301
|
+
const result = Utils.findComponentDefinitionInForm(pageComponentDef, componentByIdMap, componentByFieldIdMap);
|
|
302
|
+
expect(result).toEqual(component);
|
|
303
|
+
expect(result).not.toBe(component); // Ensure it is a clone
|
|
304
|
+
});
|
|
305
|
+
test('returns pageComponentDef if not found in either map', () => {
|
|
306
|
+
const pageComponentDef = {
|
|
307
|
+
id: 'comp1',
|
|
308
|
+
definitions: "def values"
|
|
309
|
+
};
|
|
310
|
+
const result = Utils.findComponentDefinitionInForm(pageComponentDef, componentByIdMap, componentByFieldIdMap);
|
|
311
|
+
expect(result).toEqual(pageComponentDef);
|
|
312
|
+
expect(result).not.toBe(pageComponentDef); // Ensure it is a clone
|
|
313
|
+
});
|
|
314
|
+
test('ensures returned component is a deep clone', () => {
|
|
315
|
+
const component = {
|
|
316
|
+
id: 'comp3',
|
|
317
|
+
nested: {
|
|
318
|
+
key: 'value'
|
|
319
|
+
}
|
|
320
|
+
};
|
|
321
|
+
componentByIdMap.set('comp3', component);
|
|
322
|
+
const pageComponentDef = {
|
|
323
|
+
use: 'comp3'
|
|
324
|
+
};
|
|
325
|
+
const result = Utils.findComponentDefinitionInForm(pageComponentDef, componentByIdMap, componentByFieldIdMap);
|
|
326
|
+
|
|
327
|
+
// Modify the original to check deep cloning
|
|
328
|
+
component.nested.key = 'changed';
|
|
329
|
+
expect(result.nested.key).toBe('value'); // Should remain unchanged
|
|
330
|
+
expect(result).not.toBe(component); // Should not be the same reference
|
|
331
|
+
});
|
|
332
|
+
});
|
|
333
|
+
describe('deleteCorrespondingMetaInfo', () => {
|
|
334
|
+
let formData;
|
|
335
|
+
let collectionDataObject;
|
|
336
|
+
let component;
|
|
337
|
+
beforeEach(() => {
|
|
338
|
+
formData = {
|
|
339
|
+
meta: {
|
|
340
|
+
documents: [{
|
|
341
|
+
url: 'https://example.com/file1.pdf'
|
|
342
|
+
}, {
|
|
343
|
+
url: 'https://example.com/file2.pdf'
|
|
344
|
+
}, {
|
|
345
|
+
url: 'https://example.com/file3.pdf'
|
|
346
|
+
}]
|
|
347
|
+
}
|
|
348
|
+
};
|
|
349
|
+
collectionDataObject = {};
|
|
350
|
+
component = {
|
|
351
|
+
fieldId: 'fileField'
|
|
352
|
+
};
|
|
353
|
+
});
|
|
354
|
+
test('removes a matching document when found', () => {
|
|
355
|
+
collectionDataObject.fileField = {
|
|
356
|
+
url: 'https://example.com/file2.pdf'
|
|
357
|
+
};
|
|
358
|
+
Utils.deleteCorrespondingMetaInfo(component, collectionDataObject, formData);
|
|
359
|
+
expect(formData.meta.documents).toEqual([{
|
|
360
|
+
url: 'https://example.com/file1.pdf'
|
|
361
|
+
}, {
|
|
362
|
+
url: 'https://example.com/file3.pdf'
|
|
363
|
+
}]);
|
|
364
|
+
});
|
|
365
|
+
test('removes a matching document when fileDataBeingDeleted is an array', () => {
|
|
366
|
+
collectionDataObject.fileField = [{
|
|
367
|
+
url: 'https://example.com/file2.pdf'
|
|
368
|
+
}, {
|
|
369
|
+
url: 'https://example.com/file3.pdf'
|
|
370
|
+
}];
|
|
371
|
+
Utils.deleteCorrespondingMetaInfo(component, collectionDataObject, formData);
|
|
372
|
+
expect(formData.meta.documents).toEqual([{
|
|
373
|
+
url: 'https://example.com/file1.pdf'
|
|
374
|
+
}]);
|
|
375
|
+
});
|
|
376
|
+
test('does nothing if documents array is empty', () => {
|
|
377
|
+
formData.meta.documents = [];
|
|
378
|
+
collectionDataObject.fileField = {
|
|
379
|
+
url: 'https://example.com/file2.pdf'
|
|
380
|
+
};
|
|
381
|
+
Utils.deleteCorrespondingMetaInfo(component, collectionDataObject, formData);
|
|
382
|
+
expect(formData.meta.documents).toEqual([]);
|
|
383
|
+
});
|
|
384
|
+
test('does nothing if fileDataBeingDeleted is undefined', () => {
|
|
385
|
+
Utils.deleteCorrespondingMetaInfo(component, collectionDataObject, formData);
|
|
386
|
+
expect(formData.meta.documents).toEqual([{
|
|
387
|
+
url: 'https://example.com/file1.pdf'
|
|
388
|
+
}, {
|
|
389
|
+
url: 'https://example.com/file2.pdf'
|
|
390
|
+
}, {
|
|
391
|
+
url: 'https://example.com/file3.pdf'
|
|
392
|
+
}]);
|
|
393
|
+
});
|
|
394
|
+
test('does nothing if there is no match', () => {
|
|
395
|
+
collectionDataObject.fileField = {
|
|
396
|
+
url: 'https://example.com/nonexistent.pdf'
|
|
397
|
+
};
|
|
398
|
+
Utils.deleteCorrespondingMetaInfo(component, collectionDataObject, formData);
|
|
399
|
+
expect(formData.meta.documents).toEqual([{
|
|
400
|
+
url: 'https://example.com/file1.pdf'
|
|
401
|
+
}, {
|
|
402
|
+
url: 'https://example.com/file2.pdf'
|
|
403
|
+
}, {
|
|
404
|
+
url: 'https://example.com/file3.pdf'
|
|
405
|
+
}]);
|
|
406
|
+
});
|
|
407
|
+
});
|
|
408
|
+
describe("removeEmptyArraysAndUnusedCollectionIDs", () => {
|
|
409
|
+
test("removes empty arrays from an array", () => {
|
|
410
|
+
const input = [1, [], 2, []];
|
|
411
|
+
Utils.removeEmptyArraysAndUnusedCollectionIDs(input);
|
|
412
|
+
expect(input).toEqual([1, 2]);
|
|
413
|
+
});
|
|
414
|
+
test("removes empty arrays from an object and deletes corresponding ActiveId", () => {
|
|
415
|
+
const input = {
|
|
416
|
+
key1: [],
|
|
417
|
+
key1ActiveId: "123",
|
|
418
|
+
key2: "value"
|
|
419
|
+
};
|
|
420
|
+
Utils.removeEmptyArraysAndUnusedCollectionIDs(input);
|
|
421
|
+
expect(input).toEqual({
|
|
422
|
+
key2: "value"
|
|
423
|
+
});
|
|
424
|
+
});
|
|
425
|
+
test("removes empty arrays in nested structures", () => {
|
|
426
|
+
const input = {
|
|
427
|
+
level1: {
|
|
428
|
+
level2: {
|
|
429
|
+
arr: [],
|
|
430
|
+
arrActiveId: "456",
|
|
431
|
+
anotherKey: "data"
|
|
432
|
+
}
|
|
433
|
+
}
|
|
434
|
+
};
|
|
435
|
+
Utils.removeEmptyArraysAndUnusedCollectionIDs(input);
|
|
436
|
+
expect(input).toEqual({
|
|
437
|
+
level1: {
|
|
438
|
+
level2: {
|
|
439
|
+
anotherKey: "data"
|
|
440
|
+
}
|
|
441
|
+
}
|
|
442
|
+
});
|
|
443
|
+
});
|
|
444
|
+
test("removes multiple empty arrays and their ActiveIds", () => {
|
|
445
|
+
const input = {
|
|
446
|
+
list1: [],
|
|
447
|
+
list1ActiveId: "789",
|
|
448
|
+
list2: [1, 2],
|
|
449
|
+
nested: {
|
|
450
|
+
emptyList: [],
|
|
451
|
+
emptyListActiveId: "999"
|
|
452
|
+
}
|
|
453
|
+
};
|
|
454
|
+
Utils.removeEmptyArraysAndUnusedCollectionIDs(input);
|
|
455
|
+
expect(input).toEqual({
|
|
456
|
+
list2: [1, 2],
|
|
457
|
+
nested: {}
|
|
458
|
+
});
|
|
459
|
+
});
|
|
460
|
+
test("does not modify non-empty arrays", () => {
|
|
461
|
+
const input = {
|
|
462
|
+
arr: [1, 2, 3],
|
|
463
|
+
obj: {
|
|
464
|
+
nestedArr: ["a"]
|
|
465
|
+
}
|
|
466
|
+
};
|
|
467
|
+
const expected = {
|
|
468
|
+
arr: [1, 2, 3],
|
|
469
|
+
obj: {
|
|
470
|
+
nestedArr: ["a"]
|
|
471
|
+
}
|
|
472
|
+
};
|
|
473
|
+
Utils.removeEmptyArraysAndUnusedCollectionIDs(input);
|
|
474
|
+
expect(input).toEqual(expected);
|
|
475
|
+
});
|
|
476
|
+
test("handles null and empty object gracefully", () => {
|
|
477
|
+
const input1 = null;
|
|
478
|
+
const input2 = {};
|
|
479
|
+
Utils.removeEmptyArraysAndUnusedCollectionIDs(input1);
|
|
480
|
+
Utils.removeEmptyArraysAndUnusedCollectionIDs(input2);
|
|
481
|
+
expect(input1).toBeNull();
|
|
482
|
+
expect(input2).toEqual({});
|
|
483
|
+
});
|
|
484
|
+
test("handles arrays containing objects with empty arrays", () => {
|
|
485
|
+
const input = [{
|
|
486
|
+
key: []
|
|
487
|
+
}, {
|
|
488
|
+
key: "value"
|
|
489
|
+
}, {
|
|
490
|
+
nested: {
|
|
491
|
+
arr: [],
|
|
492
|
+
arrActiveId: "555"
|
|
493
|
+
}
|
|
494
|
+
}];
|
|
495
|
+
Utils.removeEmptyArraysAndUnusedCollectionIDs(input);
|
|
496
|
+
expect(input).toEqual([{
|
|
497
|
+
key: "value"
|
|
498
|
+
}, {
|
|
499
|
+
nested: {}
|
|
500
|
+
}]);
|
|
501
|
+
});
|
|
502
|
+
});
|
|
503
|
+
describe("pruneCollectionEntry", () => {
|
|
504
|
+
let pathsToKeep;
|
|
505
|
+
let componentsToPrune;
|
|
506
|
+
let collectionDataObject;
|
|
507
|
+
let formData;
|
|
508
|
+
let expectedFormData;
|
|
509
|
+
beforeEach(() => {
|
|
510
|
+
pathsToKeep = new Set(['keepThis']);
|
|
511
|
+
componentsToPrune = [{
|
|
512
|
+
fieldId: 'removeThis',
|
|
513
|
+
type: 'text'
|
|
514
|
+
}, {
|
|
515
|
+
fieldId: 'keepThis',
|
|
516
|
+
type: 'text'
|
|
517
|
+
}, {
|
|
518
|
+
fieldId: 'removeMultifile',
|
|
519
|
+
type: 'multifile'
|
|
520
|
+
}];
|
|
521
|
+
collectionDataObject = {
|
|
522
|
+
'removeThis': 'value',
|
|
523
|
+
'keepThis': 'value',
|
|
524
|
+
'removeMultifile': {
|
|
525
|
+
'url': 'https://example.com/file1.pdf'
|
|
526
|
+
}
|
|
527
|
+
};
|
|
528
|
+
formData = {
|
|
529
|
+
meta: {
|
|
530
|
+
documents: [{
|
|
531
|
+
url: 'https://example.com/file1.pdf'
|
|
532
|
+
}, {
|
|
533
|
+
url: 'https://example.com/file2.pdf'
|
|
534
|
+
}, {
|
|
535
|
+
url: 'https://example.com/file3.pdf'
|
|
536
|
+
}]
|
|
537
|
+
}
|
|
538
|
+
};
|
|
539
|
+
expectedFormData = {
|
|
540
|
+
meta: {
|
|
541
|
+
documents: [{
|
|
542
|
+
url: 'https://example.com/file2.pdf'
|
|
543
|
+
}, {
|
|
544
|
+
url: 'https://example.com/file3.pdf'
|
|
545
|
+
}]
|
|
546
|
+
}
|
|
547
|
+
};
|
|
548
|
+
});
|
|
549
|
+
it('should remove components not in pathsToKeep', () => {
|
|
550
|
+
Utils.pruneCollectionEntry(pathsToKeep, componentsToPrune, collectionDataObject, formData);
|
|
551
|
+
expect(collectionDataObject).toEqual({
|
|
552
|
+
'keepThis': 'value'
|
|
553
|
+
});
|
|
554
|
+
});
|
|
555
|
+
it('should delete meta.documents for multifile components', () => {
|
|
556
|
+
Utils.pruneCollectionEntry(pathsToKeep, componentsToPrune, collectionDataObject, formData);
|
|
557
|
+
expect(formData).toEqual(expectedFormData);
|
|
558
|
+
});
|
|
559
|
+
});
|
|
@@ -30,6 +30,13 @@ const onCYAAction = (setPagePoint, action, pages, validate, components, data, se
|
|
|
30
30
|
pages,
|
|
31
31
|
components
|
|
32
32
|
}, _objectSpread({}, data), _models.EventTypes.SUBMIT);
|
|
33
|
+
|
|
34
|
+
// Cleanse hidden data
|
|
35
|
+
const formPagesAndComponents = {
|
|
36
|
+
pages,
|
|
37
|
+
components
|
|
38
|
+
};
|
|
39
|
+
_helpers.default.clearOutUncompletedRoutes(formPagesAndComponents, submissionData);
|
|
33
40
|
submissionData.formStatus = _helpers.default.getSubmissionStatus(type, pages, pageId, action, submissionData, currentTask, true);
|
|
34
41
|
setData(submissionData);
|
|
35
42
|
// Now submit the data to the backend...
|
|
@@ -69,6 +76,11 @@ const onCYAAction = (setPagePoint, action, pages, validate, components, data, se
|
|
|
69
76
|
pages,
|
|
70
77
|
components
|
|
71
78
|
}, _objectSpread({}, data), _models.EventTypes.SUBMIT);
|
|
79
|
+
const formPagesAndComponents = {
|
|
80
|
+
pages,
|
|
81
|
+
components
|
|
82
|
+
};
|
|
83
|
+
_helpers.default.clearOutUncompletedRoutes(formPagesAndComponents, submissionData);
|
|
72
84
|
submissionData.formStatus = _helpers.default.getSubmissionStatus(type, pages, pageId, action, submissionData, currentTask, true);
|
|
73
85
|
setData(submissionData);
|
|
74
86
|
// Now submit the data to the backend...
|
|
@@ -55,6 +55,11 @@ jest.mock('./helpers', () => ({
|
|
|
55
55
|
this.getNextPageIdCalls = 0;
|
|
56
56
|
this.getFormStateCalls = 0;
|
|
57
57
|
this.getSubmissionStatusCalls = 0;
|
|
58
|
+
},
|
|
59
|
+
clearOutUncompletedRoutesCalls: 0,
|
|
60
|
+
clearOutUncompletedRoutes(formPagesAndComponents, formData) {
|
|
61
|
+
this.clearOutUncompletedRoutesCalls += 1;
|
|
62
|
+
return formData;
|
|
58
63
|
}
|
|
59
64
|
}));
|
|
60
65
|
jest.mock('../../utils', () => ({
|
|
@@ -8,10 +8,6 @@ var _axios = _interopRequireDefault(require("axios"));
|
|
|
8
8
|
var _react = require("react");
|
|
9
9
|
var _useAxios = _interopRequireDefault(require("./useAxios"));
|
|
10
10
|
function _interopRequireDefault(e) { return e && e.__esModule ? e : { default: e }; }
|
|
11
|
-
// Global imports
|
|
12
|
-
|
|
13
|
-
// Local imports
|
|
14
|
-
|
|
15
11
|
// Caches for responses and errors.
|
|
16
12
|
const cache = {};
|
|
17
13
|
const errorCache = {};
|
|
@@ -31,15 +27,18 @@ const STATUS_IDLE = exports.STATUS_IDLE = 'idle';
|
|
|
31
27
|
const STATUS_FETCHING = exports.STATUS_FETCHING = 'fetching';
|
|
32
28
|
const STATUS_FETCHED = exports.STATUS_FETCHED = 'fetched';
|
|
33
29
|
const STATUS_ERROR = exports.STATUS_ERROR = 'error';
|
|
34
|
-
const useGetRequest = url
|
|
30
|
+
const useGetRequest = function (url) {
|
|
31
|
+
let caching = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : true;
|
|
35
32
|
const axiosInstance = (0, _useAxios.default)();
|
|
36
|
-
const
|
|
37
|
-
const cancelRequests = () => {
|
|
38
|
-
if (cancelToken) cancelToken.cancel();
|
|
39
|
-
};
|
|
33
|
+
const cancelTokenRef = (0, _react.useRef)(_axios.default.CancelToken.source());
|
|
40
34
|
const [status, setStatus] = (0, _react.useState)(STATUS_IDLE);
|
|
41
35
|
const [error, setError] = (0, _react.useState)(null);
|
|
42
36
|
const [data, setData] = (0, _react.useState)(null);
|
|
37
|
+
const cancelRequests = () => {
|
|
38
|
+
if (cancelTokenRef.current) {
|
|
39
|
+
cancelTokenRef.current.cancel();
|
|
40
|
+
}
|
|
41
|
+
};
|
|
43
42
|
(0, _react.useEffect)(() => {
|
|
44
43
|
if (!url || !axiosInstance) return;
|
|
45
44
|
const fetchData = async () => {
|
|
@@ -47,9 +46,9 @@ const useGetRequest = url => {
|
|
|
47
46
|
setError(null);
|
|
48
47
|
setStatus(STATUS_FETCHING);
|
|
49
48
|
let fetchedData;
|
|
50
|
-
if (cache[url]) {
|
|
49
|
+
if (caching && cache[url]) {
|
|
51
50
|
fetchedData = cache[url];
|
|
52
|
-
} else if (errorCache[url]) {
|
|
51
|
+
} else if (caching && errorCache[url]) {
|
|
53
52
|
/**
|
|
54
53
|
* This logic is intended to stop multiple requests being made in succession
|
|
55
54
|
* that all fail. Presently, this will only allow the first request to be
|
|
@@ -60,24 +59,25 @@ const useGetRequest = url => {
|
|
|
60
59
|
throw errorCache[url];
|
|
61
60
|
} else {
|
|
62
61
|
const response = await axiosInstance.get(url, {
|
|
63
|
-
cancelToken:
|
|
62
|
+
cancelToken: cancelTokenRef.current.token
|
|
64
63
|
}).catch(e => {
|
|
64
|
+
setError(e);
|
|
65
65
|
throw e;
|
|
66
66
|
});
|
|
67
67
|
fetchedData = response.data;
|
|
68
|
-
cache[url] = fetchedData;
|
|
68
|
+
if (caching) cache[url] = fetchedData;
|
|
69
69
|
}
|
|
70
70
|
setData(fetchedData);
|
|
71
71
|
setStatus(STATUS_FETCHED);
|
|
72
72
|
} catch (e) {
|
|
73
|
-
errorCache[url] = e;
|
|
73
|
+
if (caching) errorCache[url] = e;
|
|
74
74
|
setError(e);
|
|
75
75
|
setData(null);
|
|
76
76
|
setStatus(STATUS_ERROR);
|
|
77
77
|
}
|
|
78
78
|
};
|
|
79
79
|
fetchData();
|
|
80
|
-
}, [axiosInstance, url,
|
|
80
|
+
}, [axiosInstance, url, caching]);
|
|
81
81
|
return {
|
|
82
82
|
status,
|
|
83
83
|
error,
|
package/dist/hooks/useRefData.js
CHANGED
|
@@ -28,13 +28,14 @@ const getRefDataUrl = component => {
|
|
|
28
28
|
return undefined;
|
|
29
29
|
};
|
|
30
30
|
const useRefData = (component, formData) => {
|
|
31
|
+
var _component$data;
|
|
31
32
|
const url = getRefDataUrl(_objectSpread(_objectSpread({}, component), {}, {
|
|
32
33
|
formData
|
|
33
34
|
}));
|
|
34
35
|
const {
|
|
35
36
|
status: _status,
|
|
36
37
|
data: _data
|
|
37
|
-
} = (0, _useGetRequest.default)(url);
|
|
38
|
+
} = (0, _useGetRequest.default)(url, component === null || component === void 0 || (_component$data = component.data) === null || _component$data === void 0 ? void 0 : _component$data.useCache);
|
|
38
39
|
const [data, setData] = (0, _react.useState)([]);
|
|
39
40
|
const [status, setStatus] = (0, _react.useState)(STATUS_LOADING);
|
|
40
41
|
(0, _react.useEffect)(() => {
|
|
@@ -52,7 +53,7 @@ const useRefData = (component, formData) => {
|
|
|
52
53
|
setData([]);
|
|
53
54
|
setStatus(STATUS_COMPLETE);
|
|
54
55
|
}
|
|
55
|
-
}, [component, _status, _data, url
|
|
56
|
+
}, [component.id, _status, _data, url]);
|
|
56
57
|
return {
|
|
57
58
|
data,
|
|
58
59
|
status
|