@planning-inspectorate/dynamic-forms 3.2.0 → 3.3.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.
Files changed (44) hide show
  1. package/README.md +9 -9
  2. package/package.json +1 -1
  3. package/src/components/address/question.js +2 -4
  4. package/src/components/boolean/question.js +6 -11
  5. package/src/components/date/question.js +5 -11
  6. package/src/components/date-period/question.js +5 -9
  7. package/src/components/date-time/question.js +5 -8
  8. package/src/components/date-time/question.test.js +3 -24
  9. package/src/components/manage-list/README.md +21 -2
  10. package/src/components/manage-list/index.njk +3 -3
  11. package/src/components/manage-list/question.js +24 -15
  12. package/src/components/manage-list/question.test.js +38 -1
  13. package/src/components/manage-list/utils.js +35 -0
  14. package/src/components/manage-list/utils.test.js +80 -0
  15. package/src/components/multi-field-input/question.js +7 -11
  16. package/src/components/multi-field-input/question.test.js +29 -1
  17. package/src/components/text-entry-redact/question.js +3 -4
  18. package/src/components/unit-option-entry/question.js +6 -13
  19. package/src/components/utils/question-has-answer.js +45 -11
  20. package/src/components/utils/question-utils.js +3 -37
  21. package/src/controller.js +11 -0
  22. package/src/controller.test.js +13 -1
  23. package/src/journey/journey.js +1 -0
  24. package/src/lib/utils.js +11 -0
  25. package/src/lib/utils.test.js +19 -0
  26. package/src/lib/uuid.test.js +0 -12
  27. package/src/questions/options-question.js +6 -13
  28. package/src/questions/question.js +17 -31
  29. package/src/questions/question.test.js +5 -19
  30. package/src/section-types.d.ts +2 -0
  31. package/src/section.js +5 -1
  32. package/src/section.test.js +7 -3
  33. package/test/check-your-answers.test.js +1 -1
  34. package/test/mock/uuid.js +13 -0
  35. package/test/questions.test.js +3 -200
  36. package/test/utils/question-test-utils.js +200 -0
  37. package/src/components/macros/answer-list.njk +0 -29
  38. package/src/middleware/check-not-submitted.js +0 -13
  39. package/src/middleware/check-not-submitted.test.js +0 -118
  40. package/src/middleware/dynamic-req-files-to-req-body-files.js +0 -46
  41. package/src/middleware/dynamic-req-files-to-req-body-files.test.js +0 -221
  42. package/src/validator/required-file-upload-validator.js +0 -38
  43. package/src/validator/required-file-upload-validator.test.js +0 -141
  44. package/src/validator/schemas/required-file-upload-schema.js +0 -35
@@ -1,76 +1,11 @@
1
1
  import { describe, it } from 'node:test';
2
2
  import assert from 'assert';
3
- import { createApp } from './utils/app.js';
4
- import { TestServer } from './utils/test-server.js';
5
- import { getQuestions, manageListQuestions, questionsInOrder } from './questions.js';
6
- import { buildGetJourney } from '../src/middleware/build-get-journey.js';
7
- import { createJourney, JOURNEY_ID } from './journey.js';
8
- import { buildGetJourneyResponseFromSession, saveDataToSession } from '../src/lib/session-answer-store.js';
9
- import validate from '../src/validator/validator.js';
10
- import { validationErrorHandler } from '../src/validator/validation-error-handler.js';
11
- import { buildSave, list, question } from '../src/controller.js';
3
+ import { manageListQuestions, questionsInOrder } from './questions.js';
12
4
  import path from 'path';
13
5
  import { COMPONENT_TYPES } from '../src/index.js';
14
6
  import { escapeForRegExp, snapshotsDir } from './utils/utils.js';
15
- import { BOOLEAN_OPTIONS } from '../src/components/boolean/question.js';
16
- import { mockRandomUUID } from '#src/lib/uuid.test.js';
17
-
18
- /**
19
- * @param {import('node:test').TestContext} ctx
20
- * @returns {Promise<TestServer>}
21
- */
22
- export async function createAppWithQuestions(ctx) {
23
- const app = createApp();
24
- const questions = getQuestions();
25
- const getJourney = buildGetJourney((req, journeyResponse) => createJourney(questions, journeyResponse));
26
- const getJourneyResponse = buildGetJourneyResponseFromSession(JOURNEY_ID);
27
-
28
- // app.use((req, res, next) => {
29
- // console.log(req.method, req.url, req.session);
30
- // next();
31
- // });
32
-
33
- app.get(
34
- '/:section/:question{/:manageListAction/:manageListItemId/:manageListQuestion}',
35
- getJourneyResponse,
36
- getJourney,
37
- question
38
- );
39
-
40
- app.post(
41
- '/:section/:question{/:manageListAction/:manageListItemId/:manageListQuestion}',
42
- getJourneyResponse,
43
- getJourney,
44
- validate,
45
- validationErrorHandler,
46
- buildSave(saveDataToSession)
47
- );
48
-
49
- app.get('/check-your-answers', getJourneyResponse, getJourney, (req, res) => list(req, res, '', {}));
50
-
51
- app.use((req, res) => {
52
- res.status(404);
53
- });
54
-
55
- /**
56
- * @type {import('express').ErrorRequestHandler}
57
- */
58
- function errorHandler(error, req, res, next) {
59
- console.error('Internal Server Error:', error);
60
- if (res.headersSent) {
61
- return next(error);
62
- }
63
- res.status(500);
64
- res.render('error', { error });
65
- }
66
-
67
- app.use(errorHandler);
68
-
69
- const server = new TestServer(app, { rememberCookies: true, timeoutMs: 2000 });
70
- await server.start();
71
- ctx.after(async () => await server.stop());
72
- return server;
73
- }
7
+ import { createAppWithQuestions, mockAnswerBody } from '#test/utils/question-test-utils.js';
8
+ import { mockRandomUUID } from '#test/mock/uuid.js';
74
9
 
75
10
  describe('question pages', () => {
76
11
  it('should have a test for each question type', () => {
@@ -187,135 +122,3 @@ describe('question pages', () => {
187
122
  }
188
123
  });
189
124
  });
190
-
191
- /**
192
- * Build a valid payload for a question.
193
- * @param {import('../src/questions/question-props.js').QuestionProps} q
194
- * @returns {Object}
195
- */
196
- export function mockAnswerBody(q) {
197
- switch (q.type) {
198
- case COMPONENT_TYPES.BOOLEAN:
199
- return { [q.fieldName]: BOOLEAN_OPTIONS.YES };
200
- case COMPONENT_TYPES.CHECKBOX:
201
- return { [q.fieldName]: [q.options[0].value] };
202
- case COMPONENT_TYPES.RADIO:
203
- case COMPONENT_TYPES.SELECT:
204
- return { [q.fieldName]: q.options[0].value };
205
- case COMPONENT_TYPES.NUMBER:
206
- return { [q.fieldName]: 1 };
207
- case COMPONENT_TYPES.DATE:
208
- return {
209
- [q.fieldName + '_day']: 1,
210
- [q.fieldName + '_month']: 1,
211
- [q.fieldName + '_year']: 2025
212
- };
213
- case COMPONENT_TYPES.DATE_PERIOD:
214
- return {
215
- [q.fieldName + '_start_day']: 1,
216
- [q.fieldName + '_start_month']: 1,
217
- [q.fieldName + '_start_year']: 2025,
218
- [q.fieldName + '_end_day']: 2,
219
- [q.fieldName + '_end_month']: 1,
220
- [q.fieldName + '_end_year']: 2025
221
- };
222
- case COMPONENT_TYPES.DATE_TIME:
223
- return {
224
- [q.fieldName + '_day']: 1,
225
- [q.fieldName + '_month']: 1,
226
- [q.fieldName + '_year']: 2025,
227
- [q.fieldName + '_hour']: 10,
228
- [q.fieldName + '_minutes']: 45,
229
- [q.fieldName + '_period']: 'am'
230
- };
231
- case COMPONENT_TYPES.TEXT_ENTRY:
232
- case COMPONENT_TYPES.SINGLE_LINE_INPUT:
233
- return { [q.fieldName]: 'test' };
234
- case COMPONENT_TYPES.TEXT_ENTRY_REDACT:
235
- return { [q.fieldName]: 'secret' };
236
- case COMPONENT_TYPES.MULTI_FIELD_INPUT: {
237
- const res = {};
238
- for (const field of q.inputFields) {
239
- res[field.fieldName] = mockAnswer(field);
240
- }
241
- return res;
242
- }
243
- case COMPONENT_TYPES.ADDRESS:
244
- return {
245
- [q.fieldName + '_addressLine1']: '1 Test St',
246
- [q.fieldName + '_townCity']: 'Testville',
247
- [q.fieldName + '_postcode']: 'TE5 5ST'
248
- };
249
- case COMPONENT_TYPES.UNIT_OPTION:
250
- return {
251
- [q.fieldName]: 'kg',
252
- [q.options[0].conditional.fieldName]: 10
253
- };
254
- case COMPONENT_TYPES.EMAIL:
255
- return { [q.fieldName]: 'test@example.com' };
256
- case COMPONENT_TYPES.MANAGE_LIST: {
257
- // manage list questions not available here so just hard coded
258
- return {
259
- [q.fieldName]: [
260
- { id: '1234', travelCompanionName: 'Companion 1', travelCompanionEmail: 'companion-1@example.com' },
261
- { id: '4567', travelCompanionName: 'Companion 2', travelCompanionEmail: 'companion-2@example.com' },
262
- { id: '8901', travelCompanionName: 'Companion 3', travelCompanionEmail: 'companion-3@example.com' }
263
- ]
264
- };
265
- }
266
- default:
267
- return { [q.fieldName]: 'test' };
268
- }
269
- }
270
-
271
- /**
272
- * Build a valid & formatted answer for a question.
273
- * @param {import('../src/questions/question-props.js').QuestionProps} q
274
- * @returns {Object}
275
- */
276
- export function mockAnswer(q) {
277
- switch (q.type) {
278
- case COMPONENT_TYPES.BOOLEAN:
279
- return BOOLEAN_OPTIONS.YES;
280
- case COMPONENT_TYPES.CHECKBOX:
281
- return q.options[0].text;
282
- case COMPONENT_TYPES.RADIO:
283
- case COMPONENT_TYPES.SELECT:
284
- return q.options[0].text;
285
- case COMPONENT_TYPES.NUMBER:
286
- return 1;
287
- case COMPONENT_TYPES.DATE:
288
- return '1 January 2025';
289
- case COMPONENT_TYPES.DATE_PERIOD:
290
- return 'Start: 00:00 1 January 2025<br>End: 00:00 2 January 2025';
291
- case COMPONENT_TYPES.DATE_TIME:
292
- return '1 January 2025<br>10:45am';
293
- case COMPONENT_TYPES.TEXT_ENTRY:
294
- case COMPONENT_TYPES.SINGLE_LINE_INPUT:
295
- return 'test';
296
- case COMPONENT_TYPES.TEXT_ENTRY_REDACT:
297
- return 'secret';
298
- case COMPONENT_TYPES.MULTI_FIELD_INPUT:
299
- if (q.inputFields) {
300
- let res = '';
301
- for (const field of q.inputFields) {
302
- res += mockAnswer(field) + '<br>';
303
- }
304
- return res;
305
- }
306
- break;
307
- case COMPONENT_TYPES.ADDRESS: {
308
- const answer = mockAnswerBody(q);
309
- return Object.values(answer).filter(Boolean).join('<br>');
310
- }
311
- case COMPONENT_TYPES.UNIT_OPTION:
312
- return '10 kg';
313
- case COMPONENT_TYPES.EMAIL:
314
- return 'test@example.com';
315
- case COMPONENT_TYPES.MANAGE_LIST: {
316
- return `3 ${q.title}`;
317
- }
318
- default:
319
- return 'test';
320
- }
321
- }
@@ -0,0 +1,200 @@
1
+ import { getQuestions } from '#test/questions.js';
2
+ import { COMPONENT_TYPES } from '#src/index.js';
3
+ import { createApp } from '#test/utils/app.js';
4
+ import { buildGetJourney } from '#src/middleware/build-get-journey.js';
5
+ import { buildGetJourneyResponseFromSession, saveDataToSession } from '#src/lib/session-answer-store.js';
6
+ import { createJourney, JOURNEY_ID } from '#test/journey.js';
7
+ import { buildList, buildSave, question } from '#src/controller.js';
8
+ import validate from '#src/validator/validator.js';
9
+ import { validationErrorHandler } from '#src/validator/validation-error-handler.js';
10
+ import { TestServer } from '#test/utils/test-server.js';
11
+ import { BOOLEAN_OPTIONS } from '#src/components/boolean/question.js';
12
+
13
+ /**
14
+ * @param {import('node:test').TestContext} ctx
15
+ * @returns {Promise<TestServer>}
16
+ */
17
+ export async function createAppWithQuestions(ctx) {
18
+ const app = createApp();
19
+ const questions = getQuestions();
20
+ const getJourney = buildGetJourney((req, journeyResponse) => createJourney(questions, journeyResponse));
21
+ const getJourneyResponse = buildGetJourneyResponseFromSession(JOURNEY_ID);
22
+
23
+ // app.use((req, res, next) => {
24
+ // console.log(req.method, req.url, req.session);
25
+ // next();
26
+ // });
27
+
28
+ app.get(
29
+ '/:section/:question{/:manageListAction/:manageListItemId/:manageListQuestion}',
30
+ getJourneyResponse,
31
+ getJourney,
32
+ question
33
+ );
34
+
35
+ app.post(
36
+ '/:section/:question{/:manageListAction/:manageListItemId/:manageListQuestion}',
37
+ getJourneyResponse,
38
+ getJourney,
39
+ validate,
40
+ validationErrorHandler,
41
+ buildSave(saveDataToSession)
42
+ );
43
+
44
+ app.get('/check-your-answers', getJourneyResponse, getJourney, buildList());
45
+
46
+ app.use((req, res) => {
47
+ res.status(404);
48
+ });
49
+
50
+ /**
51
+ * @type {import('express').ErrorRequestHandler}
52
+ */
53
+ function errorHandler(error, req, res, next) {
54
+ console.error('Internal Server Error:', error);
55
+ if (res.headersSent) {
56
+ return next(error);
57
+ }
58
+ res.status(500);
59
+ res.render('error', { error });
60
+ }
61
+
62
+ app.use(errorHandler);
63
+
64
+ const server = new TestServer(app, { rememberCookies: true, timeoutMs: 2000 });
65
+ await server.start();
66
+ ctx.after(async () => await server.stop());
67
+ return server;
68
+ }
69
+
70
+ /**
71
+ * Build a valid payload for a question.
72
+ * @param {import('../src/questions/question-props.js').QuestionProps} q
73
+ * @returns {Object}
74
+ */
75
+ export function mockAnswerBody(q) {
76
+ switch (q.type) {
77
+ case COMPONENT_TYPES.BOOLEAN:
78
+ return { [q.fieldName]: BOOLEAN_OPTIONS.YES };
79
+ case COMPONENT_TYPES.CHECKBOX:
80
+ return { [q.fieldName]: [q.options[0].value] };
81
+ case COMPONENT_TYPES.RADIO:
82
+ case COMPONENT_TYPES.SELECT:
83
+ return { [q.fieldName]: q.options[0].value };
84
+ case COMPONENT_TYPES.NUMBER:
85
+ return { [q.fieldName]: 1 };
86
+ case COMPONENT_TYPES.DATE:
87
+ return {
88
+ [q.fieldName + '_day']: 1,
89
+ [q.fieldName + '_month']: 1,
90
+ [q.fieldName + '_year']: 2025
91
+ };
92
+ case COMPONENT_TYPES.DATE_PERIOD:
93
+ return {
94
+ [q.fieldName + '_start_day']: 1,
95
+ [q.fieldName + '_start_month']: 1,
96
+ [q.fieldName + '_start_year']: 2025,
97
+ [q.fieldName + '_end_day']: 2,
98
+ [q.fieldName + '_end_month']: 1,
99
+ [q.fieldName + '_end_year']: 2025
100
+ };
101
+ case COMPONENT_TYPES.DATE_TIME:
102
+ return {
103
+ [q.fieldName + '_day']: 1,
104
+ [q.fieldName + '_month']: 1,
105
+ [q.fieldName + '_year']: 2025,
106
+ [q.fieldName + '_hour']: 10,
107
+ [q.fieldName + '_minutes']: 45,
108
+ [q.fieldName + '_period']: 'am'
109
+ };
110
+ case COMPONENT_TYPES.TEXT_ENTRY:
111
+ case COMPONENT_TYPES.SINGLE_LINE_INPUT:
112
+ return { [q.fieldName]: 'test' };
113
+ case COMPONENT_TYPES.TEXT_ENTRY_REDACT:
114
+ return { [q.fieldName]: 'secret' };
115
+ case COMPONENT_TYPES.MULTI_FIELD_INPUT: {
116
+ const res = {};
117
+ for (const field of q.inputFields) {
118
+ res[field.fieldName] = mockAnswer(field);
119
+ }
120
+ return res;
121
+ }
122
+ case COMPONENT_TYPES.ADDRESS:
123
+ return {
124
+ [q.fieldName + '_addressLine1']: '1 Test St',
125
+ [q.fieldName + '_townCity']: 'Testville',
126
+ [q.fieldName + '_postcode']: 'TE5 5ST'
127
+ };
128
+ case COMPONENT_TYPES.UNIT_OPTION:
129
+ return {
130
+ [q.fieldName]: 'kg',
131
+ [q.options[0].conditional.fieldName]: 10
132
+ };
133
+ case COMPONENT_TYPES.EMAIL:
134
+ return { [q.fieldName]: 'test@example.com' };
135
+ case COMPONENT_TYPES.MANAGE_LIST: {
136
+ // manage list questions not available here so just hard coded
137
+ return {
138
+ [q.fieldName]: [
139
+ { id: '1234', travelCompanionName: 'Companion 1', travelCompanionEmail: 'companion-1@example.com' },
140
+ { id: '4567', travelCompanionName: 'Companion 2', travelCompanionEmail: 'companion-2@example.com' },
141
+ { id: '8901', travelCompanionName: 'Companion 3', travelCompanionEmail: 'companion-3@example.com' }
142
+ ]
143
+ };
144
+ }
145
+ default:
146
+ return { [q.fieldName]: 'test' };
147
+ }
148
+ }
149
+
150
+ /**
151
+ * Build a valid & formatted answer for a question.
152
+ * @param {import('../src/questions/question-props.js').QuestionProps} q
153
+ * @returns {Object}
154
+ */
155
+ export function mockAnswer(q) {
156
+ switch (q.type) {
157
+ case COMPONENT_TYPES.BOOLEAN:
158
+ return BOOLEAN_OPTIONS.YES;
159
+ case COMPONENT_TYPES.CHECKBOX:
160
+ return q.options[0].text;
161
+ case COMPONENT_TYPES.RADIO:
162
+ case COMPONENT_TYPES.SELECT:
163
+ return q.options[0].text;
164
+ case COMPONENT_TYPES.NUMBER:
165
+ return 1;
166
+ case COMPONENT_TYPES.DATE:
167
+ return '1 January 2025';
168
+ case COMPONENT_TYPES.DATE_PERIOD:
169
+ return 'Start: 00:00 1 January 2025<br>End: 00:00 2 January 2025';
170
+ case COMPONENT_TYPES.DATE_TIME:
171
+ return '1 January 2025<br>10:45am';
172
+ case COMPONENT_TYPES.TEXT_ENTRY:
173
+ case COMPONENT_TYPES.SINGLE_LINE_INPUT:
174
+ return 'test';
175
+ case COMPONENT_TYPES.TEXT_ENTRY_REDACT:
176
+ return 'secret';
177
+ case COMPONENT_TYPES.MULTI_FIELD_INPUT:
178
+ if (q.inputFields) {
179
+ let res = '';
180
+ for (const field of q.inputFields) {
181
+ res += mockAnswer(field) + '<br>';
182
+ }
183
+ return res;
184
+ }
185
+ break;
186
+ case COMPONENT_TYPES.ADDRESS: {
187
+ const answer = mockAnswerBody(q);
188
+ return Object.values(answer).filter(Boolean).join('<br>');
189
+ }
190
+ case COMPONENT_TYPES.UNIT_OPTION:
191
+ return '10 kg';
192
+ case COMPONENT_TYPES.EMAIL:
193
+ return 'test@example.com';
194
+ case COMPONENT_TYPES.MANAGE_LIST: {
195
+ return `3 ${q.title}`;
196
+ }
197
+ default:
198
+ return 'test';
199
+ }
200
+ }
@@ -1,29 +0,0 @@
1
- {% from "govuk/components/summary-list/macro.njk" import govukSummaryList %}
2
-
3
- {% macro answerList(answers) %}
4
- {% set summaryRows = [] %}
5
-
6
- {% for item in answers %}
7
- {% set summaryRows = (summaryRows.push({
8
- key: {
9
- text: item.label
10
- },
11
- value: {
12
- html: item.answer | escape | nl2br | safe
13
- },
14
- actions: {
15
- items: [
16
- {
17
- href: item.removeLink,
18
- text: "Remove",
19
- visuallyHiddenText: item.label + ", " + item.answer
20
- }
21
- ]
22
- }
23
- }), summaryRows) %}
24
- {% endfor %}
25
-
26
- {{ govukSummaryList({
27
- rows: summaryRows
28
- }) }}
29
- {% endmacro %}
@@ -1,13 +0,0 @@
1
- /**
2
- * @param {string} alreadySubmittedUrl
3
- * @returns {import('express').Handler}
4
- */
5
- export default (alreadySubmittedUrl) => (req, res, next) => {
6
- const journeyResponse = res?.locals?.journeyResponse;
7
-
8
- if (journeyResponse?.answers?.submitted === 'yes' || journeyResponse?.answers?.submitted === true) {
9
- return res.redirect(alreadySubmittedUrl);
10
- }
11
-
12
- return next();
13
- };
@@ -1,118 +0,0 @@
1
- import { describe, it, mock } from 'node:test';
2
- import assert from 'node:assert';
3
- import checkNotSubmitted from './check-not-submitted.js';
4
-
5
- const testUrl = '/test';
6
-
7
- describe('dynamic-forms/middleware/check-not-submitted', () => {
8
- const mockReq = () => ({
9
- cookies: {},
10
- log: {},
11
- params: {}
12
- });
13
-
14
- [
15
- {
16
- description: 'should do nothing if submitted is undefined',
17
- given: () => {
18
- return {
19
- req: mockReq(),
20
- res: {}
21
- };
22
- },
23
- expected: (req, res, next) => {
24
- assert.strictEqual(next.mock.callCount(), 1);
25
- }
26
- },
27
- {
28
- description: 'should do nothing if submitted is false',
29
- given: () => {
30
- return {
31
- req: mockReq(),
32
- res: {
33
- locals: {
34
- journeyResponse: {
35
- answers: { submitted: false }
36
- }
37
- }
38
- }
39
- };
40
- },
41
- expected: (req, res, next) => {
42
- assert.strictEqual(next.mock.callCount(), 1);
43
- }
44
- },
45
- {
46
- description: 'should do nothing if submitted is no',
47
- given: () => {
48
- return {
49
- req: mockReq(),
50
- res: {
51
- locals: {
52
- journeyResponse: {
53
- answers: {
54
- submitted: 'no'
55
- }
56
- }
57
- }
58
- }
59
- };
60
- },
61
- expected: (req, res, next) => {
62
- assert.strictEqual(next.mock.callCount(), 1);
63
- }
64
- },
65
- {
66
- description: 'should redirect if submitted is true',
67
- given: () => {
68
- return {
69
- req: mockReq(),
70
- res: {
71
- locals: {
72
- journeyResponse: {
73
- answers: {
74
- submitted: true
75
- }
76
- }
77
- },
78
- redirect: mock.fn()
79
- }
80
- };
81
- },
82
- expected: (req, res) => {
83
- assert.strictEqual(res.redirect.mock.callCount(), 1);
84
- }
85
- },
86
- {
87
- description: 'should redirect if submitted is yes',
88
- given: () => {
89
- return {
90
- req: mockReq(),
91
- res: {
92
- locals: {
93
- journeyResponse: {
94
- answers: {
95
- submitted: 'yes'
96
- }
97
- }
98
- },
99
- redirect: mock.fn()
100
- }
101
- };
102
- },
103
- expected: (req, res) => {
104
- assert.strictEqual(res.redirect.mock.callCount(), 1);
105
- }
106
- }
107
- ].forEach(({ description, given, expected }) => {
108
- it(description, () => {
109
- const next = mock.fn();
110
- const req = given().req;
111
- const res = given().res;
112
-
113
- checkNotSubmitted(testUrl)(req, res, next);
114
-
115
- expected(req, res, next);
116
- });
117
- });
118
- });
@@ -1,46 +0,0 @@
1
- /**
2
- * Middleware for converting `req.files` to `req.body.files`.
3
- *
4
- * The requirement for this is due to `express-validator` only considering a limited subset of
5
- * keys on the `req` object as being eligible for validation.
6
- *
7
- * This middleware is designed for use with component-based dynamic forms
8
- *
9
- * This has not been tested with
10
- * pages that contain two or more multiple file uploads.
11
- *
12
- * @returns {function(*, *, *): (*)}
13
- */
14
- export default () => (req, res, next) => {
15
- if (!req.body || !req.files || !req.params) {
16
- return next();
17
- }
18
-
19
- const { journey } = res.locals;
20
-
21
- const questionObj = journey.getQuestionByParams(req.params);
22
- const filesPropertyPath = questionObj.fieldName;
23
-
24
- if (typeof req.files[filesPropertyPath] === 'undefined') {
25
- req.body.files = [];
26
- return next();
27
- }
28
-
29
- try {
30
- req.body.files = {
31
- ...req.body.files,
32
- // Ensure we are always working with an array. Single files would otherwise be an object.
33
- [filesPropertyPath]: Array.isArray(req.files[filesPropertyPath])
34
- ? req.files[filesPropertyPath]
35
- : [req.files[filesPropertyPath]]
36
- };
37
- } catch (err) {
38
- // this feels like it should never happen, as nothing in the try block throws. This is a precaution.
39
- /* istanbul ignore next */
40
- req.log.debug({ err, filesPropertyPath }, 'Error extracting req.files[filesPropertyPath]');
41
- /* istanbul ignore next */
42
- req.body.files = [];
43
- }
44
-
45
- return next();
46
- };