import { render, fireEvent } from "@testing-library/react";
import React from "react";
import {
DEFAULT_YEAR_ITEM_NUMBER,
KeyType,
formatDate,
getStartOfYear,
getYear,
getYearsPeriod,
newDate,
setYear,
} from "../date_utils";
import DatePicker from "../index";
import Year from "../year";
import {
getKey,
gotoNextView,
openDateInput,
SafeElementWrapper,
safeQuerySelector,
safeQuerySelectorAll,
} from "./test_utils";
const getYearOffset = (calendar: Element, date: Date): number => {
const dateNode = calendar.querySelector(
`.react-datepicker__year-text.react-datepicker__year-${date.getFullYear()}`,
)!;
const yearPicker = calendar.querySelector(".react-datepicker__year-wrapper")!;
return Array.from(yearPicker.children).indexOf(dateNode);
};
describe("YearPicker", () => {
it("should show year picker component when showYearPicker prop is present", () => {
const { container } = render();
const year = container.querySelector(".react-datepicker__year");
expect(year).not.toBeNull();
});
it("should show year picker component with default year item number", () => {
const { container } = render(
{}}
onYearMouseLeave={() => {}}
/>,
);
const yearItems = container.querySelectorAll(
".react-datepicker__year-text",
);
expect(yearItems.length).toBe(DEFAULT_YEAR_ITEM_NUMBER);
});
it("should show year picker component with specific year item number", () => {
const yearItemNumber = 9;
const { container } = render(
{}}
onYearMouseLeave={() => {}}
/>,
);
const yearItems = container.querySelectorAll(
".react-datepicker__year-text",
);
expect(yearItems.length).toBe(yearItemNumber);
});
it("should change the year when clicked on any option in the picker", () => {
const onYearChangeSpy = jest.fn();
const { container } = render(
{}}
onYearMouseLeave={() => {}}
/>,
);
const minRequiredYearLen = 2;
const yearDivs = safeQuerySelectorAll(
container,
".react-datepicker__year-text",
minRequiredYearLen,
);
const firstYearDiv = yearDivs[1]!;
fireEvent.click(firstYearDiv);
expect(onYearChangeSpy).toHaveBeenCalled();
});
it("should has selected class when element of array equal of chosen year", () => {
const date = new Date("2015-01-01");
const { container } = render(
{}}
onYearMouseLeave={() => {}}
/>,
);
const year = container.querySelectorAll(
".react-datepicker__year-text--selected",
)[0]?.textContent;
expect(year).toBe(getYear(date).toString());
});
it("should has selected class applied for all the selectedDates when selectsMultiple is set", () => {
const selectedDates = [new Date("2025-01-01"), new Date("2026-01-01")];
const { container } = render(
{}}
onYearMouseLeave={() => {}}
/>,
);
const yearElements = Array.from(
container.querySelectorAll(".react-datepicker__year-text--selected"),
);
expect(yearElements.length).toBe(selectedDates.length);
const isSelectedDatesHighlighted = yearElements.every((yearElement) => {
const yearValue = yearElement?.textContent;
return selectedDates.some(
(selectedDate) => getYear(selectedDate).toString() === yearValue,
);
});
expect(isSelectedDatesHighlighted).toBe(true);
});
it("should not has selected class where there is no selectedDates when selectsMultiple is set", () => {
const { container } = render(
{}}
onYearMouseLeave={() => {}}
/>,
);
const yearElements = Array.from(
container.querySelectorAll(".react-datepicker__year-text--selected"),
);
expect(yearElements.length).toBe(0);
});
it("should not has selected class where there is no selectedDates", () => {
const { container } = render(
{}}
onYearMouseLeave={() => {}}
/>,
);
const yearElements = Array.from(
container.querySelectorAll(".react-datepicker__year-text--selected"),
);
expect(yearElements.length).toBe(0);
});
it("should have current year class when element of array equal of current year", () => {
const date = new Date();
const { container } = render(
{}}
onYearMouseLeave={() => {}}
/>,
);
const year = container.querySelector(
".react-datepicker__year-text--today",
)?.textContent;
expect(year).toBe(getYear(date).toString());
});
it("should have aria-current date when element of array equal to current year", () => {
const date = new Date();
const { container } = render(
{}}
onYearMouseLeave={() => {}}
/>,
);
const ariaCurrent = container
.querySelector(".react-datepicker__year-text--today")
?.getAttribute("aria-current");
expect(ariaCurrent).toBe("date");
});
it("should not have aria-current date when element of array does not equal current year", () => {
const date = new Date("2015-01-01");
const { container } = render(
{}}
onYearMouseLeave={() => {}}
/>,
);
const ariaCurrent = container
.querySelector(".react-datepicker__year-text")
?.getAttribute("aria-current");
expect(ariaCurrent).toBeNull();
});
it("should return disabled class if current date is out of bound of minDate and maxDate", () => {
const { container } = render(
{}}
onYearMouseLeave={() => {}}
/>,
);
const year = container.querySelector(".react-datepicker__year-text");
expect(
year?.classList.contains("react-datepicker__year-text--disabled"),
).toBe(true);
});
it("should not return disabled class if current date is before minDate but same year", () => {
const date = newDate("2023-01-01");
const { container } = render(
{}}
onYearMouseLeave={() => {}}
/>,
);
const yearTexts = container.querySelectorAll(
".react-datepicker__year-text",
);
const firstYear = getYearsPeriod(
date,
DEFAULT_YEAR_ITEM_NUMBER,
).startPeriod;
expect(
yearTexts[2023 - firstYear]?.classList.contains(
"react-datepicker__year-text--disabled",
),
).toBe(false);
});
it("should not return disabled class if current date is after maxDate but same year", () => {
const date = newDate("2023-12-31");
const { container } = render(
{}}
onYearMouseLeave={() => {}}
/>,
);
const yearTexts = container.querySelectorAll(
".react-datepicker__year-text",
);
const firstYear = getYearsPeriod(
date,
DEFAULT_YEAR_ITEM_NUMBER,
).startPeriod;
expect(
yearTexts[2023 - firstYear]?.classList.contains(
"react-datepicker__year-text--disabled",
),
).toBe(false);
});
it("should return disabled class if specified excludeDate", () => {
const date = newDate("2023-12-31");
const firstYear = getYearsPeriod(
date,
DEFAULT_YEAR_ITEM_NUMBER,
).startPeriod;
const excludeDates: Date[] = [];
for (let year = firstYear; year <= 2023; year++) {
excludeDates.push(newDate(`${year}-01-01`));
}
const { container } = render(
{}}
onYearMouseLeave={() => {}}
/>,
);
const yearTexts = container.querySelectorAll(
".react-datepicker__year-text",
);
for (let i = 0; i <= 2023 - firstYear; i++) {
const year = yearTexts[i];
expect(
year?.classList.contains("react-datepicker__year-text--disabled"),
).toBe(true);
}
});
it("should return disabled class if specified includeDate", () => {
const date = newDate("2023-12-31");
const firstYear = getYearsPeriod(
date,
DEFAULT_YEAR_ITEM_NUMBER,
).startPeriod;
const includeDates: Date[] = [];
for (let year = firstYear; year <= 2023; year++) {
includeDates.push(newDate(`${year}-01-01`));
}
const { container } = render(
{}}
onYearMouseLeave={() => {}}
/>,
);
const yearTexts = container.querySelectorAll(
".react-datepicker__year-text",
);
const pos = 2023 - firstYear;
for (let i = 0; i <= pos; i++) {
const year = yearTexts[i];
expect(
year?.classList.contains("react-datepicker__year-text--disabled"),
).toBe(false);
}
for (let i = pos + 1; i < 12; i++) {
const year = yearTexts[i];
expect(
year?.classList.contains("react-datepicker__year-text--disabled"),
).toBe(true);
}
});
it("should render custom year content", () => {
function renderYearContent() {
return custom render;
}
const { container } = render(
{}}
onYearMouseLeave={() => {}}
/>,
);
const year = container.querySelector(".react-datepicker__year-text");
expect(year?.querySelector("span")?.textContent).toBe("custom render");
});
describe("range", () => {
it("should add range classes", () => {
const { container } = render(
{}}
onYearMouseLeave={() => {}}
/>,
);
const inRangeYears = container.querySelectorAll(
".react-datepicker__year-text--in-range",
);
expect(inRangeYears.length).toBe(4);
expect(inRangeYears[0]?.textContent).toBe("2009");
expect(inRangeYears[1]?.textContent).toBe("2010");
expect(inRangeYears[2]?.textContent).toBe("2011");
expect(inRangeYears[3]?.textContent).toBe("2012");
const rangeStartYear = container.querySelectorAll(
".react-datepicker__year-text--range-start",
);
expect(rangeStartYear.length).toBe(1);
expect(rangeStartYear[0]?.textContent).toBe("2009");
const rangeEndYear = container.querySelectorAll(
".react-datepicker__year-text--range-end",
);
expect(rangeEndYear.length).toBe(1);
expect(rangeEndYear[0]?.textContent).toBe("2012");
});
it("should not add range classes when start date is not defined", () => {
const { container } = render(
{}}
onYearMouseLeave={() => {}}
/>,
);
const inRangeYears = container.querySelectorAll(
".react-datepicker__year-text--in-range",
);
const rangeStartYear = container.querySelectorAll(
".react-datepicker__year-text--range-start",
);
const rangeEndYear = container.querySelectorAll(
".react-datepicker__year-text--range-end",
);
expect(inRangeYears.length).toBe(0);
expect(rangeEndYear.length).toBe(0);
expect(rangeStartYear.length).toBe(0);
});
it("should not add range classes when end date is not defined", () => {
const { container } = render(
{}}
onYearMouseLeave={() => {}}
/>,
);
const inRangeYears = container.querySelectorAll(
".react-datepicker__year-text--in-range",
);
const rangeStartYear = container.querySelectorAll(
".react-datepicker__year-text--range-start",
);
const rangeEndYear = container.querySelectorAll(
".react-datepicker__year-text--range-end",
);
expect(inRangeYears.length).toBe(0);
expect(rangeEndYear.length).toBe(0);
expect(rangeStartYear.length).toBe(0);
});
describe("selecting", () => {
it("should add in-selecting-range class if year is between the selecting date and end date", () => {
const { container } = render(
{}}
onYearMouseLeave={() => {}}
/>,
);
const years = container.querySelectorAll(
".react-datepicker__year-text--in-selecting-range",
);
expect(years.length).toBe(2);
expect(years[0]?.textContent).toBe("2015");
expect(years[1]?.textContent).toBe("2016");
});
it("should add in-selecting-range class if year is between the start date and selecting date", () => {
const { container } = render(
{}}
onYearMouseLeave={() => {}}
/>,
);
const years = container.querySelectorAll(
".react-datepicker__year-text--in-selecting-range",
);
expect(years.length).toBe(2);
expect(years[0]?.textContent).toBe("2010");
expect(years[1]?.textContent).toBe("2011");
});
it("should use pre selection date if selecting date is not defined", () => {
const { container } = render(
{}}
onYearMouseLeave={() => {}}
/>,
);
const years = container.querySelectorAll(
".react-datepicker__year-text--in-selecting-range",
);
expect(years.length).toBe(2);
expect(years[0]?.textContent).toBe("2010");
expect(years[1]?.textContent).toBe("2011");
});
it("should add in-selecting-range class for one year picker if year is between the start date and selecting date", () => {
const { container } = render(
{}}
onYearMouseLeave={() => {}}
/>,
);
const years = container.querySelectorAll(
".react-datepicker__year-text--in-selecting-range",
);
expect(years.length).toBe(2);
expect(years[0]?.textContent).toBe("2010");
expect(years[1]?.textContent).toBe("2011");
});
it("should not add in-selecting-range class for one year picker if the start date is not defined", () => {
const { container } = render(
{}}
onYearMouseLeave={() => {}}
/>,
);
const years = container.querySelectorAll(
".react-datepicker__year-text--in-selecting-range",
);
expect(years.length).toBe(0);
});
it("should not add in-selecting-range class for one year picker if the end date is defined", () => {
const { container } = render(
{}}
onYearMouseLeave={() => {}}
/>,
);
const years = container.querySelectorAll(
".react-datepicker__month-text--in-selecting-range",
);
expect(years.length).toBe(0);
});
it("should add 'selecting-range-start' class to the start selecting year", () => {
const { container } = render(
{}}
onYearMouseLeave={() => {}}
/>,
);
const years = container.querySelectorAll(
".react-datepicker__year-text--selecting-range-start",
);
expect(years.length).toBe(1);
expect(years[0]?.textContent).toBe("2012");
});
it("should add 'selecting-range-end' class to the end selecting year", () => {
const { container } = render(
{}}
onYearMouseLeave={() => {}}
/>,
);
const years = container.querySelectorAll(
".react-datepicker__year-text--selecting-range-end",
);
expect(years.length).toBe(1);
expect(years[0]?.textContent).toBe("2014");
});
});
});
describe("keyboard-selected", () => {
const className = "react-datepicker__year-text--keyboard-selected";
it("should set the key-selected class automatically to the current year when there is no selected date passed", () => {
const { container } = render(
,
);
const dateInput = safeQuerySelector(container, "input");
fireEvent.focus(dateInput);
const selectedYear = container.querySelector(
".react-datepicker__year-text--keyboard-selected",
);
expect(selectedYear).toBeDefined();
const currentYear = new Date().getFullYear();
expect(selectedYear?.textContent).toBe(`${currentYear}`);
});
it("should set the date to the selected year of the previous period when previous button clicked", () => {
let date: Date | null;
const expectedDate = getStartOfYear(setYear(newDate(), 2008));
const { container } = render(
{
date = d;
}}
/>,
);
const input = safeQuerySelector(container, "input");
fireEvent.focus(input);
const previousButton = new SafeElementWrapper(container)
.safeQuerySelector(".react-datepicker")
.safeQuerySelector(".react-datepicker__navigation--previous")
.getElement();
fireEvent.click(previousButton);
const year = container.querySelector(".react-datepicker__year");
const allPreselectedYears = year?.querySelectorAll(`.${className}`) ?? [];
expect(formatDate(date!, "dd.MM.yyyy")).toBe(
formatDate(expectedDate, "dd.MM.yyyy"),
);
expect(allPreselectedYears.length).toBe(1);
});
it("should set the date to the selected year of the next period when next button clicked", () => {
let date: Date | null;
const expectedDate = getStartOfYear(setYear(newDate(), 2032));
const { container } = render(
{
date = d;
}}
/>,
);
const input = safeQuerySelector(container, "input");
fireEvent.focus(input);
const nextButton = new SafeElementWrapper(container)
.safeQuerySelector(".react-datepicker")
.safeQuerySelector(".react-datepicker__navigation--next")
.getElement();
fireEvent.click(nextButton);
const year = container.querySelector(".react-datepicker__year");
const allPreselectedYears = year?.querySelectorAll(`.${className}`) ?? [];
expect(formatDate(date!, "dd.MM.yyyy")).toBe(
formatDate(expectedDate, "dd.MM.yyyy"),
);
expect(allPreselectedYears.length).toBe(1);
});
it("should not set the key-selected class when the year is a part of disabled dates", () => {
const date = newDate("2024-06-01");
const excludeDates = [newDate("2036-05-05")];
const { container } = render(
,
);
const dateInput = container.querySelector("input")!;
fireEvent.focus(dateInput);
const calendar = container.querySelector(".react-datepicker")!;
const nextButton = calendar.querySelector(
".react-datepicker__navigation--next",
)!;
fireEvent.click(nextButton);
expect(
container.querySelector(
".react-datepicker__year-text--keyboard-selected",
),
).toBeNull();
});
});
describe("Keyboard navigation", () => {
let preSelected: Date | null | undefined;
const setPreSelection = (preSelection: Date | null | undefined) => {
preSelected = preSelection ? newDate(preSelection) : preSelection;
};
let selectedDay: Date | null | undefined;
const onDayClick = (day: Date | null | undefined) => {
selectedDay = day;
};
const getPicker = (
initialDate: string | number | Date | null | undefined,
props = {},
) =>
render(
{}}
onYearMouseLeave={() => {}}
{...props}
/>,
).container;
const simulateLeft = (target: HTMLElement) =>
fireEvent.keyDown(target, getKey(KeyType.ArrowLeft));
const simulateRight = (target: HTMLElement) =>
fireEvent.keyDown(target, getKey(KeyType.ArrowRight));
const simulateUp = (target: HTMLElement) =>
fireEvent.keyDown(target, getKey(KeyType.ArrowUp));
const simulateDown = (target: HTMLElement) =>
fireEvent.keyDown(target, getKey(KeyType.ArrowDown));
it("should preSelect and set 2020 on left arrow press", () => {
const yearPicker = getPicker("2021-01-01");
const target = safeQuerySelector(
yearPicker,
".react-datepicker__year-text--selected",
);
simulateLeft(target);
expect(preSelected ? getYear(preSelected) : preSelected).toBe(2020);
});
it("should preSelect and set 2022 on left arrow press", () => {
const yearPicker = getPicker("2021-01-01");
const target = safeQuerySelector(
yearPicker,
".react-datepicker__year-text--selected",
);
simulateRight(target);
expect(preSelected ? getYear(preSelected) : preSelected).toBe(2022);
});
it("should preSelect and set 2021 on up arrow press", () => {
const yearPicker = getPicker("2024-01-01");
const target = safeQuerySelector(
yearPicker,
".react-datepicker__year-text--selected",
);
simulateUp(target);
expect(preSelected ? getYear(preSelected) : preSelected).toBe(2021);
});
it("should preSelect and set 2027 on down arrow press", () => {
const yearPicker = getPicker("2024-01-01");
const target = safeQuerySelector(
yearPicker,
".react-datepicker__year-text--selected",
);
simulateDown(target);
expect(preSelected ? getYear(preSelected) : preSelected).toBe(2027);
});
it("should paginate from 2018 to 2015", () => {
const yearPicker = getPicker("2018-01-01");
const target = safeQuerySelector(
yearPicker,
".react-datepicker__year-text--selected",
);
simulateUp(target);
expect(preSelected ? getYear(preSelected) : preSelected).toBe(2015);
});
it("should paginate from 2018 to 2016 with custom yearItemNumber", () => {
const yearPicker = getPicker("2018-01-01", { yearItemNumber: 8 });
const target = safeQuerySelector(
yearPicker,
".react-datepicker__year-text--selected",
);
simulateUp(target);
expect(preSelected ? getYear(preSelected) : preSelected).toBe(2016);
});
it("should paginate from 2019 to 2014 with custom yearItemNumber", () => {
const yearPicker = getPicker("2019-01-01", { yearItemNumber: 8 });
const target = safeQuerySelector(
yearPicker,
".react-datepicker__year-text--selected",
);
simulateUp(target);
expect(preSelected ? getYear(preSelected) : preSelected).toBe(2014);
});
it("should paginate from 2028 to 2031", () => {
const yearPicker = getPicker("2028-01-01");
const target = safeQuerySelector(
yearPicker,
".react-datepicker__year-text--selected",
);
simulateDown(target);
expect(preSelected ? getYear(preSelected) : preSelected).toBe(2031);
});
it("should paginate from 2024 to 2026 with custom yearItemNumber", () => {
const yearPicker = getPicker("2024-01-01", { yearItemNumber: 8 });
const target = safeQuerySelector(
yearPicker,
".react-datepicker__year-text--selected",
);
simulateDown(target);
expect(preSelected ? getYear(preSelected) : preSelected).toBe(2026);
});
it("should paginate from 2022 to 2027 with custom yearItemNumber", () => {
const yearPicker = getPicker("2022-01-01", { yearItemNumber: 8 });
const target = safeQuerySelector(
yearPicker,
".react-datepicker__year-text--selected",
);
simulateDown(target);
expect(preSelected ? getYear(preSelected) : preSelected).toBe(2027);
});
it("should paginate from 2017 to 2016", () => {
const yearPicker = getPicker("2017-01-01");
const target = safeQuerySelector(
yearPicker,
".react-datepicker__year-text--selected",
);
simulateLeft(target);
expect(preSelected ? getYear(preSelected) : preSelected).toBe(2016);
});
it("should paginate from 2028 to 2029", () => {
const yearPicker = getPicker("2028-01-01");
const target = safeQuerySelector(
yearPicker,
".react-datepicker__year-text--selected",
);
simulateRight(target);
expect(preSelected ? getYear(preSelected) : preSelected).toBe(2029);
});
it("should select 2021 when Enter key is pressed", () => {
const yearPicker = getPicker("2021-01-01");
const target = safeQuerySelector(
yearPicker,
".react-datepicker__year-text--selected",
);
fireEvent.keyDown(target, getKey(KeyType.Enter));
expect(selectedDay ? getYear(selectedDay) : selectedDay).toBe(2021);
});
it("should call onKeyDown handler on any key press", () => {
const onKeyDownSpy = jest.fn();
const { container } = render(
,
);
const dateInput = safeQuerySelector(container, "input");
fireEvent.focus(dateInput);
const year = safeQuerySelector(container, ".react-datepicker__year-text");
fireEvent.keyDown(year, getKey(KeyType.ArrowDown));
expect(onKeyDownSpy).toHaveBeenCalledTimes(1);
});
it("should select 2021 when Space key is pressed", () => {
const yearPicker = getPicker("2021-01-01");
const target = safeQuerySelector(
yearPicker,
".react-datepicker__year-text--selected",
);
fireEvent.keyDown(target, getKey(KeyType.Space));
expect(selectedDay ? getYear(selectedDay) : selectedDay).toBe(2021);
});
it("should disable keyboard navigation", () => {
const yearPicker = getPicker("2021-01-01", {
disabledKeyboardNavigation: true,
});
const target = safeQuerySelector(
yearPicker,
".react-datepicker__year-text--selected",
);
simulateRight(target);
expect(preSelected ? getYear(preSelected) : preSelected).toBe(2021);
});
});
it("should apply className returned from passed yearClassName prop function", () => {
const className = "customClassName";
const yearClassNameFunc = () => className;
const date = new Date();
const { container } = render(
{}}
onYearMouseLeave={() => {}}
yearClassName={yearClassNameFunc}
/>,
);
expect(
container
.querySelector(".react-datepicker__year-text")
?.classList.contains(className),
).toBe(true);
expect(
container.querySelector(`.react-datepicker__year-${date.getFullYear()}`),
).not.toBeNull();
});
describe("Auto-Focus", () => {
it("should auto-focus on the same year offset in the next/previous view when navigating", () => {
const date = newDate("2024-06-01");
const { container } = render(
,
);
openDateInput(container);
const calendar = container.querySelector(".react-datepicker")!;
const selectedElementOffset = getYearOffset(calendar, date);
gotoNextView(container);
const preSelectedDateElement = container.querySelector(
`.react-datepicker__year-wrapper :nth-child(${selectedElementOffset + 1}).react-datepicker__year-text`,
)!;
expect(preSelectedDateElement.getAttribute("tabindex")).toBe("0");
});
it("shouldn't auto-focus on the same year offset in the next/previous view when navigating if the year in the corresponding offset is disabled", () => {
const date = newDate("2024-06-01");
const excludeDate = newDate("2036-05-01"); // 2036 is in the same 8th offset like 2024
const { container } = render(
,
);
openDateInput(container);
const calendar = container.querySelector(".react-datepicker")!;
const selectedElementOffset = getYearOffset(calendar, date);
gotoNextView(container);
const preSelectedDateElement = container.querySelector(
`.react-datepicker__year-wrapper :nth-child(${selectedElementOffset + 1}).react-datepicker__year-${excludeDate.getFullYear()}`,
)!;
expect(preSelectedDateElement.getAttribute("tabindex")).toBe("-1");
});
});
describe("edge cases for coverage", () => {
it("should handle keyboard navigation with null selected date (Enter key)", () => {
const onDayClickMock = jest.fn();
const { container } = render(
{}}
onSelect={onDayClickMock}
showYearPicker
/>,
);
openDateInput(container);
const currentYear = container.querySelector(
".react-datepicker__year-text--today",
) as HTMLElement;
fireEvent.keyDown(currentYear, getKey(KeyType.Enter));
// When selected is null and Enter is pressed, onDayClick should not be called
// because of the early return at line 297
expect(onDayClickMock).not.toHaveBeenCalled();
});
it("should handle keyboard navigation with null preSelection (Arrow keys)", () => {
const { container } = render(
{}}
showYearPicker
disabledKeyboardNavigation={false}
/>,
);
openDateInput(container);
const calendar = container.querySelector(".react-datepicker")!;
const yearElements = container.querySelectorAll(
".react-datepicker__year-text",
);
const firstYear = yearElements[0] as HTMLElement;
// Simulate a scenario where preSelection is null
// This tests the early returns at lines 304, 313, 326, 356
fireEvent.keyDown(firstYear, getKey(KeyType.ArrowRight));
fireEvent.keyDown(firstYear, getKey(KeyType.ArrowLeft));
fireEvent.keyDown(firstYear, getKey(KeyType.ArrowUp));
fireEvent.keyDown(firstYear, getKey(KeyType.ArrowDown));
// Should not throw errors
expect(calendar).not.toBeNull();
});
it("should handle undefined date in handleYearNavigation", () => {
const { container } = render(
{}}
preSelection={newDate()}
setPreSelection={() => {}}
onYearMouseEnter={() => {}}
onYearMouseLeave={() => {}}
yearItemNumber={DEFAULT_YEAR_ITEM_NUMBER}
/>,
);
// When date is undefined, render returns null (line 450)
const yearWrapper = container.querySelector(
".react-datepicker__year-wrapper",
);
expect(yearWrapper).toBeNull();
});
it("should handle undefined date in onYearClick", () => {
const onDayClickMock = jest.fn();
const { container } = render(
{}}
onYearMouseEnter={() => {}}
onYearMouseLeave={() => {}}
yearItemNumber={DEFAULT_YEAR_ITEM_NUMBER}
/>,
);
// When date is undefined, component renders null
expect(container.querySelector(".react-datepicker__year")).toBeNull();
});
it("should use requestAnimationFrame for updateFocusOnPaginate", () => {
// This test verifies the updateFocusOnPaginate method uses requestAnimationFrame
// The method is called during keyboard navigation when moving to a different year period
const { container } = render(
{}} showYearPicker />,
);
openDateInput(container);
const yearElements = container.querySelectorAll(
".react-datepicker__year-text",
);
// Keyboard navigation that triggers updateFocusOnPaginate happens when
// navigating beyond the current year period
expect(yearElements.length).toBeGreaterThan(0);
});
it("should test usePointerEvent for year mouse events", () => {
const { container } = render(
{}}
showYearPicker
usePointerEvent
/>,
);
openDateInput(container);
const yearElement = container.querySelector(
".react-datepicker__year-text",
) as HTMLElement;
// Test pointer events instead of mouse events (lines 476-489)
fireEvent.pointerEnter(yearElement);
fireEvent.pointerLeave(yearElement);
// Pointer events should be handled
expect(yearElement).not.toBeNull();
});
it("should handle disabled and excluded dates in handleYearNavigation", () => {
const excludeDate = new Date();
excludeDate.setFullYear(excludeDate.getFullYear() + 1);
const { container } = render(
{}}
showYearPicker
excludeDates={[excludeDate]}
/>,
);
openDateInput(container);
const currentYear = container.querySelector(
".react-datepicker__year-text--today",
) as HTMLElement;
// Try to navigate to next year using arrow key
fireEvent.keyDown(currentYear, getKey(KeyType.ArrowRight));
// Should handle the navigation even with excluded dates
expect(currentYear).not.toBeNull();
});
it("should call updateFocusOnPaginate after keyboard navigation (line 118)", () => {
const rafSpy = jest.spyOn(window, "requestAnimationFrame");
const { container } = render(
{}}
showYearPicker
yearItemNumber={12}
/>,
);
openDateInput(container);
const yearElements = container.querySelectorAll(
".react-datepicker__year-text",
);
expect(yearElements.length).toBeGreaterThan(0);
// Get the last year element in the current view to trigger pagination
const lastYearElement = yearElements[
yearElements.length - 1
] as HTMLElement;
// Line 118: Navigate down from last year to trigger updateFocusOnPaginate
fireEvent.keyDown(lastYearElement, getKey(KeyType.ArrowDown));
// updateFocusOnPaginate uses requestAnimationFrame
expect(rafSpy).toHaveBeenCalled();
rafSpy.mockRestore();
});
it("should handle onYearClick when date is undefined (line 279)", () => {
const onDayClickMock = jest.fn();
// Render Year component directly with undefined date
const { container } = render(
{}}
onYearMouseEnter={() => {}}
onYearMouseLeave={() => {}}
yearItemNumber={DEFAULT_YEAR_ITEM_NUMBER}
/>,
);
// Line 279: when date is undefined, onYearClick early returns
// Component should render null
const yearWrapper = container.querySelector(
".react-datepicker__year-wrapper",
);
expect(yearWrapper).toBeNull();
});
it("should handle keyboard navigation when yearItemNumber is undefined (line 138)", () => {
const { container } = render(
{}}
preSelection={newDate()}
setPreSelection={() => {}}
onYearMouseEnter={() => {}}
onYearMouseLeave={() => {}}
yearItemNumber={undefined}
/>,
);
// Line 138: when yearItemNumber is undefined, early return
const yearElements = container.querySelectorAll(
".react-datepicker__year-text",
);
expect(yearElements.length).toBeGreaterThan(0);
const firstYear = yearElements[0] as HTMLElement;
// Should not throw when navigating
expect(() =>
fireEvent.keyDown(firstYear, getKey(KeyType.ArrowRight)),
).not.toThrow();
});
it("should handle all keyboard navigation edge cases with null preSelection", () => {
const setPreSelectionMock = jest.fn();
const { container } = render(
{}}
preSelection={null}
setPreSelection={setPreSelectionMock}
onYearMouseEnter={() => {}}
onYearMouseLeave={() => {}}
yearItemNumber={DEFAULT_YEAR_ITEM_NUMBER}
/>,
);
const yearElements = container.querySelectorAll(
".react-datepicker__year-text",
);
expect(yearElements.length).toBeGreaterThan(0);
const firstYear = yearElements[0] as HTMLElement;
// Lines 304, 313, 326, 356: keyboard navigation with null preSelection
fireEvent.keyDown(firstYear, getKey(KeyType.ArrowRight));
fireEvent.keyDown(firstYear, getKey(KeyType.ArrowLeft));
fireEvent.keyDown(firstYear, getKey(KeyType.ArrowUp));
fireEvent.keyDown(firstYear, getKey(KeyType.ArrowDown));
// Should handle all cases without throwing
expect(firstYear).not.toBeNull();
});
it("should handle Enter key when selected is null (line 297)", () => {
const onDayClickMock = jest.fn();
const { container } = render(
{}}
onSelect={onDayClickMock}
showYearPicker
/>,
);
openDateInput(container);
const currentYear = container.querySelector(
".react-datepicker__year-text--today",
) as HTMLElement;
expect(currentYear).not.toBeNull();
// Line 297: Enter key with null selected
fireEvent.keyDown(currentYear, getKey(KeyType.Enter));
// Should still work
expect(currentYear).not.toBeNull();
});
it("should handle keyboard-selected year focus updates", () => {
const { container } = render(
{}} showYearPicker />,
);
openDateInput(container);
const years = container.querySelectorAll(".react-datepicker__year-text");
expect(years.length).toBeGreaterThan(1);
const firstYear = years[0] as HTMLElement;
// Navigate with keyboard
fireEvent.keyDown(firstYear, getKey(KeyType.ArrowRight));
// Should update keyboard-selected class
const keyboardSelected = container.querySelector(
".react-datepicker__year-text--keyboard-selected",
);
expect(keyboardSelected).not.toBeNull();
});
});
});