diff --git a/src/date_utils.ts b/src/date_utils.ts
index eee05b9b0b..6667108394 100644
--- a/src/date_utils.ts
+++ b/src/date_utils.ts
@@ -1090,6 +1090,7 @@ export function isTimeInDisabledRange(
try {
valid = !isWithinInterval(baseTime, { start: min, end: max });
} catch (err) {
+ /* istanbul ignore next - date-fns historically threw on invalid intervals */
valid = false;
}
return valid;
diff --git a/src/test/calendar_container.test.tsx b/src/test/calendar_container.test.tsx
index a79514a2cf..6edbca40be 100644
--- a/src/test/calendar_container.test.tsx
+++ b/src/test/calendar_container.test.tsx
@@ -2,6 +2,7 @@ import { render } from "@testing-library/react";
import React from "react";
import CalendarContainer from "../calendar_container";
+import { CalendarContainer as CalendarContainerFromIndex } from "../index";
describe("CalendarContainer", () => {
it("renders with default props", () => {
@@ -18,6 +19,16 @@ describe("CalendarContainer", () => {
expect(dialog?.textContent).toBe("Test Content");
});
+ it("exposes CalendarContainer via the package entry point", () => {
+ const { container } = render(
+
+ Entry Content
+ ,
+ );
+
+ expect(container.querySelector('[role="dialog"]')).toBeTruthy();
+ });
+
it("renders with showTimeSelectOnly prop", () => {
const { container } = render(
diff --git a/src/test/click_outside_wrapper.test.tsx b/src/test/click_outside_wrapper.test.tsx
index e1bd54a7a2..6f5ee5725a 100644
--- a/src/test/click_outside_wrapper.test.tsx
+++ b/src/test/click_outside_wrapper.test.tsx
@@ -225,4 +225,58 @@ describe("ClickOutsideWrapper", () => {
addEventListenerSpy.mockRestore();
removeEventListenerSpy.mockRestore();
});
+
+ it("falls back to event.target when composedPath does not return nodes", () => {
+ const addEventListenerSpy = jest.spyOn(document, "addEventListener");
+ render(
+
+ Inside
+ ,
+ );
+
+ const handlerEntry = addEventListenerSpy.mock.calls.find(
+ ([type]) => type === "mousedown",
+ );
+ const handler = handlerEntry?.[1] as EventListener;
+
+ const outsideNode = document.createElement("div");
+ const mockEvent = {
+ composed: true,
+ composedPath: () => [{}],
+ target: outsideNode,
+ } as unknown as MouseEvent;
+
+ handler(mockEvent);
+
+ expect(onClickOutsideMock).toHaveBeenCalledTimes(1);
+ addEventListenerSpy.mockRestore();
+ });
+
+ it("does not treat non-HTMLElement targets as ignored elements", () => {
+ const addEventListenerSpy = jest.spyOn(document, "addEventListener");
+ render(
+
+ Inside
+ ,
+ );
+
+ const handlerEntry = addEventListenerSpy.mock.calls.find(
+ ([type]) => type === "mousedown",
+ );
+ const handler = handlerEntry?.[1] as EventListener;
+
+ const textNode = document.createTextNode("outside");
+ const mockEvent = {
+ composed: false,
+ target: textNode,
+ } as unknown as MouseEvent;
+
+ handler(mockEvent);
+
+ expect(onClickOutsideMock).toHaveBeenCalledTimes(1);
+ addEventListenerSpy.mockRestore();
+ });
});
diff --git a/src/test/date_utils_test.test.ts b/src/test/date_utils_test.test.ts
index b9d85167e0..bf07bc5967 100644
--- a/src/test/date_utils_test.test.ts
+++ b/src/test/date_utils_test.test.ts
@@ -1148,6 +1148,14 @@ describe("date_utils", () => {
expect(isMonthInRange(startDate, endDate, 1, day)).toBe(false);
});
+
+ it("should return false when the start year is after the end year", () => {
+ const day = newDate("2025-01-01");
+ const startDate = newDate("2026-01-01");
+ const endDate = newDate("2024-01-01");
+
+ expect(isMonthInRange(startDate, endDate, 1, day)).toBe(false);
+ });
});
describe("getStartOfYear", () => {
@@ -1190,6 +1198,14 @@ describe("date_utils", () => {
expect(isQuarterInRange(startDate, endDate, 1, day)).toBe(false);
});
+
+ it("should return false when the start year is after the end year", () => {
+ const day = newDate("2025-01-01");
+ const startDate = newDate("2026-01-01");
+ const endDate = newDate("2024-04-01");
+
+ expect(isQuarterInRange(startDate, endDate, 1, day)).toBe(false);
+ });
});
describe("isYearInRange", () => {
@@ -1214,6 +1230,12 @@ describe("date_utils", () => {
it("should return false if range isn't passed", () => {
expect(isYearInRange(2016)).toBe(false);
});
+
+ it("should return false if provided dates are invalid", () => {
+ expect(
+ isYearInRange(2016, new Date("invalid"), new Date("invalid")),
+ ).toBe(false);
+ });
});
describe("getYearsPeriod", () => {
diff --git a/src/test/datepicker_test.test.tsx b/src/test/datepicker_test.test.tsx
index 230b9f879b..51521c3c41 100644
--- a/src/test/datepicker_test.test.tsx
+++ b/src/test/datepicker_test.test.tsx
@@ -26,7 +26,11 @@ import {
subWeeks,
subYears,
} from "../date_utils";
-import DatePicker, { registerLocale } from "../index";
+import DatePicker, {
+ registerLocale,
+ setDefaultLocale,
+ getDefaultLocale,
+} from "../index";
import CustomInput from "./helper_components/custom_input";
import ShadowRoot from "./helper_components/shadow_root";
@@ -121,6 +125,73 @@ describe("DatePicker", () => {
jest.resetAllMocks();
});
+ it("exposes locale helpers via the main entry point", () => {
+ const originalLocale = getDefaultLocale();
+ try {
+ expect(getDefaultLocale()).toBe(originalLocale);
+ setDefaultLocale("en-GB");
+ expect(getDefaultLocale()).toBe("en-GB");
+ } finally {
+ setDefaultLocale(originalLocale);
+ }
+ });
+
+ it("does not trigger selection changes when readOnly", () => {
+ const onChange = jest.fn();
+ const { instance } = renderDatePickerWithRef({
+ readOnly: true,
+ onChange,
+ });
+
+ act(() => {
+ instance?.handleSelect(newDate("2024-05-01"));
+ });
+
+ expect(onChange).not.toHaveBeenCalled();
+ });
+
+ it("skips updating preSelection when readOnly", () => {
+ const selected = newDate("2024-01-01");
+ const { instance } = renderDatePickerWithRef({
+ readOnly: true,
+ selected,
+ });
+
+ const originalPreSelection = instance?.state.preSelection;
+
+ act(() => {
+ instance?.setPreSelection(newDate("2024-02-01"));
+ });
+
+ expect(instance?.state.preSelection).toBe(originalPreSelection);
+ });
+
+ it("short-circuits day key navigation when keyboard navigation is disabled", () => {
+ const onKeyDown = jest.fn();
+ const preSelection = newDate("2024-06-15");
+ const { instance } = renderDatePickerWithRef({
+ disabledKeyboardNavigation: true,
+ onKeyDown,
+ inline: true,
+ selected: preSelection,
+ });
+
+ act(() => {
+ instance?.setState({ preSelection });
+ });
+
+ act(() => {
+ instance?.onDayKeyDown({
+ key: "ArrowRight",
+ shiftKey: false,
+ preventDefault: jest.fn(),
+ } as unknown as React.KeyboardEvent);
+ });
+
+ expect(onKeyDown).toHaveBeenCalled();
+ expect(instance?.state.preSelection).toBe(preSelection);
+ });
+
it("should retain the calendar open status when the document visibility change", () => {
const { container } = render();
const input = safeQuerySelector(container, "input");
diff --git a/src/test/month_logic.test.ts b/src/test/month_logic.test.ts
new file mode 100644
index 0000000000..52f2b7c18a
--- /dev/null
+++ b/src/test/month_logic.test.ts
@@ -0,0 +1,75 @@
+import type React from "react";
+import Month from "../month";
+import { KeyType, newDate } from "../date_utils";
+
+type MonthComponentProps = React.ComponentProps;
+
+const buildProps = (
+ override: Partial = {},
+): MonthComponentProps =>
+ ({
+ day: newDate("2024-01-01"),
+ onDayClick: jest.fn(),
+ onDayMouseEnter: jest.fn(),
+ onMouseLeave: jest.fn(),
+ setPreSelection: jest.fn(),
+ preSelection: newDate("2024-01-01"),
+ showFourColumnMonthYearPicker: false,
+ showTwoColumnMonthYearPicker: false,
+ disabledKeyboardNavigation: false,
+ ...override,
+ }) as MonthComponentProps;
+
+describe("Month logic helpers", () => {
+ it("short-circuits keyboard navigation when there is no preSelection", () => {
+ const props = buildProps({ preSelection: undefined });
+ const instance = new Month(props);
+ const getVerticalOffsetSpy = jest.spyOn(instance, "getVerticalOffset");
+
+ instance.handleKeyboardNavigation(
+ {
+ preventDefault: jest.fn(),
+ } as unknown as React.KeyboardEvent,
+ KeyType.ArrowRight,
+ 1,
+ );
+
+ expect(getVerticalOffsetSpy).not.toHaveBeenCalled();
+ expect(props.setPreSelection).not.toHaveBeenCalled();
+ });
+
+ it("prevents quarter navigation when the destination date is disabled", () => {
+ const props = buildProps();
+ const instance = new Month(props);
+ jest.spyOn(instance, "isDisabled").mockReturnValue(true);
+ jest.spyOn(instance, "isExcluded").mockReturnValue(false);
+
+ instance.handleQuarterNavigation(2, newDate("2024-04-01"));
+
+ expect(props.setPreSelection).not.toHaveBeenCalled();
+ });
+
+ it("does not handle quarter arrow keys without a preSelection value", () => {
+ const props = buildProps({ preSelection: undefined });
+ const instance = new Month(props);
+ const navigationSpy = jest.spyOn(instance, "handleQuarterNavigation");
+
+ instance.onQuarterKeyDown(
+ {
+ key: KeyType.ArrowRight,
+ preventDefault: jest.fn(),
+ } as unknown as React.KeyboardEvent,
+ 2,
+ );
+
+ instance.onQuarterKeyDown(
+ {
+ key: KeyType.ArrowLeft,
+ preventDefault: jest.fn(),
+ } as unknown as React.KeyboardEvent,
+ 2,
+ );
+
+ expect(navigationSpy).not.toHaveBeenCalled();
+ });
+});
diff --git a/src/test/shadow_root.test.tsx b/src/test/shadow_root.test.tsx
index 0098af79d9..51362de480 100644
--- a/src/test/shadow_root.test.tsx
+++ b/src/test/shadow_root.test.tsx
@@ -75,4 +75,16 @@ describe("ShadowRoot", () => {
expect(container.querySelector("div")).not.toBeNull();
});
+
+ it("should avoid re-initializing when effect runs multiple times", () => {
+ const { container } = render(
+
+
+ Strict Content
+
+ ,
+ );
+
+ expect(container.querySelector("div")).not.toBeNull();
+ });
});
diff --git a/src/test/test_utils.test.ts b/src/test/test_utils.test.ts
index 4503cb0cfb..0836a50118 100644
--- a/src/test/test_utils.test.ts
+++ b/src/test/test_utils.test.ts
@@ -1,7 +1,9 @@
+import { KeyType } from "../date_utils";
import {
SafeElementWrapper,
safeQuerySelector,
safeQuerySelectorAll,
+ getKey,
} from "./test_utils";
describe("test_utils", () => {
@@ -37,6 +39,12 @@ describe("test_utils", () => {
});
});
+ describe("getKey", () => {
+ it("should throw when key is not supported", () => {
+ expect(() => getKey("?" as KeyType)).toThrow("Unknown key");
+ });
+ });
+
describe("safeQuerySelectorAll", () => {
let container: HTMLElement;
diff --git a/src/test/year_logic.test.tsx b/src/test/year_logic.test.tsx
new file mode 100644
index 0000000000..ff44f7a6b3
--- /dev/null
+++ b/src/test/year_logic.test.tsx
@@ -0,0 +1,65 @@
+import type React from "react";
+import { act } from "react";
+
+import Year from "../year";
+import { newDate } from "../date_utils";
+
+type YearComponentProps = React.ComponentProps;
+
+const buildYearProps = (
+ override: Partial = {},
+): YearComponentProps =>
+ ({
+ date: newDate("2024-01-01"),
+ yearItemNumber: 12,
+ onDayClick: jest.fn(),
+ onYearMouseEnter: jest.fn(),
+ onYearMouseLeave: jest.fn(),
+ preSelection: newDate("2024-01-01"),
+ setPreSelection: jest.fn(),
+ ...override,
+ }) as YearComponentProps;
+
+describe("Year logic helpers", () => {
+ it("focuses the requested ref after pagination updates", () => {
+ const props = buildYearProps();
+ const instance = new Year(props);
+ const focusSpy = jest.fn();
+ instance.YEAR_REFS[0]!.current = {
+ focus: focusSpy,
+ } as unknown as HTMLDivElement;
+
+ const rafSpy = jest
+ .spyOn(window, "requestAnimationFrame")
+ .mockImplementation((callback: FrameRequestCallback) => {
+ callback(0);
+ return 0;
+ });
+
+ act(() => {
+ instance.updateFocusOnPaginate(0);
+ });
+
+ expect(focusSpy).toHaveBeenCalled();
+ rafSpy.mockRestore();
+ });
+
+ it("skips onYearClick when no base date is provided", () => {
+ const instance = new Year(buildYearProps({ date: undefined }));
+ const handleYearClickSpy = jest.spyOn(instance, "handleYearClick");
+
+ instance.onYearClick(
+ {} as React.MouseEvent,
+ newDate("2024-01-01").getFullYear(),
+ );
+
+ expect(handleYearClickSpy).not.toHaveBeenCalled();
+ });
+
+ it("exposes an isSameDay helper", () => {
+ const instance = new Year(buildYearProps());
+ const day = newDate("2024-03-01");
+
+ expect(instance.isSameDay(day, newDate("2024-03-01"))).toBe(true);
+ });
+});