import SettingsPage from '@/features/Settings/SettingsPage' import settingsReducer, { setIsBrowserDefaultTimeZone, setTimeZone } from '@/features/Settings/SettingsSlice' import userReducer, { getOpenPaasUserDataAsync, setTimezone as setUserTimeZone } from '@/features/User/userSlice' import { api } from '@/utils/apiUtils' import { browserDefaultTimeZone } from '@/utils/timezone' import { configureStore } from '@reduxjs/toolkit' import { fireEvent, screen, waitFor } from '@testing-library/react' import { renderWithProviders } from '../../utils/Renderwithproviders' jest.mock('@/utils/apiUtils') describe('Timezone synchronization after getOpenPaasUserDataAsync', () => { let apiGetSpy: jest.SpyInstance beforeEach(() => { jest.clearAllMocks() localStorage.clear() apiGetSpy = jest.spyOn(api, 'get') }) afterEach(() => { apiGetSpy.mockRestore() }) it('should sync timezone to both user and settings state after fetching user data', async () => { const mockUserData = { id: '667037022b752d0026472254', firstname: 'John', lastname: 'Doe', preferredEmail: ['test@test.com'], configurations: { modules: [ { name: 'core', configurations: [ { name: 'language', value: 'fr' }, { name: 'datetime', value: { timeZone: 'Europe/Paris' } } ] } ] } } // Return a fake fetch-like response apiGetSpy.mockResolvedValue({ json: async () => mockUserData }) 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' } }, loading: false, error: null }, settings: { language: 'en', timeZone: 'UTC', view: 'calendar' } } }) const result = await store.dispatch(getOpenPaasUserDataAsync()) expect(result.type).toBe('user/getOpenPaasUserData/fulfilled') const state = store.getState() expect(state.user.coreConfig.datetime.timeZone).toBe('Europe/Paris') expect(state.settings.timeZone).toBe('Europe/Paris') expect(localStorage.getItem('timeZone')).toBe('Europe/Paris') }) it('should keep browser timezone if API response has no timezone configuration', async () => { const mockUserData = { id: '667037022b752d0026472254', firstname: 'John', lastname: 'Doe', preferredEmail: '[test@test.com](mailto:test@test.com)', configurations: { modules: [ { name: 'core', configurations: [{ name: 'language', value: 'en' }] } ] } } apiGetSpy.mockResolvedValue({ json: async () => mockUserData }) const browserTimezone = browserDefaultTimeZone ?? 'UTC' 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: browserTimezone } }, loading: false, error: null }, settings: { language: 'en', timeZone: browserTimezone, view: 'calendar' } } }) const result = await store.dispatch(getOpenPaasUserDataAsync()) expect(result.type).toBe('user/getOpenPaasUserData/fulfilled') const state = store.getState() expect(state.user.coreConfig.datetime.timeZone).toBe(null) expect(state.settings.timeZone).toBe(browserTimezone) }) }) // Mock localStorage const localStorageMock = (() => { let store: Record = {} return { getItem: (key: string) => store[key] || null, setItem: (key: string, value: string) => { store[key] = value }, removeItem: (key: string) => { delete store[key] }, clear: () => { store = {} } } })() Object.defineProperty(window, 'localStorage', { value: localStorageMock }) describe('Timezone Logic - Backend to Frontend Flow', () => { let store: any beforeEach(() => { localStorageMock.clear() store = configureStore({ reducer: { settings: settingsReducer, user: userReducer }, 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' } }, loading: false, error: null }, settings: { language: 'en', timeZone: 'UTC', view: 'calendar' } } }) }) describe('Backend Response Handling', () => { test('Backend returns NULL => Should use browser default timezone', async () => { const backendResponse = { firstname: 'John', lastname: 'Doe', id: '123', preferredEmail: 'john@example.com', configurations: { modules: [ { name: 'core', configurations: [ { name: 'datetime', value: { timeZone: null } } ] } ] } } await store.dispatch( getOpenPaasUserDataAsync.fulfilled(backendResponse, '', undefined) ) const settingsState = store.getState().settings const userState = store.getState().user expect(settingsState.timeZone).toBe(browserDefaultTimeZone) expect(settingsState.isBrowserDefaultTimeZone).toBe(true) expect(userState.coreConfig.datetime.timeZone).toBe(null) expect(localStorage.getItem('timeZone')).toBe(browserDefaultTimeZone) }) test('Backend returns SPECIFIC VALUE => Should use that value', async () => { const specificTimezone = 'America/New_York' const backendResponse = { firstname: 'Jane', lastname: 'Smith', id: '456', preferredEmail: 'jane@example.com', configurations: { modules: [ { name: 'core', configurations: [ { name: 'datetime', value: { timeZone: specificTimezone } } ] } ] } } await store.dispatch( getOpenPaasUserDataAsync.fulfilled(backendResponse, '', undefined) ) const settingsState = store.getState().settings const userState = store.getState().user expect(settingsState.timeZone).toBe(specificTimezone) expect(settingsState.isBrowserDefaultTimeZone).toBe(false) expect(userState.coreConfig.datetime.timeZone).toBe(specificTimezone) expect(localStorage.getItem('timeZone')).toBe(specificTimezone) }) test('Backend returns NO datetime config => Should use browser default', async () => { const backendResponse = { firstname: 'Bob', lastname: 'Johnson', id: '789', preferredEmail: 'bob@example.com', configurations: { modules: [ { name: 'core', configurations: [ // No datetime config ] } ] } } await store.dispatch( getOpenPaasUserDataAsync.fulfilled(backendResponse, '', undefined) ) const settingsState = store.getState().settings const userState = store.getState().user expect(settingsState.timeZone).toBe(browserDefaultTimeZone) expect(settingsState.isBrowserDefaultTimeZone).toBe(true) expect(userState.coreConfig.datetime.timeZone).toBe(null) expect(localStorage.getItem('timeZone')).toBe(browserDefaultTimeZone) }) }) test('Settings state ALWAYS has a concrete value (never null)', () => { // Test 1: Browser default scenario store.dispatch(setIsBrowserDefaultTimeZone(true)) let settingsState = store.getState().settings expect(settingsState.timeZone).toBe(browserDefaultTimeZone) expect(settingsState.timeZone).not.toBe(null) // Test 2: Specific timezone scenario store.dispatch(setTimeZone('Europe/Paris')) settingsState = store.getState().settings expect(settingsState.timeZone).toBe('Europe/Paris') expect(settingsState.timeZone).not.toBe(null) }) describe('User Actions - Changing Timezone', () => { beforeEach(() => { ;(api.patch as jest.Mock).mockResolvedValue({ status: 204 }) }) test('User enables browser default => Settings gets browser TZ, User gets null', async () => { const { store } = renderWithProviders(, { user: { userData: { sub: 'test' }, organiserData: null, tokens: null, coreConfig: { language: 'en', datetime: { timeZone: 'America/Los_Angeles' } }, loading: false, error: null }, settings: { language: 'en', timeZone: 'America/Los_Angeles', isBrowserDefaultTimeZone: false, view: 'settings' } }) const browserDefaultSwitch = screen.getAllByLabelText( 'settings.timeZoneBrowserDefault' )[0] expect(browserDefaultSwitch).toBeInTheDocument() // Enable browser default fireEvent.click(browserDefaultSwitch) await waitFor(() => { const state = store.getState() expect(state.settings.isBrowserDefaultTimeZone).toBe(true) }) await waitFor(() => { const state = store.getState() expect(state.settings.timeZone).toBe(browserDefaultTimeZone) }) await waitFor(() => { const state = store.getState() expect(state.user.coreConfig.datetime.timeZone).toBe(null) }) }) test('User selects specific timezone => Both states get the value', async () => { const { store } = renderWithProviders(, { user: { userData: { sub: 'test' }, organiserData: null, tokens: null, coreConfig: { language: 'en', datetime: { timeZone: null } }, loading: false, error: null }, settings: { language: 'en', timeZone: browserDefaultTimeZone, isBrowserDefaultTimeZone: true, view: 'settings' } }) const browserDefaultSwitch = screen.getAllByLabelText( 'settings.timeZoneBrowserDefault' )[0] // Disable browser default so manual selector appears fireEvent.click(browserDefaultSwitch) // Now timezone combobox is visible const timezoneInput = screen.getAllByRole('combobox')[1] // Type to filter options fireEvent.change(timezoneInput, { target: { value: 'Australia/Sydney' } }) // Select from autocomplete dropdown const option = await screen.findByText(/Australia\/Sydney/i) fireEvent.click(option) await waitFor(() => { const state = store.getState() expect(state.settings.isBrowserDefaultTimeZone).toBe(false) }) await waitFor(() => { const state = store.getState() expect(state.settings.timeZone).toBe('Australia/Sydney') }) await waitFor(() => { const state = store.getState() expect(state.user.coreConfig.datetime.timeZone).toBe('Australia/Sydney') }) }) }) describe('LocalStorage Persistence', () => { test('LocalStorage always stores concrete values (browser TZ or specific TZ)', () => { // Scenario 1: Browser default store.dispatch(setTimeZone(browserDefaultTimeZone)) expect(localStorage.getItem('timeZone')).toBe(browserDefaultTimeZone) expect(localStorage.getItem('timeZone')).not.toBe('null') // Scenario 2: Specific timezone store.dispatch(setTimeZone('Africa/Cairo')) expect(localStorage.getItem('timeZone')).toBe('Africa/Cairo') }) }) test('Full user journey: Backend null -> User changes -> Backend saves', async () => { // Step 1: Backend returns null const backendResponseNull = { firstname: 'Test', lastname: 'User', id: '999', preferredEmail: 'test@example.com', configurations: { modules: [ { name: 'core', configurations: [ { name: 'datetime', value: { timeZone: null } } ] } ] } } await store.dispatch( getOpenPaasUserDataAsync.fulfilled(backendResponseNull, '', undefined) ) // Verify initial state expect(store.getState().settings.timeZone).toBe(browserDefaultTimeZone) expect(store.getState().user.coreConfig.datetime.timeZone).toBe(null) // Step 2: User changes to specific timezone const userSelectedTZ = 'Europe/London' store.dispatch(setIsBrowserDefaultTimeZone(false)) store.dispatch(setTimeZone(userSelectedTZ)) store.dispatch(setUserTimeZone(userSelectedTZ)) expect(store.getState().settings.timeZone).toBe(userSelectedTZ) expect(store.getState().user.coreConfig.datetime.timeZone).toBe( userSelectedTZ ) // Step 3: User switches back to browser default store.dispatch(setIsBrowserDefaultTimeZone(true)) store.dispatch(setUserTimeZone(null)) store.dispatch(setTimeZone(browserDefaultTimeZone)) expect(store.getState().settings.timeZone).toBe(browserDefaultTimeZone) expect(store.getState().user.coreConfig.datetime.timeZone).toBe(null) }) })