Skip to content

Commit

Permalink
Improve datetimepicker time input (#7625)
Browse files Browse the repository at this point in the history
* Update datepicker insertion logic

* Improve test coverage, clean input values better

* Hide debug UI

* Add changes

* Update format
  • Loading branch information
Etheryte authored Oct 4, 2023
1 parent a828af9 commit 167fc28
Show file tree
Hide file tree
Showing 5 changed files with 91 additions and 7 deletions.
49 changes: 48 additions & 1 deletion web/html/src/components/datetime/DateTimePicker.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,7 @@ describe("DateTimePicker", () => {
expect(timePicker.value).toEqual("23:30");
});

test("clearing the time input doesn't change the date (bsc#1210253)", (done) => {
test("clearing or manually editing the time input doesn't change the date (bsc#1210253, bsc#1215820)", (done) => {
const validISOString = "2020-01-30T15:00:00.000Z";
let changeEventCount = 0;

Expand Down Expand Up @@ -89,4 +89,51 @@ describe("DateTimePicker", () => {
screen.getByText("16").click();
type(timePicker, "0", false);
});

test("picking a time from the dropdown works", (done) => {
const validISOString = "2020-02-01T04:00:00.000Z";

const Setup = () => {
const [value, setValue] = useState(localizedMoment(validISOString));
return (
<DateTimePicker
value={value}
onChange={(newValue) => {
setValue(newValue);

expect(newValue.toUserDateTimeString()).toEqual("2020-01-31 14:30");
done();
}}
/>
);
};
render(<Setup />);

const { timePicker } = getInputs();
timePicker.click();
screen.getByText("14:30").click();
});

test("manually entering a time value works", (done) => {
const validISOString = "2020-02-01T04:00:00.000Z";

const Setup = () => {
const [value, setValue] = useState(localizedMoment(validISOString));
return (
<DateTimePicker
value={value}
onChange={(newValue) => {
setValue(newValue);

expect(newValue.toUserDateTimeString()).toEqual("2020-01-31 23:45");
done();
}}
/>
);
};
render(<Setup />);

const { timePicker } = getInputs();
type(timePicker, "23:45");
});
});
25 changes: 21 additions & 4 deletions web/html/src/components/datetime/DateTimePicker.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import { forwardRef, useRef } from "react";

import ReactDatePicker from "react-datepicker";

import { localizedMoment } from "utils";
import { localizedMoment, parseTimeString } from "utils";

// Turn this on to view internal state under the picker in the UI
const SHOW_DEBUG_VALUES = false;
Expand Down Expand Up @@ -173,8 +173,10 @@ export const DateTimePicker = (props: Props) => {
portalId="time-picker-portal"
ref={timePickerRef}
selected={browserTimezoneValue.toDate()}
onChange={(date) => {
if (date === null) {
onChange={(date, event) => {
// If this fires without an event, it means the user picked a time from the dropdown
// This handler is only used the dropdown selection, onChangeRaw() handles regular user input
if (date === null || event) {
return;
}
/**
Expand All @@ -185,6 +187,22 @@ export const DateTimePicker = (props: Props) => {
mergedDate.setHours(date.getHours(), date.getMinutes());
onChange(mergedDate);
}}
onChangeRaw={(event) => {
// In case the user pastes a value, clean it up and cut it to max length
const rawValue = event.target.value.replaceAll(/[^\d:]/g, "");
const cutValue = rawValue.includes(":") ? rawValue.substring(0, 5) : rawValue.substring(0, 4);
if (cutValue !== event.target.value) {
event.target.value = cutValue;
}

const parsed = parseTimeString(cutValue);
if (!parsed) {
return;
}
const mergedDate = browserTimezoneValue.toDate();
mergedDate.setHours(parsed.hours, parsed.minutes);
onChange(mergedDate);
}}
showTimeSelect
showTimeSelectOnly
// We want the regular primary display to only show the time here, so using TIME_FORMAT is intentional
Expand All @@ -200,7 +218,6 @@ export const DateTimePicker = (props: Props) => {
className="form-control"
// This is used by Cucumber to interact with the component
data-testid="time-picker"
maxLength={5}
/>
}
/>
Expand Down
10 changes: 9 additions & 1 deletion web/html/src/utils/datetime/localizedMoment.test.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { localizedMoment } from "./localizedMoment";
import { localizedMoment, parseTimeString } from "./localizedMoment";

describe("localizedMoment", () => {
const validISOString = "2020-01-30T23:00:00.000Z";
Expand Down Expand Up @@ -82,4 +82,12 @@ describe("localizedMoment", () => {
const nextWeek = localizedMoment().add(1, "week");
expect(nextWeek.calendar()).toEqual(nextWeek.format("YYYY-MM-DD"));
});

// This function is a thin wrapper around moment so we only add basic test coverage
test("parseTimeString", () => {
expect(parseTimeString("")).toEqual(null);
expect(parseTimeString("2345")).toEqual({ hours: 23, minutes: 45 });
expect(parseTimeString("23:45")).toEqual({ hours: 23, minutes: 45 });
expect(parseTimeString(" 23 45 67 ")).toEqual({ hours: 23, minutes: 45 });
});
});
13 changes: 12 additions & 1 deletion web/html/src/utils/datetime/localizedMoment.ts
Original file line number Diff line number Diff line change
Expand Up @@ -199,6 +199,17 @@ Object.defineProperties(moment, {
},
});

const parseTimeString = function (input: string): { hours: number; minutes: number } | null {
const parsed = moment(input.trim(), ["H:m", "Hm"]);
if (parsed.isValid()) {
return {
hours: parsed.hours(),
minutes: parsed.minutes(),
};
}
return null;
};

function localizedMomentConstructor(input?: moment.MomentInput) {
// We make all inputs UTC internally and only format them for output
const utcMoment =
Expand All @@ -214,4 +225,4 @@ function localizedMomentConstructor(input?: moment.MomentInput) {
}

const localizedMoment: typeof moment = Object.setPrototypeOf(localizedMomentConstructor, moment);
export { localizedMoment };
export { localizedMoment, parseTimeString };
1 change: 1 addition & 0 deletions web/spacewalk-web.changes.eth.date-improve
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
- Improve datetimepicker input formatting

0 comments on commit 167fc28

Please sign in to comment.