From dfe62783b9b1b8bbd51f088197f98671fa2b5462 Mon Sep 17 00:00:00 2001 From: Dotta Date: Mon, 13 Jul 2026 09:36:24 +0000 Subject: [PATCH] test(ui): unit-test comment-reply helpers Cover buildReplyExcerpt (whitespace collapse, exact-length no-truncate, over-length clamp+flag, boundary trim), buildOptimisticReplyTo (identity mirror + null normalization), and formatReplyAuthorName (agent name, unknown agent fallback, System, You). Satisfies the PR quality gate's test requirement and locks the excerpt contract shared with the server snapshot. Co-Authored-By: Claude Opus 4.8 --- ui/src/lib/comment-reply.test.ts | 116 +++++++++++++++++++++++++++++++ 1 file changed, 116 insertions(+) create mode 100644 ui/src/lib/comment-reply.test.ts diff --git a/ui/src/lib/comment-reply.test.ts b/ui/src/lib/comment-reply.test.ts new file mode 100644 index 00000000000..72abd62dcd0 --- /dev/null +++ b/ui/src/lib/comment-reply.test.ts @@ -0,0 +1,116 @@ +import type { Agent, IssueComment } from "@paperclipai/shared"; +import { describe, expect, it } from "vitest"; +import { + REPLY_EXCERPT_MAX_LENGTH, + buildOptimisticReplyTo, + buildReplyExcerpt, + formatReplyAuthorName, +} from "./comment-reply"; + +function makeComment(overrides: Partial = {}): IssueComment { + const now = new Date("2026-04-06T13:00:00.000Z"); + return { + id: "comment-1", + companyId: "company-1", + issueId: "issue-1", + authorType: "agent", + authorAgentId: "agent-1", + authorUserId: null, + body: "Hello there", + presentation: null, + metadata: null, + createdAt: now, + updatedAt: now, + ...overrides, + }; +} + +describe("buildReplyExcerpt", () => { + it("collapses runs of whitespace and newlines into a single line", () => { + const { excerpt, truncated } = buildReplyExcerpt(" line one\n\n line two\t"); + expect(excerpt).toBe("line one line two"); + expect(truncated).toBe(false); + }); + + it("does not truncate a body at exactly the max length", () => { + const body = "a".repeat(REPLY_EXCERPT_MAX_LENGTH); + const { excerpt, truncated } = buildReplyExcerpt(body); + expect(excerpt).toHaveLength(REPLY_EXCERPT_MAX_LENGTH); + expect(truncated).toBe(false); + }); + + it("clamps and flags a body longer than the max length", () => { + const body = "b".repeat(REPLY_EXCERPT_MAX_LENGTH + 50); + const { excerpt, truncated } = buildReplyExcerpt(body); + expect(excerpt).toHaveLength(REPLY_EXCERPT_MAX_LENGTH); + expect(truncated).toBe(true); + }); + + it("trims trailing whitespace left at the clamp boundary", () => { + const body = `${"word ".repeat(60)}`; // well over the limit, boundary lands on a space + const { excerpt } = buildReplyExcerpt(body); + expect(excerpt.length).toBeLessThanOrEqual(REPLY_EXCERPT_MAX_LENGTH); + expect(excerpt).toBe(excerpt.trimEnd()); + }); +}); + +describe("buildOptimisticReplyTo", () => { + it("mirrors the target's identity and a clamped excerpt", () => { + const target = makeComment({ + id: "comment-src", + authorType: "agent", + authorAgentId: "agent-7", + authorUserId: null, + body: "c".repeat(REPLY_EXCERPT_MAX_LENGTH + 10), + }); + const snapshot = buildOptimisticReplyTo(target); + expect(snapshot).toEqual({ + commentId: "comment-src", + authorType: "agent", + authorAgentId: "agent-7", + authorUserId: null, + excerpt: "c".repeat(REPLY_EXCERPT_MAX_LENGTH), + excerptTruncated: true, + }); + }); + + it("normalizes undefined author ids to null for a user comment", () => { + const target = makeComment({ + id: "comment-user", + authorType: "user", + authorAgentId: undefined as unknown as string | null, + authorUserId: "user-9", + body: "short", + }); + const snapshot = buildOptimisticReplyTo(target); + expect(snapshot.authorAgentId).toBeNull(); + expect(snapshot.authorUserId).toBe("user-9"); + expect(snapshot.excerptTruncated).toBe(false); + }); +}); + +describe("formatReplyAuthorName", () => { + const agentMap = new Map([ + ["agent-1", { id: "agent-1", name: "CodexCoder" } as Agent], + ]); + + it("uses the agent name when the target is an agent in the map", () => { + expect( + formatReplyAuthorName({ authorType: "agent", authorAgentId: "agent-1" }, agentMap), + ).toBe("CodexCoder"); + }); + + it("falls back to a short agent id when the agent is unknown", () => { + expect( + formatReplyAuthorName({ authorType: "agent", authorAgentId: "agent-abcdef123" }, agentMap), + ).toBe("agent-ab"); + }); + + it("labels system authors as System", () => { + expect(formatReplyAuthorName({ authorType: "system", authorAgentId: null })).toBe("System"); + }); + + it("labels user authors as You", () => { + expect(formatReplyAuthorName({ authorType: "user", authorAgentId: null })).toBe("You"); + }); +});