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')
})
})