|
1 | 1 | import { describe, expect, it } from "vitest"; |
2 | 2 | import { |
| 3 | + githubPrMatchesCurrentBranch, |
3 | 4 | lanePrMatchesCurrentBranch, |
4 | 5 | resolveLaneDeleteStartSelection, |
5 | 6 | resolveCreateLaneRequest, |
6 | 7 | resolveLaneIdsDeepLinkSelection, |
| 8 | + selectGithubLanePrTag, |
| 9 | + selectLaneTabPrTag, |
7 | 10 | selectLanePrTag, |
8 | | -} from "./LanesPage"; |
9 | | -import type { LaneSummary, PrSummary } from "../../../shared/types"; |
| 11 | + sortLaneListRows, |
| 12 | +} from "./lanePageModel"; |
| 13 | +import type { GitHubPrListItem, LaneSummary, PrSummary } from "../../../shared/types"; |
10 | 14 |
|
11 | 15 | type LanePrTarget = Pick<LaneSummary, "id" | "laneType" | "branchRef" | "baseRef">; |
12 | 16 |
|
@@ -45,6 +49,36 @@ function makePr(overrides: Partial<PrSummary> = {}): PrSummary { |
45 | 49 | }; |
46 | 50 | } |
47 | 51 |
|
| 52 | +function makeGitHubPr(overrides: Partial<GitHubPrListItem> = {}): GitHubPrListItem { |
| 53 | + return { |
| 54 | + id: "repo-pr-224", |
| 55 | + scope: "repo", |
| 56 | + repoOwner: "arul28", |
| 57 | + repoName: "ADE", |
| 58 | + githubPrNumber: 224, |
| 59 | + githubUrl: "https://github.com/arul28/ADE/pull/224", |
| 60 | + title: "Show merged PR state", |
| 61 | + state: "open", |
| 62 | + isDraft: false, |
| 63 | + baseBranch: "main", |
| 64 | + headBranch: "origin/ade/pr-state", |
| 65 | + author: "arul", |
| 66 | + createdAt: "2026-05-01T00:00:00.000Z", |
| 67 | + updatedAt: "2026-05-01T00:00:00.000Z", |
| 68 | + linkedPrId: null, |
| 69 | + linkedGroupId: null, |
| 70 | + linkedLaneId: null, |
| 71 | + linkedLaneName: null, |
| 72 | + adeKind: null, |
| 73 | + workflowDisplayState: null, |
| 74 | + cleanupState: null, |
| 75 | + labels: [], |
| 76 | + isBot: false, |
| 77 | + commentCount: 0, |
| 78 | + ...overrides, |
| 79 | + }; |
| 80 | +} |
| 81 | + |
48 | 82 | describe("resolveCreateLaneRequest", () => { |
49 | 83 | it("creates an independent lane from the selected primary branch", () => { |
50 | 84 | expect( |
@@ -235,3 +269,119 @@ describe("selectLanePrTag", () => { |
235 | 269 | ).toBe(false); |
236 | 270 | }); |
237 | 271 | }); |
| 272 | + |
| 273 | +describe("selectLaneTabPrTag", () => { |
| 274 | + it("falls back to repo GitHub PRs by lane branch when no ADE PR row is mapped", () => { |
| 275 | + const githubPr = makeGitHubPr({ |
| 276 | + state: "merged", |
| 277 | + linkedPrId: null, |
| 278 | + headBranch: "ade/pr-state", |
| 279 | + }); |
| 280 | + |
| 281 | + expect(githubPrMatchesCurrentBranch(makeLane(), githubPr)).toBe(true); |
| 282 | + expect(selectGithubLanePrTag(makeLane(), [githubPr])).toBe(githubPr); |
| 283 | + expect(selectLaneTabPrTag(makeLane(), [], [githubPr])).toMatchObject({ |
| 284 | + source: "github", |
| 285 | + linkedPrId: null, |
| 286 | + githubPrNumber: 224, |
| 287 | + state: "merged", |
| 288 | + }); |
| 289 | + }); |
| 290 | + |
| 291 | + it("prefers an ADE-mapped PR over an unlinked GitHub branch match", () => { |
| 292 | + const mappedPr = makePr({ id: "mapped-pr", state: "closed" }); |
| 293 | + const githubPr = makeGitHubPr({ id: "github-pr", state: "open" }); |
| 294 | + |
| 295 | + expect(selectLaneTabPrTag(makeLane(), [mappedPr], [githubPr])).toMatchObject({ |
| 296 | + source: "ade", |
| 297 | + id: "mapped-pr", |
| 298 | + linkedPrId: "mapped-pr", |
| 299 | + state: "closed", |
| 300 | + }); |
| 301 | + }); |
| 302 | + |
| 303 | + it("labels GitHub-only draft PRs as draft lane tags", () => { |
| 304 | + expect(selectLaneTabPrTag(makeLane(), [], [ |
| 305 | + makeGitHubPr({ |
| 306 | + state: "open", |
| 307 | + isDraft: true, |
| 308 | + }), |
| 309 | + ])).toMatchObject({ |
| 310 | + source: "github", |
| 311 | + state: "draft", |
| 312 | + }); |
| 313 | + }); |
| 314 | + |
| 315 | + it("does not route a branch-matched GitHub PR through a stale linked lane", () => { |
| 316 | + const githubPr = makeGitHubPr({ |
| 317 | + linkedPrId: "other-pr", |
| 318 | + linkedLaneId: "other-lane", |
| 319 | + }); |
| 320 | + |
| 321 | + expect(selectLaneTabPrTag(makeLane(), [], [githubPr])).toMatchObject({ |
| 322 | + source: "github", |
| 323 | + linkedPrId: null, |
| 324 | + githubUrl: "https://github.com/arul28/ADE/pull/224", |
| 325 | + }); |
| 326 | + }); |
| 327 | + |
| 328 | + it("does not match repo GitHub PRs for primary while primary is on its base branch", () => { |
| 329 | + expect( |
| 330 | + githubPrMatchesCurrentBranch( |
| 331 | + makeLane({ |
| 332 | + laneType: "primary", |
| 333 | + branchRef: "main", |
| 334 | + baseRef: "main", |
| 335 | + }), |
| 336 | + makeGitHubPr({ headBranch: "main" }), |
| 337 | + ), |
| 338 | + ).toBe(false); |
| 339 | + }); |
| 340 | +}); |
| 341 | + |
| 342 | +describe("sortLaneListRows", () => { |
| 343 | + it("keeps primary first and promotes pinned lanes before runtime buckets", () => { |
| 344 | + const lanes = [ |
| 345 | + { id: "primary", laneType: "primary" }, |
| 346 | + { id: "running", laneType: "worktree" }, |
| 347 | + { id: "pinned-ended", laneType: "worktree" }, |
| 348 | + { id: "idle", laneType: "worktree" }, |
| 349 | + ] as const; |
| 350 | + |
| 351 | + const result = sortLaneListRows({ |
| 352 | + lanes: [...lanes], |
| 353 | + laneRuntimeById: new Map([ |
| 354 | + ["running", { bucket: "running" }], |
| 355 | + ["pinned-ended", { bucket: "ended" }], |
| 356 | + ["idle", { bucket: "none" }], |
| 357 | + ]), |
| 358 | + laneStatusFilter: "all", |
| 359 | + laneOrderById: new Map(lanes.map((lane, index) => [lane.id, index])), |
| 360 | + pinnedLaneIds: new Set(["pinned-ended"]), |
| 361 | + }); |
| 362 | + |
| 363 | + expect(result.map((lane) => lane.id)).toEqual(["primary", "pinned-ended", "running", "idle"]); |
| 364 | + }); |
| 365 | + |
| 366 | + it("keeps pinned ordering after applying a status filter", () => { |
| 367 | + const lanes = [ |
| 368 | + { id: "running-a", laneType: "worktree" }, |
| 369 | + { id: "running-pinned", laneType: "worktree" }, |
| 370 | + { id: "ended", laneType: "worktree" }, |
| 371 | + ] as const; |
| 372 | + |
| 373 | + const result = sortLaneListRows({ |
| 374 | + lanes: [...lanes], |
| 375 | + laneRuntimeById: new Map([ |
| 376 | + ["running-a", { bucket: "running" }], |
| 377 | + ["running-pinned", { bucket: "running" }], |
| 378 | + ["ended", { bucket: "ended" }], |
| 379 | + ]), |
| 380 | + laneStatusFilter: "running", |
| 381 | + laneOrderById: new Map(lanes.map((lane, index) => [lane.id, index])), |
| 382 | + pinnedLaneIds: new Set(["running-pinned"]), |
| 383 | + }); |
| 384 | + |
| 385 | + expect(result.map((lane) => lane.id)).toEqual(["running-pinned", "running-a"]); |
| 386 | + }); |
| 387 | +}); |
0 commit comments