[#72] added tests
This commit is contained in:
@@ -1,26 +1,11 @@
|
|||||||
import { screen, fireEvent, waitFor, act } from "@testing-library/react";
|
import { fireEvent, screen } from "@testing-library/react";
|
||||||
import userEvent from "@testing-library/user-event";
|
import EventDuplication from "../../src/components/Event/EventDuplicate";
|
||||||
import { renderWithProviders } from "../utils/Renderwithproviders";
|
import EventDisplayModal from "../../src/features/Events/EventDisplay";
|
||||||
import RepeatEvent from "../../src/components/Event/EventRepeat";
|
|
||||||
import EventPopover from "../../src/features/Events/EventModal";
|
import EventPopover from "../../src/features/Events/EventModal";
|
||||||
import { RepetitionObject } from "../../src/features/Events/EventsTypes";
|
import { renderWithProviders } from "../utils/Renderwithproviders";
|
||||||
import { DateSelectArg } from "@fullcalendar/core";
|
import preview from "jest-preview";
|
||||||
import { formatDateToYYYYMMDDTHHMMSS } from "../../src/utils/dateUtils";
|
|
||||||
import * as eventThunks from "../../src/features/Calendars/CalendarSlice";
|
|
||||||
import * as apiUtils from "../../src/utils/apiUtils";
|
|
||||||
|
|
||||||
const baseRepetition: RepetitionObject = {
|
|
||||||
freq: "",
|
|
||||||
interval: 1,
|
|
||||||
occurrences: 0,
|
|
||||||
endDate: "",
|
|
||||||
selectedDays: [],
|
|
||||||
};
|
|
||||||
|
|
||||||
const mockOnClose = jest.fn();
|
|
||||||
const mockSetSelectedRange = jest.fn();
|
|
||||||
const mockCalendarRef = { current: { select: jest.fn() } } as any;
|
|
||||||
|
|
||||||
|
const day = new Date();
|
||||||
const preloadedState = {
|
const preloadedState = {
|
||||||
user: {
|
user: {
|
||||||
userData: {
|
userData: {
|
||||||
@@ -31,336 +16,161 @@ const preloadedState = {
|
|||||||
},
|
},
|
||||||
organiserData: {
|
organiserData: {
|
||||||
cn: "test",
|
cn: "test",
|
||||||
cal_address: "test@test.com",
|
cal_address: "mailto:test@test.com",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
calendars: {
|
calendars: {
|
||||||
list: {
|
list: {
|
||||||
"667037022b752d0026472254/cal1": {
|
"667037022b752d0026472254/cal1": {
|
||||||
id: "667037022b752d0026472254/cal1",
|
id: "667037022b752d0026472254/cal1",
|
||||||
name: "Calendar 1",
|
name: "Calendar",
|
||||||
color: "#FF0000",
|
color: "#FF0000",
|
||||||
},
|
events: {
|
||||||
"667037022b752d0026472254/cal2": {
|
event1: {
|
||||||
id: "667037022b752d0026472254/cal2",
|
uid: "event1",
|
||||||
name: "Calendar 2",
|
URL: "calendars/667037022b752d0026472254/cal1/event1.ics",
|
||||||
color: "#00FF00",
|
title: "Test Event",
|
||||||
|
calId: "667037022b752d0026472254/cal1",
|
||||||
|
start: day,
|
||||||
|
end: day,
|
||||||
|
timezone: "UTC",
|
||||||
|
organizer: { cn: "test", cal_address: "test@test.com" },
|
||||||
|
attendee: [
|
||||||
|
{
|
||||||
|
cn: "test",
|
||||||
|
cal_address: "test@test.com",
|
||||||
|
partstat: "NEEDS-ACTION",
|
||||||
|
rsvp: "TRUE",
|
||||||
|
role: "REQ-PARTICIPANT",
|
||||||
|
cutype: "INDIVIDUAL",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
cn: "John",
|
||||||
|
cal_address: "john@test.com",
|
||||||
|
partstat: "NEEDS-ACTION",
|
||||||
|
rsvp: "TRUE",
|
||||||
|
role: "REQ-PARTICIPANT",
|
||||||
|
cutype: "INDIVIDUAL",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
pending: false,
|
pending: false,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
const defaultSelectedRange = {
|
describe("EventDuplication", () => {
|
||||||
startStr: "2025-07-18T09:00",
|
it("opens EventPopover when button clicked", () => {
|
||||||
endStr: "2025-07-18T10:00",
|
const handleClose = jest.fn();
|
||||||
start: new Date("2025-07-18T09:00"),
|
renderWithProviders(
|
||||||
end: new Date("2025-07-18T10:00"),
|
<EventDuplication
|
||||||
allDay: false,
|
event={
|
||||||
resource: undefined,
|
preloadedState.calendars.list["667037022b752d0026472254/cal1"].events
|
||||||
} as unknown as DateSelectArg;
|
.event1
|
||||||
|
}
|
||||||
function setupRepeatEvent(props?: Partial<RepetitionObject>, state?: any) {
|
onClose={handleClose}
|
||||||
const setRepetition = jest.fn();
|
/>,
|
||||||
renderWithProviders(
|
preloadedState
|
||||||
<RepeatEvent
|
|
||||||
repetition={{ ...baseRepetition, ...props }}
|
|
||||||
eventStart={defaultSelectedRange.start}
|
|
||||||
setRepetition={setRepetition}
|
|
||||||
isOwn={true}
|
|
||||||
/>,
|
|
||||||
state
|
|
||||||
);
|
|
||||||
return { setRepetition };
|
|
||||||
}
|
|
||||||
|
|
||||||
async function setupEventPopover(
|
|
||||||
overrides?: Partial<{ start: string; end: string }>
|
|
||||||
) {
|
|
||||||
jest
|
|
||||||
.spyOn(crypto, "randomUUID")
|
|
||||||
.mockReturnValue("fixed-uuid-with-correct-format");
|
|
||||||
const originalDateResolvedOptions =
|
|
||||||
new Intl.DateTimeFormat().resolvedOptions();
|
|
||||||
jest.spyOn(Intl.DateTimeFormat.prototype, "resolvedOptions").mockReturnValue({
|
|
||||||
...originalDateResolvedOptions,
|
|
||||||
timeZone: "UTC",
|
|
||||||
});
|
|
||||||
|
|
||||||
renderWithProviders(
|
|
||||||
<EventPopover
|
|
||||||
anchorEl={document.body}
|
|
||||||
open={true}
|
|
||||||
onClose={mockOnClose}
|
|
||||||
selectedRange={defaultSelectedRange}
|
|
||||||
setSelectedRange={mockSetSelectedRange}
|
|
||||||
calendarRef={mockCalendarRef}
|
|
||||||
/>,
|
|
||||||
preloadedState
|
|
||||||
);
|
|
||||||
act(() => {
|
|
||||||
fireEvent.change(screen.getByLabelText("Title"), {
|
|
||||||
target: { value: "Meeting" },
|
|
||||||
});
|
|
||||||
fireEvent.click(screen.getByLabelText("All day"));
|
|
||||||
fireEvent.change(screen.getByLabelText("Start"), {
|
|
||||||
target: {
|
|
||||||
value: (overrides?.start ?? "2025-07-18T00:00:00.000Z").split("T")[0],
|
|
||||||
},
|
|
||||||
});
|
|
||||||
fireEvent.change(screen.getByLabelText("End"), {
|
|
||||||
target: {
|
|
||||||
value: (overrides?.end ?? "2025-07-19T00:00:00.000Z").split("T")[0],
|
|
||||||
},
|
|
||||||
});
|
|
||||||
fireEvent.click(screen.getByText("Show More"));
|
|
||||||
});
|
|
||||||
const select = screen.getByLabelText(/repetition/i);
|
|
||||||
userEvent.click(select);
|
|
||||||
|
|
||||||
return jest.spyOn(apiUtils, "api");
|
|
||||||
}
|
|
||||||
|
|
||||||
async function expectRRule(expected: any) {
|
|
||||||
const spyAPi = jest.spyOn(apiUtils, "api");
|
|
||||||
|
|
||||||
act(() => fireEvent.click(screen.getByText("Save")));
|
|
||||||
|
|
||||||
await waitFor(() => {
|
|
||||||
expect(spyAPi).toHaveBeenCalled();
|
|
||||||
});
|
|
||||||
|
|
||||||
const receivedPayload: string =
|
|
||||||
spyAPi.mock.calls[0][1]?.body?.toString() ?? "";
|
|
||||||
const [, , [vevent]] = JSON.parse(receivedPayload);
|
|
||||||
const rrule = vevent[1].find(([name]: any) => name === "rrule");
|
|
||||||
|
|
||||||
if (rrule[3].byday) {
|
|
||||||
expect({
|
|
||||||
...rrule[3],
|
|
||||||
byday: rrule[3].byday.sort(),
|
|
||||||
}).toEqual({
|
|
||||||
...expected,
|
|
||||||
byday: expected.byday.sort(),
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
expect(rrule[3]).toEqual(expected);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
describe("RepeatEvent", () => {
|
|
||||||
it("renders with no repetition by default", () => {
|
|
||||||
setupRepeatEvent();
|
|
||||||
expect(screen.getByLabelText(/repetition/i)).toBeInTheDocument();
|
|
||||||
expect(screen.queryByText(/daily/i)).not.toBeInTheDocument();
|
|
||||||
});
|
|
||||||
|
|
||||||
it("allows selecting repetition frequency", async () => {
|
|
||||||
const { setRepetition } = setupRepeatEvent();
|
|
||||||
const select = screen.getByLabelText(/repetition/i);
|
|
||||||
act(() => {
|
|
||||||
userEvent.click(select);
|
|
||||||
});
|
|
||||||
await waitFor(async () =>
|
|
||||||
userEvent.click(await screen.findByText(/repeat weekly/i))
|
|
||||||
);
|
);
|
||||||
|
|
||||||
expect(setRepetition).toHaveBeenCalledWith(
|
const button = screen.getByRole("button");
|
||||||
expect.objectContaining({ freq: "weekly" })
|
fireEvent.click(button);
|
||||||
);
|
|
||||||
|
expect(screen.getByText(/Duplicate Event/i)).toBeInTheDocument();
|
||||||
});
|
});
|
||||||
|
|
||||||
it("renders interval input when frequency is selected", () => {
|
it("calls onClose when closing the popover", () => {
|
||||||
setupRepeatEvent({ freq: "daily", interval: 2 });
|
const handleClose = jest.fn();
|
||||||
expect(screen.getByText(/interval/i)).toBeInTheDocument();
|
renderWithProviders(
|
||||||
expect(screen.getByDisplayValue("2")).toBeInTheDocument();
|
<EventDuplication
|
||||||
});
|
event={
|
||||||
|
preloadedState.calendars.list["667037022b752d0026472254/cal1"].events
|
||||||
it("updates interval value", () => {
|
.event1
|
||||||
const { setRepetition } = setupRepeatEvent({ freq: "daily", interval: 1 });
|
}
|
||||||
const input = screen.getByDisplayValue("1");
|
onClose={handleClose}
|
||||||
fireEvent.change(input, { target: { value: "5" } });
|
/>,
|
||||||
expect(setRepetition).toHaveBeenCalledWith(
|
preloadedState
|
||||||
expect.objectContaining({ interval: 5 })
|
|
||||||
);
|
);
|
||||||
});
|
|
||||||
|
|
||||||
it("toggles day selection for weekly frequency", () => {
|
fireEvent.click(screen.getByRole("button"));
|
||||||
const { setRepetition } = setupRepeatEvent({
|
|
||||||
freq: "weekly",
|
fireEvent.click(screen.getByRole("button", { name: /Cancel/i }));
|
||||||
selectedDays: [],
|
|
||||||
});
|
expect(handleClose).toHaveBeenCalled();
|
||||||
act(() => {
|
|
||||||
const mondayCheckbox = screen.getByLabelText("MO");
|
|
||||||
fireEvent.click(mondayCheckbox);
|
|
||||||
});
|
|
||||||
expect(setRepetition).toHaveBeenCalledWith(
|
|
||||||
expect.objectContaining({ selectedDays: ["MO"] })
|
|
||||||
);
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe("Repeat Event API calls", () => {
|
describe("EventPopover", () => {
|
||||||
beforeEach(() => {
|
it("renders with event data", () => {
|
||||||
jest.clearAllMocks();
|
renderWithProviders(
|
||||||
jest.restoreAllMocks();
|
<EventPopover
|
||||||
});
|
anchorEl={null}
|
||||||
|
open={true}
|
||||||
it("sends correct CalendarEvent payload", async () => {
|
onClose={jest.fn()}
|
||||||
setupEventPopover();
|
selectedRange={null}
|
||||||
|
setSelectedRange={jest.fn()}
|
||||||
userEvent.click(await screen.findByText(/repeat weekly/i));
|
calendarRef={{ current: null }}
|
||||||
|
event={
|
||||||
const spy = jest
|
preloadedState.calendars.list["667037022b752d0026472254/cal1"].events
|
||||||
.spyOn(eventThunks, "putEventAsync")
|
.event1
|
||||||
.mockImplementation((payload) => () => Promise.resolve(payload) as any);
|
}
|
||||||
act(() => fireEvent.click(screen.getByText("Save")));
|
/>,
|
||||||
await waitFor(() => expect(spy).toHaveBeenCalled());
|
preloadedState
|
||||||
|
|
||||||
const received = spy.mock.calls[0][0];
|
|
||||||
expect(received.cal).toEqual(
|
|
||||||
preloadedState.calendars.list["667037022b752d0026472254/cal1"]
|
|
||||||
);
|
);
|
||||||
expect(received.newEvent.title).toBe("Meeting");
|
|
||||||
expect(
|
expect(screen.getByDisplayValue(/Test Event/i)).toBeInTheDocument();
|
||||||
formatDateToYYYYMMDDTHHMMSS(received.newEvent.start).split("T")[0]
|
});
|
||||||
).toBe("20250718");
|
|
||||||
expect(
|
it("saves duplicated event when Save is clicked", () => {
|
||||||
formatDateToYYYYMMDDTHHMMSS(received.newEvent.end || new Date()).split(
|
const onClose = jest.fn();
|
||||||
"T"
|
|
||||||
)[0]
|
renderWithProviders(
|
||||||
).toBe("20250719");
|
<EventPopover
|
||||||
expect(received.newEvent.organizer).toEqual(
|
anchorEl={null}
|
||||||
preloadedState.user.organiserData
|
open={true}
|
||||||
|
onClose={onClose}
|
||||||
|
selectedRange={null}
|
||||||
|
setSelectedRange={jest.fn()}
|
||||||
|
calendarRef={{ current: null }}
|
||||||
|
event={
|
||||||
|
preloadedState.calendars.list["667037022b752d0026472254/cal1"].events
|
||||||
|
.event1
|
||||||
|
}
|
||||||
|
/>,
|
||||||
|
preloadedState
|
||||||
);
|
);
|
||||||
const day = new Date(received.newEvent.start)
|
|
||||||
.toLocaleString("en-UK", {
|
|
||||||
weekday: "short",
|
|
||||||
})
|
|
||||||
.slice(0, 2)
|
|
||||||
.toUpperCase();
|
|
||||||
|
|
||||||
expect(received.newEvent.repetition).toEqual({
|
fireEvent.change(screen.getByLabelText(/Title/i), {
|
||||||
freq: "weekly",
|
target: { value: "Duplicated Event" },
|
||||||
selectedDays: [day],
|
|
||||||
});
|
});
|
||||||
expect(received.newEvent.color).toEqual(
|
fireEvent.click(screen.getByRole("button", { name: /Save/i }));
|
||||||
preloadedState.calendars.list["667037022b752d0026472254/cal1"].color
|
|
||||||
);
|
|
||||||
expect(mockOnClose).toHaveBeenCalledWith({}, "backdropClick");
|
|
||||||
});
|
|
||||||
|
|
||||||
it("sends correct API payload for repeat daily", async () => {
|
expect(onClose).toHaveBeenCalled();
|
||||||
await setupEventPopover();
|
});
|
||||||
|
});
|
||||||
userEvent.click(await screen.findByText(/repeat daily/i));
|
|
||||||
|
describe("EventDisplayModal", () => {
|
||||||
await expectRRule({ freq: "daily" });
|
it("shows duplication button and opens duplication form", () => {
|
||||||
expect(mockOnClose).toHaveBeenCalledWith({}, "backdropClick");
|
renderWithProviders(
|
||||||
});
|
<EventDisplayModal
|
||||||
|
eventId="event1"
|
||||||
it("sends correct API payload for repeat daily with 2 day interval", async () => {
|
calId="667037022b752d0026472254/cal1"
|
||||||
await setupEventPopover();
|
open={true}
|
||||||
|
onClose={jest.fn()}
|
||||||
userEvent.click(await screen.findByText(/repeat daily/i));
|
/>,
|
||||||
const intervalInput = screen.getByDisplayValue("1");
|
preloadedState
|
||||||
fireEvent.change(intervalInput, { target: { value: "2" } });
|
);
|
||||||
|
|
||||||
await expectRRule({ freq: "daily", interval: 2 });
|
fireEvent.click(screen.getByTestId("AddToPhotosIcon"));
|
||||||
expect(mockOnClose).toHaveBeenCalledWith({}, "backdropClick");
|
preview.debug();
|
||||||
});
|
expect(screen.getByText(/Duplicate Event/i)).toBeInTheDocument();
|
||||||
|
expect(
|
||||||
it("sends correct API payload for repeat daily for 5 repetitions", async () => {
|
screen.getAllByDisplayValue(
|
||||||
await setupEventPopover();
|
preloadedState.calendars.list["667037022b752d0026472254/cal1"].events
|
||||||
|
.event1.title
|
||||||
userEvent.click(await screen.findByText(/repeat daily/i));
|
)
|
||||||
userEvent.click(screen.getByLabelText(/after/i));
|
).toHaveLength(2);
|
||||||
const input = screen.getAllByRole("spinbutton")[1];
|
|
||||||
fireEvent.change(input, { target: { value: "5" } });
|
|
||||||
|
|
||||||
await expectRRule({ freq: "daily", count: 5 });
|
|
||||||
expect(mockOnClose).toHaveBeenCalledWith({}, "backdropClick");
|
|
||||||
});
|
|
||||||
|
|
||||||
it("sends correct API payload for repeat daily until now+5days", async () => {
|
|
||||||
await setupEventPopover();
|
|
||||||
|
|
||||||
userEvent.click(await screen.findByText(/repeat daily/i));
|
|
||||||
userEvent.click(screen.getAllByLabelText(/on/i)[3]);
|
|
||||||
|
|
||||||
const untilInput = screen.getByTestId("end-date");
|
|
||||||
const futureDate = new Date();
|
|
||||||
futureDate.setDate(futureDate.getDate() + 5);
|
|
||||||
|
|
||||||
fireEvent.change(untilInput, {
|
|
||||||
target: { value: futureDate.toISOString().split("T")[0] },
|
|
||||||
});
|
|
||||||
await expectRRule({
|
|
||||||
freq: "daily",
|
|
||||||
until: futureDate.toISOString().split("T")[0],
|
|
||||||
});
|
|
||||||
expect(mockOnClose).toHaveBeenCalledWith({}, "backdropClick");
|
|
||||||
});
|
|
||||||
|
|
||||||
it("sends correct API payload for repeat weekly on Thursday and event day (Friday)", async () => {
|
|
||||||
await setupEventPopover();
|
|
||||||
|
|
||||||
userEvent.click(await screen.findByText(/repeat weekly/i));
|
|
||||||
userEvent.click(screen.getByLabelText("TH"));
|
|
||||||
|
|
||||||
await expectRRule({ freq: "weekly", byday: ["TH", "FR"] });
|
|
||||||
expect(mockOnClose).toHaveBeenCalledWith({}, "backdropClick");
|
|
||||||
});
|
|
||||||
it("sends correct API payload for repeat weekly on Thursday and event day (Friday) and an interval of 3 weeks", async () => {
|
|
||||||
await setupEventPopover();
|
|
||||||
|
|
||||||
userEvent.click(await screen.findByText(/repeat weekly/i));
|
|
||||||
|
|
||||||
userEvent.click(screen.getByLabelText("TH"));
|
|
||||||
|
|
||||||
const intervalInput = screen.getByDisplayValue("1");
|
|
||||||
fireEvent.change(intervalInput, { target: { value: "3" } });
|
|
||||||
|
|
||||||
await expectRRule({ freq: "weekly", byday: ["TH", "FR"], interval: 3 });
|
|
||||||
expect(mockOnClose).toHaveBeenCalledWith({}, "backdropClick");
|
|
||||||
});
|
|
||||||
it("sends correct API payload for repeat monthly", async () => {
|
|
||||||
await setupEventPopover();
|
|
||||||
|
|
||||||
userEvent.click(await screen.findByText(/repeat monthly/i));
|
|
||||||
|
|
||||||
await expectRRule({ freq: "monthly" });
|
|
||||||
expect(mockOnClose).toHaveBeenCalledWith({}, "backdropClick");
|
|
||||||
});
|
|
||||||
it("sends correct API payload for repeat monthly and end after 5 occurrences", async () => {
|
|
||||||
await setupEventPopover();
|
|
||||||
|
|
||||||
userEvent.click(await screen.findByText(/repeat monthly/i));
|
|
||||||
userEvent.click(screen.getByLabelText(/after/i));
|
|
||||||
const input = screen.getAllByRole("spinbutton")[1];
|
|
||||||
fireEvent.change(input, { target: { value: "5" } });
|
|
||||||
await expectRRule({ freq: "monthly", count: 5 });
|
|
||||||
expect(mockOnClose).toHaveBeenCalledWith({}, "backdropClick");
|
|
||||||
});
|
|
||||||
it("sends correct API payload for repeat yearly", async () => {
|
|
||||||
await setupEventPopover();
|
|
||||||
|
|
||||||
userEvent.click(await screen.findByText(/repeat yearly/i));
|
|
||||||
|
|
||||||
await expectRRule({ freq: "yearly" });
|
|
||||||
expect(mockOnClose).toHaveBeenCalledWith({}, "backdropClick");
|
|
||||||
});
|
|
||||||
|
|
||||||
it("sends correct API payload for repeat yearly, but user first choose to end after 5 occurrences then changed mind and chose to not end", async () => {
|
|
||||||
await setupEventPopover();
|
|
||||||
|
|
||||||
userEvent.click(await screen.findByText(/repeat yearly/i));
|
|
||||||
userEvent.click(screen.getByLabelText(/after/i));
|
|
||||||
const input = screen.getAllByRole("spinbutton")[1];
|
|
||||||
fireEvent.change(input, { target: { value: "5" } });
|
|
||||||
userEvent.click(screen.getByLabelText(/never/i));
|
|
||||||
|
|
||||||
await expectRRule({ freq: "yearly" });
|
|
||||||
expect(mockOnClose).toHaveBeenCalledWith({}, "backdropClick");
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -22,7 +22,7 @@ export interface CalendarEvent {
|
|||||||
status?: string;
|
status?: string;
|
||||||
timezone: string;
|
timezone: string;
|
||||||
repetition?: RepetitionObject;
|
repetition?: RepetitionObject;
|
||||||
alarm: AlarmObject;
|
alarm?: AlarmObject;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface RepetitionObject {
|
export interface RepetitionObject {
|
||||||
|
|||||||
Reference in New Issue
Block a user