import SettingsPage from '@/features/Settings/SettingsPage'
import settingsReducer from '@/features/Settings/SettingsSlice'
import userReducer, {
getOpenPaasUserDataAsync
} from '@/features/User/userSlice'
import { api } from '@/utils/apiUtils'
import { configureStore } from '@reduxjs/toolkit'
import '@testing-library/jest-dom'
import { fireEvent, screen, waitFor } from '@testing-library/react'
import { renderWithProviders } from '../../utils/Renderwithproviders'
jest.mock('@/utils/apiUtils')
const basePreloadedState = {
user: {
userData: {
sub: 'test',
email: 'test@test.com',
family_name: 'Doe',
name: 'John',
sid: 'mockSid',
openpaasId: '667037022b752d0026472254'
},
organiserData: null,
tokens: null,
coreConfig: {
language: 'en',
datetime: { timeZone: 'UTC' }
},
alarmEmailsEnabled: false,
loading: false,
error: null
},
settings: {
language: 'en',
timeZone: 'UTC',
isBrowserDefaultTimeZone: false,
view: 'settings',
businessHours: {
start: '8:0',
end: '19:0',
daysOfWeek: [1, 2, 3, 4, 5]
},
workingDays: false
}
}
describe('Working Days and Business Hours Settings', () => {
beforeEach(() => {
jest.clearAllMocks()
;(api.patch as jest.Mock).mockResolvedValue({ status: 204 })
})
describe('Initial rendering', () => {
it('renders the working days section with day selector', () => {
renderWithProviders(, basePreloadedState)
expect(screen.getByText('settings.chooseWorkingDays')).toBeInTheDocument()
})
it('renders the show only working days toggle', () => {
renderWithProviders(, basePreloadedState)
expect(
screen.getByRole('switch', { name: /settings.showOnlyWorkingDays/i })
).toBeInTheDocument()
})
it('shows working days toggle as unchecked when workingDays is false', () => {
renderWithProviders(, basePreloadedState)
const toggle = screen.getByRole('switch', {
name: /settings.showOnlyWorkingDays/i
}) as HTMLInputElement
expect(toggle.checked).toBe(false)
})
it('shows working days toggle as checked when workingDays is true', () => {
renderWithProviders(, {
...basePreloadedState,
settings: { ...basePreloadedState.settings, workingDays: true }
})
const toggle = screen.getByRole('switch', {
name: /settings.showOnlyWorkingDays/i
}) as HTMLInputElement
expect(toggle.checked).toBe(true)
})
it('shows working days toggle as unchecked when workingDays is null', () => {
renderWithProviders(, {
...basePreloadedState,
settings: { ...basePreloadedState.settings, workingDays: null }
})
const toggle = screen.getByRole('switch', {
name: /settings.showOnlyWorkingDays/i
}) as HTMLInputElement
expect(toggle.checked).toBe(false)
})
})
describe('workingDays toggle', () => {
it('updates workingDays immediately (optimistic update) and calls API', async () => {
const { store } = renderWithProviders(
,
basePreloadedState
)
const toggle = screen.getByRole('switch', {
name: /settings.showOnlyWorkingDays/i
})
fireEvent.click(toggle)
await waitFor(() => {
expect(store.getState().settings.workingDays).toBe(true)
})
await waitFor(() => {
expect(api.patch).toHaveBeenCalledWith(
'api/configurations?scope=user',
expect.objectContaining({
json: expect.arrayContaining([
expect.objectContaining({
name: 'linagora.esn.calendar',
configurations: expect.arrayContaining([
expect.objectContaining({
name: 'workingDays',
value: true
})
])
})
])
})
)
})
})
it('rolls back workingDays if API call fails', async () => {
;(api.patch as jest.Mock).mockRejectedValue(new Error('API Error'))
const { store } = renderWithProviders(
,
basePreloadedState
)
const toggle = screen.getByRole('switch', {
name: /settings.showOnlyWorkingDays/i
})
fireEvent.click(toggle)
await waitFor(
() => {
expect(store.getState().settings.workingDays).toBe(false)
},
{ timeout: 3000 }
)
await waitFor(() => {
expect(
screen.getByText('settings.workingDaysUpdateError')
).toBeInTheDocument()
})
})
it('toggles workingDays from true to false', async () => {
const { store } = renderWithProviders(, {
...basePreloadedState,
settings: { ...basePreloadedState.settings, workingDays: true }
})
const toggle = screen.getByRole('switch', {
name: /settings.showOnlyWorkingDays/i
})
fireEvent.click(toggle)
await waitFor(() => {
expect(store.getState().settings.workingDays).toBe(false)
})
await waitFor(() => {
expect(api.patch).toHaveBeenCalledWith(
'api/configurations?scope=user',
expect.objectContaining({
json: expect.arrayContaining([
expect.objectContaining({
name: 'linagora.esn.calendar',
configurations: expect.arrayContaining([
expect.objectContaining({
name: 'workingDays',
value: false
})
])
})
])
})
)
})
})
})
describe('businessHours day selector', () => {
it('updates businessHours in Redux immediately when a day is clicked', async () => {
const { store } = renderWithProviders(
,
basePreloadedState
)
// Sunday (0) is not in initial daysOfWeek [1,2,3,4,5], click it to add
const sundayButton = screen.getByRole('button', {
name: /sunday/i
})
fireEvent.click(sundayButton)
await waitFor(() => {
expect(store.getState().settings.businessHours?.daysOfWeek).toContain(0)
})
})
it('removes a day from businessHours when an already selected day is clicked', async () => {
const { store } = renderWithProviders(
,
basePreloadedState
)
// Monday (1) is in initial daysOfWeek, click to remove
const mondayButton = screen.getByRole('button', { name: /monday/i })
fireEvent.click(mondayButton)
await waitFor(() => {
expect(
store.getState().settings.businessHours?.daysOfWeek
).not.toContain(1)
})
})
it('debounces API call when multiple days are clicked rapidly', async () => {
jest.useFakeTimers()
renderWithProviders(, basePreloadedState)
const sundayButton = screen.getByRole('button', { name: /sunday/i })
const saturdayButton = screen.getByRole('button', { name: /saturday/i })
fireEvent.click(sundayButton)
fireEvent.click(saturdayButton)
fireEvent.click(sundayButton)
// API should not have been called yet
expect(api.patch).not.toHaveBeenCalled()
// Advance timers past debounce threshold
jest.runAllTimers()
await waitFor(() => {
expect(api.patch).toHaveBeenCalledTimes(1)
})
jest.useRealTimers()
})
it('sends businessHours as array to API', async () => {
jest.useFakeTimers()
renderWithProviders(, basePreloadedState)
const sundayButton = screen.getByRole('button', { name: /sunday/i })
fireEvent.click(sundayButton)
jest.runAllTimers()
await waitFor(() => {
expect(api.patch).toHaveBeenCalledWith(
'api/configurations?scope=user',
expect.objectContaining({
json: expect.arrayContaining([
expect.objectContaining({
name: 'core',
configurations: expect.arrayContaining([
expect.objectContaining({
name: 'businessHours',
value: expect.arrayContaining([
expect.objectContaining({
start: '8:0',
end: '19:0',
daysOfWeek: expect.arrayContaining([0])
})
])
})
])
})
])
})
)
})
jest.useRealTimers()
})
it('rolls back businessHours if API call fails', async () => {
jest.useFakeTimers()
;(api.patch as jest.Mock).mockRejectedValue(new Error('API Error'))
const { store } = renderWithProviders(
,
basePreloadedState
)
const sundayButton = screen.getByRole('button', { name: /sunday/i })
fireEvent.click(sundayButton)
jest.runAllTimers()
await waitFor(
() => {
expect(
store.getState().settings.businessHours?.daysOfWeek
).not.toContain(0)
},
{ timeout: 3000 }
)
await waitFor(() => {
expect(
screen.getByText('settings.workingDaysUpdateError')
).toBeInTheDocument()
})
jest.useRealTimers()
})
})
describe('getOpenPaasUserDataAsync integration', () => {
it('populates businessHours from core module configurations', async () => {
const store = configureStore({
reducer: { user: userReducer, settings: settingsReducer },
preloadedState: {
user: {
userData: {
sub: 'test',
email: 'test@test.com',
family_name: 'Doe',
name: 'John',
sid: 'mockSid',
openpaasId: '667037022b752d0026472254'
},
organiserData: null,
tokens: null,
coreConfig: { language: 'en', datetime: { timeZone: 'UTC' } },
alarmEmailsEnabled: null,
loading: false,
error: null
},
settings: {
language: 'en',
timeZone: 'UTC',
isBrowserDefaultTimeZone: false,
view: 'calendar',
businessHours: null,
workingDays: null
}
}
})
const mockResponse = {
id: '667037022b752d0026472254',
firstname: 'John',
lastname: 'Doe',
preferredEmail: 'test@test.com',
configurations: {
modules: [
{
name: 'core',
configurations: [
{
name: 'businessHours',
value: [
{ start: '8:0', end: '19:0', daysOfWeek: [1, 2, 3, 4, 5] }
]
}
]
},
{
name: 'linagora.esn.calendar',
configurations: [{ name: 'workingDays', value: true }]
}
]
}
}
await store.dispatch(
getOpenPaasUserDataAsync.fulfilled(mockResponse, '', undefined)
)
const state = store.getState()
expect(state.settings.businessHours).toEqual({
start: '8:0',
end: '19:0',
daysOfWeek: [1, 2, 3, 4, 5]
})
expect(state.settings.workingDays).toBe(true)
})
it('sets businessHours to null when not present in API response', async () => {
const store = configureStore({
reducer: { user: userReducer, settings: settingsReducer },
preloadedState: {
user: {
userData: {
sub: 'test',
email: 'test@test.com',
family_name: 'Doe',
name: 'John',
sid: 'mockSid',
openpaasId: '667037022b752d0026472254'
},
organiserData: null,
tokens: null,
coreConfig: { language: 'en', datetime: { timeZone: 'UTC' } },
alarmEmailsEnabled: null,
loading: false,
error: null
},
settings: {
language: 'en',
timeZone: 'UTC',
isBrowserDefaultTimeZone: false,
view: 'calendar',
businessHours: null,
workingDays: null
}
}
})
const mockResponse = {
id: '667037022b752d0026472254',
firstname: 'John',
lastname: 'Doe',
preferredEmail: 'test@test.com',
configurations: {
modules: [
{
name: 'core',
configurations: []
},
{
name: 'linagora.esn.calendar',
configurations: [{ name: 'workingDays', value: null }]
}
]
}
}
await store.dispatch(
getOpenPaasUserDataAsync.fulfilled(mockResponse, '', undefined)
)
const state = store.getState()
expect(state.settings.businessHours).toBeNull()
expect(state.settings.workingDays).toBeNull()
})
})
})