1249 lines · 41.2 KB · TypeScript
RawDownload
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.