import { PullRequestAction, type PullRequestCheck, type PullRequestComment, type PullRequestDetailView, type PullRequestReviewThread,} from "@t3tools/contracts";import { describe, expect, it } from "vite-plus/test"; import { buildAddSelectionToAgentHandoff, buildAskAboutPullRequestHandoff, buildExplainPullRequestHandoff, buildFixFindingHandoff, buildFixFindingsHandoff, groupPullRequestTimelineConversations, handoffPrompt, handoffReviewComments, isPullRequestVerdictStale, isStackedPullRequestBase, isThreadOwnPullRequest, latestPullRequestReviewOutcomes, newestPullRequestCommitAt, mergePullRequestThreadComments, orderPullRequestComments, pullRequestActionMenuHasGroup, pullRequestActionNeedsHostRefresh, pullRequestComposerTarget, pullRequestFindingKey, pullRequestHandoffLabels, pullRequestReviewOutcome, readableFailure, shouldRefreshPullRequestActivity, resolveBaseFreshness, buildPullRequestTimeline, describePullRequestState, editPullRequestThreadComment,} from "./pullRequestDetail.logic";import type { ReviewCommentContext } from "~/reviewCommentContext"; const TIMELINE_SOURCE: Pick< PullRequestDetailView, "createdAt" | "author" | "commits" | "comments" | "mergedAt" | "closedAt"> = { createdAt: "2026-07-01T00:00:00Z", author: { login: "octocat", name: null, avatarUrl: null }, commits: [ { oid: "1baf7bdcafe", messageHeadline: "add the page", committedDate: "2026-07-02T00:00:00Z" }, ], comments: [ { id: "c1", kind: "issue-comment", author: { login: "bilal", name: null, avatarUrl: null }, body: "looks good", createdAt: "2026-07-03T00:00:00Z", url: null, path: null, reviewState: null, }, ], mergedAt: null, closedAt: null,}; describe("pull request activity refresh", () => { const first = { key: "project:acme/web#7", updatedAt: "2026-08-13T13:00:00Z", }; it("refreshes activity only after the same pull request changes", () => { expect( shouldRefreshPullRequestActivity(first, { ...first, updatedAt: "2026-08-13T13:01:00Z", }), ).toBe(true); }); it("does not duplicate the first activity read or carry a revision across pull requests", () => { expect(shouldRefreshPullRequestActivity(null, first)).toBe(false); expect(shouldRefreshPullRequestActivity(first, first)).toBe(false); expect( shouldRefreshPullRequestActivity(first, { key: "project:acme/web#8", updatedAt: "2026-08-13T13:01:00Z", }), ).toBe(false); });});describe("review thread comment pages", () => { it("appends new comments once and keeps refreshed base comments", () => { expect( mergePullRequestThreadComments( [ { id: "c1", body: "refreshed" }, { id: "c2", body: "already in base" }, ], [ { id: "c2", body: "stale page copy" }, { id: "c3", body: "next page" }, ], ), ).toEqual([ { id: "c1", body: "refreshed" }, { id: "c2", body: "already in base" }, { id: "c3", body: "next page" }, ]); }); it("keeps a loaded comment after its body is edited", () => { const loaded = [ { id: "c2", body: "old body" }, { id: "c3", body: "another loaded comment" }, ]; expect(editPullRequestThreadComment(loaded, "c2", "saved body")).toEqual([ { id: "c2", body: "saved body" }, { id: "c3", body: "another loaded comment" }, ]); });}); describe("pull request action menu", () => { it("keeps the group divider when auto-merge is the only action", () => { expect(pullRequestActionMenuHasGroup(false, true, false)).toBe(true); });}); describe("pull request state description", () => { it("keeps draft and conflicts orthogonal to the terminal states", () => { expect(describePullRequestState("open", true)).toBe("Draft"); expect(describePullRequestState("open", false)).toBe("Ready for review"); expect(describePullRequestState("merged", true)).toBe("Merged"); expect(describePullRequestState("closed", false)).toBe("Closed"); });}); describe("pull request handoff labels", () => { it("names the open thread when actions write to its composer", () => { expect(pullRequestHandoffLabels(true)).toEqual({ fixFinding: "Fix in this thread", fixCheck: "Fix in this thread", fixFindings: "Fix findings in this thread", }); }); it("keeps the standalone pull request page labels", () => { expect(pullRequestHandoffLabels(false)).toEqual({ fixFinding: "Fix in a thread", fixCheck: "Fix", fixFindings: "Fix findings in a thread", }); });}); describe("pull request composer target", () => { it("rejects a page composer so agent comments cannot open another thread", () => { const target = { environmentId: "env-1", threadId: "thread-1" }; expect(pullRequestComposerTarget("page", target)).toBeNull(); expect(pullRequestComposerTarget("thread", target)).toBe(target); });}); describe("stacked pull request classification", () => { it("requires a known default branch", () => { expect(isStackedPullRequestBase("main", [{ name: "main", isDefault: false }])).toBe(false); }); it("recognizes local and remote forms of the default branch", () => { expect( isStackedPullRequestBase("main", [{ name: "main", isDefault: true, isRemote: false }]), ).toBe(false); expect( isStackedPullRequestBase("main", [ { name: "origin/main", isDefault: true, isRemote: true, remoteName: "origin" }, ]), ).toBe(false); }); it("classifies a non-default base as stacked once the default is known", () => { expect( isStackedPullRequestBase("feature-base", [ { name: "origin/main", isDefault: true, isRemote: true, remoteName: "origin" }, ]), ).toBe(true); }); it("does not mistake a nested branch suffix for the default branch", () => { expect( isStackedPullRequestBase("main", [ { name: "origin/feature/main", isDefault: true, isRemote: true, remoteName: "origin", }, ]), ).toBe(true); expect( isStackedPullRequestBase("1.0", [{ name: "release/1.0", isDefault: true, isRemote: false }]), ).toBe(true); });}); describe("ordering comments", () => { it("reverses the chronological list for newest first, and leaves oldest first alone", () => { const comments = [{ createdAt: "a" }, { createdAt: "b" }, { createdAt: "c" }]; expect(orderPullRequestComments(comments, "newest")).toEqual([ { createdAt: "c" }, { createdAt: "b" }, { createdAt: "a" }, ]); expect(orderPullRequestComments(comments, "oldest")).toEqual(comments); // The source array is chronological input, not a mutation target. expect(comments).toEqual([{ createdAt: "a" }, { createdAt: "b" }, { createdAt: "c" }]); });}); describe("review verdicts", () => { it("reads the same three verdicts however a host spells them", () => { expect(pullRequestReviewOutcome("APPROVED")).toBe("approved"); expect(pullRequestReviewOutcome("approved")).toBe("approved"); expect(pullRequestReviewOutcome("CHANGES_REQUESTED")).toBe("changes-requested"); expect(pullRequestReviewOutcome("changes_requested")).toBe("changes-requested"); expect(pullRequestReviewOutcome("DISMISSED")).toBe("dismissed"); }); it("is not a verdict where the review only carried remarks", () => { expect(pullRequestReviewOutcome("COMMENTED")).toBeNull(); expect(pullRequestReviewOutcome("PENDING")).toBeNull(); expect(pullRequestReviewOutcome(null)).toBeNull(); }); it("keeps each reviewer's last word, whatever order the host returned them in", () => { const review = ( id: string, login: string, reviewState: string, createdAt: string, ): PullRequestComment => ({ id, kind: "review", author: { login, name: null, avatarUrl: null }, body: "", createdAt, url: null, path: null, reviewState, }); expect( latestPullRequestReviewOutcomes([ review("r3", "bilal", "APPROVED", "2026-07-03T00:00:00Z"), review("r1", "bilal", "CHANGES_REQUESTED", "2026-07-01T00:00:00Z"), review("r2", "octocat", "CHANGES_REQUESTED", "2026-07-02T00:00:00Z"), // Not a verdict, so it neither adds a reviewer nor overwrites one. review("r4", "octocat", "COMMENTED", "2026-07-04T00:00:00Z"), ]).map((entry) => [entry.actor?.login, entry.outcome]), ).toEqual([ ["bilal", "approved"], ["octocat", "changes-requested"], ]); }); it("keeps two deleted accounts apart rather than counting them as one reviewer", () => { expect( latestPullRequestReviewOutcomes([ { ...TIMELINE_SOURCE.comments[0]!, id: "r1", kind: "review", author: null, reviewState: "APPROVED", createdAt: "2026-07-01T00:00:00Z", }, { ...TIMELINE_SOURCE.comments[0]!, id: "r2", kind: "review", author: null, reviewState: "APPROVED", createdAt: "2026-07-02T00:00:00Z", }, ]), ).toHaveLength(2); }); it("gives every entry a key that separates the reviewers it kept apart", () => { const entries = latestPullRequestReviewOutcomes([ { ...TIMELINE_SOURCE.comments[0]!, id: "r1", kind: "review", author: null, reviewState: "APPROVED", createdAt: "2026-07-01T00:00:00Z", }, { ...TIMELINE_SOURCE.comments[0]!, id: "r2", kind: "review", author: null, reviewState: "APPROVED", createdAt: "2026-07-01T00:00:00Z", }, ]); // Same author (none) and the same instant, so only the review's own id tells them apart. expect(new Set(entries.map((entry) => entry.key)).size).toBe(2); }); it("calls a verdict stale once commits land after it, and current before that", () => { const commits = [ { oid: "c0ffee", messageHeadline: "later work", committedDate: "2026-07-05T00:00:00Z" }, ]; const review = (createdAt: string): PullRequestComment => ({ ...TIMELINE_SOURCE.comments[0]!, kind: "review", reviewState: "APPROVED", createdAt, }); expect( latestPullRequestReviewOutcomes([review("2026-07-01T00:00:00Z")], commits)[0]?.stale, ).toBe(true); expect( latestPullRequestReviewOutcomes([review("2026-07-06T00:00:00Z")], commits)[0]?.stale, ).toBe(false); // Nothing to be overtaken by, so nothing is stale. expect(latestPullRequestReviewOutcomes([review("2026-07-01T00:00:00Z")], [])[0]?.stale).toBe( false, ); }); it("measures staleness against the newest commit, not the last one listed", () => { expect( newestPullRequestCommitAt([ { oid: "a", messageHeadline: "", committedDate: "2026-07-09T00:00:00Z" }, { oid: "b", messageHeadline: "", committedDate: "2026-07-02T00:00:00Z" }, ]), ).toBe("2026-07-09T00:00:00Z"); expect(newestPullRequestCommitAt([])).toBeNull(); }); it("orders instants rather than their text, so a UTC offset cannot invert them", () => { // 01:00+02:00 is 23:00 the previous day, so as text it sorts after the Z stamp and in time // it falls well before it. expect( newestPullRequestCommitAt([ { oid: "a", messageHeadline: "", committedDate: "2026-07-05T00:30:00Z" }, { oid: "b", messageHeadline: "", committedDate: "2026-07-05T01:00:00+02:00" }, ]), ).toBe("2026-07-05T00:30:00Z"); expect(isPullRequestVerdictStale("2026-07-05T00:30:00Z", "2026-07-05T01:00:00+02:00")).toBe( false, ); // A timestamp nothing can parse is not a position, so it settles nothing either way. expect(isPullRequestVerdictStale("2026-07-01T00:00:00Z", "not a date")).toBe(false); expect( newestPullRequestCommitAt([{ oid: "a", messageHeadline: "", committedDate: "not a date" }]), ).toBeNull(); }); it("shows nothing for a reviewer whose verdict was dismissed", () => { expect( latestPullRequestReviewOutcomes([ { ...TIMELINE_SOURCE.comments[0]!, kind: "review", reviewState: "APPROVED", createdAt: "2026-07-01T00:00:00Z", }, { ...TIMELINE_SOURCE.comments[0]!, id: "c2", kind: "review", reviewState: "DISMISSED", createdAt: "2026-07-02T00:00:00Z", }, ]), ).toEqual([]); });}); describe("pull request timeline", () => { it("orders creation, commits and comments newest first", () => { // What happened last is what the reader opening the tab is asking about. expect(buildPullRequestTimeline(TIMELINE_SOURCE).map((event) => event.id)).toEqual([ "c1", "1baf7bdcafe", "created", ]); }); it("carries the comment url, and leaves the events the host cannot address without one", () => { const events = buildPullRequestTimeline({ ...TIMELINE_SOURCE, comments: [{ ...TIMELINE_SOURCE.comments[0]!, url: "https://example.test/pull/1#c1" }], }); expect(events.map((event) => event.url)).toEqual([ "https://example.test/pull/1#c1", null, null, ]); }); it("carries actors and review context into the presentation model", () => { const commitAuthors = [ { login: "octocat", name: null, avatarUrl: "https://example.test/octocat.png" }, { login: "pair", name: "Pair Author", avatarUrl: null }, ]; const events = buildPullRequestTimeline({ ...TIMELINE_SOURCE, commits: [{ ...TIMELINE_SOURCE.commits[0]!, authors: commitAuthors }], comments: [ { ...TIMELINE_SOURCE.comments[0]!, kind: "review", path: "src/app.ts", reviewState: "APPROVED", }, ], }); expect(events.find((event) => event.kind === "commit")?.commitAuthors).toEqual(commitAuthors); expect(events.find((event) => event.kind === "review")).toMatchObject({ actor: TIMELINE_SOURCE.comments[0]?.author, path: "src/app.ts", reviewState: "APPROVED", }); }); it("carries each commit's line counts into its timeline event", () => { const events = buildPullRequestTimeline({ ...TIMELINE_SOURCE, commits: [{ ...TIMELINE_SOURCE.commits[0]!, additions: 12, deletions: 4 }], }); expect(events.find((event) => event.kind === "commit")).toMatchObject({ additions: 12, deletions: 4, }); }); it("drops a body that is nothing but a bot's HTML comment, and keeps one that says more", () => { const events = buildPullRequestTimeline({ ...TIMELINE_SOURCE, comments: [ { ...TIMELINE_SOURCE.comments[0]!, body: "<!-- MURMUR_IGNORE -->" }, { ...TIMELINE_SOURCE.comments[0]!, id: "c2", body: "<!-- summarize by coderabbit.ai -->\nNeeds a test.", createdAt: "2026-07-04T00:00:00Z", }, ], }); expect(events.find((event) => event.id === "c1")?.body).toBeNull(); // Kept whole: the renderer drops the marker itself, and stripping it here would also // strip an HTML comment a reviewer quoted inside a code fence. expect(events.find((event) => event.id === "c2")?.body).toBe( "<!-- summarize by coderabbit.ai -->\nNeeds a test.", ); }); it("calls a comment markdown and a commit headline plain text", () => { const events = buildPullRequestTimeline(TIMELINE_SOURCE); // A headline reading `fix: drop *legacy* path` is not asking for emphasis. expect(events.map((event) => [event.title.startsWith("Commit"), event.markdown])).toEqual( expect.arrayContaining([[true, false]]), ); expect(events.find((event) => event.id === "c1")?.markdown).toBe(true); }); it("reports a merge rather than the close GitHub records alongside it", () => { const events = buildPullRequestTimeline({ ...TIMELINE_SOURCE, mergedAt: "2026-07-04T00:00:00Z", closedAt: "2026-07-04T00:00:00Z", }); // Newest first, so the terminal event opens the list rather than ending it. expect(events[0]?.id).toBe("merged"); expect(events.some((event) => event.id === "closed")).toBe(false); }); it("groups conversation sections without crossing commits or PR updates", () => { const events = buildPullRequestTimeline({ ...TIMELINE_SOURCE, comments: [ { ...TIMELINE_SOURCE.comments[0]!, id: "new-comment-1", createdAt: "2026-07-04T00:00:00Z", }, { ...TIMELINE_SOURCE.comments[0]!, id: "new-comment-2", createdAt: "2026-07-03T00:00:00Z",Lines 1–500 of 1249. Load more
import { PullRequestAction, type PullRequestCheck, type PullRequestComment, type PullRequestDetailView, type PullRequestReviewThread, } from "@t3tools/contracts"; import { describe, expect, it } from "vite-plus/test"; import { buildAddSelectionToAgentHandoff, buildAskAboutPullRequestHandoff, buildExplainPullRequestHandoff, buildFixFindingHandoff, buildFixFindingsHandoff, groupPullRequestTimelineConversations, handoffPrompt, handoffReviewComments, isPullRequestVerdictStale, isStackedPullRequestBase, isThreadOwnPullRequest, latestPullRequestReviewOutcomes, newestPullRequestCommitAt, mergePullRequestThreadComments, orderPullRequestComments, pullRequestActionMenuHasGroup, pullRequestActionNeedsHostRefresh, pullRequestComposerTarget, pullRequestFindingKey, pullRequestHandoffLabels, pullRequestReviewOutcome, readableFailure, shouldRefreshPullRequestActivity, resolveBaseFreshness, buildPullRequestTimeline, describePullRequestState, editPullRequestThreadComment, } from "./pullRequestDetail.logic"; import type { ReviewCommentContext } from "~/reviewCommentContext"; const TIMELINE_SOURCE: Pick< PullRequestDetailView, "createdAt" | "author" | "commits" | "comments" | "mergedAt" | "closedAt" > = { createdAt: "2026-07-01T00:00:00Z", author: { login: "octocat", name: null, avatarUrl: null }, commits: [ { oid: "1baf7bdcafe", messageHeadline: "add the page", committedDate: "2026-07-02T00:00:00Z" }, ], comments: [ { id: "c1", kind: "issue-comment", author: { login: "bilal", name: null, avatarUrl: null }, body: "looks good", createdAt: "2026-07-03T00:00:00Z", url: null, path: null, reviewState: null, }, ], mergedAt: null, closedAt: null, }; describe("pull request activity refresh", () => { const first = { key: "project:acme/web#7", updatedAt: "2026-08-13T13:00:00Z", }; it("refreshes activity only after the same pull request changes", () => { expect( shouldRefreshPullRequestActivity(first, { ...first, updatedAt: "2026-08-13T13:01:00Z", }), ).toBe(true); }); it("does not duplicate the first activity read or carry a revision across pull requests", () => { expect(shouldRefreshPullRequestActivity(null, first)).toBe(false); expect(shouldRefreshPullRequestActivity(first, first)).toBe(false); expect( shouldRefreshPullRequestActivity(first, { key: "project:acme/web#8", updatedAt: "2026-08-13T13:01:00Z", }), ).toBe(false); }); }); describe("review thread comment pages", () => { it("appends new comments once and keeps refreshed base comments", () => { expect( mergePullRequestThreadComments( [ { id: "c1", body: "refreshed" }, { id: "c2", body: "already in base" }, ], [ { id: "c2", body: "stale page copy" }, { id: "c3", body: "next page" }, ], ), ).toEqual([ { id: "c1", body: "refreshed" }, { id: "c2", body: "already in base" }, { id: "c3", body: "next page" }, ]); }); it("keeps a loaded comment after its body is edited", () => { const loaded = [ { id: "c2", body: "old body" }, { id: "c3", body: "another loaded comment" }, ]; expect(editPullRequestThreadComment(loaded, "c2", "saved body")).toEqual([ { id: "c2", body: "saved body" }, { id: "c3", body: "another loaded comment" }, ]); }); }); describe("pull request action menu", () => { it("keeps the group divider when auto-merge is the only action", () => { expect(pullRequestActionMenuHasGroup(false, true, false)).toBe(true); }); }); describe("pull request state description", () => { it("keeps draft and conflicts orthogonal to the terminal states", () => { expect(describePullRequestState("open", true)).toBe("Draft"); expect(describePullRequestState("open", false)).toBe("Ready for review"); expect(describePullRequestState("merged", true)).toBe("Merged"); expect(describePullRequestState("closed", false)).toBe("Closed"); }); }); describe("pull request handoff labels", () => { it("names the open thread when actions write to its composer", () => { expect(pullRequestHandoffLabels(true)).toEqual({ fixFinding: "Fix in this thread", fixCheck: "Fix in this thread", fixFindings: "Fix findings in this thread", }); }); it("keeps the standalone pull request page labels", () => { expect(pullRequestHandoffLabels(false)).toEqual({ fixFinding: "Fix in a thread", fixCheck: "Fix", fixFindings: "Fix findings in a thread", }); }); }); describe("pull request composer target", () => { it("rejects a page composer so agent comments cannot open another thread", () => { const target = { environmentId: "env-1", threadId: "thread-1" }; expect(pullRequestComposerTarget("page", target)).toBeNull(); expect(pullRequestComposerTarget("thread", target)).toBe(target); }); }); describe("stacked pull request classification", () => { it("requires a known default branch", () => { expect(isStackedPullRequestBase("main", [{ name: "main", isDefault: false }])).toBe(false); }); it("recognizes local and remote forms of the default branch", () => { expect( isStackedPullRequestBase("main", [{ name: "main", isDefault: true, isRemote: false }]), ).toBe(false); expect( isStackedPullRequestBase("main", [ { name: "origin/main", isDefault: true, isRemote: true, remoteName: "origin" }, ]), ).toBe(false); }); it("classifies a non-default base as stacked once the default is known", () => { expect( isStackedPullRequestBase("feature-base", [ { name: "origin/main", isDefault: true, isRemote: true, remoteName: "origin" }, ]), ).toBe(true); }); it("does not mistake a nested branch suffix for the default branch", () => { expect( isStackedPullRequestBase("main", [ { name: "origin/feature/main", isDefault: true, isRemote: true, remoteName: "origin", }, ]), ).toBe(true); expect( isStackedPullRequestBase("1.0", [{ name: "release/1.0", isDefault: true, isRemote: false }]), ).toBe(true); }); }); describe("ordering comments", () => { it("reverses the chronological list for newest first, and leaves oldest first alone", () => { const comments = [{ createdAt: "a" }, { createdAt: "b" }, { createdAt: "c" }]; expect(orderPullRequestComments(comments, "newest")).toEqual([ { createdAt: "c" }, { createdAt: "b" }, { createdAt: "a" }, ]); expect(orderPullRequestComments(comments, "oldest")).toEqual(comments); // The source array is chronological input, not a mutation target. expect(comments).toEqual([{ createdAt: "a" }, { createdAt: "b" }, { createdAt: "c" }]); }); }); describe("review verdicts", () => { it("reads the same three verdicts however a host spells them", () => { expect(pullRequestReviewOutcome("APPROVED")).toBe("approved"); expect(pullRequestReviewOutcome("approved")).toBe("approved"); expect(pullRequestReviewOutcome("CHANGES_REQUESTED")).toBe("changes-requested"); expect(pullRequestReviewOutcome("changes_requested")).toBe("changes-requested"); expect(pullRequestReviewOutcome("DISMISSED")).toBe("dismissed"); }); it("is not a verdict where the review only carried remarks", () => { expect(pullRequestReviewOutcome("COMMENTED")).toBeNull(); expect(pullRequestReviewOutcome("PENDING")).toBeNull(); expect(pullRequestReviewOutcome(null)).toBeNull(); }); it("keeps each reviewer's last word, whatever order the host returned them in", () => { const review = ( id: string, login: string, reviewState: string, createdAt: string, ): PullRequestComment => ({ id, kind: "review", author: { login, name: null, avatarUrl: null }, body: "", createdAt, url: null, path: null, reviewState, }); expect( latestPullRequestReviewOutcomes([ review("r3", "bilal", "APPROVED", "2026-07-03T00:00:00Z"), review("r1", "bilal", "CHANGES_REQUESTED", "2026-07-01T00:00:00Z"), review("r2", "octocat", "CHANGES_REQUESTED", "2026-07-02T00:00:00Z"), // Not a verdict, so it neither adds a reviewer nor overwrites one. review("r4", "octocat", "COMMENTED", "2026-07-04T00:00:00Z"), ]).map((entry) => [entry.actor?.login, entry.outcome]), ).toEqual([ ["bilal", "approved"], ["octocat", "changes-requested"], ]); }); it("keeps two deleted accounts apart rather than counting them as one reviewer", () => { expect( latestPullRequestReviewOutcomes([ { ...TIMELINE_SOURCE.comments[0]!, id: "r1", kind: "review", author: null, reviewState: "APPROVED", createdAt: "2026-07-01T00:00:00Z", }, { ...TIMELINE_SOURCE.comments[0]!, id: "r2", kind: "review", author: null, reviewState: "APPROVED", createdAt: "2026-07-02T00:00:00Z", }, ]), ).toHaveLength(2); }); it("gives every entry a key that separates the reviewers it kept apart", () => { const entries = latestPullRequestReviewOutcomes([ { ...TIMELINE_SOURCE.comments[0]!, id: "r1", kind: "review", author: null, reviewState: "APPROVED", createdAt: "2026-07-01T00:00:00Z", }, { ...TIMELINE_SOURCE.comments[0]!, id: "r2", kind: "review", author: null, reviewState: "APPROVED", createdAt: "2026-07-01T00:00:00Z", }, ]); // Same author (none) and the same instant, so only the review's own id tells them apart. expect(new Set(entries.map((entry) => entry.key)).size).toBe(2); }); it("calls a verdict stale once commits land after it, and current before that", () => { const commits = [ { oid: "c0ffee", messageHeadline: "later work", committedDate: "2026-07-05T00:00:00Z" }, ]; const review = (createdAt: string): PullRequestComment => ({ ...TIMELINE_SOURCE.comments[0]!, kind: "review", reviewState: "APPROVED", createdAt, }); expect( latestPullRequestReviewOutcomes([review("2026-07-01T00:00:00Z")], commits)[0]?.stale, ).toBe(true); expect( latestPullRequestReviewOutcomes([review("2026-07-06T00:00:00Z")], commits)[0]?.stale, ).toBe(false); // Nothing to be overtaken by, so nothing is stale. expect(latestPullRequestReviewOutcomes([review("2026-07-01T00:00:00Z")], [])[0]?.stale).toBe( false, ); }); it("measures staleness against the newest commit, not the last one listed", () => { expect( newestPullRequestCommitAt([ { oid: "a", messageHeadline: "", committedDate: "2026-07-09T00:00:00Z" }, { oid: "b", messageHeadline: "", committedDate: "2026-07-02T00:00:00Z" }, ]), ).toBe("2026-07-09T00:00:00Z"); expect(newestPullRequestCommitAt([])).toBeNull(); }); it("orders instants rather than their text, so a UTC offset cannot invert them", () => { // 01:00+02:00 is 23:00 the previous day, so as text it sorts after the Z stamp and in time // it falls well before it. expect( newestPullRequestCommitAt([ { oid: "a", messageHeadline: "", committedDate: "2026-07-05T00:30:00Z" }, { oid: "b", messageHeadline: "", committedDate: "2026-07-05T01:00:00+02:00" }, ]), ).toBe("2026-07-05T00:30:00Z"); expect(isPullRequestVerdictStale("2026-07-05T00:30:00Z", "2026-07-05T01:00:00+02:00")).toBe( false, ); // A timestamp nothing can parse is not a position, so it settles nothing either way. expect(isPullRequestVerdictStale("2026-07-01T00:00:00Z", "not a date")).toBe(false); expect( newestPullRequestCommitAt([{ oid: "a", messageHeadline: "", committedDate: "not a date" }]), ).toBeNull(); }); it("shows nothing for a reviewer whose verdict was dismissed", () => { expect( latestPullRequestReviewOutcomes([ { ...TIMELINE_SOURCE.comments[0]!, kind: "review", reviewState: "APPROVED", createdAt: "2026-07-01T00:00:00Z", }, { ...TIMELINE_SOURCE.comments[0]!, id: "c2", kind: "review", reviewState: "DISMISSED", createdAt: "2026-07-02T00:00:00Z", }, ]), ).toEqual([]); }); }); describe("pull request timeline", () => { it("orders creation, commits and comments newest first", () => { // What happened last is what the reader opening the tab is asking about. expect(buildPullRequestTimeline(TIMELINE_SOURCE).map((event) => event.id)).toEqual([ "c1", "1baf7bdcafe", "created", ]); }); it("carries the comment url, and leaves the events the host cannot address without one", () => { const events = buildPullRequestTimeline({ ...TIMELINE_SOURCE, comments: [{ ...TIMELINE_SOURCE.comments[0]!, url: "https://example.test/pull/1#c1" }], }); expect(events.map((event) => event.url)).toEqual([ "https://example.test/pull/1#c1", null, null, ]); }); it("carries actors and review context into the presentation model", () => { const commitAuthors = [ { login: "octocat", name: null, avatarUrl: "https://example.test/octocat.png" }, { login: "pair", name: "Pair Author", avatarUrl: null }, ]; const events = buildPullRequestTimeline({ ...TIMELINE_SOURCE, commits: [{ ...TIMELINE_SOURCE.commits[0]!, authors: commitAuthors }], comments: [ { ...TIMELINE_SOURCE.comments[0]!, kind: "review", path: "src/app.ts", reviewState: "APPROVED", }, ], }); expect(events.find((event) => event.kind === "commit")?.commitAuthors).toEqual(commitAuthors); expect(events.find((event) => event.kind === "review")).toMatchObject({ actor: TIMELINE_SOURCE.comments[0]?.author, path: "src/app.ts", reviewState: "APPROVED", }); }); it("carries each commit's line counts into its timeline event", () => { const events = buildPullRequestTimeline({ ...TIMELINE_SOURCE, commits: [{ ...TIMELINE_SOURCE.commits[0]!, additions: 12, deletions: 4 }], }); expect(events.find((event) => event.kind === "commit")).toMatchObject({ additions: 12, deletions: 4, }); }); it("drops a body that is nothing but a bot's HTML comment, and keeps one that says more", () => { const events = buildPullRequestTimeline({ ...TIMELINE_SOURCE, comments: [ { ...TIMELINE_SOURCE.comments[0]!, body: "<!-- MURMUR_IGNORE -->" }, { ...TIMELINE_SOURCE.comments[0]!, id: "c2", body: "<!-- summarize by coderabbit.ai -->\nNeeds a test.", createdAt: "2026-07-04T00:00:00Z", }, ], }); expect(events.find((event) => event.id === "c1")?.body).toBeNull(); // Kept whole: the renderer drops the marker itself, and stripping it here would also // strip an HTML comment a reviewer quoted inside a code fence. expect(events.find((event) => event.id === "c2")?.body).toBe( "<!-- summarize by coderabbit.ai -->\nNeeds a test.", ); }); it("calls a comment markdown and a commit headline plain text", () => { const events = buildPullRequestTimeline(TIMELINE_SOURCE); // A headline reading `fix: drop *legacy* path` is not asking for emphasis. expect(events.map((event) => [event.title.startsWith("Commit"), event.markdown])).toEqual( expect.arrayContaining([[true, false]]), ); expect(events.find((event) => event.id === "c1")?.markdown).toBe(true); }); it("reports a merge rather than the close GitHub records alongside it", () => { const events = buildPullRequestTimeline({ ...TIMELINE_SOURCE, mergedAt: "2026-07-04T00:00:00Z", closedAt: "2026-07-04T00:00:00Z", }); // Newest first, so the terminal event opens the list rather than ending it. expect(events[0]?.id).toBe("merged"); expect(events.some((event) => event.id === "closed")).toBe(false); }); it("groups conversation sections without crossing commits or PR updates", () => { const events = buildPullRequestTimeline({ ...TIMELINE_SOURCE, comments: [ { ...TIMELINE_SOURCE.comments[0]!, id: "new-comment-1", createdAt: "2026-07-04T00:00:00Z", }, { ...TIMELINE_SOURCE.comments[0]!, id: "new-comment-2", createdAt: "2026-07-03T00:00:00Z",