@planning-inspectorate/dynamic-forms 3.13.1 → 3.14.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.
package/package.json
CHANGED
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
import { body } from 'express-validator';
|
|
2
|
-
|
|
3
2
|
import { isAfter, isBefore, isValid, parse } from 'date-fns';
|
|
4
3
|
import { enGB } from 'date-fns/locale';
|
|
5
4
|
|
|
@@ -12,8 +11,9 @@ import { endOfDay, parseDateInput, startOfDay } from '../lib/date-utils.js';
|
|
|
12
11
|
|
|
13
12
|
/**
|
|
14
13
|
* @typedef {Object} DateValidationSettings
|
|
15
|
-
* @property {Boolean} ensureFuture
|
|
16
|
-
* @property {Boolean} ensurePast
|
|
14
|
+
* @property {Boolean} [ensureFuture]
|
|
15
|
+
* @property {Boolean} [ensurePast]
|
|
16
|
+
* @property {Boolean} [optional]
|
|
17
17
|
*/
|
|
18
18
|
|
|
19
19
|
/**
|
|
@@ -34,7 +34,8 @@ export class DateValidator extends BaseValidator {
|
|
|
34
34
|
inputLabel,
|
|
35
35
|
dateValidationSettings = {
|
|
36
36
|
ensureFuture: false,
|
|
37
|
-
ensurePast: false
|
|
37
|
+
ensurePast: false,
|
|
38
|
+
optional: false
|
|
38
39
|
},
|
|
39
40
|
errorMessages
|
|
40
41
|
) {
|
|
@@ -63,7 +64,7 @@ export class DateValidator extends BaseValidator {
|
|
|
63
64
|
}
|
|
64
65
|
|
|
65
66
|
isRequired() {
|
|
66
|
-
return
|
|
67
|
+
return !this.dateValidationSettings.optional;
|
|
67
68
|
}
|
|
68
69
|
|
|
69
70
|
/**
|
|
@@ -76,9 +77,21 @@ export class DateValidator extends BaseValidator {
|
|
|
76
77
|
const monthInput = `${fieldName}_month`;
|
|
77
78
|
const yearInput = `${fieldName}_year`;
|
|
78
79
|
|
|
80
|
+
/**
|
|
81
|
+
* Run before every check, if the correct param set then skip the validation
|
|
82
|
+
* when there is no data entered.
|
|
83
|
+
*/
|
|
84
|
+
const shouldValidate = (value, { req }) => {
|
|
85
|
+
if (this.dateValidationSettings.optional === true) {
|
|
86
|
+
return !!(req.body[dayInput] || req.body[monthInput] || req.body[yearInput]);
|
|
87
|
+
}
|
|
88
|
+
return true;
|
|
89
|
+
};
|
|
90
|
+
|
|
79
91
|
const rules = [
|
|
80
92
|
// check all or some date inputs are not empty
|
|
81
93
|
body(dayInput)
|
|
94
|
+
.if(shouldValidate)
|
|
82
95
|
.notEmpty()
|
|
83
96
|
.withMessage((_, { req }) => {
|
|
84
97
|
if (!req.body[monthInput] && !req.body[yearInput]) {
|
|
@@ -96,6 +109,7 @@ export class DateValidator extends BaseValidator {
|
|
|
96
109
|
return this.noDayErrorMessage;
|
|
97
110
|
}),
|
|
98
111
|
body(monthInput)
|
|
112
|
+
.if(shouldValidate)
|
|
99
113
|
.notEmpty()
|
|
100
114
|
.withMessage((_, { req }) => {
|
|
101
115
|
if (req.body[dayInput] && !req.body[yearInput]) {
|
|
@@ -108,6 +122,7 @@ export class DateValidator extends BaseValidator {
|
|
|
108
122
|
// empty error message returned
|
|
109
123
|
}),
|
|
110
124
|
body(yearInput)
|
|
125
|
+
.if(shouldValidate)
|
|
111
126
|
.notEmpty()
|
|
112
127
|
.withMessage((_, { req }) => {
|
|
113
128
|
if (req.body[dayInput] && req.body[monthInput]) return this.noYearErrorMessage;
|
|
@@ -117,6 +132,7 @@ export class DateValidator extends BaseValidator {
|
|
|
117
132
|
|
|
118
133
|
// check date values entered are within valid ranges
|
|
119
134
|
body(dayInput)
|
|
135
|
+
.if(shouldValidate)
|
|
120
136
|
.isInt({ min: 1, max: 31 })
|
|
121
137
|
.withMessage(this.invalidDateErrorMessage)
|
|
122
138
|
.bail()
|
|
@@ -135,38 +151,42 @@ export class DateValidator extends BaseValidator {
|
|
|
135
151
|
|
|
136
152
|
return true;
|
|
137
153
|
}),
|
|
138
|
-
body(monthInput).isInt({ min: 1, max: 12 }).withMessage(this.invalidMonthErrorMessage),
|
|
139
|
-
body(yearInput).isInt({ min: 1000, max: 9999 }).withMessage(this.invalidYearErrorMessage)
|
|
154
|
+
body(monthInput).if(shouldValidate).isInt({ min: 1, max: 12 }).withMessage(this.invalidMonthErrorMessage),
|
|
155
|
+
body(yearInput).if(shouldValidate).isInt({ min: 1000, max: 9999 }).withMessage(this.invalidYearErrorMessage)
|
|
140
156
|
];
|
|
141
157
|
|
|
142
158
|
if (this.dateValidationSettings.ensurePast === true) {
|
|
143
159
|
rules.push(
|
|
144
|
-
body(dayInput)
|
|
145
|
-
|
|
160
|
+
body(dayInput)
|
|
161
|
+
.if(shouldValidate)
|
|
162
|
+
.custom((value, { req }) => {
|
|
163
|
+
const inputDate = parseDateInput({ day: value, month: req.body[monthInput], year: req.body[yearInput] });
|
|
146
164
|
|
|
147
|
-
|
|
165
|
+
const today = endOfDay();
|
|
148
166
|
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
167
|
+
if (isAfter(inputDate, today)) {
|
|
168
|
+
throw new Error(this.futureDateErrorMessage);
|
|
169
|
+
}
|
|
152
170
|
|
|
153
|
-
|
|
154
|
-
|
|
171
|
+
return true;
|
|
172
|
+
})
|
|
155
173
|
);
|
|
156
174
|
}
|
|
157
175
|
|
|
158
176
|
if (this.dateValidationSettings.ensureFuture === true) {
|
|
159
177
|
rules.push(
|
|
160
|
-
body(dayInput)
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
178
|
+
body(dayInput)
|
|
179
|
+
.if(shouldValidate)
|
|
180
|
+
.custom((value, { req }) => {
|
|
181
|
+
const inputDate = parseDateInput({ day: value, month: req.body[monthInput], year: req.body[yearInput] });
|
|
182
|
+
const today = startOfDay();
|
|
183
|
+
|
|
184
|
+
if (isBefore(inputDate, today)) {
|
|
185
|
+
throw new Error(this.pastDateErrorMessage);
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
return true;
|
|
189
|
+
})
|
|
170
190
|
);
|
|
171
191
|
}
|
|
172
192
|
|
|
@@ -389,6 +389,109 @@ describe('./src/dynamic-forms/validator/date-validator.js', () => {
|
|
|
389
389
|
assert.strictEqual(errors[`${question.fieldName}_month`].msg, errorMessages.invalidMonthErrorMessage);
|
|
390
390
|
assert.strictEqual(errors[`${question.fieldName}_year`].msg, errorMessages.invalidYearErrorMessage);
|
|
391
391
|
});
|
|
392
|
+
|
|
393
|
+
it('passes validation if optional is true and no fields are provided (undefined)', async () => {
|
|
394
|
+
const req = {
|
|
395
|
+
body: {
|
|
396
|
+
['date-question_day']: undefined,
|
|
397
|
+
['date-question_month']: undefined,
|
|
398
|
+
['date-question_year']: undefined
|
|
399
|
+
}
|
|
400
|
+
};
|
|
401
|
+
|
|
402
|
+
const question = {
|
|
403
|
+
fieldName: 'date-question'
|
|
404
|
+
};
|
|
405
|
+
|
|
406
|
+
const errors = await _validationMappedErrors(req, question, 'the required date', undefined, {
|
|
407
|
+
optional: true
|
|
408
|
+
});
|
|
409
|
+
|
|
410
|
+
assert.strictEqual(Object.keys(errors).length, 0);
|
|
411
|
+
});
|
|
412
|
+
|
|
413
|
+
it('passes validation if optional is true and fields are empty strings', async () => {
|
|
414
|
+
const req = {
|
|
415
|
+
body: {
|
|
416
|
+
['date-question_day']: '',
|
|
417
|
+
['date-question_month']: '',
|
|
418
|
+
['date-question_year']: ''
|
|
419
|
+
}
|
|
420
|
+
};
|
|
421
|
+
|
|
422
|
+
const question = {
|
|
423
|
+
fieldName: 'date-question'
|
|
424
|
+
};
|
|
425
|
+
|
|
426
|
+
const errors = await _validationMappedErrors(req, question, 'the required date', undefined, {
|
|
427
|
+
optional: true
|
|
428
|
+
});
|
|
429
|
+
|
|
430
|
+
assert.strictEqual(Object.keys(errors).length, 0);
|
|
431
|
+
});
|
|
432
|
+
|
|
433
|
+
it('throws error if optional is true but date is partially filled (missing day)', async () => {
|
|
434
|
+
const req = {
|
|
435
|
+
body: {
|
|
436
|
+
['date-question_day']: '',
|
|
437
|
+
['date-question_month']: '10',
|
|
438
|
+
['date-question_year']: '2023'
|
|
439
|
+
}
|
|
440
|
+
};
|
|
441
|
+
|
|
442
|
+
const question = {
|
|
443
|
+
fieldName: 'date-question'
|
|
444
|
+
};
|
|
445
|
+
|
|
446
|
+
const errors = await _validationMappedErrors(req, question, 'the required date', undefined, {
|
|
447
|
+
optional: true
|
|
448
|
+
});
|
|
449
|
+
|
|
450
|
+
assert.strictEqual(Object.keys(errors).length, 1);
|
|
451
|
+
assert.strictEqual(errors[`${question.fieldName}_day`].msg, 'The required date must include a day');
|
|
452
|
+
});
|
|
453
|
+
|
|
454
|
+
it('throws error if optional is true but invalid values are provided', async () => {
|
|
455
|
+
const req = {
|
|
456
|
+
body: {
|
|
457
|
+
['date-question_day']: '35',
|
|
458
|
+
['date-question_month']: '10',
|
|
459
|
+
['date-question_year']: '2023'
|
|
460
|
+
}
|
|
461
|
+
};
|
|
462
|
+
|
|
463
|
+
const question = {
|
|
464
|
+
fieldName: 'date-question'
|
|
465
|
+
};
|
|
466
|
+
|
|
467
|
+
const errors = await _validationMappedErrors(req, question, 'the required date', undefined, {
|
|
468
|
+
optional: true
|
|
469
|
+
});
|
|
470
|
+
|
|
471
|
+
assert.strictEqual(Object.keys(errors).length, 1);
|
|
472
|
+
assert.strictEqual(errors[`${question.fieldName}_day`].msg, 'The required date day must be a real day');
|
|
473
|
+
});
|
|
474
|
+
|
|
475
|
+
it('throws error if an object is passed without the optional key (undefined) and nothing is entered', async () => {
|
|
476
|
+
const req = {
|
|
477
|
+
body: {
|
|
478
|
+
['date-question_day']: undefined,
|
|
479
|
+
['date-question_month']: undefined,
|
|
480
|
+
['date-question_year']: undefined
|
|
481
|
+
}
|
|
482
|
+
};
|
|
483
|
+
|
|
484
|
+
const question = {
|
|
485
|
+
fieldName: 'date-question'
|
|
486
|
+
};
|
|
487
|
+
|
|
488
|
+
const errors = await _validationMappedErrors(req, question, 'the required date', undefined, {
|
|
489
|
+
randomKey: true
|
|
490
|
+
});
|
|
491
|
+
|
|
492
|
+
assert.strictEqual(Object.keys(errors).length, 3);
|
|
493
|
+
assert.strictEqual(errors[`${question.fieldName}_day`].msg, 'Enter the required date');
|
|
494
|
+
});
|
|
392
495
|
});
|
|
393
496
|
});
|
|
394
497
|
|
|
@@ -3,8 +3,9 @@
|
|
|
3
3
|
*/
|
|
4
4
|
/**
|
|
5
5
|
* @typedef {Object} DateValidationSettings
|
|
6
|
-
* @property {Boolean} ensureFuture
|
|
7
|
-
* @property {Boolean} ensurePast
|
|
6
|
+
* @property {Boolean} [ensureFuture]
|
|
7
|
+
* @property {Boolean} [ensurePast]
|
|
8
|
+
* @property {Boolean} [optional]
|
|
8
9
|
*/
|
|
9
10
|
/**
|
|
10
11
|
* enforces a user has entered a valid date
|
|
@@ -42,8 +43,9 @@ export class DateValidator extends BaseValidator {
|
|
|
42
43
|
export default DateValidator;
|
|
43
44
|
export type DateQuestion = typeof import("../components/date/question.js");
|
|
44
45
|
export type DateValidationSettings = {
|
|
45
|
-
ensureFuture
|
|
46
|
-
ensurePast
|
|
46
|
+
ensureFuture?: boolean | undefined;
|
|
47
|
+
ensurePast?: boolean | undefined;
|
|
48
|
+
optional?: boolean | undefined;
|
|
47
49
|
};
|
|
48
50
|
import BaseValidator from './base-validator.js';
|
|
49
51
|
//# sourceMappingURL=date-validator.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"date-validator.d.ts","sourceRoot":"","sources":["../../../src/validator/date-validator.js"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"date-validator.d.ts","sourceRoot":"","sources":["../../../src/validator/date-validator.js"],"names":[],"mappings":"AAOA;;GAEG;AAEH;;;;;GAKG;AAEH;;;GAGG;AACH;IAIC;;;;;OAKG;IACH,wBAJW,MAAM,2BACN,sBAAsB,kBACtB,MAAM,EAiChB;IAxCD,qCAAqC;IACrC,wBADW,sBAAsB,CACV;IAqBtB,uBAAmG;IACnG,uBAAmG;IACnG,yBAAyG;IACzG,wBAAsG;IACtG,4BAAkH;IAClH,2BAA+G;IAC/G,6BACuF;IACvF,6BACuF;IACvF,8BACyF;IACzF,6BACuF;IACvF,4BAAkH;IAClH,0BAA4G;IAS7G;;;OAGG;IACH,sBAFW,YAAY,iDA0HtB;;CA6BD;;;;;;;;0BA1NyB,qBAAqB"}
|