@planning-inspectorate/dynamic-forms 3.14.1 → 3.15.1

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 (28) hide show
  1. package/package.json +1 -1
  2. package/src/components/address/question.js +14 -0
  3. package/src/components/address/question.test.js +13 -0
  4. package/src/components/date/question.js +8 -0
  5. package/src/components/date/question.test.js +17 -0
  6. package/src/components/date-period/question.js +15 -0
  7. package/src/components/date-period/question.test.js +20 -0
  8. package/src/components/date-time/question.js +15 -0
  9. package/src/components/date-time/question.test.js +11 -0
  10. package/src/components/multi-field-input/question.js +8 -0
  11. package/src/components/multi-field-input/question.test.js +7 -0
  12. package/src/questions/question-props.d.ts +1 -0
  13. package/src/questions/question.js +11 -0
  14. package/src/questions/question.test.js +7 -0
  15. package/src/validator/cross-question-validator.README.md +131 -0
  16. package/src/validator/cross-question-validator.js +47 -4
  17. package/src/validator/cross-question-validator.test.js +448 -11
  18. package/types/src/components/address/question.d.ts.map +1 -1
  19. package/types/src/components/date/question.d.ts.map +1 -1
  20. package/types/src/components/date-period/question.d.ts.map +1 -1
  21. package/types/src/components/date-time/question.d.ts.map +1 -1
  22. package/types/src/components/multi-field-input/question.d.ts +1 -0
  23. package/types/src/components/multi-field-input/question.d.ts.map +1 -1
  24. package/types/src/questions/question-props.d.ts +1 -0
  25. package/types/src/questions/question.d.ts +8 -0
  26. package/types/src/questions/question.d.ts.map +1 -1
  27. package/types/src/validator/cross-question-validator.d.ts +9 -2
  28. package/types/src/validator/cross-question-validator.d.ts.map +1 -1
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@planning-inspectorate/dynamic-forms",
3
- "version": "3.14.1",
3
+ "version": "3.15.1",
4
4
  "description": "This package is for building GDS forms using a configuration-based approach.",
5
5
  "main": "./src/index.js",
6
6
  "type": "module",
@@ -30,6 +30,20 @@ export class AddressQuestion extends Question {
30
30
  };
31
31
  }
32
32
 
33
+ /**
34
+ * Gets the body field names used by this question in form submissions.
35
+ * @returns {string[]}
36
+ */
37
+ get bodyFieldNames() {
38
+ return [
39
+ `${this.fieldName}_addressLine1`,
40
+ `${this.fieldName}_addressLine2`,
41
+ `${this.fieldName}_townCity`,
42
+ `${this.fieldName}_county`,
43
+ `${this.fieldName}_postcode`
44
+ ];
45
+ }
46
+
33
47
  /**
34
48
  * @param {Record<string, any>} answers
35
49
  * @returns {*|string}
@@ -56,6 +56,19 @@ describe('AddressQuestion', () => {
56
56
  return { question, mockApi, req, journeyResponse };
57
57
  };
58
58
 
59
+ describe('bodyFieldNames', () => {
60
+ it('should return all address sub-field names', () => {
61
+ const { question } = setup();
62
+ assert.deepStrictEqual(question.bodyFieldNames, [
63
+ `${FIELDNAME}_addressLine1`,
64
+ `${FIELDNAME}_addressLine2`,
65
+ `${FIELDNAME}_townCity`,
66
+ `${FIELDNAME}_county`,
67
+ `${FIELDNAME}_postcode`
68
+ ]);
69
+ });
70
+ });
71
+
59
72
  describe('getDataToSave', () => {
60
73
  it('should format the data correctly to be saved', async () => {
61
74
  const { question, req } = setup();
@@ -19,6 +19,14 @@ export class DateQuestion extends Question {
19
19
  this.dateFormat = dateFormat;
20
20
  }
21
21
 
22
+ /**
23
+ * Gets the body field names used by this question in form submissions.
24
+ * @returns {string[]}
25
+ */
26
+ get bodyFieldNames() {
27
+ return [`${this.fieldName}_day`, `${this.fieldName}_month`, `${this.fieldName}_year`];
28
+ }
29
+
22
30
  /**
23
31
  * Get the data to save from the request, returns an object of answers
24
32
  * @param {import('express').Request} req
@@ -26,6 +26,23 @@ describe('DateQuestion', () => {
26
26
  });
27
27
  });
28
28
 
29
+ describe('bodyFieldNames', () => {
30
+ it('should return day, month, and year field names', () => {
31
+ const dateQuestion = new DateQuestion({
32
+ title: TITLE,
33
+ question: QUESTION,
34
+ fieldName: FIELDNAME,
35
+ hint: HINT,
36
+ validators: VALIDATORS
37
+ });
38
+ assert.deepStrictEqual(dateQuestion.bodyFieldNames, [
39
+ `${FIELDNAME}_day`,
40
+ `${FIELDNAME}_month`,
41
+ `${FIELDNAME}_year`
42
+ ]);
43
+ });
44
+ });
45
+
29
46
  describe('getDataToSave', () => {
30
47
  it('should return data correctly', async () => {
31
48
  const dateQuestion = new DateQuestion({
@@ -34,6 +34,21 @@ export class DatePeriodQuestion extends Question {
34
34
  this.endTime = endTime || { hour: 0, minute: 0, second: 0 };
35
35
  }
36
36
 
37
+ /**
38
+ * Gets the body field names used by this question in form submissions.
39
+ * @returns {string[]}
40
+ */
41
+ get bodyFieldNames() {
42
+ return [
43
+ `${this.fieldName}_start_day`,
44
+ `${this.fieldName}_start_month`,
45
+ `${this.fieldName}_start_year`,
46
+ `${this.fieldName}_end_day`,
47
+ `${this.fieldName}_end_month`,
48
+ `${this.fieldName}_end_year`
49
+ ];
50
+ }
51
+
37
52
  /**
38
53
  * Get the data to save from the request, returns an object of answers
39
54
  * @param {import('express').Request} req
@@ -30,6 +30,26 @@ describe('DatePeriodQuestion', () => {
30
30
  });
31
31
  });
32
32
 
33
+ describe('bodyFieldNames', () => {
34
+ it('should return all start and end date field names', () => {
35
+ const dateQuestion = new DatePeriodQuestion({
36
+ title: TITLE,
37
+ question: QUESTION,
38
+ fieldName: FIELDNAME,
39
+ hint: HINT,
40
+ validators: VALIDATORS
41
+ });
42
+ assert.deepStrictEqual(dateQuestion.bodyFieldNames, [
43
+ `${FIELDNAME}_start_day`,
44
+ `${FIELDNAME}_start_month`,
45
+ `${FIELDNAME}_start_year`,
46
+ `${FIELDNAME}_end_day`,
47
+ `${FIELDNAME}_end_month`,
48
+ `${FIELDNAME}_end_year`
49
+ ]);
50
+ });
51
+ });
52
+
33
53
  describe('getDataToSave', () => {
34
54
  it('should return data correctly', async () => {
35
55
  const dateQuestion = new DatePeriodQuestion({
@@ -22,6 +22,21 @@ export class DateTimeQuestion extends Question {
22
22
  this.timeFormat = timeFormat;
23
23
  }
24
24
 
25
+ /**
26
+ * Gets the body field names used by this question in form submissions.
27
+ * @returns {string[]}
28
+ */
29
+ get bodyFieldNames() {
30
+ return [
31
+ `${this.fieldName}_day`,
32
+ `${this.fieldName}_month`,
33
+ `${this.fieldName}_year`,
34
+ `${this.fieldName}_hour`,
35
+ `${this.fieldName}_minutes`,
36
+ `${this.fieldName}_period`
37
+ ];
38
+ }
39
+
25
40
  /**
26
41
  * Get the data to save from the request, returns an object of answers
27
42
  * @param {import('express').Request} req
@@ -15,6 +15,17 @@ describe('./lib/forms/custom-components/date-time/question.js', () => {
15
15
  it('should create', () => {
16
16
  assert.strictEqual(question.viewFolder, 'date-time');
17
17
  });
18
+
19
+ it('should return all date and time field names from bodyFieldNames', () => {
20
+ assert.deepStrictEqual(question.bodyFieldNames, [
21
+ 'siteVisitDate_day',
22
+ 'siteVisitDate_month',
23
+ 'siteVisitDate_year',
24
+ 'siteVisitDate_hour',
25
+ 'siteVisitDate_minutes',
26
+ 'siteVisitDate_period'
27
+ ]);
28
+ });
18
29
  });
19
30
  describe('prepQuestionForRendering', () => {
20
31
  it('should prep question for rendering when value not in payload', () => {
@@ -30,6 +30,14 @@ export class MultiFieldInputQuestion extends Question {
30
30
  }
31
31
  }
32
32
 
33
+ /**
34
+ * Gets the body field names used by this question in form submissions.
35
+ * @returns {string[]}
36
+ */
37
+ get bodyFieldNames() {
38
+ return this.inputFields.map((inputField) => inputField.fieldName);
39
+ }
40
+
33
41
  answerForViewModel(answers) {
34
42
  return this.inputFields.map((inputField) => {
35
43
  return {
@@ -51,6 +51,13 @@ describe('./src/dynamic-forms/components/multi-field-input/question.js', () => {
51
51
  }, new Error('inputFields are mandatory'));
52
52
  });
53
53
 
54
+ describe('bodyFieldNames', () => {
55
+ it('should return field names from all inputFields', () => {
56
+ const testQuestion = createMultiFieldInputQuestion();
57
+ assert.deepStrictEqual(testQuestion.bodyFieldNames, ['testField1', 'testField2']);
58
+ });
59
+ });
60
+
54
61
  describe('prepQuestionForRendering', () => {
55
62
  it('should call super and set inputFields', () => {
56
63
  const question = createMultiFieldInputQuestion();
@@ -68,6 +68,7 @@ interface InputField {
68
68
  suffix?: Affix; // used to add a suffix to the field
69
69
  prefix?: Affix; // used to add a prefix to the field
70
70
  hint?: string;
71
+ classes?: string;
71
72
  }
72
73
 
73
74
  /*
@@ -181,6 +181,17 @@ export class Question {
181
181
  this._isInManageListSection = value;
182
182
  }
183
183
 
184
+ /**
185
+ * Gets the body field names used by this question in form submissions.
186
+ * Returns the field names that should be present in req.body when this question is submitted.
187
+ * Subclasses should override this for questions with multiple or differently-named body fields.
188
+ *
189
+ * @returns {string[]}
190
+ */
191
+ get bodyFieldNames() {
192
+ return [this.fieldName];
193
+ }
194
+
184
195
  /**
185
196
  * gets the view model for this question
186
197
  *
@@ -135,6 +135,13 @@ describe('./src/dynamic-forms/question.js', () => {
135
135
  });
136
136
  });
137
137
 
138
+ describe('bodyFieldNames', () => {
139
+ it('should return an array containing the fieldName by default', () => {
140
+ const question = getTestQuestion();
141
+ assert.deepStrictEqual(question.bodyFieldNames, [FIELDNAME]);
142
+ });
143
+ });
144
+
138
145
  describe('prepQuestionForRendering', () => {
139
146
  it('should prepQuestionForRendering', () => {
140
147
  const question = getTestQuestion();
@@ -0,0 +1,131 @@
1
+ # Cross-question validator
2
+
3
+ The `CrossQuestionValidator` enables validation of a question's answer against another question's answer using a custom validation function. This is useful for scenarios where the validity of one answer depends on another answer.
4
+
5
+ ## Constructor options
6
+
7
+ | Parameter | Type | Default | Description |
8
+ | ------------------------- | ---------------------------------------------- | ------------ | ---------------------------------------------------------------------------------------------- |
9
+ | `dependencyFieldName` | `string` | **required** | The field name of the other question to compare against |
10
+ | `validationFunction` | `(currentAnswer, dependencyAnswer) => boolean` | **required** | Function that returns `true` if valid, or throws an `Error` with a specific message if invalid |
11
+ | `useBodyValues` | `boolean` | `false` | Use `req.body` values for **both** current and dependency answers |
12
+ | `useBodyValuesForCurrent` | `boolean` | `false` | Use `req.body` value for current answer only (dependency still uses JourneyResponse) |
13
+
14
+ ## Use cases
15
+
16
+ ### 1. Validation between separate manage list questions (JourneyResponse data)
17
+
18
+ When validating between questions where both answers are already saved to the JourneyResponse (e.g., in a manage list journey), use the default settings.
19
+
20
+ ```js
21
+ import { CrossQuestionValidator } from '@planning-inspectorate/dynamic-forms';
22
+
23
+ const startDateQuestion = {
24
+ type: COMPONENT_TYPES.DATE,
25
+ fieldName: 'startDate',
26
+ question: 'What is the start date?'
27
+ // ... other config
28
+ };
29
+
30
+ const endDateQuestion = {
31
+ type: COMPONENT_TYPES.DATE,
32
+ fieldName: 'endDate',
33
+ question: 'What is the end date?',
34
+ validators: [
35
+ new CrossQuestionValidator({
36
+ dependencyFieldName: 'startDate',
37
+ validationFunction: (endDate, startDate) => {
38
+ if (!endDate || !startDate) return true;
39
+ if (new Date(endDate) < new Date(startDate)) {
40
+ throw new Error('End date must be on or after the start date');
41
+ }
42
+ return true;
43
+ }
44
+ })
45
+ ]
46
+ };
47
+ ```
48
+
49
+ **When to use:** Both questions are on separate pages and their answers are saved to the JourneyResponse before validation runs.
50
+
51
+ ### 2. Validation between regular questions (body data with formatting)
52
+
53
+ When the current question's data needs to be extracted from `req.body` and formatted (e.g., date fields that come as separate day/month/year inputs), use `useBodyValuesForCurrent: true`.
54
+
55
+ The validator will automatically use the question's `getDataToSave()` method to format the data correctly.
56
+
57
+ ```js
58
+ const projectEndDateQuestion = {
59
+ type: COMPONENT_TYPES.DATE,
60
+ fieldName: 'projectEndDate',
61
+ question: 'When does the project end?',
62
+ validators: [
63
+ new CrossQuestionValidator({
64
+ dependencyFieldName: 'projectStartDate', // Already saved in JourneyResponse
65
+ useBodyValuesForCurrent: true, // Current answer from body, formatted via getDataToSave()
66
+ validationFunction: (endDate, startDate) => {
67
+ if (!endDate || !startDate) return true;
68
+ if (endDate < new Date(startDate)) {
69
+ throw new Error('Project end date must be on or after the project start date');
70
+ }
71
+ return true;
72
+ }
73
+ })
74
+ ]
75
+ };
76
+ ```
77
+
78
+ **When to use:** The current question's answer is in `req.body` (not yet saved), but the dependency answer is already saved to the JourneyResponse. Works automatically with date questions, date-period questions, and other question types that have a `getDataToSave()` method.
79
+
80
+ ### 3. Validation between fields in a multi-field question (both from body)
81
+
82
+ When validating between multiple fields on the same page (e.g., a `MultiFieldInputQuestion`), both current and reference values come from `req.body`. Use `useBodyValues: true`.
83
+
84
+ ```js
85
+ const siteAreaQuestion = {
86
+ type: COMPONENT_TYPES.MULTI_FIELD_INPUT,
87
+ fieldName: 'siteArea',
88
+ question: 'What is the area of the site?',
89
+ inputFields: [
90
+ { fieldName: 'siteAreaHectares', label: 'Site area in hectares (optional)' },
91
+ { fieldName: 'siteAreaSquareMetres', label: 'Site area in square metres (optional)' }
92
+ ],
93
+ validators: [
94
+ // Note: this is a custom validator not provided by default
95
+ new MultiFieldInputValidator({
96
+ fields: [
97
+ {
98
+ fieldName: 'siteAreaHectares',
99
+ validators: [
100
+ new CrossQuestionValidator({
101
+ dependencyFieldName: 'siteAreaSquareMetres',
102
+ useBodyValues: true,
103
+ validationFunction: (hectares, squareMetres) => {
104
+ if (hectares?.trim() && squareMetres?.trim()) {
105
+ throw new Error('Enter the site area in either hectares or square metres, not both');
106
+ }
107
+ return true;
108
+ }
109
+ })
110
+ ]
111
+ }
112
+ ]
113
+ })
114
+ ]
115
+ };
116
+ ```
117
+
118
+ **When to use:** Multiple fields are on the same page and both values need to be read from `req.body` before either is saved.
119
+
120
+ ## How data sources are determined
121
+
122
+ | `useBodyValues` | `useBodyValuesForCurrent` | Current Answer Source | Dependency Answer Source |
123
+ | --------------- | ------------------------- | ---------------------------- | ------------------------ |
124
+ | `false` | `false` | JourneyResponse | JourneyResponse |
125
+ | `false` | `true` | Body (via `getDataToSave()`) | JourneyResponse |
126
+ | `true` | (ignored) | Body (via `getDataToSave()`) | Body (raw) |
127
+
128
+ ## Notes
129
+
130
+ - When `useBodyValuesForCurrent` is `true`, the validator uses the question's `getDataToSave()` method to format body data. This automatically handles complex field types like dates (`fieldName_day`, `fieldName_month`, `fieldName_year`).
131
+ - The validator binds to the appropriate body field using the question's `bodyFieldNames` getter. For simple questions this returns `[fieldName]`, but complex question types override it (e.g., `DateQuestion` returns `[fieldName_day, fieldName_month, fieldName_year]`). Custom question components can define their own `bodyFieldNames` getter to ensure proper validation binding.
@@ -11,16 +11,24 @@ export class CrossQuestionValidator extends BaseValidator {
11
11
  validationFunction;
12
12
  /** @type {boolean} */
13
13
  useBodyValues;
14
+ /** @type {boolean} */
15
+ useBodyValuesForCurrent;
14
16
 
15
17
  /**
16
18
  * @param {Object} params
17
19
  * @param {string} params.dependencyFieldName - The field name of the other question to compare against.
18
20
  * @param {((currentAnswer: unknown, dependencyAnswer: unknown) => boolean)| null} params.validationFunction - A function that takes the current question's answer and the other question's answer and returns true if valid, false otherwise.
19
- * @param {boolean} [params.useBodyValues=false] - Whether to use req.body values for validation (default: false). Usually CrossQuestionValidator will be for validating across saved session answers, but if used between multiple fields on one question, it will need to use the body.
21
+ * @param {boolean} [params.useBodyValues=false] - Whether to use req.body values for validation (default: false). Usually CrossQuestionValidator will be for validating across saved JourneyResponse answers, but if used between multiple fields on one question, it will need to use the body.
22
+ * @param {boolean} [params.useBodyValuesForCurrent=false] - Whether to use req.body values for the current question's answer (default: false). This may help with questions where data is not saved to the JourneyResponse on POST (e.g. non-multiple entities questions).
20
23
  * @throws {Error} If validationFunction is not provided or is not a function.
21
24
  * @throws {Error} If dependencyFieldName is not provided
22
25
  */
23
- constructor({ dependencyFieldName, validationFunction, useBodyValues = false } = {}) {
26
+ constructor({
27
+ dependencyFieldName,
28
+ validationFunction,
29
+ useBodyValues = false,
30
+ useBodyValuesForCurrent = false
31
+ } = {}) {
24
32
  super();
25
33
 
26
34
  if (!dependencyFieldName) {
@@ -33,24 +41,59 @@ export class CrossQuestionValidator extends BaseValidator {
33
41
  this.dependencyFieldName = dependencyFieldName;
34
42
  this.validationFunction = validationFunction;
35
43
  this.useBodyValues = useBodyValues;
44
+ this.useBodyValuesForCurrent = useBodyValues || useBodyValuesForCurrent;
45
+ }
46
+
47
+ /**
48
+ * Gets the body field name to bind validation to.
49
+ * Uses the question's bodyFieldNames getter to determine which body field
50
+ * express-validator binds to for triggering validation.
51
+ * @param {import('../questions/question.js').Question} questionObj
52
+ * @returns {string}
53
+ */
54
+ #getBodyFieldName(questionObj) {
55
+ // Use the first body field name from the question's bodyFieldNames getter
56
+ // This allows questions to define their own body field names, supporting
57
+ // custom components and complex field structures (e.g. date, date-time, address)
58
+ return questionObj.bodyFieldNames?.[0] ?? questionObj.fieldName;
36
59
  }
37
60
 
38
61
  /**
39
62
  * Validates response body against individual field validators.
63
+ * Uses the question's getDataToSave method to extract and format body values when useBodyValuesForCurrent is true,
64
+ * allowing generic handling of any question type without needing to know its internal structure.
40
65
  * @param {import('../questions/question.js').Question} questionObj - The question object containing the fieldName to validate.
41
66
  * @param {import('../journey/journey-response.js').JourneyResponse} [journeyResponse={}] - The current journey response, used to access saved answers for validation.
42
67
  * @returns {import('express-validator').ValidationChain[]}
43
68
  */
44
69
  validate(questionObj, journeyResponse = {}) {
45
70
  const fieldName = questionObj.fieldName;
71
+ const bodyFieldName = this.#getBodyFieldName(questionObj);
46
72
 
47
73
  return [
48
- body(fieldName).custom((value, { req }) => {
74
+ body(bodyFieldName).custom(async (value, { req }) => {
49
75
  const answers = journeyResponse?.answers || {};
50
- const currentAnswer = this.useBodyValues ? value : answers[fieldName];
76
+
77
+ let currentAnswer;
78
+ if (this.useBodyValuesForCurrent) {
79
+ // Use the question's own getDataToSave to format body data generically
80
+ if (typeof questionObj.getDataToSave === 'function') {
81
+ const { answers: formattedAnswers } = await questionObj.getDataToSave(req, journeyResponse);
82
+ // Use the formatted answer if available, otherwise return all formatted answers
83
+ // (handles MultiFieldInputQuestion which returns answers keyed by input field names)
84
+ currentAnswer = fieldName in formattedAnswers ? formattedAnswers[fieldName] : formattedAnswers;
85
+ } else {
86
+ // Fallback to raw body value if getDataToSave is not available
87
+ currentAnswer = req.body[fieldName];
88
+ }
89
+ } else {
90
+ currentAnswer = answers[fieldName];
91
+ }
92
+
51
93
  const dependencyAnswer = this.useBodyValues
52
94
  ? req.body[this.dependencyFieldName]
53
95
  : answers[this.dependencyFieldName];
96
+
54
97
  return (
55
98
  this.validationFunction(currentAnswer, dependencyAnswer) ||
56
99
  // Fallback if validation fails without throwing an error
@@ -14,6 +14,89 @@ function createMockRequest(answers) {
14
14
  };
15
15
  }
16
16
 
17
+ /**
18
+ * Creates a mock question object with a simple getDataToSave that returns the body field value.
19
+ * @param {string} fieldName
20
+ * @returns {{fieldName: string, bodyFieldNames: string[], getDataToSave: Function}}
21
+ */
22
+ function createMockQuestion(fieldName) {
23
+ return {
24
+ fieldName,
25
+ bodyFieldNames: [fieldName],
26
+ getDataToSave: async (req) => ({
27
+ answers: {
28
+ [fieldName]: req.body[fieldName]
29
+ }
30
+ })
31
+ };
32
+ }
33
+
34
+ /**
35
+ * Creates a mock date question that extracts date from day/month/year fields.
36
+ * @param {string} fieldName
37
+ * @returns {{fieldName: string, bodyFieldNames: string[], getDataToSave: Function}}
38
+ */
39
+ function createMockDateQuestion(fieldName) {
40
+ return {
41
+ fieldName,
42
+ bodyFieldNames: [`${fieldName}_day`, `${fieldName}_month`, `${fieldName}_year`],
43
+ getDataToSave: async (req) => {
44
+ const day = req.body[`${fieldName}_day`];
45
+ const month = req.body[`${fieldName}_month`];
46
+ const year = req.body[`${fieldName}_year`];
47
+ if (!day || !month || !year) {
48
+ throw new Error(`Incomplete date fields for ${fieldName}`);
49
+ }
50
+ return {
51
+ answers: {
52
+ [fieldName]: new Date(year, month - 1, day)
53
+ }
54
+ };
55
+ }
56
+ };
57
+ }
58
+
59
+ /**
60
+ * Creates a mock date-period question that extracts start/end dates.
61
+ * @param {string} fieldName
62
+ * @returns {{fieldName: string, bodyFieldNames: string[], getDataToSave: Function}}
63
+ */
64
+ function createMockDatePeriodQuestion(fieldName) {
65
+ return {
66
+ fieldName,
67
+ bodyFieldNames: [
68
+ `${fieldName}_start_day`,
69
+ `${fieldName}_start_month`,
70
+ `${fieldName}_start_year`,
71
+ `${fieldName}_end_day`,
72
+ `${fieldName}_end_month`,
73
+ `${fieldName}_end_year`
74
+ ],
75
+ getDataToSave: async (req) => {
76
+ const startDay = req.body[`${fieldName}_start_day`];
77
+ const startMonth = req.body[`${fieldName}_start_month`];
78
+ const startYear = req.body[`${fieldName}_start_year`];
79
+ const endDay = req.body[`${fieldName}_end_day`];
80
+ const endMonth = req.body[`${fieldName}_end_month`];
81
+ const endYear = req.body[`${fieldName}_end_year`];
82
+ if (!startDay || !startMonth || !startYear) {
83
+ throw new Error(`Incomplete start date fields for ${fieldName}`);
84
+ }
85
+ if (!endDay || !endMonth || !endYear) {
86
+ throw new Error(`Incomplete end date fields for ${fieldName}`);
87
+ }
88
+ return {
89
+ answers: {
90
+ [fieldName]: {
91
+ start: new Date(startYear, startMonth - 1, startDay),
92
+ end: new Date(endYear, endMonth - 1, endDay)
93
+ }
94
+ }
95
+ };
96
+ }
97
+ };
98
+ }
99
+
17
100
  describe('CrossQuestionValidator', () => {
18
101
  test('should throw an error if dependencyFieldName is missing', () => {
19
102
  assert.throws(() => {
@@ -39,7 +122,7 @@ describe('CrossQuestionValidator', () => {
39
122
  dependencyFieldName: 'other',
40
123
  validationFunction: validationFn
41
124
  });
42
- const questionObj = { fieldName: 'field' };
125
+ const questionObj = createMockQuestion('field');
43
126
  const req = createMockRequest({ field: 'foo', other: 'bar' });
44
127
  const chain = validator.validate(questionObj, req.res.locals.journeyResponse);
45
128
  await chain[0].run(req);
@@ -51,7 +134,7 @@ describe('CrossQuestionValidator', () => {
51
134
  dependencyFieldName: 'other',
52
135
  validationFunction: () => false
53
136
  });
54
- const questionObj = { fieldName: 'field' };
137
+ const questionObj = createMockQuestion('field');
55
138
  const chain = validator.validate(questionObj);
56
139
  const req = createMockRequest({ field: 'foo', other: 'bar' });
57
140
  req.body = { field: 'foo' };
@@ -66,25 +149,28 @@ describe('CrossQuestionValidator', () => {
66
149
  dependencyFieldName: 'other',
67
150
  validationFunction: validationFn
68
151
  });
69
- const questionObj = { fieldName: 'field' };
152
+ const questionObj = createMockQuestion('field');
70
153
  const chain = validator.validate(questionObj);
71
154
  const req = { res: { locals: {} } };
72
155
  await chain[0].run(req);
73
156
  assert.deepEqual(validationFn.mock.calls[0].arguments, [undefined, undefined]);
74
157
  });
75
158
 
76
- it('should use session answers instead of request body values when useBodyValues is false', async () => {
159
+ it('should use JourneyResponse answers instead of request body values when useBodyValues is false', async () => {
77
160
  const validationFn = mock.fn(() => true);
78
161
  const validator = new CrossQuestionValidator({
79
162
  dependencyFieldName: 'other',
80
163
  validationFunction: validationFn
81
164
  });
82
- const questionObj = { fieldName: 'field' };
83
- const req = createMockRequest({ field: 'from-session', other: 'from-session-dependency' });
165
+ const questionObj = createMockQuestion('field');
166
+ const req = createMockRequest({ field: 'from-journey-response', other: 'from-journey-response-dependency' });
84
167
  req.body = { field: 'from-body', other: 'from-body-dependency' };
85
168
  const chain = validator.validate(questionObj, req.res.locals.journeyResponse);
86
169
  await chain[0].run(req);
87
- assert.deepEqual(validationFn.mock.calls[0].arguments, ['from-session', 'from-session-dependency']);
170
+ assert.deepEqual(validationFn.mock.calls[0].arguments, [
171
+ 'from-journey-response',
172
+ 'from-journey-response-dependency'
173
+ ]);
88
174
  });
89
175
 
90
176
  it('should use request body values when useBodyValues is true', async () => {
@@ -94,9 +180,9 @@ describe('CrossQuestionValidator', () => {
94
180
  useBodyValues: true,
95
181
  validationFunction: validationFn
96
182
  });
97
- const questionObj = { fieldName: 'field' };
183
+ const questionObj = createMockQuestion('field');
98
184
  const chain = validator.validate(questionObj);
99
- const req = createMockRequest({ field: 'from-session', other: 'from-session-dependency' });
185
+ const req = createMockRequest({ field: 'from-journey-response', other: 'from-journey-response-dependency' });
100
186
  req.body = { field: 'from-body', other: 'from-body-dependency' };
101
187
  await chain[0].run(req);
102
188
  assert.deepEqual(validationFn.mock.calls[0].arguments, ['from-body', 'from-body-dependency']);
@@ -109,7 +195,7 @@ describe('CrossQuestionValidator', () => {
109
195
  throw new Error('boom');
110
196
  }
111
197
  });
112
- const questionObj = { fieldName: 'field' };
198
+ const questionObj = createMockQuestion('field');
113
199
  const chain = validator.validate(questionObj);
114
200
  const req = createMockRequest({ field: 'foo', other: 'bar' });
115
201
  const result = await chain[0].run(req);
@@ -123,10 +209,361 @@ describe('CrossQuestionValidator', () => {
123
209
  dependencyFieldName: 'other',
124
210
  validationFunction: validationFn
125
211
  });
126
- const questionObj = { fieldName: 'field' };
212
+ const questionObj = createMockQuestion('field');
127
213
  const chain = validator.validate(questionObj);
128
214
  const req = { res: { locals: { journeyResponse: {} } } };
129
215
  await chain[0].run(req);
130
216
  assert.deepEqual(validationFn.mock.calls[0].arguments, [undefined, undefined]);
131
217
  });
218
+
219
+ describe('date question integration', () => {
220
+ it('should extract date using getDataToSave when useBodyValues is true', async () => {
221
+ const validationFn = mock.fn(() => true);
222
+ const validator = new CrossQuestionValidator({
223
+ dependencyFieldName: 'other',
224
+ validationFunction: validationFn,
225
+ useBodyValues: true
226
+ });
227
+ const questionObj = createMockDateQuestion('dateField');
228
+ const req = createMockRequest({});
229
+ req.body = {
230
+ dateField_day: '15',
231
+ dateField_month: '6',
232
+ dateField_year: '2026',
233
+ other: 'dependency-value'
234
+ };
235
+ const chain = validator.validate(questionObj);
236
+ await chain[0].run(req);
237
+
238
+ const [currentAnswer, dependencyAnswer] = validationFn.mock.calls[0].arguments;
239
+ assert.ok(currentAnswer instanceof Date, 'currentAnswer should be a Date');
240
+ assert.strictEqual(currentAnswer.getFullYear(), 2026);
241
+ assert.strictEqual(currentAnswer.getMonth() + 1, 6);
242
+ assert.strictEqual(currentAnswer.getDate(), 15);
243
+ assert.strictEqual(dependencyAnswer, 'dependency-value');
244
+ });
245
+
246
+ it('should propagate error when getDataToSave throws (e.g. incomplete date fields)', async () => {
247
+ const validator = new CrossQuestionValidator({
248
+ dependencyFieldName: 'other',
249
+ validationFunction: () => true,
250
+ useBodyValues: true
251
+ });
252
+ const questionObj = createMockDateQuestion('dateField');
253
+ const req = createMockRequest({});
254
+ req.body = {
255
+ dateField_day: '15',
256
+ dateField_month: '6',
257
+ // missing year
258
+ other: 'dependency-value'
259
+ };
260
+ const chain = validator.validate(questionObj);
261
+ const result = await chain[0].run(req);
262
+ assert.strictEqual(result.isEmpty(), false);
263
+ assert.match(result.array()[0].msg, /Incomplete date fields/);
264
+ });
265
+
266
+ it('should use JourneyResponse answer directly when useBodyValues is false', async () => {
267
+ const storedDate = new Date('2026-06-15T00:00:00.000Z');
268
+ const validationFn = mock.fn(() => true);
269
+ const validator = new CrossQuestionValidator({
270
+ dependencyFieldName: 'other',
271
+ validationFunction: validationFn
272
+ });
273
+ const questionObj = createMockDateQuestion('dateField');
274
+ const req = createMockRequest({ dateField: storedDate, other: 'dependency-value' });
275
+ req.body = { dateField_day: '15' };
276
+ const chain = validator.validate(questionObj, req.res.locals.journeyResponse);
277
+ await chain[0].run(req);
278
+
279
+ const [currentAnswer, dependencyAnswer] = validationFn.mock.calls[0].arguments;
280
+ assert.strictEqual(currentAnswer, storedDate);
281
+ assert.strictEqual(dependencyAnswer, 'dependency-value');
282
+ });
283
+ });
284
+
285
+ describe('date-period question integration', () => {
286
+ it('should extract date period using getDataToSave when useBodyValues is true', async () => {
287
+ const validationFn = mock.fn(() => true);
288
+ const validator = new CrossQuestionValidator({
289
+ dependencyFieldName: 'other',
290
+ validationFunction: validationFn,
291
+ useBodyValues: true
292
+ });
293
+ const questionObj = createMockDatePeriodQuestion('periodField');
294
+ const req = createMockRequest({});
295
+ req.body = {
296
+ periodField_start_day: '1',
297
+ periodField_start_month: '6',
298
+ periodField_start_year: '2026',
299
+ periodField_end_day: '30',
300
+ periodField_end_month: '6',
301
+ periodField_end_year: '2026',
302
+ other: 'dependency-value'
303
+ };
304
+ const chain = validator.validate(questionObj);
305
+ await chain[0].run(req);
306
+
307
+ const [currentAnswer, dependencyAnswer] = validationFn.mock.calls[0].arguments;
308
+ assert.ok(currentAnswer.start instanceof Date, 'start should be a Date');
309
+ assert.ok(currentAnswer.end instanceof Date, 'end should be a Date');
310
+ assert.strictEqual(currentAnswer.start.getDate(), 1);
311
+ assert.strictEqual(currentAnswer.end.getDate(), 30);
312
+ assert.strictEqual(dependencyAnswer, 'dependency-value');
313
+ });
314
+
315
+ it('should propagate error when getDataToSave throws for incomplete start date', async () => {
316
+ const validator = new CrossQuestionValidator({
317
+ dependencyFieldName: 'other',
318
+ validationFunction: () => true,
319
+ useBodyValues: true
320
+ });
321
+ const questionObj = createMockDatePeriodQuestion('periodField');
322
+ const req = createMockRequest({});
323
+ req.body = {
324
+ periodField_start_day: '1',
325
+ // missing start month and year
326
+ periodField_end_day: '30',
327
+ periodField_end_month: '6',
328
+ periodField_end_year: '2026',
329
+ other: 'dependency-value'
330
+ };
331
+ const chain = validator.validate(questionObj);
332
+ const result = await chain[0].run(req);
333
+ assert.strictEqual(result.isEmpty(), false);
334
+ assert.match(result.array()[0].msg, /Incomplete start date fields/);
335
+ });
336
+
337
+ it('should propagate error when getDataToSave throws for incomplete end date', async () => {
338
+ const validator = new CrossQuestionValidator({
339
+ dependencyFieldName: 'other',
340
+ validationFunction: () => true,
341
+ useBodyValues: true
342
+ });
343
+ const questionObj = createMockDatePeriodQuestion('periodField');
344
+ const req = createMockRequest({});
345
+ req.body = {
346
+ periodField_start_day: '1',
347
+ periodField_start_month: '6',
348
+ periodField_start_year: '2026',
349
+ periodField_end_day: '30',
350
+ // missing end month and year
351
+ other: 'dependency-value'
352
+ };
353
+ const chain = validator.validate(questionObj);
354
+ const result = await chain[0].run(req);
355
+ assert.strictEqual(result.isEmpty(), false);
356
+ assert.match(result.array()[0].msg, /Incomplete end date fields/);
357
+ });
358
+
359
+ it('should use JourneyResponse answer directly when useBodyValues is false', async () => {
360
+ const storedPeriod = {
361
+ start: new Date('2026-06-01T00:00:00.000Z'),
362
+ end: new Date('2026-06-30T00:00:00.000Z')
363
+ };
364
+ const validationFn = mock.fn(() => true);
365
+ const validator = new CrossQuestionValidator({
366
+ dependencyFieldName: 'other',
367
+ validationFunction: validationFn
368
+ });
369
+ const questionObj = createMockDatePeriodQuestion('periodField');
370
+ const req = createMockRequest({ periodField: storedPeriod, other: 'dependency-value' });
371
+ req.body = { periodField_start_day: '1' };
372
+ const chain = validator.validate(questionObj, req.res.locals.journeyResponse);
373
+ await chain[0].run(req);
374
+
375
+ const [currentAnswer, dependencyAnswer] = validationFn.mock.calls[0].arguments;
376
+ assert.strictEqual(currentAnswer, storedPeriod);
377
+ assert.strictEqual(dependencyAnswer, 'dependency-value');
378
+ });
379
+ });
380
+
381
+ describe('useBodyValuesForCurrent', () => {
382
+ it('should set useBodyValuesForCurrent property correctly', () => {
383
+ const validator1 = new CrossQuestionValidator({
384
+ dependencyFieldName: 'other',
385
+ validationFunction: () => true,
386
+ useBodyValuesForCurrent: true
387
+ });
388
+ assert.strictEqual(validator1.useBodyValuesForCurrent, true);
389
+
390
+ const validator2 = new CrossQuestionValidator({
391
+ dependencyFieldName: 'other',
392
+ validationFunction: () => true,
393
+ useBodyValues: true
394
+ });
395
+ assert.strictEqual(validator2.useBodyValuesForCurrent, true);
396
+
397
+ const validator3 = new CrossQuestionValidator({
398
+ dependencyFieldName: 'other',
399
+ validationFunction: () => true
400
+ });
401
+ assert.strictEqual(validator3.useBodyValuesForCurrent, false);
402
+ });
403
+
404
+ it('should use body value for current and JourneyResponse value for dependency when useBodyValuesForCurrent is true', async () => {
405
+ const validationFn = mock.fn(() => true);
406
+ const validator = new CrossQuestionValidator({
407
+ dependencyFieldName: 'other',
408
+ validationFunction: validationFn,
409
+ useBodyValuesForCurrent: true
410
+ });
411
+ const questionObj = createMockQuestion('field');
412
+ const req = createMockRequest({ field: 'from-journey-response', other: 'from-journey-response-dependency' });
413
+ req.body = { field: 'from-body', other: 'from-body-dependency' };
414
+ const chain = validator.validate(questionObj, req.res.locals.journeyResponse);
415
+ await chain[0].run(req);
416
+
417
+ const [currentAnswer, dependencyAnswer] = validationFn.mock.calls[0].arguments;
418
+ assert.strictEqual(currentAnswer, 'from-body');
419
+ assert.strictEqual(dependencyAnswer, 'from-journey-response-dependency');
420
+ });
421
+
422
+ it('should use body values for both current and dependency when useBodyValues is true', async () => {
423
+ const validationFn = mock.fn(() => true);
424
+ const validator = new CrossQuestionValidator({
425
+ dependencyFieldName: 'other',
426
+ validationFunction: validationFn,
427
+ useBodyValues: true
428
+ });
429
+ const questionObj = createMockQuestion('field');
430
+ const req = createMockRequest({ field: 'from-journey-response', other: 'from-journey-response-dependency' });
431
+ req.body = { field: 'from-body', other: 'from-body-dependency' };
432
+ const chain = validator.validate(questionObj, req.res.locals.journeyResponse);
433
+ await chain[0].run(req);
434
+
435
+ const [currentAnswer, dependencyAnswer] = validationFn.mock.calls[0].arguments;
436
+ assert.strictEqual(currentAnswer, 'from-body');
437
+ assert.strictEqual(dependencyAnswer, 'from-body-dependency');
438
+ });
439
+
440
+ it('should use JourneyResponse value for current when useBodyValuesForCurrent is false', async () => {
441
+ const validationFn = mock.fn(() => true);
442
+ const validator = new CrossQuestionValidator({
443
+ dependencyFieldName: 'other',
444
+ validationFunction: validationFn,
445
+ useBodyValuesForCurrent: false
446
+ });
447
+ const questionObj = createMockQuestion('field');
448
+ const req = createMockRequest({ field: 'from-journey-response', other: 'from-journey-response-dependency' });
449
+ req.body = { field: 'from-body', other: 'from-body-dependency' };
450
+ const chain = validator.validate(questionObj, req.res.locals.journeyResponse);
451
+ await chain[0].run(req);
452
+
453
+ const [currentAnswer, dependencyAnswer] = validationFn.mock.calls[0].arguments;
454
+ assert.strictEqual(currentAnswer, 'from-journey-response');
455
+ assert.strictEqual(dependencyAnswer, 'from-journey-response-dependency');
456
+ });
457
+
458
+ it('should fallback to req.body[fieldName] when getDataToSave is not available', async () => {
459
+ const validationFn = mock.fn(() => true);
460
+ const validator = new CrossQuestionValidator({
461
+ dependencyFieldName: 'other',
462
+ validationFunction: validationFn,
463
+ useBodyValuesForCurrent: true
464
+ });
465
+ // Question without getDataToSave method
466
+ const questionObj = {
467
+ fieldName: 'field'
468
+ };
469
+ const req = createMockRequest({ field: 'from-journey-response', other: 'from-journey-response-dependency' });
470
+ req.body = { field: 'from-body', other: 'from-body-dependency' };
471
+ const chain = validator.validate(questionObj, req.res.locals.journeyResponse);
472
+ await chain[0].run(req);
473
+
474
+ const [currentAnswer, dependencyAnswer] = validationFn.mock.calls[0].arguments;
475
+ assert.strictEqual(currentAnswer, 'from-body');
476
+ assert.strictEqual(dependencyAnswer, 'from-journey-response-dependency');
477
+ });
478
+
479
+ it('should return all formatted answers when fieldName is not in formattedAnswers', async () => {
480
+ const validationFn = mock.fn(() => true);
481
+ const validator = new CrossQuestionValidator({
482
+ dependencyFieldName: 'otherField',
483
+ validationFunction: validationFn,
484
+ useBodyValues: true
485
+ });
486
+ // Mock a behavior where getDataToSave returns answers keyed by input
487
+ // field names, not the question's fieldName
488
+ const questionObj = {
489
+ fieldName: 'contactDetails', // This won't be in formattedAnswers
490
+ bodyFieldNames: ['email', 'phone'],
491
+ getDataToSave: async (req) => ({
492
+ answers: {
493
+ // Returns individual input fields, not 'contactDetails'
494
+ email: req.body.email,
495
+ phone: req.body.phone
496
+ }
497
+ })
498
+ };
499
+ const req = createMockRequest({});
500
+ req.body = {
501
+ contactDetails: 'ignored',
502
+ email: 'test@example.com',
503
+ phone: '123456789',
504
+ otherField: 'dependency-value'
505
+ };
506
+ const chain = validator.validate(questionObj);
507
+ await chain[0].run(req);
508
+
509
+ const [currentAnswer, dependencyAnswer] = validationFn.mock.calls[0].arguments;
510
+ // Should receive all formatted answers since 'contactDetails' is not in formattedAnswers
511
+ assert.deepStrictEqual(currentAnswer, {
512
+ email: 'test@example.com',
513
+ phone: '123456789'
514
+ });
515
+ assert.strictEqual(dependencyAnswer, 'dependency-value');
516
+ });
517
+ });
518
+
519
+ describe('bodyFieldNames getter support', () => {
520
+ it('should use bodyFieldNames[0] when question provides it', async () => {
521
+ const validationFn = mock.fn(() => true);
522
+ const validator = new CrossQuestionValidator({
523
+ dependencyFieldName: 'other',
524
+ validationFunction: validationFn
525
+ });
526
+ // Mock question with custom bodyFieldNames getter
527
+ const questionObj = {
528
+ fieldName: 'customField',
529
+ bodyFieldNames: ['customField_first', 'customField_second'],
530
+ getDataToSave: async (req) => ({
531
+ answers: {
532
+ customField: req.body.customField_first + '-' + req.body.customField_second
533
+ }
534
+ })
535
+ };
536
+ const req = createMockRequest({ customField: 'stored-value', other: 'dep-value' });
537
+ req.body = {
538
+ customField_first: 'a',
539
+ customField_second: 'b',
540
+ other: 'body-dep'
541
+ };
542
+ const chain = validator.validate(questionObj, req.res.locals.journeyResponse);
543
+ await chain[0].run(req);
544
+
545
+ // Should have called validation function with stored answer since useBodyValuesForCurrent is false
546
+ assert.deepEqual(validationFn.mock.calls[0].arguments, ['stored-value', 'dep-value']);
547
+ });
548
+
549
+ it('should fallback to fieldName when bodyFieldNames is not available', async () => {
550
+ const validationFn = mock.fn(() => true);
551
+ const validator = new CrossQuestionValidator({
552
+ dependencyFieldName: 'other',
553
+ validationFunction: validationFn
554
+ });
555
+ // Question without bodyFieldNames getter
556
+ const questionObj = {
557
+ fieldName: 'simpleField'
558
+ };
559
+ const req = createMockRequest({ simpleField: 'stored-value', other: 'dep-value' });
560
+ req.body = {
561
+ simpleField: 'body-value'
562
+ };
563
+ const chain = validator.validate(questionObj, req.res.locals.journeyResponse);
564
+ await chain[0].run(req);
565
+
566
+ assert.deepEqual(validationFn.mock.calls[0].arguments, ['stored-value', 'dep-value']);
567
+ });
568
+ });
132
569
  });
@@ -1 +1 @@
1
- {"version":3,"file":"question.d.ts","sourceRoot":"","sources":["../../../../src/components/address/question.js"],"names":[],"mappings":"AAOA;IACC;;OAEG;IACH,oBAFW,OAAO,iBAAiB,EAAE,kBAAkB,EAqBtD;IAXE;;;;;;kBAA8C;IAIhD;;;;;;MAMC;IAGF;;;OAGG;IACH,4BAHW,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,GACjB,GAAC,GAAC,MAAM,CAqBpB;IAqCD;;;OAGG;IACH;;eAUC;IAuBD,kEAMC;CACD;;yBAxIwB,WAAW"}
1
+ {"version":3,"file":"question.d.ts","sourceRoot":"","sources":["../../../../src/components/address/question.js"],"names":[],"mappings":"AAOA;IACC;;OAEG;IACH,oBAFW,OAAO,iBAAiB,EAAE,kBAAkB,EAqBtD;IAXE;;;;;;kBAA8C;IAIhD;;;;;;MAMC;IAiBF;;;OAGG;IACH,4BAHW,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,GACjB,GAAC,GAAC,MAAM,CAqBpB;IAqCD;;;OAGG;IACH;;eAUC;IAuBD,kEAMC;CACD;;yBAtJwB,WAAW"}
@@ -1 +1 @@
1
- {"version":3,"file":"question.d.ts","sourceRoot":"","sources":["../../../../src/components/date/question.js"],"names":[],"mappings":"AAKA;;GAEG;AACH;IACC;;;OAGG;IACH,uCAHW,OAAO,iBAAiB,EAAE,kBAAkB,EAStD;IADA,gBAA4B;IAG7B;;;;;OAKG,CACH,mBAJW,OAAO,SAAS,EAAE,OAAO,mBACzB,OAAO,mBAAmB,EAAE,eAAe,GACzC,OAAO,CAAE,MAAM,CAAC,CAY5B;IAED;;MAwBC;CAoBD;;yBAlFwB,6BAA6B"}
1
+ {"version":3,"file":"question.d.ts","sourceRoot":"","sources":["../../../../src/components/date/question.js"],"names":[],"mappings":"AAKA;;GAEG;AACH;IACC;;;OAGG;IACH,uCAHW,OAAO,iBAAiB,EAAE,kBAAkB,EAStD;IADA,gBAA4B;IAW7B;;;;;OAKG,CACH,mBAJW,OAAO,SAAS,EAAE,OAAO,mBACzB,OAAO,mBAAmB,EAAE,eAAe,GACzC,OAAO,CAAE,MAAM,CAAC,CAY5B;IAED;;MAwBC;CAoBD;;yBA1FwB,6BAA6B"}
@@ -1 +1 @@
1
- {"version":3,"file":"question.d.ts","sourceRoot":"","sources":["../../../../src/components/date-period/question.js"],"names":[],"mappings":"AAOA;;;GAGG;AACH;IACC;;;;;;;;;OASG;IACH,6FATW,OAAO,iBAAiB,EAAE,kBAAkB,EAqBtD;IAPA,gBAA4B;IAC5B,YAAsD;IAEtD,eAA0B;IAC1B,aAAsB;IACtB,eAA+D;IAC/D,aAA2D;IAG5D;;;;;OAKG,CACH,mBAJW,OAAO,SAAS,EAAE,OAAO,mBACzB,OAAO,mBAAmB,EAAE,eAAe,GACzC,OAAO,CAAE,MAAM,CAAC,CAgC5B;IAED;;MAuCC;CAyCD;;yBAzJwB,WAAW"}
1
+ {"version":3,"file":"question.d.ts","sourceRoot":"","sources":["../../../../src/components/date-period/question.js"],"names":[],"mappings":"AAOA;;;GAGG;AACH;IACC;;;;;;;;;OASG;IACH,6FATW,OAAO,iBAAiB,EAAE,kBAAkB,EAqBtD;IAPA,gBAA4B;IAC5B,YAAsD;IAEtD,eAA0B;IAC1B,aAAsB;IACtB,eAA+D;IAC/D,aAA2D;IAkB5D;;;;;OAKG,CACH,mBAJW,OAAO,SAAS,EAAE,OAAO,mBACzB,OAAO,mBAAmB,EAAE,eAAe,GACzC,OAAO,CAAE,MAAM,CAAC,CAgC5B;IAED;;MAuCC;CAyCD;;yBAxKwB,WAAW"}
@@ -1 +1 @@
1
- {"version":3,"file":"question.d.ts","sourceRoot":"","sources":["../../../../src/components/date-time/question.js"],"names":[],"mappings":"AAMA;IACC,kBAAiB;IACjB,kBAAiB;IAEjB;;;;OAIG;IACH,mDAJW,OAAO,iBAAiB,EAAE,kBAAkB,EAWtD;IAFA,gBAA4B;IAC5B,gBAA4B;IAG7B;;;;;OAKG,CACH,mBAJW,OAAO,SAAS,EAAE,OAAO,mBACzB,eAAe,GACb,OAAO,CAAE,MAAM,CAAC,CAqB5B;IAED;;MAqCC;;CAoCD;;yBA5HwB,6BAA6B"}
1
+ {"version":3,"file":"question.d.ts","sourceRoot":"","sources":["../../../../src/components/date-time/question.js"],"names":[],"mappings":"AAMA;IACC,kBAAiB;IACjB,kBAAiB;IAEjB;;;;OAIG;IACH,mDAJW,OAAO,iBAAiB,EAAE,kBAAkB,EAWtD;IAFA,gBAA4B;IAC5B,gBAA4B;IAkB7B;;;;;OAKG,CACH,mBAJW,OAAO,SAAS,EAAE,OAAO,mBACzB,eAAe,GACb,OAAO,CAAE,MAAM,CAAC,CAqB5B;IAED;;MAqCC;;CAoCD;;yBA3IwB,6BAA6B"}
@@ -25,6 +25,7 @@ export class MultiFieldInputQuestion extends Question {
25
25
  suffix?: import("#src/questions/question-props.d.ts").Affix;
26
26
  prefix?: import("#src/questions/question-props.d.ts").Affix;
27
27
  hint?: string;
28
+ classes?: string;
28
29
  }[];
29
30
  /**
30
31
  * returns the formatted answers values to be used to build task list elements
@@ -1 +1 @@
1
- {"version":3,"file":"question.d.ts","sourceRoot":"","sources":["../../../../src/components/multi-field-input/question.js"],"names":[],"mappings":"AAIA;;;;;;GAMG;AAEH;;GAEG;AACH;IACC;;OAEG;IACH,wCAFW,OAAO,oCAAoC,EAAE,4BAA4B,EAanF;IAJC,uEAA8B;IAMhC;;;;;;;;;;;QAOC;IAsBD;;;;;;;;;;;;;OAaG;IACH,wDAZW,OAAO,GAEL,KAAK,CAAC;QACd,GAAG,EAAE,MAAM,CAAC;QACZ,KAAK,EAAE,MAAM,GAAG,MAAM,CAAC;QACvB,MAAM,EAAE;YACN,IAAI,EAAE,MAAM,CAAC;YACb,IAAI,EAAE,MAAM,CAAC;YACb,kBAAkB,EAAE,MAAM,CAAC;SAC5B,CAAC;KACH,CAAC,CAkBJ;;CAyBD;;gCA/GY,OAAO,WAAW,EAAE,iBAAiB;sBACrC,OAAO,UAAU,EAAE,OAAO;8BAC1B,OAAO,mBAAmB,EAAE,eAAe;;;yBAP/B,WAAW"}
1
+ {"version":3,"file":"question.d.ts","sourceRoot":"","sources":["../../../../src/components/multi-field-input/question.js"],"names":[],"mappings":"AAIA;;;;;;GAMG;AAEH;;GAEG;AACH;IACC;;OAEG;IACH,wCAFW,OAAO,oCAAoC,EAAE,4BAA4B,EAanF;IAJC,uEAA8B;IAchC;;;;;;;;;;;;QAOC;IAsBD;;;;;;;;;;;;;OAaG;IACH,wDAZW,OAAO,GAEL,KAAK,CAAC;QACd,GAAG,EAAE,MAAM,CAAC;QACZ,KAAK,EAAE,MAAM,GAAG,MAAM,CAAC;QACvB,MAAM,EAAE;YACN,IAAI,EAAE,MAAM,CAAC;YACb,IAAI,EAAE,MAAM,CAAC;YACb,kBAAkB,EAAE,MAAM,CAAC;SAC5B,CAAC;KACH,CAAC,CAkBJ;;CAyBD;;gCAvHY,OAAO,WAAW,EAAE,iBAAiB;sBACrC,OAAO,UAAU,EAAE,OAAO;8BAC1B,OAAO,mBAAmB,EAAE,eAAe;;;yBAP/B,WAAW"}
@@ -68,6 +68,7 @@ interface InputField {
68
68
  suffix?: Affix; // used to add a suffix to the field
69
69
  prefix?: Affix; // used to add a prefix to the field
70
70
  hint?: string;
71
+ classes?: string;
71
72
  }
72
73
 
73
74
  /*
@@ -111,6 +111,14 @@ export class Question {
111
111
  * @returns {boolean}
112
112
  */
113
113
  get isInManageListSection(): boolean;
114
+ /**
115
+ * Gets the body field names used by this question in form submissions.
116
+ * Returns the field names that should be present in req.body when this question is submitted.
117
+ * Subclasses should override this for questions with multiple or differently-named body fields.
118
+ *
119
+ * @returns {string[]}
120
+ */
121
+ get bodyFieldNames(): string[];
114
122
  /**
115
123
  * gets the view model for this question
116
124
  *
@@ -1 +1 @@
1
- {"version":3,"file":"question.d.ts","sourceRoot":"","sources":["../../../src/questions/question.js"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"question.d.ts","sourceRoot":"","sources":["../../../src/questions/question.js"],"names":[],"mappings":"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"}
@@ -6,14 +6,16 @@ export class CrossQuestionValidator extends BaseValidator {
6
6
  * @param {Object} params
7
7
  * @param {string} params.dependencyFieldName - The field name of the other question to compare against.
8
8
  * @param {((currentAnswer: unknown, dependencyAnswer: unknown) => boolean)| null} params.validationFunction - A function that takes the current question's answer and the other question's answer and returns true if valid, false otherwise.
9
- * @param {boolean} [params.useBodyValues=false] - Whether to use req.body values for validation (default: false). Usually CrossQuestionValidator will be for validating across saved session answers, but if used between multiple fields on one question, it will need to use the body.
9
+ * @param {boolean} [params.useBodyValues=false] - Whether to use req.body values for validation (default: false). Usually CrossQuestionValidator will be for validating across saved JourneyResponse answers, but if used between multiple fields on one question, it will need to use the body.
10
+ * @param {boolean} [params.useBodyValuesForCurrent=false] - Whether to use req.body values for the current question's answer (default: false). This may help with questions where data is not saved to the JourneyResponse on POST (e.g. non-multiple entities questions).
10
11
  * @throws {Error} If validationFunction is not provided or is not a function.
11
12
  * @throws {Error} If dependencyFieldName is not provided
12
13
  */
13
- constructor({ dependencyFieldName, validationFunction, useBodyValues }?: {
14
+ constructor({ dependencyFieldName, validationFunction, useBodyValues, useBodyValuesForCurrent }?: {
14
15
  dependencyFieldName: string;
15
16
  validationFunction: ((currentAnswer: unknown, dependencyAnswer: unknown) => boolean) | null;
16
17
  useBodyValues?: boolean | undefined;
18
+ useBodyValuesForCurrent?: boolean | undefined;
17
19
  });
18
20
  /** @type {string} */
19
21
  dependencyFieldName: string;
@@ -21,13 +23,18 @@ export class CrossQuestionValidator extends BaseValidator {
21
23
  validationFunction: ((currentAnswer: unknown, dependencyAnswer: unknown) => boolean);
22
24
  /** @type {boolean} */
23
25
  useBodyValues: boolean;
26
+ /** @type {boolean} */
27
+ useBodyValuesForCurrent: boolean;
24
28
  /**
25
29
  * Validates response body against individual field validators.
30
+ * Uses the question's getDataToSave method to extract and format body values when useBodyValuesForCurrent is true,
31
+ * allowing generic handling of any question type without needing to know its internal structure.
26
32
  * @param {import('../questions/question.js').Question} questionObj - The question object containing the fieldName to validate.
27
33
  * @param {import('../journey/journey-response.js').JourneyResponse} [journeyResponse={}] - The current journey response, used to access saved answers for validation.
28
34
  * @returns {import('express-validator').ValidationChain[]}
29
35
  */
30
36
  validate(questionObj: import("../questions/question.js").Question, journeyResponse?: import("../journey/journey-response.js").JourneyResponse): import("express-validator").ValidationChain[];
37
+ #private;
31
38
  }
32
39
  export default CrossQuestionValidator;
33
40
  import BaseValidator from './base-validator.js';
@@ -1 +1 @@
1
- {"version":3,"file":"cross-question-validator.d.ts","sourceRoot":"","sources":["../../../src/validator/cross-question-validator.js"],"names":[],"mappings":"AAGA;;GAEG;AACH;IAQC;;;;;;;OAOG;IACH,yEANG;QAAuB,mBAAmB,EAAlC,MAAM;QACyE,kBAAkB,EAAjG,CAAC,CAAC,aAAa,EAAE,OAAO,EAAE,gBAAgB,EAAE,OAAO,KAAK,OAAO,CAAC,GAAE,IAAI;QACrD,aAAa;KACtC,EAgBF;IA5BD,qBAAqB;IACrB,qBADW,MAAM,CACG;IACpB,+EAA+E;IAC/E,oBADW,CAAC,CAAC,aAAa,EAAE,OAAO,EAAE,gBAAgB,EAAE,OAAO,KAAK,OAAO,CAAC,CACxD;IACnB,sBAAsB;IACtB,eADW,OAAO,CACJ;IAyBd;;;;;OAKG;IACH,sBAJW,OAAO,0BAA0B,EAAE,QAAQ,oBAC3C,OAAO,gCAAgC,EAAE,eAAe,GACtD,OAAO,mBAAmB,EAAE,eAAe,EAAE,CAqBzD;CACD;;0BA/DyB,qBAAqB"}
1
+ {"version":3,"file":"cross-question-validator.d.ts","sourceRoot":"","sources":["../../../src/validator/cross-question-validator.js"],"names":[],"mappings":"AAGA;;GAEG;AACH;IAUC;;;;;;;;OAQG;IACH,kGAPG;QAAuB,mBAAmB,EAAlC,MAAM;QACyE,kBAAkB,EAAjG,CAAC,CAAC,aAAa,EAAE,OAAO,EAAE,gBAAgB,EAAE,OAAO,KAAK,OAAO,CAAC,GAAE,IAAI;QACrD,aAAa;QACb,uBAAuB;KAChD,EAsBF;IArCD,qBAAqB;IACrB,qBADW,MAAM,CACG;IACpB,+EAA+E;IAC/E,oBADW,CAAC,CAAC,aAAa,EAAE,OAAO,EAAE,gBAAgB,EAAE,OAAO,KAAK,OAAO,CAAC,CACxD;IACnB,sBAAsB;IACtB,eADW,OAAO,CACJ;IACd,sBAAsB;IACtB,yBADW,OAAO,CACM;IA8CxB;;;;;;;OAOG;IACH,sBAJW,OAAO,0BAA0B,EAAE,QAAQ,oBAC3C,OAAO,gCAAgC,EAAE,eAAe,GACtD,OAAO,mBAAmB,EAAE,eAAe,EAAE,CAuCzD;;CACD;;0BA1GyB,qBAAqB"}