Skip to content

Commit 3c79e39

Browse files
Merge pull request #5974 from Hacker0x01/improve-test-coverage
Improve test coverage
2 parents 19e218a + ec836b3 commit 3c79e39

15 files changed

+2107
-451
lines changed

src/test/calendar_icon.test.tsx

Lines changed: 103 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -86,4 +86,107 @@ describe("CalendarIcon", () => {
8686
expect(onClickMock).toHaveBeenCalledTimes(1);
8787
expect(onClickCustomIcon).toHaveBeenCalledTimes(1);
8888
});
89+
90+
it("should fire only custom icon onClick when CalendarIcon onClick is not provided", () => {
91+
const onClickCustomIcon = jest.fn();
92+
93+
const { container } = render(
94+
<CalendarIcon
95+
icon={
96+
<svg
97+
xmlns="http://www.w3.org/2000/svg"
98+
width="1em"
99+
height="1em"
100+
viewBox="0 0 48 48"
101+
onClick={onClickCustomIcon}
102+
/>
103+
}
104+
/>,
105+
);
106+
107+
const icon = safeQuerySelector(
108+
container,
109+
"svg.react-datepicker__calendar-icon",
110+
);
111+
fireEvent.click(icon);
112+
113+
// Lines 55-57: custom icon onClick is called
114+
expect(onClickCustomIcon).toHaveBeenCalledTimes(1);
115+
});
116+
117+
it("should fire only CalendarIcon onClick when custom icon onClick is not provided", () => {
118+
const { container } = render(
119+
<CalendarIcon
120+
icon={
121+
<svg
122+
xmlns="http://www.w3.org/2000/svg"
123+
width="1em"
124+
height="1em"
125+
viewBox="0 0 48 48"
126+
/>
127+
}
128+
onClick={onClickMock}
129+
/>,
130+
);
131+
132+
const icon = safeQuerySelector(
133+
container,
134+
"svg.react-datepicker__calendar-icon",
135+
);
136+
fireEvent.click(icon);
137+
138+
// Lines 59-61: CalendarIcon onClick is called
139+
expect(onClickMock).toHaveBeenCalledTimes(1);
140+
});
141+
142+
it("should handle custom icon without onClick prop", () => {
143+
const { container } = render(
144+
<CalendarIcon
145+
icon={
146+
<svg
147+
xmlns="http://www.w3.org/2000/svg"
148+
width="1em"
149+
height="1em"
150+
viewBox="0 0 48 48"
151+
/>
152+
}
153+
/>,
154+
);
155+
156+
const icon = safeQuerySelector(
157+
container,
158+
"svg.react-datepicker__calendar-icon",
159+
);
160+
161+
// Should not throw when clicking without any onClick handlers
162+
expect(() => fireEvent.click(icon)).not.toThrow();
163+
});
164+
165+
it("should apply className to custom icon", () => {
166+
const { container } = render(
167+
<CalendarIcon
168+
icon={<IconParkSolidApplication />}
169+
className="custom-class"
170+
/>,
171+
);
172+
173+
const icon = container.querySelector(".custom-class");
174+
expect(icon).not.toBeNull();
175+
});
176+
177+
it("should apply className to string icon", () => {
178+
const { container } = render(
179+
<CalendarIcon icon="fa-calendar" className="custom-class" />,
180+
);
181+
182+
const icon = container.querySelector("i.custom-class");
183+
expect(icon).not.toBeNull();
184+
});
185+
186+
it("should apply className to default SVG icon", () => {
187+
const { container } = render(<CalendarIcon className="custom-class" />);
188+
189+
const icon = container.querySelector("svg.custom-class");
190+
expect(icon).not.toBeNull();
191+
});
89192
});

src/test/calendar_test.test.tsx

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -221,6 +221,58 @@ describe("Calendar", () => {
221221
expect(isSameDay(instance?.state.date, openToDate)).toBeTruthy();
222222
});
223223

224+
it("should move pre-selection to first enabled day when month changes", () => {
225+
const onSelect = jest.fn();
226+
const setOpen = jest.fn();
227+
const setPreSelection = jest.fn();
228+
const filterDate = (date: Date) => date.getDate() >= 3;
229+
230+
const { instance } = getCalendar({
231+
adjustDateOnChange: true,
232+
onSelect,
233+
setOpen,
234+
setPreSelection,
235+
filterDate,
236+
selected: new Date("2024-01-15T00:00:00"),
237+
});
238+
239+
const targetMonth = new Date("2024-02-01T00:00:00");
240+
act(() => {
241+
instance?.handleMonthChange(targetMonth);
242+
});
243+
244+
const expectedDate = new Date("2024-02-03T00:00:00");
245+
const [selectedDate] = onSelect.mock.calls[0];
246+
expect(isSameDay(selectedDate, expectedDate)).toBe(true);
247+
expect(setOpen).toHaveBeenCalledWith(true);
248+
const [preSelectionDate] = setPreSelection.mock.calls[0];
249+
expect(isSameDay(preSelectionDate, expectedDate)).toBe(true);
250+
expect(instance?.state.isRenderAriaLiveMessage).toBe(true);
251+
});
252+
253+
it("should fall back to provided month date when no enabled days exist", () => {
254+
const onSelect = jest.fn();
255+
const setPreSelection = jest.fn();
256+
const filterDate = () => false;
257+
258+
const { instance } = getCalendar({
259+
adjustDateOnChange: true,
260+
onSelect,
261+
setPreSelection,
262+
filterDate,
263+
});
264+
265+
const targetDate = new Date("2024-03-10T00:00:00");
266+
act(() => {
267+
instance?.handleMonthChange(targetDate);
268+
});
269+
270+
const [fallbackSelected] = onSelect.mock.calls[0];
271+
expect(isSameDay(fallbackSelected, targetDate)).toBe(true);
272+
const [fallbackPreSelection] = setPreSelection.mock.calls[0];
273+
expect(isSameDay(fallbackPreSelection, targetDate)).toBe(true);
274+
});
275+
224276
it("should open on openToDate date rather than selected date when both are specified", () => {
225277
const openToDate =
226278
parseDate("09/28/1993", DATE_FORMAT, undefined, false) ?? undefined;

src/test/click_outside_wrapper.test.tsx

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -148,6 +148,34 @@ describe("ClickOutsideWrapper", () => {
148148
expect(containerRef.current?.tagName).toBe("DIV");
149149
});
150150

151+
it("handles composedPath events (e.g. shadow DOM)", () => {
152+
render(
153+
<div>
154+
<ClickOutsideWrapper onClickOutside={onClickOutsideMock}>
155+
<div data-testid="inside">Inside</div>
156+
</ClickOutsideWrapper>
157+
</div>,
158+
);
159+
160+
const outsideNode = document.createElement("div");
161+
document.body.appendChild(outsideNode);
162+
163+
const event = new MouseEvent("mousedown", {
164+
bubbles: true,
165+
composed: true,
166+
});
167+
Object.defineProperty(event, "composed", { value: true });
168+
Object.defineProperty(event, "composedPath", {
169+
value: () => [outsideNode, document.body],
170+
});
171+
172+
outsideNode.dispatchEvent(event);
173+
174+
expect(onClickOutsideMock).toHaveBeenCalled();
175+
176+
document.body.removeChild(outsideNode);
177+
});
178+
151179
it("cleans up event listener on unmount", () => {
152180
const removeEventListenerSpy = jest.spyOn(document, "removeEventListener");
153181

Lines changed: 95 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,95 @@
1+
import { render, fireEvent } from "@testing-library/react";
2+
import React from "react";
3+
4+
import CustomInput from "./helper_components/custom_input";
5+
import CustomTimeInput from "./helper_components/custom_time_input";
6+
7+
describe("CustomInput", () => {
8+
it("should call onChange when input value changes", () => {
9+
const onChange = jest.fn();
10+
const { container } = render(<CustomInput onChange={onChange} />);
11+
12+
const input = container.querySelector("input") as HTMLInputElement;
13+
fireEvent.change(input, { target: { value: "test value" } });
14+
15+
// Line 22: onChange is called
16+
expect(onChange).toHaveBeenCalled();
17+
expect(onChange).toHaveBeenCalledWith(expect.any(Object), "test value");
18+
});
19+
20+
it("should handle onChange without onChangeArgs", () => {
21+
const onChange = jest.fn();
22+
const { container } = render(<CustomInput onChange={onChange} />);
23+
24+
const input = container.querySelector("input") as HTMLInputElement;
25+
fireEvent.change(input, { target: { value: "hello" } });
26+
27+
expect(onChange).toHaveBeenCalledWith(expect.any(Object), "hello");
28+
});
29+
30+
it("should use onChangeArgs when provided", () => {
31+
const onChange = jest.fn();
32+
const onChangeArgs = (
33+
event: React.ChangeEvent<HTMLInputElement>,
34+
): [React.ChangeEvent<HTMLInputElement>, string] => {
35+
return [event, `modified: ${event.target.value}`];
36+
};
37+
38+
const { container } = render(
39+
<CustomInput onChange={onChange} onChangeArgs={onChangeArgs} />,
40+
);
41+
42+
const input = container.querySelector("input") as HTMLInputElement;
43+
fireEvent.change(input, { target: { value: "test" } });
44+
45+
// Lines 19-20: onChangeArgs is used
46+
expect(onChange).toHaveBeenCalledWith(expect.any(Object), "modified: test");
47+
});
48+
49+
it("should not throw when onChange is not provided", () => {
50+
const { container } = render(<CustomInput />);
51+
52+
const input = container.querySelector("input") as HTMLInputElement;
53+
54+
expect(() =>
55+
fireEvent.change(input, { target: { value: "test" } }),
56+
).not.toThrow();
57+
});
58+
59+
it("should render input element", () => {
60+
const { container } = render(<CustomInput />);
61+
62+
const input = container.querySelector("input");
63+
expect(input).not.toBeNull();
64+
});
65+
});
66+
67+
describe("CustomTimeInput", () => {
68+
it("should call onChange when time input value changes", () => {
69+
const onChange = jest.fn();
70+
const { container } = render(<CustomTimeInput onChange={onChange} />);
71+
72+
const input = container.querySelector("input") as HTMLInputElement;
73+
fireEvent.change(input, { target: { value: "12:30" } });
74+
75+
// Line 20: onChange is called
76+
expect(onChange).toHaveBeenCalled();
77+
});
78+
79+
it("should not throw when onChange is not provided", () => {
80+
const { container } = render(<CustomTimeInput />);
81+
82+
const input = container.querySelector("input") as HTMLInputElement;
83+
84+
expect(() =>
85+
fireEvent.change(input, { target: { value: "10:00" } }),
86+
).not.toThrow();
87+
});
88+
89+
it("should render input element", () => {
90+
const { container } = render(<CustomTimeInput />);
91+
92+
const input = container.querySelector("input");
93+
expect(input).not.toBeNull();
94+
});
95+
});

0 commit comments

Comments
 (0)