Files
kandev/apps/web/lib/state/dockview-effective-default-profile.test.ts
T
2026-08-31 21:42:12 +00:00

182 lines
5.7 KiB
TypeScript

import { describe, expect, it, vi, beforeEach } from "vitest";
import type { DockviewApi } from "dockview-react";
import { getBuiltInLayoutOverrideId, getLayoutProfileIdentity } from "@/lib/layout/layout-profiles";
import type { LayoutState } from "./layout-manager";
vi.mock("@/lib/local-storage", () => ({
setEnvLayout: vi.fn(),
getEnvLayout: vi.fn(() => null),
getEnvLayoutProfile: vi.fn(() => null),
setEnvLayoutProfile: vi.fn(),
getEnvMaximizeState: vi.fn(() => null),
setEnvMaximizeState: vi.fn(),
removeEnvMaximizeState: vi.fn(),
getGlobalSidebarWidth: vi.fn(() => null),
setGlobalSidebarWidth: vi.fn(),
clearGlobalSidebarWidth: vi.fn(),
getManualRightWidth: vi.fn(() => null),
}));
vi.mock("@/lib/layout/panel-portal-manager", () => ({
panelPortalManager: { releaseByEnv: vi.fn(), reconcile: vi.fn() },
}));
vi.mock("./dockview-scroll-preserve", () => ({
preserveChatScrollDuringLayout: vi.fn(),
}));
vi.mock("./dockview-measure", () => ({
measureDockviewContainer: vi.fn(() => ({ width: 800, height: 600 })),
}));
vi.mock("./dockview-pinned-enforce", () => ({
enforcePinnedTargets: vi.fn(),
}));
vi.mock("./dockview-layout-builders", () => ({
applyLayoutFixups: vi.fn(() => ({
sidebarGroupId: "g-sidebar",
centerGroupId: "g-center",
rightTopGroupId: "g-right-top",
rightBottomGroupId: "g-right-bottom",
})),
focusOrAddPanel: vi.fn(),
}));
vi.mock("./layout-manager", async (importOriginal) => {
const actual = await importOriginal<typeof import("./layout-manager")>();
return {
...actual,
SIDEBAR_GROUP: "sidebar",
CENTER_GROUP: "center",
RIGHT_TOP_GROUP: "right-top",
RIGHT_BOTTOM_GROUP: "right-bottom",
TERMINAL_DEFAULT_ID: "terminal",
LAYOUT_SIDEBAR_RATIO: 0.2,
LAYOUT_RIGHT_RATIO: 0.25,
LAYOUT_PINNED_MIN_PX: 200,
computeSidebarMaxPx: vi.fn(() => 350),
computeRightMaxPx: vi.fn(() => 500),
getPresetLayout: vi.fn(() => ({ columns: [] })),
applyLayout: vi.fn(() => ({
sidebarGroupId: "g-sidebar",
centerGroupId: "g-center",
rightTopGroupId: "g-right-top",
rightBottomGroupId: "g-right-bottom",
})),
getPinnedWidth: vi.fn(() => 350),
getRootSplitview: vi.fn(() => null),
fromDockviewApi: vi.fn(() => ({ columns: [] })),
filterEphemeral: vi.fn((s: unknown) => s),
defaultLayout: vi.fn(() => ({ columns: [] })),
mergeCurrentPanelsIntoPreset: vi.fn((_api: unknown, preset: unknown) => preset),
toSerializedDockview: vi.fn((s: unknown) => s),
injectIntentPanels: vi.fn(),
applyActivePanelOverrides: vi.fn(),
resolveNamedIntent: vi.fn(),
setPinnedTarget: vi.fn(),
clearPinnedTarget: vi.fn(),
getPinnedTarget: vi.fn(() => undefined),
layoutStructuresMatch: vi.fn(() => false),
savedLayoutMatchesLive: vi.fn(() => false),
};
});
import { useDockviewStore } from "./dockview-store";
import { applyLayout } from "./layout-manager";
function makeStoreApi(): DockviewApi {
return {
width: 800,
height: 600,
panels: [],
groups: [],
layout: vi.fn(),
fromJSON: vi.fn(),
toJSON: vi.fn(() => ({ columns: [{ id: "center" }] })),
getPanel: vi.fn(() => null),
addPanel: vi.fn(),
activeGroup: null,
hasMaximizedGroup: vi.fn(() => false),
exitMaximizedGroup: vi.fn(),
onDidActivePanelChange: vi.fn(() => ({ dispose: vi.fn() })),
} as unknown as DockviewApi;
}
function flushRaf(): Promise<void> {
return new Promise((resolve) => requestAnimationFrame(() => resolve()));
}
function resetStore(): void {
vi.clearAllMocks();
useDockviewStore.setState({
api: null,
currentLayoutEnvId: null,
preMaximizeLayout: null,
maximizedGroupId: null,
isRestoringLayout: false,
pinnedWidths: new Map(),
userDefaultLayout: null,
userDefaultLayoutProfile: { kind: "built-in", id: "default" },
});
}
describe("effective default layout profile identity", () => {
beforeEach(resetStore);
// @covers AC-UI-TASK-LAYOUT-PROFILES-001.11
it("preserves built-in Default through a fresh build and Reset Layout", async () => {
const api = makeStoreApi();
const userDefaultLayout = {
columns: [
{
id: "center",
groups: [{ panels: [{ id: "chat", component: "chat", title: "Agent" }] }],
},
],
} as LayoutState;
const reservedDefaultProfile = getLayoutProfileIdentity({
id: getBuiltInLayoutOverrideId("default"),
});
useDockviewStore.setState({ api, currentLayoutEnvId: "env-default" });
useDockviewStore.getState().setUserDefaultLayout(userDefaultLayout, reservedDefaultProfile);
useDockviewStore.getState().buildDefaultLayout(api);
expect(useDockviewStore.getState().activeLayoutProfile).toEqual({
kind: "built-in",
id: "default",
});
expect(applyLayout).toHaveBeenCalledWith(api, userDefaultLayout, expect.any(Map), 800, 600);
await flushRaf();
useDockviewStore.getState().resetLayout();
expect(useDockviewStore.getState().activeLayoutProfile).toEqual({
kind: "built-in",
id: "default",
});
expect(applyLayout).toHaveBeenLastCalledWith(api, userDefaultLayout, expect.any(Map), 800, 600);
await flushRaf();
});
it("keeps an arbitrary saved default custom", () => {
const api = makeStoreApi();
const userDefaultLayout = { columns: [{ id: "center", groups: [] }] } as LayoutState;
useDockviewStore.setState({ api, currentLayoutEnvId: "env-custom" });
useDockviewStore
.getState()
.setUserDefaultLayout(
userDefaultLayout,
getLayoutProfileIdentity({ id: "layout-copied-default" }),
);
useDockviewStore.getState().buildDefaultLayout(api);
expect(useDockviewStore.getState().activeLayoutProfile).toEqual({
kind: "custom",
id: "layout-copied-default",
});
});
});