By default, component's timezone is the user locale timezone. Component now offers a way to set its timezone to any supported Intl timezone format. Please note that output values from the component will always be converted to a UTC timezone.
1125 lines
33 KiB
TypeScript
1125 lines
33 KiB
TypeScript
import { render, screen, within } from "@testing-library/react";
|
|
import React, { FormEvent, useState } from "react";
|
|
import { expect, vi } from "vitest";
|
|
import userEvent from "@testing-library/user-event";
|
|
import { CunninghamProvider } from ":/components/Provider";
|
|
import { DateRangePicker } from ":/components/Forms/DatePicker/DateRangePicker";
|
|
import { Button } from ":/components/Button";
|
|
|
|
vi.mock("@internationalized/date", async () => {
|
|
const mod = await vi.importActual<typeof import("@internationalized/date")>(
|
|
"@internationalized/date",
|
|
);
|
|
return {
|
|
...mod,
|
|
// Note: Restoring mocks will cause the function to return 'undefined'.
|
|
// Consider providing a default implementation to be restored instead.
|
|
getLocalTimeZone: vi.fn().mockReturnValue("Europe/Paris"),
|
|
};
|
|
});
|
|
|
|
describe("<DateRangePicker/>", () => {
|
|
const expectDatesToBeEqual = (
|
|
firstDate: Date | string | undefined | null,
|
|
secondDate: Date | string | undefined | null,
|
|
) => {
|
|
expect(firstDate).toBeDefined();
|
|
expect(secondDate).toBeDefined();
|
|
expect(new Date(firstDate!).toLocaleDateString()).eq(
|
|
new Date(secondDate!).toLocaleDateString(),
|
|
);
|
|
};
|
|
|
|
const expectCalendarToBeClosed = () => {
|
|
expect(screen.queryByRole("application")).toBeNull();
|
|
};
|
|
|
|
const expectDateFieldsToBeDisplayed = () => {
|
|
expect(screen.queryAllByRole("presentation")).toBeTruthy();
|
|
};
|
|
|
|
const expectDateFieldsToBeHidden = async () => {
|
|
const dateFields = await screen.queryAllByRole("presentation");
|
|
expect(dateFields.length).eq(2);
|
|
dateFields.forEach((dateField) => {
|
|
expect(Array.from(dateField.parentElement!.classList)).contains(
|
|
"c__date-picker__inner--collapsed",
|
|
);
|
|
});
|
|
};
|
|
|
|
const expectDateRangePickerStateToBe = async (state: string) => {
|
|
const input = (await screen.findAllByRole("button"))![0];
|
|
const classNames =
|
|
input.parentElement && Array.from(input.parentElement.classList);
|
|
expect(classNames).contains(`c__date-picker--${state}`);
|
|
};
|
|
|
|
const expectCalendarToBeOpen = () => {
|
|
const calendar = screen.queryByRole("application");
|
|
expect(calendar).toBeDefined();
|
|
expect(calendar).not.toBeNull();
|
|
expect(Array.from(calendar!.classList)).contains(
|
|
"c__calendar__wrapper--opened",
|
|
);
|
|
};
|
|
|
|
const expectedSelectedCells = (selectionSize: number) => {
|
|
const selectedCells = screen.queryAllByRole("gridcell", {
|
|
selected: true,
|
|
});
|
|
expect(selectedCells.length).eq(selectionSize);
|
|
};
|
|
|
|
it("toggles calendar", async () => {
|
|
const user = userEvent.setup();
|
|
render(
|
|
<CunninghamProvider>
|
|
<DateRangePicker
|
|
startLabel="Pick a date"
|
|
endLabel="Pick a date"
|
|
name="datepicker"
|
|
/>
|
|
</CunninghamProvider>,
|
|
);
|
|
|
|
const [input, button] = await screen.findAllByRole("button");
|
|
|
|
// It returns the clickable div.
|
|
expect(input.tagName).toEqual("DIV");
|
|
|
|
// It returns the toggle button.
|
|
expect(button.tagName).toEqual("BUTTON");
|
|
|
|
// Calendar is initially closed.
|
|
expectCalendarToBeClosed();
|
|
await expectDateFieldsToBeHidden();
|
|
|
|
// Toggle button opens the calendar.
|
|
await user.click(button);
|
|
expectCalendarToBeOpen();
|
|
expectDateFieldsToBeDisplayed();
|
|
|
|
// Clicking again closes the calendar.
|
|
await user.click(button);
|
|
expectCalendarToBeClosed();
|
|
await expectDateFieldsToBeHidden();
|
|
|
|
// Click on the input opens the calendar.
|
|
await user.click(input);
|
|
expectCalendarToBeOpen();
|
|
expectDateFieldsToBeDisplayed();
|
|
|
|
// Clicking again on the input should not close the calendar.
|
|
// Click could happen while selecting the date field input.
|
|
await user.click(input);
|
|
expectCalendarToBeOpen();
|
|
expectDateFieldsToBeDisplayed();
|
|
|
|
// While the calendar open, clicking on
|
|
// the toggle button closes the calendar.
|
|
await user.click(button);
|
|
expectCalendarToBeClosed();
|
|
});
|
|
|
|
it("focuses in the right order with no picked date range", async () => {
|
|
const user = userEvent.setup();
|
|
render(
|
|
<CunninghamProvider>
|
|
<DateRangePicker
|
|
startLabel="Start date"
|
|
endLabel="End date"
|
|
name="datepicker"
|
|
/>
|
|
</CunninghamProvider>,
|
|
);
|
|
// Get elements that should receive focus when no date is picked.
|
|
const [input, toggleButton] = await screen.findAllByRole("button")!;
|
|
|
|
await user.keyboard("{Tab}");
|
|
expect(input).toHaveFocus();
|
|
expectCalendarToBeClosed();
|
|
|
|
await user.keyboard("{Tab}");
|
|
expect(toggleButton).toHaveFocus();
|
|
expectCalendarToBeClosed();
|
|
|
|
await user.keyboard("{Enter}");
|
|
expectCalendarToBeOpen();
|
|
|
|
expectedSelectedCells(0);
|
|
});
|
|
|
|
it.each([
|
|
["2022-05-25T00:00:00.000Z", "2022-05-26T00:00:00.000Z"],
|
|
["2023-06-01T00:00:00.000Z", "2024-05-26T00:00:00.000Z"],
|
|
])("has a default value", async (start: string, end: string) => {
|
|
render(
|
|
<CunninghamProvider>
|
|
<DateRangePicker
|
|
startLabel="Start date"
|
|
endLabel="End date"
|
|
name="datepicker"
|
|
defaultValue={[start, end]}
|
|
/>
|
|
</CunninghamProvider>,
|
|
);
|
|
// Get picked date.
|
|
const [startInput, endInput] = await screen.queryAllByRole("presentation");
|
|
expectDatesToBeEqual(start, startInput.textContent);
|
|
expectDatesToBeEqual(end, endInput.textContent);
|
|
expectDateFieldsToBeDisplayed();
|
|
});
|
|
|
|
it("focuses in the right order with a default value", async () => {
|
|
const user = userEvent.setup();
|
|
render(
|
|
<CunninghamProvider>
|
|
<DateRangePicker
|
|
startLabel="Start date"
|
|
endLabel="End date"
|
|
name="datepicker"
|
|
defaultValue={[
|
|
"2023-04-25T00:00:00.000Z",
|
|
"2023-05-25T00:00:00.000Z",
|
|
]}
|
|
/>
|
|
</CunninghamProvider>,
|
|
);
|
|
|
|
// Get elements that should receive focus when a date is already picked.
|
|
const [input, toggleButton] = await screen.findAllByRole("button")!;
|
|
const clearButton = screen.getByRole("button", {
|
|
name: "Clear date",
|
|
});
|
|
const [
|
|
monthStartSegment,
|
|
dayStartSegment,
|
|
yearStartSegment,
|
|
monthEndSegment,
|
|
dayEndSegment,
|
|
yearEndSegment,
|
|
] = await screen.findAllByRole("spinbutton")!;
|
|
|
|
// Navigate through elements using Tab.
|
|
await user.keyboard("{Tab}");
|
|
expect(input).toHaveFocus();
|
|
|
|
await user.keyboard("{Tab}");
|
|
expect(toggleButton).toHaveFocus();
|
|
|
|
await user.keyboard("{Tab}");
|
|
expect(monthStartSegment).toHaveFocus();
|
|
|
|
await user.keyboard("{Tab}");
|
|
expect(dayStartSegment).toHaveFocus();
|
|
|
|
await user.keyboard("{Tab}");
|
|
expect(yearStartSegment).toHaveFocus();
|
|
|
|
await user.keyboard("{Tab}");
|
|
expect(monthEndSegment).toHaveFocus();
|
|
|
|
await user.keyboard("{Tab}");
|
|
expect(dayEndSegment).toHaveFocus();
|
|
|
|
await user.keyboard("{Tab}");
|
|
expect(yearEndSegment).toHaveFocus();
|
|
|
|
await user.keyboard("{Tab}");
|
|
expect(clearButton).toHaveFocus();
|
|
});
|
|
|
|
it("picks a date range", async () => {
|
|
const user = userEvent.setup();
|
|
render(
|
|
<CunninghamProvider>
|
|
<DateRangePicker
|
|
startLabel="Start date"
|
|
endLabel="End date"
|
|
name="datepicker"
|
|
/>
|
|
</CunninghamProvider>,
|
|
);
|
|
const [input, toggleButton] = await screen.findAllByRole("button");
|
|
await user.click(input);
|
|
|
|
// Select all grid-cells.
|
|
const gridCells = await screen.findAllByRole("gridcell");
|
|
|
|
// Select the first clickable grid-cell.
|
|
const startGridCellButton = within(
|
|
gridCells.filter(
|
|
(gridCell) => !gridCell.getAttribute("aria-disabled"),
|
|
)![0],
|
|
).getByRole("button")!;
|
|
|
|
// Pick a start date.
|
|
const gridCellStartDate = startGridCellButton
|
|
.getAttribute("aria-label")
|
|
?.replace("Today, ", "");
|
|
await user.click(startGridCellButton);
|
|
|
|
expectCalendarToBeOpen();
|
|
|
|
// Select the second clickable grid-cell.
|
|
const endGridCellButton = within(
|
|
gridCells.filter(
|
|
(gridCell) => !gridCell.getAttribute("aria-disabled"),
|
|
)![1],
|
|
).getByRole("button")!;
|
|
|
|
// Pick an end date.
|
|
const gridCellEndDate = endGridCellButton
|
|
.getAttribute("aria-label")
|
|
?.replace("Today, ", "");
|
|
await user.click(endGridCellButton);
|
|
|
|
// Calendar should close on range's selection.
|
|
expectCalendarToBeClosed();
|
|
expectDateFieldsToBeDisplayed();
|
|
|
|
// Reopen the calendar.
|
|
await user.click(toggleButton);
|
|
|
|
// Get the selected cells in calendar grid.
|
|
const selectedCells = screen.queryAllByRole("gridcell", {
|
|
selected: true,
|
|
})!;
|
|
|
|
// Extract the selected range
|
|
const selectedRange = within(selectedCells[0])
|
|
.getByRole("button")!
|
|
.getAttribute("aria-label")
|
|
?.replace("selected", "")
|
|
?.replace("Today, ", "");
|
|
|
|
// Make sure start and end dates are in the selected range,
|
|
// that describes the range with its start and end date.
|
|
expect(selectedRange).contains(gridCellStartDate);
|
|
expect(selectedRange).contains(gridCellEndDate);
|
|
|
|
// Get the selected date in the date field.
|
|
const [startInput, endInput] = await screen.queryAllByRole("presentation");
|
|
expectDatesToBeEqual(gridCellStartDate, startInput.textContent);
|
|
expectDatesToBeEqual(gridCellEndDate, endInput.textContent);
|
|
});
|
|
|
|
it("picks a date range with start and end date equal", async () => {
|
|
const user = userEvent.setup();
|
|
render(
|
|
<CunninghamProvider>
|
|
<DateRangePicker
|
|
startLabel="Start date"
|
|
endLabel="End date"
|
|
name="datepicker"
|
|
/>
|
|
</CunninghamProvider>,
|
|
);
|
|
const [input, toggleButton] = await screen.findAllByRole("button");
|
|
await user.click(input);
|
|
|
|
// Select all grid-cells.
|
|
const gridCells = await screen.findAllByRole("gridcell");
|
|
|
|
// Select the first clickable grid-cell.
|
|
const gridCellButton = within(
|
|
gridCells.filter(
|
|
(gridCell) => !gridCell.getAttribute("aria-disabled"),
|
|
)![0],
|
|
).getByRole("button")!;
|
|
|
|
// Pick a start date.
|
|
const gridCellDate = gridCellButton
|
|
.getAttribute("aria-label")
|
|
?.replace("Today, ", "");
|
|
await user.click(gridCellButton);
|
|
|
|
expectCalendarToBeOpen();
|
|
|
|
// Click again on the same date, to select the end date.
|
|
await user.click(gridCellButton);
|
|
|
|
// Calendar should close on range's selection.
|
|
expectCalendarToBeClosed();
|
|
expectDateFieldsToBeDisplayed();
|
|
|
|
// Reopen the calendar.
|
|
await user.click(toggleButton);
|
|
|
|
// Get the selected cells in calendar grid.
|
|
const selectedCells = screen.queryAllByRole("gridcell", {
|
|
selected: true,
|
|
})!;
|
|
|
|
// Extract the selected range
|
|
const selectedRange = within(selectedCells[0])
|
|
.getByRole("button")!
|
|
.getAttribute("aria-label")
|
|
?.replace("selected", "")
|
|
?.replace("Today, ", "");
|
|
|
|
// Make sure start and end dates are in the selected range,
|
|
// that describes the range with its start and end date.
|
|
expect(selectedRange).contains(gridCellDate);
|
|
expect(selectedRange).contains(gridCellDate);
|
|
|
|
// Get the selected date in the date field.
|
|
const [startInput, endInput] = await screen.queryAllByRole("presentation");
|
|
expectDatesToBeEqual(gridCellDate, startInput.textContent);
|
|
expectDatesToBeEqual(gridCellDate, endInput.textContent);
|
|
});
|
|
|
|
it("picks a date range using keyboard", async () => {
|
|
const user = userEvent.setup();
|
|
render(
|
|
<CunninghamProvider>
|
|
<DateRangePicker
|
|
startLabel="Start date"
|
|
endLabel="End date"
|
|
defaultValue={[
|
|
"2023-01-01T00:00:00.000Z",
|
|
"2023-01-01T00:00:00.000Z",
|
|
]}
|
|
name="datepicker"
|
|
/>
|
|
</CunninghamProvider>,
|
|
);
|
|
const input = (await screen.findAllByRole("button"))![0];
|
|
await user.click(input);
|
|
expectCalendarToBeOpen();
|
|
|
|
// Start range selection using keyboard.
|
|
await user.keyboard("{Enter}");
|
|
expectCalendarToBeOpen();
|
|
expectedSelectedCells(1);
|
|
|
|
// Select the next cell.
|
|
await user.keyboard("{ArrowRight}");
|
|
expectCalendarToBeOpen();
|
|
expectedSelectedCells(2);
|
|
|
|
// Select the next cell.
|
|
await user.keyboard("{ArrowRight}");
|
|
expectCalendarToBeOpen();
|
|
expectedSelectedCells(3);
|
|
|
|
// End range selection using keyboard.
|
|
await user.keyboard("{Enter}");
|
|
expectCalendarToBeClosed();
|
|
|
|
await user.click(input);
|
|
expectCalendarToBeOpen();
|
|
|
|
expectedSelectedCells(3);
|
|
|
|
const selectedCells = screen.getAllByRole("gridcell", {
|
|
selected: true,
|
|
});
|
|
|
|
// Extract the selected range
|
|
const selectedRange = within(selectedCells[0])
|
|
.getByRole("button")!
|
|
.getAttribute("aria-label")
|
|
?.replace("selected", "");
|
|
|
|
// Make sure start and end dates are in the selected range.
|
|
expect(selectedRange).contains("January 1");
|
|
expect(selectedRange).not.contains("January 2");
|
|
expect(selectedRange).contains("January 3");
|
|
|
|
// Get the selected date in the date field.
|
|
const [startInput, endInput] = await screen.queryAllByRole("presentation");
|
|
expectDatesToBeEqual("2023-01-01", startInput.textContent);
|
|
expectDatesToBeEqual("2023-01-03", endInput.textContent);
|
|
});
|
|
|
|
it("picks a date range spanning multiple months", async () => {
|
|
const user = userEvent.setup();
|
|
render(
|
|
<CunninghamProvider>
|
|
<DateRangePicker
|
|
startLabel="Start date"
|
|
endLabel="End date"
|
|
name="datepicker"
|
|
defaultValue={[
|
|
"2023-01-01T00:00:00.000Z",
|
|
"2023-01-01T00:00:00.000Z",
|
|
]}
|
|
/>
|
|
</CunninghamProvider>,
|
|
);
|
|
const input = (await screen.findAllByRole("button"))![0];
|
|
await user.click(input);
|
|
expectCalendarToBeOpen();
|
|
|
|
const startDate = "Tuesday, January 3, 2023";
|
|
const endDate = "Tuesday, February 28, 2023";
|
|
|
|
// Select the start date.
|
|
const startGridCellButton = await screen.getByRole("button", {
|
|
name: startDate,
|
|
})!;
|
|
await user.click(startGridCellButton);
|
|
expectCalendarToBeOpen();
|
|
|
|
// Navigate to the next month.
|
|
const nextMonthButton = screen.getByRole("button", {
|
|
name: "Next month",
|
|
});
|
|
await user.click(nextMonthButton);
|
|
expectCalendarToBeOpen();
|
|
|
|
// Select the end date.
|
|
const endGridCellButton = await screen.getByRole("button", {
|
|
name: endDate,
|
|
})!;
|
|
await user.click(endGridCellButton);
|
|
expectCalendarToBeClosed();
|
|
|
|
// Reopen calendar.
|
|
await user.click(input);
|
|
|
|
// Get the selected date in the date field.
|
|
const [startInput, endInput] = await screen.queryAllByRole("presentation");
|
|
expectDatesToBeEqual(startDate, startInput.textContent);
|
|
expectDatesToBeEqual(endDate, endInput.textContent);
|
|
});
|
|
|
|
it("picks a date range spanning multiple years", async () => {
|
|
const user = userEvent.setup();
|
|
render(
|
|
<CunninghamProvider>
|
|
<DateRangePicker
|
|
startLabel="Start date"
|
|
endLabel="End date"
|
|
name="datepicker"
|
|
defaultValue={[
|
|
"2023-01-01T00:00:00.000Z",
|
|
"2023-01-01T00:00:00.000Z",
|
|
]}
|
|
/>
|
|
</CunninghamProvider>,
|
|
);
|
|
const input = (await screen.findAllByRole("button"))![0];
|
|
await user.click(input);
|
|
expectCalendarToBeOpen();
|
|
|
|
const startDate = "Tuesday, January 3, 2023";
|
|
const endDate = "Thursday, January 4, 2024";
|
|
|
|
// Select the start date.
|
|
const startGridCellButton = await screen.getByRole("button", {
|
|
name: startDate,
|
|
})!;
|
|
await user.click(startGridCellButton);
|
|
expectCalendarToBeOpen();
|
|
|
|
// Navigate to the next year.
|
|
const nextYearButton = screen.getByRole("button", {
|
|
name: "Next year",
|
|
});
|
|
await user.click(nextYearButton);
|
|
expectCalendarToBeOpen();
|
|
|
|
// Select the end date.
|
|
const endGridCellButton = await screen.getByRole("button", {
|
|
name: endDate,
|
|
})!;
|
|
await user.click(endGridCellButton);
|
|
expectCalendarToBeClosed();
|
|
|
|
// Reopen calendar.
|
|
await user.click(input);
|
|
|
|
// Get the selected date in the date field.
|
|
const [startInput, endInput] = await screen.queryAllByRole("presentation");
|
|
expectDatesToBeEqual(startDate, startInput.textContent);
|
|
expectDatesToBeEqual(endDate, endInput.textContent);
|
|
});
|
|
|
|
it("types a date range", async () => {
|
|
const user = userEvent.setup();
|
|
render(
|
|
<CunninghamProvider>
|
|
<DateRangePicker
|
|
startLabel="Start date"
|
|
endLabel="End date"
|
|
name="datepicker"
|
|
/>
|
|
</CunninghamProvider>,
|
|
);
|
|
|
|
// Open calendar to display text segment
|
|
const input = (await screen.findAllByRole("button"))![0];
|
|
await user.click(input);
|
|
|
|
const [
|
|
monthStartSegment,
|
|
dayStartSegment,
|
|
yearStartSegment,
|
|
monthEndSegment,
|
|
dayEndSegment,
|
|
yearEndSegment,
|
|
] = await screen.findAllByRole("spinbutton")!;
|
|
|
|
// Select the first segment, month one.
|
|
await user.click(monthStartSegment);
|
|
expect(monthStartSegment).toHaveFocus();
|
|
|
|
// Type month's start value.
|
|
await user.keyboard("{1}{2}");
|
|
expect(dayStartSegment).toHaveFocus();
|
|
|
|
// Type day's start value.
|
|
await user.keyboard("{5}");
|
|
expect(yearStartSegment).toHaveFocus();
|
|
|
|
// Type year's start value.
|
|
await user.keyboard("{2}{0}{2}{3}");
|
|
expect(monthEndSegment).toHaveFocus();
|
|
|
|
// Type month's end value.
|
|
await user.keyboard("{1}{2}");
|
|
expect(dayEndSegment).toHaveFocus();
|
|
|
|
// Type day's end value.
|
|
await user.keyboard("{6}");
|
|
expect(yearEndSegment).toHaveFocus();
|
|
|
|
// Type year's end value.
|
|
await user.keyboard("{2}{0}{2}{3}");
|
|
|
|
// Check date field value.
|
|
expectDateFieldsToBeDisplayed();
|
|
const [startInput, endInput] = await screen.queryAllByRole("presentation");
|
|
expect(startInput.textContent).eq("12/5/2023");
|
|
expect(endInput.textContent).eq("12/6/2023");
|
|
});
|
|
|
|
it("types an invalid date range", async () => {
|
|
const user = userEvent.setup();
|
|
render(
|
|
<CunninghamProvider>
|
|
<DateRangePicker
|
|
startLabel="Start date"
|
|
endLabel="End date"
|
|
name="datepicker"
|
|
/>
|
|
</CunninghamProvider>,
|
|
);
|
|
|
|
// Open calendar to display text segment
|
|
const input = (await screen.findAllByRole("button"))![0];
|
|
await user.click(input);
|
|
|
|
const [
|
|
monthStartSegment,
|
|
dayStartSegment,
|
|
yearStartSegment,
|
|
monthEndSegment,
|
|
dayEndSegment,
|
|
yearEndSegment,
|
|
] = await screen.findAllByRole("spinbutton")!;
|
|
|
|
// Select the first segment, month one.
|
|
await user.click(monthStartSegment);
|
|
expect(monthStartSegment).toHaveFocus();
|
|
|
|
// Type month's start value.
|
|
await user.keyboard("{1}{2}");
|
|
expect(dayStartSegment).toHaveFocus();
|
|
|
|
// Type day's start value.
|
|
await user.keyboard("{6}");
|
|
expect(yearStartSegment).toHaveFocus();
|
|
|
|
// Type year's start value.
|
|
await user.keyboard("{2}{0}{2}{3}");
|
|
expect(monthEndSegment).toHaveFocus();
|
|
|
|
// Type month's end value.
|
|
await user.keyboard("{1}{2}");
|
|
expect(dayEndSegment).toHaveFocus();
|
|
|
|
// Type day's end value.
|
|
await user.keyboard("{5}");
|
|
expect(yearEndSegment).toHaveFocus();
|
|
|
|
// Type year's end value.
|
|
await user.keyboard("{2}{0}{2}{3}");
|
|
|
|
// Check date field value.
|
|
expectDateFieldsToBeDisplayed();
|
|
const [startInput, endInput] = await screen.queryAllByRole("presentation");
|
|
expect(startInput.textContent).eq("12/6/2023");
|
|
expect(endInput.textContent).eq("12/5/2023");
|
|
|
|
// Make sure the date picker is showing error.
|
|
await expectDateRangePickerStateToBe("invalid");
|
|
});
|
|
|
|
it("has an uncontrolled and a controlled value", async () => {
|
|
vi.spyOn(console, "error").mockImplementation(() => undefined);
|
|
expect(() =>
|
|
render(
|
|
<CunninghamProvider>
|
|
<DateRangePicker
|
|
startLabel="Start date"
|
|
endLabel="End date"
|
|
name="datepicker"
|
|
defaultValue={[
|
|
"2022-05-25T00:00:00.000Z",
|
|
"2022-05-26T00:00:00.000Z",
|
|
]}
|
|
value={["2022-05-25T00:00:00.000Z", "2022-05-26T00:00:00.000Z"]}
|
|
/>
|
|
</CunninghamProvider>,
|
|
),
|
|
).toThrow(
|
|
"You cannot use both defaultValue and value props on DateRangePicker component",
|
|
);
|
|
});
|
|
|
|
it.each([
|
|
["this_not_a_valid_date", "2022-05-12T00:00:00.000Z"],
|
|
["2022-05-12T00:00:00.000Z", "2023-13-13"],
|
|
["2025-25-05T00:00:00.000Z", "2022-05-12T00:00:00.000Z"],
|
|
])("has not a valid range value", async (start: string, end: string) => {
|
|
vi.spyOn(console, "error").mockImplementation(() => undefined);
|
|
expect(() =>
|
|
render(
|
|
<CunninghamProvider>
|
|
<DateRangePicker
|
|
startLabel="Start date"
|
|
endLabel="End date"
|
|
name="datepicker"
|
|
defaultValue={[start, end]}
|
|
/>
|
|
</CunninghamProvider>,
|
|
),
|
|
).toThrow(/Failed to parse date value:/);
|
|
});
|
|
|
|
it("has not a valid range value", async () => {
|
|
// Start date is superior to end date in the default range.
|
|
render(
|
|
<CunninghamProvider>
|
|
<DateRangePicker
|
|
startLabel="Start date"
|
|
endLabel="End date"
|
|
name="datepicker"
|
|
defaultValue={[
|
|
"2024-05-25T00:00:00.000Z",
|
|
"2022-05-26T00:00:00.000Z",
|
|
]}
|
|
/>
|
|
</CunninghamProvider>,
|
|
);
|
|
await expectDateRangePickerStateToBe("invalid");
|
|
});
|
|
|
|
it("clears date", async () => {
|
|
const user = userEvent.setup();
|
|
render(
|
|
<CunninghamProvider>
|
|
<DateRangePicker
|
|
startLabel="Start date"
|
|
endLabel="End date"
|
|
name="datepicker"
|
|
defaultValue={[
|
|
"2023-05-25T00:00:00.000Z",
|
|
"2023-05-27T00:00:00.000Z",
|
|
]}
|
|
/>
|
|
</CunninghamProvider>,
|
|
);
|
|
|
|
const clearButton = screen.getByRole("button", {
|
|
name: "Clear date",
|
|
});
|
|
await user.click(clearButton);
|
|
expectCalendarToBeOpen();
|
|
|
|
// Date field's value should be set to a placeholder value.
|
|
const [startInput, endInput] = await screen.queryAllByRole("presentation");
|
|
expect(startInput.textContent).eq("mm/dd/yyyy");
|
|
expect(endInput.textContent).eq("mm/dd/yyyy");
|
|
|
|
const startGridCell = screen.getByRole("gridcell", {
|
|
name: "25",
|
|
})!;
|
|
|
|
// Make sure start grid-cell is not selected anymore.
|
|
expect(startGridCell.getAttribute("aria-selected")).toBeNull();
|
|
expect(
|
|
startGridCell.classList.contains(
|
|
"c__calendar__wrapper__grid__week-row__background--range--start",
|
|
),
|
|
).toBe(false);
|
|
|
|
// Make sure end grid-cell is not selected anymore.
|
|
const endGridCell = screen.getByRole("gridcell", {
|
|
name: "27",
|
|
})!;
|
|
expect(endGridCell.getAttribute("aria-selected")).toBeNull();
|
|
expect(
|
|
endGridCell.classList.contains(
|
|
"c__calendar__wrapper__grid__week-row__background--range--end",
|
|
),
|
|
).toBe(false);
|
|
|
|
// Close the calendar.
|
|
const toggleButton = (await screen.findAllByRole("button"))![1];
|
|
await user.click(toggleButton);
|
|
|
|
// Make sure the empty date field is hidden when closing the calendar.
|
|
await expectDateFieldsToBeHidden();
|
|
});
|
|
|
|
it.each([
|
|
["2023-01-01T00:00:00.000Z", "2023-01-01T00:00:00.000Z"],
|
|
["2023-01-01T00:00:00.000Z", "2023-03-01T00:00:00.000Z"],
|
|
])(
|
|
"has a start or a end date inferior to minValue",
|
|
async (start: string, end: string) => {
|
|
render(
|
|
<CunninghamProvider>
|
|
<DateRangePicker
|
|
startLabel="Start date"
|
|
endLabel="End date"
|
|
name="datepicker"
|
|
defaultValue={[start, end]}
|
|
minValue="2023-02-01T00:00:00.000Z"
|
|
/>
|
|
</CunninghamProvider>,
|
|
);
|
|
await expectDateRangePickerStateToBe("invalid");
|
|
},
|
|
);
|
|
|
|
it.each([
|
|
["2023-01-01T00:00:00.000Z", "2023-03-01T00:00:00.000Z"],
|
|
["2023-03-01T00:00:00.000Z", "2023-03-01T00:00:00.000Z"],
|
|
])(
|
|
"has a start or a end date superior to maxValue",
|
|
async (start: string, end: string) => {
|
|
render(
|
|
<CunninghamProvider>
|
|
<DateRangePicker
|
|
startLabel="Start date"
|
|
endLabel="End date"
|
|
name="datepicker"
|
|
defaultValue={[start, end]}
|
|
maxValue="2023-02-01T00:00:00.000Z"
|
|
/>
|
|
</CunninghamProvider>,
|
|
);
|
|
await expectDateRangePickerStateToBe("invalid");
|
|
},
|
|
);
|
|
|
|
it("renders disabled", async () => {
|
|
render(
|
|
<CunninghamProvider>
|
|
<DateRangePicker
|
|
startLabel="Start date"
|
|
endLabel="End date"
|
|
name="datepicker"
|
|
defaultValue={[
|
|
"2023-01-01T00:00:00.000Z",
|
|
"2023-01-01T00:00:00.000Z",
|
|
]}
|
|
disabled={true}
|
|
/>
|
|
</CunninghamProvider>,
|
|
);
|
|
await expectDateRangePickerStateToBe("disabled");
|
|
|
|
const [input, button] = await screen.findAllByRole("button");
|
|
|
|
// Make sure toggle button and click on input are disabled.
|
|
expect(input.getAttribute("aria-disabled")).eq("true");
|
|
expect(button).toBeDisabled();
|
|
|
|
// Make sure the clear button is not visible and disabled.
|
|
expect(
|
|
screen.queryByRole("button", { name: "Clear date", hidden: true }),
|
|
).toBeDisabled();
|
|
|
|
// Make sure each segment of the date field is disabled.
|
|
const dateFieldInputs = await screen.queryAllByRole("spinbutton");
|
|
dateFieldInputs.forEach((dateFieldInput) =>
|
|
expect(dateFieldInput).toHaveAttribute("aria-disabled"),
|
|
);
|
|
});
|
|
|
|
it("renders focused", async () => {
|
|
const user = userEvent.setup();
|
|
render(
|
|
<CunninghamProvider>
|
|
<DateRangePicker
|
|
startLabel="Start date"
|
|
endLabel="End date"
|
|
name="datepicker"
|
|
defaultValue={[
|
|
"2023-01-01T00:00:00.000Z",
|
|
"2023-01-01T00:00:00.000Z",
|
|
]}
|
|
/>
|
|
</CunninghamProvider>,
|
|
);
|
|
const toggleButton = (await screen.findAllByRole("button"))![1];
|
|
await user.click(toggleButton);
|
|
expectCalendarToBeOpen();
|
|
await expectDateRangePickerStateToBe("focused");
|
|
});
|
|
|
|
it("renders a selected range", async () => {
|
|
const user = userEvent.setup();
|
|
render(
|
|
<CunninghamProvider>
|
|
<DateRangePicker
|
|
startLabel="Start date"
|
|
endLabel="End date"
|
|
name="datepicker"
|
|
defaultValue={[
|
|
"2023-01-01T00:00:00.000Z",
|
|
"2023-01-10T00:00:00.000Z",
|
|
]}
|
|
/>
|
|
</CunninghamProvider>,
|
|
);
|
|
// Toggle button opens the calendar.
|
|
const button = (await screen.findAllByRole("button"))![0];
|
|
await user.click(button);
|
|
|
|
const selectedCells = screen.queryAllByRole("gridcell", {
|
|
selected: true,
|
|
});
|
|
|
|
// Make sure all selected cells render the selected styling.
|
|
selectedCells.forEach((selectedCell) => {
|
|
const cellButton = within(selectedCell).getByRole("button")!;
|
|
expect(Array.from(cellButton!.classList)).contains(
|
|
"c__calendar__wrapper__grid__week-row__button--selected",
|
|
);
|
|
});
|
|
const commonClassName =
|
|
"c__calendar__wrapper__grid__week-row__background--range";
|
|
// Make sure the start of selection has the start styling.
|
|
const startCellButton = within(selectedCells[0]).getByRole("button")!;
|
|
expect(Array.from(startCellButton.parentElement!.classList)).contains(
|
|
`${commonClassName}--start`,
|
|
);
|
|
// Make sure the end of selection has the end styling.
|
|
const endCellButton = within(
|
|
selectedCells[selectedCells.length - 1],
|
|
).getByRole("button")!;
|
|
expect(Array.from(endCellButton.parentElement!.classList)).contains(
|
|
`${commonClassName}--end`,
|
|
);
|
|
});
|
|
|
|
it("works controlled", async () => {
|
|
const user = userEvent.setup();
|
|
const Wrapper = () => {
|
|
const [value, setValue] = useState<[string, string] | null>([
|
|
"2023-04-25T00:00:00.000Z",
|
|
"2023-04-26T00:00:00.000Z",
|
|
]);
|
|
return (
|
|
<CunninghamProvider>
|
|
<div>
|
|
<div>Value = {value?.join(" ")}|</div>
|
|
<Button onClick={() => setValue(null)}>Clear</Button>
|
|
<DateRangePicker
|
|
startLabel="Start date"
|
|
endLabel="End date"
|
|
value={value}
|
|
onChange={(e) => setValue(e)}
|
|
/>
|
|
</div>
|
|
</CunninghamProvider>
|
|
);
|
|
};
|
|
render(<Wrapper />);
|
|
|
|
// Make sure value is selected.
|
|
screen.getByText(
|
|
"Value = 2023-04-25T00:00:00.000Z 2023-04-26T00:00:00.000Z|",
|
|
);
|
|
|
|
// Make sure value is initially render in the date field component.
|
|
const [startInput, endInput] = await screen.queryAllByRole("presentation");
|
|
expectDatesToBeEqual("2023-04-25", startInput.textContent);
|
|
expectDatesToBeEqual("2023-04-26", endInput.textContent);
|
|
|
|
// Open the calendar grid.
|
|
const toggleButton = (await screen.findAllByRole("button"))![1];
|
|
await user.click(toggleButton);
|
|
expectCalendarToBeOpen();
|
|
|
|
const startGridCell = within(
|
|
await screen.getByRole("gridcell", { name: "12" }),
|
|
).getByRole("button")!;
|
|
|
|
const endGridCell = within(
|
|
await screen.getByRole("gridcell", { name: "14" }),
|
|
).getByRole("button")!;
|
|
|
|
// Select a new value in the calendar grid.
|
|
await user.click(startGridCell);
|
|
await user.click(endGridCell);
|
|
expectCalendarToBeClosed();
|
|
|
|
// Make sure value is selected.
|
|
screen.getByText(
|
|
`Value = 2023-04-12T00:00:00.000Z 2023-04-14T00:00:00.000Z|`,
|
|
);
|
|
|
|
// Clear value.
|
|
const clearButton = screen.getByRole("button", {
|
|
name: "Clear",
|
|
});
|
|
await user.click(clearButton);
|
|
|
|
// Make sure value is cleared.
|
|
await expectDateFieldsToBeHidden();
|
|
screen.getByText("Value = |");
|
|
});
|
|
|
|
it("has a timezone", async () => {
|
|
const user = userEvent.setup();
|
|
const Wrapper = () => {
|
|
const [value, setValue] = useState<[string, string] | null>(null);
|
|
return (
|
|
<CunninghamProvider>
|
|
<div>
|
|
<div>Value = {value?.join(" ")}|</div>
|
|
<Button onClick={() => setValue(null)}>Clear</Button>
|
|
<DateRangePicker
|
|
startLabel="Start date"
|
|
endLabel="End date"
|
|
value={value}
|
|
onChange={(e) => setValue(e)}
|
|
timezone="America/Sao_Paulo"
|
|
/>
|
|
</div>
|
|
</CunninghamProvider>
|
|
);
|
|
};
|
|
render(<Wrapper />);
|
|
|
|
// Make sure any value is selected.
|
|
screen.getByText("Value = |");
|
|
|
|
const allSegments = await screen.getAllByRole("spinbutton");
|
|
const startMonthSegment = allSegments![0];
|
|
|
|
// Select the first segment, month one.
|
|
await user.click(startMonthSegment);
|
|
expect(startMonthSegment).toHaveFocus();
|
|
|
|
// Type start date's value.
|
|
await user.keyboard("{5}{1}{0}{2}{0}{2}{3}");
|
|
|
|
// Type end date's value.
|
|
await user.keyboard("{5}{1}{2}{2}{0}{2}{3}");
|
|
|
|
// Make sure values is selected at midnight on America/Sao_Paulo.
|
|
screen.getByText(
|
|
`Value = 2023-05-10T03:00:00.000Z 2023-05-12T03:00:00.000Z|`,
|
|
);
|
|
});
|
|
|
|
it("submits forms data", async () => {
|
|
let formData: any;
|
|
const Wrapper = () => {
|
|
const onSubmit = (e: FormEvent<HTMLFormElement>) => {
|
|
e.preventDefault();
|
|
const data = new FormData(e.currentTarget);
|
|
formData = {
|
|
datepickerStart: data.get("datepicker_start"),
|
|
datepickerEnd: data.get("datepicker_end"),
|
|
};
|
|
};
|
|
|
|
return (
|
|
<CunninghamProvider>
|
|
<div>
|
|
<form onSubmit={onSubmit}>
|
|
<DateRangePicker
|
|
startLabel="Pick a date"
|
|
endLabel="Pick a date"
|
|
name="datepicker"
|
|
/>
|
|
<Button>Submit</Button>
|
|
</form>
|
|
</div>
|
|
</CunninghamProvider>
|
|
);
|
|
};
|
|
render(<Wrapper />);
|
|
|
|
const user = userEvent.setup();
|
|
const submitButton = screen.getByRole("button", {
|
|
name: "Submit",
|
|
});
|
|
|
|
// Submit the form being empty.
|
|
await user.click(submitButton);
|
|
expect(formData).toEqual({
|
|
datepickerStart: "",
|
|
datepickerEnd: "",
|
|
});
|
|
|
|
// Open calendar
|
|
const toggleButton = (await screen.findAllByRole("button"))![1];
|
|
await user.click(toggleButton);
|
|
|
|
const allSegments = await screen.getAllByRole("spinbutton");
|
|
const startMonthSegment = allSegments![0];
|
|
|
|
// Select the first segment, month one.
|
|
await user.click(startMonthSegment);
|
|
expect(startMonthSegment).toHaveFocus();
|
|
|
|
// Type start date's value.
|
|
await user.keyboard("{5}{1}{0}{2}{0}{2}{3}");
|
|
|
|
// Type end date's value.
|
|
await user.keyboard("{5}{1}{2}{2}{0}{2}{3}");
|
|
|
|
// Submit form being filled with a date.
|
|
await user.click(submitButton);
|
|
expectCalendarToBeClosed();
|
|
expectDateFieldsToBeDisplayed();
|
|
|
|
// Make sure form's value matches.
|
|
expect(formData).toEqual({
|
|
datepickerStart: "2023-05-09T22:00:00.000Z",
|
|
datepickerEnd: "2023-05-11T22:00:00.000Z",
|
|
});
|
|
|
|
// Clear picked date.
|
|
const clearButton = screen.getByRole("button", {
|
|
name: "Clear date",
|
|
});
|
|
await user.click(clearButton);
|
|
expectDateFieldsToBeDisplayed();
|
|
|
|
// Submit the form being empty.
|
|
await user.click(submitButton);
|
|
|
|
// Date field disappears when the user click outside the comppnent.
|
|
await expectDateFieldsToBeHidden();
|
|
|
|
// Make sure form's value is null.
|
|
expect(formData).toEqual({
|
|
datepickerStart: "",
|
|
datepickerEnd: "",
|
|
});
|
|
});
|
|
});
|