import { RootState } from '@/app/store' import { createEventHandlers, EventHandlersProps } from '@/components/Calendar/handlers/eventHandlers' import { EditModeDialog } from '@/components/Event/EditModeDialog' import * as eventThunks from '@/features/Calendars/services' import * as EventApi from '@/features/Events/EventApi' import EventPreviewModal from '@/features/Events/EventPreview' import EventUpdateModal from '@/features/Events/EventUpdateModal' import { act, fireEvent, screen, waitFor } from '@testing-library/react' import preview from 'jest-preview' import { renderWithProviders } from '../../utils/Renderwithproviders' jest.mock('@/components/Event/utils/eventUtils', () => { const actual = jest.requireActual('@/components/Event/utils/eventUtils') return { ...actual, refreshCalendars: jest.fn(() => Promise.resolve()), refreshSingularCalendar: jest.fn(() => Promise.resolve()) } }) const mockOnClose = jest.fn() const day = new Date('2025-03-15T10:00:00Z') const basePreloadedState = { 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: { 'recurring-base/20250315T100000': { uid: 'recurring-base/20250315T100000', title: 'Recurring Event Instance', calId: '667037022b752d0026472254/cal1', start: day.toISOString(), end: new Date('2025-03-15T11:00:00Z').toISOString(), organizer: { cn: 'test', cal_address: 'test@test.com' }, recurrenceId: '20250315T100000', repetition: { freq: 'weekly', occurrences: 4, interval: 1 }, timezone: 'UTC', URL: '/calendars/667037022b752d0026472254/cal1/recurring-base.ics', 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' } ] }, 'recurring-base/20250322T100000': { uid: 'recurring-base/20250322T100000', title: 'Recurring Event Instance', calId: '667037022b752d0026472254/cal1', start: new Date('2025-03-22T10:00:00Z').toISOString(), end: new Date('2025-03-22T11:00:00Z').toISOString(), organizer: { cn: 'test', cal_address: 'test@test.com' }, recurrenceId: '20250322T100000', timezone: 'UTC', URL: '/calendars/667037022b752d0026472254/cal1/recurring-base.ics', attendee: [ { cn: 'test', cal_address: 'test@test.com', partstat: 'NEEDS-ACTION', rsvp: 'TRUE', role: 'REQ-PARTICIPANT', cutype: 'INDIVIDUAL' } ] } }, owner: { emails: ['test@test.com'] } } }, pending: false, templist: {} } } as unknown as RootState describe('EditModeDialog Component', () => { beforeEach(() => { jest.clearAllMocks() }) it("renders dialog when type is 'edit'", async () => { const mockSetOpen = jest.fn() const mockEventAction = jest.fn() await act(async () => renderWithProviders( , basePreloadedState ) ) expect( screen.getByText('editModeDialog.updateRecurrentEvent') ).toBeInTheDocument() expect(screen.getByText('editModeDialog.thisEvent')).toBeInTheDocument() expect(screen.getByText('editModeDialog.allEvents')).toBeInTheDocument() }) it("renders dialog when type is 'attendance'", async () => { const mockSetOpen = jest.fn() const mockEventAction = jest.fn() await act(async () => renderWithProviders( , basePreloadedState ) ) expect( screen.getByText('editModeDialog.updateParticipationStatus') ).toBeInTheDocument() }) it("calls eventAction with 'solo' when solo option is selected and Ok clicked", async () => { const mockSetOpen = jest.fn() const mockEventAction = jest.fn() await act(async () => renderWithProviders( , basePreloadedState ) ) await act(async () => { fireEvent.click(screen.getByLabelText('editModeDialog.thisEvent')) fireEvent.click(screen.getByRole('button', { name: 'common.ok' })) }) await waitFor(() => { expect(mockEventAction).toHaveBeenCalledWith('solo') expect(mockSetOpen).toHaveBeenCalledWith(null) }) }) it("calls eventAction with 'all' when all option is selected and Ok clicked", async () => { const mockSetOpen = jest.fn() const mockEventAction = jest.fn() await act(async () => renderWithProviders( , basePreloadedState ) ) fireEvent.click(screen.getByLabelText('editModeDialog.allEvents')) fireEvent.click(screen.getByRole('button', { name: /Ok/i })) await waitFor(() => { expect(mockEventAction).toHaveBeenCalledWith('all') expect(mockSetOpen).toHaveBeenCalledWith(null) }) }) it('calls setOpen with null when Cancel is clicked', async () => { const mockSetOpen = jest.fn() const mockEventAction = jest.fn() await act(async () => renderWithProviders( , basePreloadedState ) ) fireEvent.click(screen.getByText('common.cancel')) await waitFor(() => { expect(mockSetOpen).toHaveBeenCalledWith(null) expect(mockEventAction).not.toHaveBeenCalled() }) }) }) describe('EventPreviewModal - Recurring Event Interactions', () => { beforeEach(() => { jest.clearAllMocks() }) it('shows EditModeDialog when editing a recurring event', async () => { jest.spyOn(eventThunks, 'getEventAsync').mockImplementation(payload => { return () => Promise.resolve({ calId: payload.calId, event: basePreloadedState.calendars.list['667037022b752d0026472254/cal1'] .events['recurring-base/20250315T100000'] }) as any }) await act(async () => renderWithProviders( , basePreloadedState ) ) fireEvent.click(screen.getByTestId('EditIcon')) await waitFor(() => { expect( screen.getByText('editModeDialog.updateRecurrentEvent') ).toBeInTheDocument() }) }) it('shows EditModeDialog when deleting a recurring event', async () => { await act(async () => renderWithProviders( , basePreloadedState ) ) fireEvent.click(screen.getByTestId('MoreVertIcon')) fireEvent.click( screen.getByRole('menuitem', { name: 'eventPreview.deleteEvent' }) ) await waitFor(() => { expect( screen.getByText('editModeDialog.deleteRecurrentEvent') ).toBeInTheDocument() }) }) it('shows EditModeDialog when RSVP to a recurring event', async () => { await act(async () => renderWithProviders( , basePreloadedState ) ) fireEvent.click( screen.getByRole('button', { name: 'eventPreview.ACCEPTED' }) ) await waitFor(() => { expect( screen.getByText('editModeDialog.updateParticipationStatus') ).toBeInTheDocument() }) }) it('does not show EditModeDialog for non-recurring events', async () => { const nonRecurringState = { ...basePreloadedState, calendars: { ...basePreloadedState.calendars, list: { '667037022b752d0026472254/cal1': { ...basePreloadedState.calendars.list[ '667037022b752d0026472254/cal1' ], events: { 'single-event': { uid: 'single-event', title: 'Single Event', calId: '667037022b752d0026472254/cal1', start: day.toISOString(), end: new Date('2025-03-15T11:00:00Z').toISOString(), organizer: { cn: 'test', cal_address: 'test@test.com' }, timezone: 'UTC', URL: '/calendars/667037022b752d0026472254/cal1/single-event.ics', attendee: [] } } } } } } const spy = jest .spyOn(eventThunks, 'deleteEventAsync') .mockImplementation(payload => { return () => Promise.resolve(payload) as any }) await act(async () => renderWithProviders( , nonRecurringState ) ) fireEvent.click(screen.getByTestId('MoreVertIcon')) fireEvent.click( screen.getByRole('menuitem', { name: 'eventPreview.deleteEvent' }) ) await waitFor(() => { expect(spy).toHaveBeenCalled() expect(mockOnClose).toHaveBeenCalled() }) expect( screen.queryByText('editModeDialog.deleteRecurrentEvent') ).not.toBeInTheDocument() }) }) describe('Delete Recurring Event Instance', () => { beforeEach(() => { jest.clearAllMocks() }) it('calls deleteEventInstanceAsync when deleting single instance', async () => { const spy = jest .spyOn(eventThunks, 'deleteEventInstanceAsync') .mockImplementation(payload => { return () => Promise.resolve(payload) as any }) await act(async () => renderWithProviders( , basePreloadedState ) ) fireEvent.click(screen.getByTestId('MoreVertIcon')) fireEvent.click( screen.getByRole('menuitem', { name: 'eventPreview.deleteEvent' }) ) await waitFor(() => { expect( screen.getByText('editModeDialog.deleteRecurrentEvent') ).toBeInTheDocument() }) fireEvent.click(screen.getByLabelText('editModeDialog.thisEvent')) fireEvent.click(screen.getByRole('button', { name: /Ok/i })) await waitFor(() => { expect(spy).toHaveBeenCalled() }) const receivedPayload = spy.mock.calls[0][0] expect(receivedPayload.event.uid).toBe('recurring-base/20250315T100000') }) it('calls deleteEventAsync when deleting all instances', async () => { jest.spyOn(EventApi, 'getEvent').mockResolvedValue({ ...basePreloadedState.calendars.list['667037022b752d0026472254/cal1'] .events['recurring-base/20250315T100000'], uid: 'recurring-base' } as any) const spy = jest .spyOn(eventThunks, 'deleteEventAsync') .mockImplementation(payload => { return () => Promise.resolve(payload) as any }) await act(async () => renderWithProviders( , basePreloadedState ) ) fireEvent.click(screen.getByTestId('MoreVertIcon')) fireEvent.click( screen.getByRole('menuitem', { name: 'eventPreview.deleteEvent' }) ) await waitFor(() => { expect( screen.getByText('editModeDialog.deleteRecurrentEvent') ).toBeInTheDocument() }) fireEvent.click(screen.getByLabelText('editModeDialog.allEvents')) fireEvent.click(screen.getByRole('button', { name: /Ok/i })) await waitFor(() => { expect(spy).toHaveBeenCalled() }) const receivedPayload = spy.mock.calls[0][0] expect(receivedPayload.eventId).toBe('recurring-base/20250315T100000') }) }) describe('RSVP to Recurring Event', () => { beforeEach(() => { jest.clearAllMocks() }) it('calls updateEventInstanceAsync when accepting single instance', async () => { const spy = jest .spyOn(eventThunks, 'updateEventInstanceAsync') .mockImplementation(payload => { const promise = Promise.resolve(payload) ;(promise as any).unwrap = () => promise return () => promise as any }) await act(async () => renderWithProviders( , basePreloadedState ) ) fireEvent.click( screen.getByRole('button', { name: 'eventPreview.ACCEPTED' }) ) await waitFor(() => { expect( screen.getByText('editModeDialog.updateParticipationStatus') ).toBeInTheDocument() }) fireEvent.click(screen.getByLabelText('editModeDialog.thisEvent')) fireEvent.click(screen.getByRole('button', { name: /Ok/i })) await waitFor(() => { expect(spy).toHaveBeenCalled() }) const updatedEvent = spy.mock.calls[0][0].event expect(updatedEvent.attendee[0].partstat).toBe('ACCEPTED') }) it('calls updateSeriesPartstat when accepting all instances', async () => { const spy = jest .spyOn(EventApi, 'updateSeriesPartstat') .mockResolvedValue({} as any) await act(async () => renderWithProviders( , basePreloadedState ) ) fireEvent.click( screen.getByRole('button', { name: 'eventPreview.ACCEPTED' }) ) await waitFor(() => { expect( screen.getByText('editModeDialog.updateParticipationStatus') ).toBeInTheDocument() }) fireEvent.click(screen.getByLabelText('editModeDialog.allEvents')) fireEvent.click(screen.getByRole('button', { name: 'common.ok' })) await waitFor(() => { expect(spy).toHaveBeenCalled() }) const callArgs = spy.mock.calls[0] expect(callArgs[0].uid).toBe('recurring-base/20250315T100000') expect(callArgs[1]).toBe('test@test.com') expect(callArgs[2]).toBe('ACCEPTED') }) }) describe('Edit Recurring Event in Full Display', () => { beforeEach(() => { jest.clearAllMocks() }) it('renders event with recurrenceId in URL', async () => { await act(async () => renderWithProviders( , basePreloadedState ) ) expect( screen.getByDisplayValue('Recurring Event Instance') ).toBeInTheDocument() }) it("calls updateEventInstanceAsync when saving single instance with typeOfAction='solo'", async () => { jest.spyOn(EventApi, 'getEvent').mockResolvedValue({ ...basePreloadedState.calendars.list['667037022b752d0026472254/cal1'] .events['recurring-base/20250315T100000'] } as any) const spy = jest .spyOn(eventThunks, 'updateEventInstanceAsync') .mockImplementation(payload => { return () => { const promise = Promise.resolve() ;(promise as any).unwrap = () => promise return promise as any } }) await act(async () => renderWithProviders( , basePreloadedState ) ) const titleField = screen.getByLabelText('event.form.title') fireEvent.change(titleField, { target: { value: 'Updated Single Instance' } }) // Click Save fireEvent.click(screen.getByText('actions.save')) await waitFor(() => { expect(spy).toHaveBeenCalled() }) const updatedEvent = spy.mock.calls[0][0].event expect(updatedEvent.title).toBe('Updated Single Instance') expect(updatedEvent.recurrenceId).toBe('20250315T100000') }) it("calls updateSeriesAsync when saving all instances with typeOfAction='all'", async () => { const getEventSpy = jest.spyOn(EventApi, 'getEvent').mockResolvedValue({ ...basePreloadedState.calendars.list['667037022b752d0026472254/cal1'] .events['recurring-base/20250315T100000'] } as any) const spy = jest .spyOn(eventThunks, 'updateSeriesAsync') .mockImplementation(payload => { return () => { const promise = Promise.resolve() ;(promise as any).unwrap = () => promise return promise as any } }) await act(async () => renderWithProviders( , basePreloadedState ) ) await waitFor(() => { expect(getEventSpy).toHaveBeenCalled() }) const titleField = screen.getByDisplayValue('Recurring Event Instance') fireEvent.change(titleField, { target: { value: 'Updated All Instances' } }) fireEvent.click(screen.getByRole('button', { name: 'actions.save' })) await waitFor(() => { expect(spy).toHaveBeenCalled() }) const updatedEvent = spy.mock.calls[0][0].event expect(updatedEvent.title).toBe('Updated All Instances') }) it("disables repetition editing when typeOfAction='solo'", async () => { await act(async () => renderWithProviders( , basePreloadedState ) ) fireEvent.click(screen.getByRole('button', { name: 'common.moreOptions' })) expect(screen.getByText('event.repeat.every')).toBeInTheDocument() expect(screen.getByText('event.repeat.end.label')).toBeInTheDocument() const frequencySelect = screen.getByRole('radio', { name: /event.repeat.end.after \d event.repeat.end.occurrences/i }) expect(frequencySelect).toBeDisabled() }) it("fetches master event data when typeOfAction='all'", async () => { const getEventSpy = jest.spyOn(EventApi, 'getEvent').mockResolvedValue({ ...basePreloadedState.calendars.list['667037022b752d0026472254/cal1'] .events['recurring-base/20250315T100000'], uid: 'recurring-base', title: 'Master Event Title', description: 'Master Description' } as any) await act(async () => renderWithProviders( , basePreloadedState ) ) await waitFor(() => { expect(getEventSpy).toHaveBeenCalled() expect(screen.getByDisplayValue('Master Event Title')).toBeInTheDocument() }) }) }) describe('Event Drag and Drop - Recurring Events', () => { beforeEach(() => { jest.clearAllMocks() }) it('shows EditModeDialog when dragging recurring event', () => { const mockDispatch = jest.fn() const mockSetSelectedEvent = jest.fn() const mockSetOpenEditModePopup = jest.fn() const mockSetAfterChoiceFunc = jest.fn() const eventHandlers = createEventHandlers({ setSelectedRange: jest.fn(), setOpenEventModal: jest.fn(), setTempEvent: jest.fn(), setOpenEventDisplay: jest.fn(), dispatch: mockDispatch, calendarRange: { start: new Date(), end: new Date() }, setEventDisplayedId: jest.fn(), setEventDisplayedCalId: jest.fn(), setEventDisplayedTemp: jest.fn(), calendars: basePreloadedState.calendars.list, setSelectedEvent: mockSetSelectedEvent, setAfterChoiceFunc: mockSetAfterChoiceFunc, setOpenEditModePopup: mockSetOpenEditModePopup } as unknown as EventHandlersProps) const mockArg = { event: { _def: { extendedProps: { uid: 'recurring-base/20250315T100000', calId: '667037022b752d0026472254/cal1' } } }, delta: { years: 0, months: 0, days: 1, milliseconds: 0 } } eventHandlers.handleEventDrop(mockArg) expect(mockSetSelectedEvent).toHaveBeenCalled() expect(mockSetOpenEditModePopup).toHaveBeenCalledWith('edit') expect(mockSetAfterChoiceFunc).toHaveBeenCalled() }) }) describe('Event Resize - Recurring Events', () => { beforeEach(() => { jest.clearAllMocks() }) it('shows EditModeDialog when resizing recurring event', () => { const mockDispatch = jest.fn() const mockSetSelectedEvent = jest.fn() const mockSetOpenEditModePopup = jest.fn() const mockSetAfterChoiceFunc = jest.fn() const eventHandlers = createEventHandlers({ setSelectedRange: jest.fn(), setOpenEventModal: jest.fn(), setTempEvent: jest.fn(), setOpenEventDisplay: jest.fn(), dispatch: mockDispatch, calendarRange: { start: new Date(), end: new Date() }, setEventDisplayedId: jest.fn(), setEventDisplayedCalId: jest.fn(), setEventDisplayedTemp: jest.fn(), calendars: basePreloadedState.calendars.list, setSelectedEvent: mockSetSelectedEvent, setAfterChoiceFunc: mockSetAfterChoiceFunc, setOpenEditModePopup: mockSetOpenEditModePopup } as unknown as EventHandlersProps) const mockArg = { event: { _def: { extendedProps: { uid: 'recurring-base/20250315T100000', calId: '667037022b752d0026472254/cal1' } } }, startDelta: { years: 0, months: 0, days: 0, milliseconds: 0 }, endDelta: { years: 0, months: 0, days: 0, milliseconds: 3600000 } // 1 hour } eventHandlers.handleEventResize(mockArg) expect(mockSetSelectedEvent).toHaveBeenCalled() expect(mockSetOpenEditModePopup).toHaveBeenCalledWith('edit') expect(mockSetAfterChoiceFunc).toHaveBeenCalled() }) }) describe('RepeatEvent Component - Recurrence Editing', () => { beforeEach(() => { jest.clearAllMocks() }) it('disables repetition fields when isOwn is false', async () => { await act(async () => renderWithProviders( , { ...basePreloadedState, calendars: { ...basePreloadedState.calendars, list: { 'otherCal/cal': { id: 'otherCal/cal', name: 'Other Calendar', color: '#00FF00', events: { 'recurring-base/20250315T100000': { ...basePreloadedState.calendars.list[ '667037022b752d0026472254/cal1' ].events['recurring-base/20250315T100000'], calId: 'otherCal/cal', organizer: { cn: 'other', cal_address: 'other@test.com' } } } } } } } ) ) fireEvent.click(screen.getByRole('button', { name: 'common.moreOptions' })) // Repetition section should not be visible for non-owner expect( screen.queryByLabelText('event.form.repetition') ).not.toBeInTheDocument() }) }) describe('handleRSVP function', () => { beforeEach(() => { jest.clearAllMocks() }) it('calls putEventAsync for non-recurring events', async () => { const mockDispatch = jest.fn() const mockOnClose = jest.fn() const { handleRSVP } = require('@/components/Event/eventHandlers/eventHandlers') jest.spyOn(eventThunks, 'putEventAsync').mockImplementation(payload => { const promise = Promise.resolve(payload) ;(promise as any).unwrap = () => promise return () => promise as any }) const nonRecurringEvent = { uid: 'single-event', title: 'Single Event', calId: '667037022b752d0026472254/cal1', start: day.toISOString(), end: new Date('2025-03-15T11:00:00Z').toISOString(), organizer: { cn: 'test', cal_address: 'test@test.com' }, attendee: [ { cn: 'test', cal_address: 'test@test.com', partstat: 'NEEDS-ACTION' } ] } await handleRSVP( mockDispatch, basePreloadedState.calendars.list['667037022b752d0026472254/cal1'], basePreloadedState.user, nonRecurringEvent, 'ACCEPTED' ) expect(mockDispatch).toHaveBeenCalled() }) }) describe('handleDelete function', () => { beforeEach(() => { jest.clearAllMocks() }) it('calls deleteEventAsync for non-recurring events', () => { const mockDispatch = jest.fn() const mockOnClose = jest.fn() const { handleDelete } = require('@/components/Event/eventHandlers/eventHandlers') jest.spyOn(eventThunks, 'deleteEventAsync').mockImplementation(payload => { return () => Promise.resolve(payload) as any }) const nonRecurringEvent = { uid: 'single-event', title: 'Single Event', calId: '667037022b752d0026472254/cal1', URL: '/calendars/667037022b752d0026472254/cal1/single-event.ics' } handleDelete( false, // isRecurring undefined, mockOnClose, mockDispatch, basePreloadedState.calendars.list['667037022b752d0026472254/cal1'], nonRecurringEvent, '667037022b752d0026472254/cal1', 'single-event' ) expect(mockOnClose).toHaveBeenCalledWith({}, 'backdropClick') expect(mockDispatch).toHaveBeenCalled() }) it('calls deleteEventInstanceAsync when deleting solo recurring event', () => { const mockDispatch = jest.fn() const mockOnClose = jest.fn() const { handleDelete } = require('@/components/Event/eventHandlers/eventHandlers') jest .spyOn(eventThunks, 'deleteEventInstanceAsync') .mockImplementation(payload => { return () => Promise.resolve(payload) as any }) handleDelete( true, // isRecurring 'solo', mockOnClose, mockDispatch, basePreloadedState.calendars.list['667037022b752d0026472254/cal1'], basePreloadedState.calendars.list['667037022b752d0026472254/cal1'].events[ 'recurring-base/20250315T100000' ], '667037022b752d0026472254/cal1', 'recurring-base/20250315T100000' ) expect(mockOnClose).toHaveBeenCalledWith({}, 'backdropClick') expect(mockDispatch).toHaveBeenCalled() }) it('calls deleteEventAsync when deleting all recurring events', () => { const mockDispatch = jest.fn() const mockOnClose = jest.fn() const { handleDelete } = require('@/components/Event/eventHandlers/eventHandlers') jest.spyOn(eventThunks, 'deleteEventAsync').mockImplementation(payload => { return () => Promise.resolve(payload) as any }) handleDelete( true, // isRecurring 'all', mockOnClose, mockDispatch, basePreloadedState.calendars.list['667037022b752d0026472254/cal1'], basePreloadedState.calendars.list['667037022b752d0026472254/cal1'].events[ 'recurring-base/20250315T100000' ], '667037022b752d0026472254/cal1', 'recurring-base/20250315T100000' ) expect(mockOnClose).toHaveBeenCalledWith({}, 'backdropClick') expect(mockDispatch).toHaveBeenCalled() }) }) describe('Calendar Integration - EditModeDialog Flow', () => { beforeEach(() => { jest.clearAllMocks() }) it('passes correct eventId when editing all instances from preview', async () => { await act(async () => renderWithProviders( , basePreloadedState ) ) // Click edit fireEvent.click(screen.getByTestId('EditIcon')) await waitFor(() => { expect( screen.getByText('editModeDialog.updateRecurrentEvent') ).toBeInTheDocument() }) // Select "All the events" fireEvent.click(screen.getByLabelText('editModeDialog.allEvents')) fireEvent.click(screen.getByRole('button', { name: /Ok/i })) await waitFor(() => { // Update modal should open expect(screen.getByText('event.updateEvent')).toBeInTheDocument() }) }) }) describe('Event URL handling for recurring events', () => { beforeEach(() => { jest.clearAllMocks() }) it('uses base ID for event URL when moving recurring event', async () => { const moveEventSpy = jest .spyOn(eventThunks, 'moveEventAsync') .mockImplementation(payload => { const result = { calId: payload.cal.id, events: [] } const promise = Promise.resolve(result) ;(promise as any).unwrap = () => promise return () => promise as any }) const twoCalState = { 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: { 'recurring-base/20250315T100000': { uid: 'recurring-base/20250315T100000', title: 'Recurring Event Instance', calId: '667037022b752d0026472254/cal1', start: day.toISOString(), end: new Date('2025-03-15T11:00:00Z').toISOString(), organizer: { cn: 'test', cal_address: 'test@test.com' }, recurrenceId: '20250315T100000', repetition: { freq: 'weekly', occurrences: 4, interval: 1 }, timezone: 'UTC', URL: '/calendars/667037022b752d0026472254/cal1/recurring-base.ics', 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' } ] }, 'recurring-base/20250322T100000': { uid: 'recurring-base/20250322T100000', title: 'Recurring Event Instance', calId: '667037022b752d0026472254/cal1', start: new Date('2025-03-22T10:00:00Z').toISOString(), end: new Date('2025-03-22T11:00:00Z').toISOString(), organizer: { cn: 'test', cal_address: 'test@test.com' }, recurrenceId: '20250322T100000', timezone: 'UTC', URL: '/calendars/667037022b752d0026472254/cal1/recurring-base.ics', attendee: [ { cn: 'test', cal_address: 'test@test.com', partstat: 'NEEDS-ACTION', rsvp: 'TRUE', role: 'REQ-PARTICIPANT', cutype: 'INDIVIDUAL' } ] } }, owner: { emails: ['test@test.com'] } }, '667037022b752d0026472254/cal2': { id: '667037022b752d0026472254/cal2', name: 'Calendar 2', color: '#00FF00', events: {} } }, pending: false, templist: {} } } await act(async () => renderWithProviders( , twoCalState ) ) // Expand to show calendar combobox (normal mode shows SectionPreviewRow) fireEvent.click(screen.getByRole('button', { name: 'common.moreOptions' })) fireEvent.mouseDown(screen.getByLabelText('event.form.calendar')) const option = await screen.findByText('Calendar 2') fireEvent.click(option) // Click Save button const saveButton = screen.getByRole('button', { name: 'actions.save' }) await act(async () => { fireEvent.click(saveButton) // Wait for the 500ms delay in the code await new Promise(resolve => setTimeout(resolve, 600)) }) await waitFor(() => { expect(moveEventSpy).toHaveBeenCalled() }) const movePayload = moveEventSpy.mock.calls[0][0] // Should use base ID, not full uid with recurrence ID expect(movePayload.newURL).toContain('recurring-base.ics') expect(movePayload.newURL).not.toContain('/20250315T100000') }) })