Files
kandev/apps/web/components/task/dockview-review-panel-sync.test.ts
T
2026-08-03 15:12:19 +01:00

333 lines
10 KiB
TypeScript

import { describe, expect, it, vi } from "vitest";
import type { DockviewApi } from "dockview-react";
import type { TaskPR } from "@/lib/types/github";
import type { TaskMR } from "@/lib/types/gitlab";
import {
resolveCanonicalReviewParams,
resolveConfiguredReviewPanelPlacement,
resolveConditionalReviewPanelAction,
syncCanonicalReviewPanel,
type ConditionalReviewPanelOptions,
} from "./dockview-review-panel-sync";
const CENTER_GROUP_ID = "group-center";
const RIGHT_GROUP_ID = "group-right-top";
const SESSION_PANEL_ID = "session:session-a";
const PR_KEY = "kandev/kandev/42";
const DEFAULT_OPTIONS: ConditionalReviewPanelOptions = {
sessionId: "session-a",
centerGroupId: CENTER_GROUP_ID,
reviewsLoaded: true,
isRestoringLayout: false,
isMaximized: false,
wasOffered: false,
};
const syncWithOptions = (
api: DockviewApi,
next: ReturnType<typeof resolveCanonicalReviewParams>,
options: ConditionalReviewPanelOptions,
) => syncCanonicalReviewPanel(api, next, options);
function makeApi(
panel?: { params?: Record<string, unknown>; groupId?: string },
sessionGroupId = CENTER_GROUP_ID,
extraGroupIds: string[] = [],
): {
api: DockviewApi;
updateParameters: ReturnType<typeof vi.fn>;
close: ReturnType<typeof vi.fn>;
addPanel: ReturnType<typeof vi.fn>;
} {
const updateParameters = vi.fn((next: Record<string, unknown>) => {
Object.assign(panel?.params ?? {}, next);
});
const close = vi.fn();
const addPanel = vi.fn();
const reviewPanel = panel
? {
id: "pr-detail",
params: panel.params ?? {},
group: { id: panel.groupId ?? RIGHT_GROUP_ID },
api: { updateParameters, close },
}
: undefined;
return {
api: {
getPanel: (id: string) => {
if (id === "pr-detail") return reviewPanel;
if (id === SESSION_PANEL_ID) return { id, group: { id: sessionGroupId } };
return undefined;
},
panels: [
...(reviewPanel ? [reviewPanel] : []),
{ id: SESSION_PANEL_ID, group: { id: sessionGroupId } },
],
groups: [
{ id: sessionGroupId },
...(sessionGroupId === CENTER_GROUP_ID ? [] : [{ id: CENTER_GROUP_ID }]),
...extraGroupIds.map((id) => ({ id })),
],
addPanel,
removePanel: vi.fn(),
} as unknown as DockviewApi,
updateParameters,
close,
addPanel,
};
}
const githubPR = {
owner: "kandev",
repo: "kandev",
pr_number: 42,
} as TaskPR;
const gitlabMR = {
host: "https://gitlab.example.test",
project_path: "group/project",
mr_iid: 7,
} as TaskMR;
describe("resolveCanonicalReviewParams", () => {
it("prefers the primary GitHub pull request when both providers are linked", () => {
expect(resolveCanonicalReviewParams([githubPR], [gitlabMR])).toEqual({
provider: "github",
prKey: PR_KEY,
mrKey: undefined,
});
});
it("selects the first linked GitLab merge request when GitHub is absent", () => {
expect(resolveCanonicalReviewParams([], [gitlabMR])).toEqual({
provider: "gitlab",
prKey: undefined,
mrKey: "https://gitlab.example.test|group/project|7",
});
});
it("clears review identity when the active task has no linked review", () => {
expect(resolveCanonicalReviewParams([], [])).toEqual({
provider: undefined,
prKey: undefined,
mrKey: undefined,
});
});
});
describe("resolveConditionalReviewPanelAction", () => {
it.each([
["adds a linked review", { hasReview: true, panelExists: false, wasOffered: false }, "add"],
[
"waits while restoring",
{ hasReview: true, panelExists: false, isRestoringLayout: true },
"none",
],
["waits while maximized", { hasReview: true, panelExists: false, isMaximized: true }, "none"],
[
"respects a dismissed offer",
{ hasReview: true, panelExists: false, wasOffered: true },
"none",
],
[
"removes a conditional panel without a review",
{ hasReview: false, panelExists: true },
"remove",
],
[
"removes an explicitly configured panel without a review",
{ hasReview: false, panelExists: true },
"remove",
],
])("$0", (_name, input, expected) => {
expect(
resolveConditionalReviewPanelAction({
isRestoringLayout: false,
isMaximized: false,
reviewsLoaded: true,
wasOffered: false,
...input,
}),
).toBe(expected);
});
it("waits for review hydration before removing any panel", () => {
expect(
resolveConditionalReviewPanelAction({
hasReview: false,
panelExists: true,
reviewsLoaded: false,
isRestoringLayout: false,
isMaximized: false,
wasOffered: true,
}),
).toBe("none");
});
});
describe("resolveConfiguredReviewPanelPlacement", () => {
it("returns the saved group and tab index", () => {
expect(
resolveConfiguredReviewPanelPlacement({
columns: [
{
id: "right",
groups: [
{
id: RIGHT_GROUP_ID,
panels: [
{ id: "files", component: "files", title: "Files" },
{ id: "pr-detail", component: "pr-detail", title: "PR Details" },
{ id: "changes", component: "changes", title: "Changes" },
],
},
],
},
],
}),
).toEqual({ groupId: RIGHT_GROUP_ID, index: 1 });
});
it("returns no placement for the built-in Default", () => {
expect(resolveConfiguredReviewPanelPlacement(null)).toBeNull();
});
});
describe("syncCanonicalReviewPanel placement", () => {
it("leaves a layout without PR Details structurally untouched", () => {
const { api, updateParameters, addPanel } = makeApi();
expect(
syncCanonicalReviewPanel(api, resolveCanonicalReviewParams([], []), DEFAULT_OPTIONS),
).toBe(false);
expect(updateParameters).not.toHaveBeenCalled();
expect(addPanel).not.toHaveBeenCalled();
expect(api.removePanel).not.toHaveBeenCalled();
});
it("adds a linked review beside the live Agent without activating it", () => {
const { api, addPanel } = makeApi();
expect(
syncWithOptions(api, resolveCanonicalReviewParams([githubPR], []), {
sessionId: "session-a",
centerGroupId: CENTER_GROUP_ID,
reviewsLoaded: true,
isRestoringLayout: false,
isMaximized: false,
wasOffered: false,
}),
).toBe(true);
expect(addPanel).toHaveBeenCalledWith(
expect.objectContaining({
id: "pr-detail",
component: "pr-detail",
inactive: true,
position: { referenceGroup: CENTER_GROUP_ID },
params: {
provider: "github",
prKey: PR_KEY,
mrKey: undefined,
},
}),
);
});
it("falls back to the configured center group when Agent is in a side group", () => {
const { api, addPanel } = makeApi(undefined, RIGHT_GROUP_ID);
expect(
syncWithOptions(api, resolveCanonicalReviewParams([githubPR], []), {
sessionId: "session-a",
centerGroupId: CENTER_GROUP_ID,
reviewsLoaded: true,
isRestoringLayout: false,
isMaximized: false,
wasOffered: false,
}),
).toBe(true);
expect(addPanel).toHaveBeenCalledWith(
expect.objectContaining({ position: { referenceGroup: CENTER_GROUP_ID } }),
);
});
it("adds a linked review in the group configured by the saved layout", () => {
const { api, addPanel } = makeApi(undefined, CENTER_GROUP_ID, [RIGHT_GROUP_ID]);
expect(
syncWithOptions(api, resolveCanonicalReviewParams([githubPR], []), {
sessionId: "session-a",
centerGroupId: CENTER_GROUP_ID,
configuredPlacement: { groupId: RIGHT_GROUP_ID, index: 2 },
reviewsLoaded: true,
isRestoringLayout: false,
isMaximized: false,
wasOffered: false,
}),
).toBe(true);
expect(addPanel).toHaveBeenCalledWith(
expect.objectContaining({
inactive: true,
position: { referenceGroup: RIGHT_GROUP_ID, index: 2 },
}),
);
});
});
describe("syncCanonicalReviewPanel lifecycle", () => {
it("does not add while restoring, maximized, or dismissed", () => {
for (const options of [
{ isRestoringLayout: true, isMaximized: false, wasOffered: false },
{ isRestoringLayout: false, isMaximized: true, wasOffered: false },
{ isRestoringLayout: false, isMaximized: false, wasOffered: true },
]) {
const { api, addPanel } = makeApi();
expect(
syncWithOptions(api, resolveCanonicalReviewParams([githubPR], []), {
sessionId: "session-a",
centerGroupId: CENTER_GROUP_ID,
reviewsLoaded: true,
...options,
}),
).toBe(false);
expect(addPanel).not.toHaveBeenCalled();
}
});
it("closes a conditionally added panel when review data disappears", () => {
const { api, close } = makeApi({ params: { autoAddedForReview: true } });
expect(
syncCanonicalReviewPanel(api, resolveCanonicalReviewParams([], []), DEFAULT_OPTIONS),
).toBe(true);
expect(close).toHaveBeenCalledOnce();
});
it("closes an explicitly configured panel when review data disappears", () => {
const { api, close, updateParameters } = makeApi({ params: { provider: "github" } });
expect(
syncCanonicalReviewPanel(api, resolveCanonicalReviewParams([], []), DEFAULT_OPTIONS),
).toBe(true);
expect(close).toHaveBeenCalledOnce();
expect(updateParameters).not.toHaveBeenCalled();
});
it("updates an existing panel's identity without changing its configured group", () => {
const params: Record<string, unknown> = { provider: "gitlab", mrKey: "old/mr" };
const { api, updateParameters } = makeApi({ params, groupId: "custom-review-group" });
expect(
syncCanonicalReviewPanel(api, resolveCanonicalReviewParams([githubPR], []), DEFAULT_OPTIONS),
).toBe(true);
expect(updateParameters).toHaveBeenCalledWith({
provider: "github",
prKey: PR_KEY,
mrKey: undefined,
});
expect(api.getPanel("pr-detail")?.group.id).toBe("custom-review-group");
expect(params).toMatchObject({ provider: "github", prKey: PR_KEY });
expect(params.mrKey).toBeUndefined();
});
});