@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.
- package/README.md +9 -9
- package/package.json +1 -1
- package/src/components/address/question.js +2 -4
- package/src/components/boolean/question.js +6 -11
- package/src/components/date/question.js +5 -11
- package/src/components/date-period/question.js +5 -9
- package/src/components/date-time/question.js +5 -8
- package/src/components/date-time/question.test.js +3 -24
- package/src/components/manage-list/README.md +21 -2
- package/src/components/manage-list/index.njk +3 -3
- package/src/components/manage-list/question.js +24 -15
- package/src/components/manage-list/question.test.js +38 -1
- package/src/components/manage-list/utils.js +35 -0
- package/src/components/manage-list/utils.test.js +80 -0
- package/src/components/multi-field-input/question.js +7 -11
- package/src/components/multi-field-input/question.test.js +29 -1
- package/src/components/text-entry-redact/question.js +3 -4
- package/src/components/unit-option-entry/question.js +6 -13
- package/src/components/utils/question-has-answer.js +45 -11
- package/src/components/utils/question-utils.js +3 -37
- package/src/controller.js +11 -0
- package/src/controller.test.js +13 -1
- package/src/journey/journey.js +1 -0
- package/src/lib/utils.js +11 -0
- package/src/lib/utils.test.js +19 -0
- package/src/lib/uuid.test.js +0 -12
- package/src/questions/options-question.js +6 -13
- package/src/questions/question.js +17 -31
- package/src/questions/question.test.js +5 -19
- package/src/section-types.d.ts +2 -0
- package/src/section.js +5 -1
- package/src/section.test.js +7 -3
- package/test/check-your-answers.test.js +1 -1
- package/test/mock/uuid.js +13 -0
- package/test/questions.test.js +3 -200
- package/test/utils/question-test-utils.js +200 -0
- package/src/components/macros/answer-list.njk +0 -29
- package/src/middleware/check-not-submitted.js +0 -13
- package/src/middleware/check-not-submitted.test.js +0 -118
- package/src/middleware/dynamic-req-files-to-req-body-files.js +0 -46
- package/src/middleware/dynamic-req-files-to-req-body-files.test.js +0 -221
- package/src/validator/required-file-upload-validator.js +0 -38
- package/src/validator/required-file-upload-validator.test.js +0 -141
- package/src/validator/schemas/required-file-upload-schema.js +0 -35
package/test/questions.test.js
CHANGED
|
@@ -1,76 +1,11 @@
|
|
|
1
1
|
import { describe, it } from 'node:test';
|
|
2
2
|
import assert from 'assert';
|
|
3
|
-
import {
|
|
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 {
|
|
16
|
-
import { mockRandomUUID } from '#
|
|
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
|
-
};
|