♻️(react) extract duplicated utility functions and types

DateRangePicker and DatePicker would share quite a lot of
common logics. Make sure the code elements are factorised
easily reusable in each components.
This commit is contained in:
Lebaud Antoine
2023-06-16 13:49:47 +02:00
committed by aleb_the_flash
parent d16ada2825
commit 0dae71aa92
3 changed files with 213 additions and 0 deletions

View File

@@ -0,0 +1,2 @@
export type StringOrDate = string | Date;
export type StringsOrDateRange = [StringOrDate, StringOrDate];

View File

@@ -0,0 +1,163 @@
import { CalendarDate, DateValue, parseDate } from "@internationalized/date";
import {
parseCalendarDate,
parseRangeCalendarDate,
} from ":/components/Forms/DatePicker/utils";
import { StringOrDate } from ":/components/Forms/DatePicker/types";
const expectDateToBeEqual = (
parsedDate: CalendarDate | DateValue | undefined,
expectedYear: number,
expectedMonth: number,
expectedDay: number
) => {
expect(parsedDate).not.eq(undefined);
expect(parsedDate?.year === expectedYear);
expect(parsedDate?.month === expectedMonth);
expect(parsedDate?.day === expectedDay);
};
describe("parseCalendarDate", () => {
it.each([
[2023, 4, 12],
[2022, 1, 1],
[2022, 12, 31],
[2022, 5, 2],
])("parse an iso string date", (year: number, month: number, day: number) => {
const d = new Date(year, month, day);
const parsedDate = parseCalendarDate(d.toISOString());
expectDateToBeEqual(parsedDate, year, month, day);
});
it.each([
[2023, 4, 12],
[2022, 1, 1],
[2022, 12, 31],
[2022, 5, 2],
])(
"parse a 'YYYY-MM-DD' date",
(year: number, month: number, day: number) => {
const stringDate = `${year}-${month}-${day}`;
const parsedDate = parseCalendarDate(stringDate);
expectDateToBeEqual(parsedDate, year, month, day);
}
);
it.each([
[2023, 4, 12],
[2022, 1, 1],
[2022, 12, 31],
[2022, 5, 2],
])("parse a datetime date", (year: number, month: number, day: number) => {
const date = new Date(year, month, day);
const parsedDate = parseCalendarDate(date);
expectDateToBeEqual(parsedDate, year, month, day);
});
it.each([undefined, ""])("parse an empty or null date", (date) => {
const parsedDate = parseCalendarDate(date);
expect(parsedDate).eq(undefined);
});
it.each([
"35/04/2024",
"11 janvier 20O2",
"22.04.2022",
"22-4-2022",
"2022-04-1T00:00:00-00:00",
"2022-04-01 T00:00:00-00:00",
])("parse a wrong date", (wrongFormattedDate) => {
expect(() => parseCalendarDate(wrongFormattedDate)).toThrow(
"Invalid date format when initializing props on DatePicker component"
);
});
it.each([
[4, "2023-04-22"],
[5, "2023-04-30"],
[7, "2023-04-22"],
])(
"parse date to locale timezone, converted to day before",
(offset: number, dateString: string) => {
// Get the local offset
const localOffset = new Date().getTimezoneOffset() / 60;
const formattedOffset = offset.toLocaleString("en-US", {
minimumIntegerDigits: 2,
});
// Create an ISO string in a timezone that is the day after in local timezone
const offsetISODate = `${dateString}T${
24 - (offset - localOffset - 1)
}:00:00-${formattedOffset}:00`;
// Parse this ISO string, that should be converted to local timezone
const parsedDate = parseCalendarDate(offsetISODate);
// Make sure the ISO string have been converted to the local timezone
const nextDay = parseDate(dateString).add({ days: 1 });
expect(parsedDate?.compare(nextDay)).eq(0);
}
);
it.each([
[4, "2023-04-22"],
[5, "2023-04-30"],
[7, "2023-04-22"],
])(
"parse date to locale timezone, converted to same day",
(offset: number, dateString: string) => {
// Get the local offset
const localOffset = new Date().getTimezoneOffset() / 60;
const formattedOffset = offset.toLocaleString("en-US", {
minimumIntegerDigits: 2,
});
// Create an ISO string in a timezone that is the day after in local timezone
const offsetISODate = `${dateString}T${
24 - (offset - localOffset + 2)
}:00:00-${formattedOffset}:00`;
// Parse this ISO string, that should be converted to local timezone
const parsedDate = parseCalendarDate(offsetISODate);
const sameDay = parseDate(dateString);
// Make sure the ISO string have been converted to the local timezone
expect(parsedDate?.compare(sameDay)).eq(0);
}
);
});
describe("parseRangeCalendarDate", () => {
it.each([
["2023-03-22", "2023-04-22"],
[new Date(2023, 3, 22), "2023-04-22"],
["2023-03-22", new Date(2023, 4, 22)],
["2022-03-22T00:00:00-00:00", "2023-04-22"],
])("parse a date range", (start: string | Date, end: string | Date) => {
const range = parseRangeCalendarDate([start, end]);
expectDateToBeEqual(range?.start, 2023, 3, 22);
expectDateToBeEqual(range?.end, 2023, 4, 22);
});
it.each([
["", "2023-03-22"],
["2023-03-22", ""],
])(
"parse a partially null or empty date range",
(start: StringOrDate, end: StringOrDate) => {
expect(parseRangeCalendarDate([start, end])).eq(undefined);
}
);
it("parse an undefined date range", () => {
expect(parseRangeCalendarDate(undefined)).eq(undefined);
});
it("parse an inverted date range", () => {
// Utils function accepts start date superior to the end date
// However, DateRangePicker will trigger an error with the parsed range.
const range = parseRangeCalendarDate(["2023-05-22", "2023-04-22"]);
expectDateToBeEqual(range?.start, 2023, 5, 22);
expectDateToBeEqual(range?.end, 2023, 4, 22);
});
});

View File

@@ -0,0 +1,48 @@
import {
CalendarDate,
parseAbsoluteToLocal,
toCalendarDate,
} from "@internationalized/date";
import { DateRange } from "react-aria";
import {
StringOrDate,
StringsOrDateRange,
} from ":/components/Forms/DatePicker/types";
import { DatePickerAuxSubProps } from ":/components/Forms/DatePicker/DatePickerAux";
export const parseCalendarDate = (
rawDate: StringOrDate | undefined
): undefined | CalendarDate => {
if (!rawDate) {
return undefined;
}
try {
const ISODateString = new Date(rawDate).toISOString();
return toCalendarDate(parseAbsoluteToLocal(ISODateString));
} catch (e) {
throw new Error(
"Invalid date format when initializing props on DatePicker component"
);
}
};
export const parseRangeCalendarDate = (
rawRange: StringsOrDateRange | undefined
): DateRange | undefined => {
if (!rawRange || !rawRange[0] || !rawRange[1]) {
return undefined;
}
return {
start: parseCalendarDate(rawRange[0])!,
end: parseCalendarDate(rawRange[1])!,
};
};
export const getDefaultPickerOptions = (props: DatePickerAuxSubProps): any => ({
minValue: parseCalendarDate(props.minValue),
maxValue: parseCalendarDate(props.maxValue),
shouldCloseOnSelect: true,
granularity: "day",
isDisabled: props.disabled,
label: props.label,
});