@planning-inspectorate/dynamic-forms 3.20.5 → 3.22.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 (69) hide show
  1. package/README.md +100 -0
  2. package/docs/Dynamic Sections.md +47 -0
  3. package/docs/Getting Started.md +8 -2
  4. package/package.json +1 -1
  5. package/src/components/boolean/question.d.ts +1 -1
  6. package/src/components/boolean/question.js +1 -1
  7. package/src/components/date/question.d.ts +1 -1
  8. package/src/components/date/question.js +1 -1
  9. package/src/components/date-period/question.d.ts +1 -1
  10. package/src/components/date-period/question.js +1 -1
  11. package/src/components/date-time/question.d.ts +1 -1
  12. package/src/components/date-time/question.js +1 -1
  13. package/src/components/manage-list/question.d.ts.map +1 -1
  14. package/src/components/manage-list/question.js +5 -0
  15. package/src/components/manage-list/question.test.js +3 -1
  16. package/src/components/multi-field-input/question.d.ts.map +1 -1
  17. package/src/components/multi-field-input/question.js +8 -6
  18. package/src/components/multi-field-input/question.test.js +126 -213
  19. package/src/components/single-line-input/index.njk +4 -0
  20. package/src/components/single-line-input/question.d.ts +2 -0
  21. package/src/components/single-line-input/question.d.ts.map +1 -1
  22. package/src/components/single-line-input/question.js +4 -0
  23. package/src/components/single-line-input/question.test.js +6 -0
  24. package/src/components/text-entry-redact/question.d.ts.map +1 -1
  25. package/src/components/text-entry-redact/question.js +3 -1
  26. package/src/components/text-entry-redact/question.test.js +9 -0
  27. package/src/components/unit-option-entry/question.d.ts +1 -1
  28. package/src/components/unit-option-entry/question.d.ts.map +1 -1
  29. package/src/components/unit-option-entry/question.js +4 -2
  30. package/src/components/unit-option-entry/question.test.js +9 -0
  31. package/src/controller.d.ts +15 -3
  32. package/src/controller.d.ts.map +1 -1
  33. package/src/controller.js +19 -11
  34. package/src/controller.test.js +182 -153
  35. package/src/dynamic-section.d.ts +40 -0
  36. package/src/dynamic-section.d.ts.map +1 -0
  37. package/src/dynamic-section.js +51 -0
  38. package/src/dynamic-section.test.js +113 -0
  39. package/src/index.d.ts +2 -1
  40. package/src/index.js +2 -1
  41. package/src/index.test.js +3 -0
  42. package/src/journey/journey.d.ts +10 -0
  43. package/src/journey/journey.d.ts.map +1 -1
  44. package/src/journey/journey.js +15 -0
  45. package/src/lib/answer-utils.d.ts +22 -0
  46. package/src/lib/answer-utils.d.ts.map +1 -0
  47. package/src/lib/answer-utils.js +34 -0
  48. package/src/{components/manage-list/utils.test.js → lib/answer-utils.test.js} +9 -11
  49. package/src/lib/session-answer-store.d.ts.map +1 -1
  50. package/src/lib/session-answer-store.js +14 -6
  51. package/src/lib/session-answer-store.test.js +43 -0
  52. package/src/middleware/redirect-to-unanswered-question.d.ts.map +1 -1
  53. package/src/middleware/redirect-to-unanswered-question.js +4 -3
  54. package/src/middleware/redirect-to-unanswered-question.test.js +32 -0
  55. package/src/questions/options-question.js +1 -1
  56. package/src/questions/question.d.ts +8 -3
  57. package/src/questions/question.d.ts.map +1 -1
  58. package/src/questions/question.js +16 -6
  59. package/src/questions/question.test.js +19 -0
  60. package/src/section.d.ts +19 -0
  61. package/src/section.d.ts.map +1 -1
  62. package/src/section.js +50 -9
  63. package/src/section.test.js +9 -0
  64. package/test/dynamic-journey.test.js +372 -0
  65. package/typedefs/question-props.d.ts +3 -1
  66. package/typedefs/question-types.d.ts +3 -0
  67. package/src/components/manage-list/utils.d.ts +0 -18
  68. package/src/components/manage-list/utils.d.ts.map +0 -1
  69. package/src/components/manage-list/utils.js +0 -35
@@ -0,0 +1,40 @@
1
+ /**
2
+ * Use a dynamic section for sections which are generated from an array of objects
3
+ *
4
+ * This can be useful for adding a section for each entry in a ManageList, or each row in a database
5
+ * The controller logic, when encountering a DynamicSection, will check for an answer object within
6
+ * `journeyResponse.answers[fieldName]`, matching by the `id` property
7
+ *
8
+ * ```
9
+ * answers = {
10
+ * myField: 'some answer',
11
+ * list: [
12
+ * { id: 'person-id-1', name: 'Grogu' },
13
+ * { id: 'person-id-2', name: 'Mando' }
14
+ * ]
15
+ * }
16
+ * ```
17
+ *
18
+ * In this example, create a DynamicSection for each list entry:
19
+ *
20
+ * ```
21
+ * function createPersonSection(person) {
22
+ * return new DynamicSection(person.name, person.id, 'list')
23
+ * .addQuestion(questions.name)
24
+ * // ... add questions
25
+ * }
26
+ * ```
27
+ *
28
+ * When using with buildSave, be sure to handle the parameters: `isDynamicSection`, `dynamicSectionId`,`dynamicSectionFieldName`
29
+ */
30
+ export class DynamicSection extends Section {
31
+ /**
32
+ * @param name
33
+ * @param segment
34
+ * @param fieldName the key used in the answers object, which will be an array of answers for this section
35
+ */
36
+ constructor(name: any, segment: any, fieldName: any);
37
+ fieldName: any;
38
+ }
39
+ import { Section } from '#section';
40
+ //# sourceMappingURL=dynamic-section.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"dynamic-section.d.ts","sourceRoot":"","sources":["dynamic-section.js"],"names":[],"mappings":"AAEA;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA4BG;AACH;IACC;;;;OAIG;IACH,qDAIC;IADA,eAA0B;CAU3B;wBAlDuB,UAAU"}
@@ -0,0 +1,51 @@
1
+ import { Section } from '#section';
2
+
3
+ /**
4
+ * Use a dynamic section for sections which are generated from an array of objects
5
+ *
6
+ * This can be useful for adding a section for each entry in a ManageList, or each row in a database
7
+ * The controller logic, when encountering a DynamicSection, will check for an answer object within
8
+ * `journeyResponse.answers[fieldName]`, matching by the `id` property
9
+ *
10
+ * ```
11
+ * answers = {
12
+ * myField: 'some answer',
13
+ * list: [
14
+ * { id: 'person-id-1', name: 'Grogu' },
15
+ * { id: 'person-id-2', name: 'Mando' }
16
+ * ]
17
+ * }
18
+ * ```
19
+ *
20
+ * In this example, create a DynamicSection for each list entry:
21
+ *
22
+ * ```
23
+ * function createPersonSection(person) {
24
+ * return new DynamicSection(person.name, person.id, 'list')
25
+ * .addQuestion(questions.name)
26
+ * // ... add questions
27
+ * }
28
+ * ```
29
+ *
30
+ * When using with buildSave, be sure to handle the parameters: `isDynamicSection`, `dynamicSectionId`,`dynamicSectionFieldName`
31
+ */
32
+ export class DynamicSection extends Section {
33
+ /**
34
+ * @param name
35
+ * @param segment
36
+ * @param fieldName the key used in the answers object, which will be an array of answers for this section
37
+ */
38
+ constructor(name, segment, fieldName) {
39
+ super(name, segment);
40
+
41
+ this.fieldName = fieldName;
42
+ }
43
+
44
+ /**
45
+ * Indicate that this section is dynamic, and answers should be pulled from an array
46
+ * @returns {boolean}
47
+ */
48
+ get isDynamicSection() {
49
+ return true;
50
+ }
51
+ }
@@ -0,0 +1,113 @@
1
+ import { describe, it, mock } from 'node:test';
2
+ import assert from 'node:assert';
3
+ import { DynamicSection } from '#src/dynamic-section.js';
4
+ import { SECTION_STATUS } from '#section';
5
+ import { Question } from '#question';
6
+
7
+ // use a real question to avoid mocking lots of logic
8
+ const mockQuestion = (fieldName = 'fieldName', required = true) =>
9
+ new Question({
10
+ fieldName: fieldName,
11
+ title: 'question',
12
+ question: 'question',
13
+ viewFolder: 'folder',
14
+ validators: [
15
+ {
16
+ isRequired() {
17
+ return required;
18
+ }
19
+ }
20
+ ]
21
+ });
22
+
23
+ describe('dynamic-section', () => {
24
+ describe('responseForSection', () => {
25
+ it('should return inner object for dynamic section', () => {
26
+ const section = new DynamicSection('s1', 'id-1', 'myList');
27
+ const answers = { id: 'id-1', myField: 'answer' };
28
+ const res = {
29
+ answers: {
30
+ myList: [{ id: 'id-2' }, { id: 'id-3' }, answers]
31
+ }
32
+ };
33
+ const got = section.getResponse(res);
34
+ assert.strictEqual(got?.answers, answers);
35
+ });
36
+ });
37
+
38
+ describe('getStatus', () => {
39
+ it('should use section answers for status', () => {
40
+ const s1Answers = { id: 'S', fieldOne: 'answer' };
41
+ const mockJourneyResponse = {
42
+ answers: {
43
+ visitFrequently: 'Answer 1',
44
+ anotherFieldName: 'Answer 2',
45
+ myList: [s1Answers]
46
+ }
47
+ };
48
+
49
+ const requiredQuestion = mockQuestion('visitFrequently');
50
+
51
+ const section = new DynamicSection('s1', 'S', 'myList');
52
+ section.addQuestion(requiredQuestion);
53
+
54
+ mock.method(requiredQuestion, 'shouldDisplay');
55
+ mock.method(requiredQuestion, 'isAnswered');
56
+
57
+ section.getStatus(mockJourneyResponse);
58
+
59
+ assert.strictEqual(requiredQuestion.shouldDisplay.mock.callCount(), 1);
60
+ assert.strictEqual(requiredQuestion.isAnswered.mock.callCount(), 2);
61
+ assert.strictEqual(requiredQuestion.shouldDisplay.mock.calls[0].arguments[0].answers, s1Answers);
62
+ assert.strictEqual(requiredQuestion.isAnswered.mock.calls[0].arguments[0].answers, s1Answers);
63
+ });
64
+
65
+ it('should return NOT_STARTED when no answers are given', () => {
66
+ const mockJourneyResponse = {
67
+ answers: {}
68
+ };
69
+ const section = new DynamicSection('s1', 'S', 'myList');
70
+ section.addQuestion(mockQuestion());
71
+ const result = section.getStatus(mockJourneyResponse);
72
+ assert.strictEqual(result, SECTION_STATUS.NOT_STARTED);
73
+ const isComplete = section.isComplete(mockJourneyResponse);
74
+ assert.strictEqual(isComplete, false);
75
+ });
76
+
77
+ it('should return IN_PROGRESS when at least one answer is given', () => {
78
+ const mockJourneyResponse = {
79
+ answers: {
80
+ myList: [{ id: 'S', visitFrequently: 'Answer 1' }]
81
+ }
82
+ };
83
+
84
+ const section = new DynamicSection('s1', 'S', 'myList');
85
+ section.addQuestion(mockQuestion());
86
+ section.addQuestion(mockQuestion('visitFrequently'));
87
+ const result = section.getStatus(mockJourneyResponse);
88
+ assert.strictEqual(result, SECTION_STATUS.IN_PROGRESS);
89
+ const isComplete = section.isComplete(mockJourneyResponse);
90
+ assert.strictEqual(isComplete, false);
91
+ });
92
+
93
+ it('should return COMPLETE when all required answers are given', () => {
94
+ const s1Answers = { id: 'S', visitFrequently: 'answer', anotherFieldName: true };
95
+ const mockJourneyResponse = {
96
+ answers: {
97
+ myList: [s1Answers]
98
+ }
99
+ };
100
+
101
+ const section = new DynamicSection('s1', 'S', 'myList');
102
+ section.addQuestion(mockQuestion('visitFrequently'));
103
+ section.addQuestion(mockQuestion('anotherFieldName'));
104
+ section.addQuestion(mockQuestion('someQuestion', false));
105
+
106
+ const result = section.getStatus(mockJourneyResponse);
107
+ assert.strictEqual(result, SECTION_STATUS.COMPLETE);
108
+
109
+ const isComplete = section.isComplete(mockJourneyResponse);
110
+ assert.strictEqual(isComplete, true);
111
+ });
112
+ });
113
+ });
package/src/index.d.ts CHANGED
@@ -32,6 +32,8 @@ export * from "./lib/utils.js";
32
32
  export * from "./middleware/build-get-journey.js";
33
33
  export * from "./middleware/redirect-to-unanswered-question.js";
34
34
  export * from "./questions/options-question.js";
35
+ export * from "./dynamic-section.js";
36
+ export * from "./section.js";
35
37
  export * from "./validator/address-validator.js";
36
38
  export * from "./validator/base-validator.js";
37
39
  export * from "./validator/conditional-required-validator.js";
@@ -58,6 +60,5 @@ export { JourneyResponse } from "./journey/journey-response.js";
58
60
  export { Question } from "./questions/question.js";
59
61
  export { createQuestions } from "./questions/create-questions.js";
60
62
  export { questionClasses } from "./questions/questions.js";
61
- export { Section } from "./section.js";
62
63
  export { default as validate } from "./validator/validator.js";
63
64
  //# sourceMappingURL=index.d.ts.map
package/src/index.js CHANGED
@@ -55,7 +55,8 @@ export * from './questions/options-question.js';
55
55
  export { questionClasses } from './questions/questions.js';
56
56
 
57
57
  // Section
58
- export { Section } from './section.js';
58
+ export * from './dynamic-section.js';
59
+ export * from './section.js';
59
60
 
60
61
  // Validators
61
62
  export * from './validator/address-validator.js';
package/src/index.test.js CHANGED
@@ -52,6 +52,7 @@ describe('public API', () => {
52
52
  assert.equal(typeof api.Question, 'function');
53
53
  assert.equal(typeof api.createQuestions, 'function');
54
54
  assert.equal(typeof api.questionClasses, 'object');
55
+ assert.equal(typeof api.DynamicSection, 'function');
55
56
  assert.equal(typeof api.Section, 'function');
56
57
  assert.equal(typeof api.Journey, 'function');
57
58
  assert.equal(typeof api.JourneyResponse, 'function');
@@ -84,5 +85,7 @@ describe('public API', () => {
84
85
  assert.equal(typeof api.booleanToYesNoOrNull, 'function');
85
86
  assert.equal(typeof api.REDACT_CHARACTER, 'string');
86
87
  assert.equal(typeof api.TRUNCATED_MAX_LENGTH, 'number');
88
+ assert.equal(typeof api.END_OF_SECTION, 'symbol');
89
+ assert.equal(typeof api.SECTION_STATUS, 'object');
87
90
  });
88
91
  });
@@ -60,6 +60,16 @@ export class Journey {
60
60
  * @returns {import('../section.js').Section | undefined}
61
61
  */
62
62
  getSection(sectionSegment: string): import("../section.js").Section | undefined;
63
+ /**
64
+ * Get the response for the given section, required to support dynamic sections
65
+ *
66
+ * Often used in formatAnswerForSummary to get other answer fields.
67
+ *
68
+ * @param {string} sectionSegment
69
+ */
70
+ responseForSection(sectionSegment: string): import("./journey-response.js").JourneyResponse<import("#typedefs/journey-types.d.ts").JourneyAnswers> | {
71
+ answers: Record<string, unknown>;
72
+ };
63
73
  /**
64
74
  * gets a question from the object's sections based on a section + question names
65
75
  * @param {import('#typedefs/journey-types.d.ts').RouteParams} params
@@ -1 +1 @@
1
- {"version":3,"file":"journey.d.ts","sourceRoot":"","sources":["journey.js"],"names":[],"mappings":"AAQA;;;GAGG;AACH;IAwBC;;;;;;;;;;;;;;OAcG;IACH,qLAZG;QAAwB,SAAS,EAAzB,MAAM;QACyE,WAAW,EAA1F,CAAC,QAAQ,EAAE,OAAO,uBAAuB,EAAE,eAAe,KAAK,MAAM;QACpD,WAAW;QAC6B,QAAQ,EAAjE,OAAO,uBAAuB,EAAE,eAAe;QAC/B,eAAe,EAA/B,MAAM;QACU,gBAAgB,EAAhC,MAAM;QACW,uBAAuB;QACxB,YAAY,EAA5B,MAAM;QACY,eAAe;QACU,QAAQ,EAAnD,OAAO,eAAe,EAAE,OAAO,EAAE;QAChB,eAAe;KAC1C,EAoDA;IAzFD,8EAA8E;IAC9E,WADW,MAAM,CACP;IACV,6FAA6F;IAC7F,UADW,KAAK,CAAE,OAAO,eAAe,EAAE,OAAO,CAAC,CACpC;IACd,mHAAmH;IACnH,UADW,OAAO,uBAAuB,EAAE,eAAe,CACjD;IACT,wFAAwF;IACxF,SADW,MAAM,CACJ;IACb,sJAAsJ;IACtJ,aADW,CAAC,eAAe,EAAE,OAAO,uBAAuB,EAAE,eAAe,KAAK,MAAM,CAChE;IACvB,kEAAkE;IAClE,aADW,MAAM,CACA;IACjB,uEAAuE;IACvE,iBADW,MAAM,CACI;IACrB,qFAAqF;IACrF,kBADW,MAAM,CACK;IACtB,4GAA4G;IAC5G,yBADW,MAAM,CACY;IAC7B,qFAAqF;IACrF,iBADW,OAAO,CACM;IACxB,2DAA2D;IAC3D,cADU,MAAM,CACH;IAmEZ,+BAA8C;IA2C/C;;;;OAIG;IACH,2BAHW,MAAM,GACJ,OAAO,eAAe,EAAE,OAAO,GAAG,SAAS,CAMvD;IA8BD;;;;OAIG;IACH,4BAHW,OAAO,8BAA8B,EAAE,WAAW,GAChD,OAAO,0BAA0B,EAAE,QAAQ,GAAG,SAAS,CAiBnE;IAED;;;;;;;OAOG;IACH,4CAJG;QAAoE,MAAM,EAAlE,OAAO,8BAA8B,EAAE,WAAW;QACU,kBAAkB;KACtF,GAAU,MAAM,GAAC,IAAI,CAWvB;IAED;;;;;;;;OAQG;IACH,4BALW,OAAO,SAAS,EAAE,QAAQ,UAC1B,OAAO,8BAA8B,EAAE,WAAW,kFAEhD,IAAI,CAKhB;IAED;;;;;;;;OAQG;IACH,2BANW,OAAO,8BAA8B,EAAE,WAAW,oCAE1D;QAA0B,OAAO;QACmC,kBAAkB;KACtF,GAAU,MAAM,GAAC,IAAI,CAyEvB;IAED;;;;;OAKG;IACH,wBAAyB,gBAJd,MAI4B,EAAE,iBAH9B,MAG6C,KAF3C,MAAM,CAqBjB;IAEF;;;;OAIG;IACH,sCAAuC,iBAH5B,MAG2C,KAFzC,MAAM,CAIjB;IAEF;;;;;;OAMG;IACH,0BAA2B,gBALhB,MAK8B,EAAE,iBAJhC,MAI+C,EAAE,UAHjD,MAGyD,KAFvD,MAAM,CAuBjB;IAEF;;;OAGG;IACH,cAFa,OAAO,CAInB;IAED;;OAEG;IACH,6BAFW,OAAO,uBAAuB,EAAE,eAAe,QAKzD;;CACD"}
1
+ {"version":3,"file":"journey.d.ts","sourceRoot":"","sources":["journey.js"],"names":[],"mappings":"AAQA;;;GAGG;AACH;IAwBC;;;;;;;;;;;;;;OAcG;IACH,qLAZG;QAAwB,SAAS,EAAzB,MAAM;QACyE,WAAW,EAA1F,CAAC,QAAQ,EAAE,OAAO,uBAAuB,EAAE,eAAe,KAAK,MAAM;QACpD,WAAW;QAC6B,QAAQ,EAAjE,OAAO,uBAAuB,EAAE,eAAe;QAC/B,eAAe,EAA/B,MAAM;QACU,gBAAgB,EAAhC,MAAM;QACW,uBAAuB;QACxB,YAAY,EAA5B,MAAM;QACY,eAAe;QACU,QAAQ,EAAnD,OAAO,eAAe,EAAE,OAAO,EAAE;QAChB,eAAe;KAC1C,EAoDA;IAzFD,8EAA8E;IAC9E,WADW,MAAM,CACP;IACV,6FAA6F;IAC7F,UADW,KAAK,CAAE,OAAO,eAAe,EAAE,OAAO,CAAC,CACpC;IACd,mHAAmH;IACnH,UADW,OAAO,uBAAuB,EAAE,eAAe,CACjD;IACT,wFAAwF;IACxF,SADW,MAAM,CACJ;IACb,sJAAsJ;IACtJ,aADW,CAAC,eAAe,EAAE,OAAO,uBAAuB,EAAE,eAAe,KAAK,MAAM,CAChE;IACvB,kEAAkE;IAClE,aADW,MAAM,CACA;IACjB,uEAAuE;IACvE,iBADW,MAAM,CACI;IACrB,qFAAqF;IACrF,kBADW,MAAM,CACK;IACtB,4GAA4G;IAC5G,yBADW,MAAM,CACY;IAC7B,qFAAqF;IACrF,iBADW,OAAO,CACM;IACxB,2DAA2D;IAC3D,cADU,MAAM,CACH;IAmEZ,+BAA8C;IA2C/C;;;;OAIG;IACH,2BAHW,MAAM,GACJ,OAAO,eAAe,EAAE,OAAO,GAAG,SAAS,CAMvD;IAED;;;;;;OAMG;IACH,mCAFW,MAAM;;MAQhB;IA8BD;;;;OAIG;IACH,4BAHW,OAAO,8BAA8B,EAAE,WAAW,GAChD,OAAO,0BAA0B,EAAE,QAAQ,GAAG,SAAS,CAiBnE;IAED;;;;;;;OAOG;IACH,4CAJG;QAAoE,MAAM,EAAlE,OAAO,8BAA8B,EAAE,WAAW;QACU,kBAAkB;KACtF,GAAU,MAAM,GAAC,IAAI,CAWvB;IAED;;;;;;;;OAQG;IACH,4BALW,OAAO,SAAS,EAAE,QAAQ,UAC1B,OAAO,8BAA8B,EAAE,WAAW,kFAEhD,IAAI,CAKhB;IAED;;;;;;;;OAQG;IACH,2BANW,OAAO,8BAA8B,EAAE,WAAW,oCAE1D;QAA0B,OAAO;QACmC,kBAAkB;KACtF,GAAU,MAAM,GAAC,IAAI,CAyEvB;IAED;;;;;OAKG;IACH,wBAAyB,gBAJd,MAI4B,EAAE,iBAH9B,MAG6C,KAF3C,MAAM,CAqBjB;IAEF;;;;OAIG;IACH,sCAAuC,iBAH5B,MAG2C,KAFzC,MAAM,CAIjB;IAEF;;;;;;OAMG;IACH,0BAA2B,gBALhB,MAK8B,EAAE,iBAJhC,MAI+C,EAAE,UAHjD,MAGyD,KAFvD,MAAM,CAuBjB;IAEF;;;OAGG;IACH,cAFa,OAAO,CAInB;IAED;;OAEG;IACH,6BAFW,OAAO,uBAAuB,EAAE,eAAe,QAKzD;;CACD"}
@@ -153,6 +153,21 @@ export class Journey {
153
153
  });
154
154
  }
155
155
 
156
+ /**
157
+ * Get the response for the given section, required to support dynamic sections
158
+ *
159
+ * Often used in formatAnswerForSummary to get other answer fields.
160
+ *
161
+ * @param {string} sectionSegment
162
+ */
163
+ responseForSection(sectionSegment) {
164
+ const section = this.getSection(sectionSegment);
165
+ if (!section) {
166
+ throw new Error(`No section found for section segment: '${sectionSegment}'`);
167
+ }
168
+ return section.getResponse(this.response);
169
+ }
170
+
156
171
  /**
157
172
  * Get question within a section
158
173
  * @param {import('../section.js').Section} section
@@ -0,0 +1,22 @@
1
+ /**
2
+ *
3
+ * @param {import('#journey-response').JourneyResponse} response
4
+ * @param {{fieldName: string}} withFieldName
5
+ * @param {string} itemId
6
+ * @returns {Record<string, unknown>}
7
+ */
8
+ export function answerObjectForListItem(response: import("#journey-response").JourneyResponse, withFieldName: {
9
+ fieldName: string;
10
+ }, itemId: string): Record<string, unknown>;
11
+ /**
12
+ * Similar to answerObjectForListItem but will edit response and add a new array entry if not found
13
+ *
14
+ * @param {import('#journey-response').JourneyResponse} response
15
+ * @param {{fieldName: string}} withFieldName
16
+ * @param {string} itemId
17
+ * @returns {import('#typedefs/journey-types.d.ts').ManageListAnswers}
18
+ */
19
+ export function answerObjectForListItemSaving(response: import("#journey-response").JourneyResponse, withFieldName: {
20
+ fieldName: string;
21
+ }, itemId: string): import("#typedefs/journey-types.d.ts").ManageListAnswers;
22
+ //# sourceMappingURL=answer-utils.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"answer-utils.d.ts","sourceRoot":"","sources":["answer-utils.js"],"names":[],"mappings":"AAAA;;;;;;GAMG;AACH,kDALW,OAAO,mBAAmB,EAAE,eAAe,iBAC3C;IAAC,SAAS,EAAE,MAAM,CAAA;CAAC,UACnB,MAAM,GACJ,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAQnC;AAED;;;;;;;GAOG;AACH,wDALW,OAAO,mBAAmB,EAAE,eAAe,iBAC3C;IAAC,SAAS,EAAE,MAAM,CAAA;CAAC,UACnB,MAAM,GACJ,OAAO,8BAA8B,EAAE,iBAAiB,CAYpE"}
@@ -0,0 +1,34 @@
1
+ /**
2
+ *
3
+ * @param {import('#journey-response').JourneyResponse} response
4
+ * @param {{fieldName: string}} withFieldName
5
+ * @param {string} itemId
6
+ * @returns {Record<string, unknown>}
7
+ */
8
+ export function answerObjectForListItem(response, withFieldName, itemId) {
9
+ const answers = response.answers[withFieldName.fieldName];
10
+ if (!Array.isArray(answers)) {
11
+ return {};
12
+ }
13
+ return answers.find((a) => a.id === itemId) || {};
14
+ }
15
+
16
+ /**
17
+ * Similar to answerObjectForListItem but will edit response and add a new array entry if not found
18
+ *
19
+ * @param {import('#journey-response').JourneyResponse} response
20
+ * @param {{fieldName: string}} withFieldName
21
+ * @param {string} itemId
22
+ * @returns {import('#typedefs/journey-types.d.ts').ManageListAnswers}
23
+ */
24
+ export function answerObjectForListItemSaving(response, withFieldName, itemId) {
25
+ const answersList = response.answers[withFieldName?.fieldName] || (response.answers[withFieldName?.fieldName] = []);
26
+ let answers = answersList.find((item) => item.id === itemId);
27
+ if (!answers) {
28
+ answers = {
29
+ id: itemId
30
+ };
31
+ answersList.push(answers);
32
+ }
33
+ return answers;
34
+ }
@@ -1,9 +1,9 @@
1
1
  import { describe, it } from 'node:test';
2
2
  import assert from 'node:assert';
3
- import { answerObjectForManageList, answerObjectForManageListSaving } from '#src/components/manage-list/utils.js';
3
+ import { answerObjectForListItem, answerObjectForListItemSaving } from '#src/lib/answer-utils.js';
4
4
 
5
- describe('manage-list-utils', () => {
6
- describe('answerObjectForManageList', () => {
5
+ describe('answer-utils', () => {
6
+ describe('answerObjectForListItem', () => {
7
7
  it('should return the manage list item object for manage list questions', () => {
8
8
  const manageListQuestion = {
9
9
  fieldName: 'manageListQuestion'
@@ -18,7 +18,7 @@ describe('manage-list-utils', () => {
18
18
  }
19
19
  };
20
20
  const manageListItemId = '1';
21
- const got = answerObjectForManageList(journeyResponse, manageListQuestion, manageListItemId);
21
+ const got = answerObjectForListItem(journeyResponse, manageListQuestion, manageListItemId);
22
22
  assert.strictEqual(got, answers);
23
23
  });
24
24
 
@@ -36,16 +36,16 @@ describe('manage-list-utils', () => {
36
36
  }
37
37
  };
38
38
  const manageListItemId = '1';
39
- let got = answerObjectForManageList(journeyResponse, manageListQuestion, manageListItemId);
39
+ let got = answerObjectForListItem(journeyResponse, manageListQuestion, manageListItemId);
40
40
  assert.deepStrictEqual(got, {});
41
41
  // also fallback to {} if there is no manageListQuestion answer
42
42
  delete journeyResponse.answers.manageListQuestion;
43
- got = answerObjectForManageList(journeyResponse, manageListQuestion, manageListItemId);
43
+ got = answerObjectForListItem(journeyResponse, manageListQuestion, manageListItemId);
44
44
  assert.deepStrictEqual(got, {});
45
45
  });
46
46
  });
47
47
 
48
- describe('answerObjectForManageListSaving', () => {
48
+ describe('answerObjectForListItemSaving', () => {
49
49
  it('should add an array with a new object if not found', () => {
50
50
  const manageListQuestion = {
51
51
  fieldName: 'manageListQuestion'
@@ -53,8 +53,7 @@ describe('manage-list-utils', () => {
53
53
  const journeyResponse = {
54
54
  answers: {}
55
55
  };
56
- const params = { manageListItemId: '1' };
57
- const got = answerObjectForManageListSaving(journeyResponse, manageListQuestion, params);
56
+ const got = answerObjectForListItemSaving(journeyResponse, manageListQuestion, '1');
58
57
  assert.deepStrictEqual(got, { id: '1' });
59
58
  assert.ok(Array.isArray(journeyResponse.answers.manageListQuestion));
60
59
  assert.ok(journeyResponse.answers.manageListQuestion[0]?.id, '1');
@@ -72,8 +71,7 @@ describe('manage-list-utils', () => {
72
71
  manageListQuestion: [answers]
73
72
  }
74
73
  };
75
- const params = { manageListItemId: '1' };
76
- const got = answerObjectForManageListSaving(journeyResponse, manageListQuestion, params);
74
+ const got = answerObjectForListItemSaving(journeyResponse, manageListQuestion, '1');
77
75
  assert.strictEqual(got, answers);
78
76
  });
79
77
  });
@@ -1 +1 @@
1
- {"version":3,"file":"session-answer-store.d.ts","sourceRoot":"","sources":["session-answer-store.js"],"names":[],"mappings":"AAKA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAuCG;AACH,sDAJG;IAAsB,QAAQ;CAE9B,GAAU,OAAO,kBAAkB,EAAE,UAAU,CA8CjD;AASD;;;;;;;;;;;;;;;;;;;;;GAqBG;AACH,gFANG;IAA0C,GAAG,EAArC,OAAO,SAAS,EAAE,OAAO;IACV,SAAS,EAAxB,MAAM;IACuB,WAAW;;;IACxB,QAAQ;CAChC,GAAU,IAAI,CAkBhB;AAED;;;;;;GAMG;AACH,8DAJW,MAAM,aACN,MAAM,GACJ,OAAO,SAAS,EAAE,OAAO,CA2BrC;AA/ED;;;;GAIG;AACH,gCAFU,OAAO,kBAAkB,EAAE,UAAU,CAEW"}
1
+ {"version":3,"file":"session-answer-store.d.ts","sourceRoot":"","sources":["session-answer-store.js"],"names":[],"mappings":"AAKA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAuCG;AACH,sDAJG;IAAsB,QAAQ;CAE9B,GAAU,OAAO,kBAAkB,EAAE,UAAU,CAsDjD;AASD;;;;;;;;;;;;;;;;;;;;;GAqBG;AACH,gFANG;IAA0C,GAAG,EAArC,OAAO,SAAS,EAAE,OAAO;IACV,SAAS,EAAxB,MAAM;IACuB,WAAW;;;IACxB,QAAQ;CAChC,GAAU,IAAI,CAkBhB;AAED;;;;;;GAMG;AACH,8DAJW,MAAM,aACN,MAAM,GACJ,OAAO,SAAS,EAAE,OAAO,CA2BrC;AA/ED;;;;GAIG;AACH,gCAFU,OAAO,kBAAkB,EAAE,UAAU,CAEW"}
@@ -50,7 +50,10 @@ export function buildSaveDataToSession({ reqParam } = {}) {
50
50
  data,
51
51
  isManageListItem,
52
52
  manageListQuestionFieldName,
53
- manageListItemRemove = false
53
+ manageListItemRemove = false,
54
+ isDynamicSection,
55
+ dynamicSectionFieldName,
56
+ dynamicSectionId
54
57
  }) => {
55
58
  if (!req.session) {
56
59
  throw new Error('request session required');
@@ -64,12 +67,17 @@ export function buildSaveDataToSession({ reqParam } = {}) {
64
67
  }
65
68
  let answers = forms[journeyId] || (forms[journeyId] = {});
66
69
 
67
- if (isManageListItem) {
68
- const answersList = answers[manageListQuestionFieldName] || (answers[manageListQuestionFieldName] = []);
69
- answers = answersList.find((item) => item.id === req.params.manageListItemId);
70
+ if (isManageListItem || isDynamicSection) {
71
+ // manage list and dynamic sections can be handled the same, just with different properties
72
+ // for the fieldName and item id
73
+ const fieldName = isManageListItem ? manageListQuestionFieldName : dynamicSectionFieldName;
74
+ const itemId = isManageListItem ? req.params.manageListItemId : dynamicSectionId;
75
+
76
+ const answersList = answers[fieldName] || (answers[fieldName] = []);
77
+ answers = answersList.find((item) => item.id === itemId);
70
78
  if (!answers) {
71
- answers = { id: req.params.manageListItemId };
72
- answersList.push(answers);
79
+ answers = { id: itemId }; // answers object to manipulate and add other answers to
80
+ answersList.push(answers); // add the answers object to the array
73
81
  }
74
82
  } else if (manageListItemRemove && manageListQuestionFieldName && req.params.manageListItemId) {
75
83
  const answersList = answers[manageListQuestionFieldName];
@@ -123,6 +123,49 @@ describe('session-answer-store', () => {
123
123
  });
124
124
  });
125
125
 
126
+ it('should add a new dynamic section when not present in the session', async () => {
127
+ const req = mockReq();
128
+ req.session = {};
129
+ const journeyId = 'j-1';
130
+ const data = { answers: { q1: 'a1' } };
131
+ const isDynamicSection = true;
132
+ const dynamicSectionFieldName = 'myList';
133
+ const dynamicSectionId = 'section-1';
134
+ const saveDataToSession = buildSaveDataToSession();
135
+ await saveDataToSession({ req, journeyId, data, isDynamicSection, dynamicSectionFieldName, dynamicSectionId });
136
+ assert.deepStrictEqual(req.session, {
137
+ forms: {
138
+ 'j-1': {
139
+ myList: [{ id: 'section-1', q1: 'a1' }]
140
+ }
141
+ }
142
+ });
143
+ });
144
+ it('should add to an existing dynamic section using the id when present in the session', async () => {
145
+ const req = mockReq();
146
+ req.session = {
147
+ forms: {
148
+ 'j-1': {
149
+ myList: [{ id: 'section-1', q1: 'old value' }]
150
+ }
151
+ }
152
+ };
153
+ const journeyId = 'j-1';
154
+ const data = { answers: { q2: 'a1' } };
155
+ const isDynamicSection = true;
156
+ const dynamicSectionFieldName = 'myList';
157
+ const dynamicSectionId = 'section-1';
158
+ const saveDataToSession = buildSaveDataToSession();
159
+ await saveDataToSession({ req, journeyId, data, isDynamicSection, dynamicSectionFieldName, dynamicSectionId });
160
+ assert.deepStrictEqual(req.session, {
161
+ forms: {
162
+ 'j-1': {
163
+ myList: [{ id: 'section-1', q1: 'old value', q2: 'a1' }]
164
+ }
165
+ }
166
+ });
167
+ });
168
+
126
169
  it('should remove a managed list item using the id when present in the session', async () => {
127
170
  const req = mockReq();
128
171
  req.params = { manageListItemId: '56789' };
@@ -1 +1 @@
1
- {"version":3,"file":"redirect-to-unanswered-question.d.ts","sourceRoot":"","sources":["redirect-to-unanswered-question.js"],"names":[],"mappings":"AAAA;;GAEG;AAEH;;;;;GAKG;AACH,0DAHW,sBAAsB,EAAE,GACtB,OAAO,SAAS,EAAE,OAAO,CA8BrC;qCArCY,CAAS,IAA2C,EAA3C,OAAO,0BAA0B,EAAE,QAAQ,EAAE,IAA2C,EAA3C,OAAO,mBAAmB,EAAE,eAAe,KAAG,OAAO"}
1
+ {"version":3,"file":"redirect-to-unanswered-question.d.ts","sourceRoot":"","sources":["redirect-to-unanswered-question.js"],"names":[],"mappings":"AAAA;;GAEG;AAEH;;;;;GAKG;AACH,0DAHW,sBAAsB,EAAE,GACtB,OAAO,SAAS,EAAE,OAAO,CA+BrC;qCAtCY,CAAS,IAA2C,EAA3C,OAAO,0BAA0B,EAAE,QAAQ,EAAE,IAA2C,EAA3C,OAAO,mBAAmB,EAAE,eAAe,KAAG,OAAO"}
@@ -13,11 +13,12 @@ export function redirectToUnansweredQuestion(conditions = []) {
13
13
  const { journeyResponse, journey } = res.locals;
14
14
 
15
15
  for (const section of journey.sections) {
16
+ const response = section.getResponse(journeyResponse);
16
17
  for (const question of section.questions) {
17
- const answer = journey.response?.answers[question.fieldName];
18
+ const answer = response?.answers[question.fieldName];
18
19
 
19
- const shouldSkip = conditions.some((condition) => condition(question, journeyResponse));
20
- const shouldDisplay = !question.shouldDisplay || question.shouldDisplay(journeyResponse);
20
+ const shouldSkip = conditions.some((condition) => condition(question, response));
21
+ const shouldDisplay = !question.shouldDisplay || question.shouldDisplay(response);
21
22
 
22
23
  if (shouldSkip || !shouldDisplay) {
23
24
  continue;
@@ -7,6 +7,7 @@ import { Journey } from '../journey/journey.js';
7
7
  import { Section } from '../section.js';
8
8
  import { COMPONENT_TYPES } from '../components/utils/component-types.js';
9
9
  import { questionHasAnswer } from '../components/utils/question-has-answer.js';
10
+ import { DynamicSection } from '#src/dynamic-section.js';
10
11
 
11
12
  describe('redirectToUnansweredQuestion Middleware', () => {
12
13
  const questions = {
@@ -145,4 +146,35 @@ describe('redirectToUnansweredQuestion Middleware', () => {
145
146
  assert.deepStrictEqual(res.redirect.mock.calls[0].arguments, ['base/url/task-list/page']);
146
147
  assert.strictEqual(next.mock.callCount(), 0);
147
148
  });
149
+
150
+ it('should support dynamic sections', () => {
151
+ const journeyResponse = new JourneyResponse(
152
+ 'id-1',
153
+ '0000003',
154
+ {
155
+ questionOne: true,
156
+ questionTwo: false,
157
+ items: [
158
+ { id: 'item-1', questionThree: true, questionFour: true },
159
+ { id: 'item-2', questionThree: true }
160
+ ]
161
+ },
162
+ 'Q9999'
163
+ );
164
+ res.locals.journeyResponse = journeyResponse;
165
+ const sections = [
166
+ new Section('S1', 's1').addQuestion(questions.q1).addQuestion(questions.q2),
167
+ new DynamicSection('S2', 'item-1', 'items').addQuestion(questions.q3).addQuestion(questions.q4),
168
+ new DynamicSection('S3', 'item-2', 'items').addQuestion(questions.q3).addQuestion(questions.q4)
169
+ ];
170
+ const journey = new Journey({ response: journeyResponse, ...params, sections });
171
+ res.locals.journey = journey;
172
+
173
+ redirectToUnansweredQuestion([])(req, res, next);
174
+
175
+ assert.strictEqual(res.redirect.mock.callCount(), 1);
176
+ // all questions answered apart from the last dynamic section question
177
+ assert.deepStrictEqual(res.redirect.mock.calls[0].arguments, ['base/url/item-2/question-4']);
178
+ assert.strictEqual(next.mock.callCount(), 0);
179
+ });
148
180
  });
@@ -147,7 +147,7 @@ export class OptionsQuestion extends Question {
147
147
  /**
148
148
  * Get the data to save from the request, returns an object of answers
149
149
  * @param {import('express').Request} req
150
- * @param {import('../journey/journey-response.js').JourneyResponse} journeyResponse - current journey response, modified with the new answers
150
+ * @param {import('../journey/journey-response.js').JourneyResponse} journeyResponse - current journey response
151
151
  * @returns {Promise<{ answers: Record<string, unknown> }>}
152
152
  */ //eslint-disable-next-line no-unused-vars -- journeyResponse kept for other questions to use
153
153
  async getDataToSave(req, journeyResponse) {
@@ -127,7 +127,7 @@ export class Question {
127
127
  * @param {import('../journey/journey.js').Journey} journey - the journey we are in
128
128
  * @param {Record<string, unknown>} [customViewData] additional data to send to view
129
129
  * @param {unknown} [payload]
130
- * @param {import('#typedefs/question-types.d.ts').PrepQuestionForRenderingOptions} [options] - required to support manage list question
130
+ * @param {import('#typedefs/question-types.d.ts').PrepQuestionForRenderingOptions} [options] - required to support manage list question and dynamic sections
131
131
  * @returns {import('#typedefs/question-types.d.ts').QuestionViewModel}
132
132
  */
133
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;
@@ -148,13 +148,13 @@ export class Question {
148
148
  * @param {import('#typedefs/question-types.d.ts').QuestionViewModel} viewModel
149
149
  */ addCustomDataToViewModel(viewModel: import("#typedefs/question-types.d.ts").QuestionViewModel): void;
150
150
  /**
151
- * Get the answers object from the journey response, which may be nested in an array for manage list questions
151
+ * Get the answers object from the journey response, which may be nested in an array for manage list questions or dynamic sections
152
152
  *
153
153
  * @param {import('../journey/journey-response.js').JourneyResponse} response
154
154
  * @param {import('#typedefs/question-types.d.ts').PrepQuestionForRenderingOptions} [options]
155
155
  * @returns {Record<string, any>}
156
156
  */
157
- answerObjectFromJourneyResponse(response: import("../journey/journey-response.js").JourneyResponse, { params, manageListQuestion }?: import("#typedefs/question-types.d.ts").PrepQuestionForRenderingOptions): Record<string, any>;
157
+ answerObjectFromJourneyResponse(response: import("../journey/journey-response.js").JourneyResponse, { params, manageListQuestion, dynamicSection }?: import("#typedefs/question-types.d.ts").PrepQuestionForRenderingOptions): Record<string, any>;
158
158
  /**
159
159
  * renders the question
160
160
  * @param {import('express').Response} res - the express response
@@ -200,6 +200,11 @@ export class Question {
200
200
  handleNextQuestion(res: import("express").Response, journey: import("../journey/journey.js").Journey, sectionSegment: string, questionSegment: string): void;
201
201
  /**
202
202
  * returns the formatted answers values to be used to build task list elements
203
+ *
204
+ * If overriding this method and access answers on the journey, use journey.responseForSection
205
+ * to get the answers, instead of `journey.response` directly. This is to support DynamicSections
206
+ * where answers are in an array. See `MultiFieldInputQuestion` for an example.
207
+ *
203
208
  * @param {string} sectionSegment
204
209
  * @param {import('../journey/journey.js').Journey} journey
205
210
  * @param {unknown} answer
@@ -1 +1 @@
1
- {"version":3,"file":"question.d.ts","sourceRoot":"","sources":["question.js"],"names":[],"mappings":"AAKA;;;GAGG;AACH;IAiEC;;;OAGG;IACH,+NAHW,OAAO,+BAA+B,EAAE,kBAAkB,oBAC1D,MAAM,CAAC,MAAM,WAAW,EA0DlC;IA5HD,6EAA6E;IAC7E,WADW,MAAM,CACP;IACV,sDAAsD;IACtD,OADW,MAAM,CACX;IACN,+DAA+D;IAC/D,UADW,MAAM,CACR;IACT,mFAAmF;IACnF,aADW,MAAM,GAAC,SAAS,CACf;IACZ,iDAAiD;IACjD,YADW,MAAM,CACN;IACX,qHAAqH;IACrH,WADW,MAAM,CACP;IACV,6FAA6F;IAC7F,UADW,OAAO,CACF;IAChB,0IAA0I;IAC1I,YADW,KAAK,CAAE,OAAO,gCAAgC,EAAE,aAAa,CAAC,CACzD;IAChB,2DAA2D;IAC3D,MADW,MAAM,GAAC,SAAS,CACtB;IACL,sEAAsE;IACtE,oBADW,OAAO,CACQ;IAC1B,oDAAoD;IACpD,KADW,MAAM,GAAC,SAAS,CACvB;IACJ,qDAAqD;IACrD,MADW,MAAM,GAAC,SAAS,CACtB;IACL,sDAAsD;IACtD,eADW,MAAM,GAAC,SAAS,CACb;IACd,gGAAgG;IAChG,YADW,OAAO,+BAA+B,EAAE,UAAU,GAAC,SAAS,CAC5D;IAEX,iFAAiF;IACjF,gBADW,MAAM,CACc;IAC/B,sFAAsF;IACtF,kBADW,OAAO,CACM;IACxB,4CAA4C;IAC5C,oBADW,MAAM,CACe;IAChC,uDAAuD;IACvD,kBADW,MAAM,CACW;IAC5B,uDAAuD;IACvD,kBADW,MAAM,CACW;IAC5B,oDAAoD;IACpD,eADW,MAAM,CACK;IACtB,0IAA0I;IAC1I,oBADW,OAAO,+BAA+B,EAAE,qBAAqB,GAAC,SAAS,CAC/D;IAEnB;;;OAGG;IAEH,gBAAiB,WAJN,OAAO,gCAAgC,EAAE,eAI3B,KAHZ,OAAO,CAGe;IAEnC;;;MAGE;IACF;;;;;;OAMG;IACH,wBAFU,OAAO,CAEc;IA2C9B,iCAAgC;IAChC,kBAAwB;IAExB;;;;MAAwB;IAkBzB;;;;;;OAMG;IACH,4BAFa,OAAO,CAInB;IAWD,iCANa,OAAO,EAWnB;IAdD;;;;OAIG;IACH,6BAFa,OAAO,CAInB;IAqBD;;;;;;OAMG;IACH,sBAFa,MAAM,EAAE,CAIpB;IAED;;;;;;;;;;;;;;OAcG;IACH,uFARG;QAAoE,MAAM,EAAlE,OAAO,8BAA8B,EAAE,WAAW;QAC2B,kBAAkB;QACtD,OAAO,EAAhD,OAAO,eAAe,EAAE,OAAO;QACkB,OAAO,EAAxD,OAAO,uBAAuB,EAAE,OAAO;QACL,cAAc;QAC9B,OAAO,GAAzB,OAAO;KACf,GAAU,OAAO,+BAA+B,EAAE,iBAAiB,CASrE;IAED;;;;;;;;;OASG;IACH,kCAPW,OAAO,eAAe,EAAE,OAAO,WAC/B,OAAO,uBAAuB,EAAE,OAAO,mBACvC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,YACvB,OAAO,YACP,OAAO,+BAA+B,EAAE,+BAA+B,GACrE,OAAO,+BAA+B,EAAE,iBAAiB,CAuCrE;IAED;;;;;;;;OAQG,CACH,4BAJW,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,uBAEjB,GAAC,GAAC,MAAM,CAIpB;IAED;;;;;;OAMG,CACH,oCAFW,OAAO,+BAA+B,EAAE,iBAAiB,QAE9B;IAEtC;;;;;;OAMG;IACH,0CAJW,OAAO,gCAAgC,EAAE,eAAe,mCACxD,OAAO,+BAA+B,EAAE,+BAA+B,GACrE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,CAc/B;IAED;;;;;OAKG;IACH,kBAJW,OAAO,SAAS,EAAE,QAAQ,aAC1B,OAAO,+BAA+B,EAAE,iBAAiB,GACvD,IAAI,CAShB;IAED;;;;;;;OAOG;IACH,8BANW,OAAO,SAAS,EAAE,OAAO,WAEzB,OAAO,eAAe,EAAE,OAAO,WAD/B,OAAO,uBAAuB,EAAE,OAAO,uBAEvC,OAAO,uCAAuC,EAAE,kBAAkB,GAChE,OAAO,+BAA+B,EAAE,iBAAiB,GAAC,SAAS,CAmB/E;IAED;;;;;;OAMG,CACH,mBAJW,OAAO,SAAS,EAAE,OAAO,mBACzB,OAAO,gCAAgC,EAAE,eAAe,GACtD,OAAO,CAAC;QAAE,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAA;KAAE,CAAC,CAczD;IAED;;;;;;OAMG,CACH,0BALW,OAAO,SAAS,EAAE,OAAO,cAEzB,OAAO,eAAe,EAAE,OAAO,WAD/B,OAAO,uBAAuB,EAAE,OAAO,GAErC,OAAO,+BAA+B,EAAE,iBAAiB,GAAG,SAAS,CAIjF;IAED;;;;;;;;;OASG;IACH,wBAPW,OAAO,SAAS,EAAE,QAAQ,WAC1B,OAAO,uBAAuB,EAAE,OAAO,kBACvC,MAAM,mBACN,MAAM,GACJ,IAAI,CAQhB;IAED;;;;;;;OAOG;IACH,uCANW,MAAM,WACN,OAAO,uBAAuB,EAAE,OAAO,UACvC,OAAO,aACP,OAAO,GACL,OAAO,+BAA+B,EAAE,UAAU,EAAE,CA+BhE;IAED;;;;;;OAMG;IACH,0BALW,MAAM,WACN,OAAO,uBAAuB,EAAE,OAAO,UACvC,OAAO,GACL,OAAO,+BAA+B,EAAE,UAAU,GAAG,OAAO,+BAA+B,EAAE,UAAU,EAAE,GAAG,SAAS,CAqBjI;IAED;;;OAGG;IACH,eAHW,OAAO,GACL,OAAO,CAInB;IAED;;;;;;;OAOG;IACH,qBAHW,OAAO,GACL,MAAM,CAQlB;IAED;;OAEG;IACH,cAFa,OAAO,CAInB;IACD;;;OAGG;IACH,4BAHW,MAAM,GACJ,OAAO,CAMnB;IAED;;;;OAIG;IACH,4BAJW,OAAO,gCAAgC,EAAE,eAAe,cACxD,MAAM,GACJ,OAAO,CAInB;;CACD"}
1
+ {"version":3,"file":"question.d.ts","sourceRoot":"","sources":["question.js"],"names":[],"mappings":"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"}