4e57f6b342
* draft for tablet view * adapt sidebar to tablet view * split menubar to handle tablet and desktop separatly * split sidebar to handle tablet and desktop separatly * prettier day navigationin tablet view * added missing translations * coderabbit comments * fix UI of view switcher on tablet, remove hard text * fix style of selected view option * fix wrong I18n key * extracted menubar recurring components to own files * adjust test case of calendar component * refactor function change calendar view Co-authored-by: Camille Moussu <cmoussu@linagora.com> Co-authored-by: lethemanh <lethemanh@lethemanhs-MacBook-Pro.local>
791 lines
23 KiB
TypeScript
791 lines
23 KiB
TypeScript
import CalendarApp from '@/components/Calendar/Calendar'
|
|
import CalendarLayout from '@/components/Calendar/CalendarLayout'
|
|
import { CALENDAR_VIEWS } from '@/components/Calendar/utils/constants'
|
|
import * as calendarDetailThunks from '@/features/Calendars/services'
|
|
import * as servicesModule from '@/features/Calendars/services'
|
|
import { searchUsers } from '@/features/User/userAPI'
|
|
import { act, fireEvent, screen, waitFor } from '@testing-library/react'
|
|
import userEvent from '@testing-library/user-event'
|
|
import { useRef } from 'react'
|
|
import { renderWithProviders } from '../utils/Renderwithproviders'
|
|
|
|
jest.mock('@/features/User/userAPI')
|
|
const mockedSearchUsers = searchUsers as jest.MockedFunction<typeof searchUsers>
|
|
|
|
// Test wrapper component to provide calendarRef
|
|
function CalendarTestWrapper() {
|
|
const calendarRef = useRef(null)
|
|
return (
|
|
<CalendarApp
|
|
setCurrentView={jest.fn()}
|
|
onViewChange={jest.fn()}
|
|
openSidebar={false}
|
|
onCloseSidebar={jest.fn()}
|
|
currentView="timeGridWeek"
|
|
calendarRef={calendarRef}
|
|
/>
|
|
)
|
|
}
|
|
|
|
describe('CalendarSelection', () => {
|
|
beforeEach(() => {
|
|
localStorage.clear()
|
|
})
|
|
afterEach(() => {
|
|
jest.useRealTimers()
|
|
})
|
|
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)
|
|
const preloadedState = {
|
|
user: {
|
|
userData: {
|
|
sub: 'test',
|
|
email: 'test@test.com',
|
|
sid: 'mockSid',
|
|
openpaasId: 'user1'
|
|
},
|
|
tokens: { accessToken: 'token' } // required to avoid redirect
|
|
},
|
|
calendars: {
|
|
list: {
|
|
'user1/cal1': {
|
|
name: 'Calendar personal',
|
|
id: 'user1/cal1',
|
|
color: { light: '#FF0000', dark: '#000' },
|
|
owner: { emails: ['alice@example.com'] },
|
|
events: {
|
|
event1: {
|
|
id: 'event1',
|
|
calId: 'user1/cal1',
|
|
uid: 'event1',
|
|
title: 'Test Event',
|
|
start: start.toISOString(),
|
|
end: end.toISOString(),
|
|
partstat: 'ACCEPTED',
|
|
organizer: {
|
|
cn: 'Alice',
|
|
cal_address: 'alice@example.com'
|
|
},
|
|
attendee: [
|
|
{
|
|
cn: 'Alice',
|
|
partstat: 'ACCEPTED',
|
|
rsvp: 'TRUE',
|
|
role: 'REQ-PARTICIPANT',
|
|
cutype: 'INDIVIDUAL',
|
|
cal_address: 'alice@example.com'
|
|
}
|
|
]
|
|
}
|
|
}
|
|
},
|
|
'user2/cal1': {
|
|
name: 'Calendar delegated',
|
|
delegated: true,
|
|
id: 'user2/cal1',
|
|
color: { light: '#FF0000', dark: '#000' },
|
|
owner: { emails: ['alice@example.com'], lastname: 'alice' },
|
|
events: {
|
|
event1: {
|
|
id: 'event1',
|
|
calId: 'user2/cal1',
|
|
uid: 'event1',
|
|
title: 'Test Event',
|
|
start: start.toISOString(),
|
|
end: end.toISOString(),
|
|
partstat: 'ACCEPTED',
|
|
organizer: {
|
|
cn: 'Alice',
|
|
cal_address: 'alice@example.com'
|
|
},
|
|
attendee: [
|
|
{
|
|
cn: 'Alice',
|
|
partstat: 'ACCEPTED',
|
|
rsvp: 'TRUE',
|
|
role: 'REQ-PARTICIPANT',
|
|
cutype: 'INDIVIDUAL',
|
|
cal_address: 'alice@example.com'
|
|
}
|
|
]
|
|
}
|
|
}
|
|
},
|
|
'user3/cal1': {
|
|
name: 'Calendar shared',
|
|
id: 'user3/cal1',
|
|
color: { light: '#FF0000', dark: '#000' },
|
|
owner: { emails: ['alice@example.com'], lastname: 'alice' },
|
|
events: {
|
|
event1: {
|
|
id: 'event1',
|
|
calId: 'user3/cal1',
|
|
uid: 'event1',
|
|
title: 'Test Event',
|
|
start: start.toISOString(),
|
|
end: end.toISOString(),
|
|
partstat: 'ACCEPTED',
|
|
organizer: {
|
|
cn: 'Alice',
|
|
cal_address: 'alice@example.com'
|
|
},
|
|
attendee: [
|
|
{
|
|
cn: 'Alice',
|
|
partstat: 'ACCEPTED',
|
|
rsvp: 'TRUE',
|
|
role: 'REQ-PARTICIPANT',
|
|
cutype: 'INDIVIDUAL',
|
|
cal_address: 'alice@example.com'
|
|
}
|
|
]
|
|
}
|
|
}
|
|
}
|
|
},
|
|
pending: true
|
|
}
|
|
}
|
|
it('renders calendars', async () => {
|
|
const mockCalendarRef = { current: null }
|
|
await act(async () => {
|
|
renderWithProviders(
|
|
<CalendarApp
|
|
setCurrentView={jest.fn()}
|
|
onViewChange={jest.fn()}
|
|
openSidebar={false}
|
|
onCloseSidebar={jest.fn()}
|
|
currentView="timeGridWeek"
|
|
calendarRef={mockCalendarRef}
|
|
/>,
|
|
preloadedState
|
|
)
|
|
})
|
|
expect(screen.getByText('calendar.personal')).toBeInTheDocument()
|
|
expect(screen.getByText('calendar.delegated')).toBeInTheDocument()
|
|
expect(screen.getByText('calendar.other')).toBeInTheDocument()
|
|
|
|
expect(screen.getByLabelText('Calendar personal')).toBeInTheDocument()
|
|
expect(screen.getByLabelText('Calendar delegated')).toBeInTheDocument()
|
|
expect(screen.getByLabelText('Calendar shared')).toBeInTheDocument()
|
|
})
|
|
it('open accordeon when clicking on button only', async () => {
|
|
const mockCalendarRef = { current: null }
|
|
await act(async () => {
|
|
renderWithProviders(
|
|
<CalendarApp
|
|
setCurrentView={jest.fn()}
|
|
onViewChange={jest.fn()}
|
|
openSidebar={false}
|
|
onCloseSidebar={jest.fn()}
|
|
currentView="timeGridWeek"
|
|
calendarRef={mockCalendarRef}
|
|
/>,
|
|
preloadedState
|
|
)
|
|
})
|
|
expect(screen.getByText('calendar.personal')).toBeInTheDocument()
|
|
expect(screen.getByText('calendar.delegated')).toBeInTheDocument()
|
|
expect(screen.getByText('calendar.other')).toBeInTheDocument()
|
|
|
|
expect(screen.getByLabelText('Calendar personal')).toBeInTheDocument()
|
|
expect(screen.getByLabelText('Calendar delegated')).toBeInTheDocument()
|
|
expect(screen.getByLabelText('Calendar shared')).toBeInTheDocument()
|
|
|
|
const sharedAccordionSummary = screen
|
|
.getByText('calendar.other')
|
|
.closest('.MuiAccordionSummary-root')
|
|
|
|
const addButton = screen.getAllByTestId('AddIcon')[2]
|
|
await act(async () => {
|
|
fireEvent.click(addButton)
|
|
})
|
|
expect(sharedAccordionSummary).toHaveAttribute('aria-expanded', 'true')
|
|
|
|
await act(async () => {
|
|
fireEvent.click(addButton)
|
|
})
|
|
expect(sharedAccordionSummary).toHaveAttribute('aria-expanded', 'true')
|
|
})
|
|
it('renders calendars with local storage', async () => {
|
|
const mockCalendarRef = { current: null }
|
|
localStorage.setItem(
|
|
'selectedCalendars',
|
|
JSON.stringify(Object.keys(preloadedState.calendars.list))
|
|
)
|
|
await act(async () => {
|
|
renderWithProviders(
|
|
<CalendarApp
|
|
setCurrentView={jest.fn()}
|
|
onViewChange={jest.fn()}
|
|
openSidebar={false}
|
|
onCloseSidebar={jest.fn()}
|
|
currentView="timeGridWeek"
|
|
calendarRef={mockCalendarRef}
|
|
/>,
|
|
preloadedState
|
|
)
|
|
})
|
|
|
|
expect(screen.getByLabelText('Calendar personal')).toBeChecked()
|
|
expect(screen.getByLabelText('Calendar delegated')).toBeChecked()
|
|
expect(screen.getByLabelText('Calendar shared')).toBeChecked()
|
|
})
|
|
it('persist selected calendars in local storage', async () => {
|
|
const mockCalendarRef = { current: null }
|
|
await act(async () => {
|
|
renderWithProviders(
|
|
<CalendarApp
|
|
setCurrentView={jest.fn()}
|
|
onViewChange={jest.fn()}
|
|
openSidebar={false}
|
|
onCloseSidebar={jest.fn()}
|
|
currentView="timeGridWeek"
|
|
calendarRef={mockCalendarRef}
|
|
/>,
|
|
preloadedState
|
|
)
|
|
})
|
|
|
|
expect(screen.getByLabelText('Calendar personal')).toBeChecked()
|
|
expect(screen.getByLabelText('Calendar delegated')).not.toBeChecked()
|
|
expect(screen.getByLabelText('Calendar shared')).not.toBeChecked()
|
|
|
|
await act(async () => {
|
|
fireEvent.click(screen.getByLabelText('Calendar personal'))
|
|
fireEvent.click(screen.getByLabelText('Calendar shared'))
|
|
})
|
|
|
|
expect(localStorage.getItem('selectedCalendars')).toBe('["user3/cal1"]')
|
|
})
|
|
})
|
|
|
|
describe('calendar Availability search', () => {
|
|
beforeEach(() => {
|
|
localStorage.clear()
|
|
})
|
|
afterEach(() => {
|
|
jest.useRealTimers()
|
|
})
|
|
const preloadedState = {
|
|
user: {
|
|
userData: {
|
|
sub: 'test',
|
|
email: 'test@test.com',
|
|
sid: 'mockSid',
|
|
openpaasId: 'user1'
|
|
},
|
|
organiserData: {
|
|
cn: 'test',
|
|
cal_address: 'test@test.com'
|
|
},
|
|
tokens: { accessToken: 'token' }
|
|
},
|
|
calendars: {
|
|
list: {
|
|
'user1/cal1': {
|
|
name: 'Calendar personal',
|
|
id: 'user1/cal1',
|
|
color: { light: '#FF0000', dark: '#000' },
|
|
owner: { emails: ['alice@example.com'] },
|
|
events: {}
|
|
}
|
|
},
|
|
pending: true,
|
|
templist: {}
|
|
}
|
|
}
|
|
|
|
it('imports temporary calendars when selecting new users', async () => {
|
|
const spy = jest
|
|
.spyOn(servicesModule, 'getTempCalendarsListAsync')
|
|
.mockImplementation(payload => {
|
|
return () => Promise.resolve(payload) as any
|
|
})
|
|
mockedSearchUsers.mockResolvedValueOnce([
|
|
{
|
|
email: 'newuser@example.com',
|
|
displayName: 'New User',
|
|
avatarUrl: 'image.png',
|
|
openpaasId: '1234567890'
|
|
}
|
|
])
|
|
|
|
await act(async () =>
|
|
renderWithProviders(<CalendarTestWrapper />, preloadedState)
|
|
)
|
|
|
|
const input = screen.getByPlaceholderText(
|
|
'peopleSearch.availabilityPlaceholder'
|
|
)
|
|
act(() => {
|
|
userEvent.type(input, 'New')
|
|
})
|
|
const option = await screen.findByText('New User')
|
|
await act(async () => {
|
|
fireEvent.click(option)
|
|
})
|
|
|
|
expect(spy).toHaveBeenCalled()
|
|
})
|
|
|
|
it('open window with attendees filled after temp search on create event button click', async () => {
|
|
const spy = jest
|
|
.spyOn(servicesModule, 'getTempCalendarsListAsync')
|
|
.mockImplementation(payload => {
|
|
return () => Promise.resolve(payload) as any
|
|
})
|
|
mockedSearchUsers.mockResolvedValue([
|
|
{
|
|
email: 'newuser@example.com',
|
|
displayName: 'New User',
|
|
avatarUrl: 'image.png',
|
|
openpaasId: '1234567890'
|
|
}
|
|
])
|
|
|
|
await act(async () =>
|
|
renderWithProviders(<CalendarTestWrapper />, preloadedState)
|
|
)
|
|
|
|
const input = screen.getByPlaceholderText(
|
|
'peopleSearch.availabilityPlaceholder'
|
|
)
|
|
await act(async () => userEvent.type(input, 'New'))
|
|
|
|
const option = await screen.findByText('New User')
|
|
|
|
await act(async () => {
|
|
fireEvent.click(option)
|
|
})
|
|
expect(spy).toHaveBeenCalled()
|
|
await act(async () => {
|
|
fireEvent.click(screen.getByRole('button', { name: 'event.createEvent' }))
|
|
})
|
|
await waitFor(
|
|
() => {
|
|
expect(screen.getAllByText('event.createEvent')).toHaveLength(2)
|
|
expect(screen.getAllByText(/New User/i)).toHaveLength(2)
|
|
},
|
|
{ timeout: 10000 }
|
|
)
|
|
}, 15000)
|
|
|
|
it('open window with attendees filled after temp search on enter in temp input', async () => {
|
|
const spy = jest
|
|
.spyOn(servicesModule, 'getTempCalendarsListAsync')
|
|
.mockImplementation(payload => {
|
|
return () => Promise.resolve(payload) as any
|
|
})
|
|
mockedSearchUsers.mockResolvedValue([
|
|
{
|
|
email: 'newuser@example.com',
|
|
displayName: 'New User',
|
|
avatarUrl: 'image.png',
|
|
openpaasId: '1234567890'
|
|
}
|
|
])
|
|
|
|
await act(async () =>
|
|
renderWithProviders(<CalendarTestWrapper />, preloadedState)
|
|
)
|
|
|
|
const input = screen.getByPlaceholderText(
|
|
'peopleSearch.availabilityPlaceholder'
|
|
)
|
|
await act(async () => userEvent.type(input, 'New'))
|
|
|
|
const option = await screen.findByText('New User')
|
|
await act(async () => {
|
|
fireEvent.click(option)
|
|
})
|
|
expect(spy).toHaveBeenCalled()
|
|
await act(async () => {
|
|
fireEvent.keyDown(input, { key: 'Enter' })
|
|
})
|
|
|
|
await waitFor(
|
|
() => {
|
|
expect(screen.getAllByText('event.createEvent')).toHaveLength(2)
|
|
expect(screen.getAllByText(/New User/i)).toHaveLength(2)
|
|
},
|
|
{ timeout: 10000 }
|
|
)
|
|
}, 15000)
|
|
|
|
it('BUGFIX: can untoggle all calendar.personal', async () => {
|
|
jest
|
|
.spyOn(calendarDetailThunks, 'getCalendarDetailAsync')
|
|
.mockImplementation(
|
|
() =>
|
|
({
|
|
type: 'getCalendarDetailAsync',
|
|
unwrap: () => Promise.resolve({})
|
|
}) as any
|
|
)
|
|
await act(async () =>
|
|
renderWithProviders(<CalendarTestWrapper />, {
|
|
user: preloadedState.user,
|
|
calendars: {
|
|
list: { 'user1/cal1': preloadedState.calendars.list['user1/cal1'] }
|
|
}
|
|
})
|
|
)
|
|
|
|
const checkbox = screen.getByLabelText('Calendar personal')
|
|
expect(checkbox).toBeChecked()
|
|
|
|
fireEvent.click(checkbox) // toggle off
|
|
|
|
expect(checkbox).not.toBeChecked()
|
|
})
|
|
|
|
it("BUGFIX: monthview doesn't show days numbers in banner", async () => {
|
|
await act(async () =>
|
|
renderWithProviders(<CalendarLayout />, {
|
|
user: preloadedState.user,
|
|
calendars: {
|
|
list: { 'user1/cal1': preloadedState.calendars.list['user1/cal1'] },
|
|
pending: false
|
|
}
|
|
})
|
|
)
|
|
|
|
const calendarRef = window.__calendarRef
|
|
|
|
await waitFor(() => {
|
|
expect(calendarRef.current).not.toBeNull()
|
|
})
|
|
|
|
const calendarApi = calendarRef.current
|
|
expect(calendarApi).not.toBeNull()
|
|
await act(async () => {
|
|
calendarApi?.changeView(CALENDAR_VIEWS.dayGridMonth)
|
|
})
|
|
await waitFor(() => {
|
|
expect(screen.queryAllByRole('columnheader').length).toBe(14)
|
|
})
|
|
|
|
const dayNumbers = screen.getAllByRole('columnheader')
|
|
expect(dayNumbers.length).toBeGreaterThan(0)
|
|
|
|
const dayNumbersWithContent = dayNumbers.filter(cell => {
|
|
const dayNum = cell.querySelector(
|
|
'.fc-daygrid-day-number, .fc-daygrid-day-top'
|
|
)
|
|
return dayNum && dayNum.textContent && /\d/.test(dayNum.textContent)
|
|
})
|
|
expect(dayNumbersWithContent.length).toBe(0)
|
|
})
|
|
|
|
it('should fetch calendar details for February after navigating to month view and clicking next', async () => {
|
|
const spy = jest
|
|
.spyOn(calendarDetailThunks, 'getCalendarDetailAsync')
|
|
.mockImplementation(payload => {
|
|
const promise = Promise.resolve(payload)
|
|
const thunk = () => Object.assign(promise, { unwrap: () => promise })
|
|
return thunk as any
|
|
})
|
|
jest.useFakeTimers().setSystemTime(new Date('2025-01-01'))
|
|
|
|
await act(async () =>
|
|
renderWithProviders(<CalendarLayout />, {
|
|
...preloadedState,
|
|
calendars: { ...preloadedState.calendars, pending: false }
|
|
})
|
|
)
|
|
|
|
// Advance past debounce so the initial load fires
|
|
await act(async () => {
|
|
jest.advanceTimersByTime(300)
|
|
})
|
|
|
|
await waitFor(() => {
|
|
expect(spy).toHaveBeenCalled()
|
|
})
|
|
|
|
spy.mockClear()
|
|
|
|
const calendarRef = window.__calendarRef
|
|
const calendarApi = calendarRef.current
|
|
expect(calendarApi).not.toBeNull()
|
|
|
|
await act(async () => {
|
|
calendarApi?.changeView(CALENDAR_VIEWS.dayGridMonth)
|
|
fireEvent.click(screen.getByTestId('ChevronRightIcon'))
|
|
})
|
|
|
|
// Advance past debounce so the navigation fetch fires
|
|
await act(async () => {
|
|
jest.advanceTimersByTime(300)
|
|
})
|
|
|
|
await waitFor(() => {
|
|
expect(spy).toHaveBeenCalled()
|
|
})
|
|
|
|
// Find the call that covers February 2025 (the navigated-to month)
|
|
const februaryCall = spy.mock.calls.find(call => {
|
|
const start = call[0].match.start as string
|
|
// start should be in January or February 2025 (month view includes padding days)
|
|
return start.startsWith('2025')
|
|
})
|
|
|
|
expect(februaryCall).toBeDefined()
|
|
const callArgs = februaryCall![0]
|
|
expect(callArgs.calId).toBe('user1/cal1')
|
|
|
|
const parseDate = (s: string) =>
|
|
new Date(
|
|
s.replace(
|
|
/(\d{4})(\d{2})(\d{2})T(\d{2})(\d{2})(\d{2})/,
|
|
'$1-$2-$3T$4:$5:$6'
|
|
)
|
|
)
|
|
|
|
const startDate = parseDate(callArgs.match.start)
|
|
const endDate = parseDate(callArgs.match.end)
|
|
|
|
// February 2025 month view: range must cover Feb 1 through Mar 1
|
|
expect(startDate.getTime()).toBeLessThanOrEqual(
|
|
new Date('2025-02-01').getTime()
|
|
)
|
|
expect(endDate.getTime()).toBeGreaterThanOrEqual(
|
|
new Date('2025-03-01').getTime()
|
|
)
|
|
})
|
|
|
|
describe('Batch loading and prefetching', () => {
|
|
beforeEach(() => {
|
|
jest.clearAllMocks()
|
|
})
|
|
|
|
it('loads active calendars in batches of 5', async () => {
|
|
const manyCalendars = Array.from({ length: 12 }, (_, i) => ({
|
|
[`user1/cal${i + 1}`]: {
|
|
name: `Calendar ${i + 1}`,
|
|
id: `user1/cal${i + 1}`,
|
|
color: { light: '#FF0000', dark: '#000' },
|
|
events: {}
|
|
}
|
|
})).reduce((acc, cal) => ({ ...acc, ...cal }), {})
|
|
|
|
const stateWithManyCalendars = {
|
|
...preloadedState,
|
|
calendars: {
|
|
...preloadedState.calendars,
|
|
list: manyCalendars,
|
|
pending: false
|
|
}
|
|
}
|
|
|
|
const spy = jest
|
|
.spyOn(calendarDetailThunks, 'getCalendarDetailAsync')
|
|
.mockImplementation(
|
|
() =>
|
|
({
|
|
type: 'getCalendarDetailAsync',
|
|
unwrap: () => Promise.resolve({})
|
|
}) as any
|
|
)
|
|
|
|
await act(async () => {
|
|
renderWithProviders(
|
|
<CalendarApp
|
|
setCurrentView={jest.fn()}
|
|
onViewChange={jest.fn()}
|
|
openSidebar={false}
|
|
onCloseSidebar={jest.fn()}
|
|
currentView="timeGridWeek"
|
|
calendarRef={{ current: null }}
|
|
/>,
|
|
stateWithManyCalendars
|
|
)
|
|
})
|
|
|
|
await waitFor(
|
|
() => {
|
|
expect(spy).toHaveBeenCalled()
|
|
},
|
|
{ timeout: 3000 }
|
|
)
|
|
|
|
const callCount = spy.mock.calls.length
|
|
expect(callCount).toBeGreaterThan(0)
|
|
expect(callCount).toBeLessThanOrEqual(36)
|
|
})
|
|
|
|
it('prefetches hidden calendars only after active load completes', async () => {
|
|
const calendarsWithSelected = {
|
|
...preloadedState,
|
|
calendars: {
|
|
...preloadedState.calendars,
|
|
list: {
|
|
'user1/cal1': {
|
|
name: 'Selected Calendar',
|
|
id: 'user1/cal1',
|
|
color: { light: '#FF0000', dark: '#000' },
|
|
events: {}
|
|
},
|
|
'user1/cal2': {
|
|
name: 'Hidden Calendar 1',
|
|
id: 'user1/cal2',
|
|
color: { light: '#00FF00', dark: '#000' },
|
|
events: {}
|
|
},
|
|
'user1/cal3': {
|
|
name: 'Hidden Calendar 2',
|
|
id: 'user1/cal3',
|
|
color: { light: '#0000FF', dark: '#000' },
|
|
events: {}
|
|
}
|
|
},
|
|
pending: false
|
|
}
|
|
}
|
|
|
|
const spy = jest
|
|
.spyOn(calendarDetailThunks, 'getCalendarDetailAsync')
|
|
.mockImplementation(
|
|
() =>
|
|
({
|
|
type: 'getCalendarDetailAsync',
|
|
unwrap: () => Promise.resolve({})
|
|
}) as any
|
|
)
|
|
|
|
await act(async () => {
|
|
renderWithProviders(
|
|
<CalendarApp
|
|
setCurrentView={jest.fn()}
|
|
onViewChange={jest.fn()}
|
|
openSidebar={false}
|
|
onCloseSidebar={jest.fn()}
|
|
currentView="timeGridWeek"
|
|
calendarRef={{ current: null }}
|
|
/>,
|
|
calendarsWithSelected
|
|
)
|
|
})
|
|
|
|
await waitFor(
|
|
() => {
|
|
expect(spy).toHaveBeenCalled()
|
|
},
|
|
{ timeout: 3000 }
|
|
)
|
|
|
|
const selectedCalls = spy.mock.calls.filter(
|
|
call => call[0].calId === 'user1/cal1'
|
|
)
|
|
spy.mock.calls.filter(
|
|
call => call[0].calId === 'user1/cal2' || call[0].calId === 'user1/cal3'
|
|
)
|
|
|
|
expect(selectedCalls.length).toBeGreaterThan(0)
|
|
})
|
|
|
|
it('does not make duplicate API calls for same calendar and range', async () => {
|
|
const spy = jest
|
|
.spyOn(calendarDetailThunks, 'getCalendarDetailAsync')
|
|
.mockImplementation(
|
|
() =>
|
|
({
|
|
type: 'getCalendarDetailAsync',
|
|
unwrap: () => Promise.resolve({})
|
|
}) as any
|
|
)
|
|
|
|
await act(async () => {
|
|
renderWithProviders(
|
|
<CalendarApp
|
|
setCurrentView={jest.fn()}
|
|
onViewChange={jest.fn()}
|
|
openSidebar={false}
|
|
onCloseSidebar={jest.fn()}
|
|
currentView="timeGridWeek"
|
|
calendarRef={{ current: null }}
|
|
/>,
|
|
{
|
|
...preloadedState,
|
|
calendars: { ...preloadedState.calendars, pending: false }
|
|
}
|
|
)
|
|
})
|
|
|
|
await waitFor(
|
|
() => {
|
|
expect(spy).toHaveBeenCalled()
|
|
},
|
|
{ timeout: 3000 }
|
|
)
|
|
|
|
const callsForCal1 = spy.mock.calls.filter(
|
|
call => call[0].calId === 'user1/cal1'
|
|
)
|
|
|
|
const uniqueRanges = new Set(
|
|
callsForCal1.map(call => `${call[0].match.start}_${call[0].match.end}`)
|
|
)
|
|
|
|
expect(uniqueRanges.size).toBeLessThanOrEqual(callsForCal1.length)
|
|
})
|
|
|
|
it('handles undefined calendars gracefully', async () => {
|
|
const stateWithUndefinedCalendars = {
|
|
...preloadedState,
|
|
calendars: {
|
|
list: undefined,
|
|
templist: undefined,
|
|
pending: false
|
|
}
|
|
}
|
|
|
|
await act(async () => {
|
|
renderWithProviders(
|
|
<CalendarApp
|
|
setCurrentView={jest.fn()}
|
|
onViewChange={jest.fn()}
|
|
openSidebar={false}
|
|
onCloseSidebar={jest.fn()}
|
|
currentView="timeGridWeek"
|
|
calendarRef={{ current: null }}
|
|
/>,
|
|
stateWithUndefinedCalendars
|
|
)
|
|
})
|
|
|
|
expect(screen.getByText('calendar.personal')).toBeInTheDocument()
|
|
})
|
|
|
|
it('handles undefined tempcalendars gracefully', async () => {
|
|
const stateWithUndefinedTemp = {
|
|
...preloadedState,
|
|
calendars: {
|
|
...preloadedState.calendars,
|
|
templist: undefined
|
|
}
|
|
}
|
|
|
|
await act(async () => {
|
|
renderWithProviders(
|
|
<CalendarApp
|
|
setCurrentView={jest.fn()}
|
|
onViewChange={jest.fn()}
|
|
openSidebar={false}
|
|
onCloseSidebar={jest.fn()}
|
|
currentView="timeGridWeek"
|
|
calendarRef={{ current: null }}
|
|
/>,
|
|
stateWithUndefinedTemp
|
|
)
|
|
})
|
|
|
|
expect(screen.getByText('calendar.personal')).toBeInTheDocument()
|
|
})
|
|
})
|
|
})
|