Skip to content

Commit 139f974

Browse files
Merge pull request #6025 from Hacker0x01/more-tests
Add tests for month and year logic and improve coverage
2 parents 1bc835f + 8ce6054 commit 139f974

9 files changed

+320
-1
lines changed

src/date_utils.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1090,6 +1090,7 @@ export function isTimeInDisabledRange(
10901090
try {
10911091
valid = !isWithinInterval(baseTime, { start: min, end: max });
10921092
} catch (err) {
1093+
/* istanbul ignore next - date-fns historically threw on invalid intervals */
10931094
valid = false;
10941095
}
10951096
return valid;

src/test/calendar_container.test.tsx

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import { render } from "@testing-library/react";
22
import React from "react";
33

44
import CalendarContainer from "../calendar_container";
5+
import { CalendarContainer as CalendarContainerFromIndex } from "../index";
56

67
describe("CalendarContainer", () => {
78
it("renders with default props", () => {
@@ -18,6 +19,16 @@ describe("CalendarContainer", () => {
1819
expect(dialog?.textContent).toBe("Test Content");
1920
});
2021

22+
it("exposes CalendarContainer via the package entry point", () => {
23+
const { container } = render(
24+
<CalendarContainerFromIndex>
25+
<div>Entry Content</div>
26+
</CalendarContainerFromIndex>,
27+
);
28+
29+
expect(container.querySelector('[role="dialog"]')).toBeTruthy();
30+
});
31+
2132
it("renders with showTimeSelectOnly prop", () => {
2233
const { container } = render(
2334
<CalendarContainer showTimeSelectOnly>

src/test/click_outside_wrapper.test.tsx

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -225,4 +225,58 @@ describe("ClickOutsideWrapper", () => {
225225
addEventListenerSpy.mockRestore();
226226
removeEventListenerSpy.mockRestore();
227227
});
228+
229+
it("falls back to event.target when composedPath does not return nodes", () => {
230+
const addEventListenerSpy = jest.spyOn(document, "addEventListener");
231+
render(
232+
<ClickOutsideWrapper onClickOutside={onClickOutsideMock}>
233+
<div>Inside</div>
234+
</ClickOutsideWrapper>,
235+
);
236+
237+
const handlerEntry = addEventListenerSpy.mock.calls.find(
238+
([type]) => type === "mousedown",
239+
);
240+
const handler = handlerEntry?.[1] as EventListener;
241+
242+
const outsideNode = document.createElement("div");
243+
const mockEvent = {
244+
composed: true,
245+
composedPath: () => [{}],
246+
target: outsideNode,
247+
} as unknown as MouseEvent;
248+
249+
handler(mockEvent);
250+
251+
expect(onClickOutsideMock).toHaveBeenCalledTimes(1);
252+
addEventListenerSpy.mockRestore();
253+
});
254+
255+
it("does not treat non-HTMLElement targets as ignored elements", () => {
256+
const addEventListenerSpy = jest.spyOn(document, "addEventListener");
257+
render(
258+
<ClickOutsideWrapper
259+
onClickOutside={onClickOutsideMock}
260+
ignoreClass="ignore-me"
261+
>
262+
<div>Inside</div>
263+
</ClickOutsideWrapper>,
264+
);
265+
266+
const handlerEntry = addEventListenerSpy.mock.calls.find(
267+
([type]) => type === "mousedown",
268+
);
269+
const handler = handlerEntry?.[1] as EventListener;
270+
271+
const textNode = document.createTextNode("outside");
272+
const mockEvent = {
273+
composed: false,
274+
target: textNode,
275+
} as unknown as MouseEvent;
276+
277+
handler(mockEvent);
278+
279+
expect(onClickOutsideMock).toHaveBeenCalledTimes(1);
280+
addEventListenerSpy.mockRestore();
281+
});
228282
});

src/test/date_utils_test.test.ts

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1148,6 +1148,14 @@ describe("date_utils", () => {
11481148

11491149
expect(isMonthInRange(startDate, endDate, 1, day)).toBe(false);
11501150
});
1151+
1152+
it("should return false when the start year is after the end year", () => {
1153+
const day = newDate("2025-01-01");
1154+
const startDate = newDate("2026-01-01");
1155+
const endDate = newDate("2024-01-01");
1156+
1157+
expect(isMonthInRange(startDate, endDate, 1, day)).toBe(false);
1158+
});
11511159
});
11521160

11531161
describe("getStartOfYear", () => {
@@ -1190,6 +1198,14 @@ describe("date_utils", () => {
11901198

11911199
expect(isQuarterInRange(startDate, endDate, 1, day)).toBe(false);
11921200
});
1201+
1202+
it("should return false when the start year is after the end year", () => {
1203+
const day = newDate("2025-01-01");
1204+
const startDate = newDate("2026-01-01");
1205+
const endDate = newDate("2024-04-01");
1206+
1207+
expect(isQuarterInRange(startDate, endDate, 1, day)).toBe(false);
1208+
});
11931209
});
11941210

11951211
describe("isYearInRange", () => {
@@ -1214,6 +1230,12 @@ describe("date_utils", () => {
12141230
it("should return false if range isn't passed", () => {
12151231
expect(isYearInRange(2016)).toBe(false);
12161232
});
1233+
1234+
it("should return false if provided dates are invalid", () => {
1235+
expect(
1236+
isYearInRange(2016, new Date("invalid"), new Date("invalid")),
1237+
).toBe(false);
1238+
});
12171239
});
12181240

12191241
describe("getYearsPeriod", () => {

src/test/datepicker_test.test.tsx

Lines changed: 72 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,11 @@ import {
2626
subWeeks,
2727
subYears,
2828
} from "../date_utils";
29-
import DatePicker, { registerLocale } from "../index";
29+
import DatePicker, {
30+
registerLocale,
31+
setDefaultLocale,
32+
getDefaultLocale,
33+
} from "../index";
3034

3135
import CustomInput from "./helper_components/custom_input";
3236
import ShadowRoot from "./helper_components/shadow_root";
@@ -121,6 +125,73 @@ describe("DatePicker", () => {
121125
jest.resetAllMocks();
122126
});
123127

128+
it("exposes locale helpers via the main entry point", () => {
129+
const originalLocale = getDefaultLocale();
130+
try {
131+
expect(getDefaultLocale()).toBe(originalLocale);
132+
setDefaultLocale("en-GB");
133+
expect(getDefaultLocale()).toBe("en-GB");
134+
} finally {
135+
setDefaultLocale(originalLocale);
136+
}
137+
});
138+
139+
it("does not trigger selection changes when readOnly", () => {
140+
const onChange = jest.fn();
141+
const { instance } = renderDatePickerWithRef({
142+
readOnly: true,
143+
onChange,
144+
});
145+
146+
act(() => {
147+
instance?.handleSelect(newDate("2024-05-01"));
148+
});
149+
150+
expect(onChange).not.toHaveBeenCalled();
151+
});
152+
153+
it("skips updating preSelection when readOnly", () => {
154+
const selected = newDate("2024-01-01");
155+
const { instance } = renderDatePickerWithRef({
156+
readOnly: true,
157+
selected,
158+
});
159+
160+
const originalPreSelection = instance?.state.preSelection;
161+
162+
act(() => {
163+
instance?.setPreSelection(newDate("2024-02-01"));
164+
});
165+
166+
expect(instance?.state.preSelection).toBe(originalPreSelection);
167+
});
168+
169+
it("short-circuits day key navigation when keyboard navigation is disabled", () => {
170+
const onKeyDown = jest.fn();
171+
const preSelection = newDate("2024-06-15");
172+
const { instance } = renderDatePickerWithRef({
173+
disabledKeyboardNavigation: true,
174+
onKeyDown,
175+
inline: true,
176+
selected: preSelection,
177+
});
178+
179+
act(() => {
180+
instance?.setState({ preSelection });
181+
});
182+
183+
act(() => {
184+
instance?.onDayKeyDown({
185+
key: "ArrowRight",
186+
shiftKey: false,
187+
preventDefault: jest.fn(),
188+
} as unknown as React.KeyboardEvent<HTMLDivElement>);
189+
});
190+
191+
expect(onKeyDown).toHaveBeenCalled();
192+
expect(instance?.state.preSelection).toBe(preSelection);
193+
});
194+
124195
it("should retain the calendar open status when the document visibility change", () => {
125196
const { container } = render(<DatePicker />);
126197
const input = safeQuerySelector(container, "input");

src/test/month_logic.test.ts

Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
import type React from "react";
2+
import Month from "../month";
3+
import { KeyType, newDate } from "../date_utils";
4+
5+
type MonthComponentProps = React.ComponentProps<typeof Month>;
6+
7+
const buildProps = (
8+
override: Partial<MonthComponentProps> = {},
9+
): MonthComponentProps =>
10+
({
11+
day: newDate("2024-01-01"),
12+
onDayClick: jest.fn(),
13+
onDayMouseEnter: jest.fn(),
14+
onMouseLeave: jest.fn(),
15+
setPreSelection: jest.fn(),
16+
preSelection: newDate("2024-01-01"),
17+
showFourColumnMonthYearPicker: false,
18+
showTwoColumnMonthYearPicker: false,
19+
disabledKeyboardNavigation: false,
20+
...override,
21+
}) as MonthComponentProps;
22+
23+
describe("Month logic helpers", () => {
24+
it("short-circuits keyboard navigation when there is no preSelection", () => {
25+
const props = buildProps({ preSelection: undefined });
26+
const instance = new Month(props);
27+
const getVerticalOffsetSpy = jest.spyOn(instance, "getVerticalOffset");
28+
29+
instance.handleKeyboardNavigation(
30+
{
31+
preventDefault: jest.fn(),
32+
} as unknown as React.KeyboardEvent<HTMLDivElement>,
33+
KeyType.ArrowRight,
34+
1,
35+
);
36+
37+
expect(getVerticalOffsetSpy).not.toHaveBeenCalled();
38+
expect(props.setPreSelection).not.toHaveBeenCalled();
39+
});
40+
41+
it("prevents quarter navigation when the destination date is disabled", () => {
42+
const props = buildProps();
43+
const instance = new Month(props);
44+
jest.spyOn(instance, "isDisabled").mockReturnValue(true);
45+
jest.spyOn(instance, "isExcluded").mockReturnValue(false);
46+
47+
instance.handleQuarterNavigation(2, newDate("2024-04-01"));
48+
49+
expect(props.setPreSelection).not.toHaveBeenCalled();
50+
});
51+
52+
it("does not handle quarter arrow keys without a preSelection value", () => {
53+
const props = buildProps({ preSelection: undefined });
54+
const instance = new Month(props);
55+
const navigationSpy = jest.spyOn(instance, "handleQuarterNavigation");
56+
57+
instance.onQuarterKeyDown(
58+
{
59+
key: KeyType.ArrowRight,
60+
preventDefault: jest.fn(),
61+
} as unknown as React.KeyboardEvent<HTMLDivElement>,
62+
2,
63+
);
64+
65+
instance.onQuarterKeyDown(
66+
{
67+
key: KeyType.ArrowLeft,
68+
preventDefault: jest.fn(),
69+
} as unknown as React.KeyboardEvent<HTMLDivElement>,
70+
2,
71+
);
72+
73+
expect(navigationSpy).not.toHaveBeenCalled();
74+
});
75+
});

src/test/shadow_root.test.tsx

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -75,4 +75,16 @@ describe("ShadowRoot", () => {
7575

7676
expect(container.querySelector("div")).not.toBeNull();
7777
});
78+
79+
it("should avoid re-initializing when effect runs multiple times", () => {
80+
const { container } = render(
81+
<React.StrictMode>
82+
<ShadowRoot>
83+
<div>Strict Content</div>
84+
</ShadowRoot>
85+
</React.StrictMode>,
86+
);
87+
88+
expect(container.querySelector("div")).not.toBeNull();
89+
});
7890
});

src/test/test_utils.test.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,9 @@
1+
import { KeyType } from "../date_utils";
12
import {
23
SafeElementWrapper,
34
safeQuerySelector,
45
safeQuerySelectorAll,
6+
getKey,
57
} from "./test_utils";
68

79
describe("test_utils", () => {
@@ -37,6 +39,12 @@ describe("test_utils", () => {
3739
});
3840
});
3941

42+
describe("getKey", () => {
43+
it("should throw when key is not supported", () => {
44+
expect(() => getKey("?" as KeyType)).toThrow("Unknown key");
45+
});
46+
});
47+
4048
describe("safeQuerySelectorAll", () => {
4149
let container: HTMLElement;
4250

0 commit comments

Comments
 (0)