@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
@@ -96,78 +96,157 @@ describe('./src/dynamic-forms/components/radio/question.js', () => {
96
96
  assert.strictEqual(preppedQuestion.question.label, LABEL);
97
97
  });
98
98
 
99
- it('should customise answer for summary when question has a conditional field filled in', () => {
100
- const radioQuestion = new RadioQuestion({
101
- title: TITLE,
102
- question: QUESTION,
103
- description: DESCRIPTION,
104
- fieldName: FIELDNAME,
105
- viewFolder: VIEWFOLDER,
106
- html: HTML,
107
- label: LABEL,
108
- options: OPTIONS
99
+ describe('formatAnswer', () => {
100
+ it('should delegate simple string answers to parent class', () => {
101
+ const radioQuestion = new RadioQuestion({
102
+ title: TITLE,
103
+ question: QUESTION,
104
+ description: DESCRIPTION,
105
+ fieldName: FIELDNAME,
106
+ options: [
107
+ { text: 'Option 1', value: 'op-1' },
108
+ { text: 'Option 2', value: 'op-2' }
109
+ ]
110
+ });
111
+
112
+ const result = radioQuestion.formatAnswer('op-1');
113
+ assert.strictEqual(result, 'Option 1');
109
114
  });
110
115
 
111
- const rowParams = radioQuestion.formatAnswerForSummary(SECTION, JOURNEY, {
112
- value: 'yes',
113
- conditional: 'test'
116
+ it('should format object answer with value only (no conditional)', () => {
117
+ const radioQuestion = new RadioQuestion({
118
+ title: TITLE,
119
+ question: QUESTION,
120
+ description: DESCRIPTION,
121
+ fieldName: FIELDNAME,
122
+ options: OPTIONS
123
+ });
124
+
125
+ const result = radioQuestion.formatAnswer({ value: 'no' });
126
+ assert.strictEqual(result, 'No');
114
127
  });
115
- assert.strictEqual(rowParams[0].value, 'Yes<br>test');
116
- });
117
128
 
118
- it('should use option text (not value) for display', () => {
119
- const radioQuestion = new RadioQuestion({
120
- title: TITLE,
121
- question: QUESTION,
122
- description: DESCRIPTION,
123
- fieldName: FIELDNAME,
124
- viewFolder: VIEWFOLDER,
125
- html: HTML,
126
- label: LABEL,
127
- options: [
128
- { text: 'Option 1', value: 'op-1' },
129
- { text: 'Option 2', value: 'op-2' }
130
- ]
129
+ it('should format object answer with conditional', () => {
130
+ const radioQuestion = new RadioQuestion({
131
+ title: TITLE,
132
+ question: QUESTION,
133
+ description: DESCRIPTION,
134
+ fieldName: FIELDNAME,
135
+ options: OPTIONS
136
+ });
137
+
138
+ const result = radioQuestion.formatAnswer({
139
+ value: 'yes',
140
+ conditional: { yes: 'test details' }
141
+ });
142
+ assert.strictEqual(result, 'Yes<br>test details');
131
143
  });
132
144
 
133
- const rowParams = radioQuestion.formatAnswerForSummary(SECTION, JOURNEY, 'op-1');
134
- assert.strictEqual(rowParams[0].value, 'Option 1');
135
- });
145
+ it('should format object answer with conditional and label', () => {
146
+ const optionsWithLabel = [...OPTIONS];
147
+ optionsWithLabel[0] = {
148
+ ...optionsWithLabel[0],
149
+ conditional: {
150
+ ...optionsWithLabel[0].conditional,
151
+ label: 'label:'
152
+ }
153
+ };
154
+ const radioQuestion = new RadioQuestion({
155
+ title: TITLE,
156
+ question: QUESTION,
157
+ description: DESCRIPTION,
158
+ fieldName: FIELDNAME,
159
+ options: optionsWithLabel
160
+ });
161
+
162
+ const result = radioQuestion.formatAnswer({
163
+ value: 'yes',
164
+ conditional: { yes: 'test details' }
165
+ });
166
+ assert.strictEqual(result, 'Yes<br>label: test details');
167
+ });
136
168
 
137
- it('should customise answer for summary and add label when question has a conditional field filled in and conditional field has a label', () => {
138
- const optionsWithLabel = [...OPTIONS];
139
- optionsWithLabel[0].conditional.label = 'label:';
140
- const radioQuestion = new RadioQuestion({
141
- title: TITLE,
142
- question: QUESTION,
143
- description: DESCRIPTION,
144
- fieldName: FIELDNAME,
145
- viewFolder: VIEWFOLDER,
146
- html: HTML,
147
- label: LABEL,
148
- options: optionsWithLabel
169
+ it('should fall back to value when option not found in object answer', () => {
170
+ const radioQuestion = new RadioQuestion({
171
+ title: TITLE,
172
+ question: QUESTION,
173
+ description: DESCRIPTION,
174
+ fieldName: FIELDNAME,
175
+ options: OPTIONS
176
+ });
177
+
178
+ const result = radioQuestion.formatAnswer({ value: 'unknown' });
179
+ assert.strictEqual(result, 'unknown');
149
180
  });
150
181
 
151
- const rowParams = radioQuestion.formatAnswerForSummary(SECTION, JOURNEY, {
152
- value: 'yes',
153
- conditional: 'test'
182
+ it('should escape HTML in option text', () => {
183
+ const radioQuestion = new RadioQuestion({
184
+ title: TITLE,
185
+ question: QUESTION,
186
+ description: DESCRIPTION,
187
+ fieldName: FIELDNAME,
188
+ options: [{ text: '<script>alert("xss")</script>', value: 'xss' }]
189
+ });
190
+
191
+ const result = radioQuestion.formatAnswer('xss');
192
+ assert.strictEqual(result, '&lt;script&gt;alert(&quot;xss&quot;)&lt;/script&gt;');
154
193
  });
155
- assert.strictEqual(rowParams[0].value, 'Yes<br>label: test');
156
- });
157
194
 
158
- it('should not customise answer for summary when question does not have a conditional field filled in', () => {
159
- const radioQuestion = new RadioQuestion({
160
- title: TITLE,
161
- question: QUESTION,
162
- description: DESCRIPTION,
163
- fieldName: FIELDNAME,
164
- viewFolder: VIEWFOLDER,
165
- html: HTML,
166
- label: LABEL,
167
- options: OPTIONS
195
+ it('should escape HTML in conditional value', () => {
196
+ const radioQuestion = new RadioQuestion({
197
+ title: TITLE,
198
+ question: QUESTION,
199
+ description: DESCRIPTION,
200
+ fieldName: FIELDNAME,
201
+ options: [
202
+ {
203
+ text: 'Yes',
204
+ value: 'yes',
205
+ conditional: { fieldName: 'details', type: 'text' }
206
+ }
207
+ ]
208
+ });
209
+
210
+ const result = radioQuestion.formatAnswer({
211
+ value: 'yes',
212
+ conditional: { yes: '<img src=x onerror=alert(1)>' }
213
+ });
214
+ assert.strictEqual(result, 'Yes<br>&lt;img src=x onerror=alert(1)&gt;');
168
215
  });
169
216
 
170
- const rowParams = radioQuestion.formatAnswerForSummary(SECTION, JOURNEY, 'no');
171
- assert.strictEqual(rowParams[0].value, 'No');
217
+ it('should escape HTML in conditional label', () => {
218
+ const radioQuestion = new RadioQuestion({
219
+ title: TITLE,
220
+ question: QUESTION,
221
+ description: DESCRIPTION,
222
+ fieldName: FIELDNAME,
223
+ options: [
224
+ {
225
+ text: 'Yes',
226
+ value: 'yes',
227
+ conditional: { fieldName: 'details', type: 'text', label: '<b>Label</b>' }
228
+ }
229
+ ]
230
+ });
231
+
232
+ const result = radioQuestion.formatAnswer({
233
+ value: 'yes',
234
+ conditional: { yes: 'safe value' }
235
+ });
236
+ assert.strictEqual(result, 'Yes<br>&lt;b&gt;Label&lt;/b&gt; safe value');
237
+ });
238
+
239
+ it('should escape HTML in fallback value when option not found', () => {
240
+ const radioQuestion = new RadioQuestion({
241
+ title: TITLE,
242
+ question: QUESTION,
243
+ description: DESCRIPTION,
244
+ fieldName: FIELDNAME,
245
+ options: OPTIONS
246
+ });
247
+
248
+ const result = radioQuestion.formatAnswer({ value: '<script>xss</script>' });
249
+ assert.strictEqual(result, '&lt;script&gt;xss&lt;/script&gt;');
250
+ });
172
251
  });
173
252
  });
@@ -0,0 +1,12 @@
1
+ export class SelectQuestion extends OptionsQuestion {
2
+ /**
3
+ * @param {import('#typedefs/question-props.d.ts').SelectQuestionParams} params
4
+ */
5
+ constructor({ label, html, legend, disableAccessibleAutocomplete, viewFolder, ...parentParams }: import("#typedefs/question-props.d.ts").SelectQuestionParams);
6
+ label: string | undefined;
7
+ legend: string | undefined;
8
+ #private;
9
+ }
10
+ export default SelectQuestion;
11
+ import OptionsQuestion from '../../questions/options-question.js';
12
+ //# sourceMappingURL=question.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"question.d.ts","sourceRoot":"","sources":["question.js"],"names":[],"mappings":"AAEA;IAEC;;OAEG;IACH,iGAFW,OAAO,+BAA+B,EAAE,oBAAoB,EAYtE;IAHA,0BAAkB;IAClB,2BAAoB;;CAYrB;;4BA3B2B,qCAAqC"}
@@ -3,37 +3,12 @@ import OptionsQuestion from '../../questions/options-question.js';
3
3
  export class SelectQuestion extends OptionsQuestion {
4
4
  #disableAccessibleAutocomplete;
5
5
  /**
6
- * @param {import('../../questions/question-props.d.ts').SelectQuestionParams} params
6
+ * @param {import('#typedefs/question-props.d.ts').SelectQuestionParams} params
7
7
  */
8
- constructor({
9
- title,
10
- question,
11
- fieldName,
12
- viewFolder,
13
- url,
14
- hint,
15
- pageTitle,
16
- description,
17
- label,
18
- html,
19
- legend,
20
- disableAccessibleAutocomplete,
21
- options,
22
- validators,
23
- viewData
24
- }) {
8
+ constructor({ label, html, legend, disableAccessibleAutocomplete, viewFolder, ...parentParams }) {
25
9
  super({
26
- title,
27
- question,
28
- viewFolder: !viewFolder ? 'select' : viewFolder,
29
- fieldName,
30
- url,
31
- hint,
32
- pageTitle,
33
- description,
34
- options,
35
- validators,
36
- viewData
10
+ ...parentParams,
11
+ viewFolder: viewFolder || 'select'
37
12
  });
38
13
 
39
14
  this.html = html;
@@ -43,31 +18,13 @@ export class SelectQuestion extends OptionsQuestion {
43
18
  }
44
19
 
45
20
  /**
46
- * @param {import('#question').QuestionViewModel} viewModel
21
+ * @param {import('#typedefs/question-types.d.ts').QuestionViewModel} viewModel
47
22
  */
48
23
  addCustomDataToViewModel(viewModel) {
49
24
  viewModel.question.label = this.label;
50
25
  viewModel.question.legend = this.legend;
51
26
  viewModel.question.disableAccessibleAutocomplete = this.#disableAccessibleAutocomplete;
52
27
  }
53
-
54
- /**
55
- * returns the formatted answers values to be used to build task list elements
56
- * note: only supports a single answer
57
- *
58
- * @param {unknown} answer
59
- * @param {Journey} journey
60
- * @param {String} sectionSegment
61
- * @returns {Array.<Object>}
62
- */
63
- formatAnswerForSummary(sectionSegment, journey, answer) {
64
- if (answer) {
65
- const selectedOption = this.options.find((option) => option.value === answer);
66
- const selectedText = selectedOption?.text || '';
67
- return super.formatAnswerForSummary(sectionSegment, journey, selectedText, false);
68
- }
69
- return super.formatAnswerForSummary(sectionSegment, journey, answer);
70
- }
71
28
  }
72
29
 
73
30
  export default SelectQuestion;
@@ -3,9 +3,9 @@
3
3
  */
4
4
  export class SingleLineInputQuestion extends Question {
5
5
  /**
6
- * @param {import('../../questions/question-props.d.ts').SingleLineInputQuestionParams} params
6
+ * @param {import('#typedefs/question-props.d.ts').SingleLineInputQuestionParams} params
7
7
  */
8
- constructor(params: import("../../questions/question-props.d.ts").SingleLineInputQuestionParams);
8
+ constructor(params: import("#typedefs/question-props.d.ts").SingleLineInputQuestionParams);
9
9
  /** @type {Record<string, string>} */
10
10
  inputAttributes: Record<string, string>;
11
11
  label: string | undefined;
@@ -0,0 +1 @@
1
+ {"version":3,"file":"question.d.ts","sourceRoot":"","sources":["question.js"],"names":[],"mappings":"AAEA;;GAEG;AACH;IAIC;;OAEG;IACH,oBAFW,OAAO,+BAA+B,EAAE,6BAA6B,EAW/E;IAfD,qCAAqC;IACrC,iBADW,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CACjB;IAWf,0BAAyB;IAEzB,gBAAmC;CAepC;;yBAnCwB,WAAW"}
@@ -8,7 +8,7 @@ export class SingleLineInputQuestion extends Question {
8
8
  inputAttributes;
9
9
 
10
10
  /**
11
- * @param {import('../../questions/question-props.d.ts').SingleLineInputQuestionParams} params
11
+ * @param {import('#typedefs/question-props.d.ts').SingleLineInputQuestionParams} params
12
12
  */
13
13
  constructor(params) {
14
14
  super({
@@ -22,7 +22,7 @@ export class SingleLineInputQuestion extends Question {
22
22
  }
23
23
 
24
24
  /**
25
- * @param {import('#question').QuestionViewModel} viewModel
25
+ * @param {import('#typedefs/question-types.d.ts').QuestionViewModel} viewModel
26
26
  */
27
27
  addCustomDataToViewModel(viewModel) {
28
28
  viewModel.question.label = this.label;
@@ -16,10 +16,10 @@
16
16
  */
17
17
  export class TextEntryQuestion extends Question {
18
18
  /**
19
- * @param {import('../../questions/question-props.d.ts').TextEntryQuestionParams} params
19
+ * @param {import('#typedefs/question-props.d.ts').TextEntryQuestionParams} params
20
20
  */
21
- constructor({ textEntryCheckbox, label, ...parentParams }: import("../../questions/question-props.d.ts").TextEntryQuestionParams);
22
- textEntryCheckbox: import("../../questions/question-props.d.ts").TextEntryCheckbox | undefined;
21
+ constructor({ textEntryCheckbox, label, ...parentParams }: import("#typedefs/question-props.d.ts").TextEntryQuestionParams);
22
+ textEntryCheckbox: import("#typedefs/question-props.d.ts").TextEntryCheckbox | undefined;
23
23
  label: string | undefined;
24
24
  }
25
25
  export default TextEntryQuestion;
@@ -0,0 +1 @@
1
+ {"version":3,"file":"question.d.ts","sourceRoot":"","sources":["question.js"],"names":[],"mappings":"AACA;;;;;GAKG;AAEH;;;;;;GAMG;AAEH;;GAEG;AACH;IACC;;OAEG;IACH,2DAFW,OAAO,+BAA+B,EAAE,uBAAuB,EAUzE;IAFA,yFAA0C;IAC1C,0BAAkB;CAUnB;;;YA9Ba,MAAM;UACN,MAAM;UACN,MAAM;;;yBAZK,WAAW"}
@@ -19,7 +19,7 @@ import { Question } from '#question';
19
19
  */
20
20
  export class TextEntryQuestion extends Question {
21
21
  /**
22
- * @param {import('../../questions/question-props.d.ts').TextEntryQuestionParams} params
22
+ * @param {import('#typedefs/question-props.d.ts').TextEntryQuestionParams} params
23
23
  */
24
24
  constructor({ textEntryCheckbox, label, ...parentParams }) {
25
25
  super({
@@ -32,7 +32,7 @@ export class TextEntryQuestion extends Question {
32
32
  }
33
33
 
34
34
  /**
35
- * @param {import('#question').QuestionViewModel} viewModel
35
+ * @param {import('#typedefs/question-types.d.ts').QuestionViewModel} viewModel
36
36
  */
37
37
  addCustomDataToViewModel(viewModel) {
38
38
  viewModel.question.label = this.label;
@@ -5,23 +5,22 @@ export const TRUNCATED_MAX_LENGTH: 500;
5
5
  */
6
6
  export class TextEntryRedactQuestion extends Question {
7
7
  /**
8
- * @param {import('../../questions/question-props.d.ts').TextEntryRedactQuestionParams} params
8
+ * @param {import('#typedefs/question-props.d.ts').TextEntryRedactQuestionParams} params
9
9
  */
10
- constructor({ textEntryCheckbox, label, onlyShowRedactedValueForSummary, useRedactedFieldNameForSave, showSuggestionsUi, summaryText, shouldTruncateSummary, ...parentParams }: import("../../questions/question-props.d.ts").TextEntryRedactQuestionParams);
11
- textEntryCheckbox: import("../../questions/question-props.d.ts").TextEntryCheckbox | undefined;
10
+ constructor({ textEntryCheckbox, label, onlyShowRedactedValueForSummary, useRedactedFieldNameForSave, showSuggestionsUi, summaryText, shouldTruncateSummary, ...parentParams }: import("#typedefs/question-props.d.ts").TextEntryRedactQuestionParams);
11
+ textEntryCheckbox: import("#typedefs/question-props.d.ts").TextEntryCheckbox | undefined;
12
12
  label: string | undefined;
13
13
  onlyShowRedactedValueForSummary: boolean | undefined;
14
14
  useRedactedFieldNameForSave: boolean | undefined;
15
15
  showSuggestionsUi: boolean | undefined;
16
16
  summaryText: string | undefined;
17
17
  shouldTruncateSummary: boolean | undefined;
18
- prepQuestionForRendering(section: any, journey: any, customViewData: any, payload: any, options: any): import("#question").QuestionViewModel;
18
+ prepQuestionForRendering(section: any, journey: any, customViewData: any, payload: any, options: any): import("#typedefs/question-types.d.ts").QuestionViewModel<import("#typedefs/question-types.d.ts").BaseQuestionViewData>;
19
19
  answerForViewModel(answers: any): string;
20
- formatAnswerForSummary(sectionSegment: any, journey: any, answer: any, capitals?: boolean): {
21
- key: string;
22
- value: string | Object;
23
- action?: import("../../controller.js").ActionView | import("../../controller.js").ActionView[];
24
- }[];
20
+ /**
21
+ * returns the formatted answers values to be used to build task list elements
22
+ */
23
+ formatAnswerForSummary(sectionSegment: any, journey: any, answer: any, capitals?: boolean): import("#typedefs/question-types.d.ts").SummaryRow[];
25
24
  }
26
25
  export default TextEntryRedactQuestion;
27
26
  import { Question } from '#question';
@@ -0,0 +1 @@
1
+ {"version":3,"file":"question.d.ts","sourceRoot":"","sources":["question.js"],"names":[],"mappings":"AAGA,+BAAgC,QAAG,CAAC;AACpC,mCAAoC,GAAG,CAAC;AAExC;;GAEG;AACH;IACC;;OAEG;IACH,gLAFW,OAAO,+BAA+B,EAAE,6BAA6B,EAwB/E;IAPA,yFAA0C;IAC1C,0BAAkB;IAClB,qDAAsE;IACtE,iDAA8D;IAC9D,uCAA0C;IAC1C,gCAA8B;IAC9B,2CAAkD;IAkBnD,+NAOC;IAED,yCAEC;IAYD;;OAEG;IACH,iJAuBC;CACD;;yBAtGwB,WAAW"}
@@ -9,7 +9,7 @@ export const TRUNCATED_MAX_LENGTH = 500;
9
9
  */
10
10
  export class TextEntryRedactQuestion extends Question {
11
11
  /**
12
- * @param {import('../../questions/question-props.d.ts').TextEntryRedactQuestionParams} params
12
+ * @param {import('#typedefs/question-props.d.ts').TextEntryRedactQuestionParams} params
13
13
  */
14
14
  constructor({
15
15
  textEntryCheckbox,
@@ -64,7 +64,7 @@ export class TextEntryRedactQuestion extends Question {
64
64
  }
65
65
 
66
66
  /**
67
- * @param {import('#question').QuestionViewModel} viewModel
67
+ * @param {import('#typedefs/question-types.d.ts').QuestionViewModel} viewModel
68
68
  */
69
69
  addCustomDataToViewModel(viewModel) {
70
70
  viewModel.question.label = this.label;
@@ -73,6 +73,9 @@ export class TextEntryRedactQuestion extends Question {
73
73
  viewModel.showSuggestionsUi = this.showSuggestionsUi;
74
74
  }
75
75
 
76
+ /**
77
+ * returns the formatted answers values to be used to build task list elements
78
+ */
76
79
  formatAnswerForSummary(sectionSegment, journey, answer, capitals = true) {
77
80
  const redacted = journey.response.answers[this.fieldName + 'Redacted'];
78
81
  let toShow;
@@ -24,10 +24,10 @@
24
24
  */
25
25
  export class UnitOptionEntryQuestion extends Question {
26
26
  /**
27
- * @param {import('../../questions/question-props.d.ts').UnitOptionEntryQuestionParams} params
27
+ * @param {import('#typedefs/question-props.d.ts').UnitOptionEntryQuestionParams} params
28
28
  * @param {Record<string, Function>} [methodOverrides]
29
29
  */
30
- constructor({ conditionalFieldName, options, label, ...parentParams }: import("../../questions/question-props.d.ts").UnitOptionEntryQuestionParams, methodOverrides?: Record<string, Function>);
30
+ constructor({ conditionalFieldName, options, label, ...parentParams }: import("#typedefs/question-props.d.ts").UnitOptionEntryQuestionParams, methodOverrides?: Record<string, Function>);
31
31
  /** @type {Array<UnitOption>} */
32
32
  options: Array<UnitOption>;
33
33
  conditionalFieldName: string;
@@ -35,19 +35,19 @@ export class UnitOptionEntryQuestion extends Question {
35
35
  optionJoinString: string;
36
36
  /**
37
37
  * gets the view model for this question
38
- * @param {import('../../section').Section} section - the current section
38
+ * @param {import('../../section.js').Section} section - the current section
39
39
  * @param {import('#journey').Journey} journey - the journey we are in
40
40
  * @param {Record<string, unknown>} [customViewData] additional data to send to view
41
41
  * @param {Record<string, unknown>} [payload]
42
- * @param {import('#question-types').PrepQuestionForRenderingOptions} options
43
- * @returns {import('../../questions/question.js').QuestionViewModel & {
44
- * question: import('../../questions/question.js').QuestionViewModel['question'] & {
42
+ * @param {import('#typedefs/question-types.d.ts').PrepQuestionForRenderingOptions} options
43
+ * @returns {import('#typedefs/question-types.d.ts').QuestionViewModel & {
44
+ * question: import('#typedefs/question-types.d.ts').QuestionViewModel['question'] & {
45
45
  * options:UnitOption[]
46
46
  * }
47
47
  * }}
48
48
  */
49
- prepQuestionForRendering(section: any, journey: import("#journey").Journey, customViewData?: Record<string, unknown>, payload?: Record<string, unknown>, options: import("#question-types").PrepQuestionForRenderingOptions): import("../../questions/question.js").QuestionViewModel & {
50
- question: import("../../questions/question.js").QuestionViewModel["question"] & {
49
+ prepQuestionForRendering(section: import("../../section.js").Section, journey: import("#journey").Journey, customViewData?: Record<string, unknown>, payload?: Record<string, unknown>, options: import("#typedefs/question-types.d.ts").PrepQuestionForRenderingOptions): import("#typedefs/question-types.d.ts").QuestionViewModel & {
50
+ question: import("#typedefs/question-types.d.ts").QuestionViewModel["question"] & {
51
51
  options: UnitOption[];
52
52
  };
53
53
  };
@@ -59,6 +59,10 @@ export class UnitOptionEntryQuestion extends Question {
59
59
  */ getDataToSave(req: import("express").Request, journeyResponse: JourneyResponse): Promise<{
60
60
  answers: Record<string, unknown>;
61
61
  }>;
62
+ /**
63
+ * returns the formatted answers values to be used to build task list elements
64
+ */
65
+ formatAnswerForSummary(sectionSegment: any, journey: any, answer: any): import("#typedefs/question-types.d.ts").SummaryRow[];
62
66
  }
63
67
  export default UnitOptionEntryQuestion;
64
68
  /**
@@ -0,0 +1 @@
1
+ {"version":3,"file":"question.d.ts","sourceRoot":"","sources":["question.js"],"names":[],"mappings":"AAOA;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AAEH;IAIC;;;OAGG;IACH,uEAHW,OAAO,+BAA+B,EAAE,6BAA6B,oBACrE,MAAM,CAAC,MAAM,WAAW,EAkBlC;IAvBD,gCAAgC;IAChC,SADW,KAAK,CAAC,UAAU,CAAC,CACpB;IAkBP,6BAAgD;IAEhD,0BAAkB;IAClB,yBAA+C;IAGhD;;;;;;;;;;;;OAYG;IACH,kCAXW,OAAO,kBAAkB,EAAE,OAAO,WAClC,OAAO,UAAU,EAAE,OAAO,mBAC1B,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,YACvB,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,WACvB,OAAO,+BAA+B,EAAE,+BAA+B,GACrE,OAAO,+BAA+B,EAAE,iBAAiB,GAAG;QACpE,QAAQ,EAAE,OAAO,+BAA+B,EAAE,iBAAiB,CAAC,UAAU,CAAC,GAAG;YAChF,OAAO,EAAC,UAAU,EAAE,CAAA;SACrB,CAAA;KACF,CAmDH;IAED;;;;;OAKG,CACH,mBAJW,OAAO,SAAS,EAAE,OAAO,mBACzB,eAAe,GACb,OAAO,CAAC;QAAE,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAA;KAAE,CAAC,CAgCzD;IAED;;OAEG;IACH,6HAaC;CACD;;;;;;;;;;yBAnKY;IACR,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,EAAE,MAAM,CAAC;IACZ,IAAI,CAAC,EAAE,MAAM,CAAC;IAChB,OAAO,CAAC,EAAE,OAAO,GAAG,SAAS,CAAC;IAC9B,UAAU,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACpC,SAAS,CAAC,EAAE,WAAW,CAAC;IACxB,WAAW,EAAE;QACX,SAAS,EAAE,MAAM,CAAC;QACd,MAAM,EAAE,MAAM,CAAC;QACnB,KAAK,CAAC,EAAE,OAAO,CAAC;QACZ,KAAK,CAAC,EAAE,MAAM,CAAC;QACf,IAAI,CAAC,EAAE,MAAM,CAAC;QAClB,gBAAgB,CAAC,EAAE,MAAM,CAAC;KAC3B,CAAA;CACH;yBA5BsB,6BAA6B"}
@@ -35,7 +35,7 @@ export class UnitOptionEntryQuestion extends Question {
35
35
  options;
36
36
 
37
37
  /**
38
- * @param {import('../../questions/question-props.d.ts').UnitOptionEntryQuestionParams} params
38
+ * @param {import('#typedefs/question-props.d.ts').UnitOptionEntryQuestionParams} params
39
39
  * @param {Record<string, Function>} [methodOverrides]
40
40
  */
41
41
  constructor({ conditionalFieldName, options, label, ...parentParams }, methodOverrides) {
@@ -58,13 +58,13 @@ export class UnitOptionEntryQuestion extends Question {
58
58
 
59
59
  /**
60
60
  * gets the view model for this question
61
- * @param {import('../../section').Section} section - the current section
61
+ * @param {import('../../section.js').Section} section - the current section
62
62
  * @param {import('#journey').Journey} journey - the journey we are in
63
63
  * @param {Record<string, unknown>} [customViewData] additional data to send to view
64
64
  * @param {Record<string, unknown>} [payload]
65
- * @param {import('#question-types').PrepQuestionForRenderingOptions} options
66
- * @returns {import('../../questions/question.js').QuestionViewModel & {
67
- * question: import('../../questions/question.js').QuestionViewModel['question'] & {
65
+ * @param {import('#typedefs/question-types.d.ts').PrepQuestionForRenderingOptions} options
66
+ * @returns {import('#typedefs/question-types.d.ts').QuestionViewModel & {
67
+ * question: import('#typedefs/question-types.d.ts').QuestionViewModel['question'] & {
68
68
  * options:UnitOption[]
69
69
  * }
70
70
  * }}
@@ -160,7 +160,6 @@ export class UnitOptionEntryQuestion extends Question {
160
160
 
161
161
  /**
162
162
  * returns the formatted answers values to be used to build task list elements
163
- * @type {Question['formatAnswerForSummary']}
164
163
  */
165
164
  formatAnswerForSummary(sectionSegment, journey, answer) {
166
165
  if (answer == null) return super.formatAnswerForSummary(sectionSegment, journey, answer);
@@ -0,0 +1 @@
1
+ {"version":3,"file":"component-types.d.ts","sourceRoot":"","sources":["component-types.js"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;GAiBG"}
@@ -0,0 +1 @@
1
+ {"version":3,"file":"persisted-number-answer.d.ts","sourceRoot":"","sources":["persisted-number-answer.js"],"names":[],"mappings":"AAGO,iDAFI,MAAM,mBAYhB"}
@@ -0,0 +1 @@
1
+ {"version":3,"file":"question-has-answer.d.ts","sourceRoot":"","sources":["question-has-answer.js"],"names":[],"mappings":"AAOO,8CAHI,OAAO,mCAAmC,EAAE,eAAe,GACzD;IAAC,GAAG,EAAE,eAAe,CAAC;IAAC,EAAE,EAAE,eAAe,CAAA;CAAC,CAOtD;AAYK,gDAJI,OAAO,6BAA6B,EAAE,QAAQ,iBAC9C,OAAO,GACL,OAAO,kBAAkB,EAAE,iBAAiB,CAIxD;AAUM,4CALI,OAAO,mCAAmC,EAAE,eAAe,YAC3D,OAAO,6BAA6B,EAAE,QAAQ,GAAC;IAAC,gBAAgB,EAAE,MAAM,CAAA;CAAC,iBACzE,OAAO,GACL,OAAO,CAgBnB;AAUM,sDALI,OAAO,mCAAmC,EAAE,eAAe,YAC3D,GAAG,gBACH,CAAC,IAAI,EAAE,GAAG,KAAK,OAAO,GACpB,OAAO,CAWnB;AASM,+CANI,OAAO,mCAAmC,EAAE,eAAe,qBAC3D,iBAAiB,0BAEzB;IAA8B,iBAAiB,EAAvC,KAAK,GAAG,IAAI;CACpB,GAAU,OAAO,CAUnB;AAOM,0DAJI,OAAO,mCAAmC,EAAE,eAAe,YAC3D,OAAO,6BAA6B,EAAE,QAAQ,GAC5C,OAAO,CAMnB;AAOM,0DAJI,OAAO,mCAAmC,EAAE,eAAe,YAC3D,OAAO,6BAA6B,EAAE,QAAQ,GAC5C,OAAO,CAMnB;gCA5Ga,CAAC,GAAG,EAAE,OAAO,CAAC,EAAE;8BAChB,CAAC,iBAAiB,EAAE,iBAAiB,KAAK,OAAO"}
@@ -41,7 +41,7 @@ export const questionHasAnswer = (response, question, expectedValue) => {
41
41
  if (Array.isArray(answerField)) {
42
42
  return answerField.includes(expectedValue);
43
43
  } else if (question.optionJoinString && typeof answerField === 'string') {
44
- // todo: answers from options questions sometimes are array sometimes string, why?
44
+ // todo: DF-51 answers from options questions sometimes are array sometimes string, why?
45
45
  if (!answerField) return false;
46
46
  const answers = answerField.split(question.optionJoinString);
47
47
  return answers.includes(expectedValue);
@@ -0,0 +1,33 @@
1
+ export function getConditionalFieldName(parentField: any, conditionalField: any): string;
2
+ /**
3
+ * Get conditional answer(s) for a question.
4
+ * Returns an object keyed by option value with conditional answers, or null if no conditionals exist.
5
+ *
6
+ * @param {Record<string, unknown>} answers - all answers
7
+ * @param {{options?: Array<{value: string, conditional?: {fieldName: string}}>, fieldName: string, optionJoinString?: string}} question - question with options
8
+ * @param {string} answer - the answer value (may be separated by optionJoinString for checkboxes)
9
+ * @returns {Record<string, string> | null} conditional answer(s) keyed by option value
10
+ */
11
+ export function getConditionalAnswer(answers: Record<string, unknown>, question: {
12
+ options?: Array<{
13
+ value: string;
14
+ conditional?: {
15
+ fieldName: string;
16
+ };
17
+ }>;
18
+ fieldName: string;
19
+ optionJoinString?: string;
20
+ }, answer: string): Record<string, string> | null;
21
+ /**
22
+ * @param {unknown} conditional
23
+ * @returns {conditional is {html: string}}
24
+ */
25
+ export function conditionalIsJustHTML(conditional: unknown): conditional is {
26
+ html: string;
27
+ };
28
+ declare namespace _default {
29
+ export { getConditionalAnswer };
30
+ export { getConditionalFieldName };
31
+ }
32
+ export default _default;
33
+ //# sourceMappingURL=question-utils.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"question-utils.d.ts","sourceRoot":"","sources":["question-utils.js"],"names":[],"mappings":"AAAA,yFAEC;AAED;;;;;;;;GAQG;AACH,8CALW,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,YACvB;IAAC,OAAO,CAAC,EAAE,KAAK,CAAC;QAAC,KAAK,EAAE,MAAM,CAAC;QAAC,WAAW,CAAC,EAAE;YAAC,SAAS,EAAE,MAAM,CAAA;SAAC,CAAA;KAAC,CAAC,CAAC;IAAC,SAAS,EAAE,MAAM,CAAC;IAAC,gBAAgB,CAAC,EAAE,MAAM,CAAA;CAAC,UACnH,MAAM,GACJ,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,GAAG,IAAI,CAyBzC;AAED;;;GAGG;AACH,mDAHW,OAAO,GACL,WAAW,IAAI;IAAC,IAAI,EAAE,MAAM,CAAA;CAAC,CAIzC"}
@@ -2,9 +2,38 @@ export function getConditionalFieldName(parentField, conditionalField) {
2
2
  return `${parentField}_${conditionalField}`;
3
3
  }
4
4
 
5
+ /**
6
+ * Get conditional answer(s) for a question.
7
+ * Returns an object keyed by option value with conditional answers, or null if no conditionals exist.
8
+ *
9
+ * @param {Record<string, unknown>} answers - all answers
10
+ * @param {{options?: Array<{value: string, conditional?: {fieldName: string}}>, fieldName: string, optionJoinString?: string}} question - question with options
11
+ * @param {string} answer - the answer value (may be separated by optionJoinString for checkboxes)
12
+ * @returns {Record<string, string> | null} conditional answer(s) keyed by option value
13
+ */
5
14
  export function getConditionalAnswer(answers, question, answer) {
6
- const conditionalFieldName = question.options?.find((option) => option.value === answer)?.conditional?.fieldName;
7
- return conditionalFieldName ? answers[getConditionalFieldName(question.fieldName, conditionalFieldName)] : null;
15
+ if (!answer || !question.options) {
16
+ return null;
17
+ }
18
+
19
+ // TODO DF-51 treat as an array
20
+ const joinString = question.optionJoinString || ',';
21
+ const answerValues = String(answer)
22
+ .split(joinString)
23
+ .map((v) => v.trim());
24
+
25
+ const conditionals = {};
26
+ for (const value of answerValues) {
27
+ const option = question.options.find((opt) => opt.value === value);
28
+ if (!option?.conditional?.fieldName) {
29
+ continue;
30
+ }
31
+ const conditionalValue = answers[getConditionalFieldName(question.fieldName, option.conditional.fieldName)];
32
+ if (conditionalValue) {
33
+ conditionals[value] = conditionalValue;
34
+ }
35
+ }
36
+ return Object.keys(conditionals).length > 0 ? conditionals : null;
8
37
  }
9
38
 
10
39
  /**