Date picker validation fixed (#2789)

* Date picker validation fixed

* Added date value check to remove unncessary validations

Commented defaultDate validation test as invalid date cannot be saved in date control

* Fixed date selection on user change

Fixed min and max date parsing for date picker control default date

* Added validation for min and max date in validations to handle validations when field is converted to JS
This commit is contained in:
Vicky Bansal 2021-02-02 20:12:49 +05:30 committed by GitHub
parent 9d97645b3c
commit 11f5687b38
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 210 additions and 75 deletions

View File

@ -101,18 +101,18 @@ describe("DatePicker Widget Functionality", function() {
); );
}); });
it("Datepicker default date validation", function() { // it("Datepicker default date validation", function() {
cy.get(formWidgetsPage.defaultDate).click(); // cy.get(formWidgetsPage.defaultDate).click();
cy.wait(1000); // cy.wait(1000);
cy.setDate(-2, "ddd MMM DD YYYY"); // cy.setDate(-2, "ddd MMM DD YYYY");
cy.get(formWidgetsPage.defaultDate).should( // cy.get(formWidgetsPage.defaultDate).should(
"have.css", // "have.css",
"border", // "border",
"1px solid rgb(206, 66, 87)", // "1px solid rgb(206, 66, 87)",
); // );
cy.PublishtheApp(); // cy.PublishtheApp();
}); // });
// it("DatePicker-check Required field validation", function() { // it("DatePicker-check Required field validation", function() {
// // Check the required checkbox // // Check the required checkbox

View File

@ -10,6 +10,7 @@ import { DatePickerType } from "widgets/DatePickerWidget";
import { WIDGET_PADDING } from "constants/WidgetConstants"; import { WIDGET_PADDING } from "constants/WidgetConstants";
import { TimePrecision } from "@blueprintjs/datetime"; import { TimePrecision } from "@blueprintjs/datetime";
import { Colors } from "constants/Colors"; import { Colors } from "constants/Colors";
import { ISO_DATE_FORMAT } from "constants/WidgetValidation";
const StyledControlGroup = styled(ControlGroup)` const StyledControlGroup = styled(ControlGroup)`
&&& { &&& {
@ -67,10 +68,11 @@ class DatePickerComponent extends React.Component<
} }
componentDidUpdate(prevProps: DatePickerComponentProps) { componentDidUpdate(prevProps: DatePickerComponentProps) {
const dateFormat = this.props.dateFormat || ISO_DATE_FORMAT;
if ( if (
this.props.selectedDate !== this.state.selectedDate && this.props.selectedDate !== this.state.selectedDate &&
!moment(this.props.selectedDate, this.props.dateFormat).isSame( !moment(this.props.selectedDate, dateFormat).isSame(
moment(prevProps.selectedDate, this.props.dateFormat), moment(prevProps.selectedDate, dateFormat),
"seconds", "seconds",
) )
) { ) {
@ -81,13 +83,13 @@ class DatePickerComponent extends React.Component<
render() { render() {
const now = moment(); const now = moment();
const year = now.get("year"); const year = now.get("year");
const dateFormat = this.props.dateFormat || ISO_DATE_FORMAT;
const minDate = this.props.minDate const minDate = this.props.minDate
? moment(this.props.minDate) ? moment(this.props.minDate, dateFormat)
: now.clone().set({ month: 0, date: 1, year: year - 100 }); : now.clone().set({ month: 0, date: 1, year: year - 100 });
const maxDate = this.props.maxDate const maxDate = this.props.maxDate
? moment(this.props.maxDate) ? moment(this.props.maxDate, dateFormat)
: now.clone().set({ month: 11, date: 31, year: year + 20 }); : now.clone().set({ month: 11, date: 31, year: year + 20 });
return ( return (
<StyledControlGroup <StyledControlGroup
fill fill
@ -142,11 +144,13 @@ class DatePickerComponent extends React.Component<
} }
formatDate = (date: Date): string => { formatDate = (date: Date): string => {
return moment(date).format(this.props.dateFormat); const dateFormat = this.props.dateFormat || ISO_DATE_FORMAT;
return moment(date).format(dateFormat);
}; };
parseDate = (dateStr: string): Date => { parseDate = (dateStr: string): Date => {
return moment(dateStr, this.props.dateFormat).toDate(); const dateFormat = this.props.dateFormat || ISO_DATE_FORMAT;
return moment(dateStr, dateFormat).toDate();
}; };
/** /**
@ -156,16 +160,18 @@ class DatePickerComponent extends React.Component<
* *
* @param selectedDate * @param selectedDate
*/ */
onDateSelected = (selectedDate: Date) => { onDateSelected = (selectedDate: Date, isUserChange: boolean) => {
const { onDateSelected } = this.props; if (isUserChange) {
const { onDateSelected } = this.props;
const date = selectedDate ? this.formatDate(selectedDate) : ""; const date = selectedDate ? this.formatDate(selectedDate) : "";
this.setState({ selectedDate: date }); this.setState({ selectedDate: date });
// if date is null ( if date is cleared ), don't call onDateSelected // if date is null ( if date is cleared ), don't call onDateSelected
if (!selectedDate) return false; if (!selectedDate) return false;
onDateSelected(date); onDateSelected(date);
}
}; };
} }

View File

@ -7,6 +7,7 @@ import { TimePrecision } from "@blueprintjs/datetime";
import { WidgetProps } from "widgets/BaseWidget"; import { WidgetProps } from "widgets/BaseWidget";
import { Toaster } from "components/ads/Toast"; import { Toaster } from "components/ads/Toast";
import { Variant } from "components/ads/common"; import { Variant } from "components/ads/common";
import { ISO_DATE_FORMAT } from "constants/WidgetValidation";
const DatePickerControlWrapper = styled.div<{ isValid: boolean }>` const DatePickerControlWrapper = styled.div<{ isValid: boolean }>`
display: flex; display: flex;
@ -62,8 +63,17 @@ class DatePickerControl extends BaseControl<
} }
render() { render() {
const dateFormat =
this.props.widgetProperties.dateFormat || ISO_DATE_FORMAT;
const isValid = this.state.selectedDate
? this.validateDate(moment(this.state.selectedDate, dateFormat).toDate())
: true;
const maxDate =
this.props.widgetProperties?.evaluatedValues?.maxDate ?? this.maxDate;
const minDate =
this.props.widgetProperties?.evaluatedValues?.minDate ?? this.minDate;
return ( return (
<DatePickerControlWrapper isValid={this.props.isValid}> <DatePickerControlWrapper isValid={isValid}>
<StyledDatePicker <StyledDatePicker
formatDate={this.formatDate} formatDate={this.formatDate}
parseDate={this.parseDate} parseDate={this.parseDate}
@ -72,8 +82,16 @@ class DatePickerControl extends BaseControl<
timePrecision={TimePrecision.MINUTE} timePrecision={TimePrecision.MINUTE}
closeOnSelection closeOnSelection
onChange={this.onDateSelected} onChange={this.onDateSelected}
maxDate={this.maxDate} maxDate={
minDate={this.minDate} this.props.propertyName === "defaultDate"
? moment(maxDate, dateFormat).toDate()
: undefined
}
minDate={
this.props.propertyName === "defaultDate"
? moment(minDate, dateFormat).toDate()
: undefined
}
value={ value={
this.props.propertyValue this.props.propertyValue
? this.parseDate(this.props.propertyValue) ? this.parseDate(this.props.propertyValue)
@ -91,15 +109,17 @@ class DatePickerControl extends BaseControl<
* *
* @param date * @param date
*/ */
onDateSelected = (date: Date): void => { onDateSelected = (date: Date, isUserChange: boolean): void => {
const selectedDate = date ? this.formatDate(date) : undefined; if (isUserChange) {
const isValid = this.validateDate(date); const selectedDate = date ? this.formatDate(date) : undefined;
const isValid = this.validateDate(date);
if (!isValid) return; if (!isValid) return;
// if everything is ok, put date in state // if everything is ok, put date in state
this.setState({ selectedDate: selectedDate }); this.setState({ selectedDate: selectedDate });
this.updateProperty(this.props.propertyName, selectedDate); this.updateProperty(this.props.propertyName, selectedDate);
}
}; };
/** /**
@ -108,17 +128,37 @@ class DatePickerControl extends BaseControl<
* 2. if default date is in range of min and max date * 2. if default date is in range of min and max date
*/ */
validateDate = (date: Date): boolean => { validateDate = (date: Date): boolean => {
const parsedSelectedDate = moment( const dateFormat =
date, this.props.widgetProperties.dateFormat || ISO_DATE_FORMAT;
this.props.widgetProperties.dateFormat, const parsedSelectedDate = moment(date, dateFormat);
); //validate defaultDate if both minDate and maxDate is already selected
if (this.props.propertyName === "defaultDate") {
if (
parsedSelectedDate.isValid() &&
this.props.widgetProperties?.evaluatedValues?.minDate &&
this.props.widgetProperties?.evaluatedValues?.maxDate
) {
const parsedMinDate = moment(
this.props.widgetProperties.evaluatedValues.minDate,
dateFormat,
);
const parsedMaxDate = moment(
this.props.widgetProperties.evaluatedValues.maxDate,
dateFormat,
);
if (
parsedSelectedDate.isBefore(parsedMinDate) ||
parsedSelectedDate.isAfter(parsedMaxDate)
) {
return false;
}
}
}
if (this.props.widgetProperties?.evaluatedValues?.value) { if (this.props.widgetProperties?.evaluatedValues?.value) {
const parsedWidgetDate = moment( const parsedWidgetDate = moment(
this.props.widgetProperties.evaluatedValues.value, this.props.widgetProperties.evaluatedValues.value,
this.props.widgetProperties.dateFormat, dateFormat,
); );
// checking if widget date is after min date // checking if widget date is after min date
if (this.props.propertyName === "minDate") { if (this.props.propertyName === "minDate") {
if ( if (
@ -149,21 +189,19 @@ class DatePickerControl extends BaseControl<
} }
} }
} }
return true; return true;
}; };
formatDate = (date: Date): string => { formatDate = (date: Date): string => {
return moment(date).format( const dateFormat =
this.props.widgetProperties.dateFormat || "DD/MM/YYYY HH:mm", this.props.widgetProperties.dateFormat || ISO_DATE_FORMAT;
); return moment(date).format(dateFormat);
}; };
parseDate = (dateStr: string): Date => { parseDate = (dateStr: string): Date => {
return moment( const dateFormat =
dateStr, this.props.widgetProperties.dateFormat || ISO_DATE_FORMAT;
this.props.widgetProperties.dateFormat || "DD/MM/YYYY HH:mm", return moment(dateStr, dateFormat).toDate();
).toDate();
}; };
static getControlType() { static getControlType() {

View File

@ -18,7 +18,7 @@ const FIELD_VALUES: Record<
isVisible: "boolean", isVisible: "boolean",
}, },
DATE_PICKER_WIDGET: { DATE_PICKER_WIDGET: {
defaultDate: "Date", defaultDate: "string", //TODO:Vicky validate this property
isRequired: "boolean", isRequired: "boolean",
isVisible: "boolean", isVisible: "boolean",
isDisabled: "boolean", isDisabled: "boolean",

View File

@ -14,6 +14,8 @@ export const VALIDATION_TYPES = {
OPTIONS_DATA: "OPTIONS_DATA", OPTIONS_DATA: "OPTIONS_DATA",
DATE: "DATE", DATE: "DATE",
DEFAULT_DATE: "DEFAULT_DATE", DEFAULT_DATE: "DEFAULT_DATE",
MIN_DATE: "MIN_DATE",
MAX_DATE: "MAX_DATE",
TABS_DATA: "TABS_DATA", TABS_DATA: "TABS_DATA",
CHART_DATA: "CHART_DATA", CHART_DATA: "CHART_DATA",
MARKERS: "MARKERS", MARKERS: "MARKERS",
@ -39,7 +41,7 @@ export type Validator = (
dataTree?: DataTree, dataTree?: DataTree,
) => ValidationResponse; ) => ValidationResponse;
export const ISO_DATE_FORMAT = "YYYY-MM-DDTHH:mm:ss.SSSZ"; export const ISO_DATE_FORMAT = "YYYY-MM-DDTHH:mm:ss.Z";
export const JAVASCRIPT_KEYWORDS = { export const JAVASCRIPT_KEYWORDS = {
true: "true", true: "true",

View File

@ -25,8 +25,8 @@ class DatePickerWidget extends BaseWidget<DatePickerWidgetProps, WidgetState> {
dateFormat: VALIDATION_TYPES.TEXT, dateFormat: VALIDATION_TYPES.TEXT,
label: VALIDATION_TYPES.TEXT, label: VALIDATION_TYPES.TEXT,
datePickerType: VALIDATION_TYPES.TEXT, datePickerType: VALIDATION_TYPES.TEXT,
maxDate: VALIDATION_TYPES.DATE, maxDate: VALIDATION_TYPES.MAX_DATE,
minDate: VALIDATION_TYPES.DATE, minDate: VALIDATION_TYPES.MIN_DATE,
isRequired: VALIDATION_TYPES.BOOLEAN, isRequired: VALIDATION_TYPES.BOOLEAN,
// onDateSelected: VALIDATION_TYPES.ACTION_SELECTOR, // onDateSelected: VALIDATION_TYPES.ACTION_SELECTOR,
// onDateRangeSelected: VALIDATION_TYPES.ACTION_SELECTOR, // onDateRangeSelected: VALIDATION_TYPES.ACTION_SELECTOR,

View File

@ -390,14 +390,8 @@ export const VALIDATORS: Record<ValidationType, Validator> = {
dateString: string, dateString: string,
props: WidgetProps, props: WidgetProps,
): ValidationResponse => { ): ValidationResponse => {
const today = moment()
.hour(0)
.minute(0)
.second(0)
.millisecond(0);
const dateFormat = props.dateFormat ? props.dateFormat : ISO_DATE_FORMAT; const dateFormat = props.dateFormat ? props.dateFormat : ISO_DATE_FORMAT;
const todayDateString = today.format(dateFormat);
if (dateString === undefined) { if (dateString === undefined) {
return { return {
isValid: false, isValid: false,
@ -409,10 +403,16 @@ export const VALIDATORS: Record<ValidationType, Validator> = {
}; };
} }
const isValid = moment(dateString, dateFormat).isValid(); const isValid = moment(dateString, dateFormat).isValid();
const parsed = isValid ? dateString : todayDateString; if (!isValid) {
return {
isValid: isValid,
parsed: "",
message: `${WIDGET_TYPE_VALIDATION_ERROR}: Date`,
};
}
return { return {
isValid, isValid,
parsed, parsed: dateString,
message: isValid ? "" : `${WIDGET_TYPE_VALIDATION_ERROR}: Date`, message: isValid ? "" : `${WIDGET_TYPE_VALIDATION_ERROR}: Date`,
}; };
}, },
@ -420,14 +420,7 @@ export const VALIDATORS: Record<ValidationType, Validator> = {
dateString: string, dateString: string,
props: WidgetProps, props: WidgetProps,
): ValidationResponse => { ): ValidationResponse => {
const today = moment()
.hour(0)
.minute(0)
.second(0)
.millisecond(0);
const dateFormat = props.dateFormat ? props.dateFormat : ISO_DATE_FORMAT; const dateFormat = props.dateFormat ? props.dateFormat : ISO_DATE_FORMAT;
const todayDateString = today.format(dateFormat);
if (dateString === undefined) { if (dateString === undefined) {
return { return {
isValid: false, isValid: false,
@ -460,13 +453,109 @@ export const VALIDATORS: Record<ValidationType, Validator> = {
isValid = false; isValid = false;
} }
} }
if (!isValid) {
const parsed = isValid ? dateString : todayDateString; return {
isValid: isValid,
parsed: "",
message: `${WIDGET_TYPE_VALIDATION_ERROR}: Date R`,
};
}
return { return {
isValid, isValid: isValid,
parsed, parsed: dateString,
message: isValid ? "" : `${WIDGET_TYPE_VALIDATION_ERROR}: Date R`, message: "",
};
},
[VALIDATION_TYPES.MIN_DATE]: (
dateString: string,
props: WidgetProps,
): ValidationResponse => {
const dateFormat = props.dateFormat ? props.dateFormat : ISO_DATE_FORMAT;
if (dateString === undefined) {
return {
isValid: false,
parsed: "",
message:
`${WIDGET_TYPE_VALIDATION_ERROR}: Date ` + props.dateFormat
? props.dateFormat
: "",
};
}
const parsedMinDate = moment(dateString, dateFormat);
let isValid = parsedMinDate.isValid();
if (!props.defaultDate) {
return {
isValid: isValid,
parsed: dateString,
message: "",
};
}
const parsedDefaultDate = moment(props.defaultDate, dateFormat);
if (
isValid &&
parsedDefaultDate.isValid() &&
parsedDefaultDate.isBefore(parsedMinDate)
) {
isValid = false;
}
if (!isValid) {
return {
isValid: isValid,
parsed: "",
message: `${WIDGET_TYPE_VALIDATION_ERROR}: Date R`,
};
}
return {
isValid: isValid,
parsed: dateString,
message: "",
};
},
[VALIDATION_TYPES.MAX_DATE]: (
dateString: string,
props: WidgetProps,
): ValidationResponse => {
const dateFormat = props.dateFormat ? props.dateFormat : ISO_DATE_FORMAT;
if (dateString === undefined) {
return {
isValid: false,
parsed: "",
message:
`${WIDGET_TYPE_VALIDATION_ERROR}: Date ` + props.dateFormat
? props.dateFormat
: "",
};
}
const parsedMaxDate = moment(dateString, dateFormat);
let isValid = parsedMaxDate.isValid();
if (!props.defaultDate) {
return {
isValid: isValid,
parsed: dateString,
message: "",
};
}
const parsedDefaultDate = moment(props.defaultDate, dateFormat);
if (
isValid &&
parsedDefaultDate.isValid() &&
parsedDefaultDate.isAfter(parsedMaxDate)
) {
isValid = false;
}
if (!isValid) {
return {
isValid: isValid,
parsed: "",
message: `${WIDGET_TYPE_VALIDATION_ERROR}: Date R`,
};
}
return {
isValid: isValid,
parsed: dateString,
message: "",
}; };
}, },
[VALIDATION_TYPES.ACTION_SELECTOR]: (value: any): ValidationResponse => { [VALIDATION_TYPES.ACTION_SELECTOR]: (value: any): ValidationResponse => {