Files
kandev/apps/web/lib/state/dockview-panel-actions-extra.test.ts
T
2026-08-05 20:45:59 +01:00

270 lines
9.3 KiB
TypeScript

import { describe, expect, it } from "vitest";
import type { DockviewApi } from "dockview-react";
import { buildExtraPanelActions } from "./dockview-panel-actions";
import { CENTER_GROUP } from "./layout-manager";
import { makeApi, makeStore } from "./dockview-panel-actions.test-utils";
import type { MockPanel } from "./dockview-panel-actions.test-utils";
describe("addPRPanel — dedup with legacy auto-shown panel", () => {
const PR_KEY = "testorg/testrepo/101";
const OTHER_PR_KEY = "testorg/testrepo/202";
const LEGACY_PR_ID = "pr-detail";
const KEYED_PR_ID = `pr-detail|${PR_KEY}`;
const OTHER_KEYED_PR_ID = `pr-detail|${OTHER_PR_KEY}`;
function buildExtra(api: DockviewApi) {
const store = makeStore(api);
return { api, actions: buildExtraPanelActions(store.get) };
}
/** Canonical PR Details panels retain the key currently shown for the task. */
function seedLegacyPanel(api: DockviewApi, prKey: string): void {
api.addPanel({
id: LEGACY_PR_ID,
component: "pr-detail",
title: "Pull Request",
params: { prKey },
position: { referenceGroup: CENTER_GROUP },
});
}
it("reuses the legacy panel when the requested PR is what it's already showing", () => {
const { api, actions } = buildExtra(makeApi());
seedLegacyPanel(api, PR_KEY);
actions.addPRPanel(PR_KEY);
const prPanels = api.panels.filter(
(p) => p.id === LEGACY_PR_ID || p.id.startsWith(`${LEGACY_PR_ID}|`),
);
expect(prPanels).toHaveLength(1);
expect(api.getPanel(KEYED_PR_ID)).toBeUndefined();
expect((api.getPanel(LEGACY_PR_ID) as unknown as MockPanel).isActive).toBe(true);
});
it("opens a distinct tab for a different PR instead of overwriting the legacy tab", () => {
const { api, actions } = buildExtra(makeApi());
seedLegacyPanel(api, PR_KEY);
// Multi-repo "+" menu click for a PR other than the one the legacy tab
// is showing must NOT repurpose that tab — it must open its own tab.
actions.addPRPanel(OTHER_PR_KEY);
const legacy = api.getPanel(LEGACY_PR_ID) as unknown as MockPanel;
expect(legacy.params.prKey).toBe(PR_KEY);
const other = api.getPanel(OTHER_KEYED_PR_ID) as unknown as MockPanel;
expect(other).toBeDefined();
expect(other.isActive).toBe(true);
const prPanels = api.panels.filter(
(p) => p.id === LEGACY_PR_ID || p.id.startsWith(`${LEGACY_PR_ID}|`),
);
expect(prPanels).toHaveLength(2);
});
it("creates a new keyed panel when no legacy panel exists", () => {
const { api, actions } = buildExtra(makeApi());
actions.addPRPanel(PR_KEY);
expect(api.getPanel(KEYED_PR_ID)).toBeDefined();
expect(api.getPanel(LEGACY_PR_ID)).toBeUndefined();
});
it("focuses an existing keyed panel exactly once", () => {
const { api, actions } = buildExtra(makeApi());
actions.addPRPanel(PR_KEY);
actions.addPRPanel(PR_KEY);
const prPanels = api.panels.filter(
(p) => p.id === LEGACY_PR_ID || p.id.startsWith(`${LEGACY_PR_ID}|`),
);
expect(prPanels).toHaveLength(1);
const keyed = api.getPanel(KEYED_PR_ID) as unknown as MockPanel;
expect(keyed.isActive).toBe(true);
});
});
describe("addPRPanel — layout-owned group placement", () => {
const PR_KEY = "testorg/testrepo/202";
const KEYED_PR_ID = `pr-detail|${PR_KEY}`;
const SESSION_ID = "s-1";
const SESSION_PANEL_ID = `session:${SESSION_ID}`;
const SESSION_GROUP = "group-session-host";
const PR_DETAILS_GROUP = "group-configured-pr-details";
function buildExtra(api: DockviewApi) {
const store = makeStore(api);
return { api, actions: buildExtraPanelActions(store.get) };
}
function seedSessionInGroup(api: DockviewApi, groupId: string): void {
api.addPanel({
id: SESSION_PANEL_ID,
component: "chat",
title: "Session",
params: { sessionId: SESSION_ID },
position: { referenceGroup: groupId },
});
}
function seedCanonicalPRDetails(api: DockviewApi, groupId = PR_DETAILS_GROUP): void {
api.addPanel({
id: "pr-detail",
component: "pr-detail",
title: "PR Details",
position: { referenceGroup: groupId },
});
}
it("places a new PR tab in PR Details' configured group, not beside Agent", () => {
const { api, actions } = buildExtra(makeApi());
seedSessionInGroup(api, SESSION_GROUP);
seedCanonicalPRDetails(api);
actions.addPRPanel(PR_KEY);
const pr = api.getPanel(KEYED_PR_ID) as unknown as MockPanel;
expect(pr).toBeDefined();
expect(pr.group.id).toBe(PR_DETAILS_GROUP);
expect(pr.group.id).not.toBe(SESSION_GROUP);
});
it("falls back to centerGroupId when no PR Details panel exists", () => {
const { api, actions } = buildExtra(makeApi());
actions.addPRPanel(PR_KEY);
const pr = api.getPanel(KEYED_PR_ID) as unknown as MockPanel;
expect(pr).toBeDefined();
expect(pr.group.id).toBe(CENTER_GROUP);
});
it("focuses an existing keyed PR tab without relocating it", () => {
const { api, actions } = buildExtra(makeApi());
seedCanonicalPRDetails(api);
api.addPanel({
id: KEYED_PR_ID,
component: "pr-detail",
title: "Pull Request",
params: { prKey: PR_KEY },
position: { referenceGroup: SESSION_GROUP },
});
actions.addPRPanel(PR_KEY);
const pr = api.getPanel(KEYED_PR_ID) as unknown as MockPanel;
expect(pr.group.id).toBe(SESSION_GROUP);
expect(pr.isActive).toBe(true);
});
});
describe("addMRPanel — layout-owned review tabs", () => {
const MR_KEY = "https://gitlab.example.test|platform/kandev|81";
const OTHER_MR_KEY = "https://gitlab.example.test|platform/kandev|82";
const LEGACY_MR_ID = "mr-detail";
function buildExtra(api: DockviewApi) {
const store = makeStore(api);
return { api, actions: buildExtraPanelActions(store.get) };
}
function seedLegacyPanel(api: DockviewApi, mrKey: string): void {
api.addPanel({
id: LEGACY_MR_ID,
component: "mr-detail",
title: "Merge Request",
params: { mrKey },
position: { referenceGroup: CENTER_GROUP },
});
}
it("focuses a matching legacy panel in place", () => {
const { api, actions } = buildExtra(makeApi());
seedLegacyPanel(api, MR_KEY);
actions.addMRPanel(MR_KEY);
expect(api.panels.filter((panel) => panel.id.startsWith(LEGACY_MR_ID))).toHaveLength(1);
expect(api.getPanel(`${LEGACY_MR_ID}|${MR_KEY}`)).toBeUndefined();
expect((api.getPanel(LEGACY_MR_ID) as unknown as MockPanel).isActive).toBe(true);
});
it("opens a keyed panel in the canonical PR Details group", () => {
const { api, actions } = buildExtra(makeApi());
seedLegacyPanel(api, MR_KEY);
api.addPanel({
id: "pr-detail",
component: "pr-detail",
title: "PR Details",
position: { referenceGroup: "group-configured-pr-details" },
});
actions.addMRPanel(OTHER_MR_KEY);
const keyed = api.getPanel(`${LEGACY_MR_ID}|${OTHER_MR_KEY}`) as unknown as MockPanel;
expect(keyed).toBeDefined();
expect(keyed.group.id).toBe("group-configured-pr-details");
expect(api.panels.filter((panel) => panel.id.startsWith(LEGACY_MR_ID))).toHaveLength(2);
});
});
const PLUGIN_ID = "kandev-plugin-notes";
const PLUGIN_PANEL_KEY = "notes";
const PLUGIN_PANEL_TITLE = "Notes";
const PLUGIN_PANEL_ID = "plugin:kandev-plugin-notes:notes";
describe("addPluginPanel / closePluginPanels", () => {
function buildExtra(api: DockviewApi) {
const store = makeStore(api);
return { api, actions: buildExtraPanelActions(store.get) };
}
it("opens a plugin panel beside chat by default, with the shared plugin-panel component/tab", () => {
const { api, actions } = buildExtra(makeApi());
actions.addPluginPanel(PLUGIN_ID, PLUGIN_PANEL_KEY, PLUGIN_PANEL_TITLE);
const panel = api.getPanel(PLUGIN_PANEL_ID) as unknown as MockPanel;
expect(panel).toBeDefined();
expect(panel.title).toBe(PLUGIN_PANEL_TITLE);
expect(panel.params).toEqual({ pluginId: PLUGIN_ID, panelKey: PLUGIN_PANEL_KEY });
});
it("focuses an already-open plugin panel instead of duplicating it", () => {
const { api, actions } = buildExtra(makeApi());
actions.addPluginPanel(PLUGIN_ID, PLUGIN_PANEL_KEY, PLUGIN_PANEL_TITLE);
(api.getPanel(PLUGIN_PANEL_ID) as unknown as MockPanel).isActive = false;
actions.addPluginPanel(PLUGIN_ID, PLUGIN_PANEL_KEY, PLUGIN_PANEL_TITLE);
expect(api.panels.filter((p) => p.id === PLUGIN_PANEL_ID)).toHaveLength(1);
expect((api.getPanel(PLUGIN_PANEL_ID) as unknown as MockPanel).isActive).toBe(true);
});
it("respects inCenter and quiet options", () => {
const { api, actions } = buildExtra(makeApi());
actions.addPluginPanel(PLUGIN_ID, PLUGIN_PANEL_KEY, PLUGIN_PANEL_TITLE, {
inCenter: true,
quiet: true,
});
const panel = api.getPanel(PLUGIN_PANEL_ID) as unknown as MockPanel;
expect(panel.group.id).toBe(CENTER_GROUP);
expect(panel.isActive).toBe(false);
});
it("closePluginPanels removes only the target plugin's panels", () => {
const { api, actions } = buildExtra(makeApi());
actions.addPluginPanel(PLUGIN_ID, PLUGIN_PANEL_KEY, PLUGIN_PANEL_TITLE);
actions.addPluginPanel("kandev-plugin-other", "widget", "Widget");
actions.closePluginPanels(PLUGIN_ID);
expect(api.getPanel(PLUGIN_PANEL_ID)).toBeUndefined();
expect(api.getPanel("plugin:kandev-plugin-other:widget")).toBeDefined();
});
});