@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.
Files changed (264) hide show
  1. package/.gitattributes +3 -0
  2. package/.prettierrc +1 -0
  3. package/README.md +123 -4
  4. package/package.json +6 -4
  5. package/{types/src → src}/components/address/question.d.ts +6 -2
  6. package/src/components/address/question.d.ts.map +1 -0
  7. package/src/components/address/question.js +7 -19
  8. package/{types/src → src}/components/boolean/question.d.ts +2 -2
  9. package/src/components/boolean/question.d.ts.map +1 -0
  10. package/src/components/boolean/question.js +1 -1
  11. package/src/components/checkbox/question.d.ts +9 -0
  12. package/src/components/checkbox/question.d.ts.map +1 -0
  13. package/src/components/checkbox/question.js +37 -54
  14. package/src/components/checkbox/question.test.js +170 -27
  15. package/{types/src → src}/components/date/question.d.ts +2 -2
  16. package/src/components/date/question.d.ts.map +1 -0
  17. package/src/components/date/question.js +7 -16
  18. package/{types/src → src}/components/date-period/question.d.ts +12 -4
  19. package/src/components/date-period/question.d.ts.map +1 -0
  20. package/src/components/date-period/question.js +2 -3
  21. package/{types/src → src}/components/date-time/question.d.ts +2 -2
  22. package/src/components/date-time/question.d.ts.map +1 -0
  23. package/src/components/date-time/question.js +5 -14
  24. package/src/components/email/question.d.ts +23 -0
  25. package/src/components/email/question.d.ts.map +1 -0
  26. package/src/components/email/question.js +1 -9
  27. package/src/components/manage-list/answer-summary-list.njk +1 -1
  28. package/src/components/manage-list/manage-list-actions.d.ts.map +1 -0
  29. package/src/components/manage-list/manage-list-section.d.ts.map +1 -0
  30. package/{types/src → src}/components/manage-list/question.d.ts +6 -3
  31. package/src/components/manage-list/question.d.ts.map +1 -0
  32. package/src/components/manage-list/question.js +5 -2
  33. package/{types/src → src}/components/manage-list/utils.d.ts +3 -3
  34. package/src/components/manage-list/utils.d.ts.map +1 -0
  35. package/src/components/manage-list/utils.js +2 -2
  36. package/src/components/multi-field-input/question.d.ts +52 -0
  37. package/src/components/multi-field-input/question.d.ts.map +1 -0
  38. package/src/components/multi-field-input/question.js +5 -13
  39. package/src/components/number-entry/question.d.ts +12 -0
  40. package/src/components/number-entry/question.d.ts.map +1 -0
  41. package/src/components/number-entry/question.js +4 -22
  42. package/src/components/radio/question.d.ts +11 -0
  43. package/src/components/radio/question.d.ts.map +1 -0
  44. package/src/components/radio/question.js +30 -51
  45. package/src/components/radio/question.test.js +139 -60
  46. package/src/components/select/question.d.ts +12 -0
  47. package/src/components/select/question.d.ts.map +1 -0
  48. package/src/components/select/question.js +5 -48
  49. package/{types/src → src}/components/single-line-input/question.d.ts +2 -2
  50. package/src/components/single-line-input/question.d.ts.map +1 -0
  51. package/src/components/single-line-input/question.js +2 -2
  52. package/{types/src → src}/components/text-entry/question.d.ts +3 -3
  53. package/src/components/text-entry/question.d.ts.map +1 -0
  54. package/src/components/text-entry/question.js +2 -2
  55. package/{types/src → src}/components/text-entry-redact/question.d.ts +8 -9
  56. package/src/components/text-entry-redact/question.d.ts.map +1 -0
  57. package/src/components/text-entry-redact/question.js +5 -2
  58. package/{types/src → src}/components/unit-option-entry/question.d.ts +12 -8
  59. package/src/components/unit-option-entry/question.d.ts.map +1 -0
  60. package/src/components/unit-option-entry/question.js +5 -6
  61. package/src/components/utils/component-types.d.ts.map +1 -0
  62. package/src/components/utils/persisted-number-answer.d.ts.map +1 -0
  63. package/src/components/utils/question-has-answer.d.ts.map +1 -0
  64. package/src/components/utils/question-has-answer.js +1 -1
  65. package/src/components/utils/question-utils.d.ts +33 -0
  66. package/src/components/utils/question-utils.d.ts.map +1 -0
  67. package/src/components/utils/question-utils.js +31 -2
  68. package/src/components/utils/question-utils.test.js +89 -2
  69. package/{types/src → src}/controller.d.ts +2 -7
  70. package/src/controller.d.ts.map +1 -0
  71. package/src/controller.js +3 -10
  72. package/{types/src → src}/index.d.ts +4 -3
  73. package/src/index.d.ts.map +1 -0
  74. package/src/index.js +6 -4
  75. package/src/journey/journey-response.d.ts +28 -0
  76. package/src/journey/journey-response.d.ts.map +1 -0
  77. package/src/journey/journey-response.js +4 -8
  78. package/{types/src → src}/journey/journey.d.ts +8 -8
  79. package/src/journey/journey.d.ts.map +1 -0
  80. package/src/journey/journey.js +6 -6
  81. package/src/lib/address-utils.d.ts.map +1 -0
  82. package/src/lib/address.d.ts.map +1 -0
  83. package/src/lib/date-utils.d.ts.map +1 -0
  84. package/src/lib/session-answer-store.d.ts.map +1 -0
  85. package/src/lib/utils.d.ts.map +1 -0
  86. package/src/lib/uuid.d.ts.map +1 -0
  87. package/src/middleware/build-get-journey.d.ts.map +1 -0
  88. package/src/middleware/redirect-to-unanswered-question.d.ts +12 -0
  89. package/src/middleware/redirect-to-unanswered-question.d.ts.map +1 -0
  90. package/src/middleware/redirect-to-unanswered-question.js +1 -1
  91. package/src/questions/create-questions.d.ts +18 -0
  92. package/src/questions/create-questions.d.ts.map +1 -0
  93. package/src/questions/create-questions.js +3 -3
  94. package/{types/src → src}/questions/options-question.d.ts +7 -7
  95. package/src/questions/options-question.d.ts.map +1 -0
  96. package/src/questions/options-question.js +29 -4
  97. package/src/questions/options-question.test.js +106 -0
  98. package/{types/src → src}/questions/question.d.ts +48 -79
  99. package/src/questions/question.d.ts.map +1 -0
  100. package/src/questions/question.js +83 -60
  101. package/src/questions/question.test.js +86 -0
  102. package/src/questions/questions.d.ts +3 -0
  103. package/src/questions/questions.d.ts.map +1 -0
  104. package/src/questions/questions.js +1 -1
  105. package/{types/src → src}/section.d.ts +16 -15
  106. package/src/section.d.ts.map +1 -0
  107. package/src/section.js +12 -14
  108. package/src/validator/address-validator.d.ts.map +1 -0
  109. package/src/validator/base-validator.d.ts.map +1 -0
  110. package/src/validator/conditional-required-validator.d.ts.map +1 -0
  111. package/src/validator/confirmation-checkbox-validator.d.ts.map +1 -0
  112. package/src/validator/coordinates-validator.d.ts.map +1 -0
  113. package/src/validator/cross-question-validator.d.ts.map +1 -0
  114. package/src/validator/date-period-validator.d.ts.map +1 -0
  115. package/src/validator/date-time-validator.d.ts.map +1 -0
  116. package/src/validator/date-validator.d.ts.map +1 -0
  117. package/src/validator/document-upload-validator.d.ts.map +1 -0
  118. package/src/validator/email-validator.d.ts.map +1 -0
  119. package/{types/src → src}/validator/multi-field-input-validator.d.ts +3 -3
  120. package/src/validator/multi-field-input-validator.d.ts.map +1 -0
  121. package/src/validator/multi-field-input-validator.js +3 -3
  122. package/{types/src → src}/validator/numeric-validator.d.ts +2 -2
  123. package/src/validator/numeric-validator.d.ts.map +1 -0
  124. package/src/validator/numeric-validator.js +2 -2
  125. package/src/validator/required-validator.d.ts.map +1 -0
  126. package/src/validator/same-answer-validator.d.ts.map +1 -0
  127. package/{types/src → src}/validator/string-validator.d.ts +3 -3
  128. package/src/validator/string-validator.d.ts.map +1 -0
  129. package/src/validator/string-validator.js +3 -3
  130. package/src/validator/unit-option-entry-validator.d.ts.map +1 -0
  131. package/src/validator/valid-option-validator.d.ts.map +1 -0
  132. package/src/validator/validation-error-handler.d.ts.map +1 -0
  133. package/src/validator/validator.d.ts.map +1 -0
  134. package/test/journey.js +2 -0
  135. package/test/questions.js +51 -1
  136. package/test/secondary-actions.test.js +1 -1
  137. package/test/snapshots/check-your-answers-not-started.html +22 -0
  138. package/test/snapshots/check-your-answers-with-answers.html +22 -0
  139. package/test/snapshots/favourite-activity-reason.html +54 -0
  140. package/test/snapshots/travel-requirements.html +48 -0
  141. package/test/utils/question-test-utils.js +36 -6
  142. package/tsconfig.json +11 -4
  143. package/{types/src/questions → typedefs}/question-props.d.ts +28 -26
  144. package/typedefs/question-types.d.ts +141 -0
  145. package/{src → typedefs}/section-types.d.ts +1 -1
  146. package/typedefs/section-types.js +1 -0
  147. package/move-types.js +0 -12
  148. package/src/questions/question-props.d.ts +0 -295
  149. package/src/questions/question-types.d.ts +0 -86
  150. package/types/commitlint.config.d.ts +0 -7
  151. package/types/commitlint.config.d.ts.map +0 -1
  152. package/types/eslint.config.d.ts +0 -97
  153. package/types/eslint.config.d.ts.map +0 -1
  154. package/types/move-types.d.ts +0 -2
  155. package/types/move-types.d.ts.map +0 -1
  156. package/types/src/components/address/question.d.ts.map +0 -1
  157. package/types/src/components/boolean/question.d.ts.map +0 -1
  158. package/types/src/components/checkbox/question.d.ts +0 -33
  159. package/types/src/components/checkbox/question.d.ts.map +0 -1
  160. package/types/src/components/date/question.d.ts.map +0 -1
  161. package/types/src/components/date-period/question.d.ts.map +0 -1
  162. package/types/src/components/date-time/question.d.ts.map +0 -1
  163. package/types/src/components/email/question.d.ts +0 -35
  164. package/types/src/components/email/question.d.ts.map +0 -1
  165. package/types/src/components/manage-list/manage-list-actions.d.ts.map +0 -1
  166. package/types/src/components/manage-list/manage-list-section.d.ts.map +0 -1
  167. package/types/src/components/manage-list/question.d.ts.map +0 -1
  168. package/types/src/components/manage-list/utils.d.ts.map +0 -1
  169. package/types/src/components/multi-field-input/question.d.ts +0 -64
  170. package/types/src/components/multi-field-input/question.d.ts.map +0 -1
  171. package/types/src/components/number-entry/question.d.ts +0 -36
  172. package/types/src/components/number-entry/question.d.ts.map +0 -1
  173. package/types/src/components/radio/question.d.ts +0 -23
  174. package/types/src/components/radio/question.d.ts.map +0 -1
  175. package/types/src/components/select/question.d.ts +0 -22
  176. package/types/src/components/select/question.d.ts.map +0 -1
  177. package/types/src/components/single-line-input/question.d.ts.map +0 -1
  178. package/types/src/components/text-entry/question.d.ts.map +0 -1
  179. package/types/src/components/text-entry-redact/question.d.ts.map +0 -1
  180. package/types/src/components/unit-option-entry/question.d.ts.map +0 -1
  181. package/types/src/components/utils/component-types.d.ts.map +0 -1
  182. package/types/src/components/utils/persisted-number-answer.d.ts.map +0 -1
  183. package/types/src/components/utils/question-has-answer.d.ts.map +0 -1
  184. package/types/src/components/utils/question-utils.d.ts +0 -15
  185. package/types/src/components/utils/question-utils.d.ts.map +0 -1
  186. package/types/src/controller.d.ts.map +0 -1
  187. package/types/src/index.d.ts.map +0 -1
  188. package/types/src/journey/journey-response.d.ts +0 -32
  189. package/types/src/journey/journey-response.d.ts.map +0 -1
  190. package/types/src/journey/journey-types.d.ts +0 -28
  191. package/types/src/journey/journey-types.d.ts.map +0 -1
  192. package/types/src/journey/journey.d.ts.map +0 -1
  193. package/types/src/lib/address-utils.d.ts.map +0 -1
  194. package/types/src/lib/address.d.ts.map +0 -1
  195. package/types/src/lib/date-utils.d.ts.map +0 -1
  196. package/types/src/lib/session-answer-store.d.ts.map +0 -1
  197. package/types/src/lib/utils.d.ts.map +0 -1
  198. package/types/src/lib/uuid.d.ts.map +0 -1
  199. package/types/src/middleware/build-get-journey.d.ts.map +0 -1
  200. package/types/src/middleware/redirect-to-unanswered-question.d.ts +0 -12
  201. package/types/src/middleware/redirect-to-unanswered-question.d.ts.map +0 -1
  202. package/types/src/questions/create-questions.d.ts +0 -18
  203. package/types/src/questions/create-questions.d.ts.map +0 -1
  204. package/types/src/questions/options-question.d.ts.map +0 -1
  205. package/types/src/questions/question-props.d.ts.map +0 -1
  206. package/types/src/questions/question-types.d.ts +0 -86
  207. package/types/src/questions/question-types.d.ts.map +0 -1
  208. package/types/src/questions/question.d.ts.map +0 -1
  209. package/types/src/questions/questions.d.ts +0 -3
  210. package/types/src/questions/questions.d.ts.map +0 -1
  211. package/types/src/section.d.ts.map +0 -1
  212. package/types/src/validator/address-validator.d.ts.map +0 -1
  213. package/types/src/validator/base-validator.d.ts.map +0 -1
  214. package/types/src/validator/conditional-required-validator.d.ts.map +0 -1
  215. package/types/src/validator/confirmation-checkbox-validator.d.ts.map +0 -1
  216. package/types/src/validator/coordinates-validator.d.ts.map +0 -1
  217. package/types/src/validator/cross-question-validator.d.ts.map +0 -1
  218. package/types/src/validator/date-period-validator.d.ts.map +0 -1
  219. package/types/src/validator/date-time-validator.d.ts.map +0 -1
  220. package/types/src/validator/date-validator.d.ts.map +0 -1
  221. package/types/src/validator/document-upload-validator.d.ts.map +0 -1
  222. package/types/src/validator/email-validator.d.ts.map +0 -1
  223. package/types/src/validator/multi-field-input-validator.d.ts.map +0 -1
  224. package/types/src/validator/numeric-validator.d.ts.map +0 -1
  225. package/types/src/validator/required-validator.d.ts.map +0 -1
  226. package/types/src/validator/same-answer-validator.d.ts.map +0 -1
  227. package/types/src/validator/string-validator.d.ts.map +0 -1
  228. package/types/src/validator/unit-option-entry-validator.d.ts.map +0 -1
  229. package/types/src/validator/valid-option-validator.d.ts.map +0 -1
  230. package/types/src/validator/validation-error-handler.d.ts.map +0 -1
  231. package/types/src/validator/validator.d.ts.map +0 -1
  232. /package/{types/src → src}/components/manage-list/manage-list-actions.d.ts +0 -0
  233. /package/{types/src → src}/components/manage-list/manage-list-section.d.ts +0 -0
  234. /package/{types/src → src}/components/utils/component-types.d.ts +0 -0
  235. /package/{types/src → src}/components/utils/persisted-number-answer.d.ts +0 -0
  236. /package/{types/src → src}/components/utils/question-has-answer.d.ts +0 -0
  237. /package/{types/src → src}/lib/address-utils.d.ts +0 -0
  238. /package/{types/src → src}/lib/address.d.ts +0 -0
  239. /package/{types/src → src}/lib/date-utils.d.ts +0 -0
  240. /package/{types/src → src}/lib/session-answer-store.d.ts +0 -0
  241. /package/{types/src → src}/lib/utils.d.ts +0 -0
  242. /package/{types/src → src}/lib/uuid.d.ts +0 -0
  243. /package/{types/src → src}/middleware/build-get-journey.d.ts +0 -0
  244. /package/{types/src → src}/validator/address-validator.d.ts +0 -0
  245. /package/{types/src → src}/validator/base-validator.d.ts +0 -0
  246. /package/{types/src → src}/validator/conditional-required-validator.d.ts +0 -0
  247. /package/{types/src → src}/validator/confirmation-checkbox-validator.d.ts +0 -0
  248. /package/{types/src → src}/validator/coordinates-validator.d.ts +0 -0
  249. /package/{types/src → src}/validator/cross-question-validator.d.ts +0 -0
  250. /package/{types/src → src}/validator/date-period-validator.d.ts +0 -0
  251. /package/{types/src → src}/validator/date-time-validator.d.ts +0 -0
  252. /package/{types/src → src}/validator/date-validator.d.ts +0 -0
  253. /package/{types/src → src}/validator/document-upload-validator.d.ts +0 -0
  254. /package/{types/src → src}/validator/email-validator.d.ts +0 -0
  255. /package/{types/src → src}/validator/required-validator.d.ts +0 -0
  256. /package/{types/src → src}/validator/same-answer-validator.d.ts +0 -0
  257. /package/{types/src → src}/validator/unit-option-entry-validator.d.ts +0 -0
  258. /package/{types/src → src}/validator/valid-option-validator.d.ts +0 -0
  259. /package/{types/src → src}/validator/validation-error-handler.d.ts +0 -0
  260. /package/{types/src → src}/validator/validator.d.ts +0 -0
  261. /package/{src/journey → typedefs}/journey-types.d.ts +0 -0
  262. /package/{src/journey → typedefs}/journey-types.js +0 -0
  263. /package/{src/questions → typedefs}/question-props.js +0 -0
  264. /package/{src/questions → typedefs}/question-types.js +0 -0
@@ -303,4 +303,110 @@ describe('./src/dynamic-forms/question.js', () => {
303
303
  );
304
304
  });
305
305
  });
306
+
307
+ describe('formatAnswer', () => {
308
+ it('should return not started text for null answer', () => {
309
+ const question = getTestQuestion({
310
+ options: [{ text: 'Option A', value: 'a' }]
311
+ });
312
+ const result = question.formatAnswer(null);
313
+ assert.strictEqual(result, 'Not started');
314
+ });
315
+
316
+ it('should return not started text for undefined answer', () => {
317
+ const question = getTestQuestion({
318
+ options: [{ text: 'Option A', value: 'a' }]
319
+ });
320
+ const result = question.formatAnswer(undefined);
321
+ assert.strictEqual(result, 'Not started');
322
+ });
323
+
324
+ it('should return not started text for empty string answer', () => {
325
+ const question = getTestQuestion({
326
+ options: [{ text: 'Option A', value: 'a' }]
327
+ });
328
+ const result = question.formatAnswer('');
329
+ assert.strictEqual(result, 'Not started');
330
+ });
331
+
332
+ it('should format a single value using option text', () => {
333
+ const question = getTestQuestion({
334
+ options: [
335
+ { text: 'Option A', value: 'a' },
336
+ { text: 'Option B', value: 'b' }
337
+ ]
338
+ });
339
+ const result = question.formatAnswer('a');
340
+ assert.strictEqual(result, 'Option A');
341
+ });
342
+
343
+ it('should format multiple values joined with line breaks', () => {
344
+ const question = getTestQuestion({
345
+ options: [
346
+ { text: 'Option A', value: 'a' },
347
+ { text: 'Option B', value: 'b' },
348
+ { text: 'Option C', value: 'c' }
349
+ ]
350
+ });
351
+ const result = question.formatAnswer('a,b');
352
+ assert.strictEqual(result, 'Option A<br>Option B');
353
+ });
354
+
355
+ it('should fall back to value when option not found', () => {
356
+ const question = getTestQuestion({
357
+ options: [{ text: 'Option A', value: 'a' }]
358
+ });
359
+ const result = question.formatAnswer('unknown');
360
+ assert.strictEqual(result, 'unknown');
361
+ });
362
+
363
+ it('should handle mixed known and unknown values', () => {
364
+ const question = getTestQuestion({
365
+ options: [
366
+ { text: 'Option A', value: 'a' },
367
+ { text: 'Option B', value: 'b' }
368
+ ]
369
+ });
370
+ const result = question.formatAnswer('a,unknown,b');
371
+ assert.strictEqual(result, 'Option A<br>unknown<br>Option B');
372
+ });
373
+
374
+ it('should trim whitespace from values', () => {
375
+ const question = getTestQuestion({
376
+ options: [
377
+ { text: 'Option A', value: 'a' },
378
+ { text: 'Option B', value: 'b' }
379
+ ]
380
+ });
381
+ const result = question.formatAnswer('a , b');
382
+ assert.strictEqual(result, 'Option A<br>Option B');
383
+ });
384
+
385
+ it('should escape HTML in option text', () => {
386
+ const question = getTestQuestion({
387
+ options: [{ text: '<script>alert("xss")</script>', value: 'a' }]
388
+ });
389
+ const result = question.formatAnswer('a');
390
+ assert.strictEqual(result, '&lt;script&gt;alert(&quot;xss&quot;)&lt;/script&gt;');
391
+ });
392
+
393
+ it('should escape HTML in fallback value when option not found', () => {
394
+ const question = getTestQuestion({
395
+ options: [{ text: 'Option A', value: 'a' }]
396
+ });
397
+ const result = question.formatAnswer('<img src=x onerror=alert(1)>');
398
+ assert.strictEqual(result, '&lt;img src=x onerror=alert(1)&gt;');
399
+ });
400
+
401
+ it('should escape HTML in multiple option texts', () => {
402
+ const question = getTestQuestion({
403
+ options: [
404
+ { text: '<b>Bold</b>', value: 'a' },
405
+ { text: '<i>Italic</i>', value: 'b' }
406
+ ]
407
+ });
408
+ const result = question.formatAnswer('a,b');
409
+ assert.strictEqual(result, '&lt;b&gt;Bold&lt;/b&gt;<br>&lt;i&gt;Italic&lt;/i&gt;');
410
+ });
411
+ });
306
412
  });
@@ -1,39 +1,13 @@
1
- /**
2
- * @import {BaseValidator} from "#base-validator"
3
- * @import {ActionView} from "../controller.js"
4
- * @import {ActionLink} from "./question-types.js"
5
- */
6
- /**
7
- * @typedef {Object} PreppedQuestion
8
- * @property {Object} value
9
- * @property {string} question
10
- * @property {string} fieldName
11
- * @property {string} pageTitle
12
- * @property {string} [description]
13
- * @property {string} [html]
14
- */
15
- /**
16
- * @typedef {Record<string, any>} QuestionViewModel
17
- * @property {PreppedQuestion} question
18
- * @property {string} layoutTemplate
19
- * @property {string} pageCaption
20
- * @property {string} [continueButtonText]
21
- * @property {string} backLink
22
- * @property {boolean} showBackToListLink
23
- * @property {string} listLink
24
- * @property {Object} util
25
- * @property {function(string): string} util.trimTrailingSlash
26
- */
27
1
  /**
28
2
  * A specific question within a journey which is made up of one (usually) or many (sometimes) components and their required content.
29
3
  * @class
30
4
  */
31
5
  export class Question {
32
6
  /**
33
- * @param {import('./question-types.js').QuestionParameters} params
7
+ * @param {import('#typedefs/question-types.d.ts').QuestionParameters} params
34
8
  * @param {Record<string, Function>} [methodOverrides]
35
9
  */
36
- constructor({ title, question, viewFolder, fieldName, url, pageTitle, description, validators, html, hint, interfaceType, shouldDisplay, autocomplete, editable, actionLink, viewData }: import("./question-types.js").QuestionParameters, methodOverrides?: Record<string, Function>);
10
+ constructor({ title, question, viewFolder, fieldName, url, pageTitle, description, validators, html, hint, interfaceType, shouldDisplay, autocomplete, editable, actionLink, viewData, formatSummaryValue, capitaliseAnswer }: import("#typedefs/question-types.d.ts").QuestionParameters, methodOverrides?: Record<string, Function>);
37
11
  /** @type {string} - html page title, defaults to question if not provided */
38
12
  pageTitle: string;
39
13
  /** @type {string} - title used in the summary list */
@@ -48,8 +22,8 @@ export class Question {
48
22
  fieldName: string;
49
23
  /** @type {boolean} if the question should appear in the journey overview task list or not */
50
24
  taskList: boolean;
51
- /** @type {Array.<BaseValidator>} array of validators that a question uses to validate answers */
52
- validators: Array<BaseValidator>;
25
+ /** @type {Array.<import('../validator/base-validator.js').BaseValidator>} array of validators that a question uses to validate answers */
26
+ validators: Array<import("../validator/base-validator.js").BaseValidator>;
53
27
  /** @type {string|undefined} hint text displayed to user */
54
28
  hint: string | undefined;
55
29
  /** @type {boolean} show return to listing page link after question */
@@ -60,10 +34,12 @@ export class Question {
60
34
  html: string | undefined;
61
35
  /** @type {string|undefined} optional question type */
62
36
  interfaceType: string | undefined;
63
- /** @type {ActionLink|undefined} override action link */
64
- actionLink: ActionLink | undefined;
37
+ /** @type {import('#typedefs/question-types.d.ts').ActionLink|undefined} override action link */
38
+ actionLink: import("#typedefs/question-types.d.ts").ActionLink | undefined;
65
39
  /** @type {string} 'not started' text to display (if a question has no answer) */
66
40
  notStartedText: string;
41
+ /** @type {boolean} whether to capitalize the first letter of the answer in summary */
42
+ capitaliseAnswer: boolean;
67
43
  /** @type {string} button text to display */
68
44
  continueButtonText: string;
69
45
  /** @type {string} text to display for 'change' link */
@@ -72,11 +48,13 @@ export class Question {
72
48
  answerActionText: string;
73
49
  /** @type {string} text to display for 'add' link */
74
50
  addActionText: string;
51
+ /** @type {import('#typedefs/question-types.d.ts').SummaryValueFormatter|undefined} custom function to format the summary display value */
52
+ formatSummaryValue: import("#typedefs/question-types.d.ts").SummaryValueFormatter | undefined;
75
53
  /**
76
54
  * @param {import('../journey/journey-response.js').JourneyResponse} [response]
77
55
  * @returns {boolean}
78
56
  */
79
- shouldDisplay: () => boolean;
57
+ shouldDisplay: (response?: import("../journey/journey-response.js").JourneyResponse) => boolean;
80
58
  details: {
81
59
  title: string;
82
60
  text: string;
@@ -93,8 +71,8 @@ export class Question {
93
71
  editable: boolean;
94
72
  viewData: {
95
73
  [key: string]: any;
96
- extraActionButtons?: import("./question-types.js").SecondaryAction[];
97
- secondaryActions?: import("./question-types.js").SecondaryAction[];
74
+ extraActionButtons?: import("#typedefs/question-types.d.ts").SecondaryAction[];
75
+ secondaryActions?: import("#typedefs/question-types.d.ts").SecondaryAction[];
98
76
  };
99
77
  /**
100
78
  * Is this question a manage list question?
@@ -126,22 +104,22 @@ export class Question {
126
104
  * that prepQuestionForRendering doesn't need
127
105
  *
128
106
  * @param {Object} options
129
- * @param {import('../journey/journey-types.js').RouteParams} options.params
107
+ * @param {import('#typedefs/journey-types.d.ts').RouteParams} options.params
130
108
  * @param {import('../components/manage-list/question.js')} [options.manageListQuestion]
131
109
  * @param {import('../section.js').Section} options.section - the current section
132
110
  * @param {import('../journey/journey.js').Journey} options.journey - the journey we are in
133
111
  * @param {Record<string, unknown>} [options.customViewData] additional data to send to view
134
112
  * @param {unknown} [options.payload]
135
- * @returns {QuestionViewModel}
113
+ * @returns {import('#typedefs/question-types.d.ts').QuestionViewModel}
136
114
  */
137
115
  toViewModel({ params, manageListQuestion, section, journey, customViewData, payload }: {
138
- params: import("../journey/journey-types.js").RouteParams;
116
+ params: import("#typedefs/journey-types.d.ts").RouteParams;
139
117
  manageListQuestion?: typeof import("../components/manage-list/question.js") | undefined;
140
118
  section: import("../section.js").Section;
141
119
  journey: import("../journey/journey.js").Journey;
142
120
  customViewData?: Record<string, unknown> | undefined;
143
121
  payload?: unknown;
144
- }): QuestionViewModel;
122
+ }): import("#typedefs/question-types.d.ts").QuestionViewModel;
145
123
  /**
146
124
  * gets the base view model for this question
147
125
  *
@@ -149,10 +127,10 @@ export class Question {
149
127
  * @param {import('../journey/journey.js').Journey} journey - the journey we are in
150
128
  * @param {Record<string, unknown>} [customViewData] additional data to send to view
151
129
  * @param {unknown} [payload]
152
- * @param {import('./question-types.js').PrepQuestionForRenderingOptions} [options] - required to support manage list question
153
- * @returns {QuestionViewModel}
130
+ * @param {import('#typedefs/question-types.d.ts').PrepQuestionForRenderingOptions} [options] - required to support manage list question
131
+ * @returns {import('#typedefs/question-types.d.ts').QuestionViewModel}
154
132
  */
155
- prepQuestionForRendering(section: import("../section.js").Section, journey: import("../journey/journey.js").Journey, customViewData?: Record<string, unknown>, payload?: unknown, options?: import("./question-types.js").PrepQuestionForRenderingOptions): QuestionViewModel;
133
+ prepQuestionForRendering(section: import("../section.js").Section, journey: import("../journey/journey.js").Journey, customViewData?: Record<string, unknown>, payload?: unknown, options?: import("#typedefs/question-types.d.ts").PrepQuestionForRenderingOptions): import("#typedefs/question-types.d.ts").QuestionViewModel;
156
134
  /**
157
135
  * The answer to this question for use in the viewModel
158
136
  *
@@ -167,32 +145,32 @@ export class Question {
167
145
  *
168
146
  * If possible override this method instead of prepQuestionForRendering for simple changes to the view model
169
147
  *
170
- * @param {QuestionViewModel} viewModel
171
- */ addCustomDataToViewModel(viewModel: QuestionViewModel): void;
148
+ * @param {import('#typedefs/question-types.d.ts').QuestionViewModel} viewModel
149
+ */ addCustomDataToViewModel(viewModel: import("#typedefs/question-types.d.ts").QuestionViewModel): void;
172
150
  /**
173
151
  * Get the answers object from the journey response, which may be nested in an array for manage list questions
174
152
  *
175
153
  * @param {import('../journey/journey-response.js').JourneyResponse} response
176
- * @param {import('./question-types.js').PrepQuestionForRenderingOptions} [options]
154
+ * @param {import('#typedefs/question-types.d.ts').PrepQuestionForRenderingOptions} [options]
177
155
  * @returns {Record<string, any>}
178
156
  */
179
- answerObjectFromJourneyResponse(response: import("../journey/journey-response.js").JourneyResponse, { params, manageListQuestion }?: import("./question-types.js").PrepQuestionForRenderingOptions): Record<string, any>;
157
+ answerObjectFromJourneyResponse(response: import("../journey/journey-response.js").JourneyResponse, { params, manageListQuestion }?: import("#typedefs/question-types.d.ts").PrepQuestionForRenderingOptions): Record<string, any>;
180
158
  /**
181
159
  * renders the question
182
160
  * @param {import('express').Response} res - the express response
183
- * @param {QuestionViewModel} viewModel additional data to send to view
161
+ * @param {import('#typedefs/question-types.d.ts').QuestionViewModel} viewModel additional data to send to view
184
162
  * @returns {void}
185
163
  */
186
- renderAction(res: import("express").Response, viewModel: QuestionViewModel): void;
164
+ renderAction(res: import("express").Response, viewModel: import("#typedefs/question-types.d.ts").QuestionViewModel): void;
187
165
  /**
188
166
  * check for validation errors
189
167
  * @param {import('express').Request} req
190
168
  * @param {import('../journey/journey.js').Journey} journey
191
169
  * @param {import('../section.js').Section} section
192
170
  * @param {import('../components/manage-list/question.js')} [manageListQuestion]
193
- * @returns {QuestionViewModel|undefined} returns the view model for displaying the error or undefined if there are no errors
171
+ * @returns {import('#typedefs/question-types.d.ts').QuestionViewModel|undefined} returns the view model for displaying the error or undefined if there are no errors
194
172
  */
195
- checkForValidationErrors(req: import("express").Request, section: import("../section.js").Section, journey: import("../journey/journey.js").Journey, manageListQuestion?: typeof import("../components/manage-list/question.js")): QuestionViewModel | undefined;
173
+ checkForValidationErrors(req: import("express").Request, section: import("../section.js").Section, journey: import("../journey/journey.js").Journey, manageListQuestion?: typeof import("../components/manage-list/question.js")): import("#typedefs/question-types.d.ts").QuestionViewModel | undefined;
196
174
  /**
197
175
  * Get the data to save from the request, returns an object of answers
198
176
  *
@@ -207,8 +185,8 @@ export class Question {
207
185
  * @param {import('express').Request} req
208
186
  * @param {import('../journey/journey.js').Journey} journey
209
187
  * @param {import('../section.js').Section} sectionObj
210
- * @returns {QuestionViewModel | undefined} returns the view model for displaying the error or undefined if there are no errors
211
- */ checkForSavingErrors(req: import("express").Request, sectionObj: import("../section.js").Section, journey: import("../journey/journey.js").Journey): QuestionViewModel | undefined;
188
+ * @returns {import('#typedefs/question-types.d.ts').QuestionViewModel | undefined} returns the view model for displaying the error or undefined if there are no errors
189
+ */ checkForSavingErrors(req: import("express").Request, sectionObj: import("../section.js").Section, journey: import("../journey/journey.js").Journey): import("#typedefs/question-types.d.ts").QuestionViewModel | undefined;
212
190
  /**
213
191
  * Handles redirect after saving. Kept around for backwards compatibility.
214
192
  *
@@ -222,33 +200,35 @@ export class Question {
222
200
  handleNextQuestion(res: import("express").Response, journey: import("../journey/journey.js").Journey, sectionSegment: string, questionSegment: string): void;
223
201
  /**
224
202
  * returns the formatted answers values to be used to build task list elements
225
- * @param {String} sectionSegment
203
+ * @param {string} sectionSegment
226
204
  * @param {import('../journey/journey.js').Journey} journey
227
205
  * @param {unknown} answer
228
- * @returns {Array<{
229
- * key: string;
230
- * value: string | Object;
231
- * action?: ActionView | ActionView[];
232
- * }>}
206
+ * @param {boolean} [capitals] - deprecated: use capitaliseAnswer property instead
207
+ * @returns {import('#typedefs/question-types.d.ts').SummaryRow[]}
233
208
  */
234
- formatAnswerForSummary(sectionSegment: string, journey: import("../journey/journey.js").Journey, answer: unknown, capitals?: boolean): Array<{
235
- key: string;
236
- value: string | Object;
237
- action?: ActionView | ActionView[];
238
- }>;
209
+ formatAnswerForSummary(sectionSegment: string, journey: import("../journey/journey.js").Journey, answer: unknown, capitals?: boolean): import("#typedefs/question-types.d.ts").SummaryRow[];
239
210
  /**
240
211
  * Returns the action link for the question
241
- * @param {unknown} answer
212
+ * @param {string} sectionSegment
242
213
  * @param {import('../journey/journey.js').Journey} journey
243
- * @param {String} sectionSegment
244
- * @returns {ActionView|ActionView[]|undefined}
214
+ * @param {unknown} answer
215
+ * @returns {import('#typedefs/question-types.d.ts').ActionView | import('#typedefs/question-types.d.ts').ActionView[] | undefined}
245
216
  */
246
- getAction(sectionSegment: string, journey: import("../journey/journey.js").Journey, answer: unknown): ActionView | ActionView[] | undefined;
217
+ getAction(sectionSegment: string, journey: import("../journey/journey.js").Journey, answer: unknown): import("#typedefs/question-types.d.ts").ActionView | import("#typedefs/question-types.d.ts").ActionView[] | undefined;
247
218
  /**
248
219
  * @param {unknown} answer
249
220
  * @returns {unknown}
250
221
  */
251
222
  format(answer: unknown): unknown;
223
+ /**
224
+ * Formats an answer value for display in the summary.
225
+ * Subclasses can override this to provide custom formatting without
226
+ * needing to override the full formatAnswerForSummary method.
227
+ *
228
+ * @param {unknown} answer - the raw answer value
229
+ * @returns {string} the formatted answer for display
230
+ */
231
+ formatAnswer(answer: unknown): string;
252
232
  /**
253
233
  * @returns {boolean}
254
234
  */
@@ -264,17 +244,6 @@ export class Question {
264
244
  * @returns {boolean}
265
245
  */
266
246
  isAnswered(journeyResponse: import("../journey/journey-response.js").JourneyResponse, fieldName?: string): boolean;
247
+ #private;
267
248
  }
268
- export type PreppedQuestion = {
269
- value: Object;
270
- question: string;
271
- fieldName: string;
272
- pageTitle: string;
273
- description?: string | undefined;
274
- html?: string | undefined;
275
- };
276
- export type QuestionViewModel = Record<string, any>;
277
- import type { BaseValidator } from "#base-validator";
278
- import type { ActionLink } from "./question-types.js";
279
- import type { ActionView } from "../controller.js";
280
249
  //# sourceMappingURL=question.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"question.d.ts","sourceRoot":"","sources":["question.js"],"names":[],"mappings":"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"}
@@ -3,35 +3,6 @@ import { capitalize, nl2br, trimTrailingSlash } from '../lib/utils.js';
3
3
  import MultiFieldInputValidator from '../validator/multi-field-input-validator.js';
4
4
  import { answerObjectForManageList } from '#src/components/manage-list/utils.js';
5
5
 
6
- /**
7
- * @import {BaseValidator} from "#base-validator"
8
- * @import {ActionView} from "../controller.js"
9
- * @import {ActionLink} from "./question-types.js"
10
- */
11
-
12
- /**
13
- * @typedef {Object} PreppedQuestion
14
- * @property {Object} value
15
- * @property {string} question
16
- * @property {string} fieldName
17
- * @property {string} pageTitle
18
- * @property {string} [description]
19
- * @property {string} [html]
20
- */
21
-
22
- /**
23
- * @typedef {Record<string, any>} QuestionViewModel
24
- * @property {PreppedQuestion} question
25
- * @property {string} layoutTemplate
26
- * @property {string} pageCaption
27
- * @property {string} [continueButtonText]
28
- * @property {string} backLink
29
- * @property {boolean} showBackToListLink
30
- * @property {string} listLink
31
- * @property {Object} util
32
- * @property {function(string): string} util.trimTrailingSlash
33
- */
34
-
35
6
  /**
36
7
  * A specific question within a journey which is made up of one (usually) or many (sometimes) components and their required content.
37
8
  * @class
@@ -51,7 +22,7 @@ export class Question {
51
22
  fieldName;
52
23
  /** @type {boolean} if the question should appear in the journey overview task list or not */
53
24
  taskList = true;
54
- /** @type {Array.<BaseValidator>} array of validators that a question uses to validate answers */
25
+ /** @type {Array.<import('../validator/base-validator.js').BaseValidator>} array of validators that a question uses to validate answers */
55
26
  validators = [];
56
27
  /** @type {string|undefined} hint text displayed to user */
57
28
  hint;
@@ -63,11 +34,13 @@ export class Question {
63
34
  html;
64
35
  /** @type {string|undefined} optional question type */
65
36
  interfaceType;
66
- /** @type {ActionLink|undefined} override action link */
37
+ /** @type {import('#typedefs/question-types.d.ts').ActionLink|undefined} override action link */
67
38
  actionLink;
68
39
 
69
40
  /** @type {string} 'not started' text to display (if a question has no answer) */
70
41
  notStartedText = 'Not started';
42
+ /** @type {boolean} whether to capitalize the first letter of the answer in summary */
43
+ capitaliseAnswer = true;
71
44
  /** @type {string} button text to display */
72
45
  continueButtonText = 'Continue';
73
46
  /** @type {string} text to display for 'change' link */
@@ -76,12 +49,15 @@ export class Question {
76
49
  answerActionText = 'Answer';
77
50
  /** @type {string} text to display for 'add' link */
78
51
  addActionText = 'Add';
52
+ /** @type {import('#typedefs/question-types.d.ts').SummaryValueFormatter|undefined} custom function to format the summary display value */
53
+ formatSummaryValue;
79
54
 
80
55
  /**
81
56
  * @param {import('../journey/journey-response.js').JourneyResponse} [response]
82
57
  * @returns {boolean}
83
58
  */
84
- shouldDisplay = () => true;
59
+ // eslint-disable-next-line no-unused-vars -- response will be used by extending classes
60
+ shouldDisplay = (response) => true;
85
61
 
86
62
  details = {
87
63
  title: '',
@@ -97,7 +73,7 @@ export class Question {
97
73
  _isInManageListSection = false;
98
74
 
99
75
  /**
100
- * @param {import('./question-types.js').QuestionParameters} params
76
+ * @param {import('#typedefs/question-types.d.ts').QuestionParameters} params
101
77
  * @param {Record<string, Function>} [methodOverrides]
102
78
  */
103
79
  constructor(
@@ -117,7 +93,9 @@ export class Question {
117
93
  autocomplete,
118
94
  editable = true,
119
95
  actionLink,
120
- viewData = {}
96
+ viewData = {},
97
+ formatSummaryValue,
98
+ capitaliseAnswer = true
121
99
  },
122
100
  methodOverrides
123
101
  ) {
@@ -139,6 +117,8 @@ export class Question {
139
117
  this.editable = editable;
140
118
  this.actionLink = actionLink;
141
119
  this.viewData = viewData;
120
+ this.formatSummaryValue = formatSummaryValue;
121
+ this.capitaliseAnswer = capitaliseAnswer;
142
122
 
143
123
  if (shouldDisplay) {
144
124
  this.shouldDisplay = shouldDisplay;
@@ -181,6 +161,18 @@ export class Question {
181
161
  this._isInManageListSection = value;
182
162
  }
183
163
 
164
+ /**
165
+ * Applies custom summary formatting if a formatSummaryValue function is provided
166
+ * @param {import('#typedefs/question-types.d.ts').SummaryFormatterContext} context - the context for formatting
167
+ * @returns {string}
168
+ */
169
+ #applyCustomSummaryFormatter(context) {
170
+ if (this.formatSummaryValue) {
171
+ return this.formatSummaryValue(context);
172
+ }
173
+ return context.formattedAnswer;
174
+ }
175
+
184
176
  /**
185
177
  * Gets the body field names used by this question in form submissions.
186
178
  * Returns the field names that should be present in req.body when this question is submitted.
@@ -199,13 +191,13 @@ export class Question {
199
191
  * that prepQuestionForRendering doesn't need
200
192
  *
201
193
  * @param {Object} options
202
- * @param {import('../journey/journey-types.js').RouteParams} options.params
194
+ * @param {import('#typedefs/journey-types.d.ts').RouteParams} options.params
203
195
  * @param {import('../components/manage-list/question.js')} [options.manageListQuestion]
204
196
  * @param {import('../section.js').Section} options.section - the current section
205
197
  * @param {import('../journey/journey.js').Journey} options.journey - the journey we are in
206
198
  * @param {Record<string, unknown>} [options.customViewData] additional data to send to view
207
199
  * @param {unknown} [options.payload]
208
- * @returns {QuestionViewModel}
200
+ * @returns {import('#typedefs/question-types.d.ts').QuestionViewModel}
209
201
  */
210
202
  toViewModel({ params, manageListQuestion, section, journey, customViewData, payload }) {
211
203
  const viewModel = this.prepQuestionForRendering(section, journey, customViewData, payload, {
@@ -223,8 +215,8 @@ export class Question {
223
215
  * @param {import('../journey/journey.js').Journey} journey - the journey we are in
224
216
  * @param {Record<string, unknown>} [customViewData] additional data to send to view
225
217
  * @param {unknown} [payload]
226
- * @param {import('./question-types.js').PrepQuestionForRenderingOptions} [options] - required to support manage list question
227
- * @returns {QuestionViewModel}
218
+ * @param {import('#typedefs/question-types.d.ts').PrepQuestionForRenderingOptions} [options] - required to support manage list question
219
+ * @returns {import('#typedefs/question-types.d.ts').QuestionViewModel}
228
220
  */
229
221
  prepQuestionForRendering(section, journey, customViewData, payload, options) {
230
222
  const answers = payload || this.answerObjectFromJourneyResponse(journey.response, options);
@@ -283,7 +275,7 @@ export class Question {
283
275
  *
284
276
  * If possible override this method instead of prepQuestionForRendering for simple changes to the view model
285
277
  *
286
- * @param {QuestionViewModel} viewModel
278
+ * @param {import('#typedefs/question-types.d.ts').QuestionViewModel} viewModel
287
279
  */ // eslint-disable-next-line no-unused-vars
288
280
  addCustomDataToViewModel(viewModel) {}
289
281
 
@@ -291,7 +283,7 @@ export class Question {
291
283
  * Get the answers object from the journey response, which may be nested in an array for manage list questions
292
284
  *
293
285
  * @param {import('../journey/journey-response.js').JourneyResponse} response
294
- * @param {import('./question-types.js').PrepQuestionForRenderingOptions} [options]
286
+ * @param {import('#typedefs/question-types.d.ts').PrepQuestionForRenderingOptions} [options]
295
287
  * @returns {Record<string, any>}
296
288
  */
297
289
  answerObjectFromJourneyResponse(response, { params, manageListQuestion } = {}) {
@@ -311,7 +303,7 @@ export class Question {
311
303
  /**
312
304
  * renders the question
313
305
  * @param {import('express').Response} res - the express response
314
- * @param {QuestionViewModel} viewModel additional data to send to view
306
+ * @param {import('#typedefs/question-types.d.ts').QuestionViewModel} viewModel additional data to send to view
315
307
  * @returns {void}
316
308
  */
317
309
  renderAction(res, viewModel) {
@@ -329,7 +321,7 @@ export class Question {
329
321
  * @param {import('../journey/journey.js').Journey} journey
330
322
  * @param {import('../section.js').Section} section
331
323
  * @param {import('../components/manage-list/question.js')} [manageListQuestion]
332
- * @returns {QuestionViewModel|undefined} returns the view model for displaying the error or undefined if there are no errors
324
+ * @returns {import('#typedefs/question-types.d.ts').QuestionViewModel|undefined} returns the view model for displaying the error or undefined if there are no errors
333
325
  */
334
326
  checkForValidationErrors(req, section, journey, manageListQuestion) {
335
327
  const { body = {} } = req;
@@ -376,7 +368,7 @@ export class Question {
376
368
  * @param {import('express').Request} req
377
369
  * @param {import('../journey/journey.js').Journey} journey
378
370
  * @param {import('../section.js').Section} sectionObj
379
- * @returns {QuestionViewModel | undefined} returns the view model for displaying the error or undefined if there are no errors
371
+ * @returns {import('#typedefs/question-types.d.ts').QuestionViewModel | undefined} returns the view model for displaying the error or undefined if there are no errors
380
372
  */ //eslint-disable-next-line no-unused-vars
381
373
  checkForSavingErrors(req, sectionObj, journey) {
382
374
  return;
@@ -401,34 +393,49 @@ export class Question {
401
393
 
402
394
  /**
403
395
  * returns the formatted answers values to be used to build task list elements
404
- * @param {String} sectionSegment
396
+ * @param {string} sectionSegment
405
397
  * @param {import('../journey/journey.js').Journey} journey
406
398
  * @param {unknown} answer
407
- * @returns {Array<{
408
- * key: string;
409
- * value: string | Object;
410
- * action?: ActionView | ActionView[];
411
- * }>}
399
+ * @param {boolean} [capitals] - deprecated: use capitaliseAnswer property instead
400
+ * @returns {import('#typedefs/question-types.d.ts').SummaryRow[]}
412
401
  */
413
- formatAnswerForSummary(sectionSegment, journey, answer, capitals = true) {
414
- const formattedAnswer = capitals ? capitalize(answer ?? this.notStartedText) : (answer ?? this.notStartedText);
402
+ formatAnswerForSummary(sectionSegment, journey, answer, capitals) {
403
+ let formattedAnswer = this.formatAnswer(answer);
404
+
405
+ // Handle deprecated capitals parameter
406
+ // TODO DF-49 fully remove capitals parameter
407
+ if (capitals !== undefined && capitals !== this.capitaliseAnswer) {
408
+ console.warn(
409
+ `[Deprecation Warning] The 'capitals' parameter in formatAnswerForSummary is deprecated. ` +
410
+ `Set 'capitaliseAnswer: ${capitals}' in the question constructor instead.`
411
+ );
412
+ // Temporarily override for backwards compatibility
413
+ const originalCapitalize = this.capitaliseAnswer;
414
+ this.capitaliseAnswer = capitals;
415
+ formattedAnswer = this.formatAnswer(answer);
416
+ this.capitaliseAnswer = originalCapitalize;
417
+ }
418
+
415
419
  const action = this.getAction(sectionSegment, journey, answer);
416
420
  const key = this.title ?? this.question;
417
- let rowParams = [];
418
- rowParams.push({
419
- key: key,
420
- value: nl2br(escape(formattedAnswer)),
421
- action: action
421
+
422
+ const displayValue = this.#applyCustomSummaryFormatter({
423
+ answer,
424
+ formattedAnswer,
425
+ question: this,
426
+ journey,
427
+ sectionSegment
422
428
  });
423
- return rowParams;
429
+
430
+ return [{ key, value: displayValue, action }];
424
431
  }
425
432
 
426
433
  /**
427
434
  * Returns the action link for the question
428
- * @param {unknown} answer
435
+ * @param {string} sectionSegment
429
436
  * @param {import('../journey/journey.js').Journey} journey
430
- * @param {String} sectionSegment
431
- * @returns {ActionView|ActionView[]|undefined}
437
+ * @param {unknown} answer
438
+ * @returns {import('#typedefs/question-types.d.ts').ActionView | import('#typedefs/question-types.d.ts').ActionView[] | undefined}
432
439
  */
433
440
  getAction(sectionSegment, journey, answer) {
434
441
  if (this.actionLink) {
@@ -459,6 +466,22 @@ export class Question {
459
466
  return answer;
460
467
  }
461
468
 
469
+ /**
470
+ * Formats an answer value for display in the summary.
471
+ * Subclasses can override this to provide custom formatting without
472
+ * needing to override the full formatAnswerForSummary method.
473
+ *
474
+ * @param {unknown} answer - the raw answer value
475
+ * @returns {string} the formatted answer for display
476
+ */
477
+ formatAnswer(answer) {
478
+ let formatted = answer ?? this.notStartedText;
479
+ if (this.capitaliseAnswer) {
480
+ formatted = capitalize(formatted);
481
+ }
482
+ return nl2br(escape(formatted));
483
+ }
484
+
462
485
  /**
463
486
  * @returns {boolean}
464
487
  */