(react) restrict inputs formats in date picker components

Enforces a standardized approach for end consumers using the
component's API. Consumers are now required to provide a date as an
ISO string in either UTC or with a UTC offset. The support for native
Date objects has been removed to ensure consistent and reliable
behavior across all implementations. By adopting this uniform input
format, we can simplify usage and avoid potential inconsistencies in
date handling.
This commit is contained in:
Lebaud Antoine
2023-07-12 22:51:32 +02:00
committed by aleb_the_flash
parent 8cf8e1eba2
commit 0dc46d1144
11 changed files with 403 additions and 324 deletions

View File

@@ -1,27 +1,21 @@
import {
CalendarDate,
DateValue,
parseAbsolute,
parseDate,
} from "@internationalized/date";
import { DateValue, parseAbsolute, parseDate } from "@internationalized/date";
import { vi } from "vitest";
import {
convertDateValueToString,
parseCalendarDate,
parseRangeCalendarDate,
parseDateValue,
parseRangeDateValue,
} from ":/components/Forms/DatePicker/utils";
import { StringOrDate } from ":/components/Forms/DatePicker/types";
const expectDateToBeEqual = (
parsedDate: CalendarDate | DateValue | undefined,
parsedDate: 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);
expect(parsedDate?.year).eq(expectedYear);
expect(parsedDate?.month).eq(expectedMonth);
expect(parsedDate?.day).eq(expectedDay);
};
vi.mock("@internationalized/date", async () => {
@@ -36,124 +30,46 @@ vi.mock("@internationalized/date", async () => {
};
});
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);
describe("parseDateValue", () => {
it("parse a 'YYYY-MM-DDThh:mm:ssZ' date", () => {
const parsedDate = parseDateValue("2023-05-11T00:00:00.000Z");
expectDateToBeEqual(parsedDate, 2023, 5, 11);
expect(parsedDate?.hour).eq(0);
});
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("parse a 'YYYY-MM-DDThh:mm:ss±hh:mm' date", () => {
const parsedDate = parseDateValue("2023-05-11T00:00:00.000+00:00");
expectDateToBeEqual(parsedDate, 2023, 5, 11);
expect(parsedDate?.hour).eq(0);
});
it.each([undefined, ""])("parse an empty or null date", (date) => {
const parsedDate = parseCalendarDate(date);
const parsedDate = parseDateValue(date);
expect(parsedDate).eq(undefined);
});
it.each([
"35/04/2024",
"2023-05-11",
"11 janvier 20O2",
"22.04.2022",
"22-4-2022",
"2022-04-1T00:00:00-00:00",
"2022-04-01 T00:00:00-00:00",
"2022-04-01T00:00:00.000",
])("parse a wrong date", (wrongFormattedDate) => {
expect(() => parseCalendarDate(wrongFormattedDate)).toThrow(
expect(() => parseDateValue(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]);
describe("parseRangeDateValue", () => {
it("parse a date range", () => {
const range = parseRangeDateValue([
"2023-03-22T00:00:00.000Z",
"2023-04-22T00:00:00.000Z",
]);
expectDateToBeEqual(range?.start, 2023, 3, 22);
expectDateToBeEqual(range?.end, 2023, 4, 22);
});
@@ -163,19 +79,22 @@ describe("parseRangeCalendarDate", () => {
["2023-03-22", ""],
])(
"parse a partially null or empty date range",
(start: StringOrDate, end: StringOrDate) => {
expect(parseRangeCalendarDate([start, end])).eq(undefined);
(start: string, end: string) => {
expect(parseRangeDateValue([start, end])).eq(undefined);
},
);
it("parse an undefined date range", () => {
expect(parseRangeCalendarDate(undefined)).eq(undefined);
expect(parseRangeDateValue(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"]);
const range = parseRangeDateValue([
"2023-05-22T00:00:00.000Z",
"2023-04-22T00:00:00.000Z",
]);
expectDateToBeEqual(range?.start, 2023, 5, 22);
expectDateToBeEqual(range?.end, 2023, 4, 22);
});