@planning-inspectorate/dynamic-forms 3.16.0 → 3.18.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/README.md +124 -189
- package/docs/Email Validation.md +86 -0
- package/docs/Getting Started.md +299 -0
- package/package.json +1 -1
- package/src/components/address/question.d.ts +1 -9
- package/src/components/address/question.d.ts.map +1 -1
- package/src/components/address/question.js +6 -17
- package/src/components/checkbox/question.d.ts +1 -21
- package/src/components/checkbox/question.d.ts.map +1 -1
- package/src/components/checkbox/question.js +38 -50
- package/src/components/checkbox/question.test.js +177 -26
- package/src/components/date/question.d.ts +0 -8
- package/src/components/date/question.d.ts.map +1 -1
- package/src/components/date/question.js +6 -14
- package/src/components/date-period/question.d.ts +1 -5
- package/src/components/date-period/question.d.ts.map +1 -1
- package/src/components/date-period/question.js +1 -1
- package/src/components/date-time/question.d.ts +0 -8
- package/src/components/date-time/question.d.ts.map +1 -1
- package/src/components/date-time/question.js +4 -12
- package/src/components/email/question.d.ts +0 -9
- package/src/components/email/question.d.ts.map +1 -1
- package/src/components/email/question.js +2 -13
- package/src/components/email/question.test.js +19 -0
- package/src/components/manage-list/answer-summary-list.njk +1 -1
- package/src/components/manage-list/question.d.ts +1 -5
- package/src/components/manage-list/question.d.ts.map +1 -1
- package/src/components/manage-list/question.js +1 -1
- package/src/components/multi-field-input/question.d.ts +4 -19
- package/src/components/multi-field-input/question.d.ts.map +1 -1
- package/src/components/multi-field-input/question.js +89 -23
- package/src/components/multi-field-input/question.test.js +441 -3
- package/src/components/number-entry/question.d.ts +0 -28
- package/src/components/number-entry/question.d.ts.map +1 -1
- package/src/components/number-entry/question.js +3 -20
- package/src/components/number-entry/question.test.js +17 -0
- package/src/components/radio/question.d.ts +1 -9
- package/src/components/radio/question.d.ts.map +1 -1
- package/src/components/radio/question.js +29 -46
- package/src/components/radio/question.test.js +139 -60
- package/src/components/select/question.d.ts +1 -15
- package/src/components/select/question.d.ts.map +1 -1
- package/src/components/select/question.js +4 -47
- package/src/components/single-line-input/question.d.ts +0 -4
- package/src/components/single-line-input/question.d.ts.map +1 -1
- package/src/components/single-line-input/question.js +1 -1
- package/src/components/text-entry/question.d.ts +0 -4
- package/src/components/text-entry/question.d.ts.map +1 -1
- package/src/components/text-entry/question.js +1 -1
- package/src/components/text-entry-redact/question.d.ts +2 -6
- package/src/components/text-entry-redact/question.d.ts.map +1 -1
- package/src/components/text-entry-redact/question.js +1 -1
- package/src/components/unit-option-entry/question.d.ts.map +1 -1
- package/src/components/unit-option-entry/question.js +3 -1
- package/src/components/unit-option-entry/question.test.js +15 -0
- package/src/components/utils/question-has-answer.js +1 -1
- package/src/components/utils/question-utils.d.ts +19 -1
- package/src/components/utils/question-utils.d.ts.map +1 -1
- package/src/components/utils/question-utils.js +31 -2
- package/src/components/utils/question-utils.test.js +89 -2
- package/src/questions/options-question.d.ts +4 -4
- package/src/questions/options-question.d.ts.map +1 -1
- package/src/questions/options-question.js +27 -2
- package/src/questions/options-question.test.js +106 -0
- package/src/questions/question.d.ts +16 -1
- package/src/questions/question.d.ts.map +1 -1
- package/src/questions/question.js +64 -9
- package/src/questions/question.test.js +86 -0
- package/test/journey.js +2 -0
- package/test/questions.js +50 -0
- package/test/snapshots/check-your-answers-not-started.html +22 -0
- package/test/snapshots/check-your-answers-with-answers.html +23 -1
- package/test/snapshots/favourite-activity-reason.html +54 -0
- package/test/snapshots/travel-requirements.html +48 -0
- package/test/utils/question-test-utils.js +38 -7
- package/typedefs/question-props.d.ts +33 -0
- package/typedefs/question-types.d.ts +4 -0
|
@@ -1,41 +1,14 @@
|
|
|
1
|
+
import escape from 'escape-html';
|
|
1
2
|
import OptionsQuestion from '../../questions/options-question.js';
|
|
2
3
|
|
|
3
4
|
export class RadioQuestion extends OptionsQuestion {
|
|
4
5
|
/**
|
|
5
6
|
* @param {import('#typedefs/question-props.d.ts').RadioQuestionParams} params
|
|
6
7
|
*/
|
|
7
|
-
constructor({
|
|
8
|
-
title,
|
|
9
|
-
question,
|
|
10
|
-
fieldName,
|
|
11
|
-
viewFolder,
|
|
12
|
-
url,
|
|
13
|
-
hint,
|
|
14
|
-
pageTitle,
|
|
15
|
-
description,
|
|
16
|
-
label,
|
|
17
|
-
html,
|
|
18
|
-
legend,
|
|
19
|
-
options,
|
|
20
|
-
validators,
|
|
21
|
-
actionLink,
|
|
22
|
-
editable,
|
|
23
|
-
viewData
|
|
24
|
-
}) {
|
|
8
|
+
constructor({ label, html, legend, viewFolder, ...parentParams }) {
|
|
25
9
|
super({
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
viewFolder: !viewFolder ? 'radio' : viewFolder,
|
|
29
|
-
fieldName,
|
|
30
|
-
url,
|
|
31
|
-
hint,
|
|
32
|
-
pageTitle,
|
|
33
|
-
description,
|
|
34
|
-
options,
|
|
35
|
-
validators,
|
|
36
|
-
actionLink,
|
|
37
|
-
editable,
|
|
38
|
-
viewData
|
|
10
|
+
...parentParams,
|
|
11
|
+
viewFolder: viewFolder || 'radio'
|
|
39
12
|
});
|
|
40
13
|
|
|
41
14
|
this.html = html;
|
|
@@ -44,7 +17,7 @@ export class RadioQuestion extends OptionsQuestion {
|
|
|
44
17
|
}
|
|
45
18
|
|
|
46
19
|
/**
|
|
47
|
-
* @param {import('#question').QuestionViewModel} viewModel
|
|
20
|
+
* @param {import('#typedefs/question-types.d.ts').QuestionViewModel} viewModel
|
|
48
21
|
*/
|
|
49
22
|
addCustomDataToViewModel(viewModel) {
|
|
50
23
|
viewModel.question.label = this.label;
|
|
@@ -52,22 +25,32 @@ export class RadioQuestion extends OptionsQuestion {
|
|
|
52
25
|
}
|
|
53
26
|
|
|
54
27
|
/**
|
|
55
|
-
*
|
|
28
|
+
* Formats an answer value for display in the summary.
|
|
29
|
+
* Handles object answers with conditional fields (e.g. { value: 'yes', conditional: { yes: 'details' } })
|
|
30
|
+
*
|
|
31
|
+
* @param {unknown} answer - the raw answer value
|
|
32
|
+
* @returns {string} the formatted answer for display
|
|
56
33
|
*/
|
|
57
|
-
|
|
58
|
-
if (answer
|
|
59
|
-
|
|
60
|
-
const conditionalAnswerText = selectedOption.conditional?.label
|
|
61
|
-
? `${selectedOption.conditional.label} ${answer.conditional}`
|
|
62
|
-
: answer.conditional;
|
|
63
|
-
const formattedAnswer = [selectedOption.text, conditionalAnswerText].join('\n');
|
|
64
|
-
return super.formatAnswerForSummary(sectionSegment, journey, formattedAnswer, false);
|
|
65
|
-
} else if (answer) {
|
|
66
|
-
const selectedOption = this.options.find((option) => option.value === answer);
|
|
67
|
-
const selectedText = selectedOption?.text || '';
|
|
68
|
-
return super.formatAnswerForSummary(sectionSegment, journey, selectedText, false);
|
|
34
|
+
formatAnswer(answer) {
|
|
35
|
+
if (answer === null || answer === undefined || answer === '') {
|
|
36
|
+
return this.notStartedText;
|
|
69
37
|
}
|
|
70
|
-
|
|
38
|
+
|
|
39
|
+
// Handle simple string answers
|
|
40
|
+
if (typeof answer !== 'object' || answer.value === undefined) {
|
|
41
|
+
return super.formatAnswer(answer);
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
// Handle object answers with conditional fields
|
|
45
|
+
const option = this.options.find((opt) => opt.value === answer.value);
|
|
46
|
+
const optionText = escape(option ? option.text : answer.value);
|
|
47
|
+
|
|
48
|
+
const conditionalValue = answer.conditional?.[answer.value];
|
|
49
|
+
if (conditionalValue) {
|
|
50
|
+
const label = option?.conditional?.label ? `${escape(option.conditional.label)} ` : '';
|
|
51
|
+
return `${optionText}<br>${label}${escape(conditionalValue)}`;
|
|
52
|
+
}
|
|
53
|
+
return optionText;
|
|
71
54
|
}
|
|
72
55
|
}
|
|
73
56
|
|
|
@@ -96,78 +96,157 @@ describe('./src/dynamic-forms/components/radio/question.js', () => {
|
|
|
96
96
|
assert.strictEqual(preppedQuestion.question.label, LABEL);
|
|
97
97
|
});
|
|
98
98
|
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
99
|
+
describe('formatAnswer', () => {
|
|
100
|
+
it('should delegate simple string answers to parent class', () => {
|
|
101
|
+
const radioQuestion = new RadioQuestion({
|
|
102
|
+
title: TITLE,
|
|
103
|
+
question: QUESTION,
|
|
104
|
+
description: DESCRIPTION,
|
|
105
|
+
fieldName: FIELDNAME,
|
|
106
|
+
options: [
|
|
107
|
+
{ text: 'Option 1', value: 'op-1' },
|
|
108
|
+
{ text: 'Option 2', value: 'op-2' }
|
|
109
|
+
]
|
|
110
|
+
});
|
|
111
|
+
|
|
112
|
+
const result = radioQuestion.formatAnswer('op-1');
|
|
113
|
+
assert.strictEqual(result, 'Option 1');
|
|
109
114
|
});
|
|
110
115
|
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
116
|
+
it('should format object answer with value only (no conditional)', () => {
|
|
117
|
+
const radioQuestion = new RadioQuestion({
|
|
118
|
+
title: TITLE,
|
|
119
|
+
question: QUESTION,
|
|
120
|
+
description: DESCRIPTION,
|
|
121
|
+
fieldName: FIELDNAME,
|
|
122
|
+
options: OPTIONS
|
|
123
|
+
});
|
|
124
|
+
|
|
125
|
+
const result = radioQuestion.formatAnswer({ value: 'no' });
|
|
126
|
+
assert.strictEqual(result, 'No');
|
|
114
127
|
});
|
|
115
|
-
assert.strictEqual(rowParams[0].value, 'Yes<br>test');
|
|
116
|
-
});
|
|
117
128
|
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
{
|
|
130
|
-
|
|
129
|
+
it('should format object answer with conditional', () => {
|
|
130
|
+
const radioQuestion = new RadioQuestion({
|
|
131
|
+
title: TITLE,
|
|
132
|
+
question: QUESTION,
|
|
133
|
+
description: DESCRIPTION,
|
|
134
|
+
fieldName: FIELDNAME,
|
|
135
|
+
options: OPTIONS
|
|
136
|
+
});
|
|
137
|
+
|
|
138
|
+
const result = radioQuestion.formatAnswer({
|
|
139
|
+
value: 'yes',
|
|
140
|
+
conditional: { yes: 'test details' }
|
|
141
|
+
});
|
|
142
|
+
assert.strictEqual(result, 'Yes<br>test details');
|
|
131
143
|
});
|
|
132
144
|
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
145
|
+
it('should format object answer with conditional and label', () => {
|
|
146
|
+
const optionsWithLabel = [...OPTIONS];
|
|
147
|
+
optionsWithLabel[0] = {
|
|
148
|
+
...optionsWithLabel[0],
|
|
149
|
+
conditional: {
|
|
150
|
+
...optionsWithLabel[0].conditional,
|
|
151
|
+
label: 'label:'
|
|
152
|
+
}
|
|
153
|
+
};
|
|
154
|
+
const radioQuestion = new RadioQuestion({
|
|
155
|
+
title: TITLE,
|
|
156
|
+
question: QUESTION,
|
|
157
|
+
description: DESCRIPTION,
|
|
158
|
+
fieldName: FIELDNAME,
|
|
159
|
+
options: optionsWithLabel
|
|
160
|
+
});
|
|
161
|
+
|
|
162
|
+
const result = radioQuestion.formatAnswer({
|
|
163
|
+
value: 'yes',
|
|
164
|
+
conditional: { yes: 'test details' }
|
|
165
|
+
});
|
|
166
|
+
assert.strictEqual(result, 'Yes<br>label: test details');
|
|
167
|
+
});
|
|
136
168
|
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
options: optionsWithLabel
|
|
169
|
+
it('should fall back to value when option not found in object answer', () => {
|
|
170
|
+
const radioQuestion = new RadioQuestion({
|
|
171
|
+
title: TITLE,
|
|
172
|
+
question: QUESTION,
|
|
173
|
+
description: DESCRIPTION,
|
|
174
|
+
fieldName: FIELDNAME,
|
|
175
|
+
options: OPTIONS
|
|
176
|
+
});
|
|
177
|
+
|
|
178
|
+
const result = radioQuestion.formatAnswer({ value: 'unknown' });
|
|
179
|
+
assert.strictEqual(result, 'unknown');
|
|
149
180
|
});
|
|
150
181
|
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
182
|
+
it('should escape HTML in option text', () => {
|
|
183
|
+
const radioQuestion = new RadioQuestion({
|
|
184
|
+
title: TITLE,
|
|
185
|
+
question: QUESTION,
|
|
186
|
+
description: DESCRIPTION,
|
|
187
|
+
fieldName: FIELDNAME,
|
|
188
|
+
options: [{ text: '<script>alert("xss")</script>', value: 'xss' }]
|
|
189
|
+
});
|
|
190
|
+
|
|
191
|
+
const result = radioQuestion.formatAnswer('xss');
|
|
192
|
+
assert.strictEqual(result, '<script>alert("xss")</script>');
|
|
154
193
|
});
|
|
155
|
-
assert.strictEqual(rowParams[0].value, 'Yes<br>label: test');
|
|
156
|
-
});
|
|
157
194
|
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
195
|
+
it('should escape HTML in conditional value', () => {
|
|
196
|
+
const radioQuestion = new RadioQuestion({
|
|
197
|
+
title: TITLE,
|
|
198
|
+
question: QUESTION,
|
|
199
|
+
description: DESCRIPTION,
|
|
200
|
+
fieldName: FIELDNAME,
|
|
201
|
+
options: [
|
|
202
|
+
{
|
|
203
|
+
text: 'Yes',
|
|
204
|
+
value: 'yes',
|
|
205
|
+
conditional: { fieldName: 'details', type: 'text' }
|
|
206
|
+
}
|
|
207
|
+
]
|
|
208
|
+
});
|
|
209
|
+
|
|
210
|
+
const result = radioQuestion.formatAnswer({
|
|
211
|
+
value: 'yes',
|
|
212
|
+
conditional: { yes: '<img src=x onerror=alert(1)>' }
|
|
213
|
+
});
|
|
214
|
+
assert.strictEqual(result, 'Yes<br><img src=x onerror=alert(1)>');
|
|
168
215
|
});
|
|
169
216
|
|
|
170
|
-
|
|
171
|
-
|
|
217
|
+
it('should escape HTML in conditional label', () => {
|
|
218
|
+
const radioQuestion = new RadioQuestion({
|
|
219
|
+
title: TITLE,
|
|
220
|
+
question: QUESTION,
|
|
221
|
+
description: DESCRIPTION,
|
|
222
|
+
fieldName: FIELDNAME,
|
|
223
|
+
options: [
|
|
224
|
+
{
|
|
225
|
+
text: 'Yes',
|
|
226
|
+
value: 'yes',
|
|
227
|
+
conditional: { fieldName: 'details', type: 'text', label: '<b>Label</b>' }
|
|
228
|
+
}
|
|
229
|
+
]
|
|
230
|
+
});
|
|
231
|
+
|
|
232
|
+
const result = radioQuestion.formatAnswer({
|
|
233
|
+
value: 'yes',
|
|
234
|
+
conditional: { yes: 'safe value' }
|
|
235
|
+
});
|
|
236
|
+
assert.strictEqual(result, 'Yes<br><b>Label</b> safe value');
|
|
237
|
+
});
|
|
238
|
+
|
|
239
|
+
it('should escape HTML in fallback value when option not found', () => {
|
|
240
|
+
const radioQuestion = new RadioQuestion({
|
|
241
|
+
title: TITLE,
|
|
242
|
+
question: QUESTION,
|
|
243
|
+
description: DESCRIPTION,
|
|
244
|
+
fieldName: FIELDNAME,
|
|
245
|
+
options: OPTIONS
|
|
246
|
+
});
|
|
247
|
+
|
|
248
|
+
const result = radioQuestion.formatAnswer({ value: '<script>xss</script>' });
|
|
249
|
+
assert.strictEqual(result, '<script>xss</script>');
|
|
250
|
+
});
|
|
172
251
|
});
|
|
173
252
|
});
|
|
@@ -2,23 +2,9 @@ export class SelectQuestion extends OptionsQuestion {
|
|
|
2
2
|
/**
|
|
3
3
|
* @param {import('#typedefs/question-props.d.ts').SelectQuestionParams} params
|
|
4
4
|
*/
|
|
5
|
-
constructor({
|
|
5
|
+
constructor({ label, html, legend, disableAccessibleAutocomplete, viewFolder, ...parentParams }: import("#typedefs/question-props.d.ts").SelectQuestionParams);
|
|
6
6
|
label: string | undefined;
|
|
7
7
|
legend: string | undefined;
|
|
8
|
-
/**
|
|
9
|
-
* @param {import('#question').QuestionViewModel} viewModel
|
|
10
|
-
*/
|
|
11
|
-
addCustomDataToViewModel(viewModel: import("#question").QuestionViewModel): void;
|
|
12
|
-
/**
|
|
13
|
-
* returns the formatted answers values to be used to build task list elements
|
|
14
|
-
* note: only supports a single answer
|
|
15
|
-
*
|
|
16
|
-
* @param {unknown} answer
|
|
17
|
-
* @param {Journey} journey
|
|
18
|
-
* @param {string} sectionSegment
|
|
19
|
-
* @returns {Array.<Object>}
|
|
20
|
-
*/
|
|
21
|
-
formatAnswerForSummary(sectionSegment: string, journey: Journey, answer: unknown): Array<Object>;
|
|
22
8
|
#private;
|
|
23
9
|
}
|
|
24
10
|
export default SelectQuestion;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"question.d.ts","sourceRoot":"","sources":["question.js"],"names":[],"mappings":"AAEA;IAEC;;OAEG;IACH,
|
|
1
|
+
{"version":3,"file":"question.d.ts","sourceRoot":"","sources":["question.js"],"names":[],"mappings":"AAEA;IAEC;;OAEG;IACH,iGAFW,OAAO,+BAA+B,EAAE,oBAAoB,EAYtE;IAHA,0BAAkB;IAClB,2BAAoB;;CAYrB;;4BA3B2B,qCAAqC"}
|
|
@@ -5,35 +5,10 @@ export class SelectQuestion extends OptionsQuestion {
|
|
|
5
5
|
/**
|
|
6
6
|
* @param {import('#typedefs/question-props.d.ts').SelectQuestionParams} params
|
|
7
7
|
*/
|
|
8
|
-
constructor({
|
|
9
|
-
title,
|
|
10
|
-
question,
|
|
11
|
-
fieldName,
|
|
12
|
-
viewFolder,
|
|
13
|
-
url,
|
|
14
|
-
hint,
|
|
15
|
-
pageTitle,
|
|
16
|
-
description,
|
|
17
|
-
label,
|
|
18
|
-
html,
|
|
19
|
-
legend,
|
|
20
|
-
disableAccessibleAutocomplete,
|
|
21
|
-
options,
|
|
22
|
-
validators,
|
|
23
|
-
viewData
|
|
24
|
-
}) {
|
|
8
|
+
constructor({ label, html, legend, disableAccessibleAutocomplete, viewFolder, ...parentParams }) {
|
|
25
9
|
super({
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
viewFolder: !viewFolder ? 'select' : viewFolder,
|
|
29
|
-
fieldName,
|
|
30
|
-
url,
|
|
31
|
-
hint,
|
|
32
|
-
pageTitle,
|
|
33
|
-
description,
|
|
34
|
-
options,
|
|
35
|
-
validators,
|
|
36
|
-
viewData
|
|
10
|
+
...parentParams,
|
|
11
|
+
viewFolder: viewFolder || 'select'
|
|
37
12
|
});
|
|
38
13
|
|
|
39
14
|
this.html = html;
|
|
@@ -43,31 +18,13 @@ export class SelectQuestion extends OptionsQuestion {
|
|
|
43
18
|
}
|
|
44
19
|
|
|
45
20
|
/**
|
|
46
|
-
* @param {import('#question').QuestionViewModel} viewModel
|
|
21
|
+
* @param {import('#typedefs/question-types.d.ts').QuestionViewModel} viewModel
|
|
47
22
|
*/
|
|
48
23
|
addCustomDataToViewModel(viewModel) {
|
|
49
24
|
viewModel.question.label = this.label;
|
|
50
25
|
viewModel.question.legend = this.legend;
|
|
51
26
|
viewModel.question.disableAccessibleAutocomplete = this.#disableAccessibleAutocomplete;
|
|
52
27
|
}
|
|
53
|
-
|
|
54
|
-
/**
|
|
55
|
-
* returns the formatted answers values to be used to build task list elements
|
|
56
|
-
* note: only supports a single answer
|
|
57
|
-
*
|
|
58
|
-
* @param {unknown} answer
|
|
59
|
-
* @param {Journey} journey
|
|
60
|
-
* @param {string} sectionSegment
|
|
61
|
-
* @returns {Array.<Object>}
|
|
62
|
-
*/
|
|
63
|
-
formatAnswerForSummary(sectionSegment, journey, answer) {
|
|
64
|
-
if (answer) {
|
|
65
|
-
const selectedOption = this.options.find((option) => option.value === answer);
|
|
66
|
-
const selectedText = selectedOption?.text || '';
|
|
67
|
-
return super.formatAnswerForSummary(sectionSegment, journey, selectedText, false);
|
|
68
|
-
}
|
|
69
|
-
return super.formatAnswerForSummary(sectionSegment, journey, answer);
|
|
70
|
-
}
|
|
71
28
|
}
|
|
72
29
|
|
|
73
30
|
export default SelectQuestion;
|
|
@@ -10,10 +10,6 @@ export class SingleLineInputQuestion extends Question {
|
|
|
10
10
|
inputAttributes: Record<string, string>;
|
|
11
11
|
label: string | undefined;
|
|
12
12
|
classes: string;
|
|
13
|
-
/**
|
|
14
|
-
* @param {import('#question').QuestionViewModel} viewModel
|
|
15
|
-
*/
|
|
16
|
-
addCustomDataToViewModel(viewModel: import("#question").QuestionViewModel): void;
|
|
17
13
|
}
|
|
18
14
|
export default SingleLineInputQuestion;
|
|
19
15
|
import { Question } from '#question';
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"question.d.ts","sourceRoot":"","sources":["question.js"],"names":[],"mappings":"AAEA;;GAEG;AACH;IAIC;;OAEG;IACH,oBAFW,OAAO,+BAA+B,EAAE,6BAA6B,EAW/E;IAfD,qCAAqC;IACrC,iBADW,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CACjB;IAWf,0BAAyB;IAEzB,gBAAmC;
|
|
1
|
+
{"version":3,"file":"question.d.ts","sourceRoot":"","sources":["question.js"],"names":[],"mappings":"AAEA;;GAEG;AACH;IAIC;;OAEG;IACH,oBAFW,OAAO,+BAA+B,EAAE,6BAA6B,EAW/E;IAfD,qCAAqC;IACrC,iBADW,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CACjB;IAWf,0BAAyB;IAEzB,gBAAmC;CAepC;;yBAnCwB,WAAW"}
|
|
@@ -22,7 +22,7 @@ export class SingleLineInputQuestion extends Question {
|
|
|
22
22
|
}
|
|
23
23
|
|
|
24
24
|
/**
|
|
25
|
-
* @param {import('#question').QuestionViewModel} viewModel
|
|
25
|
+
* @param {import('#typedefs/question-types.d.ts').QuestionViewModel} viewModel
|
|
26
26
|
*/
|
|
27
27
|
addCustomDataToViewModel(viewModel) {
|
|
28
28
|
viewModel.question.label = this.label;
|
|
@@ -21,10 +21,6 @@ export class TextEntryQuestion extends Question {
|
|
|
21
21
|
constructor({ textEntryCheckbox, label, ...parentParams }: import("#typedefs/question-props.d.ts").TextEntryQuestionParams);
|
|
22
22
|
textEntryCheckbox: import("#typedefs/question-props.d.ts").TextEntryCheckbox | undefined;
|
|
23
23
|
label: string | undefined;
|
|
24
|
-
/**
|
|
25
|
-
* @param {import('#question').QuestionViewModel} viewModel
|
|
26
|
-
*/
|
|
27
|
-
addCustomDataToViewModel(viewModel: import("#question").QuestionViewModel): void;
|
|
28
24
|
}
|
|
29
25
|
export default TextEntryQuestion;
|
|
30
26
|
export type TextEntryCheckbox = {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"question.d.ts","sourceRoot":"","sources":["question.js"],"names":[],"mappings":"AACA;;;;;GAKG;AAEH;;;;;;GAMG;AAEH;;GAEG;AACH;IACC;;OAEG;IACH,2DAFW,OAAO,+BAA+B,EAAE,uBAAuB,EAUzE;IAFA,yFAA0C;IAC1C,0BAAkB;
|
|
1
|
+
{"version":3,"file":"question.d.ts","sourceRoot":"","sources":["question.js"],"names":[],"mappings":"AACA;;;;;GAKG;AAEH;;;;;;GAMG;AAEH;;GAEG;AACH;IACC;;OAEG;IACH,2DAFW,OAAO,+BAA+B,EAAE,uBAAuB,EAUzE;IAFA,yFAA0C;IAC1C,0BAAkB;CAUnB;;;YA9Ba,MAAM;UACN,MAAM;UACN,MAAM;;;yBAZK,WAAW"}
|
|
@@ -32,7 +32,7 @@ export class TextEntryQuestion extends Question {
|
|
|
32
32
|
}
|
|
33
33
|
|
|
34
34
|
/**
|
|
35
|
-
* @param {import('#question').QuestionViewModel} viewModel
|
|
35
|
+
* @param {import('#typedefs/question-types.d.ts').QuestionViewModel} viewModel
|
|
36
36
|
*/
|
|
37
37
|
addCustomDataToViewModel(viewModel) {
|
|
38
38
|
viewModel.question.label = this.label;
|
|
@@ -15,16 +15,12 @@ export class TextEntryRedactQuestion extends Question {
|
|
|
15
15
|
showSuggestionsUi: boolean | undefined;
|
|
16
16
|
summaryText: string | undefined;
|
|
17
17
|
shouldTruncateSummary: boolean | undefined;
|
|
18
|
-
prepQuestionForRendering(section: any, journey: any, customViewData: any, payload: any, options: any): import("
|
|
18
|
+
prepQuestionForRendering(section: any, journey: any, customViewData: any, payload: any, options: any): import("#typedefs/question-types.d.ts").QuestionViewModel<import("#typedefs/question-types.d.ts").BaseQuestionViewData>;
|
|
19
19
|
answerForViewModel(answers: any): string;
|
|
20
|
-
/**
|
|
21
|
-
* @param {import('#question').QuestionViewModel} viewModel
|
|
22
|
-
*/
|
|
23
|
-
addCustomDataToViewModel(viewModel: import("#question").QuestionViewModel): void;
|
|
24
20
|
/**
|
|
25
21
|
* returns the formatted answers values to be used to build task list elements
|
|
26
22
|
*/
|
|
27
|
-
formatAnswerForSummary(sectionSegment: any, journey: any, answer: any, capitals?: boolean): import("
|
|
23
|
+
formatAnswerForSummary(sectionSegment: any, journey: any, answer: any, capitals?: boolean): import("#typedefs/question-types.d.ts").SummaryRow[];
|
|
28
24
|
}
|
|
29
25
|
export default TextEntryRedactQuestion;
|
|
30
26
|
import { Question } from '#question';
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"question.d.ts","sourceRoot":"","sources":["question.js"],"names":[],"mappings":"AAGA,+BAAgC,QAAG,CAAC;AACpC,mCAAoC,GAAG,CAAC;AAExC;;GAEG;AACH;IACC;;OAEG;IACH,gLAFW,OAAO,+BAA+B,EAAE,6BAA6B,EAwB/E;IAPA,yFAA0C;IAC1C,0BAAkB;IAClB,qDAAsE;IACtE,iDAA8D;IAC9D,uCAA0C;IAC1C,gCAA8B;IAC9B,2CAAkD;IAkBnD
|
|
1
|
+
{"version":3,"file":"question.d.ts","sourceRoot":"","sources":["question.js"],"names":[],"mappings":"AAGA,+BAAgC,QAAG,CAAC;AACpC,mCAAoC,GAAG,CAAC;AAExC;;GAEG;AACH;IACC;;OAEG;IACH,gLAFW,OAAO,+BAA+B,EAAE,6BAA6B,EAwB/E;IAPA,yFAA0C;IAC1C,0BAAkB;IAClB,qDAAsE;IACtE,iDAA8D;IAC9D,uCAA0C;IAC1C,gCAA8B;IAC9B,2CAAkD;IAkBnD,+NAOC;IAED,yCAEC;IAYD;;OAEG;IACH,iJAuBC;CACD;;yBAtGwB,WAAW"}
|
|
@@ -64,7 +64,7 @@ export class TextEntryRedactQuestion extends Question {
|
|
|
64
64
|
}
|
|
65
65
|
|
|
66
66
|
/**
|
|
67
|
-
* @param {import('#question').QuestionViewModel} viewModel
|
|
67
|
+
* @param {import('#typedefs/question-types.d.ts').QuestionViewModel} viewModel
|
|
68
68
|
*/
|
|
69
69
|
addCustomDataToViewModel(viewModel) {
|
|
70
70
|
viewModel.question.label = this.label;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"question.d.ts","sourceRoot":"","sources":["question.js"],"names":[],"mappings":"AAOA;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AAEH;IAIC;;;OAGG;IACH,uEAHW,OAAO,+BAA+B,EAAE,6BAA6B,oBACrE,MAAM,CAAC,MAAM,WAAW,
|
|
1
|
+
{"version":3,"file":"question.d.ts","sourceRoot":"","sources":["question.js"],"names":[],"mappings":"AAOA;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AAEH;IAIC;;;OAGG;IACH,uEAHW,OAAO,+BAA+B,EAAE,6BAA6B,oBACrE,MAAM,CAAC,MAAM,WAAW,EAoBlC;IAzBD,gCAAgC;IAChC,SADW,KAAK,CAAC,UAAU,CAAC,CACpB;IAoBP,6BAAgD;IAEhD,0BAAkB;IAClB,yBAA+C;IAGhD;;;;;;;;;;;;OAYG;IACH,kCAXW,OAAO,kBAAkB,EAAE,OAAO,WAClC,OAAO,UAAU,EAAE,OAAO,mBAC1B,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,YACvB,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,WACvB,OAAO,+BAA+B,EAAE,+BAA+B,GACrE,OAAO,+BAA+B,EAAE,iBAAiB,GAAG;QACpE,QAAQ,EAAE,OAAO,+BAA+B,EAAE,iBAAiB,CAAC,UAAU,CAAC,GAAG;YAChF,OAAO,EAAC,UAAU,EAAE,CAAA;SACrB,CAAA;KACF,CAmDH;IAED;;;;;OAKG,CACH,mBAJW,OAAO,SAAS,EAAE,OAAO,mBACzB,eAAe,GACb,OAAO,CAAC;QAAE,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAA;KAAE,CAAC,CAgCzD;IAED;;OAEG;IACH,6HAaC;CACD;;;;;;;;;;yBArKY;IACR,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,EAAE,MAAM,CAAC;IACZ,IAAI,CAAC,EAAE,MAAM,CAAC;IAChB,OAAO,CAAC,EAAE,OAAO,GAAG,SAAS,CAAC;IAC9B,UAAU,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACpC,SAAS,CAAC,EAAE,WAAW,CAAC;IACxB,WAAW,EAAE;QACX,SAAS,EAAE,MAAM,CAAC;QACd,MAAM,EAAE,MAAM,CAAC;QACnB,KAAK,CAAC,EAAE,OAAO,CAAC;QACZ,KAAK,CAAC,EAAE,MAAM,CAAC;QACf,IAAI,CAAC,EAAE,MAAM,CAAC;QAClB,gBAAgB,CAAC,EAAE,MAAM,CAAC;KAC3B,CAAA;CACH;yBA5BsB,6BAA6B"}
|
|
@@ -41,6 +41,8 @@ export class UnitOptionEntryQuestion extends Question {
|
|
|
41
41
|
constructor({ conditionalFieldName, options, label, ...parentParams }, methodOverrides) {
|
|
42
42
|
super(
|
|
43
43
|
{
|
|
44
|
+
// Prevent capitalisation of answers, but allow override
|
|
45
|
+
capitaliseAnswer: false,
|
|
44
46
|
...parentParams,
|
|
45
47
|
viewFolder: 'unit-option-entry'
|
|
46
48
|
},
|
|
@@ -173,7 +175,7 @@ export class UnitOptionEntryQuestion extends Question {
|
|
|
173
175
|
if (isNaN(answerQuantity)) throw new Error('Conditional answer had an unexpected type');
|
|
174
176
|
|
|
175
177
|
const formattedAnswer = `${answerQuantity} ${answer}`;
|
|
176
|
-
return super.formatAnswerForSummary(sectionSegment, journey, formattedAnswer
|
|
178
|
+
return super.formatAnswerForSummary(sectionSegment, journey, formattedAnswer);
|
|
177
179
|
}
|
|
178
180
|
}
|
|
179
181
|
|
|
@@ -53,6 +53,21 @@ describe('./src/dynamic-forms/components/unit-option-entry/question.js', () => {
|
|
|
53
53
|
assert.strictEqual(unitOptionEntryQuestion.options, OPTIONS);
|
|
54
54
|
});
|
|
55
55
|
|
|
56
|
+
it('should default capitaliseAnswer to false', () => {
|
|
57
|
+
assert.strictEqual(unitOptionEntryQuestion.capitaliseAnswer, false);
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
it('should allow capitaliseAnswer to be set via constructor', () => {
|
|
61
|
+
const customQuestion = new UnitOptionEntryQuestion({
|
|
62
|
+
title: TITLE,
|
|
63
|
+
question: QUESTION,
|
|
64
|
+
fieldName: FIELDNAME,
|
|
65
|
+
conditionalFieldName: CONDITIONAL_FIELDNAME,
|
|
66
|
+
options: OPTIONS,
|
|
67
|
+
capitaliseAnswer: true
|
|
68
|
+
});
|
|
69
|
+
assert.strictEqual(customQuestion.capitaliseAnswer, true);
|
|
70
|
+
});
|
|
56
71
|
it('should handle decimal string formatting conversion', () => {
|
|
57
72
|
const journey = {
|
|
58
73
|
response: {
|
|
@@ -41,7 +41,7 @@ export const questionHasAnswer = (response, question, expectedValue) => {
|
|
|
41
41
|
if (Array.isArray(answerField)) {
|
|
42
42
|
return answerField.includes(expectedValue);
|
|
43
43
|
} else if (question.optionJoinString && typeof answerField === 'string') {
|
|
44
|
-
// todo: answers from options questions sometimes are array sometimes string, why?
|
|
44
|
+
// todo: DF-51 answers from options questions sometimes are array sometimes string, why?
|
|
45
45
|
if (!answerField) return false;
|
|
46
46
|
const answers = answerField.split(question.optionJoinString);
|
|
47
47
|
return answers.includes(expectedValue);
|
|
@@ -1,5 +1,23 @@
|
|
|
1
1
|
export function getConditionalFieldName(parentField: any, conditionalField: any): string;
|
|
2
|
-
|
|
2
|
+
/**
|
|
3
|
+
* Get conditional answer(s) for a question.
|
|
4
|
+
* Returns an object keyed by option value with conditional answers, or null if no conditionals exist.
|
|
5
|
+
*
|
|
6
|
+
* @param {Record<string, unknown>} answers - all answers
|
|
7
|
+
* @param {{options?: Array<{value: string, conditional?: {fieldName: string}}>, fieldName: string, optionJoinString?: string}} question - question with options
|
|
8
|
+
* @param {string} answer - the answer value (may be separated by optionJoinString for checkboxes)
|
|
9
|
+
* @returns {Record<string, string> | null} conditional answer(s) keyed by option value
|
|
10
|
+
*/
|
|
11
|
+
export function getConditionalAnswer(answers: Record<string, unknown>, question: {
|
|
12
|
+
options?: Array<{
|
|
13
|
+
value: string;
|
|
14
|
+
conditional?: {
|
|
15
|
+
fieldName: string;
|
|
16
|
+
};
|
|
17
|
+
}>;
|
|
18
|
+
fieldName: string;
|
|
19
|
+
optionJoinString?: string;
|
|
20
|
+
}, answer: string): Record<string, string> | null;
|
|
3
21
|
/**
|
|
4
22
|
* @param {unknown} conditional
|
|
5
23
|
* @returns {conditional is {html: string}}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"question-utils.d.ts","sourceRoot":"","sources":["question-utils.js"],"names":[],"mappings":"AAAA,yFAEC;AAED,
|
|
1
|
+
{"version":3,"file":"question-utils.d.ts","sourceRoot":"","sources":["question-utils.js"],"names":[],"mappings":"AAAA,yFAEC;AAED;;;;;;;;GAQG;AACH,8CALW,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,YACvB;IAAC,OAAO,CAAC,EAAE,KAAK,CAAC;QAAC,KAAK,EAAE,MAAM,CAAC;QAAC,WAAW,CAAC,EAAE;YAAC,SAAS,EAAE,MAAM,CAAA;SAAC,CAAA;KAAC,CAAC,CAAC;IAAC,SAAS,EAAE,MAAM,CAAC;IAAC,gBAAgB,CAAC,EAAE,MAAM,CAAA;CAAC,UACnH,MAAM,GACJ,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,GAAG,IAAI,CAyBzC;AAED;;;GAGG;AACH,mDAHW,OAAO,GACL,WAAW,IAAI;IAAC,IAAI,EAAE,MAAM,CAAA;CAAC,CAIzC"}
|
|
@@ -2,9 +2,38 @@ export function getConditionalFieldName(parentField, conditionalField) {
|
|
|
2
2
|
return `${parentField}_${conditionalField}`;
|
|
3
3
|
}
|
|
4
4
|
|
|
5
|
+
/**
|
|
6
|
+
* Get conditional answer(s) for a question.
|
|
7
|
+
* Returns an object keyed by option value with conditional answers, or null if no conditionals exist.
|
|
8
|
+
*
|
|
9
|
+
* @param {Record<string, unknown>} answers - all answers
|
|
10
|
+
* @param {{options?: Array<{value: string, conditional?: {fieldName: string}}>, fieldName: string, optionJoinString?: string}} question - question with options
|
|
11
|
+
* @param {string} answer - the answer value (may be separated by optionJoinString for checkboxes)
|
|
12
|
+
* @returns {Record<string, string> | null} conditional answer(s) keyed by option value
|
|
13
|
+
*/
|
|
5
14
|
export function getConditionalAnswer(answers, question, answer) {
|
|
6
|
-
|
|
7
|
-
|
|
15
|
+
if (!answer || !question.options) {
|
|
16
|
+
return null;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
// TODO DF-51 treat as an array
|
|
20
|
+
const joinString = question.optionJoinString || ',';
|
|
21
|
+
const answerValues = String(answer)
|
|
22
|
+
.split(joinString)
|
|
23
|
+
.map((v) => v.trim());
|
|
24
|
+
|
|
25
|
+
const conditionals = {};
|
|
26
|
+
for (const value of answerValues) {
|
|
27
|
+
const option = question.options.find((opt) => opt.value === value);
|
|
28
|
+
if (!option?.conditional?.fieldName) {
|
|
29
|
+
continue;
|
|
30
|
+
}
|
|
31
|
+
const conditionalValue = answers[getConditionalFieldName(question.fieldName, option.conditional.fieldName)];
|
|
32
|
+
if (conditionalValue) {
|
|
33
|
+
conditionals[value] = conditionalValue;
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
return Object.keys(conditionals).length > 0 ? conditionals : null;
|
|
8
37
|
}
|
|
9
38
|
|
|
10
39
|
/**
|