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;