From 2872d072cbf406a8fa431883fce1f65a6a8dabcc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Almeida?= <57680069+zeval@users.noreply.github.com> Date: Thu, 9 Apr 2026 23:07:49 +0100 Subject: [PATCH] fix(web): recalculate dockview layout after fast-path session switch (#571) * fix(web): recalculate dockview layout after fast-path session switch The fast path in tryFastSessionSwitch removed ephemeral panels but never called api.layout() to redistribute column widths, leaving a gap on the right side of the screen. Both slow-path branches already make this call. * test(web): add regression test for fast-path layout call Verifies that performSessionSwitch calls api.layout() on both the fast path and slow path, preventing the squished-layout regression. * test(web): cover saved-layout fast path branch * fix(web): replace no-explicit-any cast in test * fix(web): fix test isolation with mockReturnValueOnce Use mockReturnValueOnce and clearAllMocks for proper test isolation between fast-path and slow-path test cases. * test(web): fix mock shape to match LayoutGroupIds type --- .../lib/state/dockview-session-switch.test.ts | 84 +++++++++++++++++++ apps/web/lib/state/dockview-session-switch.ts | 1 + 2 files changed, 85 insertions(+) create mode 100644 apps/web/lib/state/dockview-session-switch.test.ts diff --git a/apps/web/lib/state/dockview-session-switch.test.ts b/apps/web/lib/state/dockview-session-switch.test.ts new file mode 100644 index 0000000000..5a25f5a6de --- /dev/null +++ b/apps/web/lib/state/dockview-session-switch.test.ts @@ -0,0 +1,84 @@ +import { describe, it, expect, vi, beforeEach } from "vitest"; +import { performSessionSwitch, type SessionSwitchParams } from "./dockview-session-switch"; + +// Mock dependencies +vi.mock("@/lib/local-storage", () => ({ + getSessionLayout: vi.fn(() => null), +})); + +vi.mock("./dockview-layout-builders", () => ({ + applyLayoutFixups: vi.fn(() => ({ + sidebarGroupId: "g1", + centerGroupId: "g2", + rightTopGroupId: "g3", + rightBottomGroupId: "g4", + })), +})); + +vi.mock("./layout-manager", () => ({ + fromDockviewApi: vi.fn(() => ({ columns: [] })), + savedLayoutMatchesLive: vi.fn(() => false), + layoutStructuresMatch: vi.fn(() => false), +})); + +import { getSessionLayout } from "@/lib/local-storage"; +import { layoutStructuresMatch, savedLayoutMatchesLive } from "./layout-manager"; + +function makeMockApi() { + return { + panels: [], + layout: vi.fn(), + fromJSON: vi.fn(), + getPanel: vi.fn(), + } as unknown as SessionSwitchParams["api"]; +} + +function makeParams(overrides?: Partial): SessionSwitchParams { + return { + api: makeMockApi(), + oldSessionId: "old-session", + newSessionId: "new-session", + safeWidth: 800, + safeHeight: 600, + buildDefault: vi.fn(), + getDefaultLayout: vi.fn(() => ({ columns: [] })), + ...overrides, + }; +} + +describe("performSessionSwitch", () => { + beforeEach(() => { + vi.clearAllMocks(); + }); + + it("calls api.layout on the fast path when structures match", () => { + vi.mocked(layoutStructuresMatch).mockReturnValueOnce(true); + const params = makeParams(); + + performSessionSwitch(params); + + expect(params.api.layout).toHaveBeenCalledWith(800, 600); + }); + + it("calls api.layout on the fast path when saved layout matches", () => { + vi.mocked(getSessionLayout).mockReturnValueOnce({ grid: {} } as unknown as ReturnType< + typeof getSessionLayout + >); + vi.mocked(savedLayoutMatchesLive).mockReturnValueOnce(true); + const params = makeParams(); + + performSessionSwitch(params); + + expect(params.api.layout).toHaveBeenCalledWith(800, 600); + expect(params.api.fromJSON).not.toHaveBeenCalled(); + }); + + it("calls api.layout on the slow path (buildDefault fallback)", () => { + const params = makeParams(); + + performSessionSwitch(params); + + expect(params.api.layout).toHaveBeenCalledWith(800, 600); + expect(params.buildDefault).toHaveBeenCalledWith(params.api); + }); +}); diff --git a/apps/web/lib/state/dockview-session-switch.ts b/apps/web/lib/state/dockview-session-switch.ts index 6e75fd9747..554b2aaba0 100644 --- a/apps/web/lib/state/dockview-session-switch.ts +++ b/apps/web/lib/state/dockview-session-switch.ts @@ -83,6 +83,7 @@ function tryFastSessionSwitch(params: SessionSwitchParams): LayoutGroupIds | nul // Session-scoped portals (terminal, browser, etc.) will be re-acquired // via usePortalSlot's sessionId dependency change. removeEphemeralPanels(api, newSessionId); + api.layout(params.safeWidth, params.safeHeight); return applyLayoutFixups(api); }