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({
+
+ 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],