import { screen, fireEvent, waitFor, act } from "@testing-library/react"; import userEvent from "@testing-library/user-event"; import { renderWithProviders } from "../utils/Renderwithproviders"; import RepeatEvent from "../../src/components/Event/EventRepeat"; import EventPopover from "../../src/features/Events/EventModal"; import { RepetitionObject } from "../../src/features/Events/EventsTypes"; import { DateSelectArg } from "@fullcalendar/core"; 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 preloadedState = { user: { userData: { sub: "test", email: "test@test.com", sid: "aiYbWZSk2g0F+LrQeD7Dg4QcUMR8R/zTZdZBiA7N6Ro", openpaasId: "667037022b752d0026472254", }, organiserData: { cn: "test", cal_address: "test@test.com", }, }, calendars: { list: { "667037022b752d0026472254/cal1": { id: "667037022b752d0026472254/cal1", name: "Calendar 1", color: "#FF0000", }, "667037022b752d0026472254/cal2": { id: "667037022b752d0026472254/cal2", name: "Calendar 2", color: "#00FF00", }, }, pending: false, }, }; const defaultSelectedRange = { startStr: "2025-07-18T09:00", endStr: "2025-07-18T10:00", start: new Date("2025-07-18T09:00"), end: new Date("2025-07-18T10:00"), allDay: false, resource: undefined, } as unknown as DateSelectArg; function setupRepeatEvent(props?: Partial, state?: any) { const setRepetition = jest.fn(); renderWithProviders( , 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( , 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"); 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( expect.objectContaining({ freq: "weekly" }) ); }); it("renders interval input when frequency is selected", () => { setupRepeatEvent({ freq: "daily", interval: 2 }); expect(screen.getByText(/interval/i)).toBeInTheDocument(); expect(screen.getByDisplayValue("2")).toBeInTheDocument(); }); it("updates interval value", () => { const { setRepetition } = setupRepeatEvent({ freq: "daily", interval: 1 }); const input = screen.getByDisplayValue("1"); fireEvent.change(input, { target: { value: "5" } }); expect(setRepetition).toHaveBeenCalledWith( expect.objectContaining({ interval: 5 }) ); }); it("toggles day selection for weekly frequency", () => { const { setRepetition } = setupRepeatEvent({ freq: "weekly", selectedDays: [], }); act(() => { const mondayCheckbox = screen.getByLabelText("MO"); fireEvent.click(mondayCheckbox); }); expect(setRepetition).toHaveBeenCalledWith( expect.objectContaining({ selectedDays: ["MO"] }) ); }); }); describe("Repeat Event API calls", () => { beforeEach(() => { jest.clearAllMocks(); jest.restoreAllMocks(); }); it("sends correct CalendarEvent payload", async () => { setupEventPopover(); userEvent.click(await screen.findByText(/repeat weekly/i)); const spy = jest .spyOn(eventThunks, "putEventAsync") .mockImplementation((payload) => () => Promise.resolve(payload) as any); act(() => fireEvent.click(screen.getByText("Save"))); await waitFor(() => expect(spy).toHaveBeenCalled()); const received = spy.mock.calls[0][0]; expect(received.cal).toEqual( preloadedState.calendars.list["667037022b752d0026472254/cal1"] ); expect(received.newEvent.title).toBe("Meeting"); expect( formatDateToYYYYMMDDTHHMMSS(received.newEvent.start).split("T")[0] ).toBe("20250718"); expect( formatDateToYYYYMMDDTHHMMSS(received.newEvent.end || new Date()).split( "T" )[0] ).toBe("20250719"); expect(received.newEvent.organizer).toEqual( preloadedState.user.organiserData ); expect(received.newEvent.repetition).toEqual({ freq: "weekly" }); expect(received.newEvent.color).toEqual( preloadedState.calendars.list["667037022b752d0026472254/cal1"].color ); expect(mockOnClose).toHaveBeenCalledWith({}, "backdropClick"); }); it("sends correct API payload for repeat daily", async () => { await setupEventPopover(); userEvent.click(await screen.findByText(/repeat daily/i)); await expectRRule({ freq: "daily" }); expect(mockOnClose).toHaveBeenCalledWith({}, "backdropClick"); }); it("sends correct API payload for repeat daily with 2 day interval", async () => { await setupEventPopover(); userEvent.click(await screen.findByText(/repeat daily/i)); const intervalInput = screen.getByDisplayValue("1"); fireEvent.change(intervalInput, { target: { value: "2" } }); await expectRRule({ freq: "daily", interval: 2 }); expect(mockOnClose).toHaveBeenCalledWith({}, "backdropClick"); }); it("sends correct API payload for repeat daily for 5 repetitions", async () => { await setupEventPopover(); userEvent.click(await screen.findByText(/repeat daily/i)); userEvent.click(screen.getByLabelText(/after/i)); 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 Friday", async () => { await setupEventPopover(); userEvent.click(await screen.findByText(/repeat weekly/i)); userEvent.click(screen.getByLabelText("TH")); userEvent.click(screen.getByLabelText("FR")); await expectRRule({ freq: "weekly", byday: ["TH", "FR"] }); expect(mockOnClose).toHaveBeenCalledWith({}, "backdropClick"); }); it("sends correct API payload for repeat weekly on Thursday and Friday and an interval of 3 weeks", async () => { await setupEventPopover(); userEvent.click(await screen.findByText(/repeat weekly/i)); userEvent.click(screen.getByLabelText("TH")); userEvent.click(screen.getByLabelText("FR")); 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"); }); });