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