diff --git a/__test__/components/DuplicateEvent.test.tsx b/__test__/components/DuplicateEvent.test.tsx new file mode 100644 index 0000000..32fff1a --- /dev/null +++ b/__test__/components/DuplicateEvent.test.tsx @@ -0,0 +1,176 @@ +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 { renderWithProviders } from "../utils/Renderwithproviders"; +import EventPreviewModal from "../../src/features/Events/EventDisplayPreview"; + +const day = new Date(); +const preloadedState = { + user: { + userData: { + sub: "test", + email: "test@test.com", + sid: "aiYbWZSk2g0F+LrQeD7Dg4QcUMR8R/zTZdZBiA7N6Ro", + openpaasId: "667037022b752d0026472254", + }, + organiserData: { + cn: "test", + cal_address: "mailto:test@test.com", + }, + }, + calendars: { + list: { + "667037022b752d0026472254/cal1": { + id: "667037022b752d0026472254/cal1", + name: "Calendar", + color: "#FF0000", + 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, + }, +}; + +describe("EventDuplication", () => { + it("opens EventPopover when button clicked", () => { + const handleClose = jest.fn(); + renderWithProviders( + , + preloadedState + ); + + const button = screen.getByRole("button"); + fireEvent.click(button); + + expect(screen.getByText(/Duplicate Event/i)).toBeInTheDocument(); + }); + + it("calls onClose when closing the popover", () => { + const handleClose = jest.fn(); + renderWithProviders( + , + preloadedState + ); + + fireEvent.click(screen.getByRole("button")); + + fireEvent.click(screen.getByRole("button", { name: /Cancel/i })); + + expect(handleClose).toHaveBeenCalled(); + }); +}); + +describe("EventPopover", () => { + it("renders with event data", () => { + renderWithProviders( + , + preloadedState + ); + + expect(screen.getByDisplayValue(/Test Event/i)).toBeInTheDocument(); + }); + + it("saves duplicated event when Save is clicked", () => { + const onClose = jest.fn(); + + renderWithProviders( + , + preloadedState + ); + + fireEvent.change(screen.getByLabelText(/Title/i), { + target: { value: "Duplicated Event" }, + }); + fireEvent.click(screen.getByRole("button", { name: /Save/i })); + + expect(onClose).toHaveBeenCalled(); + }); +}); + +describe("EventDisplayModal", () => { + it("shows duplication button and opens duplication form", () => { + renderWithProviders( + , + preloadedState + ); + + fireEvent.click(screen.getByTestId("AddToPhotosIcon")); + expect(screen.getByText(/Duplicate Event/i)).toBeInTheDocument(); + expect( + screen.getByDisplayValue( + preloadedState.calendars.list["667037022b752d0026472254/cal1"].events + .event1.title + ) + ).toBeInTheDocument(); + }); +}); diff --git a/__test__/features/Events/EventDisplay.test.tsx b/__test__/features/Events/EventDisplay.test.tsx index 28ded30..0fee032 100644 --- a/__test__/features/Events/EventDisplay.test.tsx +++ b/__test__/features/Events/EventDisplay.test.tsx @@ -1137,13 +1137,7 @@ describe("Event Full Display", () => { const day = new Date(); const preloadedTwoCals = { - user: { - userData: { - sub: "test", - email: "test@test.com", - openpaasId: "667037022b752d0026472254", - }, - }, + ...preloadedState, calendars: { list: { "667037022b752d0026472254/cal1": { @@ -1210,13 +1204,7 @@ describe("Event Full Display", () => { const day = new Date(); const preloadedRecurrence = { - user: { - userData: { - sub: "test", - email: "test@test.com", - openpaasId: "667037022b752d0026472254", - }, - }, + ...preloadedState, calendars: { list: { "667037022b752d0026472254/cal1": { diff --git a/src/components/Event/EventDuplicate.tsx b/src/components/Event/EventDuplicate.tsx new file mode 100644 index 0000000..9e5d453 --- /dev/null +++ b/src/components/Event/EventDuplicate.tsx @@ -0,0 +1,52 @@ +import { IconButton } from "@mui/material"; +import { CalendarEvent } from "../../features/Events/EventsTypes"; +import AddToPhotosIcon from "@mui/icons-material/AddToPhotos"; +import { useRef, useState } from "react"; +import EventPopover from "../../features/Events/EventModal"; +import { CalendarApi, DateSelectArg } from "@fullcalendar/core"; + +export default function EventDuplication({ + onClose, + event, +}: { + onClose: Function; + event: CalendarEvent; +}) { + const [openModal, setOpenModal] = useState(false); + const [selectedRange, setSelectedRange] = useState({ + start: new Date(event.start), + startStr: new Date(event.start).toISOString(), + end: new Date(event.end ?? ""), + endStr: new Date(event.end ?? "").toISOString(), + allDay: event.allday ?? false, + } as DateSelectArg); + const calendarRef = useRef(null); + + const handleClosePopover = () => { + calendarRef.current?.unselect(); + setSelectedRange(null); + setOpenModal(!openModal); + onClose({}, "backdropClick"); + }; + return ( + <> + { + setOpenModal(true); + }} + > + + + + + ); +} diff --git a/src/features/Events/EventDisplayPreview.tsx b/src/features/Events/EventDisplayPreview.tsx index a37e48c..7a58bb0 100644 --- a/src/features/Events/EventDisplayPreview.tsx +++ b/src/features/Events/EventDisplayPreview.tsx @@ -39,6 +39,7 @@ import EventDisplayModal, { } from "./EventDisplay"; import { getEvent } from "./EventApi"; import { CalendarEvent } from "./EventsTypes"; +import EventDuplication from "../../components/Event/EventDuplicate"; export default function EventPreviewModal({ eventId, @@ -122,6 +123,7 @@ export default function EventPreviewModal({ gap: 1, }} > + {mailSpaUrl && attendees.length > 0 && ( ; + event?: CalendarEvent; }) { const dispatch = useAppDispatch(); @@ -63,20 +65,32 @@ function EventPopover({ const timezones = TIMEZONES.aliases; const [showMore, setShowMore] = useState(false); - const [title, setTitle] = useState(""); - const [description, setDescription] = useState(""); - const [location, setLocation] = useState(""); - const [start, setStart] = useState(""); - const [end, setEnd] = useState(""); - const [calendarid, setCalendarid] = useState(0); - const [allday, setAllDay] = useState(false); - const [repetition, setRepetition] = useState( - {} as RepetitionObject + const [title, setTitle] = useState(event?.title ?? ""); + const [description, setDescription] = useState(event?.description ?? ""); + const [location, setLocation] = useState(event?.location ?? ""); + const [start, setStart] = useState( + event?.start ? new Date(event.start).toISOString() : "" ); - const [attendees, setAttendees] = useState([]); - const [alarm, setAlarm] = useState(""); - const [eventClass, setEventClass] = useState("PUBLIC"); - const [busy, setBusy] = useState("OPAQUE"); + const [end, setEnd] = useState( + event?.end ? new Date(event.end)?.toISOString() : "" + ); + const [calendarid, setCalendarid] = useState( + event?.calId + ? userPersonnalCalendars.findIndex((e) => e.id === event?.calId) + : 0 + ); + const [allday, setAllDay] = useState(event?.allday ?? false); + const [repetition, setRepetition] = useState( + event?.repetition ?? ({} as RepetitionObject) + ); + const [attendees, setAttendees] = useState( + event?.attendee + ? event.attendee.filter((a) => a.cal_address !== organizer.cal_address) + : [] + ); + const [alarm, setAlarm] = useState(event?.alarm?.trigger ?? ""); + const [eventClass, setEventClass] = useState(event?.class ?? "PUBLIC"); + const [busy, setBusy] = useState(event?.transp ?? "OPAQUE"); const [timezone, setTimezone] = useState( Intl.DateTimeFormat().resolvedOptions().timeZone @@ -157,7 +171,7 @@ function EventPopover({ }} > - + 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 {