Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
103 changes: 103 additions & 0 deletions src/test/calendar_icon.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -86,4 +86,107 @@ describe("CalendarIcon", () => {
expect(onClickMock).toHaveBeenCalledTimes(1);
expect(onClickCustomIcon).toHaveBeenCalledTimes(1);
});

it("should fire only custom icon onClick when CalendarIcon onClick is not provided", () => {
const onClickCustomIcon = jest.fn();

const { container } = render(
<CalendarIcon
icon={
<svg
xmlns="http://www.w3.org/2000/svg"
width="1em"
height="1em"
viewBox="0 0 48 48"
onClick={onClickCustomIcon}
/>
}
/>,
);

const icon = safeQuerySelector(
container,
"svg.react-datepicker__calendar-icon",
);
fireEvent.click(icon);

// Lines 55-57: custom icon onClick is called
expect(onClickCustomIcon).toHaveBeenCalledTimes(1);
});

it("should fire only CalendarIcon onClick when custom icon onClick is not provided", () => {
const { container } = render(
<CalendarIcon
icon={
<svg
xmlns="http://www.w3.org/2000/svg"
width="1em"
height="1em"
viewBox="0 0 48 48"
/>
}
onClick={onClickMock}
/>,
);

const icon = safeQuerySelector(
container,
"svg.react-datepicker__calendar-icon",
);
fireEvent.click(icon);

// Lines 59-61: CalendarIcon onClick is called
expect(onClickMock).toHaveBeenCalledTimes(1);
});

it("should handle custom icon without onClick prop", () => {
const { container } = render(
<CalendarIcon
icon={
<svg
xmlns="http://www.w3.org/2000/svg"
width="1em"
height="1em"
viewBox="0 0 48 48"
/>
}
/>,
);

const icon = safeQuerySelector(
container,
"svg.react-datepicker__calendar-icon",
);

// Should not throw when clicking without any onClick handlers
expect(() => fireEvent.click(icon)).not.toThrow();
});

it("should apply className to custom icon", () => {
const { container } = render(
<CalendarIcon
icon={<IconParkSolidApplication />}
className="custom-class"
/>,
);

const icon = container.querySelector(".custom-class");
expect(icon).not.toBeNull();
});

it("should apply className to string icon", () => {
const { container } = render(
<CalendarIcon icon="fa-calendar" className="custom-class" />,
);

const icon = container.querySelector("i.custom-class");
expect(icon).not.toBeNull();
});

it("should apply className to default SVG icon", () => {
const { container } = render(<CalendarIcon className="custom-class" />);

const icon = container.querySelector("svg.custom-class");
expect(icon).not.toBeNull();
});
});
52 changes: 52 additions & 0 deletions src/test/calendar_test.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -221,6 +221,58 @@ describe("Calendar", () => {
expect(isSameDay(instance?.state.date, openToDate)).toBeTruthy();
});

it("should move pre-selection to first enabled day when month changes", () => {
const onSelect = jest.fn();
const setOpen = jest.fn();
const setPreSelection = jest.fn();
const filterDate = (date: Date) => date.getDate() >= 3;

const { instance } = getCalendar({
adjustDateOnChange: true,
onSelect,
setOpen,
setPreSelection,
filterDate,
selected: new Date("2024-01-15T00:00:00"),
});

const targetMonth = new Date("2024-02-01T00:00:00");
act(() => {
instance?.handleMonthChange(targetMonth);
});

const expectedDate = new Date("2024-02-03T00:00:00");
const [selectedDate] = onSelect.mock.calls[0];
expect(isSameDay(selectedDate, expectedDate)).toBe(true);
expect(setOpen).toHaveBeenCalledWith(true);
const [preSelectionDate] = setPreSelection.mock.calls[0];
expect(isSameDay(preSelectionDate, expectedDate)).toBe(true);
expect(instance?.state.isRenderAriaLiveMessage).toBe(true);
});

it("should fall back to provided month date when no enabled days exist", () => {
const onSelect = jest.fn();
const setPreSelection = jest.fn();
const filterDate = () => false;

const { instance } = getCalendar({
adjustDateOnChange: true,
onSelect,
setPreSelection,
filterDate,
});

const targetDate = new Date("2024-03-10T00:00:00");
act(() => {
instance?.handleMonthChange(targetDate);
});

const [fallbackSelected] = onSelect.mock.calls[0];
expect(isSameDay(fallbackSelected, targetDate)).toBe(true);
const [fallbackPreSelection] = setPreSelection.mock.calls[0];
expect(isSameDay(fallbackPreSelection, targetDate)).toBe(true);
});

it("should open on openToDate date rather than selected date when both are specified", () => {
const openToDate =
parseDate("09/28/1993", DATE_FORMAT, undefined, false) ?? undefined;
Expand Down
28 changes: 28 additions & 0 deletions src/test/click_outside_wrapper.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -148,6 +148,34 @@ describe("ClickOutsideWrapper", () => {
expect(containerRef.current?.tagName).toBe("DIV");
});

it("handles composedPath events (e.g. shadow DOM)", () => {
render(
<div>
<ClickOutsideWrapper onClickOutside={onClickOutsideMock}>
<div data-testid="inside">Inside</div>
</ClickOutsideWrapper>
</div>,
);

const outsideNode = document.createElement("div");
document.body.appendChild(outsideNode);

const event = new MouseEvent("mousedown", {
bubbles: true,
composed: true,
});
Object.defineProperty(event, "composed", { value: true });
Object.defineProperty(event, "composedPath", {
value: () => [outsideNode, document.body],
});

outsideNode.dispatchEvent(event);

expect(onClickOutsideMock).toHaveBeenCalled();

document.body.removeChild(outsideNode);
});

it("cleans up event listener on unmount", () => {
const removeEventListenerSpy = jest.spyOn(document, "removeEventListener");

Expand Down
95 changes: 95 additions & 0 deletions src/test/custom_components.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
import { render, fireEvent } from "@testing-library/react";
import React from "react";

import CustomInput from "./helper_components/custom_input";
import CustomTimeInput from "./helper_components/custom_time_input";

describe("CustomInput", () => {
it("should call onChange when input value changes", () => {
const onChange = jest.fn();
const { container } = render(<CustomInput onChange={onChange} />);

const input = container.querySelector("input") as HTMLInputElement;
fireEvent.change(input, { target: { value: "test value" } });

// Line 22: onChange is called
expect(onChange).toHaveBeenCalled();
expect(onChange).toHaveBeenCalledWith(expect.any(Object), "test value");
});

it("should handle onChange without onChangeArgs", () => {
const onChange = jest.fn();
const { container } = render(<CustomInput onChange={onChange} />);

const input = container.querySelector("input") as HTMLInputElement;
fireEvent.change(input, { target: { value: "hello" } });

expect(onChange).toHaveBeenCalledWith(expect.any(Object), "hello");
});

it("should use onChangeArgs when provided", () => {
const onChange = jest.fn();
const onChangeArgs = (
event: React.ChangeEvent<HTMLInputElement>,
): [React.ChangeEvent<HTMLInputElement>, string] => {
return [event, `modified: ${event.target.value}`];
};

const { container } = render(
<CustomInput onChange={onChange} onChangeArgs={onChangeArgs} />,
);

const input = container.querySelector("input") as HTMLInputElement;
fireEvent.change(input, { target: { value: "test" } });

// Lines 19-20: onChangeArgs is used
expect(onChange).toHaveBeenCalledWith(expect.any(Object), "modified: test");
});

it("should not throw when onChange is not provided", () => {
const { container } = render(<CustomInput />);

const input = container.querySelector("input") as HTMLInputElement;

expect(() =>
fireEvent.change(input, { target: { value: "test" } }),
).not.toThrow();
});

it("should render input element", () => {
const { container } = render(<CustomInput />);

const input = container.querySelector("input");
expect(input).not.toBeNull();
});
});

describe("CustomTimeInput", () => {
it("should call onChange when time input value changes", () => {
const onChange = jest.fn();
const { container } = render(<CustomTimeInput onChange={onChange} />);

const input = container.querySelector("input") as HTMLInputElement;
fireEvent.change(input, { target: { value: "12:30" } });

// Line 20: onChange is called
expect(onChange).toHaveBeenCalled();
});

it("should not throw when onChange is not provided", () => {
const { container } = render(<CustomTimeInput />);

const input = container.querySelector("input") as HTMLInputElement;

expect(() =>
fireEvent.change(input, { target: { value: "10:00" } }),
).not.toThrow();
});

it("should render input element", () => {
const { container } = render(<CustomTimeInput />);

const input = container.querySelector("input");
expect(input).not.toBeNull();
});
});
Loading
Loading