diff --git a/__test__/components/EventModifications.test.tsx b/__test__/components/EventModifications.test.tsx index d76b2ab..e0c7717 100644 --- a/__test__/components/EventModifications.test.tsx +++ b/__test__/components/EventModifications.test.tsx @@ -209,4 +209,50 @@ describe("CalendarApp integration", () => { ); expect(found).toBe(false); }); + + it("does render a title for events without any attendees or user as organizer", async () => { + const mockCalendarRef = { current: null }; + renderWithProviders(, { + user: { + userData: { + sub: "test", + email: "test@test.com", + sid: "mockSid", + openpaasId: "667037022b752d0026472254", + }, + tokens: { + accessToken: "token", + }, + }, + calendars: { + list: { + "667037022b752d0026472254/cal1": { + name: "Calendar 1", + id: "667037022b752d0026472254/cal1", + color: "#FF0000", + ownerEmails: ["alice@example.com"], + events: { + event1: { + id: "event1", + calId: "667037022b752d0026472254/cal1", + uid: "event1", + start: new Date().toISOString(), + end: new Date(Date.now() + 3600000).toISOString(), + partstat: "ACCEPTED", + organizer: { + cn: "Alice", + cal_address: "alice@example.com", + }, + class: "PUBLIC", + title: "Public Event", + }, + }, + }, + }, + pending: false, + }, + }); + + expect(screen.getByText("Public Event")).toBeInTheDocument(); + }); }); diff --git a/__test__/features/Events/eventUtils.test.ts b/__test__/features/Events/eventUtils.test.ts index fca445f..a379a30 100644 --- a/__test__/features/Events/eventUtils.test.ts +++ b/__test__/features/Events/eventUtils.test.ts @@ -9,7 +9,7 @@ describe("parseCalendarEvent", () => { const baseColor = "#00FF00"; const calendarId = "calendar-123"; - it("parses a full event correctly", () => { + it("parses a full event with MAILTO in caps correctly", () => { const rawData = [ ["UID", {}, "text", "event-1"], ["DTSTART", {}, "date-time", "2025-07-18T09:00:00Z"], @@ -70,6 +70,67 @@ describe("parseCalendarEvent", () => { ]); }); + it("parses a full event correctly", () => { + const rawData = [ + ["UID", {}, "text", "event-1"], + ["DTSTART", {}, "date-time", "2025-07-18T09:00:00Z"], + ["DTEND", {}, "date-time", "2025-07-18T10:00:00Z"], + ["SUMMARY", {}, "text", "Team Meeting"], + ["DESCRIPTION", {}, "text", "Discuss roadmap"], + ["LOCATION", {}, "text", "Zoom"], + ["ORGANIZER", { cn: "Alice" }, "cal-address", "MAILTO:alice@example.com"], + [ + "ATTENDEE", + { cn: "Bob", partstat: "ACCEPTED" }, + "cal-address", + "MAILTO:bob@example.com", + ], + ["X-OPENPAAS-VIDEOCONFERENCE", {}, "text", "https://meet.link"], + ["STATUS", {}, "text", "CONFIRMED"], + ["SEQUENCE", {}, "integer", "2"], + ["TRANSP", {}, "text", "OPAQUE"], + ["CLASS", {}, "text", "PUBLIC"], + ["DTSTAMP", {}, "date-time", "2025-07-18T08:00:00Z"], + ] as unknown as [string, Record, string, any]; + + const result = parseCalendarEvent( + rawData, + baseColor, + calendarId, + "/calendars/test.ics" + ); + + expect(result.uid).toBe("event-1"); + expect(result.title).toBe("Team Meeting"); + expect(result.description).toBe("Discuss roadmap"); + expect(result.location).toBe("Zoom"); + expect(result.start).toBe("2025-07-18T09:00:00Z"); + expect(result.end).toBe("2025-07-18T10:00:00Z"); + expect(result.stamp).toBe("2025-07-18T08:00:00Z"); + expect(result.sequence).toBe(2); + expect(result.color).toBe(baseColor); + expect(result.status).toBe("CONFIRMED"); + expect(result.transp).toBe("OPAQUE"); + expect(result.class).toBe("PUBLIC"); + expect(result.x_openpass_videoconference).toBe("https://meet.link"); + + expect(result.organizer).toEqual({ + cn: "Alice", + cal_address: "alice@example.com", + }); + + expect(result.attendee).toEqual([ + { + cn: "Bob", + cal_address: "bob@example.com", + partstat: "ACCEPTED", + rsvp: "", + role: "", + cutype: "", + }, + ]); + }); + it("marks allday true for DATE format", () => { const rawData = [ ["UID", {}, "text", "event-2"], diff --git a/src/components/Calendar/handlers/viewHandlers.ts b/src/components/Calendar/handlers/viewHandlers.ts index bffa1af..590e48a 100644 --- a/src/components/Calendar/handlers/viewHandlers.ts +++ b/src/components/Calendar/handlers/viewHandlers.ts @@ -135,34 +135,32 @@ export const createViewHandlers = (props: ViewHandlersProps) => { const handleEventContent = (arg: any) => { const event = arg.event; - if ( - (!event._def.extendedProps.temp && - !calendars[arg.event._def.extendedProps.calId]) || - (event._def.extendedProps.temp && - !tempcalendars[arg.event._def.extendedProps.calId]) - ) - return; + const props = event._def.extendedProps; + const { calId, temp, attendees = [], class: classification } = props; - const attendees = event._def.extendedProps.attendee || []; - const isPrivate = - event._def.extendedProps.class === "PRIVATE" || - event._def.extendedProps.class === "CONFIDENTIAL"; + const calendarsSource = temp ? tempcalendars : calendars; + const calendar = calendarsSource[calId]; + if (!calendar) return null; + + const isPrivate = ["PRIVATE", "CONFIDENTIAL"].includes(classification); + const ownerEmails = new Set( + calendar.ownerEmails?.map((e) => e.toLowerCase()) + ); + const delegated = calendar.delegated; let Icon = null; let titleStyle: React.CSSProperties = {}; - const ownerEmails = new Set( - (event._def.extendedProps.temp ? tempcalendars : calendars)[ - arg.event._def.extendedProps.calId - ].ownerEmails?.map((email) => email.toLowerCase()) - ); - const delegated = ( - event._def.extendedProps.temp ? tempcalendars : calendars - )[arg.event._def.extendedProps.calId].delegated; const showSpecialDisplay = attendees.filter((att: userAttendee) => ownerEmails.has(att.cal_address.toLowerCase()) ); - if (!delegated && showSpecialDisplay.length === 0) return null; - + // if no special display + if (attendees.length && !delegated && !showSpecialDisplay.length) { + return React.createElement( + "div", + { style: { display: "flex", alignItems: "center" } }, + React.createElement("span", event.title) + ); + } switch (showSpecialDisplay?.[0]?.partstat) { case "DECLINED": Icon = null;