Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix #4076: Trigger onCalendarClose event and onChange even when the same date is selected as the start and the end date in a date range #4394

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}`;
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not able to wrap my head around why a day would be 3 digits or why this padding makes sense... is this correct?

🔺 Bug (Critical)

Image of Steven S Steven S

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hi Steven,
Actually we're using 3-digit numbers for our CSS class names. Attaching the below screenshot for your reference.

Calendar Screenshot

}
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
Loading