import { afterAll, describe, expect, test } from "bun:test"; import type { UiState } from "../src/react-app/shell/ui-state-store"; const PERSISTED_UI_STATE_KEY = "openwork:ui-state:v1"; const originalWindow = globalThis.window; const originalLocalStorage = globalThis.localStorage; function memoryStorage(): Storage { const map = new Map(); return { get length() { return map.size; }, clear() { map.clear(); }, getItem(key: string) { return map.get(key) ?? null; }, key(index: number) { return Array.from(map.keys())[index] ?? null; }, removeItem(key: string) { map.delete(key); }, setItem(key: string, value: string) { map.set(key, value); }, }; } function requireJsonObject(raw: string | null): object { expect(raw).not.toBeNull(); if (raw === null) { throw new Error("Expected persisted UI state JSON"); } const parsed: unknown = JSON.parse(raw); expect(parsed).not.toBeNull(); expect(typeof parsed).toBe("object"); if (parsed === null || typeof parsed !== "object") { throw new Error("Expected persisted UI state object"); } return parsed; } function objectValue(object: object, key: string): unknown { return Object.entries(object).find(([entryKey]) => entryKey === key)?.[1]; } const storage = memoryStorage(); storage.setItem( PERSISTED_UI_STATE_KEY, JSON.stringify({ sidePanelState: { ses_1: "extensions" }, workspaceRightSidebarExpanded: true }), ); Object.defineProperty(globalThis, "window", { configurable: true, value: { document: { cookie: "" }, localStorage: storage, }, }); Object.defineProperty(globalThis, "localStorage", { configurable: true, value: storage, }); const { persistUiState, toggleSidePanelState, useUiStateStore } = await import( "../src/react-app/shell/ui-state-store" ); const { usePanelTabStore } = await import("../src/react-app/domains/session/panel/panel-tab-store"); const importedSidePanelState = useUiStateStore.getState().sidePanelState; const importedWorkspaceRightSidebarExpanded = useUiStateStore.getState().workspaceRightSidebarExpanded; afterAll(() => { Object.defineProperty(globalThis, "window", { configurable: true, value: originalWindow, }); Object.defineProperty(globalThis, "localStorage", { configurable: true, value: originalLocalStorage, }); }); describe("ui state store", () => { test("persists UI state without side panel state", () => { const state: UiState = { sidebarOpen: true, sidePanelState: { ses_1: "extensions" }, applicationMenuVisible: false, workspaceLeftSidebarWidth: 260, workspaceLeftSidebarResizing: false, workspaceRightSidebarExpanded: true, workspaceRightSidebarExpandedWidth: 520, }; persistUiState(state); const parsed = requireJsonObject(storage.getItem(PERSISTED_UI_STATE_KEY)); expect("sidePanelState" in parsed).toBe(false); expect(objectValue(parsed, "workspaceRightSidebarExpanded")).toBe(true); expect(objectValue(parsed, "workspaceRightSidebarExpandedWidth")).toBe(520); }); test("ignores legacy persisted side panel state on startup", () => { expect(importedSidePanelState).toEqual({}); expect(importedWorkspaceRightSidebarExpanded).toBe(true); }); test("keeps side panel toggles in memory", () => { const state: UiState = { sidebarOpen: true, sidePanelState: {}, applicationMenuVisible: false, workspaceLeftSidebarWidth: 260, workspaceLeftSidebarResizing: false, workspaceRightSidebarExpanded: false, workspaceRightSidebarExpandedWidth: 520, }; const opened = toggleSidePanelState(state, "ses_1", "extensions"); expect(opened.sidePanelState).toEqual({ ses_1: "extensions" }); const closed = toggleSidePanelState(opened, "ses_1", "extensions"); expect(closed.sidePanelState).toEqual({ ses_1: null }); }); test("preserves active panel content while the panel closes and reopens", () => { usePanelTabStore.getState().openTab("ses_preserve", { id: "file:report.md", type: "artifact", label: "report.md", preview: "markdown", }); const closed = toggleSidePanelState({ ...useUiStateStore.getState(), sidePanelState: { ses_preserve: "panel" }, }, "ses_preserve", "panel"); const reopened = toggleSidePanelState(closed, "ses_preserve", "panel"); expect(reopened.sidePanelState.ses_preserve).toBe("panel"); expect(usePanelTabStore.getState().sessions.ses_preserve?.activeTabId).toBe("file:report.md"); usePanelTabStore.getState().clearSession("ses_preserve"); }); test("preserves workspace-tree artifact tabs when transcript artifacts resync", () => { const target = { id: "file:src/example.ts", kind: "file" as const, value: "src/example.ts", name: "example.ts", preview: "code" as const, confidence: 100, reason: "workspace tree", exists: true, }; usePanelTabStore.getState().openTab("ses_tree", { id: target.id, type: "artifact", label: target.name, preview: target.preview, target, }); usePanelTabStore.getState().syncTranscriptArtifacts("ses_tree", []); expect(usePanelTabStore.getState().sessions.ses_tree?.tabs).toEqual([ expect.objectContaining({ id: target.id, target }), ]); usePanelTabStore.getState().clearSession("ses_tree"); }); });