Files
openwork/apps/app/tests/ui-state-store.test.ts

179 lines
5.4 KiB
TypeScript

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<string, string>();
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");
});
});