@planning-inspectorate/dynamic-forms 3.15.6 → 3.17.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/.gitattributes +3 -0
- package/.prettierrc +1 -0
- package/README.md +123 -4
- package/package.json +6 -4
- package/{types/src → src}/components/address/question.d.ts +6 -2
- package/src/components/address/question.d.ts.map +1 -0
- package/src/components/address/question.js +7 -19
- package/{types/src → src}/components/boolean/question.d.ts +2 -2
- package/src/components/boolean/question.d.ts.map +1 -0
- package/src/components/boolean/question.js +1 -1
- package/src/components/checkbox/question.d.ts +9 -0
- package/src/components/checkbox/question.d.ts.map +1 -0
- package/src/components/checkbox/question.js +37 -54
- package/src/components/checkbox/question.test.js +170 -27
- package/{types/src → src}/components/date/question.d.ts +2 -2
- package/src/components/date/question.d.ts.map +1 -0
- package/src/components/date/question.js +7 -16
- package/{types/src → src}/components/date-period/question.d.ts +12 -4
- package/src/components/date-period/question.d.ts.map +1 -0
- package/src/components/date-period/question.js +2 -3
- package/{types/src → src}/components/date-time/question.d.ts +2 -2
- package/src/components/date-time/question.d.ts.map +1 -0
- package/src/components/date-time/question.js +5 -14
- package/src/components/email/question.d.ts +23 -0
- package/src/components/email/question.d.ts.map +1 -0
- package/src/components/email/question.js +1 -9
- package/src/components/manage-list/answer-summary-list.njk +1 -1
- package/src/components/manage-list/manage-list-actions.d.ts.map +1 -0
- package/src/components/manage-list/manage-list-section.d.ts.map +1 -0
- package/{types/src → src}/components/manage-list/question.d.ts +6 -3
- package/src/components/manage-list/question.d.ts.map +1 -0
- package/src/components/manage-list/question.js +5 -2
- package/{types/src → src}/components/manage-list/utils.d.ts +3 -3
- package/src/components/manage-list/utils.d.ts.map +1 -0
- package/src/components/manage-list/utils.js +2 -2
- package/src/components/multi-field-input/question.d.ts +52 -0
- package/src/components/multi-field-input/question.d.ts.map +1 -0
- package/src/components/multi-field-input/question.js +5 -13
- package/src/components/number-entry/question.d.ts +12 -0
- package/src/components/number-entry/question.d.ts.map +1 -0
- package/src/components/number-entry/question.js +4 -22
- package/src/components/radio/question.d.ts +11 -0
- package/src/components/radio/question.d.ts.map +1 -0
- package/src/components/radio/question.js +30 -51
- package/src/components/radio/question.test.js +139 -60
- package/src/components/select/question.d.ts +12 -0
- package/src/components/select/question.d.ts.map +1 -0
- package/src/components/select/question.js +5 -48
- package/{types/src → src}/components/single-line-input/question.d.ts +2 -2
- package/src/components/single-line-input/question.d.ts.map +1 -0
- package/src/components/single-line-input/question.js +2 -2
- package/{types/src → src}/components/text-entry/question.d.ts +3 -3
- package/src/components/text-entry/question.d.ts.map +1 -0
- package/src/components/text-entry/question.js +2 -2
- package/{types/src → src}/components/text-entry-redact/question.d.ts +8 -9
- package/src/components/text-entry-redact/question.d.ts.map +1 -0
- package/src/components/text-entry-redact/question.js +5 -2
- package/{types/src → src}/components/unit-option-entry/question.d.ts +12 -8
- package/src/components/unit-option-entry/question.d.ts.map +1 -0
- package/src/components/unit-option-entry/question.js +5 -6
- package/src/components/utils/component-types.d.ts.map +1 -0
- package/src/components/utils/persisted-number-answer.d.ts.map +1 -0
- package/src/components/utils/question-has-answer.d.ts.map +1 -0
- package/src/components/utils/question-has-answer.js +1 -1
- package/src/components/utils/question-utils.d.ts +33 -0
- package/src/components/utils/question-utils.d.ts.map +1 -0
- package/src/components/utils/question-utils.js +31 -2
- package/src/components/utils/question-utils.test.js +89 -2
- package/{types/src → src}/controller.d.ts +2 -7
- package/src/controller.d.ts.map +1 -0
- package/src/controller.js +3 -10
- package/{types/src → src}/index.d.ts +4 -3
- package/src/index.d.ts.map +1 -0
- package/src/index.js +6 -4
- package/src/journey/journey-response.d.ts +28 -0
- package/src/journey/journey-response.d.ts.map +1 -0
- package/src/journey/journey-response.js +4 -8
- package/{types/src → src}/journey/journey.d.ts +8 -8
- package/src/journey/journey.d.ts.map +1 -0
- package/src/journey/journey.js +6 -6
- package/src/lib/address-utils.d.ts.map +1 -0
- package/src/lib/address.d.ts.map +1 -0
- package/src/lib/date-utils.d.ts.map +1 -0
- package/src/lib/session-answer-store.d.ts.map +1 -0
- package/src/lib/utils.d.ts.map +1 -0
- package/src/lib/uuid.d.ts.map +1 -0
- package/src/middleware/build-get-journey.d.ts.map +1 -0
- package/src/middleware/redirect-to-unanswered-question.d.ts +12 -0
- package/src/middleware/redirect-to-unanswered-question.d.ts.map +1 -0
- package/src/middleware/redirect-to-unanswered-question.js +1 -1
- package/src/questions/create-questions.d.ts +18 -0
- package/src/questions/create-questions.d.ts.map +1 -0
- package/src/questions/create-questions.js +3 -3
- package/{types/src → src}/questions/options-question.d.ts +7 -7
- package/src/questions/options-question.d.ts.map +1 -0
- package/src/questions/options-question.js +29 -4
- package/src/questions/options-question.test.js +106 -0
- package/{types/src → src}/questions/question.d.ts +48 -79
- package/src/questions/question.d.ts.map +1 -0
- package/src/questions/question.js +83 -60
- package/src/questions/question.test.js +86 -0
- package/src/questions/questions.d.ts +3 -0
- package/src/questions/questions.d.ts.map +1 -0
- package/src/questions/questions.js +1 -1
- package/{types/src → src}/section.d.ts +16 -15
- package/src/section.d.ts.map +1 -0
- package/src/section.js +12 -14
- package/src/validator/address-validator.d.ts.map +1 -0
- package/src/validator/base-validator.d.ts.map +1 -0
- package/src/validator/conditional-required-validator.d.ts.map +1 -0
- package/src/validator/confirmation-checkbox-validator.d.ts.map +1 -0
- package/src/validator/coordinates-validator.d.ts.map +1 -0
- package/src/validator/cross-question-validator.d.ts.map +1 -0
- package/src/validator/date-period-validator.d.ts.map +1 -0
- package/src/validator/date-time-validator.d.ts.map +1 -0
- package/src/validator/date-validator.d.ts.map +1 -0
- package/src/validator/document-upload-validator.d.ts.map +1 -0
- package/src/validator/email-validator.d.ts.map +1 -0
- package/{types/src → src}/validator/multi-field-input-validator.d.ts +3 -3
- package/src/validator/multi-field-input-validator.d.ts.map +1 -0
- package/src/validator/multi-field-input-validator.js +3 -3
- package/{types/src → src}/validator/numeric-validator.d.ts +2 -2
- package/src/validator/numeric-validator.d.ts.map +1 -0
- package/src/validator/numeric-validator.js +2 -2
- package/src/validator/required-validator.d.ts.map +1 -0
- package/src/validator/same-answer-validator.d.ts.map +1 -0
- package/{types/src → src}/validator/string-validator.d.ts +3 -3
- package/src/validator/string-validator.d.ts.map +1 -0
- package/src/validator/string-validator.js +3 -3
- package/src/validator/unit-option-entry-validator.d.ts.map +1 -0
- package/src/validator/valid-option-validator.d.ts.map +1 -0
- package/src/validator/validation-error-handler.d.ts.map +1 -0
- package/src/validator/validator.d.ts.map +1 -0
- package/test/journey.js +2 -0
- package/test/questions.js +51 -1
- package/test/secondary-actions.test.js +1 -1
- package/test/snapshots/check-your-answers-not-started.html +22 -0
- package/test/snapshots/check-your-answers-with-answers.html +22 -0
- package/test/snapshots/favourite-activity-reason.html +54 -0
- package/test/snapshots/travel-requirements.html +48 -0
- package/test/utils/question-test-utils.js +36 -6
- package/tsconfig.json +11 -4
- package/{types/src/questions → typedefs}/question-props.d.ts +28 -26
- package/typedefs/question-types.d.ts +141 -0
- package/{src → typedefs}/section-types.d.ts +1 -1
- package/typedefs/section-types.js +1 -0
- package/move-types.js +0 -12
- package/src/questions/question-props.d.ts +0 -295
- package/src/questions/question-types.d.ts +0 -86
- package/types/commitlint.config.d.ts +0 -7
- package/types/commitlint.config.d.ts.map +0 -1
- package/types/eslint.config.d.ts +0 -97
- package/types/eslint.config.d.ts.map +0 -1
- package/types/move-types.d.ts +0 -2
- package/types/move-types.d.ts.map +0 -1
- package/types/src/components/address/question.d.ts.map +0 -1
- package/types/src/components/boolean/question.d.ts.map +0 -1
- package/types/src/components/checkbox/question.d.ts +0 -33
- package/types/src/components/checkbox/question.d.ts.map +0 -1
- package/types/src/components/date/question.d.ts.map +0 -1
- package/types/src/components/date-period/question.d.ts.map +0 -1
- package/types/src/components/date-time/question.d.ts.map +0 -1
- package/types/src/components/email/question.d.ts +0 -35
- package/types/src/components/email/question.d.ts.map +0 -1
- package/types/src/components/manage-list/manage-list-actions.d.ts.map +0 -1
- package/types/src/components/manage-list/manage-list-section.d.ts.map +0 -1
- package/types/src/components/manage-list/question.d.ts.map +0 -1
- package/types/src/components/manage-list/utils.d.ts.map +0 -1
- package/types/src/components/multi-field-input/question.d.ts +0 -64
- package/types/src/components/multi-field-input/question.d.ts.map +0 -1
- package/types/src/components/number-entry/question.d.ts +0 -36
- package/types/src/components/number-entry/question.d.ts.map +0 -1
- package/types/src/components/radio/question.d.ts +0 -23
- package/types/src/components/radio/question.d.ts.map +0 -1
- package/types/src/components/select/question.d.ts +0 -22
- package/types/src/components/select/question.d.ts.map +0 -1
- package/types/src/components/single-line-input/question.d.ts.map +0 -1
- package/types/src/components/text-entry/question.d.ts.map +0 -1
- package/types/src/components/text-entry-redact/question.d.ts.map +0 -1
- package/types/src/components/unit-option-entry/question.d.ts.map +0 -1
- package/types/src/components/utils/component-types.d.ts.map +0 -1
- package/types/src/components/utils/persisted-number-answer.d.ts.map +0 -1
- package/types/src/components/utils/question-has-answer.d.ts.map +0 -1
- package/types/src/components/utils/question-utils.d.ts +0 -15
- package/types/src/components/utils/question-utils.d.ts.map +0 -1
- package/types/src/controller.d.ts.map +0 -1
- package/types/src/index.d.ts.map +0 -1
- package/types/src/journey/journey-response.d.ts +0 -32
- package/types/src/journey/journey-response.d.ts.map +0 -1
- package/types/src/journey/journey-types.d.ts +0 -28
- package/types/src/journey/journey-types.d.ts.map +0 -1
- package/types/src/journey/journey.d.ts.map +0 -1
- package/types/src/lib/address-utils.d.ts.map +0 -1
- package/types/src/lib/address.d.ts.map +0 -1
- package/types/src/lib/date-utils.d.ts.map +0 -1
- package/types/src/lib/session-answer-store.d.ts.map +0 -1
- package/types/src/lib/utils.d.ts.map +0 -1
- package/types/src/lib/uuid.d.ts.map +0 -1
- package/types/src/middleware/build-get-journey.d.ts.map +0 -1
- package/types/src/middleware/redirect-to-unanswered-question.d.ts +0 -12
- package/types/src/middleware/redirect-to-unanswered-question.d.ts.map +0 -1
- package/types/src/questions/create-questions.d.ts +0 -18
- package/types/src/questions/create-questions.d.ts.map +0 -1
- package/types/src/questions/options-question.d.ts.map +0 -1
- package/types/src/questions/question-props.d.ts.map +0 -1
- package/types/src/questions/question-types.d.ts +0 -86
- package/types/src/questions/question-types.d.ts.map +0 -1
- package/types/src/questions/question.d.ts.map +0 -1
- package/types/src/questions/questions.d.ts +0 -3
- package/types/src/questions/questions.d.ts.map +0 -1
- package/types/src/section.d.ts.map +0 -1
- package/types/src/validator/address-validator.d.ts.map +0 -1
- package/types/src/validator/base-validator.d.ts.map +0 -1
- package/types/src/validator/conditional-required-validator.d.ts.map +0 -1
- package/types/src/validator/confirmation-checkbox-validator.d.ts.map +0 -1
- package/types/src/validator/coordinates-validator.d.ts.map +0 -1
- package/types/src/validator/cross-question-validator.d.ts.map +0 -1
- package/types/src/validator/date-period-validator.d.ts.map +0 -1
- package/types/src/validator/date-time-validator.d.ts.map +0 -1
- package/types/src/validator/date-validator.d.ts.map +0 -1
- package/types/src/validator/document-upload-validator.d.ts.map +0 -1
- package/types/src/validator/email-validator.d.ts.map +0 -1
- package/types/src/validator/multi-field-input-validator.d.ts.map +0 -1
- package/types/src/validator/numeric-validator.d.ts.map +0 -1
- package/types/src/validator/required-validator.d.ts.map +0 -1
- package/types/src/validator/same-answer-validator.d.ts.map +0 -1
- package/types/src/validator/string-validator.d.ts.map +0 -1
- package/types/src/validator/unit-option-entry-validator.d.ts.map +0 -1
- package/types/src/validator/valid-option-validator.d.ts.map +0 -1
- package/types/src/validator/validation-error-handler.d.ts.map +0 -1
- package/types/src/validator/validator.d.ts.map +0 -1
- /package/{types/src → src}/components/manage-list/manage-list-actions.d.ts +0 -0
- /package/{types/src → src}/components/manage-list/manage-list-section.d.ts +0 -0
- /package/{types/src → src}/components/utils/component-types.d.ts +0 -0
- /package/{types/src → src}/components/utils/persisted-number-answer.d.ts +0 -0
- /package/{types/src → src}/components/utils/question-has-answer.d.ts +0 -0
- /package/{types/src → src}/lib/address-utils.d.ts +0 -0
- /package/{types/src → src}/lib/address.d.ts +0 -0
- /package/{types/src → src}/lib/date-utils.d.ts +0 -0
- /package/{types/src → src}/lib/session-answer-store.d.ts +0 -0
- /package/{types/src → src}/lib/utils.d.ts +0 -0
- /package/{types/src → src}/lib/uuid.d.ts +0 -0
- /package/{types/src → src}/middleware/build-get-journey.d.ts +0 -0
- /package/{types/src → src}/validator/address-validator.d.ts +0 -0
- /package/{types/src → src}/validator/base-validator.d.ts +0 -0
- /package/{types/src → src}/validator/conditional-required-validator.d.ts +0 -0
- /package/{types/src → src}/validator/confirmation-checkbox-validator.d.ts +0 -0
- /package/{types/src → src}/validator/coordinates-validator.d.ts +0 -0
- /package/{types/src → src}/validator/cross-question-validator.d.ts +0 -0
- /package/{types/src → src}/validator/date-period-validator.d.ts +0 -0
- /package/{types/src → src}/validator/date-time-validator.d.ts +0 -0
- /package/{types/src → src}/validator/date-validator.d.ts +0 -0
- /package/{types/src → src}/validator/document-upload-validator.d.ts +0 -0
- /package/{types/src → src}/validator/email-validator.d.ts +0 -0
- /package/{types/src → src}/validator/required-validator.d.ts +0 -0
- /package/{types/src → src}/validator/same-answer-validator.d.ts +0 -0
- /package/{types/src → src}/validator/unit-option-entry-validator.d.ts +0 -0
- /package/{types/src → src}/validator/valid-option-validator.d.ts +0 -0
- /package/{types/src → src}/validator/validation-error-handler.d.ts +0 -0
- /package/{types/src → src}/validator/validator.d.ts +0 -0
- /package/{src/journey → typedefs}/journey-types.d.ts +0 -0
- /package/{src/journey → typedefs}/journey-types.js +0 -0
- /package/{src/questions → typedefs}/question-props.js +0 -0
- /package/{src/questions → typedefs}/question-types.js +0 -0
package/.gitattributes
CHANGED
package/.prettierrc
CHANGED
package/README.md
CHANGED
|
@@ -286,6 +286,117 @@ const questionProps = {
|
|
|
286
286
|
const questions = createQuestions(questionProps, questionClasses, {});
|
|
287
287
|
```
|
|
288
288
|
|
|
289
|
+
### Custom Summary Formatting
|
|
290
|
+
|
|
291
|
+
The `formatSummaryValue` option allows you to customise how answers are displayed on check-your-answers pages without needing to create a custom question class.
|
|
292
|
+
|
|
293
|
+
#### Basic Usage
|
|
294
|
+
|
|
295
|
+
Pass a `formatSummaryValue` function when creating a question:
|
|
296
|
+
|
|
297
|
+
```javascript
|
|
298
|
+
import { RadioQuestion } from '@planning-inspectorate/dynamic-forms';
|
|
299
|
+
|
|
300
|
+
new RadioQuestion({
|
|
301
|
+
fieldName: 'status',
|
|
302
|
+
title: 'Application status',
|
|
303
|
+
question: 'What is the application status?',
|
|
304
|
+
options: [
|
|
305
|
+
{ text: 'Approved', value: 'approved' },
|
|
306
|
+
{ text: 'Rejected', value: 'rejected' }
|
|
307
|
+
],
|
|
308
|
+
formatSummaryValue: ({ formattedAnswer }) => `<strong>${formattedAnswer}</strong>`
|
|
309
|
+
});
|
|
310
|
+
```
|
|
311
|
+
|
|
312
|
+
#### Formatter Context
|
|
313
|
+
|
|
314
|
+
The formatter function receives a context object with the following properties:
|
|
315
|
+
|
|
316
|
+
| Property | Type | Description |
|
|
317
|
+
|----------|------|------------------------------------------------|
|
|
318
|
+
| `answer` | `unknown` | The raw answer value |
|
|
319
|
+
| `formattedAnswer` | `string` | The default formatted display value |
|
|
320
|
+
| `question` | `Question` | The question instance |
|
|
321
|
+
| `journey` | `Journey` | The journey instance |
|
|
322
|
+
| `sectionSegment` | `string` | The current section segment |
|
|
323
|
+
| `selectedOptions` | `Option[] \| undefined` | For OptionsQuestions (such as Checkbox or Radio), the matched option object(s) as an array |
|
|
324
|
+
|
|
325
|
+
#### Answer Formats
|
|
326
|
+
|
|
327
|
+
Different options-based questions store answers in different formats. The `selectedOptions` array normalises these formats for your formatter:
|
|
328
|
+
|
|
329
|
+
| Question Type | Answer Format | `selectedOptions` Result |
|
|
330
|
+
|---------------|---------------|--------------------------|
|
|
331
|
+
| **Radio / Select** | Single string value: `"approved"` | `[{ text: 'Approved', value: 'approved' }]` |
|
|
332
|
+
| **Radio / Select** | Conditional object: `{ value: "yes", conditional: "details" }` | `[{ text: 'Yes', value: 'yes', conditional: {...} }]` |
|
|
333
|
+
| **Checkbox** | Comma-separated string: `"option1,option2"` | `[{ text: 'Option 1', value: 'option1' }, { text: 'Option 2', value: 'option2' }]` |
|
|
334
|
+
| **Checkbox** | Single conditional object: `{ value: "other", conditional: "details" }` | `[{ text: 'Other', value: 'other', conditional: {...} }]` |
|
|
335
|
+
|
|
336
|
+
For single-select questions (Radio, Select), use `selectedOptions[0]` to access the selected option. For multi-select questions (Checkbox), iterate over the array.
|
|
337
|
+
|
|
338
|
+
#### Examples
|
|
339
|
+
|
|
340
|
+
**Bold formatting:**
|
|
341
|
+
|
|
342
|
+
```javascript
|
|
343
|
+
formatSummaryValue: ({ formattedAnswer }) => `<strong>${escapeHtml(formattedAnswer)}</strong>`
|
|
344
|
+
```
|
|
345
|
+
|
|
346
|
+
**Conditional styling based on answer:**
|
|
347
|
+
|
|
348
|
+
```javascript
|
|
349
|
+
formatSummaryValue: ({ selectedOptions }) => {
|
|
350
|
+
const selectedOption = selectedOptions[0];
|
|
351
|
+
const color = selectedOption?.value === 'approved' ? 'green' : 'red';
|
|
352
|
+
return `<span style="color: ${color}">${escapeHtml(selectedOption?.text ?? '-')}</span>`;
|
|
353
|
+
}
|
|
354
|
+
```
|
|
355
|
+
|
|
356
|
+
**Adding a suffix from another answer:**
|
|
357
|
+
|
|
358
|
+
```javascript
|
|
359
|
+
const suffixes = { solid: ' (tonnes)', liquid: ' (litres)' };
|
|
360
|
+
|
|
361
|
+
formatSummaryValue: ({ answer, formattedAnswer }) => {
|
|
362
|
+
const suffix = suffixes[answer] ?? '';
|
|
363
|
+
return `${formattedAnswer}${suffix}`;
|
|
364
|
+
}
|
|
365
|
+
```
|
|
366
|
+
|
|
367
|
+
**Accessing other journey answers:**
|
|
368
|
+
|
|
369
|
+
```javascript
|
|
370
|
+
formatSummaryValue: ({ formattedAnswer, journey }) => {
|
|
371
|
+
const capacity = journey.response.answers.wasteCapacity;
|
|
372
|
+
return capacity ? `${formattedAnswer}: ${capacity}` : formattedAnswer;
|
|
373
|
+
}
|
|
374
|
+
```
|
|
375
|
+
|
|
376
|
+
**Using selectedOptions for single-select questions (Radio/Select):**
|
|
377
|
+
|
|
378
|
+
```javascript
|
|
379
|
+
formatSummaryValue: ({ selectedOptions }) => {
|
|
380
|
+
const selectedOption = selectedOptions[0];
|
|
381
|
+
// Access the full option object including any custom properties
|
|
382
|
+
const warning = selectedOption?.warning;
|
|
383
|
+
return warning ? `${escapeHtml(selectedOption?.text ?? '-')} ⚠️` : escapeHtml(selectedOption?.text ?? '-');
|
|
384
|
+
}
|
|
385
|
+
```
|
|
386
|
+
|
|
387
|
+
**Using selectedOptions for multi-select questions (Checkbox):**
|
|
388
|
+
|
|
389
|
+
```javascript
|
|
390
|
+
formatSummaryValue: ({ selectedOptions }) => {
|
|
391
|
+
// Join all selected option texts with custom formatting
|
|
392
|
+
return selectedOptions
|
|
393
|
+
.map(opt => `<span class="tag">${escapeHtml(opt.text)}</span>`)
|
|
394
|
+
.join(' ');
|
|
395
|
+
}
|
|
396
|
+
```
|
|
397
|
+
|
|
398
|
+
> **Note:** `formattedAnswer` is already escaped by dynamic-forms; you are responsible for escaping any additional user-controlled values you include (for example `answer`, option text from `selectedOptions`, or values read from `journey.response.answers`).
|
|
399
|
+
|
|
289
400
|
## Contributing
|
|
290
401
|
|
|
291
402
|
When contributing to this package, ensure changes are generic and not service-specific. Speak to the R&D devs if you are not sure. Prefer configuration over hardcoding values, and ensure the code is well documented.
|
|
@@ -295,9 +406,17 @@ Commits must follow conventional commits, and the commit types will be used by s
|
|
|
295
406
|
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.
|
|
296
407
|
|
|
297
408
|
### Type safety
|
|
298
|
-
We use JSDocs to describe the types used. This is helpful for the JavaScript developers, and *crucial* for this project being compatible with TypeScript. Type declarations for TypeScript users are auto-generated by `tsc`
|
|
299
|
-
|
|
300
|
-
|
|
409
|
+
We use JSDocs to describe the types used. This is helpful for the JavaScript developers, and *crucial* for this project being compatible with TypeScript. Type declarations for TypeScript users are auto-generated by `tsc` inline alongside the source files.
|
|
410
|
+
|
|
411
|
+
**Type definitions:**
|
|
412
|
+
- Shared type definitions should be placed in the `typedefs/` folder (e.g. `typedefs/question-types.d.ts`)
|
|
413
|
+
- New type definition files need a corresponding empty `.js` file, which needs to be exported from `index.js`, in order to be importable from the package root.
|
|
414
|
+
- Types can be imported using path aliases, e.g. `@param {import('#typedefs/question-types.d.ts').SummaryRow[]} rows`
|
|
415
|
+
|
|
416
|
+
**Best practices:**
|
|
417
|
+
1. Always use inline imports for JSDoc types, e.g. `@param {import('#section').Section} section`
|
|
418
|
+
2. Avoid top-level `@typedef` imports like `@typedef {import('../validator/base-validator.js')} BaseValidator` - these can create confusion when the compiler auto-generates declarations
|
|
419
|
+
3. Maintain `index.js` - if you're adding code that users of this module will import, ensure it is exported in `index.js`
|
|
301
420
|
|
|
302
421
|
Thank you for your co-operation and contributions!
|
|
303
422
|
|
|
@@ -313,7 +432,7 @@ To update any snapshots with rendering changes (or new questions), run `node --t
|
|
|
313
432
|
|
|
314
433
|
When implement a new component, extend the base `Question` class. Common methods to override are:
|
|
315
434
|
|
|
316
|
-
* `
|
|
435
|
+
* `formatAnswer` - used for check-your-answers display
|
|
317
436
|
* `getDataToSave` - answer data to save
|
|
318
437
|
* `addCustomDataToViewModel` - customise the view model with extra data/configuration
|
|
319
438
|
* `answerForViewModel` - customise the view model answer value
|
package/package.json
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@planning-inspectorate/dynamic-forms",
|
|
3
|
-
"version": "3.
|
|
3
|
+
"version": "3.17.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",
|
|
7
|
-
"types": "./
|
|
7
|
+
"types": "./src/index.d.ts",
|
|
8
8
|
"repository": {
|
|
9
9
|
"type": "git",
|
|
10
10
|
"url": "git+https://github.com/Planning-Inspectorate/dynamic-forms.git"
|
|
@@ -17,7 +17,8 @@
|
|
|
17
17
|
"lint": "npx eslint .",
|
|
18
18
|
"format": "prettier --write .",
|
|
19
19
|
"format-check": "prettier --check .",
|
|
20
|
-
"generate-types": "npx tsc --project tsconfig.json
|
|
20
|
+
"generate-types": "npx tsc --project tsconfig.json",
|
|
21
|
+
"reset-generated-types": "find . -name \"*.d.ts\" -not -path \"./typedefs/*\" -not -path \"./node_modules/*\" -delete && find . -name \"*.d.ts.map\" -not -path \"./node_modules/*\" -delete",
|
|
21
22
|
"prepare": "node .husky/install.mjs",
|
|
22
23
|
"test": "node --test",
|
|
23
24
|
"test-coverage": "node --test --experimental-test-coverage"
|
|
@@ -30,7 +31,8 @@
|
|
|
30
31
|
"nunjucks": "^3.2.4"
|
|
31
32
|
},
|
|
32
33
|
"imports": {
|
|
33
|
-
"#question-types": "./
|
|
34
|
+
"#question-types": "./typedefs/question-types.d.ts",
|
|
35
|
+
"#typedefs/*": "./typedefs/*",
|
|
34
36
|
"#test/*": "./test/*",
|
|
35
37
|
"#base-validator": "./src/validator/base-validator.js",
|
|
36
38
|
"#journey": "./src/journey/journey.js",
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
export class AddressQuestion extends Question {
|
|
2
2
|
/**
|
|
3
|
-
* @param {import('
|
|
3
|
+
* @param {import('#typedefs/question-props.d.ts').SiteAddressQuestionParams} params
|
|
4
4
|
*/
|
|
5
|
-
constructor(params: import("
|
|
5
|
+
constructor(params: import("#typedefs/question-props.d.ts").SiteAddressQuestionParams);
|
|
6
6
|
requiredFields: {
|
|
7
7
|
addressLine1: boolean;
|
|
8
8
|
addressLine2: boolean;
|
|
@@ -29,6 +29,10 @@ export class AddressQuestion extends Question {
|
|
|
29
29
|
format(answer: {
|
|
30
30
|
[x: string]: any;
|
|
31
31
|
}): string;
|
|
32
|
+
/**
|
|
33
|
+
* returns the formatted answers values to be used to build task list elements
|
|
34
|
+
*/
|
|
35
|
+
formatAnswer(answer: any): string;
|
|
32
36
|
formatLabelFromRequiredFields(fieldName: any): "" | " (optional)";
|
|
33
37
|
}
|
|
34
38
|
export default AddressQuestion;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"question.d.ts","sourceRoot":"","sources":["question.js"],"names":[],"mappings":"AAOA;IACC;;OAEG;IACH,oBAFW,OAAO,+BAA+B,EAAE,yBAAyB,EAqB3E;IAXE;;;;;;kBAA8C;IAIhD;;;;;;MAMC;IAiBF;;;OAGG;IACH,4BAHW,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,GACjB,GAAC,GAAC,MAAM,CAqBpB;IAqCD;;;OAGG;IACH;;eAUC;IAED;;OAEG;IACH,kCAIC;IAED,kEAMC;CACD;;yBA1IwB,WAAW"}
|
|
@@ -7,7 +7,7 @@ import AddressValidator from '../../validator/address-validator.js';
|
|
|
7
7
|
|
|
8
8
|
export class AddressQuestion extends Question {
|
|
9
9
|
/**
|
|
10
|
-
* @param {import('
|
|
10
|
+
* @param {import('#typedefs/question-props.d.ts').SiteAddressQuestionParams} params
|
|
11
11
|
*/
|
|
12
12
|
constructor(params) {
|
|
13
13
|
super({
|
|
@@ -70,7 +70,7 @@ export class AddressQuestion extends Question {
|
|
|
70
70
|
}
|
|
71
71
|
|
|
72
72
|
/**
|
|
73
|
-
* @param {import('#question').QuestionViewModel} viewModel
|
|
73
|
+
* @param {import('#typedefs/question-types.d.ts').QuestionViewModel} viewModel
|
|
74
74
|
*/
|
|
75
75
|
addCustomDataToViewModel(viewModel) {
|
|
76
76
|
viewModel.question.labels = this.addressLabels;
|
|
@@ -122,25 +122,13 @@ export class AddressQuestion extends Question {
|
|
|
122
122
|
|
|
123
123
|
/**
|
|
124
124
|
* returns the formatted answers values to be used to build task list elements
|
|
125
|
-
* @type {Question['formatAnswerForSummary']}
|
|
126
125
|
*/
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
formattedAnswer = nl2br(escape(this.format(answer)));
|
|
132
|
-
} else if (answer === null) {
|
|
133
|
-
formattedAnswer = '';
|
|
134
|
-
}
|
|
135
|
-
|
|
136
|
-
return [
|
|
137
|
-
{
|
|
138
|
-
key: `${this.title}`,
|
|
139
|
-
value: formattedAnswer,
|
|
140
|
-
action: this.getAction(sectionSegment, journey, answer)
|
|
141
|
-
}
|
|
142
|
-
];
|
|
126
|
+
formatAnswer(answer) {
|
|
127
|
+
if (answer === null) return '';
|
|
128
|
+
if (!answer) return this.notStartedText;
|
|
129
|
+
return nl2br(escape(this.format(answer)));
|
|
143
130
|
}
|
|
131
|
+
|
|
144
132
|
formatLabelFromRequiredFields(fieldName) {
|
|
145
133
|
if (this.requiredFields && this.requiredFields[fieldName]) {
|
|
146
134
|
return '';
|
|
@@ -7,9 +7,9 @@ export function booleanToYesNoValue(value: any): "yes" | "no";
|
|
|
7
7
|
export function booleanToYesNoOrNull(value: boolean | null): string | null;
|
|
8
8
|
export class BooleanQuestion extends RadioQuestion {
|
|
9
9
|
/**
|
|
10
|
-
* @param {import('
|
|
10
|
+
* @param {import('#typedefs/question-props.d.ts').BooleanQuestionParams} params
|
|
11
11
|
*/
|
|
12
|
-
constructor({ title, question, fieldName, url, hint, pageTitle, description, html, validators, interfaceType, options, editable, viewData }: import("
|
|
12
|
+
constructor({ title, question, fieldName, url, hint, pageTitle, description, html, validators, interfaceType, options, editable, viewData }: import("#typedefs/question-props.d.ts").BooleanQuestionParams);
|
|
13
13
|
interfaceType: "checkbox" | "radio";
|
|
14
14
|
/**
|
|
15
15
|
* Get the data to save from the request, returns an object of answers
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"question.d.ts","sourceRoot":"","sources":["question.js"],"names":[],"mappings":"AAEA;;;GAGG;AAMI,sCAHI,MAAM,GAAC,GAAC,GACN,OAAO,CAOnB;AAEM,8DAEN;AAMM,4CAHI,OAAO,GAAC,IAAI,GACV,MAAM,GAAC,IAAI,CAOvB;AAED;IACC;;OAEG;IACH,6IAFW,OAAO,+BAA+B,EAAE,qBAAqB,EAmDvE;IADA,oCAAkC;IAGnC;;;;;OAKG,CACH,mBAJW,OAAO,SAAS,EAAE,OAAO,mBACzB,eAAe,GACb,OAAO,CAAC;QAAE,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAA;KAAE,CAAC,CAezD;CACD;;0BA5GyB,sBAAsB"}
|
|
@@ -33,7 +33,7 @@ export const booleanToYesNoOrNull = (value) => {
|
|
|
33
33
|
|
|
34
34
|
export class BooleanQuestion extends RadioQuestion {
|
|
35
35
|
/**
|
|
36
|
-
* @param {import('
|
|
36
|
+
* @param {import('#typedefs/question-props.d.ts').BooleanQuestionParams} params
|
|
37
37
|
*/
|
|
38
38
|
constructor({
|
|
39
39
|
title,
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
export class CheckboxQuestion extends OptionsQuestion {
|
|
2
|
+
/**
|
|
3
|
+
* @param {import('#typedefs/question-props.d.ts').CheckboxQuestionParams} params
|
|
4
|
+
*/
|
|
5
|
+
constructor({ ...parentParams }: import("#typedefs/question-props.d.ts").CheckboxQuestionParams);
|
|
6
|
+
}
|
|
7
|
+
export default CheckboxQuestion;
|
|
8
|
+
import OptionsQuestion from '../../questions/options-question.js';
|
|
9
|
+
//# sourceMappingURL=question.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"question.d.ts","sourceRoot":"","sources":["question.js"],"names":[],"mappings":"AAKA;IACC;;OAEG;IACH,iCAFW,OAAO,+BAA+B,EAAE,sBAAsB,EAUxE;CA4CD;;4BA5D2B,qCAAqC"}
|
|
@@ -1,80 +1,63 @@
|
|
|
1
|
+
import escape from 'escape-html';
|
|
1
2
|
import OptionsQuestion from '../../questions/options-question.js';
|
|
2
|
-
import questionUtils from '../utils/question-utils.js';
|
|
3
3
|
|
|
4
4
|
const defaultOptionJoinString = ',';
|
|
5
5
|
|
|
6
|
-
/**
|
|
7
|
-
* @typedef ConditionalAnswerObject
|
|
8
|
-
* @type {object}
|
|
9
|
-
* @property {string} value the checkbox answer
|
|
10
|
-
* @property {string} conditional the conditional text input
|
|
11
|
-
*/
|
|
12
|
-
|
|
13
6
|
export class CheckboxQuestion extends OptionsQuestion {
|
|
14
7
|
/**
|
|
15
|
-
* @param {import('
|
|
8
|
+
* @param {import('#typedefs/question-props.d.ts').CheckboxQuestionParams} params
|
|
16
9
|
*/
|
|
17
|
-
constructor({
|
|
10
|
+
constructor({ ...parentParams }) {
|
|
18
11
|
super({
|
|
19
|
-
|
|
20
|
-
question,
|
|
12
|
+
...parentParams,
|
|
21
13
|
viewFolder: 'checkbox',
|
|
22
|
-
|
|
23
|
-
url,
|
|
24
|
-
pageTitle,
|
|
25
|
-
description,
|
|
26
|
-
options,
|
|
27
|
-
validators,
|
|
28
|
-
viewData
|
|
14
|
+
capitaliseAnswer: false
|
|
29
15
|
});
|
|
30
16
|
|
|
31
17
|
this.optionJoinString = defaultOptionJoinString;
|
|
32
18
|
}
|
|
33
19
|
|
|
34
20
|
/**
|
|
35
|
-
*
|
|
36
|
-
*
|
|
37
|
-
*
|
|
38
|
-
*
|
|
39
|
-
*
|
|
21
|
+
* Formats an answer value for display in the summary.
|
|
22
|
+
* Handles object answers with conditional fields.
|
|
23
|
+
* - Single value with conditional: { value: 'yes', conditional: { yes: 'details' } }
|
|
24
|
+
* - Multiple values with conditionals: { value: 'yes,no', conditional: { yes: 'details1', no: 'details2' } }
|
|
25
|
+
*
|
|
26
|
+
* @param {unknown} answer - the raw answer value
|
|
27
|
+
* @returns {string} the formatted answer for display
|
|
40
28
|
*/
|
|
41
|
-
|
|
42
|
-
if (
|
|
43
|
-
return
|
|
29
|
+
formatAnswer(answer) {
|
|
30
|
+
if (answer === null || answer === undefined || answer === '') {
|
|
31
|
+
return this.notStartedText;
|
|
44
32
|
}
|
|
45
33
|
|
|
46
|
-
//
|
|
47
|
-
if (answer
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
const conditionalAnswerText = selectedOption.conditional?.label
|
|
51
|
-
? `${selectedOption.conditional.label} ${answer.conditional}`
|
|
52
|
-
: answer.conditional;
|
|
53
|
-
|
|
54
|
-
const formattedConditionalText = [selectedOption.text, conditionalAnswerText].join('\n');
|
|
55
|
-
|
|
56
|
-
return super.formatAnswerForSummary(sectionSegment, journey, formattedConditionalText, false);
|
|
34
|
+
// Handle simple string answers (e.g. '1,2')
|
|
35
|
+
if (typeof answer !== 'object' || answer.value === undefined) {
|
|
36
|
+
return super.formatAnswer(answer);
|
|
57
37
|
}
|
|
58
38
|
|
|
59
|
-
//
|
|
60
|
-
|
|
39
|
+
// Handle object answers with conditional fields
|
|
40
|
+
// TODO DF-51 treat as an array
|
|
41
|
+
const answerValues = String(answer.value)
|
|
42
|
+
.split(this.optionJoinString)
|
|
43
|
+
.map((v) => v.trim());
|
|
44
|
+
const conditionals = answer.conditional || {};
|
|
45
|
+
|
|
46
|
+
const formattedParts = answerValues.map((value) => {
|
|
47
|
+
const option = this.options.find((opt) => opt.value === value);
|
|
48
|
+
const optionText = escape(option ? option.text : value);
|
|
61
49
|
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
questionUtils.getConditionalFieldName(this.fieldName, option.conditional.fieldName)
|
|
69
|
-
];
|
|
70
|
-
return [option.text, conditionalAnswer].join('\n');
|
|
71
|
-
}
|
|
50
|
+
// Check if this option has a conditional answer
|
|
51
|
+
const conditionalValue = conditionals[value];
|
|
52
|
+
if (conditionalValue) {
|
|
53
|
+
const label = option?.conditional?.label ? `${escape(option.conditional.label)} ` : '';
|
|
54
|
+
return `${optionText}<br>${label}${escape(conditionalValue)}`;
|
|
55
|
+
}
|
|
72
56
|
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
.join('\n');
|
|
57
|
+
return optionText;
|
|
58
|
+
});
|
|
76
59
|
|
|
77
|
-
return
|
|
60
|
+
return formattedParts.join('<br>');
|
|
78
61
|
}
|
|
79
62
|
}
|
|
80
63
|
|
|
@@ -54,35 +54,178 @@ describe('./src/dynamic-forms/components/checkbox/question.js', () => {
|
|
|
54
54
|
assert.deepStrictEqual(question.options, OPTIONS);
|
|
55
55
|
});
|
|
56
56
|
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
57
|
+
describe('formatAnswer', () => {
|
|
58
|
+
it('should delegate simple string answers to parent class', () => {
|
|
59
|
+
const question = new CheckboxQuestion(CHECKBOX_PARAMS);
|
|
60
|
+
// Simple string should use parent's formatAnswer
|
|
61
|
+
const result = question.formatAnswer('1,2');
|
|
62
|
+
assert.strictEqual(result, 'a<br>b');
|
|
63
|
+
});
|
|
63
64
|
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
65
|
+
it('should format single answer with conditional', () => {
|
|
66
|
+
const question = new CheckboxQuestion(CHECKBOX_PARAMS);
|
|
67
|
+
const answer = {
|
|
68
|
+
value: '3',
|
|
69
|
+
conditional: {
|
|
70
|
+
3: 'conditional details'
|
|
71
|
+
}
|
|
72
|
+
};
|
|
73
|
+
const result = question.formatAnswer(answer);
|
|
74
|
+
assert.strictEqual(result, 'c<br>conditional details');
|
|
75
|
+
});
|
|
70
76
|
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
+
it('should format single answer with conditional and label', () => {
|
|
78
|
+
const optionsWithLabel = [
|
|
79
|
+
{ text: 'Option A', value: 'a' },
|
|
80
|
+
{
|
|
81
|
+
text: 'Option B',
|
|
82
|
+
value: 'b',
|
|
83
|
+
conditional: {
|
|
84
|
+
fieldName: 'details',
|
|
85
|
+
label: 'Please provide details:'
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
];
|
|
89
|
+
const question = new CheckboxQuestion({
|
|
90
|
+
...CHECKBOX_PARAMS,
|
|
91
|
+
options: optionsWithLabel
|
|
92
|
+
});
|
|
93
|
+
const answer = {
|
|
94
|
+
value: 'b',
|
|
95
|
+
conditional: {
|
|
96
|
+
b: 'my details'
|
|
97
|
+
}
|
|
98
|
+
};
|
|
99
|
+
const result = question.formatAnswer(answer);
|
|
100
|
+
assert.strictEqual(result, 'Option B<br>Please provide details: my details');
|
|
101
|
+
});
|
|
77
102
|
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
103
|
+
it('should format multiple answers where one has conditional', () => {
|
|
104
|
+
const question = new CheckboxQuestion(CHECKBOX_PARAMS);
|
|
105
|
+
const answer = {
|
|
106
|
+
value: '1,3',
|
|
107
|
+
conditional: {
|
|
108
|
+
3: 'conditional for c'
|
|
109
|
+
}
|
|
110
|
+
};
|
|
111
|
+
const result = question.formatAnswer(answer);
|
|
112
|
+
assert.strictEqual(result, 'a<br>c<br>conditional for c');
|
|
113
|
+
});
|
|
114
|
+
|
|
115
|
+
it('should format multiple answers with multiple conditionals', () => {
|
|
116
|
+
const optionsWithMultipleConditionals = [
|
|
117
|
+
{
|
|
118
|
+
text: 'Option A',
|
|
119
|
+
value: 'a',
|
|
120
|
+
conditional: { fieldName: 'details-a' }
|
|
121
|
+
},
|
|
122
|
+
{ text: 'Option B', value: 'b' },
|
|
123
|
+
{
|
|
124
|
+
text: 'Option C',
|
|
125
|
+
value: 'c',
|
|
126
|
+
conditional: { fieldName: 'details-c' }
|
|
127
|
+
}
|
|
128
|
+
];
|
|
129
|
+
const question = new CheckboxQuestion({
|
|
130
|
+
...CHECKBOX_PARAMS,
|
|
131
|
+
options: optionsWithMultipleConditionals
|
|
132
|
+
});
|
|
133
|
+
const answer = {
|
|
134
|
+
value: 'a,c',
|
|
135
|
+
conditional: {
|
|
136
|
+
a: 'details for A',
|
|
137
|
+
c: 'details for C'
|
|
138
|
+
}
|
|
139
|
+
};
|
|
140
|
+
const result = question.formatAnswer(answer);
|
|
141
|
+
assert.strictEqual(result, 'Option A<br>details for A<br>Option C<br>details for C');
|
|
142
|
+
});
|
|
143
|
+
|
|
144
|
+
it('should handle object answer with empty conditional', () => {
|
|
145
|
+
const question = new CheckboxQuestion(CHECKBOX_PARAMS);
|
|
146
|
+
const answer = {
|
|
147
|
+
value: '1,2',
|
|
148
|
+
conditional: {}
|
|
149
|
+
};
|
|
150
|
+
const result = question.formatAnswer(answer);
|
|
151
|
+
assert.strictEqual(result, 'a<br>b');
|
|
152
|
+
});
|
|
153
|
+
|
|
154
|
+
it('should handle object answer with null conditional', () => {
|
|
155
|
+
const question = new CheckboxQuestion(CHECKBOX_PARAMS);
|
|
156
|
+
const answer = {
|
|
157
|
+
value: '1,2',
|
|
158
|
+
conditional: null
|
|
159
|
+
};
|
|
160
|
+
const result = question.formatAnswer(answer);
|
|
161
|
+
assert.strictEqual(result, 'a<br>b');
|
|
162
|
+
});
|
|
163
|
+
|
|
164
|
+
it('should fall back to value when option not found in object answer', () => {
|
|
165
|
+
const question = new CheckboxQuestion(CHECKBOX_PARAMS);
|
|
166
|
+
const answer = {
|
|
167
|
+
value: 'unknown',
|
|
168
|
+
conditional: {}
|
|
169
|
+
};
|
|
170
|
+
const result = question.formatAnswer(answer);
|
|
171
|
+
assert.strictEqual(result, 'unknown');
|
|
172
|
+
});
|
|
173
|
+
|
|
174
|
+
it('should escape HTML in option text', () => {
|
|
175
|
+
const question = new CheckboxQuestion({
|
|
176
|
+
...CHECKBOX_PARAMS,
|
|
177
|
+
options: [{ text: '<script>alert("xss")</script>', value: '1' }]
|
|
178
|
+
});
|
|
179
|
+
const result = question.formatAnswer('1');
|
|
180
|
+
assert.strictEqual(result, '<script>alert("xss")</script>');
|
|
181
|
+
});
|
|
182
|
+
|
|
183
|
+
it('should escape HTML in conditional value', () => {
|
|
184
|
+
const question = new CheckboxQuestion({
|
|
185
|
+
...CHECKBOX_PARAMS,
|
|
186
|
+
options: [
|
|
187
|
+
{
|
|
188
|
+
text: 'Option',
|
|
189
|
+
value: '1',
|
|
190
|
+
conditional: { fieldName: 'details', type: 'text' }
|
|
191
|
+
}
|
|
192
|
+
]
|
|
193
|
+
});
|
|
194
|
+
const answer = {
|
|
195
|
+
value: '1',
|
|
196
|
+
conditional: { 1: '<img src=x onerror=alert(1)>' }
|
|
197
|
+
};
|
|
198
|
+
const result = question.formatAnswer(answer);
|
|
199
|
+
assert.strictEqual(result, 'Option<br><img src=x onerror=alert(1)>');
|
|
200
|
+
});
|
|
201
|
+
|
|
202
|
+
it('should escape HTML in conditional label', () => {
|
|
203
|
+
const question = new CheckboxQuestion({
|
|
204
|
+
...CHECKBOX_PARAMS,
|
|
205
|
+
options: [
|
|
206
|
+
{
|
|
207
|
+
text: 'Option',
|
|
208
|
+
value: '1',
|
|
209
|
+
conditional: { fieldName: 'details', type: 'text', label: '<b>Label</b>' }
|
|
210
|
+
}
|
|
211
|
+
]
|
|
212
|
+
});
|
|
213
|
+
const answer = {
|
|
214
|
+
value: '1',
|
|
215
|
+
conditional: { 1: 'safe value' }
|
|
216
|
+
};
|
|
217
|
+
const result = question.formatAnswer(answer);
|
|
218
|
+
assert.strictEqual(result, 'Option<br><b>Label</b> safe value');
|
|
219
|
+
});
|
|
220
|
+
|
|
221
|
+
it('should escape HTML in fallback value when option not found', () => {
|
|
222
|
+
const question = new CheckboxQuestion(CHECKBOX_PARAMS);
|
|
223
|
+
const answer = {
|
|
224
|
+
value: '<script>xss</script>',
|
|
225
|
+
conditional: {}
|
|
226
|
+
};
|
|
227
|
+
const result = question.formatAnswer(answer);
|
|
228
|
+
assert.strictEqual(result, '<script>xss</script>');
|
|
229
|
+
});
|
|
87
230
|
});
|
|
88
231
|
});
|
|
@@ -3,9 +3,9 @@
|
|
|
3
3
|
*/
|
|
4
4
|
export class DateQuestion extends Question {
|
|
5
5
|
/**
|
|
6
|
-
* @param {import('
|
|
6
|
+
* @param {import('#typedefs/question-props.d.ts').DateQuestionParams} params
|
|
7
7
|
*/
|
|
8
|
-
constructor({ dateFormat, ...parentParams }: import("
|
|
8
|
+
constructor({ dateFormat, ...parentParams }: import("#typedefs/question-props.d.ts").DateQuestionParams);
|
|
9
9
|
dateFormat: string;
|
|
10
10
|
/**
|
|
11
11
|
* Get the data to save from the request, returns an object of answers
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"question.d.ts","sourceRoot":"","sources":["question.js"],"names":[],"mappings":"AAKA;;GAEG;AACH;IACC;;OAEG;IACH,6CAFW,OAAO,+BAA+B,EAAE,kBAAkB,EAQpE;IADA,mBAA4B;IAW7B;;;;;OAKG,CACH,mBAJW,OAAO,SAAS,EAAE,OAAO,mBACzB,OAAO,mBAAmB,EAAE,eAAe,GACzC,OAAO,CAAE,MAAM,CAAC,CAY5B;IAED;;MAwBC;CAWD;;yBAhFwB,6BAA6B"}
|