diff --git a/__test__/features/Events/EventDisplay.test.tsx b/__test__/features/Events/EventDisplay.test.tsx index d878e95..9ae32d5 100644 --- a/__test__/features/Events/EventDisplay.test.tsx +++ b/__test__/features/Events/EventDisplay.test.tsx @@ -1045,6 +1045,296 @@ describe("Event Full Display", () => { screen.getAllByDisplayValue(new RegExp(date, "i"))[0] ).toBeInTheDocument(); }); + it("saves event and moves it when calendar is changed", async () => { + const spyPut = jest + .spyOn(eventThunks, "putEventAsync") + .mockImplementation((payload) => () => Promise.resolve(payload) as any); + const spyMove = jest + .spyOn(eventThunks, "moveEventAsync") + .mockImplementation((payload) => () => Promise.resolve(payload) as any); + const spyRemove = jest.spyOn(eventThunks, "removeEvent"); + + const day = new Date(); + const preloadedTwoCals = { + user: { + userData: { + sub: "test", + email: "test@test.com", + openpaasId: "667037022b752d0026472254", + }, + }, + calendars: { + list: { + "667037022b752d0026472254/cal1": { + id: "667037022b752d0026472254/cal1", + name: "Calendar One", + color: "#FF0000", + events: { + event1: { + uid: "uid-base", + id: "event1", + title: "Test Event", + calId: "667037022b752d0026472254/cal1", + start: day.toISOString(), + end: day.toISOString(), + organizer: { cn: "test", cal_address: "test@test.com" }, + attendee: [{ cn: "test", cal_address: "test@test.com" }], + }, + }, + }, + "667037022b752d0026472254/cal2": { + id: "667037022b752d0026472254/cal2", + name: "Calendar Two", + color: "#00FF00", + events: {}, + }, + }, + pending: false, + }, + }; + + renderWithProviders( + , + preloadedTwoCals + ); + + fireEvent.mouseDown(screen.getByLabelText("Calendar")); + + const option = await screen.findByText("Calendar Two"); + fireEvent.click(option); + + fireEvent.click(screen.getByText("Save")); + + await waitFor(() => { + expect(spyPut).toHaveBeenCalled(); + }); + + await waitFor(() => { + expect(spyMove).toHaveBeenCalled(); + }); + + expect(spyRemove).toHaveBeenCalled(); + }); + + it("removes recurrence instances when saving an edited recurring series", async () => { + const spyPut = jest + .spyOn(eventThunks, "putEventAsync") + .mockImplementation((payload) => () => Promise.resolve(payload) as any); + const spyRemove = jest.spyOn(eventThunks, "removeEvent"); + + const day = new Date(); + const preloadedRecurrence = { + user: { + userData: { + sub: "test", + email: "test@test.com", + openpaasId: "667037022b752d0026472254", + }, + }, + calendars: { + list: { + "667037022b752d0026472254/cal1": { + id: "667037022b752d0026472254/cal1", + name: "First Calendar", + color: "#FF0000", + events: { + "base/20250101": { + uid: "base/20250101", + calId: "667037022b752d0026472254/cal1", + title: "eventA", + }, + "base/20250201": { + uid: "base/20250201", + calId: "667037022b752d0026472254/cal1", + title: "eventB", + }, + "base/20250301": { + uid: "base/20250301", + title: "Recurring event", + calId: "667037022b752d0026472254/cal1", + start: day.toISOString(), + end: day.toISOString(), + organizer: { cal_address: "test@test.com" }, + attendee: [{ cal_address: "test@test.com", cn: "Test" }], + }, + }, + }, + }, + pending: false, + }, + }; + + renderWithProviders( + , + preloadedRecurrence + ); + + act(() => fireEvent.click(screen.getByText("Save"))); + + await waitFor(() => { + expect(spyPut).toHaveBeenCalled(); + }); + + await waitFor(() => { + expect(spyRemove).toHaveBeenCalled(); + }); + }); + + it("InfoRow renders error style when error prop is true", () => { + renderWithProviders(i} text="Bad" error />); + expect(screen.getByText("Bad")).toBeInTheDocument(); + }); + + it("calls onClose from useEffect if event or calendar missing", () => { + renderWithProviders( + , + preloadedState + ); + expect(mockOnClose).toHaveBeenCalledWith({}, "backdropClick"); + }); + + it("renders error row when event has error", () => { + const errorState = { + ...preloadedState, + calendars: { + list: { + "667037022b752d0026472254/cal1": { + ...preloadedState.calendars.list["667037022b752d0026472254/cal1"], + events: { + event1: { + ...preloadedState.calendars.list[ + "667037022b752d0026472254/cal1" + ].events.event1, + error: "Something went wrong", + }, + }, + }, + }, + pending: false, + }, + }; + + renderWithProviders( + , + errorState + ); + + act(() => { + fireEvent.click(screen.getByText("Show More")); + }); + + expect(screen.getByText("Something went wrong")).toBeInTheDocument(); + }); + + it("can remove an attendee with the close button", () => { + renderWithProviders( + , + preloadedState + ); + + const removeBtn = screen.getAllByTestId("CloseIcon").pop()!; + fireEvent.click(removeBtn); + + expect(screen.queryByText(/John/)).not.toBeInTheDocument(); + }); + + it("shows more attendees when overflow, then toggles back", () => { + const overflowState = { + ...preloadedState, + calendars: { + list: { + "667037022b752d0026472254/cal1": { + ...preloadedState.calendars.list["667037022b752d0026472254/cal1"], + events: { + event1: { + ...preloadedState.calendars.list[ + "667037022b752d0026472254/cal1" + ].events.event1, + attendee: new Array(6).fill(null).map((_, i) => ({ + cn: `Person${i}`, + cal_address: `p${i}@test.com`, + })), + organizer: { cn: "test", cal_address: "test@test.com" }, + }, + }, + }, + }, + }, + }; + + renderWithProviders( + , + overflowState + ); + + const toggle = screen.getByText(/Show more/); + fireEvent.click(toggle); + + expect(screen.getByText(/Show less/)).toBeInTheDocument(); + }); + + it("renders video conference info when x_openpass_videoconference exists", () => { + const videoState = { + ...preloadedState, + calendars: { + list: { + "667037022b752d0026472254/cal1": { + ...preloadedState.calendars.list["667037022b752d0026472254/cal1"], + events: { + event1: { + ...preloadedState.calendars.list[ + "667037022b752d0026472254/cal1" + ].events.event1, + x_openpass_videoconference: "https://meet.test/video", + }, + }, + }, + }, + }, + }; + + renderWithProviders( + , + videoState + ); + + expect(screen.getByText("Video conference available")).toBeInTheDocument(); + }); }); describe("Helper functions", () => { diff --git a/__test__/features/Events/EventModal.test.tsx b/__test__/features/Events/EventModal.test.tsx index c8d0db2..8aff878 100644 --- a/__test__/features/Events/EventModal.test.tsx +++ b/__test__/features/Events/EventModal.test.tsx @@ -247,7 +247,7 @@ describe("EventPopover", () => { location: "Zoom", organizer: { cn: "test", cal_address: "test@test.com" }, timezone: "Europe/Paris", - transp: "OPAQUE", + transp: "TRANSPARENT", }; // Fill inputs diff --git a/src/features/Events/EventDisplay.tsx b/src/features/Events/EventDisplay.tsx index 9b0027d..5bc56ab 100644 --- a/src/features/Events/EventDisplay.tsx +++ b/src/features/Events/EventDisplay.tsx @@ -89,13 +89,13 @@ export default function EventDisplayModal({ ); const [allday, setAllDay] = useState(event?.allday); const [repetition, setRepetition] = useState( - event.repetition ?? ({} as RepetitionObject) + event?.repetition ?? ({} as RepetitionObject) ); const [alarm, setAlarm] = useState(event?.alarm?.trigger ?? ""); - const [busy, setBusy] = useState(""); + const [busy, setBusy] = useState(event?.transp ?? "OPAQUE"); const [eventClass, setEventClass] = useState(event?.class ?? "PUBLIC"); const [timezone, setTimezone] = useState(event?.timezone ?? "UTC"); - const [newCalId, setNewCalId] = useState(event.calId); + const [newCalId, setNewCalId] = useState(event?.calId); const [calendarid, setCalendarid] = useState( calId.split("/")[0] === user.userData.openpaasId ? userPersonnalCalendars.findIndex((cal) => cal.id === calId) @@ -112,7 +112,7 @@ export default function EventDisplayModal({ ); const organizer = - event.attendee?.find( + event?.attendee?.find( (a) => a.cal_address === event?.organizer?.cal_address ) ?? ({} as userAttendee); @@ -124,7 +124,7 @@ export default function EventDisplayModal({ if (!event || !calendar) { onClose({}, "backdropClick"); } - setRepetition(event.repetition ?? ({} as RepetitionObject)); + setRepetition(event?.repetition ?? ({} as RepetitionObject)); }, [open, eventId, dispatch, onClose, event]); if (!event || !calendar) return null; @@ -158,7 +158,7 @@ export default function EventDisplayModal({ organizer: event.organizer, timezone, attendee: [organizer, ...attendees], - transp: "OPAQUE", + transp: busy, color: userPersonnalCalendars[calendarid]?.color, alarm: { trigger: alarm, action: "EMAIL" }, }; @@ -471,32 +471,32 @@ export default function EventDisplayModal({ setRepetition={setRepetition} isOwn={isOwn} /> - - - Alarm - - - + + Alarm + + Visibility + + is Busy + + {/* Error */} {event.error && ( ([]); const [alarm, setAlarm] = useState(""); const [eventClass, setEventClass] = useState("PUBLIC"); - const [busy, setBusy] = useState(""); + const [busy, setBusy] = useState("OPAQUE"); const [timezone, setTimezone] = useState( Intl.DateTimeFormat().resolvedOptions().timeZone @@ -115,7 +115,7 @@ function EventPopover({ cutype: "INDIVIDUAL", }, ], - transp: "OPAQUE", + transp: busy, color: userPersonnalCalendars[calendarid]?.color, alarm: { trigger: alarm, action: "EMAIL" }, }; @@ -326,6 +326,18 @@ function EventPopover({ Private + + is Busy + + )} diff --git a/src/features/Events/eventUtils.ts b/src/features/Events/eventUtils.ts index 7326688..0d62ce8 100644 --- a/src/features/Events/eventUtils.ts +++ b/src/features/Events/eventUtils.ts @@ -157,7 +157,7 @@ export function calendarEventToJCal( ["summary", {}, "text", event.title ?? ""], ], ]; - if (event.alarm) { + if (event.alarm?.trigger) { const valarm = [ ["trigger", {}, "duration", event.alarm.trigger], ["action", {}, "text", event.alarm.action],