Files
kandev/apps/web/lib/state/dockview-env-switch-active-views.test.ts
T
2026-08-02 20:07:13 +01:00

386 lines
12 KiB
TypeScript

import { beforeEach, expect, it, vi } from "vitest";
import { performEnvSwitch, type EnvSwitchParams } from "./dockview-env-switch";
const { CANONICAL_CENTER_GROUP_ID, RIGHT_TOP_GROUP_ID, RIGHT_BOTTOM_GROUP_ID } = vi.hoisted(() => ({
CANONICAL_CENTER_GROUP_ID: "group-center",
RIGHT_TOP_GROUP_ID: "group-right-top",
RIGHT_BOTTOM_GROUP_ID: "group-right-bottom",
}));
vi.mock("@/lib/local-storage", () => ({
getEnvLayout: vi.fn(() => null),
getManualRightWidth: 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),
getRootSplitview: vi.fn(() => null),
getPinnedWidth: vi.fn(() => 350),
isCenterCandidateGroupId: vi.fn(
(groupId: string) =>
groupId !== "group-sidebar" &&
groupId !== RIGHT_TOP_GROUP_ID &&
groupId !== RIGHT_BOTTOM_GROUP_ID,
),
setPinnedTarget: vi.fn(),
CENTER_GROUP: CANONICAL_CENTER_GROUP_ID,
RIGHT_TOP_GROUP: RIGHT_TOP_GROUP_ID,
RIGHT_BOTTOM_GROUP: RIGHT_BOTTOM_GROUP_ID,
}));
import { getEnvLayout } from "@/lib/local-storage";
import { fromDockviewApi, layoutStructuresMatch, savedLayoutMatchesLive } from "./layout-manager";
const NEW_SESSION_ID = "new-session";
const OLD_SESSION_PANEL_ID = "session:old-session";
const NEW_SESSION_PANEL_ID = `session:${NEW_SESSION_ID}`;
const PR_DETAILS_ID = "pr-detail";
const PR_DETAILS_TITLE = "PR Details";
type TestPanel = {
id: string;
api: { component?: string; setActive?: ReturnType<typeof vi.fn>; close?: () => void };
group?: TestGroup;
};
type TestGroup = { id: string; panels: TestPanel[] };
function makeMockApi() {
return {
panels: [],
groups: [],
layout: vi.fn(),
fromJSON: vi.fn(),
getPanel: vi.fn(() => null),
addPanel: vi.fn(),
} as unknown as EnvSwitchParams["api"];
}
function makeParams(overrides?: Partial<EnvSwitchParams>): EnvSwitchParams {
return {
api: makeMockApi(),
oldEnvId: "old-env",
newEnvId: "new-env",
activeSessionId: NEW_SESSION_ID,
safeWidth: 800,
safeHeight: 600,
buildDefault: vi.fn(),
getDefaultLayout: vi.fn(() => ({ columns: [] })),
...overrides,
};
}
function makeTwoLeafSavedLayout(
leaves: Array<{ id: string; views: string[]; activeView: string }>,
activeGroup: string,
): ReturnType<typeof getEnvLayout> {
return {
grid: {
root: {
type: "branch" as const,
data: leaves.map((leaf) => ({ type: "leaf", data: leaf })),
},
height: 600,
width: 800,
orientation: "HORIZONTAL" as const,
},
panels: { chat: { contentComponent: "chat" } },
activeGroup,
} as unknown as ReturnType<typeof getEnvLayout>;
}
function makeAgentReviewApi(
setActiveAgent: ReturnType<typeof vi.fn>,
setActiveReview: ReturnType<typeof vi.fn>,
groupId = "center",
) {
const group: TestGroup = { id: groupId, panels: [] };
group.panels = [
{ id: NEW_SESSION_PANEL_ID, api: { component: "chat", setActive: setActiveAgent }, group },
{ id: PR_DETAILS_ID, api: { component: PR_DETAILS_ID, setActive: setActiveReview }, group },
];
return {
group,
api: {
...makeMockApi(),
panels: group.panels,
groups: [group],
getPanel: vi.fn((id: string) => group.panels.find((panel) => panel.id === id) ?? null),
} as unknown as EnvSwitchParams["api"],
};
}
beforeEach(() => {
vi.clearAllMocks();
});
it("rebinds a saved Chat selection to the incoming Agent panel", () => {
const setActiveAgent = vi.fn();
const setActiveReview = vi.fn();
const savedLayout = makeTwoLeafSavedLayout(
[{ id: "center", views: ["chat", PR_DETAILS_ID], activeView: "chat" }],
"center",
);
const { api } = makeAgentReviewApi(setActiveAgent, setActiveReview);
vi.mocked(getEnvLayout).mockReturnValueOnce(savedLayout);
vi.mocked(savedLayoutMatchesLive).mockReturnValueOnce(true);
performEnvSwitch(makeParams({ api }));
expect(setActiveAgent).toHaveBeenCalledOnce();
expect(setActiveReview).not.toHaveBeenCalled();
});
it("rebinds a stale saved session selection to the incoming Agent panel", () => {
const setActiveAgent = vi.fn();
const setActiveReview = vi.fn();
const savedLayout = makeTwoLeafSavedLayout(
[
{
id: "center",
views: [OLD_SESSION_PANEL_ID, PR_DETAILS_ID],
activeView: OLD_SESSION_PANEL_ID,
},
],
"center",
);
const { api } = makeAgentReviewApi(setActiveAgent, setActiveReview);
vi.mocked(getEnvLayout).mockReturnValueOnce(savedLayout);
vi.mocked(savedLayoutMatchesLive).mockReturnValueOnce(true);
performEnvSwitch(makeParams({ api }));
expect(setActiveAgent).toHaveBeenCalledOnce();
expect(setActiveReview).not.toHaveBeenCalled();
});
it("uses the effective default Agent selection when the target has no saved layout", () => {
const setActiveAgent = vi.fn();
const setActiveReview = vi.fn();
const liveGroupId = "generated-center-group";
const { api } = makeAgentReviewApi(setActiveAgent, setActiveReview, liveGroupId);
vi.mocked(layoutStructuresMatch).mockReturnValueOnce(true);
const liveLayout = {
columns: [
{
id: "center",
groups: [
{
id: liveGroupId,
panels: [
{ id: NEW_SESSION_PANEL_ID, component: "chat", title: "Agent" },
{ id: PR_DETAILS_ID, component: PR_DETAILS_ID, title: PR_DETAILS_TITLE },
],
},
],
},
],
};
vi.mocked(fromDockviewApi).mockReturnValueOnce({ columns: [] }).mockReturnValueOnce(liveLayout);
performEnvSwitch(
makeParams({
api,
getDefaultLayout: vi.fn(() => ({
columns: [
{
id: "center",
groups: [
{
activePanel: "chat",
panels: [
{ id: "chat", component: "chat", title: "Agent" },
{ id: PR_DETAILS_ID, component: PR_DETAILS_ID, title: PR_DETAILS_TITLE },
],
},
],
},
],
})),
}),
);
expect(setActiveAgent).toHaveBeenCalledOnce();
expect(setActiveReview).not.toHaveBeenCalled();
});
it("does not rebind a default Agent selection when no session exists", () => {
const setActiveReview = vi.fn();
const groupId = "generated-center-without-session";
const group: TestGroup = {
id: groupId,
panels: [{ id: PR_DETAILS_ID, api: { component: PR_DETAILS_ID, setActive: setActiveReview } }],
};
const api = {
...makeMockApi(),
groups: [group],
panels: group.panels,
} as unknown as EnvSwitchParams["api"];
vi.mocked(layoutStructuresMatch).mockReturnValueOnce(true);
const liveLayout = {
columns: [
{
id: "center",
groups: [
{
id: groupId,
panels: [{ id: PR_DETAILS_ID, component: PR_DETAILS_ID, title: PR_DETAILS_TITLE }],
},
],
},
],
};
vi.mocked(fromDockviewApi).mockReturnValueOnce({ columns: [] }).mockReturnValueOnce(liveLayout);
performEnvSwitch(
makeParams({
api,
activeSessionId: null,
getDefaultLayout: vi.fn(() => ({
columns: [
{
id: "center",
groups: [
{
activePanel: "chat",
panels: [
{ id: "chat", component: "chat", title: "Agent" },
{ id: PR_DETAILS_ID, component: PR_DETAILS_ID, title: PR_DETAILS_TITLE },
],
},
],
},
],
})),
}),
);
expect(setActiveReview).not.toHaveBeenCalled();
});
it("rebinds a stale saved session selection after slow-path replacement", () => {
const setActiveAgent = vi.fn();
const setActiveReview = vi.fn();
const group: TestGroup = { id: "saved-center", panels: [] };
const panels: TestPanel[] = [];
const removePanel = (id: string) => {
const index = panels.findIndex((panel) => panel.id === id);
if (index >= 0) panels.splice(index, 1);
const groupIndex = group.panels.findIndex((panel) => panel.id === id);
if (groupIndex >= 0) group.panels.splice(groupIndex, 1);
};
const stale: TestPanel = {
id: OLD_SESSION_PANEL_ID,
api: { component: "chat", close: () => removePanel(OLD_SESSION_PANEL_ID) },
group,
};
const review: TestPanel = {
id: PR_DETAILS_ID,
api: { component: PR_DETAILS_ID, setActive: setActiveReview },
group,
};
panels.push(stale, review);
group.panels.push(stale, review);
const addPanel = vi.fn((options: { id: string; component: string }) => {
const incoming: TestPanel = {
id: options.id,
api: { component: options.component, setActive: setActiveAgent },
group,
};
panels.unshift(incoming);
group.panels.unshift(incoming);
return incoming;
});
const savedLayout = makeTwoLeafSavedLayout(
[
{
id: group.id,
views: [OLD_SESSION_PANEL_ID, PR_DETAILS_ID],
activeView: OLD_SESSION_PANEL_ID,
},
],
group.id,
);
vi.mocked(getEnvLayout).mockReturnValueOnce(savedLayout).mockReturnValueOnce(savedLayout);
vi.mocked(savedLayoutMatchesLive).mockReturnValueOnce(false);
const api = {
...makeMockApi(),
panels,
groups: [group],
addPanel,
getPanel: vi.fn((id: string) => panels.find((panel) => panel.id === id) ?? null),
} as unknown as EnvSwitchParams["api"];
performEnvSwitch(makeParams({ api }));
expect(api.fromJSON).toHaveBeenCalledOnce();
expect(setActiveAgent).toHaveBeenCalledOnce();
expect(setActiveReview).not.toHaveBeenCalled();
});
it("keeps a deliberately selected PR Details panel selected", () => {
const setActiveAgent = vi.fn();
const setActiveReview = vi.fn();
const savedLayout = makeTwoLeafSavedLayout(
[{ id: "center", views: ["chat", PR_DETAILS_ID], activeView: PR_DETAILS_ID }],
"center",
);
const { api } = makeAgentReviewApi(setActiveAgent, setActiveReview);
vi.mocked(getEnvLayout).mockReturnValueOnce(savedLayout);
vi.mocked(savedLayoutMatchesLive).mockReturnValueOnce(true);
performEnvSwitch(makeParams({ api }));
expect(setActiveReview).toHaveBeenCalledOnce();
expect(setActiveAgent).not.toHaveBeenCalled();
});
it("restores saved per-group active tabs on the fast path", () => {
const setActiveRight = vi.fn();
const setActiveCenter = vi.fn();
const rightGroup: TestGroup = {
id: "right",
panels: [
{ id: "plan", api: { setActive: setActiveRight } },
{ id: "files", api: { setActive: vi.fn() } },
],
};
const centerGroup: TestGroup = {
id: "center",
panels: [{ id: NEW_SESSION_PANEL_ID, api: { setActive: setActiveCenter } }],
};
const savedLayout = makeTwoLeafSavedLayout(
[
{ id: "center", views: ["chat"], activeView: "chat" },
{ id: "right", views: ["plan", "files"], activeView: "plan" },
],
"right",
);
vi.mocked(getEnvLayout).mockReturnValueOnce(savedLayout).mockReturnValueOnce(savedLayout);
vi.mocked(savedLayoutMatchesLive).mockReturnValueOnce(true);
const api = {
...makeMockApi(),
groups: [centerGroup, rightGroup],
getPanel: vi.fn((id: string) => (id === NEW_SESSION_PANEL_ID ? centerGroup.panels[0] : null)),
} as unknown as EnvSwitchParams["api"];
performEnvSwitch(makeParams({ api }));
expect(setActiveCenter).toHaveBeenCalledOnce();
expect(setActiveRight).toHaveBeenCalled();
const lastRightCall = setActiveRight.mock.invocationCallOrder.at(-1) ?? 0;
const lastCenterCall = setActiveCenter.mock.invocationCallOrder.at(-1) ?? 0;
expect(lastRightCall).toBeGreaterThan(lastCenterCall);
});