diff --git a/__test__/components/DuplicateEvent.test.tsx b/__test__/components/DuplicateEvent.test.tsx index 0a89775..6d25407 100644 --- a/__test__/components/DuplicateEvent.test.tsx +++ b/__test__/components/DuplicateEvent.test.tsx @@ -1,26 +1,11 @@ -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 { fireEvent, screen } from "@testing-library/react"; +import EventDuplication from "../../src/components/Event/EventDuplicate"; +import EventDisplayModal from "../../src/features/Events/EventDisplay"; 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; +import { renderWithProviders } from "../utils/Renderwithproviders"; +import preview from "jest-preview"; +const day = new Date(); const preloadedState = { user: { userData: { @@ -31,336 +16,161 @@ const preloadedState = { }, organiserData: { cn: "test", - cal_address: "test@test.com", + cal_address: "mailto:test@test.com", }, }, calendars: { list: { "667037022b752d0026472254/cal1": { id: "667037022b752d0026472254/cal1", - name: "Calendar 1", + name: "Calendar", color: "#FF0000", - }, - "667037022b752d0026472254/cal2": { - id: "667037022b752d0026472254/cal2", - name: "Calendar 2", - color: "#00FF00", + events: { + event1: { + uid: "event1", + URL: "calendars/667037022b752d0026472254/cal1/event1.ics", + 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, }, }; -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"); - - 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)) +describe("EventDuplication", () => { + it("opens EventPopover when button clicked", () => { + const handleClose = jest.fn(); + renderWithProviders( + , + preloadedState ); - expect(setRepetition).toHaveBeenCalledWith( - expect.objectContaining({ freq: "weekly" }) - ); + const button = screen.getByRole("button"); + fireEvent.click(button); + + expect(screen.getByText(/Duplicate Event/i)).toBeInTheDocument(); }); - 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("calls onClose when closing the popover", () => { + const handleClose = jest.fn(); + renderWithProviders( + , + preloadedState ); - }); - 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"] }) - ); + fireEvent.click(screen.getByRole("button")); + + fireEvent.click(screen.getByRole("button", { name: /Cancel/i })); + + expect(handleClose).toHaveBeenCalled(); }); }); -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"] +describe("EventPopover", () => { + it("renders with event data", () => { + renderWithProviders( + , + preloadedState ); - 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(screen.getByDisplayValue(/Test Event/i)).toBeInTheDocument(); + }); + + it("saves duplicated event when Save is clicked", () => { + const onClose = jest.fn(); + + renderWithProviders( + , + preloadedState ); - const day = new Date(received.newEvent.start) - .toLocaleString("en-UK", { - weekday: "short", - }) - .slice(0, 2) - .toUpperCase(); - expect(received.newEvent.repetition).toEqual({ - freq: "weekly", - selectedDays: [day], + fireEvent.change(screen.getByLabelText(/Title/i), { + target: { value: "Duplicated Event" }, }); - expect(received.newEvent.color).toEqual( - preloadedState.calendars.list["667037022b752d0026472254/cal1"].color - ); - expect(mockOnClose).toHaveBeenCalledWith({}, "backdropClick"); - }); + fireEvent.click(screen.getByRole("button", { name: /Save/i })); - 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 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"); + expect(onClose).toHaveBeenCalled(); + }); +}); + +describe("EventDisplayModal", () => { + it("shows duplication button and opens duplication form", () => { + renderWithProviders( + , + preloadedState + ); + + fireEvent.click(screen.getByTestId("AddToPhotosIcon")); + preview.debug(); + expect(screen.getByText(/Duplicate Event/i)).toBeInTheDocument(); + expect( + screen.getAllByDisplayValue( + preloadedState.calendars.list["667037022b752d0026472254/cal1"].events + .event1.title + ) + ).toHaveLength(2); }); }); diff --git a/src/features/Events/EventsTypes.ts b/src/features/Events/EventsTypes.ts index 93df014..94d1ee8 100644 --- a/src/features/Events/EventsTypes.ts +++ b/src/features/Events/EventsTypes.ts @@ -22,7 +22,7 @@ export interface CalendarEvent { status?: string; timezone: string; repetition?: RepetitionObject; - alarm: AlarmObject; + alarm?: AlarmObject; } export interface RepetitionObject {