Skip to content

Commit

Permalink
Merge pull request #636 from open-formulieren/fix/3755-datetime-valid…
Browse files Browse the repository at this point in the history
…ation

[OF#3755] Datetime validation
  • Loading branch information
sergei-maertens authored Jan 24, 2024
2 parents 767dfcc + eb4def7 commit 646aa8c
Show file tree
Hide file tree
Showing 5 changed files with 312 additions and 21 deletions.
13 changes: 13 additions & 0 deletions src/formio/components/DateTimeField.js
Original file line number Diff line number Diff line change
@@ -1,8 +1,21 @@
import {Formio} from 'react-formio';

import MinMaxDatetimeValidator from 'formio/validators/minMaxDatetimeValidator';

const DateTimeFormio = Formio.Components.components.datetime;

class DateTimeField extends DateTimeFormio {
constructor(component, options, data) {
super(component, options, data);

if (component.datePicker.minDate || component.datePicker.maxDate) {
component.validate.datetimeMinMax = true;
}

this.validators.push('datetimeMinMax');
this.validator.validators['datetimeMinMax'] = MinMaxDatetimeValidator;
}

get inputInfo() {
const info = super.inputInfo;
// apply NLDS CSS classes
Expand Down
24 changes: 3 additions & 21 deletions src/formio/validators/minMaxDateValidator.js
Original file line number Diff line number Diff line change
@@ -1,25 +1,6 @@
import {parseISO} from 'date-fns';
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 = parseISO(value, 'yyyy-MM-dd', new Date());

if (minDate && maxDate) {
const isValid = parsedValue >= minDate && parsedValue <= maxDate;
let errorKeys = isValid ? [] : parsedValue < minDate ? ['minDate'] : ['maxDate'];
return {isValid, errorKeys};
}

if (minDate) return {isValid: parsedValue >= minDate, errorKeys: ['minDate']};
if (maxDate) return {isValid: parsedValue <= maxDate, errorKeys: ['maxDate']};
};
import {validateBoundaries} from './utils';

const MinMaxDateValidator = {
key: 'validate.dateMinMax',
Expand All @@ -38,7 +19,8 @@ const MinMaxDateValidator = {
check(component, setting, value) {
if (!value) return true;

const {isValid, errorKeys} = validateDateBoundaries(
const {isValid, errorKeys} = validateBoundaries(
component.type,
component.component.datePicker.minDate,
component.component.datePicker.maxDate,
value
Expand Down
42 changes: 42 additions & 0 deletions src/formio/validators/minMaxDatetimeValidator.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
import set from 'lodash/set';

import {validateBoundaries} from './utils';

const MinMaxDatetimeValidator = {
key: 'validate.datetimeMinMax',
message(component) {
// In the form builder, this property is called 'minDate'/'maxDate' also in the datetime component
const minDatetime = new Date(component.component.minDate);
const maxDatetime = new Date(component.component.maxDate);

const errorKeys =
component?.openForms?.validationErrorContext?.minMaxDatetimeValidatorErrorKeys;
const errorMessage = errorKeys ? errorKeys[0] : 'invalidDatetime';

return component.t(errorMessage, {
minDatetime: minDatetime,
maxDatetime: maxDatetime,
});
},
check(component, setting, value) {
if (!value) return true;

const {isValid, errorKeys} = validateBoundaries(
component.type,
component.component.datePicker.minDate,
component.component.datePicker.maxDate,
value
);

if (!isValid) {
set(
component,
'openForms.validationErrorContext.minMaxDatetimeValidatorErrorKeys',
errorKeys
);
}
return isValid;
},
};

export default MinMaxDatetimeValidator;
39 changes: 39 additions & 0 deletions src/formio/validators/utils.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
import {parseISO} from 'date-fns';

export const validateBoundaries = (componentType, minBoundary, maxBoundary, value) => {
const parsedMinBoundary = minBoundary ? new Date(minBoundary) : null;
const parsedMaxBoundary = maxBoundary ? new Date(maxBoundary) : null;

if (!parsedMinBoundary && !parsedMaxBoundary) {
return {isValid: true};
}

const parsedValue = parseISO(value);

let errorKeyMinValue, errorKeyMaxValue;
if (componentType === 'date') {
errorKeyMinValue = 'minDate';
errorKeyMaxValue = 'maxDate';
} else if (componentType === 'datetime') {
errorKeyMinValue = 'minDatetime';
errorKeyMaxValue = 'maxDatetime';
}

if (parsedMinBoundary && parsedMaxBoundary) {
const isValid = parsedValue >= parsedMinBoundary && parsedValue <= parsedMaxBoundary;
let errorKeys = [];
if (!isValid) {
if (parsedValue < parsedMinBoundary) {
errorKeys.push(errorKeyMinValue);
} else {
errorKeys.push(errorKeyMaxValue);
}
}
return {isValid, errorKeys};
}

if (parsedMinBoundary)
return {isValid: parsedValue >= parsedMinBoundary, errorKeys: [errorKeyMinValue]};
if (parsedMaxBoundary)
return {isValid: parsedValue <= parsedMaxBoundary, errorKeys: [errorKeyMaxValue]};
};
215 changes: 215 additions & 0 deletions src/jstests/formio/components/datetime.spec.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,215 @@
import set from 'lodash/set';
import {Formio} from 'react-formio';

import MinMaxDatetimeValidator from 'formio/validators/minMaxDatetimeValidator';

const FormioComponent = Formio.Components.components.component;

describe('Datetime Component', () => {
test('Datetime validator: no min/max datetime', () => {
const component = {
label: 'datetime',
key: 'datetime',
type: 'datetime',
datePicker: {
minDate: null,
maxDate: null,
},
customOptions: {
allowInvalidPreload: true,
},
validate: {datetimeMinMax: true},
};

const componentInstance = new FormioComponent(component, {}, {});

const isValid = MinMaxDatetimeValidator.check(
componentInstance,
{},
'2020-01-01T10:00:00+01:00'
);

expect(isValid).toBeTruthy();
});

test('Datetime validator: check min datetime', () => {
const component = {
label: 'datetime',
key: 'datetime',
type: 'datetime',
datePicker: {
minDate: '2023-01-01T10:00:00+01:00',
maxDate: null,
},
customOptions: {
allowInvalidPreload: true,
},
validate: {datetimeMinMax: true},
};

const componentInstance = new FormioComponent(component, {}, {});

const isValid1 = MinMaxDatetimeValidator.check(
componentInstance,
{},
'2020-01-01T10:00:00+01:00'
);

expect(isValid1).toBeFalsy();
expect(
componentInstance.openForms.validationErrorContext.minMaxDatetimeValidatorErrorKeys
).toContain('minDatetime');

const isValid2 = MinMaxDatetimeValidator.check(
componentInstance,
{},
'2024-01-01T10:00:00+01:00'
);

expect(isValid2).toBeTruthy();
});

test('Datetime validator: check max datetime', () => {
const component = {
label: 'datetime',
key: 'datetime',
type: 'datetime',
datePicker: {
minDate: null,
maxDate: '2023-01-01T10:00:00+01:00',
},
customOptions: {
allowInvalidPreload: true,
},
validate: {datetimeMinMax: true},
};

const componentInstance = new FormioComponent(component, {}, {});

const isValid1 = MinMaxDatetimeValidator.check(
componentInstance,
{},
'2024-01-01T10:00:00+01:00'
);

expect(isValid1).toBeFalsy();
expect(
componentInstance.openForms.validationErrorContext.minMaxDatetimeValidatorErrorKeys
).toContain('maxDatetime');

const isValid2 = MinMaxDatetimeValidator.check(
componentInstance,
{},
'2020-01-01T10:00:00+01:00'
);

expect(isValid2).toBeTruthy();
});

test('Datetime validator: check max datetime including the current one', () => {
const component = {
label: 'datetime',
key: 'datetime',
type: 'datetime',
datePicker: {
minDate: null,
maxDate: '2023-09-08T10:00:00+01:00',
},
customOptions: {
allowInvalidPreload: true,
},
validate: {datetimeMinMax: true},
};

const componentInstance = new FormioComponent(component, {}, {});

const isValid1 = MinMaxDatetimeValidator.check(
componentInstance,
{},
'2023-09-08T10:00:00+01:00'
);

expect(isValid1).toBeTruthy();
});

test('Datetime validator: error message', () => {
const component = {
label: 'datetime',
key: 'datetime',
type: 'datetime',
datePicker: {
minDate: '2023-09-08T10:00:00+01:00',
maxDate: null,
},
customOptions: {
allowInvalidPreload: true,
},
validate: {datetimeMinMax: true},
};

const mockTranslation = jest.fn((message, values) => message);

const componentInstance = new FormioComponent(component, {}, {});
componentInstance.t = mockTranslation;

MinMaxDatetimeValidator.message(componentInstance);

expect(mockTranslation.mock.calls[0][0]).toEqual('invalidDatetime');

set(componentInstance, 'openForms.validationErrorContext.minMaxDatetimeValidatorErrorKeys', [
'minDatetime',
]);

MinMaxDatetimeValidator.message(componentInstance);

expect(mockTranslation.mock.calls[1][0]).toEqual('minDatetime');

set(componentInstance, 'openForms.validationErrorContext.minMaxDatetimeValidatorErrorKeys', [
'maxDatetime',
]);

MinMaxDatetimeValidator.message(componentInstance);

expect(mockTranslation.mock.calls[2][0]).toEqual('maxDatetime');
});

test('Datetime validator: check max datetime AND min datetime', () => {
const component = {
label: 'datetime',
key: 'datetime',
type: 'datetime',
datePicker: {
minDate: '2023-09-01T10:00:00+01:00',
maxDate: '2023-09-08T10:00:00+01:00',
},
customOptions: {
allowInvalidPreload: true,
},
validate: {datetimeMinMax: true},
};

const componentInstance = new FormioComponent(component, {}, {});

const isValid1 = MinMaxDatetimeValidator.check(
componentInstance,
{},
'2024-01-01T10:00:00+01:00'
);

expect(isValid1).toBeFalsy();
expect(
componentInstance.openForms.validationErrorContext.minMaxDatetimeValidatorErrorKeys
).toContain('maxDatetime');

const isValid2 = MinMaxDatetimeValidator.check(
componentInstance,
{},
'2020-01-01T10:00:00+01:00'
);

expect(isValid2).toBeFalsy();
expect(
componentInstance.openForms.validationErrorContext.minMaxDatetimeValidatorErrorKeys
).toContain('minDatetime');
});
});

0 comments on commit 646aa8c

Please sign in to comment.