| 1 | import type { JjChange } from "@t3tools/contracts"; |
| 2 | import { describe, expect, it } from "vite-plus/test"; |
| 3 | |
| 4 | import { |
| 5 | clampSelection, |
| 6 | newestPullRequestIndex, |
| 7 | pullRequestFor, |
| 8 | pullRequestText, |
| 9 | selectChange, |
| 10 | selectionRange, |
| 11 | visibleChanges, |
| 12 | } from "./jjReview.logic"; |
| 13 | |
| 14 | const change = (id: string, description: string, overrides: Partial<JjChange> = {}): JjChange => ({ |
| 15 | changeId: id, |
| 16 | shortId: id.slice(0, 4), |
| 17 | commitId: `${id}-commit`, |
| 18 | parentCommitIds: [`${id}-parent`], |
| 19 | description, |
| 20 | trailers: [...description.matchAll(/^Assisted-by: (.+)$/gm)].map((m) => ({ |
| 21 | key: "Assisted-by", |
| 22 | value: m[1]!, |
| 23 | })), |
| 24 | bookmarks: [], |
| 25 | immutable: false, |
| 26 | conflict: false, |
| 27 | empty: false, |
| 28 | workingCopy: false, |
| 29 | additions: 1, |
| 30 | deletions: 0, |
| 31 | authoredAt: "2026-10-15T12:00:00-07:00", |
| 32 | ...overrides, |
| 33 | }); |
| 34 | |
| 35 | // Newest first, like the stack. |
| 36 | const followUp = change("follow", "chore: follow-up changes\n\nAssisted-by: gpt-6.1-sol"); |
| 37 | const feature = change( |
| 38 | "feature", |
| 39 | "feat(reader): keep scroll\n\nRestore offsets per tab.\n\nAssisted-by: claude-opus-5.5", |
| 40 | ); |
| 41 | const fix = change("fixit", "fix(api): retry uploads"); |
| 42 | |
| 43 | describe("selection", () => { |
| 44 | it("selects one change, or extends a contiguous range from the anchor", () => { |
| 45 | expect(selectChange(1, 2, false)).toEqual([2, 2]); |
| 46 | expect(selectChange(2, 0, true)).toEqual([0, 2]); |
| 47 | }); |
| 48 | |
| 49 | it("clamps a selection when the stack shrinks", () => { |
| 50 | expect(clampSelection([2, 3], 2)).toEqual([1, 1]); |
| 51 | expect(clampSelection([0, 0], 0)).toEqual([0, 0]); |
| 52 | }); |
| 53 | |
| 54 | it("diffs a range from the oldest change's parent to the newest change", () => { |
| 55 | expect(selectionRange([followUp, feature], [0, 1])).toEqual({ |
| 56 | fromCommitId: "feature-parent", |
| 57 | toCommitId: "follow-commit", |
| 58 | }); |
| 59 | expect( |
| 60 | selectionRange([change("root", "x", { parentCommitIds: [] })], [0, 0])?.fromCommitId, |
| 61 | ).toBe("0".repeat(40)); |
| 62 | }); |
| 63 | |
| 64 | it("hides an empty working copy but keeps one with changes", () => { |
| 65 | const stack = { workspaceRoot: "/w", repository: null, trunk: null }; |
| 66 | expect( |
| 67 | visibleChanges({ |
| 68 | ...stack, |
| 69 | changes: [change("wc", "", { workingCopy: true, empty: true }), feature], |
| 70 | }), |
| 71 | ).toEqual([feature]); |
| 72 | expect( |
| 73 | visibleChanges({ ...stack, changes: [change("wc", "", { workingCopy: true }), feature] }), |
| 74 | ).toHaveLength(2); |
| 75 | }); |
| 76 | }); |
| 77 | |
| 78 | describe("pull requests", () => { |
| 79 | const prs = new Map([["feature-commit", 80]]); |
| 80 | |
| 81 | it("puts a change in the PR of the nearest head at or above it", () => { |
| 82 | const changes = [followUp, feature, fix]; |
| 83 | expect(pullRequestFor(changes, prs, 2)).toBe(80); |
| 84 | expect(pullRequestFor(changes, prs, 1)).toBe(80); |
| 85 | expect(pullRequestFor(changes, prs, 0)).toBeNull(); |
| 86 | expect(newestPullRequestIndex(changes, prs)).toBe(1); |
| 87 | }); |
| 88 | |
| 89 | it("titles a PR after its oldest non-follow-up change and credits every model", () => { |
| 90 | expect(pullRequestText([followUp, feature])).toEqual({ |
| 91 | title: "feat(reader): keep scroll", |
| 92 | body: "Restore offsets per tab.\n\nAssisted-by: gpt-6.1-sol\nAssisted-by: claude-opus-5.5", |
| 93 | baseChangeId: "feature", |
| 94 | }); |
| 95 | }); |
| 96 | }); |