1import { describe, expect, it } from "vite-plus/test";
2import type { PullRequestReviewThread } from "@t3tools/contracts";
3import type { NativeReviewDiffData } from "../review/nativeReviewDiffAdapter";
4import {
5 annotatePullRequestDiff,
6 reviewPosition,
7 reviewPositionLabel,
8} from "./pullRequestDiffComments";
9const data: NativeReviewDiffData = {
10 rows: [
11 { kind: "file", id: "header", fileId: "f", filePath: "a.ts" },
12 { kind: "line", id: "old", fileId: "f", change: "delete", oldLineNumber: 2 },
13 { kind: "line", id: "new", fileId: "f", change: "add", newLineNumber: 2 },
14 ],
15 files: [{ id: "f", path: "a.ts", language: "typescript", additions: 1, deletions: 1 }],
16 commentTargetsByRowId: new Map(),
17 rowIdByCommentLineId: new Map(),
18 additions: 1,
19 deletions: 1,
20};
21const thread: PullRequestReviewThread = {
22 id: "t",
23 path: "a.ts",
24 line: 2,
25 side: "left",
26 isResolved: false,
27 isOutdated: false,
28 comments: [],
29};
30describe("PR comments in continuous native diffs", () => {
31 it("anchors old/new lines separately despite identical line numbers", () => {
32 const annotated = annotatePullRequestDiff(
33 data,
34 [thread, { ...thread, id: "right", side: "right" }],
35 [],
36 "abc",
37 );
38 expect(annotated.rows.map((row) => row.id)).toEqual([
39 "header",
40 "old",
41 "discussion:t",
42 "new",
43 "discussion:right",
44 ]);
45 });
46 it("keeps outdated and unloaded anchors off the current code", () => {
47 const annotated = annotatePullRequestDiff(
48 data,
49 [
50 { ...thread, isOutdated: true },
51 { ...thread, id: "missing", path: "b.ts" },
52 ],
53 [
54 {
55 id: "draft",
56 path: "a.ts",
57 position: { kind: "added", newLine: 2 },
58 body: "Feedback",
59 code: "",
60 headSha: "old",
61 },
62 ],
63 "new",
64 );
65 expect(annotated.rows).toEqual(data.rows);
66 });
67 it("supports file-level threads and current private drafts without changing code IDs", () => {
68 const annotated = annotatePullRequestDiff(
69 data,
70 [{ ...thread, line: null }],
71 [
72 {
73 id: "draft",
74 path: "a.ts",
75 position: { kind: "added", newLine: 2 },
76 body: "Feedback",
77 code: "",
78 headSha: "abc",
79 },
80 ],
81 "abc",
82 );
83 expect(annotated.rows.map((row) => row.id)).toEqual([
84 "header",
85 "discussion:t",
86 "old",
87 "new",
88 "draft:draft",
89 ]);
90 expect(annotated.commentTargetsByRowId).toBe(data.commentTargetsByRowId);
91 });
92 it("does not guess positions for missing lines, notices, or binary files", () => {
93 expect(reviewPosition(data.rows[0]!)).toBeNull();
94 expect(reviewPosition(data.rows[1]!)).toEqual({ kind: "deleted", oldLine: 2 });
95 expect(reviewPosition(data.rows[2]!)).toEqual({ kind: "added", newLine: 2 });
96 expect(
97 reviewPosition({ kind: "line", id: "context", oldLineNumber: 3, newLineNumber: 4 }),
98 ).toEqual({ kind: "context", side: "right", oldLine: 3, newLine: 4 });
99 });
100 it("uses the old line number for left-side context when the line numbers differ", () => {
101 const position = { kind: "context" as const, side: "left" as const, oldLine: 7, newLine: 8 };
102 const annotated = annotatePullRequestDiff(
103 {
104 ...data,
105 rows: [
106 ...data.rows,
107 { kind: "line", id: "context", fileId: "f", oldLineNumber: 7, newLineNumber: 8 },
108 ],
109 },
110 [],
111 [{ id: "left", path: "a.ts", position, headSha: "abc", code: "", body: "Feedback" }],
112 "abc",
113 );
114 expect(annotated.rows.at(-1)?.id).toBe("draft:left");
115 expect(reviewPositionLabel(position)).toBe("Old line 7");
116 });
117});