Skip to content

Commit

Permalink
fix: Trigger onCalendarClose and onClose event when the same date is …
Browse files Browse the repository at this point in the history
…selected for a date range

This commit resolves the following issue
1. Previously we were checking whether to emit the onCalendarClose close or not using the isBefore function but the issue is the startDate has the default time added, but the endDate we get from the calendar component doesn't has any time added to it, hence the isBefore check was failing as the startDate with some time is always ahead of the same date (endDate) without time
2. Similarly we were using the same isBefore function to decide whether to consider the date as startDate or endDate for onChange event handler, as the isBefore will fail because of the few milli-sec difference and the onChange call will fail.  As a result the end date (the currently selected date) is being considered as the startDate, hence we need to once again select the endDate value

Closes: #4076
  • Loading branch information
Balaji Sridharan committed Nov 28, 2023
1 parent a92d5e7 commit e3d65b2
Show file tree
Hide file tree
Showing 2 changed files with 136 additions and 17 deletions.
51 changes: 35 additions & 16 deletions src/index.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,12 @@ function hasPreSelectionChanged(date1, date2) {
return date1 !== date2;
}

function getDateWithoutTime(date) {
const dateWithoutTime = new Date(date);
dateWithoutTime.setHours(0, 0, 0, 0);
return dateWithoutTime;
}

/**
* General datepicker component.
*/
Expand Down Expand Up @@ -360,10 +366,10 @@ export default class DatePicker extends React.Component {
this.props.openToDate
? this.props.openToDate
: this.props.selectsEnd && this.props.startDate
? this.props.startDate
: this.props.selectsStart && this.props.endDate
? this.props.endDate
: newDate();
? this.props.startDate
: this.props.selectsStart && this.props.endDate
? this.props.endDate
: newDate();

// Convert the date from string format to standard Date format
modifyHolidays = () =>
Expand All @@ -384,8 +390,8 @@ export default class DatePicker extends React.Component {
minDate && isBefore(defaultPreSelection, startOfDay(minDate))
? minDate
: maxDate && isAfter(defaultPreSelection, endOfDay(maxDate))
? maxDate
: defaultPreSelection;
? maxDate
: defaultPreSelection;
return {
open: this.props.startOpen || false,
preventFocus: false,
Expand Down Expand Up @@ -589,8 +595,18 @@ export default class DatePicker extends React.Component {
if (!this.props.selectsRange) {
this.setOpen(false);
}

const { startDate, endDate } = this.props;
if (startDate && !endDate && !isBefore(date, startDate)) {

const startDateWithoutTime = isDate(startDate)
? getDateWithoutTime(startDate)
: null;
const dateWithoutTime = isDate(date) ? getDateWithoutTime(date) : null;
if (
startDate &&
!endDate &&
!isBefore(dateWithoutTime, startDateWithoutTime)
) {
this.setOpen(false);
}
}
Expand Down Expand Up @@ -653,7 +669,10 @@ export default class DatePicker extends React.Component {
if (noRanges) {
onChange([changedDate, null], event);
} else if (hasStartRange) {
if (isBefore(changedDate, startDate)) {
const startDateWithoutTime = getDateWithoutTime(startDate);
const changedDateWithoutTime = getDateWithoutTime(changedDate);

if (isBefore(changedDateWithoutTime, startDateWithoutTime)) {
onChange([changedDate, null], event);
} else {
onChange([startDate, changedDate], event);
Expand Down Expand Up @@ -1175,14 +1194,14 @@ export default class DatePicker extends React.Component {
typeof this.props.value === "string"
? this.props.value
: typeof this.state.inputValue === "string"
? this.state.inputValue
: this.props.selectsRange
? safeDateRangeFormat(
this.props.startDate,
this.props.endDate,
this.props,
)
: safeDateFormat(this.props.selected, this.props);
? this.state.inputValue
: this.props.selectsRange
? safeDateRangeFormat(
this.props.startDate,
this.props.endDate,
this.props,
)
: safeDateFormat(this.props.selected, this.props);

return React.cloneElement(customInput, {
[customInputRef]: (input) => {
Expand Down
102 changes: 101 additions & 1 deletion test/datepicker_test.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import { findDOMNode } from "react-dom";
import TestUtils from "react-dom/test-utils";
import { enUS, enGB } from "date-fns/locale";
import { mount } from "enzyme";
import { render, fireEvent } from "@testing-library/react";
import { render, act, waitFor, fireEvent } from "@testing-library/react";
import defer from "lodash/defer";
import DatePicker, { registerLocale } from "../src/index.jsx";
import Day from "../src/day.jsx";
Expand Down Expand Up @@ -44,6 +44,18 @@ function goToLastMonth(datePicker) {
TestUtils.Simulate.click(findDOMNode(lastMonthButton));
}

function formatDayWithZeros(day) {
const dayString = day.toString();

if (dayString.length === 1) {
return `00${dayString}`;
}
if (dayString.length === 2) {
return `0${dayString}`;
}
return dayString;
}

describe("DatePicker", () => {
afterEach(() => {
jest.resetAllMocks();
Expand Down Expand Up @@ -1941,6 +1953,94 @@ describe("DatePicker", () => {
expect(onChangeSpy.mock.calls[0][0][0]).toBeNull();
expect(onChangeSpy.mock.calls[0][0][1]).toBeNull();
});

it("should call the onChange even when the startDate and the endDate is same in the range (case when we programmatically set the startDate, but set the same endDate through UI)", async () => {
let startDate = new Date();
let endDate = null;

const onChangeSpy = jest.fn();

const { container } = render(
<DatePicker
startDate={startDate}
endDate={endDate}
onChange={onChangeSpy}
shouldCloseOnSelect
selectsRange
/>,
);

const input = container.querySelector("input");
expect(input).toBeTruthy();
fireEvent.click(input);

let calendar = container.querySelector(".react-datepicker");
expect(calendar).toBeTruthy();

// Select the same date as the start date
const startDatePrefixedWithZeros = formatDayWithZeros(
startDate.getDate(),
);
const endDateElement = container.querySelector(
`.react-datepicker__day--${startDatePrefixedWithZeros}`,
);
fireEvent.click(endDateElement);

await act(async () => {
await waitFor(() => {
expect(onChangeSpy).toHaveBeenCalled();
});
});
});

it("should hide the calendar even when the startDate and the endDate is same in the range", async () => {
let startDate = new Date();
let endDate = null;

const onCalendarCloseSpy = jest.fn();

const onChange = (dates) => {
const [start, end] = dates;
startDate = start;
endDate = end;
};

const { container } = render(
<DatePicker
startDate={startDate}
endDate={endDate}
onChange={onChange}
onCalendarClose={onCalendarCloseSpy}
shouldCloseOnSelect
selectsRange
/>,
);

const input = container.querySelector("input");
expect(input).toBeTruthy();
fireEvent.click(input);

let calendar = container.querySelector(".react-datepicker");
expect(calendar).toBeTruthy();

// Select the same date as the start date
const startDatePrefixedWithZeros = formatDayWithZeros(
startDate.getDate(),
);
const endDateElement = container.querySelector(
`.react-datepicker__day--${startDatePrefixedWithZeros}`,
);
fireEvent.click(endDateElement);

await act(async () => {
await waitFor(() => {
calendar = container.querySelector(".react-datepicker");
expect(calendar).toBeFalsy();

expect(onCalendarCloseSpy).toHaveBeenCalled();
});
});
});
});

describe("duplicate dates when multiple months", () => {
Expand Down

0 comments on commit e3d65b2

Please sign in to comment.