diff --git a/src/components/structures/LoggedInView.tsx b/src/components/structures/LoggedInView.tsx index 26e127f21f..1769a96400 100644 --- a/src/components/structures/LoggedInView.tsx +++ b/src/components/structures/LoggedInView.tsx @@ -111,6 +111,7 @@ interface IState { backgroundImage?: string; } +const NEW_ROOM_LIST_MIN_WIDTH = 224; /** * This is what our MatrixChat shows when we are logged in. The precise view is * determined by the page_type property. @@ -261,10 +262,15 @@ class LoggedInView extends React.Component { let panelCollapsed: boolean; const useNewRoomList = SettingsStore.getValue("feature_new_room_list"); // TODO decrease this once Spaces launches as it'll no longer need to include the 56px Community Panel - const toggleSize = useNewRoomList ? 224 : 206 - 50; + const toggleSize = useNewRoomList ? NEW_ROOM_LIST_MIN_WIDTH : 206 - 50; + const collapseConfig: ICollapseConfig = { toggleSize, onCollapsed: (collapsed) => { + if (useNewRoomList) { + // The new room list does not support collapsing. + return; + } panelCollapsed = collapsed; if (collapsed) { dis.dispatch({ action: "hide_left_panel" }); @@ -281,11 +287,13 @@ class LoggedInView extends React.Component { this.props.resizeNotifier.startResizing(); }, onResizeStop: () => { - if (!panelCollapsed) window.localStorage.setItem("mx_lhs_size", "" + panelSize); + // Always save the lhs size for the new room list. + if (useNewRoomList || !panelCollapsed) window.localStorage.setItem("mx_lhs_size", "" + panelSize); this.props.resizeNotifier.stopResizing(); }, isItemCollapsed: (domNode) => { - return domNode.classList.contains("mx_LeftPanel_minimized"); + // New rooms list does not support collapsing. + return !useNewRoomList && domNode.classList.contains("mx_LeftPanel_minimized"); }, handler: this.resizeHandler.current ?? undefined, }; @@ -299,8 +307,11 @@ class LoggedInView extends React.Component { } private loadResizerPreferences(): void { + const useNewRoomList = SettingsStore.getValue("feature_new_room_list"); let lhsSize = parseInt(window.localStorage.getItem("mx_lhs_size")!, 10); - if (isNaN(lhsSize)) { + // If the user has not set a size, or for the new room list if the size is less than the minimum width, + // set a default size. + if (isNaN(lhsSize) || (useNewRoomList && lhsSize < NEW_ROOM_LIST_MIN_WIDTH)) { lhsSize = 350; } this.resizer?.forHandleWithId("lp-resizer")?.resize(lhsSize); diff --git a/test/unit-tests/components/structures/LoggedInView-test.tsx b/test/unit-tests/components/structures/LoggedInView-test.tsx index 6d24cbe416..dd88625509 100644 --- a/test/unit-tests/components/structures/LoggedInView-test.tsx +++ b/test/unit-tests/components/structures/LoggedInView-test.tsx @@ -34,6 +34,27 @@ import { Action } from "../../../../src/dispatcher/actions"; import Modal from "../../../../src/Modal"; import { SETTINGS } from "../../../../src/settings/Settings"; +// Create a mock resizer instance that can be shared across tests +const mockResizerInstance = { + attach: jest.fn(), + detach: jest.fn(), + forHandleWithId: jest.fn().mockReturnValue({ resize: jest.fn() }), + setClassNames: jest.fn(), +}; + +// Mock the Resizer module +jest.mock("../../../../src/resizer", () => { + const originalModule = jest.requireActual("../../../../src/resizer"); + return { + ...originalModule, + Resizer: jest.fn().mockImplementation((container, distributorBuilder, collapseConfig) => { + // Store the callbacks globally for test access + (global as any).__resizeCallbacks = collapseConfig; + return mockResizerInstance; + }), + }; +}); + describe("", () => { const userId = "@alice:domain.org"; const mockClient = getMockClientWithEventEmitter({ @@ -475,4 +496,84 @@ describe("", () => { expect(mockClient.deleteExtendedProfileProperty).toHaveBeenCalledWith("us.cloke.msc4175.tz"); }); }); + + describe("resizer preferences", () => { + let mockResize: jest.Mock; + let mockForHandleWithId: jest.Mock; + beforeEach(() => { + // Clear localStorage before each test + window.localStorage.clear(); + + mockResize = jest.fn(); + mockForHandleWithId = jest.fn().mockReturnValue({ resize: mockResize }); + + // Update the shared mock instance for this test + mockResizerInstance.forHandleWithId = mockForHandleWithId; + + // Clear any global callback state + delete (global as any).__resizeCallbacks; + }); + + it("should call resize with default size when localStorage contains NaN value", () => { + // Set invalid value in localStorage that will result in NaN + window.localStorage.setItem("mx_lhs_size", "not-a-number"); + + getComponent(); + + // Verify that when lhsSize is NaN, it defaults to 350 and calls resize + expect(mockForHandleWithId).toHaveBeenCalledWith("lp-resizer"); + expect(mockResize).toHaveBeenCalledWith(350); + }); + + it("should use existing size when localStorage contains valid value", () => { + // Set valid value in localStorage + window.localStorage.setItem("mx_lhs_size", "400"); + + getComponent(); + + // Verify the resize method was called with the stored size (400) + expect(mockResize).toHaveBeenCalledWith(400); + }); + + it("should enforce minimum width for new room list when stored size is zero", async () => { + // Enable new room list feature + await SettingsStore.setValue("feature_new_room_list", null, SettingLevel.DEVICE, true); + + // 0 represents the collapsed state for the old room list, which could have been set before the new room list was enabled + window.localStorage.setItem("mx_lhs_size", "0"); + + getComponent(); + + // Verify the resize method was called with the default size (350) when stored size is below minimum + expect(mockResize).toHaveBeenCalledWith(350); + }); + + it("should not set localStorage to 0 when resizing lp-resizer to minimum width for new room list", async () => { + // Enable new room list feature and mock SettingsStore + await SettingsStore.setValue("feature_new_room_list", null, SettingLevel.DEVICE, true); + + const minimumWidth = 224; // NEW_ROOM_LIST_MIN_WIDTH + + // Render the component + getComponent(); + + // Get the callbacks that were captured during resizer creation + const callbacks = (global as any).__resizeCallbacks; + + // Create a mock DOM node for isItemCollapsed to check + const domNode = { + classList: { + contains: jest.fn().mockReturnValue(true), // Simulate the error where mx_LeftPanel_minimized is present + }, + } as any; + + callbacks.onResized(minimumWidth); + const isCollapsed = callbacks.isItemCollapsed(domNode); + callbacks.onCollapsed(isCollapsed); // Not collapsed for new room list + callbacks.onResizeStop(); + + // Verify localStorage was set to the minimum width (224), not 0 + expect(window.localStorage.getItem("mx_lhs_size")).toBe("224"); + }); + }); });