/* * Copyright 2023 Palantir Technologies, Inc. All rights reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ import { addDays, format, parse } from "date-fns"; import enUSLocale from "date-fns/locale/en-US"; import { mount, type ReactWrapper } from "enzyme"; import { type DayModifiers, DayPicker, type ModifiersClassNames } from "react-day-picker"; import { Button, Classes as CoreClasses, Menu, MenuItem } from "@blueprintjs/core"; import { afterAll, afterEach, beforeEach, describe, expect, it, vi } from "@blueprintjs/test-commons/vitest"; import { Classes, DatePickerShortcutMenu, type DateRange, type DateRangeShortcut, DateUtils, Errors, MonthAndYear, Months, type NonNullDateRange, TimePicker, type TimePrecision, } from "../.."; import { ReactDayPickerClasses } from "../../common/classes"; import { assertDayDisabled } from "../../common/dayPickerTestUtils"; import { loadDateFnsLocaleFake } from "../../common/loadDateFnsLocaleFake"; import { DateRangePicker, type DateRangePickerProps } from "../date-range-picker/dateRangePicker"; import type { DateRangePickerState } from "../date-range-picker/dateRangePickerState"; // Change the default for testability (DateRangePicker.defaultProps as DateRangePickerProps).dateFnsLocaleLoader = loadDateFnsLocaleFake; describe("", () => { let testsContainerElement: HTMLElement; let drpWrapper: ReactWrapper; let onChangeSpy: ReturnType void>>; let onHoverChangeSpy: ReturnType>; beforeEach(() => { testsContainerElement = document.createElement("div"); document.body.appendChild(testsContainerElement); }); afterEach(() => { drpWrapper?.unmount(); drpWrapper?.detach(); testsContainerElement.remove(); }); it("should render its template", () => { const { wrapper } = render(); expect(wrapper.find(`.${Classes.DATERANGEPICKER}`).exists()).toBe(true); }); it("should not select any days by default", () => { const { wrapper, assertSelectedDays } = render(); expect(wrapper.state("value")).toEqual([null, null]); assertSelectedDays(); }); it("should apply user-provided modifiers", () => { const modifiers: DayModifiers = { odd: (d: Date) => d.getDate() % 2 === 1 }; const modifiersClassNames: ModifiersClassNames = { odd: "test-odd", }; const { left } = render({ dayPickerProps: { modifiers, modifiersClassNames } }); expect(left.findDay(4).hasClass(modifiersClassNames.odd)).toBe(false); expect(left.findDay(5).hasClass(modifiersClassNames.odd)).toBe(true); }); describe("reconciliates dayPickerProps", () => { it("should hide unnecessary nav buttons in contiguous months mode", () => { const defaultValue: DateRange = [new Date(2017, Months.SEPTEMBER, 1), null]; const { wrapper } = wrap(); expect(wrapper.find(".rdp-month").at(0).find(`.${Classes.DATEPICKER3_NAV_BUTTON_NEXT}`).exists()).toBe( false, ); expect(wrapper.find(".rdp-month").at(1).find(`.${Classes.DATEPICKER3_NAV_BUTTON_PREVIOUS}`).exists()).toBe( false, ); }); it("should disable days according to custom modifiers in addition to default modifiers", () => { const disableFridays = { dayOfWeek: [5] }; const defaultValue: DateRange = [new Date(2017, Months.SEPTEMBER, 1), null]; const maxDate = new Date(2017, Months.OCTOBER, 20); const { left, right } = wrap( , ); assertDayDisabled(left.findDay(15).getDOMNode()); assertDayDisabled(right.findDay(21).getDOMNode()); assertDayDisabled(left.findDay(10).getDOMNode(), false); }); it("should disable out-of-range max dates", () => { const { right } = wrap( , ); assertDayDisabled(right.findDay(21).getDOMNode()); assertDayDisabled(right.findDay(10).getDOMNode(), false); }); it("should disable out-of-range min dates", () => { const { left } = wrap( , ); assertDayDisabled(left.findDay(10).getDOMNode()); assertDayDisabled(left.findDay(21).getDOMNode(), false); }); describe("event handlers", () => { // use a date that lets us navigate forward and backward in the same year const defaultValue = [new Date(2017, Months.SEPTEMBER, 1), null] as DateRange; it("should call onMonthChange on button next click", () => { const onMonthChange = vi.fn(); wrap().clickNavButton( "next", 1, ); expect(onMonthChange).toHaveBeenCalled(); }); it("should call onMonthChange on button prev click", () => { const onMonthChange = vi.fn(); wrap().clickNavButton( "previous", ); expect(onMonthChange).toHaveBeenCalled(); }); it("should call onMonthChange on button next click of left calendar", () => { const onMonthChange = vi.fn(); wrap( , ).clickNavButton("next"); expect(onMonthChange).toHaveBeenCalled(); }); it("should call onMonthChange on button prev click of left calendar", () => { const onMonthChange = vi.fn(); wrap( , ).clickNavButton("previous"); expect(onMonthChange).toHaveBeenCalled(); }); it("should call onMonthChange on button next click of right calendar", () => { const onMonthChange = vi.fn(); wrap( , ).clickNavButton("next", 1); expect(onMonthChange).toHaveBeenCalled(); }); it("should call onMonthChange on button prev click of right calendar", () => { const onMonthChange = vi.fn(); wrap( , ).clickNavButton("previous", 1); expect(onMonthChange).toHaveBeenCalled(); }); it("should call onMonthChange on month select change in left calendar", () => { const onMonthChange = vi.fn(); wrap( , ).left.monthSelect.simulate("change"); expect(onMonthChange).toHaveBeenCalled(); }); it("should call onMonthChange on month select change in right calendar", () => { const onMonthChange = vi.fn(); wrap( , ).right.monthSelect.simulate("change"); expect(onMonthChange).toHaveBeenCalled(); }); it("should call onMonthChange on year select change in left calendar", () => { const onMonthChange = vi.fn(); wrap( , ).left.monthSelect.simulate("change"); expect(onMonthChange).toHaveBeenCalled(); }); it("should call onMonthChange on year select change in right calendar", () => { const onMonthChange = vi.fn(); wrap( , ).right.monthSelect.simulate("change"); expect(onMonthChange).toHaveBeenCalled(); }); it("should call onDayMouseEnter", () => { const onDayMouseEnter = vi.fn(); render({ dayPickerProps: { onDayMouseEnter }, defaultValue }).left.mouseEnterDay(14); expect(onDayMouseEnter).toHaveBeenCalled(); }); it("should call onDayMouseLeave", () => { const onDayMouseLeave = vi.fn(); render({ dayPickerProps: { onDayMouseLeave }, defaultValue }) .left.mouseEnterDay(14) .findDay(14) .simulate("mouseleave"); expect(onDayMouseLeave).toHaveBeenCalled(); }); it("should call onDayClick", () => { const onDayClick = vi.fn(); render({ dayPickerProps: { onDayClick }, defaultValue }).left.clickDay(14); expect(onDayClick).toHaveBeenCalled(); }); }); describe("for i18n", () => { // regression test for https://github.com/palantir/blueprint/issues/6489 it("should accept custom month name formatters in DatePicker3Caption (contiguousCalendarMonths={false})", () => { const CUSTOM_MONTH_NAMES = [ "First", "Second", "Third", "Fourth", "Fifth", "Sixth", "Seventh", "Eighth", "Ninth", "Tenth", "Eleventh", "Twelfth", ]; const formatters = { formatMonthCaption: (d: Date) => CUSTOM_MONTH_NAMES[d.getMonth()], }; // try a month which is not January to make sure we're actually setting a value in the