Files
kandev/apps/web/components/task/chat-context-items.test.ts
T
2026-09-22 13:20:58 +01:00

234 lines
7.5 KiB
TypeScript

import { describe, expect, it, vi } from "vitest";
import { buildContextItems } from "./chat-context-items";
import type { AgentMessageComment } from "@/lib/state/slices/comments";
import type { FileContextItem } from "@/lib/types/context";
const comment: AgentMessageComment = {
id: "message-comment-1",
sessionId: "session-1",
source: "agent-message",
messageId: "reply-1",
selectedText: "selected answer",
anchor: {
messageId: "reply-1",
start: 0,
end: 15,
selectedText: "selected answer",
prefix: "",
suffix: " continues",
},
text: "Please clarify this.",
createdAt: "2026-07-20T00:00:00Z",
status: "pending",
};
describe("buildContextItems agent-message comments", () => {
it("creates one removable context item for pending message comments", () => {
const items = buildContextItems({
planContextEnabled: false,
contextFiles: [],
resolvedSessionId: "session-1",
removeContextFile: vi.fn(),
unpinFile: vi.fn(),
addPlan: vi.fn(),
promptsMap: new Map(),
pendingCommentsByFile: {},
handleRemoveCommentFile: vi.fn(),
handleRemoveComment: vi.fn(),
planComments: [],
handleClearPlanComments: vi.fn(),
pendingPRFeedback: [],
handleRemovePRFeedback: vi.fn(),
handleClearPRFeedback: vi.fn(),
walkthroughComments: [],
handleRemoveWalkthroughComment: vi.fn(),
handleClearWalkthroughComments: vi.fn(),
messageComments: [comment],
handleClearMessageComments: vi.fn(),
taskId: "task-1",
} as never);
const item = items.find((candidate) => candidate.kind === "agent-message-comment");
expect(item).toMatchObject({
kind: "agent-message-comment",
label: "1 message comment",
});
});
});
describe("buildContextItems task plan comments", () => {
it("opens the Plan but cannot remove shared comments from a session composer", () => {
const addPlan = vi.fn();
const clear = vi.fn();
const items = buildContextItems({
planContextEnabled: false,
contextFiles: [],
resolvedSessionId: "session-secondary",
removeContextFile: vi.fn(),
unpinFile: vi.fn(),
addPlan,
promptsMap: new Map(),
pendingCommentsByFile: {},
handleRemoveCommentFile: vi.fn(),
handleRemoveComment: vi.fn(),
planComments: [{ id: "comment-1", version: 1 }],
handleClearPlanComments: clear,
pendingPRFeedback: [],
handleRemovePRFeedback: vi.fn(),
handleClearPRFeedback: vi.fn(),
walkthroughComments: [],
handleRemoveWalkthroughComment: vi.fn(),
handleClearWalkthroughComments: vi.fn(),
messageComments: [],
handleClearMessageComments: vi.fn(),
taskId: "task-1",
} as never);
const item = items.find((candidate) => candidate.kind === "plan-comment");
expect(item).toMatchObject({ kind: "plan-comment", label: "1 plan comment", onOpen: addPlan });
expect(item?.onRemove).toBeUndefined();
expect(clear).not.toHaveBeenCalled();
});
});
describe("buildContextItems task preview feedback", () => {
it("opens the task-scoped collection from the context chip", () => {
const open = vi.fn();
const items = buildContextItems({
planContextEnabled: false,
contextFiles: [],
resolvedSessionId: "session-secondary",
removeContextFile: vi.fn(),
unpinFile: vi.fn(),
addPlan: vi.fn(),
promptsMap: new Map(),
pendingCommentsByFile: {},
handleRemoveCommentFile: vi.fn(),
handleRemoveComment: vi.fn(),
planComments: [],
handleClearPlanComments: vi.fn(),
previewFeedback: [
{
id: "preview-1",
version: 2,
comment: "Increase contrast",
kind: "element",
source_label: "Checkout preview",
page_route: "/cart",
},
],
pendingPRFeedback: [],
handleRemovePRFeedback: vi.fn(),
handleClearPRFeedback: vi.fn(),
walkthroughComments: [],
handleRemoveWalkthroughComment: vi.fn(),
handleClearWalkthroughComments: vi.fn(),
messageComments: [],
handleClearMessageComments: vi.fn(),
taskId: "task-1",
onOpenPreviewFeedback: open,
} as never);
const item = items.find((candidate) => candidate.kind === "preview-feedback");
expect(item).toMatchObject({
kind: "preview-feedback",
label: "1 preview feedback item",
onOpen: open,
});
expect(item?.onRemove).toBeUndefined();
});
});
describe("buildContextItems file and directory context", () => {
function buildItems(contextFiles: never[]) {
return buildContextItems({
planContextEnabled: false,
contextFiles,
resolvedSessionId: "session-1",
removeContextFile: vi.fn(),
unpinFile: vi.fn(),
addPlan: vi.fn(),
promptsMap: new Map(),
onOpenFile: vi.fn(),
pendingCommentsByFile: {},
handleRemoveCommentFile: vi.fn(),
handleRemoveComment: vi.fn(),
planComments: [],
handleClearPlanComments: vi.fn(),
pendingPRFeedback: [],
handleRemovePRFeedback: vi.fn(),
handleClearPRFeedback: vi.fn(),
walkthroughComments: [],
handleRemoveWalkthroughComment: vi.fn(),
handleClearWalkthroughComments: vi.fn(),
messageComments: [],
handleClearMessageComments: vi.fn(),
taskId: "task-1",
});
}
it("marks directory items and does not expose file-open behavior", () => {
const items = buildItems([
{ path: "src", name: "src", isDirectory: true },
{ path: "src/index.ts", name: "index.ts" },
] as never[]);
expect(items).toEqual(
expect.arrayContaining([
expect.objectContaining({ kind: "file", path: "src", isDirectory: true }),
expect.objectContaining({ kind: "file", path: "src/index.ts", isDirectory: false }),
]),
);
const fileItems = items.filter((item): item is FileContextItem => item.kind === "file");
const directoryItem = fileItems.find((item) => item.id === "src");
const fileItem = fileItems.find((item) => item.id === "src/index.ts");
expect(directoryItem?.onOpen).toBeUndefined();
expect(fileItem?.onOpen).toEqual(expect.any(Function));
});
});
it.each(["api", ""])("opens whole-file composer context in repository %j", (repositoryName) => {
const onOpenFileAtLine = vi.fn();
const items = buildContextItems({
planContextEnabled: false,
contextFiles: [],
resolvedSessionId: "session-1",
removeContextFile: vi.fn(),
unpinFile: vi.fn(),
addPlan: vi.fn(),
promptsMap: new Map(),
pendingCommentsByFile: {
file: [
{
source: "review-file",
id: "whole",
sessionId: "session-1",
repositoryName,
filePath: "README.md",
text: "Feedback",
status: "pending",
createdAt: "now",
},
],
},
handleRemoveCommentFile: vi.fn(),
handleRemoveComment: vi.fn(),
onOpenFileAtLine,
planComments: [],
handleClearPlanComments: vi.fn(),
pendingPRFeedback: [],
handleRemovePRFeedback: vi.fn(),
handleClearPRFeedback: vi.fn(),
walkthroughComments: [],
handleRemoveWalkthroughComment: vi.fn(),
handleClearWalkthroughComments: vi.fn(),
messageComments: [],
handleClearMessageComments: vi.fn(),
taskId: "task-1",
});
const item = items.find((candidate) => candidate.kind === "comment");
item?.onOpen?.();
expect(onOpenFileAtLine).toHaveBeenCalledWith("README.md", repositoryName);
expect(item).toHaveProperty("repositoryName", repositoryName);
});