581 lines
16 KiB
TypeScript
581 lines
16 KiB
TypeScript
import { expect, test, describe } from "bun:test";
|
|
import {
|
|
formatContext,
|
|
formatBody,
|
|
formatComments,
|
|
formatReviewComments,
|
|
formatChangedFiles,
|
|
formatChangedFilesWithSHA,
|
|
} from "../src/github/data/formatter";
|
|
import type {
|
|
GitHubPullRequest,
|
|
GitHubIssue,
|
|
GitHubComment,
|
|
GitHubFile,
|
|
} from "../src/github/types";
|
|
import type { GitHubFileWithSHA } from "../src/github/data/fetcher";
|
|
|
|
describe("formatContext", () => {
|
|
test("formats PR context correctly", () => {
|
|
const prData: GitHubPullRequest = {
|
|
title: "Test PR",
|
|
body: "PR body",
|
|
author: { login: "test-user" },
|
|
baseRefName: "main",
|
|
headRefName: "feature/test",
|
|
headRefOid: "abc123",
|
|
createdAt: "2023-01-01T00:00:00Z",
|
|
additions: 50,
|
|
deletions: 30,
|
|
state: "OPEN",
|
|
commits: {
|
|
totalCount: 3,
|
|
nodes: [],
|
|
},
|
|
files: {
|
|
nodes: [{} as GitHubFile, {} as GitHubFile],
|
|
},
|
|
comments: {
|
|
nodes: [],
|
|
},
|
|
reviews: {
|
|
nodes: [],
|
|
},
|
|
};
|
|
|
|
const result = formatContext(prData, true);
|
|
expect(result).toBe(
|
|
`PR Title: Test PR
|
|
PR Author: test-user
|
|
PR Branch: feature/test -> main
|
|
PR State: OPEN
|
|
PR Additions: 50
|
|
PR Deletions: 30
|
|
Total Commits: 3
|
|
Changed Files: 2 files`,
|
|
);
|
|
});
|
|
|
|
test("formats Issue context correctly", () => {
|
|
const issueData: GitHubIssue = {
|
|
title: "Test Issue",
|
|
body: "Issue body",
|
|
author: { login: "test-user" },
|
|
createdAt: "2023-01-01T00:00:00Z",
|
|
state: "OPEN",
|
|
comments: {
|
|
nodes: [],
|
|
},
|
|
};
|
|
|
|
const result = formatContext(issueData, false);
|
|
expect(result).toBe(
|
|
`Issue Title: Test Issue
|
|
Issue Author: test-user
|
|
Issue State: OPEN`,
|
|
);
|
|
});
|
|
});
|
|
|
|
describe("formatBody", () => {
|
|
test("replaces image URLs with local paths", () => {
|
|
const body = `Here is some text with an image: 
|
|
|
|
And another one: 
|
|
|
|
Some more text.`;
|
|
|
|
const imageUrlMap = new Map([
|
|
[
|
|
"https://github.com/user-attachments/assets/test-image.png",
|
|
"/tmp/github-images/image-1234-0.png",
|
|
],
|
|
[
|
|
"https://github.com/user-attachments/assets/another-image.jpg",
|
|
"/tmp/github-images/image-1234-1.jpg",
|
|
],
|
|
]);
|
|
|
|
const result = formatBody(body, imageUrlMap);
|
|
expect(result)
|
|
.toBe(`Here is some text with an image: 
|
|
|
|
And another one: 
|
|
|
|
Some more text.`);
|
|
});
|
|
|
|
test("handles empty image map", () => {
|
|
const body = "No images here";
|
|
const imageUrlMap = new Map<string, string>();
|
|
|
|
const result = formatBody(body, imageUrlMap);
|
|
expect(result).toBe("No images here");
|
|
});
|
|
|
|
test("preserves body when no images match", () => {
|
|
const body = "";
|
|
const imageUrlMap = new Map([
|
|
[
|
|
"https://github.com/user-attachments/assets/different.png",
|
|
"/tmp/github-images/image-1234-0.png",
|
|
],
|
|
]);
|
|
|
|
const result = formatBody(body, imageUrlMap);
|
|
expect(result).toBe("");
|
|
});
|
|
|
|
test("handles multiple occurrences of same image", () => {
|
|
const body = `First: 
|
|
Second: `;
|
|
|
|
const imageUrlMap = new Map([
|
|
[
|
|
"https://github.com/user-attachments/assets/test.png",
|
|
"/tmp/github-images/image-1234-0.png",
|
|
],
|
|
]);
|
|
|
|
const result = formatBody(body, imageUrlMap);
|
|
expect(result).toBe(`First: 
|
|
Second: `);
|
|
});
|
|
});
|
|
|
|
describe("formatComments", () => {
|
|
test("formats comments correctly", () => {
|
|
const comments: GitHubComment[] = [
|
|
{
|
|
id: "1",
|
|
databaseId: "100001",
|
|
body: "First comment",
|
|
author: { login: "user1" },
|
|
createdAt: "2023-01-01T00:00:00Z",
|
|
},
|
|
{
|
|
id: "2",
|
|
databaseId: "100002",
|
|
body: "Second comment",
|
|
author: { login: "user2" },
|
|
createdAt: "2023-01-02T00:00:00Z",
|
|
},
|
|
];
|
|
|
|
const result = formatComments(comments);
|
|
expect(result).toBe(
|
|
`[user1 at 2023-01-01T00:00:00Z]: First comment\n\n[user2 at 2023-01-02T00:00:00Z]: Second comment`,
|
|
);
|
|
});
|
|
|
|
test("returns empty string for empty comments array", () => {
|
|
const result = formatComments([]);
|
|
expect(result).toBe("");
|
|
});
|
|
|
|
test("replaces image URLs in comments", () => {
|
|
const comments: GitHubComment[] = [
|
|
{
|
|
id: "1",
|
|
databaseId: "100001",
|
|
body: "Check out this screenshot: ",
|
|
author: { login: "user1" },
|
|
createdAt: "2023-01-01T00:00:00Z",
|
|
},
|
|
{
|
|
id: "2",
|
|
databaseId: "100002",
|
|
body: "Here's another image: ",
|
|
author: { login: "user2" },
|
|
createdAt: "2023-01-02T00:00:00Z",
|
|
},
|
|
];
|
|
|
|
const imageUrlMap = new Map([
|
|
[
|
|
"https://github.com/user-attachments/assets/screenshot.png",
|
|
"/tmp/github-images/image-1234-0.png",
|
|
],
|
|
[
|
|
"https://github.com/user-attachments/assets/bug-report.jpg",
|
|
"/tmp/github-images/image-1234-1.jpg",
|
|
],
|
|
]);
|
|
|
|
const result = formatComments(comments, imageUrlMap);
|
|
expect(result).toBe(
|
|
`[user1 at 2023-01-01T00:00:00Z]: Check out this screenshot: \n\n[user2 at 2023-01-02T00:00:00Z]: Here's another image: `,
|
|
);
|
|
});
|
|
|
|
test("handles comments with multiple images", () => {
|
|
const comments: GitHubComment[] = [
|
|
{
|
|
id: "1",
|
|
databaseId: "100001",
|
|
body: "Two images:  and ",
|
|
author: { login: "user1" },
|
|
createdAt: "2023-01-01T00:00:00Z",
|
|
},
|
|
];
|
|
|
|
const imageUrlMap = new Map([
|
|
[
|
|
"https://github.com/user-attachments/assets/first.png",
|
|
"/tmp/github-images/image-1234-0.png",
|
|
],
|
|
[
|
|
"https://github.com/user-attachments/assets/second.png",
|
|
"/tmp/github-images/image-1234-1.png",
|
|
],
|
|
]);
|
|
|
|
const result = formatComments(comments, imageUrlMap);
|
|
expect(result).toBe(
|
|
`[user1 at 2023-01-01T00:00:00Z]: Two images:  and `,
|
|
);
|
|
});
|
|
|
|
test("preserves comments when imageUrlMap is undefined", () => {
|
|
const comments: GitHubComment[] = [
|
|
{
|
|
id: "1",
|
|
databaseId: "100001",
|
|
body: "Image: ",
|
|
author: { login: "user1" },
|
|
createdAt: "2023-01-01T00:00:00Z",
|
|
},
|
|
];
|
|
|
|
const result = formatComments(comments);
|
|
expect(result).toBe(
|
|
`[user1 at 2023-01-01T00:00:00Z]: Image: `,
|
|
);
|
|
});
|
|
});
|
|
|
|
describe("formatReviewComments", () => {
|
|
test("formats review with body and comments correctly", () => {
|
|
const reviewData = {
|
|
nodes: [
|
|
{
|
|
id: "review1",
|
|
databaseId: "300001",
|
|
author: { login: "reviewer1" },
|
|
body: "This is a great PR! LGTM.",
|
|
state: "APPROVED",
|
|
submittedAt: "2023-01-01T00:00:00Z",
|
|
comments: {
|
|
nodes: [
|
|
{
|
|
id: "comment1",
|
|
databaseId: "200001",
|
|
body: "Nice implementation",
|
|
author: { login: "reviewer1" },
|
|
createdAt: "2023-01-01T00:00:00Z",
|
|
path: "src/index.ts",
|
|
line: 42,
|
|
},
|
|
{
|
|
id: "comment2",
|
|
databaseId: "200002",
|
|
body: "Consider adding error handling",
|
|
author: { login: "reviewer1" },
|
|
createdAt: "2023-01-01T00:00:00Z",
|
|
path: "src/utils.ts",
|
|
line: null,
|
|
},
|
|
],
|
|
},
|
|
},
|
|
],
|
|
};
|
|
|
|
const result = formatReviewComments(reviewData);
|
|
expect(result).toBe(
|
|
`[Review by reviewer1 at 2023-01-01T00:00:00Z]: APPROVED\n [Comment on src/index.ts:42]: Nice implementation\n [Comment on src/utils.ts:?]: Consider adding error handling`,
|
|
);
|
|
});
|
|
|
|
test("formats review with only body (no comments) correctly", () => {
|
|
const reviewData = {
|
|
nodes: [
|
|
{
|
|
id: "review1",
|
|
databaseId: "300002",
|
|
author: { login: "reviewer1" },
|
|
body: "Looks good to me!",
|
|
state: "APPROVED",
|
|
submittedAt: "2023-01-01T00:00:00Z",
|
|
comments: {
|
|
nodes: [],
|
|
},
|
|
},
|
|
],
|
|
};
|
|
|
|
const result = formatReviewComments(reviewData);
|
|
expect(result).toBe(
|
|
`[Review by reviewer1 at 2023-01-01T00:00:00Z]: APPROVED`,
|
|
);
|
|
});
|
|
|
|
test("formats review without body correctly", () => {
|
|
const reviewData = {
|
|
nodes: [
|
|
{
|
|
id: "review1",
|
|
databaseId: "300003",
|
|
author: { login: "reviewer1" },
|
|
body: "",
|
|
state: "COMMENTED",
|
|
submittedAt: "2023-01-01T00:00:00Z",
|
|
comments: {
|
|
nodes: [
|
|
{
|
|
id: "comment1",
|
|
databaseId: "200003",
|
|
body: "Small suggestion here",
|
|
author: { login: "reviewer1" },
|
|
createdAt: "2023-01-01T00:00:00Z",
|
|
path: "src/main.ts",
|
|
line: 15,
|
|
},
|
|
],
|
|
},
|
|
},
|
|
],
|
|
};
|
|
|
|
const result = formatReviewComments(reviewData);
|
|
expect(result).toBe(
|
|
`[Review by reviewer1 at 2023-01-01T00:00:00Z]: COMMENTED\n [Comment on src/main.ts:15]: Small suggestion here`,
|
|
);
|
|
});
|
|
|
|
test("formats multiple reviews correctly", () => {
|
|
const reviewData = {
|
|
nodes: [
|
|
{
|
|
id: "review1",
|
|
databaseId: "300004",
|
|
author: { login: "reviewer1" },
|
|
body: "Needs changes",
|
|
state: "CHANGES_REQUESTED",
|
|
submittedAt: "2023-01-01T00:00:00Z",
|
|
comments: {
|
|
nodes: [],
|
|
},
|
|
},
|
|
{
|
|
id: "review2",
|
|
databaseId: "300005",
|
|
author: { login: "reviewer2" },
|
|
body: "LGTM",
|
|
state: "APPROVED",
|
|
submittedAt: "2023-01-02T00:00:00Z",
|
|
comments: {
|
|
nodes: [],
|
|
},
|
|
},
|
|
],
|
|
};
|
|
|
|
const result = formatReviewComments(reviewData);
|
|
expect(result).toBe(
|
|
`[Review by reviewer1 at 2023-01-01T00:00:00Z]: CHANGES_REQUESTED\n\n[Review by reviewer2 at 2023-01-02T00:00:00Z]: APPROVED`,
|
|
);
|
|
});
|
|
|
|
test("returns empty string for null reviewData", () => {
|
|
const result = formatReviewComments(null);
|
|
expect(result).toBe("");
|
|
});
|
|
|
|
test("returns empty string for empty reviewData", () => {
|
|
const result = formatReviewComments({ nodes: [] });
|
|
expect(result).toBe("");
|
|
});
|
|
|
|
test("replaces image URLs in review comments", () => {
|
|
const reviewData = {
|
|
nodes: [
|
|
{
|
|
id: "review1",
|
|
databaseId: "300001",
|
|
author: { login: "reviewer1" },
|
|
body: "Review with image: ",
|
|
state: "APPROVED",
|
|
submittedAt: "2023-01-01T00:00:00Z",
|
|
comments: {
|
|
nodes: [
|
|
{
|
|
id: "comment1",
|
|
databaseId: "200001",
|
|
body: "Comment with image: ",
|
|
author: { login: "reviewer1" },
|
|
createdAt: "2023-01-01T00:00:00Z",
|
|
path: "src/index.ts",
|
|
line: 42,
|
|
},
|
|
],
|
|
},
|
|
},
|
|
],
|
|
};
|
|
|
|
const imageUrlMap = new Map([
|
|
[
|
|
"https://github.com/user-attachments/assets/review.png",
|
|
"/tmp/github-images/image-1234-0.png",
|
|
],
|
|
[
|
|
"https://github.com/user-attachments/assets/comment.png",
|
|
"/tmp/github-images/image-1234-1.png",
|
|
],
|
|
]);
|
|
|
|
const result = formatReviewComments(reviewData, imageUrlMap);
|
|
expect(result).toBe(
|
|
`[Review by reviewer1 at 2023-01-01T00:00:00Z]: APPROVED\n [Comment on src/index.ts:42]: Comment with image: `,
|
|
);
|
|
});
|
|
|
|
test("handles multiple images in review comments", () => {
|
|
const reviewData = {
|
|
nodes: [
|
|
{
|
|
id: "review1",
|
|
databaseId: "300001",
|
|
author: { login: "reviewer1" },
|
|
body: "Good work",
|
|
state: "APPROVED",
|
|
submittedAt: "2023-01-01T00:00:00Z",
|
|
comments: {
|
|
nodes: [
|
|
{
|
|
id: "comment1",
|
|
databaseId: "200001",
|
|
body: "Two issues:  and ",
|
|
author: { login: "reviewer1" },
|
|
createdAt: "2023-01-01T00:00:00Z",
|
|
path: "src/main.ts",
|
|
line: 15,
|
|
},
|
|
],
|
|
},
|
|
},
|
|
],
|
|
};
|
|
|
|
const imageUrlMap = new Map([
|
|
[
|
|
"https://github.com/user-attachments/assets/issue1.png",
|
|
"/tmp/github-images/image-1234-0.png",
|
|
],
|
|
[
|
|
"https://github.com/user-attachments/assets/issue2.png",
|
|
"/tmp/github-images/image-1234-1.png",
|
|
],
|
|
]);
|
|
|
|
const result = formatReviewComments(reviewData, imageUrlMap);
|
|
expect(result).toBe(
|
|
`[Review by reviewer1 at 2023-01-01T00:00:00Z]: APPROVED\n [Comment on src/main.ts:15]: Two issues:  and `,
|
|
);
|
|
});
|
|
|
|
test("preserves review comments when imageUrlMap is undefined", () => {
|
|
const reviewData = {
|
|
nodes: [
|
|
{
|
|
id: "review1",
|
|
databaseId: "300001",
|
|
author: { login: "reviewer1" },
|
|
body: "Review body",
|
|
state: "APPROVED",
|
|
submittedAt: "2023-01-01T00:00:00Z",
|
|
comments: {
|
|
nodes: [
|
|
{
|
|
id: "comment1",
|
|
databaseId: "200001",
|
|
body: "Image: ",
|
|
author: { login: "reviewer1" },
|
|
createdAt: "2023-01-01T00:00:00Z",
|
|
path: "src/index.ts",
|
|
line: 42,
|
|
},
|
|
],
|
|
},
|
|
},
|
|
],
|
|
};
|
|
|
|
const result = formatReviewComments(reviewData);
|
|
expect(result).toBe(
|
|
`[Review by reviewer1 at 2023-01-01T00:00:00Z]: APPROVED\n [Comment on src/index.ts:42]: Image: `,
|
|
);
|
|
});
|
|
});
|
|
|
|
describe("formatChangedFiles", () => {
|
|
test("formats changed files correctly", () => {
|
|
const files: GitHubFile[] = [
|
|
{
|
|
path: "src/index.ts",
|
|
additions: 10,
|
|
deletions: 5,
|
|
changeType: "MODIFIED",
|
|
},
|
|
{
|
|
path: "src/utils.ts",
|
|
additions: 20,
|
|
deletions: 0,
|
|
changeType: "ADDED",
|
|
},
|
|
];
|
|
|
|
const result = formatChangedFiles(files);
|
|
expect(result).toBe(
|
|
`- src/index.ts (MODIFIED) +10/-5\n- src/utils.ts (ADDED) +20/-0`,
|
|
);
|
|
});
|
|
|
|
test("returns empty string for empty files array", () => {
|
|
const result = formatChangedFiles([]);
|
|
expect(result).toBe("");
|
|
});
|
|
});
|
|
|
|
describe("formatChangedFilesWithSHA", () => {
|
|
test("formats changed files with SHA correctly", () => {
|
|
const files: GitHubFileWithSHA[] = [
|
|
{
|
|
path: "src/index.ts",
|
|
additions: 10,
|
|
deletions: 5,
|
|
changeType: "MODIFIED",
|
|
sha: "abc123",
|
|
},
|
|
{
|
|
path: "src/utils.ts",
|
|
additions: 20,
|
|
deletions: 0,
|
|
changeType: "ADDED",
|
|
sha: "def456",
|
|
},
|
|
];
|
|
|
|
const result = formatChangedFilesWithSHA(files);
|
|
expect(result).toBe(
|
|
`- src/index.ts (MODIFIED) +10/-5 SHA: abc123\n- src/utils.ts (ADDED) +20/-0 SHA: def456`,
|
|
);
|
|
});
|
|
|
|
test("returns empty string for empty files array", () => {
|
|
const result = formatChangedFilesWithSHA([]);
|
|
expect(result).toBe("");
|
|
});
|
|
});
|