import { screen, fireEvent, waitFor, act, cleanup, } from "@testing-library/react"; import * as eventThunks from "../../../src/features/Calendars/CalendarSlice"; import { renderWithProviders } from "../../utils/Renderwithproviders"; import EventUpdateModal from "../../../src/features/Events/EventUpdateModal"; import EventPreviewModal from "../../../src/features/Events/EventDisplayPreview"; import { InfoRow } from "../../../src/components/Event/InfoRow"; import { stringToColor, stringAvatar, } from "../../../src/components/Event/utils/eventUtils"; import * as appHooks from "../../../src/app/hooks"; import { ThunkDispatch } from "@reduxjs/toolkit"; import dayjs from "dayjs"; import { LONG_DATE_FORMAT } from "../../../src/components/Event/utils/dateTimeFormatters"; describe("Event Preview Display", () => { const mockOnClose = jest.fn(); const day = new Date("2025-01-15T10:00:00.000Z"); // Fixed date in UTC: Jan 15, 2025 10:00 AM UTC 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: { uid: "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: { uid: "event2", title: "Test Event", calId: "667037022b752d0026472254/cal1", start: day.toISOString(), end: day.toISOString(), organizer: { cn: "test", cal_address: "test@test.com" }, }, event3: { uid: "event3", calId: "667037022b752d0026472254/cal1", start: day.toISOString(), end: day.toISOString(), organizer: { cn: "test", cal_address: "test@test.com" }, }, }, ownerEmails: ["test@test.com"], }, "otherCal/cal": { id: "otherCal/cal", name: "Calendar 1", color: "#FF0000", events: { event1: { uid: "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", () => { const originalToLocaleString = Date.prototype.toLocaleString; jest.spyOn(Date.prototype, "toLocaleString").mockImplementation(function ( this: Date, locales?: Intl.LocalesArgument, options?: Intl.DateTimeFormatOptions ) { return originalToLocaleString.call(this, "en-US", options); }); const originalToLocaleTimeString = Date.prototype.toLocaleTimeString; jest .spyOn(Date.prototype, "toLocaleTimeString") .mockImplementation(function ( this: Date, locales?: Intl.LocalesArgument, options?: Intl.DateTimeFormatOptions ) { return originalToLocaleTimeString.call(this, "en-US", options); }); renderWithProviders( , preloadedState ); // With UTC timezone, we can assert exact values // Date: January 15, 2025 10:00 AM UTC expect(screen.getByText("Test Event")).toBeInTheDocument(); expect(screen.getByText(/Wednesday/i)).toBeInTheDocument(); expect(screen.getByText(/January/i)).toBeInTheDocument(); expect(screen.getByText(/15/)).toBeInTheDocument(); // Check time is displayed with exact values // Format: "Wednesday, January 15, 2025 at 10:00 AM" and " – 10:00 AM" are in separate elements expect( screen.getByText(/Wednesday, January 15, 2025 at 10:00 AM/) ).toBeInTheDocument(); expect(screen.getByText(/– 10:00 AM/)).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 ); const moreButton = screen.getByTestId("MoreVertIcon"); if (moreButton) { expect( screen.queryByText("eventPreview.deleteEvent") ).not.toBeInTheDocument(); } cleanup(); // Renders the personal cal event renderWithProviders( , preloadedState ); fireEvent.click(screen.getByTestId("MoreVertIcon")); expect(screen.queryByText("eventPreview.deleteEvent")).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("MoreVertIcon")); fireEvent.click( screen.getByRole("menuitem", { name: "eventPreview.deleteEvent" }) ); 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("eventPreview.attendingQuestion") ).toBeInTheDocument(); expect( screen.getByRole("button", { name: "eventPreview.accept" }) ).toBeInTheDocument(); expect( screen.getByRole("button", { name: "eventPreview.maybe" }) ).toBeInTheDocument(); expect( screen.getByRole("button", { name: "eventPreview.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("eventPreview.attendingQuestion") ).not.toBeInTheDocument(); expect( screen.queryByRole("button", { name: "eventPreview.accept" }) ).not.toBeInTheDocument(); expect( screen.queryByRole("button", { name: "eventPreview.maybe" }) ).not.toBeInTheDocument(); expect( screen.queryByRole("button", { name: "eventPreview.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: "eventPreview.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: "eventPreview.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: "eventPreview.decline" }) ); await waitFor(() => { expect(spy).toHaveBeenCalled(); }); const updatedEvent = spy.mock.calls[0][0].newEvent; expect(updatedEvent.attendee[0].partstat).toBe("DECLINED"); }); it("handles Edit click", async () => { renderWithProviders( , preloadedState ); fireEvent.click(screen.getByTestId("EditIcon")); await waitFor(() => { expect(screen.getByText("event.updateEvent")).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 ); fireEvent.click(screen.getByTestId("MoreVertIcon")); expect(screen.getByText("eventPreview.emailAttendees")).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 ); fireEvent.click(screen.getByTestId("MoreVertIcon")); const emailButton = screen.getByRole("menuitem", { name: "eventPreview.emailAttendees", }); expect(emailButton).toBeInTheDocument(); fireEvent.click(emailButton); 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("Owner Email Permissions", () => { const today = new Date(); const start = new Date(today); start.setHours(10, 0, 0, 0); const end = new Date(today); end.setHours(11, 0, 0, 0); beforeEach(() => { jest.clearAllMocks(); const dispatch = jest.fn() as ThunkDispatch; jest.spyOn(appHooks, "useAppDispatch").mockReturnValue(dispatch); }); const createPreloadedState = ( userEmail: string, ownerEmails: string[], organizerEmail: string ) => ({ user: { userData: { sub: "test", email: userEmail, sid: "mockSid", openpaasId: "667037022b752d0026472254", }, tokens: { accessToken: "token", }, }, calendars: { list: { "667037022b752d0026472254/cal1": { name: "Calendar 1", id: "667037022b752d0026472254/cal1", color: "#FF0000", ownerEmails: ownerEmails, events: { event1: { uid: "event1", calId: "667037022b752d0026472254/cal1", title: "Test Event", start: start.toISOString(), end: end.toISOString(), partstat: "ACCEPTED", organizer: { cn: "Organizer", cal_address: organizerEmail, }, attendee: [ { cn: "Organizer", partstat: "ACCEPTED", rsvp: "TRUE", role: "REQ-PARTICIPANT", cutype: "INDIVIDUAL", cal_address: organizerEmail, }, ], }, }, }, }, pending: false, }, }); it("should show edit button when user email matches organizer and is in ownerEmails", async () => { const preloadedState = createPreloadedState( "owner@test.com", ["owner@test.com"], "owner@test.com" ); const mockOnClose = jest.fn(); renderWithProviders( , preloadedState ); await waitFor(() => { const editButton = screen.queryByTestId("EditIcon"); expect(editButton).toBeInTheDocument(); }); }); it("should NOT show edit button when user is organizer but not in owner of calendar", async () => { const preloadedState = createPreloadedState( "organizer@test.com", ["other@test.com"], // organizer not in list "organizer@test.com" ); const mockOnClose = jest.fn(); renderWithProviders( , preloadedState ); await waitFor(() => { const editButton = screen.queryByTestId("EditIcon"); expect(editButton).not.toBeInTheDocument(); }); }); it("should NOT show edit button when user is in ownerEmails but not organizer", async () => { const preloadedState = createPreloadedState( "owner@test.com", ["owner@test.com"], "other@test.com" // different organizer ); const mockOnClose = jest.fn(); renderWithProviders( , preloadedState ); await waitFor(() => { const editButton = screen.queryByTestId("EditIcon"); expect(editButton).not.toBeInTheDocument(); }); }); it("should handle calendars without ownerEmails property gracefully", async () => { const preloadedStateWithoutOwnerEmails = { 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 missing events: { event1: { calId: "667037022b752d0026472254/cal1", uid: "event1", title: "Test Event", start: start.toISOString(), end: end.toISOString(), organizer: { cn: "Test", cal_address: "test@test.com", }, }, }, }, }, pending: false, }, }; const mockOnClose = jest.fn(); expect(() => { renderWithProviders( , preloadedStateWithoutOwnerEmails ); }).not.toThrow(); }); }); it("renders correctly event data event with empty title", () => { renderWithProviders( , preloadedState ); expect(screen.getByText("event.untitled")).toBeInTheDocument(); expect(screen.getByText("Calendar")).toBeInTheDocument(); }); describe("BUGFIX", () => { it("doesnt render anything next to date of all day preview", () => { const allDayState = { ...preloadedState, calendars: { ...preloadedState.calendars, list: { ...preloadedState.calendars.list, "667037022b752d0026472254/cal1": { ...preloadedState.calendars.list["667037022b752d0026472254/cal1"], events: { event1: { ...preloadedState.calendars.list[ "667037022b752d0026472254/cal1" ].events.event1, allday: true, // <-- the fix condition }, }, }, }, }, }; renderWithProviders( , allDayState ); const dateElement = screen.getByText(/January/i).closest("p") ?? screen.getByText(/January/i); expect(dateElement).toBeInTheDocument(); console.log(dateElement.textContent); const text = dateElement.textContent?.trim() ?? ""; expect(text).toMatch(/\d{4}$/); }); }); }); describe("Event Full Display", () => { const mockOnClose = jest.fn(); const day = new Date("2025-01-15T10:00:00.000Z"); // Fixed date in UTC: Jan 15, 2025 10:00 AM UTC 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: { uid: "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: { uid: "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 with fixed timezone", () => { // Use fixed timezone UTC for consistent test results across all environments const fixedDate = new Date("2025-01-15T10:00:00.000Z"); // 10AM UTC const endDate = new Date(fixedDate.getTime() + 3600000); // 11AM UTC // With UTC timezone set, formatLocalDateTime produces predictable values const expectedStart = "2025-01-15T10:00"; // 10:00 AM UTC const expectedEnd = "2025-01-15T11:00"; // 11:00 AM UTC const stateWithFixedDate = { ...preloadedState, calendars: { list: { "667037022b752d0026472254/cal1": { ...preloadedState.calendars.list["667037022b752d0026472254/cal1"], events: { event1: { ...preloadedState.calendars.list[ "667037022b752d0026472254/cal1" ].events.event1, start: fixedDate.toISOString(), end: endDate.toISOString(), timezone: "UTC", }, }, }, }, }, }; renderWithProviders( , stateWithFixedDate ); expect(screen.getByDisplayValue("Test Event")).toBeInTheDocument(); const startDateInput = screen.getByTestId("start-date-input"); const startTimeInput = screen.getByTestId("start-time-input"); const endTimeInput = screen.getByTestId("end-time-input"); expect(startDateInput).toBeInTheDocument(); expect(startTimeInput).toBeInTheDocument(); expect(endTimeInput).toBeInTheDocument(); expect(startDateInput).toHaveValue( dayjs(expectedStart).format(LONG_DATE_FORMAT) ); expect(startTimeInput).toHaveValue(dayjs(expectedStart).format("HH:mm")); expect(endTimeInput).toHaveValue(dayjs(expectedEnd).format("HH:mm")); 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("toggle Show More reveals extra fields", async () => { renderWithProviders( , preloadedState ); act(() => { fireEvent.click( screen.getByRole("button", { name: "common.moreOptions" }) ); }); await waitFor(() => { expect(screen.getByText("event.form.notification")).toBeInTheDocument(); }); // Debug: Print DOM to see what's rendered console.log("DOM after Show More clicked:", document.body.innerHTML); // 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.getByRole("button", { name: /Show Less/i })); }); it("can edit title when user is organizer", () => { renderWithProviders( , preloadedState ); const titleField = screen.getByLabelText("event.form.title"); fireEvent.change(titleField, { target: { value: "New Title" } }); expect(screen.getByDisplayValue("New Title")).toBeInTheDocument(); }); it("toggle all-day updates end date correctly", () => { renderWithProviders( , preloadedState ); const allDayCheckbox = screen.getByLabelText("event.form.allDay"); fireEvent.click(allDayCheckbox); expect(allDayCheckbox).toBeChecked(); const expectedDate = dayjs(day).format(LONG_DATE_FORMAT); const startDateInput = screen.getByTestId("start-date-input"); const endDateInput = screen.getByTestId("end-date-input"); expect(startDateInput).toHaveValue(expectedDate); expect(endDateInput).toHaveValue(expectedDate); }); 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 testDate = new Date("2025-01-15T10:00:00.000Z"); const testEndDate = new Date("2025-01-15T11:00:00.000Z"); const preloadedTwoCals = { ...preloadedState, calendars: { list: { "667037022b752d0026472254/cal1": { id: "667037022b752d0026472254/cal1", name: "Calendar One", color: "#FF0000", events: { event1: { uid: "event1", title: "Test Event", calId: "667037022b752d0026472254/cal1", start: testDate.toISOString(), end: testEndDate.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, }, }; await act(async () => renderWithProviders( , preloadedTwoCals ) ); const calendarSelect = screen.getByLabelText("event.form.calendar"); await act(async () => fireEvent.mouseDown(calendarSelect)); const option = await screen.findByText("Calendar Two"); fireEvent.click(option); fireEvent.click(screen.getByRole("button", { name: "actions.save" })); await waitFor(() => { expect(spyPut).toHaveBeenCalled(); }); await waitFor(() => { expect(spyMove).toHaveBeenCalled(); }); expect(spyRemove).toHaveBeenCalled(); }); it("edit modal displays event time in original event timezone", () => { // GIVEN user timezone is UTC+2 // WHEN the user edits an event at 2PM UTC+7 (Asia/Bangkok) // THEN the update modal displays the time as 2PM in Asia/Bangkok timezone const eventDateUTC7 = new Date("2025-01-15T07:00:00.000Z"); // 7AM UTC = 2PM UTC+7 const stateWithTimezone = { ...preloadedState, calendars: { list: { "667037022b752d0026472254/cal1": { id: "667037022b752d0026472254/cal1", name: "Test Calendar", color: "#FF0000", events: { event1: { uid: "event1", title: "Timezone Edit Test", calId: "667037022b752d0026472254/cal1", start: eventDateUTC7.toISOString(), end: new Date(eventDateUTC7.getTime() + 3600000).toISOString(), timezone: "Asia/Bangkok", allday: false, organizer: { cn: "test", cal_address: "test@test.com" }, attendee: [{ cn: "test", cal_address: "test@test.com" }], }, }, }, }, pending: false, }, }; renderWithProviders( , stateWithTimezone ); // The timezone select should have Asia/Bangkok selected // Since the component uses formatLocalDateTime, the displayed time will be in local format // but the timezone selector should show Asia/Bangkok const timeZone = screen.getByDisplayValue(/Asia\/Bangkok/i); expect(timeZone).toBeInTheDocument(); }); it("InfoRow renders error style when error prop is true", () => { renderWithProviders(i} text="Bad" error />); expect(screen.getByText("Bad")).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("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("event.form.joinVisioConference") ).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.style.backgroundColor).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" ); }); });