@planning-inspectorate/dynamic-forms 1.13.0 → 1.14.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 (46) hide show
  1. package/.prettierignore +2 -1
  2. package/README.md +9 -1
  3. package/package.json +8 -3
  4. package/src/components/address/question.js +1 -1
  5. package/src/components/address/question.test.js +3 -3
  6. package/src/components/multi-field-input/index.njk +2 -2
  7. package/src/components/multi-field-input/question.js +5 -1
  8. package/src/components/radio/index.njk +2 -2
  9. package/src/components/select/index.njk +11 -1
  10. package/src/components/text-entry-redact/question.test.js +9 -9
  11. package/src/controller.test.js +2 -1
  12. package/src/index.js +8 -2
  13. package/src/lib/session-answer-store.test.js +1 -1
  14. package/src/questions/question-props.d.ts +5 -24
  15. package/src/questions/question.test.js +1 -1
  16. package/src/section.js +3 -0
  17. package/src/validator/validator.js +4 -1
  18. package/test/check-your-answers.test.js +69 -0
  19. package/test/journey.js +41 -0
  20. package/test/journey.test.js +21 -0
  21. package/test/questions.js +189 -0
  22. package/test/questions.test.js +246 -0
  23. package/test/snapshots/add-insurance.html +44 -0
  24. package/test/snapshots/check-your-answers-not-started.html +201 -0
  25. package/test/snapshots/check-your-answers-with-answers.html +201 -0
  26. package/test/snapshots/companions.html +51 -0
  27. package/test/snapshots/departure-date.html +57 -0
  28. package/test/snapshots/holiday-activities.html +49 -0
  29. package/test/snapshots/holiday-arrival.html +101 -0
  30. package/test/snapshots/holiday-description.html +33 -0
  31. package/test/snapshots/holiday-destination.html +56 -0
  32. package/test/snapshots/holiday-period.html +102 -0
  33. package/test/snapshots/holiday-snack.html +33 -0
  34. package/test/snapshots/hotel-address.html +65 -0
  35. package/test/snapshots/luggage-weight-units.html +75 -0
  36. package/test/snapshots/nights.html +28 -0
  37. package/test/snapshots/secret-wish.html +173 -0
  38. package/test/snapshots/travel-class.html +52 -0
  39. package/test/snapshots/travel-insurance-type.html +56 -0
  40. package/test/utils/app.js +41 -0
  41. package/test/utils/nunjucks.js +20 -0
  42. package/test/utils/test-server.js +167 -0
  43. package/test/utils/test-server.test.js +75 -0
  44. package/{src/lib/test-utils.js → test/utils/utils.js} +15 -16
  45. package/test/views/layout-check-your-answers.njk +1 -0
  46. /package/{src/lib/test-layout.njk → test/views/layout-question.njk} +0 -0
@@ -0,0 +1,189 @@
1
+ import { COMPONENT_TYPES } from '../src/index.js';
2
+ import { createQuestions } from '../src/questions/create-questions.js';
3
+ import { questionClasses } from '../src/questions/questions.js';
4
+
5
+ /**
6
+ * @typedef {import('../src/questions/question-props.js').QuestionProps} Props
7
+ */
8
+
9
+ /** @type {Record<string, Props>} */
10
+ export const questionProps = {
11
+ holidayActivities: {
12
+ type: COMPONENT_TYPES.CHECKBOX,
13
+ title: 'Holiday Activities',
14
+ question: 'Which activities do you want to do on holiday?',
15
+ fieldName: 'holidayActivities',
16
+ url: 'holiday-activities',
17
+ label: 'Select all that apply',
18
+ options: [
19
+ { value: 'swimming', text: 'Swimming' },
20
+ { value: 'hiking', text: 'Hiking' },
21
+ { value: 'sightseeing', text: 'Sightseeing' }
22
+ ]
23
+ },
24
+ addInsurance: {
25
+ type: COMPONENT_TYPES.BOOLEAN,
26
+ title: 'Travel Insurance',
27
+ question: 'Do you want to add travel insurance to your holiday?',
28
+ fieldName: 'addInsurance',
29
+ url: 'add-insurance',
30
+ label: 'Add insurance?'
31
+ },
32
+ travelInsuranceType: {
33
+ type: COMPONENT_TYPES.RADIO,
34
+ title: 'Travel Insurance Type',
35
+ question: 'Which type of travel insurance would you like?',
36
+ fieldName: 'travelInsuranceType',
37
+ url: 'travel-insurance-type',
38
+ label: 'Select an insurance type',
39
+ options: [
40
+ { value: 'basic', text: 'Basic' },
41
+ { value: 'comprehensive', text: 'Comprehensive' },
42
+ { value: 'premium', text: 'Premium' }
43
+ ]
44
+ },
45
+ holidayDestination: {
46
+ type: COMPONENT_TYPES.RADIO,
47
+ title: 'Holiday Destination',
48
+ question: 'Where would you like to go on holiday?',
49
+ fieldName: 'holidayDestination',
50
+ url: 'holiday-destination',
51
+ label: 'Choose a destination',
52
+ options: [
53
+ { value: 'beach', text: 'Beach' },
54
+ { value: 'mountains', text: 'Mountains' },
55
+ { value: 'city', text: 'City' }
56
+ ]
57
+ },
58
+ departureDate: {
59
+ type: COMPONENT_TYPES.DATE,
60
+ title: 'Departure Date',
61
+ question: 'When do you want to start your holiday?',
62
+ fieldName: 'departureDate',
63
+ url: 'departure-date',
64
+ label: 'Select a date'
65
+ },
66
+ holidayPeriod: {
67
+ type: COMPONENT_TYPES.DATE_PERIOD,
68
+ title: 'Holiday Period',
69
+ question: 'What is the period of your holiday?',
70
+ fieldName: 'holidayPeriod',
71
+ url: 'holiday-period',
72
+ label: 'Select start and end dates'
73
+ },
74
+ holidayDescription: {
75
+ type: COMPONENT_TYPES.TEXT_ENTRY,
76
+ title: 'Holiday Description',
77
+ question: 'Describe your ideal holiday.',
78
+ fieldName: 'holidayDescription',
79
+ url: 'holiday-description',
80
+ label: 'Description'
81
+ },
82
+ secretWish: {
83
+ type: COMPONENT_TYPES.TEXT_ENTRY_REDACT,
84
+ title: 'Secret Holiday Wish',
85
+ question: 'Share a secret wish for your holiday (will be redacted).',
86
+ fieldName: 'secretWish',
87
+ url: 'secret-wish',
88
+ label: 'Secret wish'
89
+ },
90
+ travelClass: {
91
+ type: COMPONENT_TYPES.SELECT,
92
+ title: 'Travel Class',
93
+ question: 'Which class do you want to travel in?',
94
+ fieldName: 'travelClass',
95
+ url: 'travel-class',
96
+ label: 'Select a class',
97
+ options: [
98
+ { value: 'economy', text: 'Economy' },
99
+ { value: 'business', text: 'Business' },
100
+ { value: 'first', text: 'First Class' }
101
+ ]
102
+ },
103
+ holidayArrival: {
104
+ type: COMPONENT_TYPES.DATE_TIME,
105
+ title: 'Holiday Arrival',
106
+ question: 'When would you like to arrive at your destination?',
107
+ fieldName: 'holidayArrival',
108
+ url: 'holiday-arrival',
109
+ label: 'Enter your arrival time'
110
+ },
111
+ holidaySnack: {
112
+ type: COMPONENT_TYPES.SINGLE_LINE_INPUT,
113
+ title: 'Favourite Holiday Snack',
114
+ question: 'What is your favourite snack to eat on holiday?',
115
+ fieldName: 'holidaySnack',
116
+ url: 'holiday-snack',
117
+ label: 'Snack'
118
+ },
119
+ companions: {
120
+ type: COMPONENT_TYPES.MULTI_FIELD_INPUT,
121
+ title: 'Travel Companions',
122
+ question: 'Enter the names and ages of your travel companions.',
123
+ fieldName: 'companions',
124
+ url: 'companions',
125
+ label: 'Companions',
126
+ inputFields: [
127
+ { fieldName: 'name', label: 'Name', type: COMPONENT_TYPES.TEXT_ENTRY },
128
+ { fieldName: 'age', label: 'Age', type: COMPONENT_TYPES.NUMBER }
129
+ ]
130
+ },
131
+ nights: {
132
+ type: COMPONENT_TYPES.NUMBER,
133
+ title: 'Number of Nights',
134
+ question: 'How many nights will you stay on holiday?',
135
+ fieldName: 'nights',
136
+ url: 'nights',
137
+ label: 'Nights'
138
+ },
139
+ hotelAddress: {
140
+ type: COMPONENT_TYPES.ADDRESS,
141
+ title: 'Hotel Address',
142
+ question: 'What is the address of your hotel?',
143
+ fieldName: 'hotelAddress',
144
+ url: 'hotel-address',
145
+ label: 'Hotel address'
146
+ },
147
+ luggageWeight: {
148
+ type: COMPONENT_TYPES.UNIT_OPTION,
149
+ title: 'Luggage Weight',
150
+ question: 'Select your luggage weight and unit.',
151
+ fieldName: 'luggageWeightUnits',
152
+ conditionalFieldName: 'luggageWeightValue',
153
+ url: 'luggage-weight-units',
154
+ label: 'Luggage weight',
155
+ options: [
156
+ {
157
+ text: 'Kilograms',
158
+ value: 'kg',
159
+ conditional: { label: 'Weight in kilograms', fieldName: 'luggageWeightValue_kg', suffix: 'kg' }
160
+ },
161
+ {
162
+ text: 'Pounds',
163
+ value: 'lbs',
164
+ conditional: { label: 'Weight in pounds', fieldName: 'luggageWeightValue_lbs', suffix: 'lbs' }
165
+ }
166
+ ]
167
+ }
168
+ };
169
+
170
+ // questions in order for the journey - used to check the journey redirects to the next question correctly
171
+ export const questionsInOrder = [
172
+ questionProps.holidayActivities,
173
+ questionProps.addInsurance,
174
+ questionProps.travelInsuranceType,
175
+ questionProps.holidayDestination,
176
+ questionProps.departureDate,
177
+ questionProps.holidayPeriod,
178
+ questionProps.holidayDescription,
179
+ questionProps.secretWish,
180
+ questionProps.travelClass,
181
+ questionProps.holidayArrival,
182
+ questionProps.holidaySnack,
183
+ questionProps.companions,
184
+ questionProps.nights,
185
+ questionProps.hotelAddress,
186
+ questionProps.luggageWeight
187
+ ];
188
+
189
+ export const getQuestions = () => createQuestions(questionProps, questionClasses, {});
@@ -0,0 +1,246 @@
1
+ import { describe, it } from 'node:test';
2
+ import assert from 'assert';
3
+ import { createApp } from './utils/app.js';
4
+ import { TestServer } from './utils/test-server.js';
5
+ import { getQuestions, 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';
12
+ import path from 'path';
13
+ import { COMPONENT_TYPES } from '../src/index.js';
14
+ import { escapeForRegExp, snapshotsDir } from './utils/utils.js';
15
+ import { BOOLEAN_OPTIONS } from '../src/components/boolean/question.js';
16
+
17
+ /**
18
+ * @param {import('node:test').TestContext} ctx
19
+ * @returns {Promise<TestServer>}
20
+ */
21
+ export async function createAppWithQuestions(ctx) {
22
+ const app = createApp();
23
+ const questions = getQuestions();
24
+ const getJourney = buildGetJourney((req, journeyResponse) => createJourney(questions, journeyResponse));
25
+ const getJourneyResponse = buildGetJourneyResponseFromSession(JOURNEY_ID);
26
+
27
+ // app.use((req, res, next) => {
28
+ // console.log(req.method, req.url, req.session);
29
+ // next();
30
+ // });
31
+
32
+ app.get('/:section/:question', getJourneyResponse, getJourney, question);
33
+
34
+ app.post(
35
+ '/:section/:question',
36
+ getJourneyResponse,
37
+ getJourney,
38
+ validate,
39
+ validationErrorHandler,
40
+ buildSave(saveDataToSession)
41
+ );
42
+
43
+ app.get('/check-your-answers', getJourneyResponse, getJourney, (req, res) => list(req, res, '', {}));
44
+
45
+ app.use((error, req, res, next) => {
46
+ if (error) {
47
+ console.error('Internal Server Error:', error);
48
+ }
49
+ next();
50
+ });
51
+
52
+ app.use((req, res) => {
53
+ res.status(404);
54
+ });
55
+
56
+ const server = new TestServer(app, { rememberCookies: true });
57
+ await server.start();
58
+ ctx.after(async () => await server.stop());
59
+ return server;
60
+ }
61
+
62
+ describe('question pages', () => {
63
+ it('should have a test for each question type', () => {
64
+ const questionsByType = new Map();
65
+ for (const q of questionsInOrder) {
66
+ if (!questionsByType.has(q.type)) {
67
+ questionsByType.set(q.type, []);
68
+ }
69
+ questionsByType.get(q.type).push(q);
70
+ }
71
+ for (const [k, v] of Object.entries(COMPONENT_TYPES)) {
72
+ const questions = questionsByType.get(v);
73
+ if (!questions || questions.length === 0) {
74
+ throw new Error(`no test questions for ${k}`);
75
+ }
76
+ }
77
+ });
78
+
79
+ for (let i = 0; i < questionsInOrder.length; i++) {
80
+ const q = questionsInOrder[i];
81
+
82
+ it(`should render question: ${q.url}`, async (ctx) => {
83
+ const testServer = await createAppWithQuestions(ctx);
84
+
85
+ const response = await testServer.get('/questions/' + q.url, {
86
+ redirect: 'manual'
87
+ });
88
+ assert.strictEqual(response.status, 200);
89
+ const text = await response.text();
90
+ // be sure to escape parentheses in the question text for the regex
91
+ assert.match(text, new RegExp(escapeForRegExp(q.question), 'i'));
92
+ assert.match(text, /<form action="" method="post"/i);
93
+ if (q.options) {
94
+ for (const option of q.options) {
95
+ if (q.type === COMPONENT_TYPES.SELECT) {
96
+ assert.match(
97
+ text,
98
+ new RegExp(`<option[^>]*value="${option.value}"[^>]*>\\s*${option.text}\\s*</option>`, 'i')
99
+ );
100
+ } else {
101
+ assert.match(text, new RegExp(`<input[^>]*value="${option.value}"[^>]*>`));
102
+ assert.match(text, new RegExp(`<label[^>]*>\\s*${option.text}\\s*</label>`, 'i'));
103
+ }
104
+ }
105
+ }
106
+ ctx.assert.fileSnapshot(text, path.join(snapshotsDir(), q.url + '.html'), {
107
+ serializers: [(v) => v]
108
+ });
109
+ });
110
+
111
+ it(`should POST valid data for question: ${q.url} and redirect to next`, async (ctx) => {
112
+ const testServer = await createAppWithQuestions(ctx);
113
+ const payload = mockAnswerBody(q);
114
+ const response = await testServer.post(`/questions/${q.url}`, payload, { redirect: 'manual' });
115
+ if (![302, 303].includes(response.status)) {
116
+ const text = await response.text();
117
+ console.log(`Response for ${q.url}:\n`, text);
118
+ }
119
+ // Should redirect (302 or 303)
120
+ assert.ok([302, 303].includes(response.status), `Expected redirect, got ${response.status}`);
121
+ // Should redirect to the next question (if there is one)
122
+ const location = response.headers.get('location');
123
+ const nextQ = questionsInOrder[i + 1];
124
+ if (nextQ) {
125
+ assert.strictEqual(location, `questions/${nextQ.url}`);
126
+ } else {
127
+ assert.strictEqual(location, 'check-your-answers');
128
+ }
129
+ });
130
+ }
131
+ });
132
+
133
+ /**
134
+ * Build a valid payload for a question.
135
+ * @param {import('../src/questions/question-props.js').QuestionProps} q
136
+ * @returns {Object}
137
+ */
138
+ export function mockAnswerBody(q) {
139
+ switch (q.type) {
140
+ case COMPONENT_TYPES.BOOLEAN:
141
+ return { [q.fieldName]: BOOLEAN_OPTIONS.YES };
142
+ case COMPONENT_TYPES.CHECKBOX:
143
+ return { [q.fieldName]: [q.options[0].value] };
144
+ case COMPONENT_TYPES.RADIO:
145
+ case COMPONENT_TYPES.SELECT:
146
+ return { [q.fieldName]: q.options[0].value };
147
+ case COMPONENT_TYPES.NUMBER:
148
+ return { [q.fieldName]: 1 };
149
+ case COMPONENT_TYPES.DATE:
150
+ return {
151
+ [q.fieldName + '_day']: 1,
152
+ [q.fieldName + '_month']: 1,
153
+ [q.fieldName + '_year']: 2025
154
+ };
155
+ case COMPONENT_TYPES.DATE_PERIOD:
156
+ return {
157
+ [q.fieldName + '_start_day']: 1,
158
+ [q.fieldName + '_start_month']: 1,
159
+ [q.fieldName + '_start_year']: 2025,
160
+ [q.fieldName + '_end_day']: 2,
161
+ [q.fieldName + '_end_month']: 1,
162
+ [q.fieldName + '_end_year']: 2025
163
+ };
164
+ case COMPONENT_TYPES.DATE_TIME:
165
+ return {
166
+ [q.fieldName + '_day']: 1,
167
+ [q.fieldName + '_month']: 1,
168
+ [q.fieldName + '_year']: 2025,
169
+ [q.fieldName + '_hour']: 10,
170
+ [q.fieldName + '_minutes']: 45,
171
+ [q.fieldName + '_period']: 'am'
172
+ };
173
+ case COMPONENT_TYPES.TEXT_ENTRY:
174
+ case COMPONENT_TYPES.SINGLE_LINE_INPUT:
175
+ return { [q.fieldName]: 'test' };
176
+ case COMPONENT_TYPES.TEXT_ENTRY_REDACT:
177
+ return { [q.fieldName]: 'secret' };
178
+ case COMPONENT_TYPES.MULTI_FIELD_INPUT: {
179
+ const res = {};
180
+ for (const field of q.inputFields) {
181
+ res[field.fieldName] = mockAnswer(field);
182
+ }
183
+ return res;
184
+ }
185
+ case COMPONENT_TYPES.ADDRESS:
186
+ return {
187
+ [q.fieldName + '_addressLine1']: '1 Test St',
188
+ [q.fieldName + '_townCity']: 'Testville',
189
+ [q.fieldName + '_postcode']: 'TE5 5ST'
190
+ };
191
+ case COMPONENT_TYPES.UNIT_OPTION:
192
+ return {
193
+ [q.fieldName]: 'kg',
194
+ [q.options[0].conditional.fieldName]: 10
195
+ };
196
+ default:
197
+ return { [q.fieldName]: 'test' };
198
+ }
199
+ }
200
+
201
+ /**
202
+ * Build a valid & formatted answer for a question.
203
+ * @param {import('../src/questions/question-props.js').QuestionProps} q
204
+ * @returns {Object}
205
+ */
206
+ export function mockAnswer(q) {
207
+ switch (q.type) {
208
+ case COMPONENT_TYPES.BOOLEAN:
209
+ return BOOLEAN_OPTIONS.YES;
210
+ case COMPONENT_TYPES.CHECKBOX:
211
+ return q.options[0].text;
212
+ case COMPONENT_TYPES.RADIO:
213
+ case COMPONENT_TYPES.SELECT:
214
+ return q.options[0].text;
215
+ case COMPONENT_TYPES.NUMBER:
216
+ return 1;
217
+ case COMPONENT_TYPES.DATE:
218
+ return '1 January 2025';
219
+ case COMPONENT_TYPES.DATE_PERIOD:
220
+ return 'Start: 00:00 1 January 2025<br>End: 00:00 2 January 2025';
221
+ case COMPONENT_TYPES.DATE_TIME:
222
+ return '1 January 2025<br>10:45am';
223
+ case COMPONENT_TYPES.TEXT_ENTRY:
224
+ case COMPONENT_TYPES.SINGLE_LINE_INPUT:
225
+ return 'test';
226
+ case COMPONENT_TYPES.TEXT_ENTRY_REDACT:
227
+ return 'secret';
228
+ case COMPONENT_TYPES.MULTI_FIELD_INPUT:
229
+ if (q.inputFields) {
230
+ let res = '';
231
+ for (const field of q.inputFields) {
232
+ res += mockAnswer(field) + '<br>';
233
+ }
234
+ return res;
235
+ }
236
+ break;
237
+ case COMPONENT_TYPES.ADDRESS: {
238
+ const answer = mockAnswerBody(q);
239
+ return Object.values(answer).filter(Boolean).join('<br>');
240
+ }
241
+ case COMPONENT_TYPES.UNIT_OPTION:
242
+ return '10 kg';
243
+ default:
244
+ return 'test';
245
+ }
246
+ }
@@ -0,0 +1,44 @@
1
+ <div class="govuk-grid-row">
2
+ <div class="govuk-grid-column-two-thirds">
3
+ </div>
4
+ </div>
5
+
6
+
7
+ <div class="govuk-grid-row">
8
+ <div class="govuk-grid-column-two-thirds">
9
+ <form action="" method="post" novalidate>
10
+ <input type="hidden" name="_csrf" value="">
11
+ <div class="govuk-form-group">
12
+ <fieldset class="govuk-fieldset" aria-describedby="addInsurance-hint">
13
+ <legend class="govuk-fieldset__legend govuk-fieldset__legend--l">
14
+ <h1 class="govuk-fieldset__heading">
15
+ Do you want to add travel insurance to your holiday?
16
+ </h1>
17
+ </legend>
18
+ <div id="addInsurance-hint" class="govuk-hint">
19
+
20
+ </div>
21
+ <div class="govuk-radios govuk-radios" data-module="govuk-radios">
22
+ <div class="govuk-radios__item">
23
+ <input class="govuk-radios__input" id="addInsurance" name="addInsurance" type="radio" value="yes" data-cy="answer-yes">
24
+ <label class="govuk-label govuk-radios__label" for="addInsurance">
25
+ Yes
26
+ </label>
27
+ </div>
28
+ <div class="govuk-radios__item">
29
+ <input class="govuk-radios__input" id="addInsurance-2" name="addInsurance" type="radio" value="no" data-cy="answer-no">
30
+ <label class="govuk-label govuk-radios__label" for="addInsurance-2">
31
+ No
32
+ </label>
33
+ </div>
34
+ </div>
35
+ </fieldset>
36
+ </div>
37
+
38
+
39
+ <button type="submit" class="govuk-button" data-module="govuk-button" data-cy="button-save-and-continue">
40
+ Continue
41
+ </button>
42
+ </form>
43
+ </div>
44
+ </div>
@@ -0,0 +1,201 @@
1
+
2
+ <style >
3
+ .app-task-list {
4
+ padding-left: 0;
5
+ }
6
+ .app-task-list>li {
7
+ position: relative;
8
+ list-style-type: none;
9
+
10
+ }
11
+ .app-task-list__section .govuk-tag {
12
+ position: absolute;
13
+ top: 3px;
14
+ right: 0;
15
+ }
16
+ .app-task-list .govuk-summary-list {
17
+ border-top: 1px solid #b1b4b6;
18
+ }
19
+ </style>
20
+
21
+ <div class="govuk-grid-row">
22
+ <div class="govuk-grid-column-full">
23
+ <dl class="govuk-summary-list">
24
+ <div class="govuk-summary-list__row">
25
+ <dt class="govuk-summary-list__key">
26
+ Holiday Activities
27
+ </dt>
28
+ <dd class="govuk-summary-list__value">
29
+ Not started
30
+ </dd>
31
+ <dd class="govuk-summary-list__actions">
32
+ <a class="govuk-link" href="questions/holiday-activities">Answer<span class="govuk-visually-hidden"> Which activities do you want to do on holiday?</span></a>
33
+ </dd>
34
+ </div>
35
+ <div class="govuk-summary-list__row">
36
+ <dt class="govuk-summary-list__key">
37
+ Travel Insurance
38
+ </dt>
39
+ <dd class="govuk-summary-list__value">
40
+ Not started
41
+ </dd>
42
+ <dd class="govuk-summary-list__actions">
43
+ <a class="govuk-link" href="questions/add-insurance">Answer<span class="govuk-visually-hidden"> Do you want to add travel insurance to your holiday?</span></a>
44
+ </dd>
45
+ </div>
46
+ <div class="govuk-summary-list__row">
47
+ <dt class="govuk-summary-list__key">
48
+ Travel Insurance Type
49
+ </dt>
50
+ <dd class="govuk-summary-list__value">
51
+ Not started
52
+ </dd>
53
+ <dd class="govuk-summary-list__actions">
54
+ <a class="govuk-link" href="questions/travel-insurance-type">Answer<span class="govuk-visually-hidden"> Which type of travel insurance would you like?</span></a>
55
+ </dd>
56
+ </div>
57
+ <div class="govuk-summary-list__row">
58
+ <dt class="govuk-summary-list__key">
59
+ Holiday Destination
60
+ </dt>
61
+ <dd class="govuk-summary-list__value">
62
+ Not started
63
+ </dd>
64
+ <dd class="govuk-summary-list__actions">
65
+ <a class="govuk-link" href="questions/holiday-destination">Answer<span class="govuk-visually-hidden"> Where would you like to go on holiday?</span></a>
66
+ </dd>
67
+ </div>
68
+ <div class="govuk-summary-list__row">
69
+ <dt class="govuk-summary-list__key">
70
+ Departure Date
71
+ </dt>
72
+ <dd class="govuk-summary-list__value">
73
+ Not started
74
+ </dd>
75
+ <dd class="govuk-summary-list__actions">
76
+ <a class="govuk-link" href="questions/departure-date">Answer<span class="govuk-visually-hidden"> When do you want to start your holiday?</span></a>
77
+ </dd>
78
+ </div>
79
+ <div class="govuk-summary-list__row">
80
+ <dt class="govuk-summary-list__key">
81
+ Holiday Period
82
+ </dt>
83
+ <dd class="govuk-summary-list__value">
84
+ Not started
85
+ </dd>
86
+ <dd class="govuk-summary-list__actions">
87
+ <a class="govuk-link" href="questions/holiday-period">Answer<span class="govuk-visually-hidden"> What is the period of your holiday?</span></a>
88
+ </dd>
89
+ </div>
90
+ <div class="govuk-summary-list__row">
91
+ <dt class="govuk-summary-list__key">
92
+ Holiday Description
93
+ </dt>
94
+ <dd class="govuk-summary-list__value">
95
+ Not started
96
+ </dd>
97
+ <dd class="govuk-summary-list__actions">
98
+ <a class="govuk-link" href="questions/holiday-description">Answer<span class="govuk-visually-hidden"> Describe your ideal holiday.</span></a>
99
+ </dd>
100
+ </div>
101
+ <div class="govuk-summary-list__row">
102
+ <dt class="govuk-summary-list__key">
103
+ Secret Holiday Wish
104
+ </dt>
105
+ <dd class="govuk-summary-list__value">
106
+ Not started
107
+ </dd>
108
+ <dd class="govuk-summary-list__actions">
109
+ <a class="govuk-link" href="questions/secret-wish">Answer<span class="govuk-visually-hidden"> Share a secret wish for your holiday (will be redacted).</span></a>
110
+ </dd>
111
+ </div>
112
+ <div class="govuk-summary-list__row">
113
+ <dt class="govuk-summary-list__key">
114
+ Travel Class
115
+ </dt>
116
+ <dd class="govuk-summary-list__value">
117
+ Not started
118
+ </dd>
119
+ <dd class="govuk-summary-list__actions">
120
+ <a class="govuk-link" href="questions/travel-class">Answer<span class="govuk-visually-hidden"> Which class do you want to travel in?</span></a>
121
+ </dd>
122
+ </div>
123
+ <div class="govuk-summary-list__row">
124
+ <dt class="govuk-summary-list__key">
125
+ Holiday Arrival
126
+ </dt>
127
+ <dd class="govuk-summary-list__value">
128
+ Not started
129
+ </dd>
130
+ <dd class="govuk-summary-list__actions">
131
+ <a class="govuk-link" href="questions/holiday-arrival">Answer<span class="govuk-visually-hidden"> When would you like to arrive at your destination?</span></a>
132
+ </dd>
133
+ </div>
134
+ <div class="govuk-summary-list__row">
135
+ <dt class="govuk-summary-list__key">
136
+ Favourite Holiday Snack
137
+ </dt>
138
+ <dd class="govuk-summary-list__value">
139
+ Not started
140
+ </dd>
141
+ <dd class="govuk-summary-list__actions">
142
+ <a class="govuk-link" href="questions/holiday-snack">Answer<span class="govuk-visually-hidden"> What is your favourite snack to eat on holiday?</span></a>
143
+ </dd>
144
+ </div>
145
+ <div class="govuk-summary-list__row">
146
+ <dt class="govuk-summary-list__key">
147
+ Travel Companions
148
+ </dt>
149
+ <dd class="govuk-summary-list__value">
150
+ Not started
151
+ </dd>
152
+ <dd class="govuk-summary-list__actions">
153
+ <a class="govuk-link" href="questions/companions">Answer<span class="govuk-visually-hidden"> Enter the names and ages of your travel companions.</span></a>
154
+ </dd>
155
+ </div>
156
+ <div class="govuk-summary-list__row">
157
+ <dt class="govuk-summary-list__key">
158
+ Number of Nights
159
+ </dt>
160
+ <dd class="govuk-summary-list__value">
161
+ Not started
162
+ </dd>
163
+ <dd class="govuk-summary-list__actions">
164
+ <a class="govuk-link" href="questions/nights">Answer<span class="govuk-visually-hidden"> How many nights will you stay on holiday?</span></a>
165
+ </dd>
166
+ </div>
167
+ <div class="govuk-summary-list__row">
168
+ <dt class="govuk-summary-list__key">
169
+ Hotel Address
170
+ </dt>
171
+ <dd class="govuk-summary-list__value">
172
+ Not started
173
+ </dd>
174
+ <dd class="govuk-summary-list__actions">
175
+ <a class="govuk-link" href="questions/hotel-address">Answer<span class="govuk-visually-hidden"> What is the address of your hotel?</span></a>
176
+ </dd>
177
+ </div>
178
+ <div class="govuk-summary-list__row">
179
+ <dt class="govuk-summary-list__key">
180
+ Luggage Weight
181
+ </dt>
182
+ <dd class="govuk-summary-list__value">
183
+ Not started
184
+ </dd>
185
+ <dd class="govuk-summary-list__actions">
186
+ <a class="govuk-link" href="questions/luggage-weight-units">Answer<span class="govuk-visually-hidden"> Select your luggage weight and unit.</span></a>
187
+ </dd>
188
+ </div>
189
+ </dl>
190
+
191
+ <form action method="post">
192
+ <input type="hidden" name="_csrf" value="">
193
+ <button type="submit" class="govuk-button" data-module="govuk-button">
194
+ Accept &amp; Submit
195
+ </button>
196
+ </form>
197
+
198
+ </div>
199
+ </div>
200
+
201
+