Files
workavia-calendar-front/__test__/features/Events/EventDisplay.test.tsx
T
lenhanphung 64393bf161 fix: preserve original timezone when reopening event update modal
- Add formatDateTimeInTimezone() helper to format dates in event's original timezone
- Fix EventUpdateModal to display event times in original timezone instead of browser timezone
- Fix EventModal (duplicate event) with same timezone handling
- Update EventDisplay tests to use flexible date pattern
2025-10-15 08:29:28 +02:00

1395 lines
40 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import { screen, fireEvent, waitFor, act } from "@testing-library/react";
import * as eventThunks from "../../../src/features/Calendars/CalendarSlice";
import { renderWithProviders } from "../../utils/Renderwithproviders";
import EventDisplayModal, {
InfoRow,
stringAvatar,
stringToColor,
} from "../../../src/features/Events/EventDisplay";
import EventPreviewModal from "../../../src/features/Events/EventDisplayPreview";
describe("Event Preview Display", () => {
const mockOnClose = jest.fn();
const day = new Date();
const RealDateToLocaleString = Date.prototype.toLocaleString;
beforeEach(() => {
jest.clearAllMocks();
(window as any).MAIL_SPA_URL = null;
});
const preloadedState = {
user: {
userData: {
sub: "test",
email: "test@test.com",
sid: "aiYbWZSk2g0F+LrQeD7Dg4QcUMR8R/zTZdZBiA7N6Ro",
openpaasId: "667037022b752d0026472254",
},
organiserData: {
cn: "test",
cal_address: "test@test.com",
},
},
calendars: {
list: {
"667037022b752d0026472254/cal1": {
id: "667037022b752d0026472254/cal1",
name: "Calendar",
color: "#FF0000",
events: {
event1: {
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",
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",
},
],
},
event2: {
id: "event2",
title: "Test Event",
calId: "667037022b752d0026472254/cal1",
start: day.toISOString(),
end: day.toISOString(),
organizer: { cn: "test", cal_address: "test@test.com" },
},
},
},
"otherCal/cal": {
id: "otherCal/cal",
name: "Calendar 1",
color: "#FF0000",
events: {
event1: {
id: "event1",
calId: "otherCal/cal",
title: "Test Event Other cal",
start: day.toISOString(),
end: day.toISOString(),
organizer: { cn: "john", cal_address: "john@test.com" },
},
},
},
},
pending: false,
},
};
it("renders correctly event data", () => {
jest.spyOn(Date.prototype, "toLocaleString").mockImplementation(function (
this: Date,
locales?: Intl.LocalesArgument,
options?: Intl.DateTimeFormatOptions | undefined
): string {
return RealDateToLocaleString.call(this, "en-UK", options);
});
renderWithProviders(
<EventPreviewModal
anchorPosition={{ top: 0, left: 0 }}
open={true}
onClose={mockOnClose}
calId={"667037022b752d0026472254/cal1"}
eventId={"event1"}
/>,
preloadedState
);
const weekday = day.toLocaleString("en-UK", { weekday: "long" });
const month = day.toLocaleString("en-UK", { month: "long" });
const dayOfMonth = day.getDate().toString();
expect(screen.getByText("Test Event")).toBeInTheDocument();
expect(screen.getByText(new RegExp(weekday, "i"))).toBeInTheDocument();
expect(screen.getByText(new RegExp(month, "i"))).toBeInTheDocument();
expect(
screen.getByText(new RegExp(`\\b${dayOfMonth}\\b`))
).toBeInTheDocument();
expect(screen.getByText(/\d{2}:\d{2} \d{2}:\d{2}/)).toBeInTheDocument();
expect(screen.getByText("Calendar")).toBeInTheDocument();
});
it("calls onClose when Cancel clicked", () => {
renderWithProviders(
<EventPreviewModal
anchorPosition={{ top: 0, left: 0 }}
open={true}
onClose={mockOnClose}
calId={"667037022b752d0026472254/cal1"}
eventId={"event1"}
/>,
preloadedState
);
fireEvent.click(screen.getByTestId("CloseIcon"));
expect(mockOnClose).toHaveBeenCalledWith({}, "backdropClick");
});
it("Shows delete button only when calendar is own", () => {
// Renders the other cal event
renderWithProviders(
<EventPreviewModal
anchorPosition={{ top: 0, left: 0 }}
open={true}
onClose={mockOnClose}
calId={"otherCal/cal"}
eventId={"event1"}
/>,
preloadedState
);
expect(screen.queryByTestId("DeleteIcon")).not.toBeInTheDocument();
// Renders the personnal cal event
renderWithProviders(
<EventPreviewModal
anchorPosition={{ top: 0, left: 0 }}
open={true}
onClose={mockOnClose}
calId={"667037022b752d0026472254/cal1"}
eventId={"event1"}
/>,
preloadedState
);
expect(screen.queryByTestId("DeleteIcon")).toBeInTheDocument();
});
it("calls delete when Delete clicked", async () => {
renderWithProviders(
<EventPreviewModal
anchorPosition={{ top: 0, left: 0 }}
open={true}
onClose={mockOnClose}
calId={"667037022b752d0026472254/cal1"}
eventId={"event1"}
/>,
preloadedState
);
const spy = jest
.spyOn(eventThunks, "deleteEventAsync")
.mockImplementation((payload) => {
return () => Promise.resolve(payload) as any;
});
fireEvent.click(screen.getByTestId("DeleteIcon"));
await waitFor(() => {
expect(spy).toHaveBeenCalled();
});
const receivedPayload = spy.mock.calls[0][0];
expect(receivedPayload).toEqual({
calId: "667037022b752d0026472254/cal1",
eventId: "event1",
});
expect(mockOnClose).toHaveBeenCalledWith({}, "backdropClick");
});
it("renders RSVP buttons when user is an attendee", () => {
const rsvpStateIsOrga = {
...preloadedState,
calendars: {
...preloadedState.calendars,
list: {
...preloadedState.calendars.list,
"667037022b752d0026472254/cal1": {
...preloadedState.calendars.list["667037022b752d0026472254/cal1"],
events: {
event1: {
...preloadedState.calendars.list[
"667037022b752d0026472254/cal1"
].events.event1,
attendee: [
{
cal_address: "test@test.com",
cn: "Test User",
partstat: "NEEDS-ACTION",
},
{
cal_address: "organizer@test.com",
cn: "Test Organizer",
partstat: "NEEDS-ACTION",
},
],
organizer: {
cal_address: "organizer@test.com",
},
},
},
},
},
},
};
renderWithProviders(
<EventPreviewModal
anchorPosition={{ top: 0, left: 0 }}
open={true}
onClose={mockOnClose}
calId={"667037022b752d0026472254/cal1"}
eventId={"event1"}
/>,
rsvpStateIsOrga
);
expect(screen.getByText("Will you attend?")).toBeInTheDocument();
expect(screen.getByRole("button", { name: "Accept" })).toBeInTheDocument();
expect(screen.getByRole("button", { name: "Maybe" })).toBeInTheDocument();
expect(screen.getByRole("button", { name: "Decline" })).toBeInTheDocument();
});
it("doesnt renders RSVP buttons when user isnt an attendee", () => {
const rsvpStateIsOrga = {
...preloadedState,
calendars: {
...preloadedState.calendars,
list: {
...preloadedState.calendars.list,
"667037022b752d0026472254/cal1": {
...preloadedState.calendars.list["667037022b752d0026472254/cal1"],
events: {
event1: {
...preloadedState.calendars.list[
"667037022b752d0026472254/cal1"
].events.event1,
attendee: [
{
cal_address: "organizer@test.com",
cn: "Test Organizer",
partstat: "NEEDS-ACTION",
},
],
organizer: {
cal_address: "organizer@test.com",
},
},
},
},
},
},
};
renderWithProviders(
<EventPreviewModal
anchorPosition={{ top: 0, left: 0 }}
open={true}
onClose={mockOnClose}
calId={"667037022b752d0026472254/cal1"}
eventId={"event1"}
/>,
rsvpStateIsOrga
);
expect(screen.queryByText("Will you attend?")).not.toBeInTheDocument();
expect(
screen.queryByRole("button", { name: "Accept" })
).not.toBeInTheDocument();
expect(
screen.queryByRole("button", { name: "Maybe" })
).not.toBeInTheDocument();
expect(
screen.queryByRole("button", { name: "Decline" })
).not.toBeInTheDocument();
});
it("handles RSVP Accept click", async () => {
const spy = jest
.spyOn(eventThunks, "putEventAsync")
.mockImplementation((payload) => {
return () => Promise.resolve(payload) as any;
});
const rsvpState = {
...preloadedState,
calendars: {
...preloadedState.calendars,
list: {
...preloadedState.calendars.list,
"667037022b752d0026472254/cal1": {
...preloadedState.calendars.list["667037022b752d0026472254/cal1"],
events: {
event1: {
...preloadedState.calendars.list[
"667037022b752d0026472254/cal1"
].events.event1,
attendee: [
{
cal_address: "test@test.com",
cn: "Test User",
partstat: "NEEDS-ACTION",
},
],
organizer: {
cal_address: "organizer@test.com",
},
},
},
},
},
},
};
renderWithProviders(
<EventPreviewModal
anchorPosition={{ top: 0, left: 0 }}
open={true}
onClose={mockOnClose}
calId={"667037022b752d0026472254/cal1"}
eventId={"event1"}
/>,
rsvpState
);
fireEvent.click(screen.getByRole("button", { name: "Accept" }));
await waitFor(() => {
expect(spy).toHaveBeenCalled();
expect(mockOnClose).toHaveBeenCalledWith({}, "backdropClick");
});
const updatedEvent = spy.mock.calls[0][0].newEvent;
expect(updatedEvent.attendee[0].partstat).toBe("ACCEPTED");
});
it("handles RSVP Maybe click", async () => {
const spy = jest
.spyOn(eventThunks, "putEventAsync")
.mockImplementation((payload) => {
return () => Promise.resolve(payload) as any;
});
const rsvpState = {
...preloadedState,
calendars: {
...preloadedState.calendars,
list: {
...preloadedState.calendars.list,
"667037022b752d0026472254/cal1": {
...preloadedState.calendars.list["667037022b752d0026472254/cal1"],
events: {
event1: {
...preloadedState.calendars.list[
"667037022b752d0026472254/cal1"
].events.event1,
attendee: [
{
cal_address: "test@test.com",
cn: "Test User",
partstat: "NEEDS-ACTION",
},
],
organizer: {
cal_address: "organizer@test.com",
},
},
},
},
},
},
};
renderWithProviders(
<EventPreviewModal
anchorPosition={{ top: 0, left: 0 }}
open={true}
onClose={mockOnClose}
calId={"667037022b752d0026472254/cal1"}
eventId={"event1"}
/>,
rsvpState
);
fireEvent.click(screen.getByRole("button", { name: "Maybe" }));
await waitFor(() => {
expect(spy).toHaveBeenCalled();
expect(mockOnClose).toHaveBeenCalledWith({}, "backdropClick");
});
const updatedEvent = spy.mock.calls[0][0].newEvent;
expect(updatedEvent.attendee[0].partstat).toBe("TENTATIVE");
});
it("handles RSVP Decline click", async () => {
const spy = jest
.spyOn(eventThunks, "putEventAsync")
.mockImplementation((payload) => {
return () => Promise.resolve(payload) as any;
});
const rsvpState = {
...preloadedState,
calendars: {
...preloadedState.calendars,
list: {
...preloadedState.calendars.list,
"667037022b752d0026472254/cal1": {
...preloadedState.calendars.list["667037022b752d0026472254/cal1"],
events: {
event1: {
...preloadedState.calendars.list[
"667037022b752d0026472254/cal1"
].events.event1,
attendee: [
{
cal_address: "test@test.com",
cn: "Test User",
partstat: "NEEDS-ACTION",
},
],
organizer: {
cal_address: "organizer@test.com",
},
},
},
},
},
},
};
renderWithProviders(
<EventPreviewModal
anchorPosition={{ top: 0, left: 0 }}
open={true}
onClose={mockOnClose}
calId={"667037022b752d0026472254/cal1"}
eventId={"event1"}
/>,
rsvpState
);
fireEvent.click(screen.getByRole("button", { name: "Decline" }));
await waitFor(() => {
expect(spy).toHaveBeenCalled();
expect(mockOnClose).toHaveBeenCalledWith({}, "backdropClick");
});
const updatedEvent = spy.mock.calls[0][0].newEvent;
expect(updatedEvent.attendee[0].partstat).toBe("DECLINED");
});
it("displays edit button", () => {
renderWithProviders(
<EventPreviewModal
anchorPosition={{ top: 0, left: 0 }}
open={true}
onClose={mockOnClose}
calId={"667037022b752d0026472254/cal1"}
eventId={"event1"}
/>,
preloadedState
);
// Check that edit button is displayed
expect(screen.getByTestId("EditIcon")).toBeInTheDocument();
});
it("properly render message button when MAIL_SPA_URL is not null and event has attendees", () => {
(window as any).MAIL_SPA_URL = "test";
renderWithProviders(
<EventPreviewModal
anchorPosition={{ top: 0, left: 0 }}
open={true}
onClose={mockOnClose}
calId={"667037022b752d0026472254/cal1"}
eventId={"event1"}
/>,
preloadedState
);
expect(screen.getByTestId("EmailIcon")).toBeInTheDocument();
});
it("doesnt render message button when MAIL_SPA_URL is not null and event has no attendees", () => {
(window as any).MAIL_SPA_URL = "test";
renderWithProviders(
<EventPreviewModal
anchorPosition={{ top: 0, left: 0 }}
open={true}
onClose={mockOnClose}
calId={"667037022b752d0026472254/cal1"}
eventId={"event2"}
/>,
preloadedState
);
expect(screen.queryByTestId("EmailIcon")).not.toBeInTheDocument();
});
it("doesnt render message button when MAIL_SPA_URL is null and event has attendees", () => {
renderWithProviders(
<EventPreviewModal
anchorPosition={{ top: 0, left: 0 }}
open={true}
onClose={mockOnClose}
calId={"667037022b752d0026472254/cal1"}
eventId={"event1"}
/>,
preloadedState
);
expect(screen.queryByTestId("EmailIcon")).not.toBeInTheDocument();
});
it("message button opens url with attendees as uri and title as subject", () => {
(window as any).MAIL_SPA_URL = "test";
const mockOpen = jest.fn();
window.open = mockOpen;
renderWithProviders(
<EventPreviewModal
anchorPosition={{ top: 0, left: 0 }}
open={true}
onClose={mockOnClose}
calId={"667037022b752d0026472254/cal1"}
eventId={"event1"}
/>,
preloadedState
);
const emailButton = screen.getByTestId("EmailIcon");
expect(emailButton).toBeInTheDocument();
fireEvent.click(emailButton.closest("button")!);
const event =
preloadedState.calendars.list["667037022b752d0026472254/cal1"].events[
"event1"
];
const expectedUrl = `test/mailto/?uri=mailto:john@test.com?subject=Test Event`;
expect(mockOpen).toHaveBeenCalledWith(expectedUrl);
});
});
describe("Event Full Display", () => {
const mockOnClose = jest.fn();
const day = new Date();
beforeEach(() => {
jest.clearAllMocks();
});
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: "First Calendar",
color: "#FF0000",
events: {
event1: {
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",
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",
},
],
},
},
},
"otherCal/cal": {
id: "otherCal/cal",
name: "Calendar 1",
color: "#FF0000",
events: {
event1: {
id: "event1",
calId: "otherCal/cal",
title: "Test Event Other cal",
start: day.toISOString(),
end: day.toISOString(),
organizer: { cn: "john", cal_address: "john@test.com" },
},
},
},
},
pending: false,
},
};
it("renders correctly event data", () => {
renderWithProviders(
<EventDisplayModal
open={true}
onClose={mockOnClose}
calId={"667037022b752d0026472254/cal1"}
eventId={"event1"}
/>,
preloadedState
);
// Check that event title is displayed
expect(screen.getAllByText("Test Event")).toHaveLength(2);
// Check that event time is displayed (use flexible pattern for any date/time)
expect(
screen.getByText(/\d{4}-\d{2}-\d{2}T\d{2}:\d{2}/)
).toBeInTheDocument();
expect(screen.getByText("First Calendar")).toBeInTheDocument();
});
it("calls onClose when Cancel clicked", () => {
renderWithProviders(
<EventDisplayModal
open={true}
onClose={mockOnClose}
calId={"667037022b752d0026472254/cal1"}
eventId={"event1"}
/>,
preloadedState
);
fireEvent.click(screen.getAllByTestId("CloseIcon")[0]);
expect(mockOnClose).toHaveBeenCalledWith({}, "backdropClick");
});
it("Shows delete button only when calendar is own", () => {
// Renders the other cal event
renderWithProviders(
<EventDisplayModal
open={true}
onClose={mockOnClose}
calId={"otherCal/cal"}
eventId={"event1"}
/>,
preloadedState
);
expect(screen.queryByTestId("DeleteIcon")).not.toBeInTheDocument();
// Renders the personnal cal event
renderWithProviders(
<EventDisplayModal
open={true}
onClose={mockOnClose}
calId={"667037022b752d0026472254/cal1"}
eventId={"event1"}
/>,
preloadedState
);
expect(screen.queryByTestId("DeleteIcon")).toBeInTheDocument();
});
it("calls delete when Delete clicked", async () => {
renderWithProviders(
<EventDisplayModal
open={true}
onClose={mockOnClose}
calId={"667037022b752d0026472254/cal1"}
eventId={"event1"}
/>,
preloadedState
);
const spy = jest
.spyOn(eventThunks, "deleteEventAsync")
.mockImplementation((payload) => {
return () => Promise.resolve(payload) as any;
});
fireEvent.click(screen.getByTestId("DeleteIcon"));
await waitFor(() => {
expect(spy).toHaveBeenCalled();
});
const receivedPayload = spy.mock.calls[0][0];
expect(receivedPayload).toEqual({
calId: "667037022b752d0026472254/cal1",
eventId: "event1",
});
expect(mockOnClose).toHaveBeenCalledWith({}, "backdropClick");
});
it("renders RSVP buttons when user is an attendee", () => {
const rsvpStateIsOrga = {
...preloadedState,
calendars: {
...preloadedState.calendars,
list: {
...preloadedState.calendars.list,
"667037022b752d0026472254/cal1": {
...preloadedState.calendars.list["667037022b752d0026472254/cal1"],
events: {
event1: {
...preloadedState.calendars.list[
"667037022b752d0026472254/cal1"
].events.event1,
attendee: [
{
cal_address: "test@test.com",
cn: "Test User",
partstat: "NEEDS-ACTION",
},
{
cal_address: "organizer@test.com",
cn: "Test Organizer",
partstat: "NEEDS-ACTION",
},
],
organizer: {
cal_address: "organizer@test.com",
},
},
},
},
},
},
};
renderWithProviders(
<EventDisplayModal
open={true}
onClose={mockOnClose}
calId={"667037022b752d0026472254/cal1"}
eventId={"event1"}
/>,
rsvpStateIsOrga
);
expect(screen.getByRole("button", { name: "Accept" })).toBeInTheDocument();
expect(screen.getByRole("button", { name: "Maybe" })).toBeInTheDocument();
expect(screen.getByRole("button", { name: "Decline" })).toBeInTheDocument();
});
it("doesnt renders RSVP buttons when user isnt an attendee", () => {
const rsvpStateIsOrga = {
...preloadedState,
calendars: {
...preloadedState.calendars,
list: {
...preloadedState.calendars.list,
"667037022b752d0026472254/cal1": {
...preloadedState.calendars.list["667037022b752d0026472254/cal1"],
events: {
event1: {
...preloadedState.calendars.list[
"667037022b752d0026472254/cal1"
].events.event1,
attendee: [
{
cal_address: "organizer@test.com",
cn: "Test Organizer",
partstat: "NEEDS-ACTION",
},
],
organizer: {
cal_address: "organizer@test.com",
},
},
},
},
},
},
};
renderWithProviders(
<EventDisplayModal
open={true}
onClose={mockOnClose}
calId={"667037022b752d0026472254/cal1"}
eventId={"event1"}
/>,
rsvpStateIsOrga
);
expect(
screen.queryByRole("button", { name: "Accept" })
).not.toBeInTheDocument();
expect(
screen.queryByRole("button", { name: "Maybe" })
).not.toBeInTheDocument();
expect(
screen.queryByRole("button", { name: "Decline" })
).not.toBeInTheDocument();
});
it("handles RSVP Accept click", async () => {
const spy = jest
.spyOn(eventThunks, "putEventAsync")
.mockImplementation((payload) => {
return () => Promise.resolve(payload) as any;
});
const rsvpState = {
...preloadedState,
calendars: {
...preloadedState.calendars,
list: {
...preloadedState.calendars.list,
"667037022b752d0026472254/cal1": {
...preloadedState.calendars.list["667037022b752d0026472254/cal1"],
events: {
event1: {
...preloadedState.calendars.list[
"667037022b752d0026472254/cal1"
].events.event1,
attendee: [
{
cal_address: "test@test.com",
cn: "Test User",
partstat: "NEEDS-ACTION",
},
],
organizer: {
cal_address: "organizer@test.com",
},
},
},
},
},
},
};
renderWithProviders(
<EventDisplayModal
open={true}
onClose={mockOnClose}
calId={"667037022b752d0026472254/cal1"}
eventId={"event1"}
/>,
rsvpState
);
fireEvent.click(screen.getByRole("button", { name: "Accept" }));
await waitFor(() => {
expect(spy).toHaveBeenCalled();
});
const updatedEvent = spy.mock.calls[0][0].newEvent;
expect(updatedEvent.attendee[0].partstat).toBe("ACCEPTED");
});
it("handles RSVP Maybe click", async () => {
const spy = jest
.spyOn(eventThunks, "putEventAsync")
.mockImplementation((payload) => {
return () => Promise.resolve(payload) as any;
});
const rsvpState = {
...preloadedState,
calendars: {
...preloadedState.calendars,
list: {
...preloadedState.calendars.list,
"667037022b752d0026472254/cal1": {
...preloadedState.calendars.list["667037022b752d0026472254/cal1"],
events: {
event1: {
...preloadedState.calendars.list[
"667037022b752d0026472254/cal1"
].events.event1,
attendee: [
{
cal_address: "test@test.com",
cn: "Test User",
partstat: "NEEDS-ACTION",
},
],
organizer: {
cal_address: "organizer@test.com",
},
},
},
},
},
},
};
renderWithProviders(
<EventDisplayModal
open={true}
onClose={mockOnClose}
calId={"667037022b752d0026472254/cal1"}
eventId={"event1"}
/>,
rsvpState
);
fireEvent.click(screen.getByRole("button", { name: "Maybe" }));
await waitFor(() => {
expect(spy).toHaveBeenCalled();
});
const updatedEvent = spy.mock.calls[0][0].newEvent;
expect(updatedEvent.attendee[0].partstat).toBe("TENTATIVE");
});
it("handles RSVP Decline click", async () => {
const spy = jest
.spyOn(eventThunks, "putEventAsync")
.mockImplementation((payload) => {
return () => Promise.resolve(payload) as any;
});
const rsvpState = {
...preloadedState,
calendars: {
...preloadedState.calendars,
list: {
...preloadedState.calendars.list,
"667037022b752d0026472254/cal1": {
...preloadedState.calendars.list["667037022b752d0026472254/cal1"],
events: {
event1: {
...preloadedState.calendars.list[
"667037022b752d0026472254/cal1"
].events.event1,
attendee: [
{
cal_address: "test@test.com",
cn: "Test User",
partstat: "NEEDS-ACTION",
},
],
organizer: {
cal_address: "organizer@test.com",
},
},
},
},
},
},
};
renderWithProviders(
<EventDisplayModal
open={true}
onClose={mockOnClose}
calId={"667037022b752d0026472254/cal1"}
eventId={"event1"}
/>,
rsvpState
);
fireEvent.click(screen.getByRole("button", { name: "Decline" }));
await waitFor(() => {
expect(spy).toHaveBeenCalled();
});
const updatedEvent = spy.mock.calls[0][0].newEvent;
expect(updatedEvent.attendee[0].partstat).toBe("DECLINED");
});
it("toggle Show More reveals extra fields", async () => {
renderWithProviders(
<EventDisplayModal
open={true}
onClose={mockOnClose}
calId={"667037022b752d0026472254/cal1"}
eventId={"event1"}
/>,
preloadedState
);
// Initially should show "Show More" button
expect(screen.getByText("Show More")).toBeInTheDocument();
act(() => {
fireEvent.click(screen.getByText("Show More"));
});
await waitFor(() => {
expect(screen.getByLabelText(/Notification/i)).toBeInTheDocument();
});
// After clicking, should show "Show Less" button
expect(screen.getByText("Show Less")).toBeInTheDocument();
// EventDisplay modal doesn't have Repeat checkbox, only RepeatEvent component
// which shows repetition settings when repetition data exists
// Since test event has no repetition data, RepeatEvent component won't show Repeat checkbox
fireEvent.click(screen.getByText("Show Less"));
});
it("displays event title for organizer", () => {
renderWithProviders(
<EventDisplayModal
open={true}
onClose={mockOnClose}
calId={"667037022b752d0026472254/cal1"}
eventId={"event1"}
/>,
preloadedState
);
// Check that the event title is displayed
expect(screen.getAllByText("Test Event")).toHaveLength(2);
});
it("displays event for non-organizer", () => {
const rsvpState = {
...preloadedState,
calendars: {
...preloadedState.calendars,
list: {
...preloadedState.calendars.list,
"667037022b752d0026472254/cal1": {
...preloadedState.calendars.list["667037022b752d0026472254/cal1"],
events: {
event1: {
...preloadedState.calendars.list[
"667037022b752d0026472254/cal1"
].events.event1,
attendee: [
{
cal_address: "test@test.com",
cn: "Test User",
partstat: "NEEDS-ACTION",
},
],
organizer: {
cal_address: "organizer@test.com",
cn: "Edgar Organiser",
},
},
},
},
},
},
};
renderWithProviders(
<EventDisplayModal
open={true}
onClose={mockOnClose}
calId={"667037022b752d0026472254/cal1"}
eventId={"event1"}
/>,
rsvpState
);
// Check that the event is displayed for non-organizer
expect(screen.getAllByText("Test Event")).toHaveLength(2);
});
it("displays event information correctly", () => {
renderWithProviders(
<EventDisplayModal
open={true}
onClose={mockOnClose}
calId={"667037022b752d0026472254/cal1"}
eventId={"event1"}
/>,
preloadedState
);
// Check that event title is displayed (use getAllByText to handle multiple instances)
expect(screen.getAllByText("Test Event")).toHaveLength(2);
// Check that event time is displayed (use flexible pattern for any date/time)
expect(
screen.getByText(/\d{4}-\d{2}-\d{2}T\d{2}:\d{2}/)
).toBeInTheDocument();
});
it("displays event with multiple calendars", () => {
const day = new Date();
const preloadedTwoCals = {
...preloadedState,
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(
<EventDisplayModal
open={true}
onClose={mockOnClose}
calId={"667037022b752d0026472254/cal1"}
eventId={"event1"}
/>,
preloadedTwoCals
);
// Check that event is displayed correctly (use getAllByText to handle multiple instances)
expect(screen.getAllByText("Test Event")).toHaveLength(2);
});
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 = {
...preloadedState,
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(
<EventDisplayModal
open={true}
onClose={mockOnClose}
calId={"667037022b752d0026472254/cal1"}
eventId={"base/20250301"}
/>,
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(<InfoRow icon={<span>i</span>} text="Bad" error />);
expect(screen.getByText("Bad")).toBeInTheDocument();
});
it("handles missing event gracefully", () => {
renderWithProviders(
<EventDisplayModal
open={true}
onClose={mockOnClose}
calId={"nonexistent/cal"}
eventId={"ghost"}
/>,
preloadedState
);
// EventDisplay should handle missing event gracefully
// When event is missing, the component may not render anything
expect(screen.queryByRole("presentation")).toBeNull();
});
it("renders event with error state", () => {
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(
<EventDisplayModal
open={true}
onClose={mockOnClose}
calId={"667037022b752d0026472254/cal1"}
eventId={"event1"}
/>,
errorState
);
// Check that the modal renders even with error state (use getAllByText to handle multiple instances)
expect(screen.getAllByText("Test Event")).toHaveLength(2);
});
it("displays attendees correctly", () => {
renderWithProviders(
<EventDisplayModal
open={true}
onClose={mockOnClose}
calId={"667037022b752d0026472254/cal1"}
eventId={"event1"}
/>,
preloadedState
);
// Check that attendees are displayed
expect(screen.getByText("John")).toBeInTheDocument();
});
it("displays multiple attendees", () => {
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(
<EventDisplayModal
open={true}
onClose={mockOnClose}
calId={"667037022b752d0026472254/cal1"}
eventId={"event1"}
/>,
overflowState
);
// Check that multiple attendees are displayed
expect(screen.getByText("Person0")).toBeInTheDocument();
expect(screen.getByText("Person1")).toBeInTheDocument();
});
it("handles video conference data", () => {
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(
<EventDisplayModal
open={true}
onClose={mockOnClose}
calId={"667037022b752d0026472254/cal1"}
eventId={"event1"}
/>,
videoState
);
// Check that the event still displays correctly with video conference data (use getAllByText to handle multiple instances)
expect(screen.getAllByText("Test Event")).toHaveLength(2);
});
});
describe("Helper functions", () => {
it("stringToColor generates consistent color", () => {
expect(stringToColor("Alice")).toMatch(/^#[0-9a-f]{6}$/);
expect(stringToColor("Alice")).toBe(stringToColor("Alice"));
});
it("stringAvatar returns correct props", () => {
const result = stringAvatar("Alice");
expect(result.children).toBe("A");
expect(result.sx.bgcolor).toMatch(/^#/);
});
it("InfoRow renders text and data", () => {
renderWithProviders(
<InfoRow
icon={<span>ico</span>}
text="Meeting"
data="https://example.com"
/>
);
expect(screen.getByText("Meeting")).toBeInTheDocument();
expect(screen.getByText("https://example.com")).toBeInTheDocument();
});
});