Files
kandev/apps/web/lib/state/slices/comments/format.test.ts
T
João Salavisa f74de1c642 fix: improve plan comment formatting to match code review style (#600)
* fix: improve plan comment formatting to match code review style

- Update formatPlanCommentsAsMarkdown to use code blocks and blockquotes
- Remove duplicate header in chat-input-area.tsx
- Update buildDocumentContext to use consistent formatting
- Fixes duplicate '### Plan Comments' headers and extra separators

* fix: add tests and fix multiline blockquote handling

- Add format.test.ts for formatPlanCommentsAsMarkdown and related functions
- Fix multiline comment text to properly prefix each line with >
- Addresses review feedback about missing tests and broken blockquotes

* fix: export and reuse toBlockquote helper to avoid duplication

* fix: update E2E test to match new plan comment format

The test was expecting 'Comment 1:' which was the old bullet-style format.
Now looks for 'Plan Comments' header instead.
2026-04-16 02:57:41 +01:00

176 lines
5.3 KiB
TypeScript

import { describe, it, expect } from "vitest";
import {
formatPlanCommentsAsMarkdown,
formatReviewCommentsAsMarkdown,
formatPRFeedbackAsMarkdown,
formatCommentsForMessage,
} from "./format";
import type { PlanComment, DiffComment, PRFeedbackComment } from "./types";
function makePlanComment(overrides: Partial<PlanComment> = {}): PlanComment {
return {
id: "plan-1",
sessionId: "sess-1",
source: "plan",
text: "fix this",
selectedText: "some code",
createdAt: new Date().toISOString(),
status: "pending",
...overrides,
};
}
describe("formatPlanCommentsAsMarkdown", () => {
it("returns empty string for empty input", () => {
expect(formatPlanCommentsAsMarkdown([])).toBe("");
expect(formatPlanCommentsAsMarkdown(null as unknown as PlanComment[])).toBe("");
expect(formatPlanCommentsAsMarkdown(undefined as unknown as PlanComment[])).toBe("");
});
it("formats comment with selected text", () => {
const comments = [makePlanComment({ selectedText: "const x = 1;", text: "rename variable" })];
const result = formatPlanCommentsAsMarkdown(comments);
expect(result).toContain("### Plan Comments");
expect(result).toContain("```\nconst x = 1;\n```");
expect(result).toContain("> rename variable");
expect(result).toContain("---");
});
it("formats comment without selected text", () => {
const comments = [makePlanComment({ selectedText: "", text: "general feedback" })];
const result = formatPlanCommentsAsMarkdown(comments);
expect(result).toContain("### Plan Comments");
expect(result).not.toContain("```\n\n```"); // No empty code block
expect(result).toContain("> general feedback");
});
it("formats multiple comments", () => {
const comments = [
makePlanComment({ selectedText: "code1", text: "comment1" }),
makePlanComment({ id: "plan-2", selectedText: "code2", text: "comment2" }),
];
const result = formatPlanCommentsAsMarkdown(comments);
expect(result).toContain("```\ncode1\n```");
expect(result).toContain("> comment1");
expect(result).toContain("```\ncode2\n```");
expect(result).toContain("> comment2");
});
it("handles multiline comment text with blockquotes", () => {
const comments = [makePlanComment({ text: "line one\nline two\nline three" })];
const result = formatPlanCommentsAsMarkdown(comments);
expect(result).toContain("> line one\n> line two\n> line three");
});
it("includes header and separator", () => {
const comments = [makePlanComment()];
const result = formatPlanCommentsAsMarkdown(comments);
expect(result.startsWith("### Plan Comments\n")).toBe(true);
expect(result).toContain("\n---\n");
});
});
describe("formatReviewCommentsAsMarkdown", () => {
function makeDiffComment(overrides: Partial<DiffComment> = {}): DiffComment {
return {
id: "diff-1",
sessionId: "sess-1",
source: "diff",
filePath: "src/app.ts",
startLine: 10,
endLine: 12,
side: "additions",
codeContent: "const x = 1;",
text: "fix this",
createdAt: new Date().toISOString(),
status: "pending",
...overrides,
};
}
it("returns empty string for empty input", () => {
expect(formatReviewCommentsAsMarkdown([])).toBe("");
});
it("formats single comment", () => {
const result = formatReviewCommentsAsMarkdown([makeDiffComment()]);
expect(result).toContain("### Review Comments");
expect(result).toContain("**src/app.ts:10-12**");
expect(result).toContain("```\nconst x = 1;\n```");
expect(result).toContain("> fix this");
});
it("formats same-line range", () => {
const result = formatReviewCommentsAsMarkdown([makeDiffComment({ startLine: 5, endLine: 5 })]);
expect(result).toContain("**src/app.ts:5**");
});
});
describe("formatPRFeedbackAsMarkdown", () => {
function makePRFeedback(overrides: Partial<PRFeedbackComment> = {}): PRFeedbackComment {
return {
id: "pr-1",
sessionId: "sess-1",
source: "pr-feedback",
text: "",
prNumber: 123,
feedbackType: "review",
content: "Please fix the failing tests",
createdAt: new Date().toISOString(),
status: "pending",
...overrides,
};
}
it("returns empty string for empty input", () => {
expect(formatPRFeedbackAsMarkdown([])).toBe("");
});
it("formats PR feedback", () => {
const result = formatPRFeedbackAsMarkdown([makePRFeedback()]);
expect(result).toContain("### PR Feedback");
expect(result).toContain("Please fix the failing tests");
expect(result).toContain("---");
});
});
describe("formatCommentsForMessage", () => {
it("separates comments by type", () => {
const diff: DiffComment = {
id: "d1",
sessionId: "s",
source: "diff",
filePath: "f.ts",
startLine: 1,
endLine: 1,
side: "additions",
codeContent: "code",
text: "fix",
createdAt: "",
status: "pending",
};
const plan: PlanComment = {
id: "p1",
sessionId: "s",
source: "plan",
selectedText: "text",
text: "comment",
createdAt: "",
status: "pending",
};
const result = formatCommentsForMessage([diff, plan]);
expect(result.diffComments).toHaveLength(1);
expect(result.planComments).toHaveLength(1);
expect(result.prFeedbackComments).toHaveLength(0);
});
});