Skip to content

Commit 2be8a06

Browse files
DF-951: MonthYearField to support absolute date range options (#423)
* Add validation to MonthYearField to support absolute date range options * Bump @defra/forms-model@3.0.678
1 parent 2c04b8a commit 2be8a06

4 files changed

Lines changed: 98 additions & 7 deletions

File tree

package-lock.json

Lines changed: 4 additions & 4 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -87,7 +87,7 @@
8787
},
8888
"license": "SEE LICENSE IN LICENSE",
8989
"dependencies": {
90-
"@defra/forms-model": "^3.0.676",
90+
"@defra/forms-model": "^3.0.678",
9191
"@defra/hapi-tracing": "^1.29.0",
9292
"@defra/interactive-map": "^0.0.22-alpha",
9393
"@elastic/ecs-pino-format": "^1.5.0",

src/server/plugins/engine/components/MonthYearField.test.ts

Lines changed: 64 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import { ComponentType, type MonthYearFieldComponent } from '@defra/forms-model'
2-
import { startOfDay } from 'date-fns'
2+
import { addMonths, format, startOfDay, startOfMonth } from 'date-fns'
33

44
import { ComponentCollection } from '~/src/server/plugins/engine/components/ComponentCollection.js'
55
import {
@@ -401,6 +401,13 @@ describe('MonthYearField', () => {
401401

402402
describe('Validation', () => {
403403
const date = new Date('2001-01-01')
404+
const today = startOfDay(new Date())
405+
const thisMonth = startOfMonth(today)
406+
407+
const OneMonthInPast = addMonths(thisMonth, -1)
408+
const TwoMonthsInPast = addMonths(thisMonth, -2)
409+
const OneMonthInFuture = addMonths(thisMonth, 1)
410+
const TwoMonthsInFuture = addMonths(thisMonth, 2)
404411

405412
describe.each([
406413
{
@@ -517,6 +524,62 @@ describe('MonthYearField', () => {
517524
}
518525
]
519526
},
527+
{
528+
description: 'Earliest month/year option',
529+
component: {
530+
title: 'Example month/year field',
531+
name: 'myComponent',
532+
type: ComponentType.MonthYearField,
533+
options: {
534+
earliestMonthYear: format(OneMonthInPast, 'yyyy-MM')
535+
}
536+
} satisfies MonthYearFieldComponent,
537+
assertions: [
538+
{
539+
input: getFormData(TwoMonthsInPast),
540+
output: {
541+
value: getFormData(TwoMonthsInPast),
542+
errors: [
543+
expect.objectContaining({
544+
text: `Example month/year field must be the same as or after ${format(OneMonthInPast, 'd MMMM yyyy')}`
545+
})
546+
]
547+
}
548+
},
549+
{
550+
input: getFormData(today),
551+
output: { value: getFormData(today) }
552+
}
553+
]
554+
},
555+
{
556+
description: 'Latest month/year option',
557+
component: {
558+
title: 'Example month/year field',
559+
name: 'myComponent',
560+
type: ComponentType.MonthYearField,
561+
options: {
562+
latestMonthYear: format(OneMonthInFuture, 'yyyy-MM')
563+
}
564+
} satisfies MonthYearFieldComponent,
565+
assertions: [
566+
{
567+
input: getFormData(TwoMonthsInFuture),
568+
output: {
569+
value: getFormData(TwoMonthsInFuture),
570+
errors: [
571+
expect.objectContaining({
572+
text: `Example month/year field must be the same as or before ${format(OneMonthInFuture, 'd MMMM yyyy')}`
573+
})
574+
]
575+
}
576+
},
577+
{
578+
input: getFormData(today),
579+
output: { value: getFormData(today) }
580+
}
581+
]
582+
},
520583
{
521584
description: 'Optional fields',
522585
component: {

src/server/plugins/engine/components/MonthYearField.ts

Lines changed: 29 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import { ComponentType, type MonthYearFieldComponent } from '@defra/forms-model'
2-
import { format, isValid } from 'date-fns'
2+
import { format, isValid, parse } from 'date-fns'
33
import {
44
type Context,
55
type CustomValidator,
@@ -259,6 +259,34 @@ export function getValidatorMonthYear(component: MonthYearField) {
259259
: payload
260260
}
261261

262+
const date = parse(
263+
`${values.year}-${values.month}-01`,
264+
'yyyy-MM-dd',
265+
new Date()
266+
)
267+
268+
if (!isValid(date)) {
269+
return helpers.error('date.format', context)
270+
}
271+
272+
// Minimum date from today
273+
const earliestDate = options.earliestMonthYear
274+
? new Date(`${options.earliestMonthYear}-01`)
275+
: undefined
276+
277+
if (earliestDate && date < earliestDate) {
278+
return helpers.error('date.min', { ...context, limit: earliestDate })
279+
}
280+
281+
// Maximum date from today
282+
const latestDate = options.latestMonthYear
283+
? new Date(`${options.latestMonthYear}-01`)
284+
: undefined
285+
286+
if (latestDate && date > latestDate) {
287+
return helpers.error('date.max', { ...context, limit: latestDate })
288+
}
289+
262290
return payload
263291
}
264292

0 commit comments

Comments
 (0)