Skip to content

Commit 1282b39

Browse files
fix: showTimeSelect now works with selectsRange (#4332)
- Modified handleTimeChange to properly handle range mode - When only startDate is selected, time is applied to startDate - When both dates are selected, time is applied to endDate - onChange is called with proper [Date, Date] tuple format - Added 6 tests for the fix
1 parent 649af62 commit 1282b39

File tree

2 files changed

+270
-17
lines changed

2 files changed

+270
-17
lines changed

src/index.tsx

Lines changed: 53 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -964,25 +964,67 @@ export class DatePicker extends Component<DatePickerProps, DatePickerState> {
964964
};
965965

966966
handleTimeChange = (time: Date): void => {
967-
if (this.props.selectsRange || this.props.selectsMultiple) {
967+
if (this.props.selectsMultiple) {
968968
return;
969969
}
970970

971-
const selected = this.props.selected
972-
? this.props.selected
973-
: this.getPreSelection();
974-
const changedDate = this.props.selected
975-
? time
976-
: setTime(selected, {
971+
const { selectsRange, startDate, endDate, onChange } = this.props;
972+
973+
if (selectsRange) {
974+
// In range mode, apply time to the appropriate date
975+
// If we have a startDate but no endDate, apply time to startDate
976+
// If we have both, apply time to endDate
977+
const hasStartRange = startDate && !endDate;
978+
979+
if (hasStartRange) {
980+
// Apply time to startDate
981+
const changedStartDate = setTime(startDate, {
982+
hour: getHours(time),
983+
minute: getMinutes(time),
984+
});
985+
this.setState({
986+
preSelection: changedStartDate,
987+
});
988+
onChange?.([changedStartDate, null], undefined);
989+
} else if (startDate && endDate) {
990+
// Apply time to endDate
991+
const changedEndDate = setTime(endDate, {
992+
hour: getHours(time),
993+
minute: getMinutes(time),
994+
});
995+
this.setState({
996+
preSelection: changedEndDate,
997+
});
998+
onChange?.([startDate, changedEndDate], undefined);
999+
} else {
1000+
// No dates selected yet, just update preSelection
1001+
const changedDate = setTime(this.getPreSelection(), {
9771002
hour: getHours(time),
9781003
minute: getMinutes(time),
9791004
});
1005+
this.setState({
1006+
preSelection: changedDate,
1007+
});
1008+
}
1009+
} else {
1010+
// Single date mode (original behavior)
1011+
const selected = this.props.selected
1012+
? this.props.selected
1013+
: this.getPreSelection();
1014+
const changedDate = this.props.selected
1015+
? time
1016+
: setTime(selected, {
1017+
hour: getHours(time),
1018+
minute: getMinutes(time),
1019+
});
9801020

981-
this.setState({
982-
preSelection: changedDate,
983-
});
1021+
this.setState({
1022+
preSelection: changedDate,
1023+
});
1024+
1025+
this.props.onChange?.(changedDate);
1026+
}
9841027

985-
this.props.onChange?.(changedDate);
9861028
if (this.props.shouldCloseOnSelect && !this.props.showTimeInput) {
9871029
this.sendFocusBackToInput();
9881030
this.setOpen(false);

src/test/datepicker_test.test.tsx

Lines changed: 217 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -5822,16 +5822,228 @@ describe("DatePicker", () => {
58225822
});
58235823
});
58245824

5825-
describe("Critical functions coverage - best in class", () => {
5826-
it("should handle handleTimeChange with selectsRange (line 942)", () => {
5825+
describe("showTimeSelect with selectsRange", () => {
5826+
it("should apply time to startDate when only startDate is selected", () => {
5827+
const startDate = newDate("2024-01-15 00:00:00");
58275828
const onChange = jest.fn();
5829+
58285830
const { container } = render(
58295831
<DatePicker
5830-
selected={newDate()}
5832+
selectsRange
5833+
startDate={startDate}
5834+
endDate={null}
58315835
onChange={onChange}
5832-
startDate={newDate()}
5836+
showTimeSelect
5837+
inline
5838+
/>,
5839+
);
5840+
5841+
const timeElements = container.querySelectorAll(
5842+
".react-datepicker__time-list-item",
5843+
);
5844+
5845+
expect(timeElements.length).toBeGreaterThan(0);
5846+
5847+
// Find a time element (e.g., 10:00 AM)
5848+
const timeElement = Array.from(timeElements).find(
5849+
(el) => el.textContent === "10:00 AM",
5850+
);
5851+
expect(timeElement).toBeTruthy();
5852+
5853+
fireEvent.click(timeElement!);
5854+
5855+
expect(onChange).toHaveBeenCalledTimes(1);
5856+
const [changedStartDate, changedEndDate] = onChange.mock.calls[0][0];
5857+
5858+
// startDate should have the new time applied
5859+
expect(changedStartDate).toBeTruthy();
5860+
expect(getHours(changedStartDate)).toBe(10);
5861+
expect(getMinutes(changedStartDate)).toBe(0);
5862+
5863+
// endDate should still be null
5864+
expect(changedEndDate).toBeNull();
5865+
});
5866+
5867+
it("should apply time to endDate when both startDate and endDate are selected", () => {
5868+
const startDate = newDate("2024-01-15 09:00:00");
5869+
const endDate = newDate("2024-01-20 00:00:00");
5870+
const onChange = jest.fn();
5871+
5872+
const { container } = render(
5873+
<DatePicker
5874+
selectsRange
5875+
startDate={startDate}
5876+
endDate={endDate}
5877+
onChange={onChange}
5878+
showTimeSelect
5879+
inline
5880+
/>,
5881+
);
5882+
5883+
const timeElements = container.querySelectorAll(
5884+
".react-datepicker__time-list-item",
5885+
);
5886+
5887+
expect(timeElements.length).toBeGreaterThan(0);
5888+
5889+
// Find a time element (e.g., 2:30 PM)
5890+
const timeElement = Array.from(timeElements).find(
5891+
(el) => el.textContent === "2:30 PM",
5892+
);
5893+
expect(timeElement).toBeTruthy();
5894+
5895+
fireEvent.click(timeElement!);
5896+
5897+
expect(onChange).toHaveBeenCalledTimes(1);
5898+
const [changedStartDate, changedEndDate] = onChange.mock.calls[0][0];
5899+
5900+
// startDate should remain unchanged
5901+
expect(changedStartDate).toBeTruthy();
5902+
expect(getHours(changedStartDate)).toBe(9);
5903+
expect(getMinutes(changedStartDate)).toBe(0);
5904+
5905+
// endDate should have the new time applied
5906+
expect(changedEndDate).toBeTruthy();
5907+
expect(getHours(changedEndDate)).toBe(14);
5908+
expect(getMinutes(changedEndDate)).toBe(30);
5909+
});
5910+
5911+
it("should not call onChange when no dates are selected in range mode", () => {
5912+
const onChange = jest.fn();
5913+
5914+
const { container } = render(
5915+
<DatePicker
5916+
selectsRange
5917+
startDate={null}
5918+
endDate={null}
5919+
onChange={onChange}
5920+
showTimeSelect
5921+
inline
5922+
/>,
5923+
);
5924+
5925+
const timeElements = container.querySelectorAll(
5926+
".react-datepicker__time-list-item",
5927+
);
5928+
5929+
expect(timeElements.length).toBeGreaterThan(0);
5930+
5931+
// Click a time when no dates are selected
5932+
fireEvent.click(timeElements[0]!);
5933+
5934+
// onChange should not be called when no dates are selected
5935+
// because we need a date to apply the time to
5936+
expect(onChange).not.toHaveBeenCalled();
5937+
});
5938+
5939+
it("should call onChange with tuple format [Date, Date] when time is selected in range mode", () => {
5940+
const startDate = newDate("2024-01-15 00:00:00");
5941+
const endDate = newDate("2024-01-20 00:00:00");
5942+
const onChange = jest.fn();
5943+
5944+
const { container } = render(
5945+
<DatePicker
5946+
selectsRange
5947+
startDate={startDate}
5948+
endDate={endDate}
5949+
onChange={onChange}
5950+
showTimeSelect
5951+
inline
5952+
/>,
5953+
);
5954+
5955+
const timeElements = container.querySelectorAll(
5956+
".react-datepicker__time-list-item",
5957+
);
5958+
const timeElement = timeElements[0];
5959+
expect(timeElement).toBeTruthy();
5960+
5961+
fireEvent.click(timeElement!);
5962+
5963+
expect(onChange).toHaveBeenCalledTimes(1);
5964+
5965+
// Verify the argument is an array (tuple)
5966+
const callArg = onChange.mock.calls[0][0];
5967+
expect(Array.isArray(callArg)).toBe(true);
5968+
expect(callArg.length).toBe(2);
5969+
});
5970+
5971+
it("should not throw TypeError when clicking time with selectsRange enabled", () => {
5972+
const startDate = newDate("2024-01-15 00:00:00");
5973+
const onChange = jest.fn();
5974+
5975+
const { container } = render(
5976+
<DatePicker
5977+
selectsRange
5978+
startDate={startDate}
58335979
endDate={null}
5980+
onChange={onChange}
5981+
showTimeSelect
5982+
inline
5983+
/>,
5984+
);
5985+
5986+
const timeElements = container.querySelectorAll(
5987+
".react-datepicker__time-list-item",
5988+
);
5989+
5990+
expect(timeElements.length).toBeGreaterThan(0);
5991+
5992+
// This should not throw "Uncaught TypeError: Invalid attempt to destructure non-iterable instance"
5993+
expect(() => {
5994+
fireEvent.click(timeElements[0]!);
5995+
}).not.toThrow();
5996+
5997+
expect(onChange).toHaveBeenCalled();
5998+
});
5999+
6000+
it("should close calendar after time selection when shouldCloseOnSelect is true", () => {
6001+
const startDate = newDate("2024-01-15 00:00:00");
6002+
const endDate = newDate("2024-01-20 00:00:00");
6003+
let instance: DatePicker | null = null;
6004+
6005+
const { container } = render(
6006+
<DatePicker
6007+
ref={(node) => {
6008+
instance = node;
6009+
}}
58346010
selectsRange
6011+
startDate={startDate}
6012+
endDate={endDate}
6013+
onChange={jest.fn()}
6014+
showTimeSelect
6015+
shouldCloseOnSelect
6016+
/>,
6017+
);
6018+
6019+
expect(instance).toBeTruthy();
6020+
6021+
// Open the calendar
6022+
const input = safeQuerySelector(container, "input");
6023+
fireEvent.focus(input);
6024+
6025+
expect(instance!.state.open).toBe(true);
6026+
6027+
const timeElements = container.querySelectorAll(
6028+
".react-datepicker__time-list-item",
6029+
);
6030+
expect(timeElements.length).toBeGreaterThan(0);
6031+
6032+
fireEvent.click(timeElements[0]!);
6033+
6034+
// Calendar should close after time selection
6035+
expect(instance!.state.open).toBe(false);
6036+
});
6037+
});
6038+
6039+
describe("Critical functions coverage - best in class", () => {
6040+
it("should handle handleTimeChange with selectsMultiple (line 942)", () => {
6041+
const onChange = jest.fn();
6042+
const { container } = render(
6043+
<DatePicker
6044+
selected={null}
6045+
onChange={onChange}
6046+
selectsMultiple
58356047
showTimeSelect
58366048
inline
58376049
/>,
@@ -5843,9 +6055,8 @@ describe("DatePicker", () => {
58436055

58446056
expect(timeElements.length).toBeGreaterThan(0);
58456057
const firstTimeElement = timeElements[0] as HTMLElement;
5846-
// Line 942: handleTimeChange early return for selectsRange
6058+
// Line 942: handleTimeChange early return for selectsMultiple
58476059
fireEvent.click(firstTimeElement);
5848-
// Time change should not affect range selection directly
58496060
expect(container.querySelector(".react-datepicker")).not.toBeNull();
58506061
});
58516062

0 commit comments

Comments
 (0)