import { startOfDay, startOfMonth } from "date-fns"; import React from "react"; import { activeElement, dateButton, grid, nav, nextButton, previousButton, } from "@/test/elements"; import { act, fireEvent, render, screen } from "@/test/render"; import { setTestTime } from "@/test/setTestTime"; import { user } from "@/test/user"; import { defaultLocale } from "./classes/DateLib"; import type { MonthProps } from "./components/Month"; import type { MonthsProps } from "./components/Months"; import { DayPicker } from "./DayPicker"; import { ja } from "./locale/ja.js"; const testId = "test"; const dayPicker = () => screen.getByTestId(testId); test("should render a date picker component", () => { render(); expect(dayPicker()).toBeInTheDocument(); }); test("render the navigation and month grids", () => { render(); expect(nav()).toBeInTheDocument(); expect(grid()).toBeInTheDocument(); }); test("apply classnames and style according to props", () => { render( , ); expect(dayPicker()).toHaveClass("rdp-root"); expect(dayPicker()).toHaveClass("custom-class"); expect(dayPicker()).toHaveStyle({ color: "rgb(255, 0, 0)" }); }); test("forward aria attributes to the root element", () => { render( , ); expect(dayPicker()).toHaveAttribute("aria-label", "Calendar"); expect(dayPicker()).toHaveAttribute("aria-labelledby", "calendar-heading"); }); test("use custom components", () => { render(
Custom Navigation
, Month: (_props: MonthProps) =>
Custom Month
, Months: (props: MonthsProps) => (
Custom Months
{props.children}
), Footer: () =>
Custom Footer
, }} footer="Footer" />, ); expect(dayPicker()).toHaveTextContent("Custom Navigation"); expect(dayPicker()).toHaveTextContent("Custom Months"); expect(dayPicker()).toHaveTextContent("Custom Month"); expect(dayPicker()).toHaveTextContent("Custom Footer"); }); describe("when the date picker is focused", () => { test("focus the previous button", async () => { render(); await user.tab(); expect(activeElement()).toBe(previousButton()); }); test("on RTL, focus the previous button", async () => { render(); await user.tab(); expect(activeElement()).toBe(previousButton()); }); }); describe("when the grid is focused", () => { const today = new Date(2024, 1, 4); setTestTime(today); test("should focus the today's date", async () => { render(); await user.tab(); await user.tab(); await user.tab(); expect(activeElement()).toBe(dateButton(today)); }); describe("when the today’s date is disabled", () => { test("should focus the first day of the month", async () => { render(); await user.tab(); await user.tab(); await user.tab(); expect(activeElement()).toBe(dateButton(startOfMonth(today))); }); }); }); describe("when a disabled day is focused", () => { test("keyboard and mouse interactions do not select it", async () => { const disabledDay = new Date(2024, 8, 5); const handleSelect = jest.fn(); render( , ); const disabledElement = dateButton(disabledDay); act(() => disabledElement.focus()); await user.keyboard("{Enter}"); await user.click(disabledElement); expect(handleSelect).not.toHaveBeenCalled(); expect(disabledElement).toHaveAttribute("aria-disabled", "true"); }); }); describe("when navigation is disabled", () => { beforeEach(() => { jest.useFakeTimers(); }); afterEach(() => { jest.useRealTimers(); }); test("keyboard navigation stays within the visible month", async () => { const defaultMonth = new Date(2025, 6, 1); const lastDay = new Date(2025, 6, 31); const previousDay = new Date(2025, 6, 30); render( , ); await user.tab(); await user.tab(); await user.tab(); const lastDayButton = dateButton(lastDay); const previousDayButton = dateButton(previousDay); expect(activeElement()).toBe(lastDayButton); await user.keyboard("{ArrowRight}"); expect(activeElement()).toBe(lastDayButton); await user.keyboard("{ArrowLeft}"); expect(activeElement()).toBe(previousDayButton); }); }); describe("when a day is mouse entered", () => { const handleDayMouseEnter = jest.fn(); const handleDayMouseLeave = jest.fn(); const today = startOfDay(new Date()); beforeEach(async () => { render( , ); fireEvent.mouseEnter(dateButton(today)); fireEvent.mouseLeave(dateButton(today)); }); test("should call the event handler", async () => { expect(handleDayMouseEnter).toHaveBeenCalled(); expect(handleDayMouseLeave).toHaveBeenCalled(); }); }); describe("when the `month` is changed programmatically", () => { test("should update the calendar to reflect the new month", async () => { const initialMonth = new Date(2023, 0, 1); // January 2023 const newMonth = new Date(2023, 1, 1); // February 2023 const { rerender } = render( , ); expect(grid("January 2023")).toBeInTheDocument(); rerender(); expect(grid("February 2023")).toBeInTheDocument(); }); }); test("extends the default locale", () => { render( "bar", }, }} />, ); // Check if the custom month name is rendered expect(grid("bar 2024")).toBeInTheDocument(); }); test("places the month dropdown before the year dropdown by default", () => { render(); const combos = screen.getAllByRole("combobox"); expect(combos[0]).toHaveAttribute("aria-label", "Choose the Month"); expect(combos[1]).toHaveAttribute("aria-label", "Choose the Year"); }); test("places the year dropdown before the month dropdown for year-first locales", () => { render(); const combos = screen.getAllByRole("combobox"); expect(combos[0]).toHaveAccessibleName( ja.labels?.labelYearDropdown as string, ); expect(combos[1]).toHaveAccessibleName( ja.labels?.labelMonthDropdown as string, ); }); test("should render the custom components", () => { render(
Custom Nav
, YearsDropdown: () =>
Custom YearsDropdown
, MonthsDropdown: () =>
Custom MonthsDropdown
, Footer: () =>
Custom Footer
, }} />, ); expect(screen.getByText("Custom Nav")).toBeInTheDocument(); expect(screen.getByText("Custom Footer")).toBeInTheDocument(); expect(screen.getByText("Custom YearsDropdown")).toBeInTheDocument(); expect(screen.getByText("Custom MonthsDropdown")).toBeInTheDocument(); }); describe("when navLayout is set", () => { const today = new Date(2024, 1, 4); describe("when navLayout is set to 'around'", () => { beforeEach(() => { render( , ); }); test("renders navigation layout as 'around'", () => { expect(dayPicker()).toHaveAttribute("data-nav-layout", "around"); }); test('render the "previous" button before the month caption', () => { expect(previousButton().nextSibling).toHaveTextContent("February 2024"); }); test('render the "next" button before the month caption', () => { expect(nextButton().previousSibling).toHaveTextContent("February 2024"); }); }); describe("when navLayout is set to 'aft er'", () => { beforeEach(() => { render( , ); }); test("renders navigation layout as 'after'", () => { expect(dayPicker()).toHaveAttribute("data-nav-layout", "after"); }); test("render the navigation after the month caption", () => { expect(nav().previousSibling).toHaveTextContent("February 2024"); }); }); });