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( , 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( , 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( , preloadedState ); expect(screen.queryByTestId("DeleteIcon")).not.toBeInTheDocument(); // Renders the personnal cal event renderWithProviders( , preloadedState ); expect(screen.queryByTestId("DeleteIcon")).toBeInTheDocument(); }); it("calls delete when Delete clicked", async () => { renderWithProviders( , 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( , 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( , 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( , 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( , 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( , 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("handles Edit click", async () => { const spy = jest .spyOn(eventThunks, "getEventAsync") .mockImplementation((payload) => { return () => Promise.resolve({ calId: payload.calId, event: preloadedState.calendars.list["667037022b752d0026472254/cal1"] .events["event1"], }) as any; }); renderWithProviders( , preloadedState ); fireEvent.click(screen.getByTestId("EditIcon")); await waitFor(() => { expect(spy).toHaveBeenCalled(); }); expect(screen.getByText("Edit Event")).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( , 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( , preloadedState ); expect(screen.queryByTestId("EmailIcon")).not.toBeInTheDocument(); }); it("doesnt render message button when MAIL_SPA_URL is null and event has attendees", () => { renderWithProviders( , 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( , 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( , preloadedState ); const tzOffset = day.getTimezoneOffset() * 60000; // offset in ms const date = new Date(day.getTime() - tzOffset).toISOString().slice(0, 16); expect(screen.getByDisplayValue("Test Event")).toBeInTheDocument(); expect( screen.getAllByDisplayValue(new RegExp(date, "i"))[0] ).toBeInTheDocument(); expect( screen.getAllByDisplayValue(new RegExp(date, "i")).length ).toBeLessThanOrEqual(2); expect(screen.getByText("First Calendar")).toBeInTheDocument(); }); it("calls onClose when Cancel clicked", () => { renderWithProviders( , 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( , preloadedState ); expect(screen.queryByTestId("DeleteIcon")).not.toBeInTheDocument(); // Renders the personnal cal event renderWithProviders( , preloadedState ); expect(screen.queryByTestId("DeleteIcon")).toBeInTheDocument(); }); it("calls delete when Delete clicked", async () => { renderWithProviders( , 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( , 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( , 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( , 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( , 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( , 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( , preloadedState ); act(() => { fireEvent.click(screen.getByText("Show More")); }); await waitFor(() => { expect(screen.getByLabelText(/Notification/i)).toBeInTheDocument(); expect(screen.getByLabelText(/Repeat/i)).toBeInTheDocument(); expect(screen.getByText(/Visible to/i)).toBeInTheDocument(); }); fireEvent.click(screen.getByText("Show Less")); }); it("can edit title when user is organizer", () => { renderWithProviders( , preloadedState ); const titleField = screen.getByLabelText("Title"); fireEvent.change(titleField, { target: { value: "New Title" } }); expect(screen.getByDisplayValue("New Title")).toBeInTheDocument(); }); it("calendar select is disabled when not 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( , rsvpState ); expect(screen.getByLabelText("Calendar")).toHaveClass("Mui-disabled"); }); it("toggle all-day updates end date correctly", () => { renderWithProviders( , preloadedState ); const allDayCheckbox = screen.getByLabelText("All day"); fireEvent.click(allDayCheckbox); expect(allDayCheckbox).toBeChecked(); const date = day.toISOString().split("T")[0]; expect( 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 = { ...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( , 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 = { ...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( , 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", () => { 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 link if url is valid", () => { renderWithProviders( ico} text="Meeting" data="https://example.com" /> ); expect(screen.getByText("Meeting").closest("a")).toHaveAttribute( "href", "https://example.com" ); }); });