@planning-inspectorate/dynamic-forms 1.14.0 → 1.15.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.prettierignore +2 -1
- package/README.md +9 -1
- package/package.json +7 -2
- package/src/components/address/question.js +1 -1
- package/src/components/address/question.test.js +3 -3
- package/src/components/multi-field-input/index.njk +2 -2
- package/src/components/multi-field-input/question.js +5 -1
- package/src/components/radio/index.njk +2 -2
- package/src/components/select/index.njk +2 -2
- package/src/components/text-entry-redact/question.test.js +9 -9
- package/src/controller.test.js +2 -1
- package/src/index.js +8 -2
- package/src/lib/session-answer-store.test.js +1 -1
- package/src/questions/question-props.d.ts +5 -24
- package/src/questions/question.test.js +1 -1
- package/src/section.js +3 -0
- package/src/validator/validator.js +4 -1
- package/test/check-your-answers.test.js +69 -0
- package/test/journey.js +41 -0
- package/test/journey.test.js +21 -0
- package/test/questions.js +189 -0
- package/test/questions.test.js +246 -0
- package/test/snapshots/add-insurance.html +44 -0
- package/test/snapshots/check-your-answers-not-started.html +201 -0
- package/test/snapshots/check-your-answers-with-answers.html +201 -0
- package/test/snapshots/companions.html +51 -0
- package/test/snapshots/departure-date.html +57 -0
- package/test/snapshots/holiday-activities.html +49 -0
- package/test/snapshots/holiday-arrival.html +101 -0
- package/test/snapshots/holiday-description.html +33 -0
- package/test/snapshots/holiday-destination.html +56 -0
- package/test/snapshots/holiday-period.html +102 -0
- package/test/snapshots/holiday-snack.html +33 -0
- package/test/snapshots/hotel-address.html +65 -0
- package/test/snapshots/luggage-weight-units.html +75 -0
- package/test/snapshots/nights.html +28 -0
- package/test/snapshots/secret-wish.html +173 -0
- package/test/snapshots/travel-class.html +52 -0
- package/test/snapshots/travel-insurance-type.html +56 -0
- package/test/utils/app.js +41 -0
- package/test/utils/nunjucks.js +20 -0
- package/test/utils/test-server.js +167 -0
- package/test/utils/test-server.test.js +75 -0
- package/{src/lib/test-utils.js → test/utils/utils.js} +15 -16
- package/test/views/layout-check-your-answers.njk +1 -0
- /package/{src/lib/test-layout.njk → test/views/layout-question.njk} +0 -0
package/.prettierignore
CHANGED
package/README.md
CHANGED
|
@@ -142,4 +142,12 @@ When contributing to this package, ensure changes are generic and not service-sp
|
|
|
142
142
|
|
|
143
143
|
Commits must follow conventional commits, and the commit types will be used by semantic-release to determine the next version number. For example `feat` commits will result in a minor version bump, while `fix` commits will result in a patch version bump. Major version bumps should be reserved for breaking changes, and should be discussed with the R&D team before being made.
|
|
144
144
|
|
|
145
|
-
The package will be released automatically using semantic-release, on merge to main. This will include a git tag for the release, and publishing to NPM.
|
|
145
|
+
The package will be released automatically using semantic-release, on merge to main. This will include a git tag for the release, and publishing to NPM.
|
|
146
|
+
|
|
147
|
+
### Tests
|
|
148
|
+
|
|
149
|
+
There are some lightweight tests in the `test` directory which sets up a basic journey and checks the rendering for each question as well as redirect logic.
|
|
150
|
+
|
|
151
|
+
When adding a new question type, be sure to add an example question into `test/questions.js`, and mock answers into `test/questions.test.js#mockAnswerBody` and `test/questions.test.js#mockAnswer`. Also, the question should be added to the journey in `test/journey.js`.
|
|
152
|
+
|
|
153
|
+
To update any snapshots with rendering changes (or new questions), run `node --test --test-update-snapshots`.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@planning-inspectorate/dynamic-forms",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.15.0",
|
|
4
4
|
"description": "This package is for building GDS forms using a configuration-based approach.",
|
|
5
5
|
"main": "./src/index.js",
|
|
6
6
|
"type": "module",
|
|
@@ -25,13 +25,18 @@
|
|
|
25
25
|
"nunjucks": "^3.2.4"
|
|
26
26
|
},
|
|
27
27
|
"imports": {
|
|
28
|
-
"#question-types": "./src/questions/question-types.d.ts"
|
|
28
|
+
"#question-types": "./src/questions/question-types.d.ts",
|
|
29
|
+
"#test/*": "./test/*"
|
|
29
30
|
},
|
|
30
31
|
"devDependencies": {
|
|
31
32
|
"@commitlint/cli": "^20.0.0",
|
|
32
33
|
"@commitlint/config-conventional": "^20.0.0",
|
|
34
|
+
"body-parser": "^2.2.0",
|
|
35
|
+
"cookie-parser": "^1.4.7",
|
|
33
36
|
"eslint": "^9.29.0",
|
|
34
37
|
"eslint-config-prettier": "^10.1.5",
|
|
38
|
+
"express": "^5.1.0",
|
|
39
|
+
"express-session": "^1.18.2",
|
|
35
40
|
"govuk-frontend": "^5.11.0",
|
|
36
41
|
"husky": "^9.1.7",
|
|
37
42
|
"lint-staged": "^16.1.2",
|
|
@@ -24,7 +24,7 @@ export default class AddressQuestion extends Question {
|
|
|
24
24
|
viewFolder: 'address'
|
|
25
25
|
});
|
|
26
26
|
|
|
27
|
-
for (const validator of params.validators) {
|
|
27
|
+
for (const validator of params.validators || []) {
|
|
28
28
|
if (validator instanceof AddressValidator) {
|
|
29
29
|
this.requiredFields = validator.requiredFields;
|
|
30
30
|
}
|
|
@@ -3,7 +3,7 @@ import assert from 'node:assert';
|
|
|
3
3
|
import AddressQuestion from './question.js';
|
|
4
4
|
import { Address } from '../../lib/address.js';
|
|
5
5
|
import AddressValidator from '../../validator/address-validator.js';
|
|
6
|
-
import {
|
|
6
|
+
import { configureNunjucksTestEnv } from '../../../test/utils/nunjucks.js';
|
|
7
7
|
|
|
8
8
|
describe('AddressQuestion', () => {
|
|
9
9
|
const TITLE = 'What is the site address?';
|
|
@@ -132,12 +132,12 @@ describe('AddressQuestion', () => {
|
|
|
132
132
|
const model = await question.prepQuestionForRendering(
|
|
133
133
|
{},
|
|
134
134
|
{ getBackLink: mock.fn(), response },
|
|
135
|
-
{ layoutTemplate: '
|
|
135
|
+
{ layoutTemplate: 'views/layout-question.njk' }
|
|
136
136
|
);
|
|
137
137
|
|
|
138
138
|
assert.strictEqual(model.question.hint, hintText);
|
|
139
139
|
|
|
140
|
-
const nunjucks =
|
|
140
|
+
const nunjucks = configureNunjucksTestEnv();
|
|
141
141
|
const mockRes = {
|
|
142
142
|
render: mock.fn((view, data) => nunjucks.render(view + '.njk', data))
|
|
143
143
|
};
|
|
@@ -39,11 +39,11 @@
|
|
|
39
39
|
describedBy: question.hint and "multi-field-hint"
|
|
40
40
|
}) %}
|
|
41
41
|
|
|
42
|
-
{
|
|
42
|
+
{%- if question.hint -%}
|
|
43
43
|
<div id="multi-field-hint" class="govuk-hint">
|
|
44
44
|
{{ question.hint }}
|
|
45
45
|
</div>
|
|
46
|
-
{
|
|
46
|
+
{%- endif %}
|
|
47
47
|
|
|
48
48
|
{% if question.description %}
|
|
49
49
|
<p class="govuk-body">
|
|
@@ -88,7 +88,11 @@ export default class MultiFieldInputQuestion extends Question {
|
|
|
88
88
|
let responseToSave = { answers: {} };
|
|
89
89
|
|
|
90
90
|
for (const inputField of this.inputFields) {
|
|
91
|
-
|
|
91
|
+
let value = req.body[inputField.fieldName];
|
|
92
|
+
if (typeof value === 'string') {
|
|
93
|
+
value = value.trim();
|
|
94
|
+
}
|
|
95
|
+
responseToSave.answers[inputField.fieldName] = value;
|
|
92
96
|
journeyResponse.answers[inputField.fieldName] = responseToSave.answers[inputField.fieldName];
|
|
93
97
|
}
|
|
94
98
|
|
|
@@ -39,7 +39,7 @@
|
|
|
39
39
|
classes: "govuk-radios",
|
|
40
40
|
idPrefix: question.fieldName,
|
|
41
41
|
name: question.fieldName,
|
|
42
|
-
hint: { text: question.hint },
|
|
42
|
+
hint: question.hint and { text: question.hint },
|
|
43
43
|
errorMessage: errors[question.fieldName] and {
|
|
44
44
|
text: errors[question.fieldName].msg
|
|
45
45
|
},
|
|
@@ -72,7 +72,7 @@
|
|
|
72
72
|
classes: "govuk-radios",
|
|
73
73
|
idPrefix: question.fieldName,
|
|
74
74
|
name: question.fieldName,
|
|
75
|
-
hint: { text: question.hint },
|
|
75
|
+
hint: question.hint and { text: question.hint },
|
|
76
76
|
errorMessage: errors[question.fieldName] and {
|
|
77
77
|
text: errors[question.fieldName].msg
|
|
78
78
|
},
|
|
@@ -84,10 +84,10 @@
|
|
|
84
84
|
let input = document.querySelector('#{{ question.fieldName }}');
|
|
85
85
|
let select = document.querySelector('#{{ question.fieldName }}-select');
|
|
86
86
|
let options = Array.from(select.options);
|
|
87
|
-
if (!input.value || !options.some(option => option.
|
|
87
|
+
if (!input.value || !options.some(option => option.innerText === input.value)) {
|
|
88
88
|
select.options.selectedIndex = -1;
|
|
89
89
|
} else {
|
|
90
|
-
select.options.selectedIndex = options.findIndex(option => option.
|
|
90
|
+
select.options.selectedIndex = options.findIndex(option => option.innerText === input.value);
|
|
91
91
|
}
|
|
92
92
|
}
|
|
93
93
|
})
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { describe, it, mock } from 'node:test';
|
|
2
2
|
import assert from 'node:assert';
|
|
3
3
|
import TextEntryRedactQuestion from './question.js';
|
|
4
|
-
import {
|
|
4
|
+
import { configureNunjucksTestEnv } from '../../../test/utils/nunjucks.js';
|
|
5
5
|
|
|
6
6
|
describe('./src/dynamic-forms/components/text-entry-redact/question.js', () => {
|
|
7
7
|
const TITLE = 'title';
|
|
@@ -55,7 +55,7 @@ describe('./src/dynamic-forms/components/text-entry-redact/question.js', () => {
|
|
|
55
55
|
}
|
|
56
56
|
};
|
|
57
57
|
const customViewData = {
|
|
58
|
-
layoutTemplate: '
|
|
58
|
+
layoutTemplate: 'views/layout-question.njk',
|
|
59
59
|
question: {
|
|
60
60
|
question: 'Redaction Question',
|
|
61
61
|
fieldName: 'field-name',
|
|
@@ -63,7 +63,7 @@ describe('./src/dynamic-forms/components/text-entry-redact/question.js', () => {
|
|
|
63
63
|
valueRedacted: 'value-redacted'
|
|
64
64
|
}
|
|
65
65
|
};
|
|
66
|
-
const nunjucks =
|
|
66
|
+
const nunjucks = configureNunjucksTestEnv();
|
|
67
67
|
const mockRes = {
|
|
68
68
|
render: mock.fn((view, data) => nunjucks.render(view + '.njk', data))
|
|
69
69
|
};
|
|
@@ -94,7 +94,7 @@ describe('./src/dynamic-forms/components/text-entry-redact/question.js', () => {
|
|
|
94
94
|
}
|
|
95
95
|
};
|
|
96
96
|
const customViewData = {
|
|
97
|
-
layoutTemplate: '
|
|
97
|
+
layoutTemplate: 'views/layout-question.njk',
|
|
98
98
|
question: {
|
|
99
99
|
question: 'Redaction Question',
|
|
100
100
|
fieldName: 'field-name',
|
|
@@ -102,7 +102,7 @@ describe('./src/dynamic-forms/components/text-entry-redact/question.js', () => {
|
|
|
102
102
|
valueRedacted: 'value-redacted'
|
|
103
103
|
}
|
|
104
104
|
};
|
|
105
|
-
const nunjucks =
|
|
105
|
+
const nunjucks = configureNunjucksTestEnv();
|
|
106
106
|
const mockRes = {
|
|
107
107
|
render: mock.fn((view, data) => nunjucks.render(view + '.njk', data))
|
|
108
108
|
};
|
|
@@ -134,7 +134,7 @@ describe('./src/dynamic-forms/components/text-entry-redact/question.js', () => {
|
|
|
134
134
|
}
|
|
135
135
|
};
|
|
136
136
|
const customViewData = {
|
|
137
|
-
layoutTemplate: '
|
|
137
|
+
layoutTemplate: 'views/layout-question.njk',
|
|
138
138
|
question: {
|
|
139
139
|
question: 'Redaction Question',
|
|
140
140
|
fieldName: 'field-name',
|
|
@@ -147,7 +147,7 @@ describe('./src/dynamic-forms/components/text-entry-redact/question.js', () => {
|
|
|
147
147
|
{ category: 'Address', suggestion: '123 Fake Street' }
|
|
148
148
|
]
|
|
149
149
|
};
|
|
150
|
-
const nunjucks =
|
|
150
|
+
const nunjucks = configureNunjucksTestEnv();
|
|
151
151
|
const mockRes = {
|
|
152
152
|
render: mock.fn((view, data) => nunjucks.render(view + '.njk', data))
|
|
153
153
|
};
|
|
@@ -181,7 +181,7 @@ describe('./src/dynamic-forms/components/text-entry-redact/question.js', () => {
|
|
|
181
181
|
}
|
|
182
182
|
};
|
|
183
183
|
const customViewData = {
|
|
184
|
-
layoutTemplate: '
|
|
184
|
+
layoutTemplate: 'views/layout-question.njk',
|
|
185
185
|
question: {
|
|
186
186
|
question: 'Redaction Question',
|
|
187
187
|
fieldName: 'field-name',
|
|
@@ -193,7 +193,7 @@ describe('./src/dynamic-forms/components/text-entry-redact/question.js', () => {
|
|
|
193
193
|
{ category: 'Address', suggestion: '123 Fake Street' }
|
|
194
194
|
]
|
|
195
195
|
};
|
|
196
|
-
const nunjucks =
|
|
196
|
+
const nunjucks = configureNunjucksTestEnv();
|
|
197
197
|
const mockRes = {
|
|
198
198
|
render: mock.fn((view, data) => nunjucks.render(view + '.njk', data))
|
|
199
199
|
};
|
package/src/controller.test.js
CHANGED
|
@@ -4,7 +4,8 @@ import { list, question, buildSave } from './controller.js';
|
|
|
4
4
|
|
|
5
5
|
import { Journey } from './journey/journey.js';
|
|
6
6
|
import { SECTION_STATUS } from './section.js';
|
|
7
|
-
|
|
7
|
+
|
|
8
|
+
import { mockReq, mockRes } from '#test/utils/utils.js';
|
|
8
9
|
|
|
9
10
|
const res = mockRes();
|
|
10
11
|
const mockBaseUrl = '/manage-appeals/questionnaire';
|
package/src/index.js
CHANGED
|
@@ -1,3 +1,10 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @typedef {import('./questions/question-props.js').QuestionTypes} QuestionTypes
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* @type {Readonly<{CHECKBOX: QuestionTypes, BOOLEAN: QuestionTypes, RADIO: QuestionTypes, DATE: QuestionTypes, DATE_PERIOD: QuestionTypes, TEXT_ENTRY: QuestionTypes, TEXT_ENTRY_REDACT: QuestionTypes, SELECT: QuestionTypes, SINGLE_LINE_INPUT: QuestionTypes, MULTI_FIELD_INPUT: QuestionTypes, NUMBER: QuestionTypes, ADDRESS: QuestionTypes, UNIT_OPTION: QuestionTypes}>}
|
|
7
|
+
*/
|
|
1
8
|
export const COMPONENT_TYPES = Object.freeze({
|
|
2
9
|
CHECKBOX: 'checkbox',
|
|
3
10
|
BOOLEAN: 'boolean',
|
|
@@ -12,6 +19,5 @@ export const COMPONENT_TYPES = Object.freeze({
|
|
|
12
19
|
MULTI_FIELD_INPUT: 'multi-field-input',
|
|
13
20
|
NUMBER: 'number',
|
|
14
21
|
ADDRESS: 'site-address',
|
|
15
|
-
UNIT_OPTION: 'unit-option'
|
|
16
|
-
LIST_ADD_MORE: 'list-add-more'
|
|
22
|
+
UNIT_OPTION: 'unit-option'
|
|
17
23
|
});
|
|
@@ -5,8 +5,8 @@ import {
|
|
|
5
5
|
clearDataFromSession,
|
|
6
6
|
buildSaveDataToSession
|
|
7
7
|
} from './session-answer-store.js';
|
|
8
|
-
import { mockReq, mockRes } from './test-utils.js';
|
|
9
8
|
import { BOOLEAN_OPTIONS } from '../components/boolean/question.js';
|
|
9
|
+
import { mockReq, mockRes } from '#test/utils/utils.js';
|
|
10
10
|
|
|
11
11
|
describe('session-answer-store', () => {
|
|
12
12
|
describe('saveDataToSession', () => {
|
|
@@ -8,18 +8,15 @@ type QuestionTypes =
|
|
|
8
8
|
| 'boolean'
|
|
9
9
|
| 'radio'
|
|
10
10
|
| 'date'
|
|
11
|
+
| 'date-period'
|
|
11
12
|
| 'text-entry'
|
|
13
|
+
| 'text-entry-redact'
|
|
14
|
+
| 'select'
|
|
12
15
|
| 'single-line-input'
|
|
13
16
|
| 'multi-field-input'
|
|
14
17
|
| 'number'
|
|
15
18
|
| 'site-address'
|
|
16
|
-
| 'unit-option'
|
|
17
|
-
| 'list-add-more'
|
|
18
|
-
// strictly for add more sub question type
|
|
19
|
-
// todo refactor list add mores so there's no sub question
|
|
20
|
-
| 'case'
|
|
21
|
-
| 'address'
|
|
22
|
-
| 'listed-building';
|
|
19
|
+
| 'unit-option';
|
|
23
20
|
|
|
24
21
|
interface CommonQuestionProps {
|
|
25
22
|
type: QuestionTypes;
|
|
@@ -155,21 +152,6 @@ type UnitOptionEntryQuestionProps = CommonQuestionProps & {
|
|
|
155
152
|
label?: string;
|
|
156
153
|
};
|
|
157
154
|
|
|
158
|
-
export type SubQuestionProps = CommonQuestionProps & {
|
|
159
|
-
type: 'case' | 'address' | 'listed-building';
|
|
160
|
-
viewFolder: string;
|
|
161
|
-
};
|
|
162
|
-
|
|
163
|
-
type ListAddMoreQuestionProps = CommonQuestionProps & {
|
|
164
|
-
type: 'list-add-more';
|
|
165
|
-
subQuestionProps: SubQuestionProps;
|
|
166
|
-
subQuestionLabel?: string;
|
|
167
|
-
subQuestionTitle?: string;
|
|
168
|
-
subQuestionFieldLabel?: string;
|
|
169
|
-
subQuestionInputClasses?: string;
|
|
170
|
-
width?: string;
|
|
171
|
-
};
|
|
172
|
-
|
|
173
155
|
export type QuestionProps =
|
|
174
156
|
| CheckboxQuestionProps
|
|
175
157
|
| MultiFileUploadQuestionProps
|
|
@@ -181,5 +163,4 @@ export type QuestionProps =
|
|
|
181
163
|
| MultiFieldInputQuestionProps
|
|
182
164
|
| NumberEntryQuestionProps
|
|
183
165
|
| SiteAddressQuestionProps
|
|
184
|
-
| UnitOptionEntryQuestionProps
|
|
185
|
-
| ListAddMoreQuestionProps;
|
|
166
|
+
| UnitOptionEntryQuestionProps;
|
package/src/section.js
CHANGED
|
@@ -80,6 +80,9 @@ export class Section {
|
|
|
80
80
|
* @returns {Section}
|
|
81
81
|
*/
|
|
82
82
|
addQuestion(question) {
|
|
83
|
+
if (!question) {
|
|
84
|
+
throw new Error('question is required');
|
|
85
|
+
}
|
|
83
86
|
this.questions.push(question);
|
|
84
87
|
this.#conditionAdded = false; // reset condition flag
|
|
85
88
|
this.#applyConditions(question);
|
|
@@ -12,7 +12,10 @@ const validate = async (req, res, next) => {
|
|
|
12
12
|
}
|
|
13
13
|
if (req.body && typeof req.body === 'object' && req.body.constructor === Object) {
|
|
14
14
|
for (const key of Object.keys(req.body)) {
|
|
15
|
-
req.body[key]
|
|
15
|
+
if (typeof req.body[key] === 'string') {
|
|
16
|
+
// Trim whitespace from string values in req.body
|
|
17
|
+
req.body[key] = req.body[key].trim();
|
|
18
|
+
}
|
|
16
19
|
}
|
|
17
20
|
} else {
|
|
18
21
|
throw new Error(`req.body is not an object: ${req.body} :: ${typeof req.body}`);
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import { describe, it } from 'node:test';
|
|
2
|
+
import assert from 'assert';
|
|
3
|
+
import { questionsInOrder } from './questions.js';
|
|
4
|
+
import path from 'path';
|
|
5
|
+
import { escapeForRegExp, snapshotsDir } from './utils/utils.js';
|
|
6
|
+
import { createAppWithQuestions, mockAnswer, mockAnswerBody } from './questions.test.js';
|
|
7
|
+
|
|
8
|
+
describe('check-your-answers', () => {
|
|
9
|
+
it(`should render all question rows with no answers`, async (ctx) => {
|
|
10
|
+
const testServer = await createAppWithQuestions(ctx);
|
|
11
|
+
|
|
12
|
+
const response = await testServer.get('/check-your-answers');
|
|
13
|
+
assert.strictEqual(response.status, 200);
|
|
14
|
+
const text = await response.text();
|
|
15
|
+
const rows = text.split('<div class="govuk-summary-list__row">').slice(1); // Skip the first split part which is before the first row
|
|
16
|
+
|
|
17
|
+
assert.strictEqual(rows.length, questionsInOrder.length, 'Expected one row per question');
|
|
18
|
+
|
|
19
|
+
for (let i = 0; i < questionsInOrder.length; i++) {
|
|
20
|
+
const q = questionsInOrder[i];
|
|
21
|
+
const row = rows[i];
|
|
22
|
+
|
|
23
|
+
const escapedTitle = escapeForRegExp(q.title);
|
|
24
|
+
const escapedQ = escapeForRegExp(q.question);
|
|
25
|
+
assert.match(row, new RegExp(`<dt[^>]*>\\s*${escapedTitle}\\s*</dt>`, 'i'));
|
|
26
|
+
assert.match(row, new RegExp(`<dd[^>]*>\\s*Not started\\s*</dd>`, 'i'));
|
|
27
|
+
assert.match(
|
|
28
|
+
row,
|
|
29
|
+
new RegExp(`<a[^>]* href="questions/${q.url}">\\s*Answer\\s*<span[^>]*>\\s*${escapedQ}\\s*</span></a>`, 'i')
|
|
30
|
+
);
|
|
31
|
+
}
|
|
32
|
+
ctx.assert.fileSnapshot(text, path.join(snapshotsDir(), 'check-your-answers-not-started.html'), {
|
|
33
|
+
serializers: [(v) => v]
|
|
34
|
+
});
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
it(`should render all question answers`, async (ctx) => {
|
|
38
|
+
const testServer = await createAppWithQuestions(ctx);
|
|
39
|
+
|
|
40
|
+
// 'answer' each question
|
|
41
|
+
for (const q of questionsInOrder) {
|
|
42
|
+
await testServer.post(`/questions/${q.url}`, mockAnswerBody(q), { redirect: 'manual' });
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
const response = await testServer.get('/check-your-answers');
|
|
46
|
+
assert.strictEqual(response.status, 200);
|
|
47
|
+
const text = await response.text();
|
|
48
|
+
const rows = text.split('<div class="govuk-summary-list__row">').slice(1); // Skip the first split part which is before the first row
|
|
49
|
+
|
|
50
|
+
assert.strictEqual(rows.length, questionsInOrder.length, 'Expected one row per question');
|
|
51
|
+
|
|
52
|
+
for (let i = 0; i < questionsInOrder.length; i++) {
|
|
53
|
+
const q = questionsInOrder[i];
|
|
54
|
+
const row = rows[i];
|
|
55
|
+
|
|
56
|
+
const escapedTitle = escapeForRegExp(q.title);
|
|
57
|
+
const escapedQ = escapeForRegExp(q.question);
|
|
58
|
+
assert.match(row, new RegExp(`<dt[^>]*>\\s*${escapedTitle}\\s*</dt>`, 'i'));
|
|
59
|
+
assert.match(row, new RegExp(`<dd[^>]*>\\s*${mockAnswer(q)}\\s*</dd>`, 'i'));
|
|
60
|
+
assert.match(
|
|
61
|
+
row,
|
|
62
|
+
new RegExp(`<a[^>]* href="questions/${q.url}">\\s*Change\\s*<span[^>]*>\\s*${escapedQ}\\s*</span></a>`, 'i')
|
|
63
|
+
);
|
|
64
|
+
}
|
|
65
|
+
ctx.assert.fileSnapshot(text, path.join(snapshotsDir(), 'check-your-answers-with-answers.html'), {
|
|
66
|
+
serializers: [(v) => v]
|
|
67
|
+
});
|
|
68
|
+
});
|
|
69
|
+
});
|
package/test/journey.js
ADDED
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import { Section } from '../src/section.js';
|
|
2
|
+
import { Journey } from '../src/journey/journey.js';
|
|
3
|
+
|
|
4
|
+
export const JOURNEY_ID = 'holiday-journey';
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* @param {{[questionType: string]: import('../src/questions/question.js').Question}} questions
|
|
8
|
+
* @param {import('../src/journey/journey-response.js').JourneyResponse} response
|
|
9
|
+
* @returns {Journey}
|
|
10
|
+
*/
|
|
11
|
+
export function createJourney(questions, response) {
|
|
12
|
+
return new Journey({
|
|
13
|
+
journeyId: JOURNEY_ID,
|
|
14
|
+
sections: [
|
|
15
|
+
new Section('Holiday Details', 'questions')
|
|
16
|
+
.addQuestion(questions.holidayActivities)
|
|
17
|
+
.addQuestion(questions.addInsurance)
|
|
18
|
+
.addQuestion(questions.travelInsuranceType)
|
|
19
|
+
.addQuestion(questions.holidayDestination)
|
|
20
|
+
.addQuestion(questions.departureDate)
|
|
21
|
+
.addQuestion(questions.holidayPeriod)
|
|
22
|
+
.addQuestion(questions.holidayDescription)
|
|
23
|
+
.addQuestion(questions.secretWish)
|
|
24
|
+
.addQuestion(questions.travelClass)
|
|
25
|
+
.addQuestion(questions.holidayArrival)
|
|
26
|
+
.addQuestion(questions.holidaySnack)
|
|
27
|
+
.addQuestion(questions.companions)
|
|
28
|
+
.addQuestion(questions.nights)
|
|
29
|
+
.addQuestion(questions.hotelAddress)
|
|
30
|
+
.addQuestion(questions.luggageWeight)
|
|
31
|
+
],
|
|
32
|
+
taskListUrl: 'check-your-answers',
|
|
33
|
+
journeyTemplate: 'views/layout-question.njk',
|
|
34
|
+
listingPageViewPath: 'views/layout-check-your-answers.njk',
|
|
35
|
+
journeyTitle: 'Holiday Booking',
|
|
36
|
+
returnToListing: false,
|
|
37
|
+
makeBaseUrl: () => '/',
|
|
38
|
+
initialBackLink: '/holidays',
|
|
39
|
+
response
|
|
40
|
+
});
|
|
41
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { describe, it } from 'node:test';
|
|
2
|
+
import assert from 'assert';
|
|
3
|
+
import { createJourney } from './journey.js';
|
|
4
|
+
import { getQuestions } from './questions.js';
|
|
5
|
+
|
|
6
|
+
// Mock response object (empty for this test)
|
|
7
|
+
const response = {};
|
|
8
|
+
|
|
9
|
+
describe('Journey', function () {
|
|
10
|
+
it('should have all questions defined on the journey', function () {
|
|
11
|
+
// Use the questions from the questions module
|
|
12
|
+
const questions = getQuestions();
|
|
13
|
+
const journey = createJourney(questions, response);
|
|
14
|
+
const journeyQuestions = journey.sections[0].questions;
|
|
15
|
+
let previousQuestion = null;
|
|
16
|
+
for (const question of journeyQuestions) {
|
|
17
|
+
assert.ok(question, `Question is not defined, after: ${previousQuestion && previousQuestion.title}`);
|
|
18
|
+
previousQuestion = question;
|
|
19
|
+
}
|
|
20
|
+
});
|
|
21
|
+
});
|