1import { describe, expect, it } from "vite-plus/test";
2
3import type { ThreadPullRequestLink } from "@t3tools/contracts";
4
5import {
6 composerPullRequestsFromLinks,
7 filterComposerPullRequestMatches,
8} from "./composerPullRequestMatches.ts";
9
10const entry = (number: number, updatedAt: string) => ({
11 number,
12 projectId: "p1",
13 repository: "owner/repo",
14 updatedAt,
15});
16
17describe("filterComposerPullRequestMatches", () => {
18 it("ranks the exact number above newer substring matches", () => {
19 const result = filterComposerPullRequestMatches({
20 entries: [entry(1234, "2026-01-03"), entry(123, "2026-01-01"), entry(1230, "2026-01-02")],
21 projectId: "p1",
22 repository: "owner/repo",
23 query: "123",
24 limit: 10,
25 });
26 expect(result.map((match) => match.number)).toEqual([123, 1234, 1230]);
27 });
28
29 it("leaves the caller's array untouched", () => {
30 // The sort runs on a copy; mutating the input would reorder whatever the caller holds.
31 const entries = [entry(2, "2026-01-01"), entry(1, "2026-01-02")];
32 const snapshot = entries.map((match) => match.number);
33 filterComposerPullRequestMatches({
34 entries,
35 projectId: "p1",
36 repository: "owner/repo",
37 query: "",
38 limit: 10,
39 });
40 expect(entries.map((match) => match.number)).toEqual(snapshot);
41 });
42
43 it("de-duplicates and honours the limit", () => {
44 const result = filterComposerPullRequestMatches({
45 entries: [entry(7, "2026-01-02"), entry(7, "2026-01-01"), entry(8, "2026-01-03")],
46 projectId: "p1",
47 repository: "owner/repo",
48 query: "",
49 limit: 1,
50 });
51 expect(result).toHaveLength(1);
52 });
53});
54
55describe("linked pull requests", () => {
56 const link = (
57 repository: string,
58 number: number,
59 overrides: Partial<ThreadPullRequestLink> = {},
60 ): ThreadPullRequestLink => ({
61 host: "github.com",
62 repository,
63 number,
64 url: `https://github.com/${repository}/pull/${number}`,
65 source: "agent",
66 linkedAt: "2026-01-01T00:00:00.000Z",
67 snapshot: {
68 state: "open",
69 title: `${repository} ${number}`,
70 headBranch: "feature",
71 baseBranch: "main",
72 isDraft: false,
73 updatedAt: "2026-01-02T00:00:00.000Z",
74 syncedAt: "2026-01-02T00:00:00.000Z",
75 },
76 stack: null,
77 ...overrides,
78 });
79
80 it("offers a linked pull request from another repository above the project's own", () => {
81 const result = filterComposerPullRequestMatches({
82 entries: [entry(4, "2026-01-03"), { ...entry(4, "2026-01-04"), repository: "owner/other" }],
83 linked: composerPullRequestsFromLinks([link("owner/repo-b", 4)], "p1"),
84 projectId: "p1",
85 repository: "owner/repo",
86 query: "4",
87 limit: 10,
88 });
89 expect(result.map((match) => `${match.repository}#${match.number}`)).toEqual([
90 "owner/repo-b#4",
91 "owner/repo#4",
92 ]);
93 });
94
95 it("keeps one row for a linked pull request the project listing already has", () => {
96 const result = filterComposerPullRequestMatches({
97 entries: [entry(4, "2026-01-03")],
98 linked: composerPullRequestsFromLinks([link("Owner/Repo", 4)], "p1"),
99 projectId: "p1",
100 repository: "owner/repo",
101 query: "",
102 limit: 10,
103 });
104 expect(result).toEqual([entry(4, "2026-01-03")]);
105 });
106
107 it("skips links that are not synced yet or were dismissed", () => {
108 expect(
109 composerPullRequestsFromLinks(
110 [
111 link("owner/repo-b", 4, { snapshot: null }),
112 link("owner/repo-b", 5, { source: "stack-dismissed" }),
113 ],
114 "p1",
115 ),
116 ).toEqual([]);
117 });
118});