From 5c2bc529970e8da8a33050eb372d7b240d18a5b9 Mon Sep 17 00:00:00 2001 From: SilviaAmAm Date: Mon, 11 Sep 2023 17:58:04 +0200 Subject: [PATCH 1/2] :adhesive_bandage: [open-formulieren/open-forms#3443] New validator to catch the case where date is entered manually --- src/formio/components/DateField.js | 13 ++++++ src/formio/validators/minMaxDateValidator.js | 47 ++++++++++++++++++++ 2 files changed, 60 insertions(+) create mode 100644 src/formio/validators/minMaxDateValidator.js diff --git a/src/formio/components/DateField.js b/src/formio/components/DateField.js index 892319731..44f3bcc1c 100644 --- a/src/formio/components/DateField.js +++ b/src/formio/components/DateField.js @@ -1,5 +1,7 @@ import {Formio} from 'react-formio'; +import MinMaxDateValidator from 'formio/validators/minMaxDateValidator'; + const DateTimeField = Formio.Components.components.datetime; const extractDate = value => { @@ -11,6 +13,17 @@ const extractDate = value => { }; class DateField extends DateTimeField { + constructor(component, options, data) { + super(component, options, data); + + if (component.datePicker.minDate || component.datePicker.maxDate) { + component.validate.dateMinMax = true; + } + + this.validators.push('dateMinMax'); + this.validator.validators['dateMinMax'] = MinMaxDateValidator; + } + get suffix() { // Don't show an icon return null; diff --git a/src/formio/validators/minMaxDateValidator.js b/src/formio/validators/minMaxDateValidator.js new file mode 100644 index 000000000..0c74f23ae --- /dev/null +++ b/src/formio/validators/minMaxDateValidator.js @@ -0,0 +1,47 @@ +import set from 'lodash/set'; + +const validateDateBoundaries = (minBoundary, maxBoundary, value) => { + const minDate = minBoundary ? new Date(minBoundary) : null; + const maxDate = maxBoundary ? new Date(maxBoundary) : null; + + if (!minDate && !maxDate) { + return {isValid: true}; + } + + const parsedValue = new Date(value); + + if (minDate) return {isValid: parsedValue >= minDate, errorKeys: ['minDate']}; + if (maxDate) return {isValid: parsedValue < maxDate, errorKeys: ['maxDate']}; +}; + +const MinMaxDateValidator = { + key: 'validate.dateMinMax', + message(component) { + const minDate = new Date(component.component.minDate); + const maxDate = new Date(component.component.maxDate); + + const errorKeys = component?.openForms?.validationErrorContext?.minMaxDateValidatorErrorKeys; + const errorMessage = errorKeys ? errorKeys[0] : 'invalidDate'; + + return component.t(errorMessage, { + minDate: minDate, + maxDate: maxDate, + }); + }, + check(component, setting, value) { + if (!value) return true; + + const {isValid, errorKeys} = validateDateBoundaries( + component.component.datePicker.minDate, + component.component.datePicker.maxDate, + value + ); + + if (!isValid) { + set(component, 'openForms.validationErrorContext.minMaxDateValidatorErrorKeys', errorKeys); + } + return isValid; + }, +}; + +export default MinMaxDateValidator; From 01cf71510c5bab22b4e29491a7f61e22f0a8fce7 Mon Sep 17 00:00:00 2001 From: SilviaAmAm Date: Wed, 13 Sep 2023 17:52:45 +0200 Subject: [PATCH 2/2] :memo: [open-formulieren/open-forms#3443] Attempt at writing a story for this... The jest test didnt go anywhere (the method that creates a form with a date component returns a promise that never resolves). In storybook you can interact manually with a component with a minDate and see that it works. It's something. --- src/formio/components/DateField.stories.js | 59 ++++++++++++++++++++++ 1 file changed, 59 insertions(+) diff --git a/src/formio/components/DateField.stories.js b/src/formio/components/DateField.stories.js index b563c2fa4..88e9869b7 100644 --- a/src/formio/components/DateField.stories.js +++ b/src/formio/components/DateField.stories.js @@ -1,3 +1,6 @@ +import {expect} from '@storybook/jest'; +import {userEvent, within} from '@storybook/testing-library'; + import {withUtrechtDocument} from 'story-utils/decorators'; import {SingleFormioComponent} from './story-util'; @@ -38,5 +41,61 @@ export const DateField = { args: { key: 'date', label: 'Datum', + extraComponentProperties: { + format: 'dd-MM-yyyy', + placeholder: 'dd-mm-yyyy', + enableTime: false, + datePicker: { + minDate: null, + maxDate: null, + }, + }, + }, + play: async ({canvasElement}) => { + const canvas = within(canvasElement); + + const dateInput = canvas.getByRole('textbox'); + + userEvent.type(dateInput, '06-06-2006'); + await expect(dateInput).toHaveDisplayValue('06-06-2006'); + + const error = canvas.queryByText('minDate'); + await expect(error).toBeNull(); + // This test succeeds, but the value is not displayed in storybook... Mystery + }, +}; + +export const DateWithMinField = { + render: SingleFormioComponent, + args: { + key: 'date', + label: 'Datum > 08-09-2023', + extraComponentProperties: { + format: 'dd-MM-yyyy', + placeholder: 'dd-mm-yyyy', + enableTime: false, + datePicker: { + minDate: '2023-09-08', + maxDate: null, + }, + customOptions: { + allowInvalidPreload: true, + }, + validate: { + dateMinMax: true, + }, + }, + }, + play: async ({canvasElement}) => { + const canvas = within(canvasElement); + + const dateInput = canvas.getByRole('textbox'); + + userEvent.type(dateInput, '06-06-2006'); + await expect(dateInput).toHaveDisplayValue('06-06-2006'); + + // TODO: I cannot get this to work. If you do it manually in storybook, it works... (it shows the error). + // const error = canvas.queryByText('minDate'); + // await expect(error).not.toBeNull(); }, };