import { render, fireEvent } from "@testing-library/react";
import React from "react";
import { addYears, getYear, newDate, subYears } from "../date_utils";
import YearDropdownOptions from "../year_dropdown_options";
import { safeQuerySelector, safeQuerySelectorAll } from "./test_utils";
describe("YearDropdownOptions", () => {
let yearDropdown: HTMLElement, handleChangeResult: number;
const mockHandleChange = function (changeInput: number) {
handleChangeResult = changeInput;
};
let onCancelSpy: jest.Mock;
beforeEach(() => {
onCancelSpy = jest.fn();
yearDropdown = render(
,
).container;
});
it("shows the available years in the initial view", () => {
const yearDropdownNode = yearDropdown?.querySelector("div");
const textContents = Array.from(
yearDropdownNode?.querySelectorAll(".react-datepicker__year-option") ??
[],
).map((node) => node.textContent);
expect(textContents).toEqual(
expect.arrayContaining([
"",
"2020",
"2019",
"2018",
"2017",
"2016",
"✓2015",
"2014",
"2013",
"2012",
"2011",
"2010",
"",
]),
);
});
it("generate 10 years, 5 below and 5 above the selected one, if prop scrollableYearDropdown is false", () => {
const yearsListLength = Array.from(
yearDropdown?.querySelectorAll(".react-datepicker__year-option") ?? [],
).filter((node) => node.textContent).length;
expect(yearsListLength).toBe(11);
});
it("increments the available years when the 'upcoming years' button is clicked", () => {
const navigationYearsUpcoming = safeQuerySelector(
yearDropdown,
".react-datepicker__navigation--years-upcoming",
);
fireEvent.click(navigationYearsUpcoming);
const textContents = Array.from(
yearDropdown?.querySelectorAll(".react-datepicker__year-option") ?? [],
).map((node) => node.textContent);
expect(textContents).toEqual(
expect.arrayContaining([
"",
"2021",
"2020",
"2019",
"2018",
"2017",
"2016",
"✓2015",
"2014",
"2013",
"2012",
"2011",
"",
]),
);
});
it("decrements the available years when the 'previous years' button is clicked", () => {
const navigationYearsPrevious = safeQuerySelector(
yearDropdown,
".react-datepicker__navigation--years-previous",
);
fireEvent.click(navigationYearsPrevious);
const textContents = Array.from(
yearDropdown?.querySelectorAll(".react-datepicker__year-option") ?? [],
).map((node) => node.textContent);
expect(textContents).toEqual(
expect.arrayContaining([
"",
"2019",
"2018",
"2017",
"2016",
"✓2015",
"2014",
"2013",
"2012",
"2011",
"2010",
"2009",
"",
]),
);
});
it("calls the supplied onChange function when a year is clicked", () => {
const yearOptions = safeQuerySelectorAll(
yearDropdown,
".react-datepicker__year-option",
);
const year = yearOptions.find((node) => node.textContent?.includes("2015"));
if (!year) {
throw new Error("Year 2015 not found!");
}
fireEvent.click(year);
expect(handleChangeResult).toBe(2015);
});
it("calls the supplied onCancel function on handleClickOutside", () => {
render(
,
);
fireEvent.mouseDown(document.body);
fireEvent.touchStart(document.body);
expect(onCancelSpy).toHaveBeenCalledTimes(2);
});
it("handles Enter key to select year", () => {
const yearOptions = safeQuerySelectorAll(
yearDropdown,
".react-datepicker__year-option",
);
const year2014Option = yearOptions.find((node) =>
node.textContent?.includes("2014"),
);
if (!year2014Option) {
throw new Error("Year 2014 not found!");
}
fireEvent.keyDown(year2014Option, { key: "Enter" });
expect(handleChangeResult).toBe(2014);
});
it("handles Escape key to cancel dropdown", () => {
const yearOptions = safeQuerySelectorAll(
yearDropdown,
".react-datepicker__year-option",
);
const year2014Option = yearOptions.find((node) =>
node.textContent?.includes("2014"),
);
if (!year2014Option) {
throw new Error("Year 2014 not found!");
}
fireEvent.keyDown(year2014Option, { key: "Escape" });
expect(onCancelSpy).toHaveBeenCalled();
});
it("handles ArrowUp key navigation", () => {
const yearOptions = safeQuerySelectorAll(
yearDropdown,
".react-datepicker__year-option",
);
const year2015Option = yearOptions.find((node) =>
node.textContent?.includes("✓2015"),
);
if (!year2015Option) {
throw new Error("Year 2015 not found!");
}
fireEvent.keyDown(year2015Option, { key: "ArrowUp" });
// ArrowUp should focus year 2016 (year + 1 in the code)
expect(document.activeElement?.textContent).toContain("2016");
});
it("handles ArrowDown key navigation", () => {
const yearOptions = safeQuerySelectorAll(
yearDropdown,
".react-datepicker__year-option",
);
const year2015Option = yearOptions.find((node) =>
node.textContent?.includes("✓2015"),
);
if (!year2015Option) {
throw new Error("Year 2015 not found!");
}
fireEvent.keyDown(year2015Option, { key: "ArrowDown" });
// ArrowDown should focus year 2014 (year - 1 in the code)
expect(document.activeElement?.textContent).toContain("2014");
});
describe("selected", () => {
const className = "react-datepicker__year-option--selected_year";
let yearOptions: HTMLElement[];
beforeEach(() => {
yearOptions = Array.from(
yearDropdown?.querySelectorAll(".react-datepicker__year-option") ?? [],
);
});
describe("if selected", () => {
let selectedYearOption: HTMLElement;
beforeEach(() => {
selectedYearOption = yearOptions.find((o) =>
o.classList.contains(className),
)!;
});
it("should apply the selected class", () => {
expect(selectedYearOption.classList.contains(className)).toBe(true);
});
it("should add aria-selected property with the value of true", () => {
const ariaSelected = selectedYearOption.getAttribute("aria-selected");
expect(ariaSelected).toBe("true");
});
});
describe("if not selected", () => {
let selectedYearOption: HTMLElement;
beforeEach(() => {
selectedYearOption = yearOptions.find(
(o) => !o.classList.contains(className),
)!;
});
it("should not apply the selected class", () => {
expect(selectedYearOption.classList.contains(className)).toBe(false);
});
it("should not add aria-selected property with the value of true", () => {
const ariaSelected = selectedYearOption.getAttribute("aria-selected");
expect(ariaSelected).toBeNull();
});
});
});
});
describe("YearDropdownOptions with scrollable dropwdown", () => {
it("should show upcoming and previous links and generate 10 years if prop scrollableYearDropdown is true", () => {
const onCancelSpy = jest.fn();
const onChangeSpy = jest.fn();
const { container } = render(
,
);
expect(
Array.from(
container.querySelectorAll(".react-datepicker__year-option"),
).filter((node) => node.textContent).length,
).toBe(21);
expect(
container.querySelectorAll(
".react-datepicker__navigation--years-upcoming",
).length,
).toBe(1);
expect(
container.querySelectorAll(
".react-datepicker__navigation--years-previous",
).length,
).toBe(1);
});
it("should generate years between minDate and maxDate if prop scrollableYearDropdown is true", () => {
const onCancelSpy = jest.fn();
const onChangeSpy = jest.fn();
const minDate = newDate();
const maxDate = addYears(newDate(), 1);
const { container } = render(
,
);
const yearsList = Array.from(
container.querySelectorAll(".react-datepicker__year-option"),
)
.filter((node) => node.textContent)
.map((node) => node.textContent?.replace("✓", ""));
expect(yearsList.length).toBe(2);
expect(yearsList).toContain(`${getYear(minDate)}`);
expect(yearsList).toContain(`${getYear(maxDate)}`);
});
it("should hide arrows to add years, if not between minDate and maxDate", () => {
const onCancelSpy = jest.fn();
const onChangeSpy = jest.fn();
const minDate = newDate();
const maxDate = addYears(newDate(), 1);
const { container } = render(
,
);
expect(
container.querySelectorAll(
".react-datepicker__navigation--years-upcoming",
).length,
).toBe(0);
expect(
container.querySelectorAll(
".react-datepicker__navigation--years-previous",
).length,
).toBe(0);
});
it("should show arrows to add years, if actual years list contains years between minDate and maxDate", () => {
const onCancelSpy = jest.fn();
const onChangeSpy = jest.fn();
const minDate = subYears(newDate(), 11);
const maxDate = addYears(newDate(), 11);
const { container } = render(
,
);
expect(
container.querySelectorAll(
".react-datepicker__navigation--years-previous",
).length,
).toBe(1);
expect(
container.querySelectorAll(
".react-datepicker__navigation--years-upcoming",
).length,
).toBe(1);
let textContents = Array.from(
container.querySelectorAll(".react-datepicker__year-option"),
).filter((node) => node.textContent);
expect(
textContents.find(
(year) => parseInt(year.textContent ?? "") === getYear(minDate),
),
).toBeUndefined();
expect(
textContents.find(
(year) => parseInt(year.textContent ?? "") === getYear(maxDate),
),
).toBeUndefined();
const navigationYearsPrevious = safeQuerySelector(
container,
".react-datepicker__navigation--years-previous",
);
fireEvent.click(navigationYearsPrevious);
textContents = Array.from(
container.querySelectorAll(".react-datepicker__year-option"),
).filter((node) => node.textContent);
const x = textContents.find(
(year) => parseInt(year.textContent ?? "") === getYear(minDate),
);
expect(x).not.toBeUndefined();
expect(
textContents.find(
(year) => parseInt(year.textContent ?? "") === getYear(maxDate),
),
).toBeUndefined();
expect(
container.querySelectorAll(
".react-datepicker__navigation--years-previous",
).length,
).toBe(0);
const navigationYearsUpcoming = safeQuerySelector(
container,
".react-datepicker__navigation--years-upcoming",
);
fireEvent.click(navigationYearsUpcoming);
textContents = Array.from(
container.querySelectorAll(".react-datepicker__year-option"),
).filter((node) => node.textContent);
expect(
textContents.find(
(year) => parseInt(year.textContent ?? "") === getYear(minDate),
),
).toBeUndefined();
expect(
textContents.find(
(year) => parseInt(year.textContent ?? "") === getYear(maxDate),
),
).toBeUndefined();
});
it("should show arrows to add previous years, if actual years list does not contain minDate year, if only minDate is provided", () => {
const onCancelSpy = jest.fn();
const onChangeSpy = jest.fn();
const minDate = subYears(newDate(), 11);
const { container } = render(
,
);
expect(
container.querySelectorAll(
".react-datepicker__navigation--years-previous",
).length,
).toBe(1);
expect(
container.querySelectorAll(
".react-datepicker__navigation--years-upcoming",
).length,
).toBe(1);
let textContents = Array.from(
container.querySelectorAll(".react-datepicker__year-option"),
).filter((node) => node.textContent);
expect(
textContents.find(
(year) => parseInt(year.textContent ?? "") === getYear(minDate),
),
).toBeUndefined();
const navigationYearsPrevious = safeQuerySelector(
container,
".react-datepicker__navigation--years-previous",
);
fireEvent.click(navigationYearsPrevious);
textContents = Array.from(
container.querySelectorAll(".react-datepicker__year-option"),
).filter((node) => node.textContent);
expect(
textContents.find(
(year) => parseInt(year.textContent ?? "") === getYear(minDate),
),
).not.toBeUndefined();
expect(
container.querySelectorAll(
".react-datepicker__navigation--years-upcoming",
).length,
).toBe(1);
expect(
container.querySelectorAll(
".react-datepicker__navigation--years-previous",
).length,
).toBe(0);
});
it("should show arrows to add upcoming years, if actual years list does not contain maxDate year, if only maxDate is provided", () => {
const onCancelSpy = jest.fn();
const onChangeSpy = jest.fn();
const maxDate = addYears(newDate(), 11);
const { container } = render(
,
);
expect(
container.querySelectorAll(
".react-datepicker__navigation--years-previous",
).length,
).toBe(1);
expect(
container.querySelectorAll(
".react-datepicker__navigation--years-upcoming",
).length,
).toBe(1);
let textContents = Array.from(
container.querySelectorAll(".react-datepicker__year-option"),
).filter((node) => node.textContent);
expect(
textContents.find(
(year) => parseInt(year.textContent ?? "") === getYear(maxDate),
),
).toBeUndefined();
const navigationYearsUpcoming = safeQuerySelector(
container,
".react-datepicker__navigation--years-upcoming",
);
fireEvent.click(navigationYearsUpcoming);
textContents = Array.from(
container.querySelectorAll(".react-datepicker__year-option"),
).filter((node) => node.textContent);
expect(
textContents.find(
(year) => parseInt(year.textContent ?? "") === getYear(maxDate),
),
).not.toBeUndefined();
expect(
container.querySelectorAll(
".react-datepicker__navigation--years-upcoming",
).length,
).toBe(0);
expect(
container.querySelectorAll(
".react-datepicker__navigation--years-previous",
).length,
).toBe(1);
});
it("should generate 25 years (25 above, 25 below selected) if prop yearDropdownItemNumber is set to 25", () => {
const onCancelSpy = jest.fn();
const onChangeSpy = jest.fn();
const { container } = render(
,
);
const yearsList = Array.from(
container.querySelectorAll(".react-datepicker__year-option"),
).filter((node) => node.textContent);
expect(yearsList.length).toBe(51);
});
it("should scroll year dropdown to the middle on open", () => {
const onCancelSpy = jest.fn();
const onChangeSpy = jest.fn();
let instance: YearDropdownOptions | null;
render(
{
instance = node;
}}
onCancel={onCancelSpy}
onChange={onChangeSpy}
scrollableYearDropdown
year={2015}
yearDropdownItemNumber={25}
/>,
);
(
instance!.dropdownRef as { current: Record | null }
).current = {
scrollHeight: 800,
clientHeight: 400,
};
instance!.componentDidMount();
expect(instance!.dropdownRef.current!.scrollTop).toBe(200);
});
});