@planning-inspectorate/dynamic-forms 3.21.0 → 3.23.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 (58) hide show
  1. package/README.md +52 -1
  2. package/docs/Dynamic Sections.md +47 -0
  3. package/package.json +1 -1
  4. package/src/components/manage-list/question.d.ts.map +1 -1
  5. package/src/components/manage-list/question.js +5 -0
  6. package/src/components/manage-list/question.test.js +3 -1
  7. package/src/components/multi-field-input/question.d.ts.map +1 -1
  8. package/src/components/multi-field-input/question.js +7 -5
  9. package/src/components/multi-field-input/question.test.js +126 -213
  10. package/src/components/number-entry/index.njk +2 -3
  11. package/src/components/number-entry/question.d.ts +3 -2
  12. package/src/components/number-entry/question.d.ts.map +1 -1
  13. package/src/components/number-entry/question.js +25 -2
  14. package/src/components/number-entry/question.test.js +65 -0
  15. package/src/components/text-entry-redact/question.d.ts.map +1 -1
  16. package/src/components/text-entry-redact/question.js +3 -1
  17. package/src/components/text-entry-redact/question.test.js +9 -0
  18. package/src/components/unit-option-entry/question.d.ts.map +1 -1
  19. package/src/components/unit-option-entry/question.js +3 -1
  20. package/src/components/unit-option-entry/question.test.js +9 -0
  21. package/src/controller.d.ts +15 -3
  22. package/src/controller.d.ts.map +1 -1
  23. package/src/controller.js +19 -11
  24. package/src/controller.test.js +182 -153
  25. package/src/dynamic-section.d.ts +40 -0
  26. package/src/dynamic-section.d.ts.map +1 -0
  27. package/src/dynamic-section.js +51 -0
  28. package/src/dynamic-section.test.js +113 -0
  29. package/src/index.d.ts +2 -1
  30. package/src/index.js +2 -1
  31. package/src/index.test.js +3 -0
  32. package/src/journey/journey.d.ts +10 -0
  33. package/src/journey/journey.d.ts.map +1 -1
  34. package/src/journey/journey.js +15 -0
  35. package/src/lib/answer-utils.d.ts +22 -0
  36. package/src/lib/answer-utils.d.ts.map +1 -0
  37. package/src/lib/answer-utils.js +34 -0
  38. package/src/{components/manage-list/utils.test.js → lib/answer-utils.test.js} +9 -11
  39. package/src/lib/session-answer-store.d.ts.map +1 -1
  40. package/src/lib/session-answer-store.js +14 -6
  41. package/src/lib/session-answer-store.test.js +43 -0
  42. package/src/middleware/redirect-to-unanswered-question.d.ts.map +1 -1
  43. package/src/middleware/redirect-to-unanswered-question.js +4 -3
  44. package/src/middleware/redirect-to-unanswered-question.test.js +32 -0
  45. package/src/questions/question.d.ts +8 -3
  46. package/src/questions/question.d.ts.map +1 -1
  47. package/src/questions/question.js +16 -6
  48. package/src/questions/question.test.js +19 -0
  49. package/src/section.d.ts +19 -0
  50. package/src/section.d.ts.map +1 -1
  51. package/src/section.js +50 -9
  52. package/src/section.test.js +9 -0
  53. package/test/dynamic-journey.test.js +372 -0
  54. package/typedefs/question-props.d.ts +3 -2
  55. package/typedefs/question-types.d.ts +3 -0
  56. package/src/components/manage-list/utils.d.ts +0 -18
  57. package/src/components/manage-list/utils.d.ts.map +0 -1
  58. package/src/components/manage-list/utils.js +0 -35
@@ -14,4 +14,69 @@ describe('number-entry', () => {
14
14
 
15
15
  assert.strictEqual(question.capitaliseAnswer, true);
16
16
  });
17
+
18
+ it('should accept Affix objects for prefix and suffix', () => {
19
+ const PREFIX = { text: '£', classes: 'govuk-!-font-weight-bold' };
20
+ const SUFFIX = { text: 'kg' };
21
+
22
+ const question = new NumberEntryQuestion({
23
+ title: 'Question',
24
+ question: 'Question',
25
+ fieldName: 'question',
26
+ url: 'Question',
27
+ prefix: PREFIX,
28
+ suffix: SUFFIX
29
+ });
30
+
31
+ assert.strictEqual(question.prefix, PREFIX);
32
+ assert.strictEqual(question.suffix, SUFFIX);
33
+ });
34
+
35
+ it('should convert string suffix to Affix object and warn about deprecation', (t) => {
36
+ const warnMock = t.mock.method(console, 'warn', () => {});
37
+
38
+ const question = new NumberEntryQuestion({
39
+ title: 'Question',
40
+ question: 'Question',
41
+ fieldName: 'question',
42
+ url: 'Question',
43
+ suffix: 'kg'
44
+ });
45
+
46
+ assert.deepStrictEqual(question.suffix, { text: 'kg' });
47
+ assert.strictEqual(warnMock.mock.calls.length, 1);
48
+ assert.match(warnMock.mock.calls[0].arguments[0], /suffix.*deprecated/);
49
+ });
50
+
51
+ it('should leave prefix and suffix undefined when not provided', () => {
52
+ const question = new NumberEntryQuestion({
53
+ title: 'Question',
54
+ question: 'Question',
55
+ fieldName: 'question',
56
+ url: 'Question'
57
+ });
58
+
59
+ assert.strictEqual(question.prefix, undefined);
60
+ assert.strictEqual(question.suffix, undefined);
61
+ });
62
+
63
+ it('should add prefix and suffix to the view model', () => {
64
+ const PREFIX = { text: '£' };
65
+ const SUFFIX = { text: 'kg' };
66
+
67
+ const question = new NumberEntryQuestion({
68
+ title: 'Question',
69
+ question: 'Question',
70
+ fieldName: 'question',
71
+ url: 'Question',
72
+ prefix: PREFIX,
73
+ suffix: SUFFIX
74
+ });
75
+
76
+ const viewModel = { question: {} };
77
+ question.addCustomDataToViewModel(viewModel);
78
+
79
+ assert.strictEqual(viewModel.question.prefix, PREFIX);
80
+ assert.strictEqual(viewModel.question.suffix, SUFFIX);
81
+ });
17
82
  });
@@ -1 +1 @@
1
- {"version":3,"file":"question.d.ts","sourceRoot":"","sources":["question.js"],"names":[],"mappings":"AAGA,+BAAgC,QAAG,CAAC;AACpC,mCAAoC,GAAG,CAAC;AAExC;;GAEG;AACH;IACC;;OAEG;IACH,gLAFW,OAAO,+BAA+B,EAAE,6BAA6B,EAwB/E;IAPA,yFAA0C;IAC1C,0BAAkB;IAClB,qDAAsE;IACtE,iDAA8D;IAC9D,uCAA0C;IAC1C,gCAA8B;IAC9B,2CAAkD;IAkBnD,+NAOC;IAED,yCAEC;IAYD;;OAEG;IACH,iJAuBC;CACD;;yBAtGwB,WAAW"}
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,iJAyBC;CACD;;yBAxGwB,WAAW"}
@@ -77,7 +77,9 @@ export class TextEntryRedactQuestion extends Question {
77
77
  * returns the formatted answers values to be used to build task list elements
78
78
  */
79
79
  formatAnswerForSummary(sectionSegment, journey, answer, capitals = true) {
80
- const redacted = journey.response.answers[this.fieldName + 'Redacted'];
80
+ // get the response/answers for the section we're in - which might be a dynamic section
81
+ const response = journey.responseForSection(sectionSegment);
82
+ const redacted = response.answers[this.fieldName + 'Redacted'];
81
83
  let toShow;
82
84
  if (this.onlyShowRedactedValueForSummary) {
83
85
  toShow = redacted;
@@ -211,6 +211,9 @@ describe('./src/dynamic-forms/components/text-entry-redact/question.js', () => {
211
211
  getCurrentQuestionUrl: () => {
212
212
  return '/redacted-comment';
213
213
  },
214
+ responseForSection() {
215
+ return this.response;
216
+ },
214
217
  response: {
215
218
  answers: {}
216
219
  }
@@ -248,6 +251,9 @@ describe('./src/dynamic-forms/components/text-entry-redact/question.js', () => {
248
251
  getCurrentQuestionUrl: () => {
249
252
  return '/redacted-comment';
250
253
  },
254
+ responseForSection() {
255
+ return this.response;
256
+ },
251
257
  response: {
252
258
  answers: {}
253
259
  }
@@ -284,6 +290,9 @@ describe('./src/dynamic-forms/components/text-entry-redact/question.js', () => {
284
290
  getCurrentQuestionUrl: () => {
285
291
  return '/redacted-comment';
286
292
  },
293
+ responseForSection() {
294
+ return this.response;
295
+ },
287
296
  response: {
288
297
  answers: {}
289
298
  }
@@ -1 +1 @@
1
- {"version":3,"file":"question.d.ts","sourceRoot":"","sources":["question.js"],"names":[],"mappings":"AAOA;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AAEH;IAIC;;;OAGG;IACH,uEAHW,OAAO,+BAA+B,EAAE,6BAA6B,oBACrE,MAAM,CAAC,MAAM,WAAW,EAoBlC;IAzBD,gCAAgC;IAChC,SADW,KAAK,CAAC,UAAU,CAAC,CACpB;IAoBP,6BAAgD;IAEhD,0BAAkB;IAClB,yBAA+C;IAGhD;;;;;;;;;;;;OAYG;IACH,kCAXW,OAAO,kBAAkB,EAAE,OAAO,WAClC,OAAO,UAAU,EAAE,OAAO,mBAC1B,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,YACvB,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,WACvB,OAAO,+BAA+B,EAAE,+BAA+B,GACrE,OAAO,+BAA+B,EAAE,iBAAiB,GAAG;QACpE,QAAQ,EAAE,OAAO,+BAA+B,EAAE,iBAAiB,CAAC,UAAU,CAAC,GAAG;YAChF,OAAO,EAAC,UAAU,EAAE,CAAA;SACrB,CAAA;KACF,CAmDH;IAED;;;;;OAKG,CACH,mBAJW,OAAO,SAAS,EAAE,OAAO,mBACzB,eAAe,GACb,OAAO,CAAC;QAAE,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAA;KAAE,CAAC,CAgCzD;IAED;;OAEG;IACH,6HAaC;CACD;;;;;;;;;;yBArKY;IACR,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,EAAE,MAAM,CAAC;IACZ,IAAI,CAAC,EAAE,MAAM,CAAC;IAChB,OAAO,CAAC,EAAE,OAAO,GAAG,SAAS,CAAC;IAC9B,UAAU,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACpC,SAAS,CAAC,EAAE,WAAW,CAAC;IACxB,WAAW,EAAE;QACX,SAAS,EAAE,MAAM,CAAC;QACd,MAAM,EAAE,MAAM,CAAC;QACnB,KAAK,CAAC,EAAE,OAAO,CAAC;QACZ,KAAK,CAAC,EAAE,MAAM,CAAC;QACf,IAAI,CAAC,EAAE,MAAM,CAAC;QAClB,gBAAgB,CAAC,EAAE,MAAM,CAAC;KAC3B,CAAA;CACH;yBA5BsB,6BAA6B"}
1
+ {"version":3,"file":"question.d.ts","sourceRoot":"","sources":["question.js"],"names":[],"mappings":"AAOA;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AAEH;IAIC;;;OAGG;IACH,uEAHW,OAAO,+BAA+B,EAAE,6BAA6B,oBACrE,MAAM,CAAC,MAAM,WAAW,EAoBlC;IAzBD,gCAAgC;IAChC,SADW,KAAK,CAAC,UAAU,CAAC,CACpB;IAoBP,6BAAgD;IAEhD,0BAAkB;IAClB,yBAA+C;IAGhD;;;;;;;;;;;;OAYG;IACH,kCAXW,OAAO,kBAAkB,EAAE,OAAO,WAClC,OAAO,UAAU,EAAE,OAAO,mBAC1B,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,YACvB,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,WACvB,OAAO,+BAA+B,EAAE,+BAA+B,GACrE,OAAO,+BAA+B,EAAE,iBAAiB,GAAG;QACpE,QAAQ,EAAE,OAAO,+BAA+B,EAAE,iBAAiB,CAAC,UAAU,CAAC,GAAG;YAChF,OAAO,EAAC,UAAU,EAAE,CAAA;SACrB,CAAA;KACF,CAmDH;IAED;;;;;OAKG,CACH,mBAJW,OAAO,SAAS,EAAE,OAAO,mBACzB,eAAe,GACb,OAAO,CAAC;QAAE,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAA;KAAE,CAAC,CAgCzD;IAED;;OAEG;IACH,6HAeC;CACD;;;;;;;;;;yBAvKY;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"}
@@ -169,7 +169,9 @@ export class UnitOptionEntryQuestion extends Question {
169
169
  const selectedOption = this.options.find((option) => option.value === answer);
170
170
  const conversionFactor =
171
171
  (!conditionalIsJustHTML(selectedOption?.conditional) && selectedOption?.conditional.conversionFactor) || 1;
172
- const unconvertedAnswer = journey.response.answers[this.conditionalFieldName];
172
+ // get the response/answers for the section we're in - which might be a dynamic section
173
+ const response = journey.responseForSection(sectionSegment);
174
+ const unconvertedAnswer = response.answers[this.conditionalFieldName];
173
175
 
174
176
  const answerQuantity = Number(unconvertedAnswer) / conversionFactor;
175
177
  if (isNaN(answerQuantity)) throw new Error('Conditional answer had an unexpected type');
@@ -75,6 +75,9 @@ describe('./src/dynamic-forms/components/unit-option-entry/question.js', () => {
75
75
  [CONDITIONAL_FIELDNAME]: '1.123456789'
76
76
  }
77
77
  },
78
+ responseForSection() {
79
+ return this.response;
80
+ },
78
81
  getCurrentQuestionUrl: mock.fn()
79
82
  };
80
83
  const result = unitOptionEntryQuestion.formatAnswerForSummary('test', journey, 'ha');
@@ -87,6 +90,9 @@ describe('./src/dynamic-forms/components/unit-option-entry/question.js', () => {
87
90
  [CONDITIONAL_FIELDNAME]: '1'
88
91
  }
89
92
  },
93
+ responseForSection() {
94
+ return this.response;
95
+ },
90
96
  getCurrentQuestionUrl: mock.fn()
91
97
  };
92
98
  const result = unitOptionEntryQuestion.formatAnswerForSummary('test', journey, 'ha');
@@ -99,6 +105,9 @@ describe('./src/dynamic-forms/components/unit-option-entry/question.js', () => {
99
105
  [CONDITIONAL_FIELDNAME]: 'hello'
100
106
  }
101
107
  },
108
+ responseForSection() {
109
+ return this.response;
110
+ },
102
111
  getCurrentQuestionUrl: mock.fn()
103
112
  };
104
113
  assert.throws(() => {
@@ -20,6 +20,9 @@ export function question(req: import("express-serve-static-core").Request<import
20
20
  * @property {boolean} isManageListItem
21
21
  * @property {string} [manageListQuestionFieldName]
22
22
  * @property {boolean} [manageListItemRemove]
23
+ * @property {boolean} isDynamicSection is this save action for a question within a DynamicSection?
24
+ * @property {string} [dynamicSectionId] if this is within a DynamicSection, what is the section ID (segment)
25
+ * @property {string} [dynamicSectionFieldName] if this is within a DynamicSection, what is the fieldName for the array?
23
26
  * @property {Object<string, any>} data
24
27
  */
25
28
  /**
@@ -31,9 +34,6 @@ export function question(req: import("express-serve-static-core").Request<import
31
34
  * @returns {import('express').Handler}
32
35
  */
33
36
  export function buildSave(saveData: SaveDataFn, redirectToTaskListOnSuccess?: boolean): import("express").Handler;
34
- export type Journey = import("./journey/journey.js").Journey;
35
- export type Question = import("./questions/question.js").Question;
36
- export type Section = import("./section.js").Section;
37
37
  export type SectionView = {
38
38
  heading: string;
39
39
  status: string;
@@ -62,6 +62,18 @@ export type SaveParams = {
62
62
  isManageListItem: boolean;
63
63
  manageListQuestionFieldName?: string | undefined;
64
64
  manageListItemRemove?: boolean | undefined;
65
+ /**
66
+ * is this save action for a question within a DynamicSection?
67
+ */
68
+ isDynamicSection: boolean;
69
+ /**
70
+ * if this is within a DynamicSection, what is the section ID (segment)
71
+ */
72
+ dynamicSectionId?: string | undefined;
73
+ /**
74
+ * if this is within a DynamicSection, what is the fieldName for the array?
75
+ */
76
+ dynamicSectionFieldName?: string | undefined;
65
77
  data: {
66
78
  [x: string]: any;
67
79
  };
@@ -1 +1 @@
1
- {"version":3,"file":"controller.d.ts","sourceRoot":"","sources":["controller.js"],"names":[],"mappings":"AA+DA;;;GAGG;AACH,qCAHW,MAAM,GACJ,OAAO,SAAS,EAAE,OAAO,CAIrC;AAED;;;;;GAKG;AACH,0BALW,OAAO,SAAS,EAAE,OAAO,OACzB,OAAO,SAAS,EAAE,QAAQ,eAC1B,MAAM,YACN,MAAM,iBA8DhB;;AAmDD;;;;;;;;;;GAUG;AAEH;;GAEG;AAEH;;;;GAIG;AACH,oCAJW,UAAU,gCACV,OAAO,GACL,OAAO,SAAS,EAAE,OAAO,CAsFrC;sBA7RY,OAAO,sBAAsB,EAAE,OAAO;uBACtC,OAAO,yBAAyB,EAAE,QAAQ;sBAC1C,OAAO,cAAc,EAAE,OAAO;;aAK7B,MAAM;YACN,MAAM;UAEjB;QAAiC,IAAI,EAA1B,KAAK,CAAE,OAAO,CAAC;KAC5B;;;SAIa;QAAE,IAAI,EAAE,MAAM,CAAA;KAAE;WAChB;QAAE,IAAI,EAAE,MAAM,CAAA;KAAE,GAAG;QAAE,IAAI,EAAE,MAAM,CAAA;KAAE;;eAC1B,OAAO,+BAA+B,EAAE,UAAU,EAAE;;;;SAqK7D,OAAO,SAAS,EAAE,OAAO;SACzB,OAAO,SAAS,EAAE,QAAQ;eAC1B,MAAM;iBACN,MAAM;sBACN,OAAO;;;;;;;yBAOR,CAAC,MAAM,EAAE,UAAU,KAAK,OAAO,CAAC,IAAI,CAAC"}
1
+ {"version":3,"file":"controller.d.ts","sourceRoot":"","sources":["controller.js"],"names":[],"mappings":"AAyDA;;;GAGG;AACH,qCAHW,MAAM,GACJ,OAAO,SAAS,EAAE,OAAO,CAIrC;AAED;;;;;GAKG;AACH,0BALW,OAAO,SAAS,EAAE,OAAO,OACzB,OAAO,SAAS,EAAE,QAAQ,eAC1B,MAAM,YACN,MAAM,iBAmEhB;;AAmDD;;;;;;;;;;;;;GAaG;AAEH;;GAEG;AAEH;;;;GAIG;AACH,oCAJW,UAAU,gCACV,OAAO,GACL,OAAO,SAAS,EAAE,OAAO,CA4FrC;;aApSa,MAAM;YACN,MAAM;UAEjB;QAAiC,IAAI,EAA1B,KAAK,CAAE,OAAO,CAAC;KAC5B;;;SAIa;QAAE,IAAI,EAAE,MAAM,CAAA;KAAE;WAChB;QAAE,IAAI,EAAE,MAAM,CAAA;KAAE,GAAG;QAAE,IAAI,EAAE,MAAM,CAAA;KAAE;;eAC1B,OAAO,+BAA+B,EAAE,UAAU,EAAE;;;;SA0K7D,OAAO,SAAS,EAAE,OAAO;SACzB,OAAO,SAAS,EAAE,QAAQ;eAC1B,MAAM;iBACN,MAAM;sBACN,OAAO;;;;;;sBAGP,OAAO;;;;;;;;;;;;;yBAOR,CAAC,MAAM,EAAE,UAAU,KAAK,OAAO,CAAC,IAAI,CAAC"}
package/src/controller.js CHANGED
@@ -1,16 +1,10 @@
1
1
  import { SECTION_STATUS } from './section.js';
2
2
  import questionUtils from './components/utils/question-utils.js';
3
- import { answerObjectForManageListSaving } from '#src/components/manage-list/utils.js';
3
+ import { answerObjectForListItemSaving } from '#src/lib/answer-utils.js';
4
4
  import { booleanToYesNoValue } from '#src/components/boolean/question.js';
5
5
  import { MANAGE_LIST_ACTIONS } from '#src/components/manage-list/manage-list-actions.js';
6
6
  import { toArray } from '#src/lib/utils.js';
7
7
 
8
- /**
9
- * @typedef {import('./journey/journey.js').Journey} Journey
10
- * @typedef {import('./questions/question.js').Question} Question
11
- * @typedef {import('./section.js').Section} Section
12
- */
13
-
14
8
  /**
15
9
  * @typedef {Object} SectionView
16
10
  * @property {string} heading
@@ -77,7 +71,7 @@ export function buildList(viewData = {}) {
77
71
  */
78
72
  export async function list(req, res, pageCaption, viewData) {
79
73
  //render check your answers view
80
- /** @type {Journey} */
74
+ /** @type {import('./journey/journey.js').Journey} */
81
75
  const journey = res.locals.journey;
82
76
  /** @type {import("./journey/journey-response.js").JourneyResponse} */
83
77
  const journeyResponse = res.locals.journeyResponse;
@@ -92,6 +86,9 @@ export async function list(req, res, pageCaption, viewData) {
92
86
  const status = section.getStatus(journeyResponse);
93
87
  const sectionView = buildSectionViewModel(section.name, status);
94
88
 
89
+ // answers for this section may be within an array, for example dynamic sections
90
+ const response = section.getResponse(journeyResponse);
91
+
95
92
  // update completed count
96
93
  if (status === SECTION_STATUS.COMPLETE) {
97
94
  summaryListData.completedSectionCount++;
@@ -104,11 +101,13 @@ export async function list(req, res, pageCaption, viewData) {
104
101
  continue;
105
102
  }
106
103
 
107
- if (!question.shouldDisplay(journeyResponse)) {
104
+ if (!question.shouldDisplay(response)) {
108
105
  continue;
109
106
  }
110
107
 
111
- const answers = journey.response?.answers;
108
+ // answers here may be from an array for dynamic sections
109
+ // this is handled by section.responseForSection above
110
+ const answers = response.answers;
112
111
  let answer = answers[question.fieldName];
113
112
  const conditionalAnswer = questionUtils.getConditionalAnswer(answers, question, answer);
114
113
  if (conditionalAnswer) {
@@ -195,6 +194,9 @@ export async function question(req, res) {
195
194
  * @property {boolean} isManageListItem
196
195
  * @property {string} [manageListQuestionFieldName]
197
196
  * @property {boolean} [manageListItemRemove]
197
+ * @property {boolean} isDynamicSection is this save action for a question within a DynamicSection?
198
+ * @property {string} [dynamicSectionId] if this is within a DynamicSection, what is the section ID (segment)
199
+ * @property {string} [dynamicSectionFieldName] if this is within a DynamicSection, what is the fieldName for the array?
198
200
  * @property {Object<string, any>} data
199
201
  */
200
202
 
@@ -229,6 +231,7 @@ export function buildSave(saveData, redirectToTaskListOnSuccess) {
229
231
  return res.redirect(journey.taskListUrl);
230
232
  }
231
233
  }
234
+ const isDynamicSection = section.isDynamicSection;
232
235
 
233
236
  try {
234
237
  // check for validation errors
@@ -248,6 +251,9 @@ export function buildSave(saveData, redirectToTaskListOnSuccess) {
248
251
  isManageListItem: question.isInManageListSection,
249
252
  manageListQuestionFieldName: manageListQuestion?.fieldName,
250
253
  manageListItemRemove: req.params?.manageListAction === MANAGE_LIST_ACTIONS.REMOVE,
254
+ isDynamicSection,
255
+ dynamicSectionId: isDynamicSection ? section.segment : undefined,
256
+ dynamicSectionFieldName: isDynamicSection ? section.fieldName : undefined,
251
257
  data
252
258
  });
253
259
 
@@ -264,7 +270,9 @@ export function buildSave(saveData, redirectToTaskListOnSuccess) {
264
270
  // as question.shouldDisplay checks the response and is used to determine the next question
265
271
  let answers = journeyResponse.answers;
266
272
  if (question.isInManageListSection) {
267
- answers = answerObjectForManageListSaving(journeyResponse, manageListQuestion, req.params);
273
+ answers = answerObjectForListItemSaving(journeyResponse, manageListQuestion, req.params.manageListItemId);
274
+ } else if (isDynamicSection) {
275
+ answers = answerObjectForListItemSaving(journeyResponse, section, section.segment);
268
276
  }
269
277
  for (const [k, v] of Object.entries(data?.answers || {})) {
270
278
  if (typeof v === 'boolean') {