1import { describe, expect, it } from "vite-plus/test";
2
3import type { ThreadPullRequestKey, ThreadPullRequestLink } from "@t3tools/contracts";
4
5import {
6 composerPullRequestsFromLinks,
7 filterComposerPullRequestMatches,
8 withLinkedComposerPullRequestsFirst,
9} from "./composerPullRequestMatches.ts";
10
11const keyOf = (entry: ThreadPullRequestKey) =>
12 `${entry.host.trim().toLowerCase()}/${entry.repository.trim().toLowerCase()}#${entry.number}`;
13
14const entry = (number: number, updatedAt: string) => ({
15 number,
16 host: "github.com",
17 projectId: "p1",
18 repository: "owner/repo",
19 updatedAt,
20});
21
22describe("filterComposerPullRequestMatches", () => {
23 it("ranks the exact number above newer substring matches", () => {
24 const result = filterComposerPullRequestMatches({
25 keyOf,
26 entries: [entry(1234, "2026-01-03"), entry(123, "2026-01-01"), entry(1230, "2026-01-02")],
27 projectId: "p1",
28 repository: "owner/repo",
29 query: "123",
30 limit: 10,
31 });
32 expect(result.map((match) => match.number)).toEqual([123, 1234, 1230]);
33 });
34
35 it("orders by update instant when timestamps carry different UTC offsets", () => {
36 const result = filterComposerPullRequestMatches({
37 keyOf,
38 entries: [entry(401, "2026-10-25T02:50:00+02:00"), entry(499, "2026-10-25T02:10:00+01:00")],
39 projectId: "p1",
40 repository: "owner/repo",
41 query: "4",
42 limit: 1,
43 });
44 expect(result.map((match) => match.number)).toEqual([499]);
45 });
46});
47
48describe("linked pull requests", () => {
49 const link = (
50 repository: string,
51 number: number,
52 overrides: Partial<ThreadPullRequestLink> = {},
53 ): ThreadPullRequestLink => ({
54 host: "github.com",
55 repository,
56 number,
57 url: `https://github.com/${repository}/pull/${number}`,
58 source: "agent",
59 linkedAt: "2026-01-01T00:00:00.000Z",
60 snapshot: {
61 state: "open",
62 title: `${repository} ${number}`,
63 headBranch: "feature",
64 baseBranch: "main",
65 isDraft: false,
66 updatedAt: "2026-01-02T00:00:00.000Z",
67 syncedAt: "2026-01-02T00:00:00.000Z",
68 },
69 stack: null,
70 ...overrides,
71 });
72
73 it("offers a linked pull request from another repository above the project's own", () => {
74 const result = filterComposerPullRequestMatches({
75 keyOf,
76 entries: [entry(4, "2026-01-03"), { ...entry(4, "2026-01-04"), repository: "owner/other" }],
77 linked: composerPullRequestsFromLinks([link("owner/repo-b", 4)], "p1"),
78 projectId: "p1",
79 repository: "owner/repo",
80 query: "4",
81 limit: 10,
82 });
83 expect(result.map((match) => `${match.repository}#${match.number}`)).toEqual([
84 "owner/repo-b#4",
85 "owner/repo#4",
86 ]);
87 });
88
89 it("keeps linked reviews with the same repository and number on different hosts", () => {
90 const github = link("wireshark/wireshark", 1);
91 const gitlab = link("wireshark/wireshark", 1, {
92 host: "gitlab.com",
93 url: "https://gitlab.com/wireshark/wireshark/-/merge_requests/1",
94 });
95 const result = filterComposerPullRequestMatches({
96 keyOf,
97 entries: [],
98 linked: composerPullRequestsFromLinks([github, gitlab], "p1"),
99 projectId: "p1",
100 repository: "wireshark/wireshark",
101 query: "1",
102 limit: 10,
103 });
104 expect(result.map((match) => match.url)).toEqual([github.url, gitlab.url]);
105 });
106
107 it("keeps one row for a linked pull request the project listing already has", () => {
108 const result = filterComposerPullRequestMatches({
109 keyOf,
110 entries: [entry(4, "2026-01-03")],
111 linked: composerPullRequestsFromLinks([link("Owner/Repo", 4)], "p1"),
112 projectId: "p1",
113 repository: "owner/repo",
114 query: "",
115 limit: 10,
116 });
117 expect(result).toEqual([entry(4, "2026-01-03")]);
118 });
119
120 it("puts linked pull requests ahead of a text search, before and after it returns", () => {
121 const linked = composerPullRequestsFromLinks(
122 [
123 link("owner/repo", 7, {
124 snapshot: { ...link("owner/repo", 7).snapshot!, title: "Fix login" },
125 }),
126 link("owner/repo", 8),
127 link("owner/repo", 8, {
128 host: "gitlab.com",
129 url: "https://gitlab.com/owner/repo/-/merge_requests/8",
130 snapshot: { ...link("owner/repo", 8).snapshot!, title: "Fix CI" },
131 }),
132 ],
133 "p1",
134 );
135 const searching = withLinkedComposerPullRequestsFirst({
136 keyOf,
137 matches: [],
138 linked,
139 query: "fix",
140 limit: 10,
141 });
142 expect(searching.map((match) => match.number)).toEqual([7, 8]);
143 const searched = withLinkedComposerPullRequestsFirst({
144 keyOf,
145 matches: [entry(9, "2026-01-05"), entry(8, "2026-01-03")],
146 linked,
147 query: "fix",
148 limit: 10,
149 });
150 expect(searched).toEqual([
151 linked[0],
152 entry(8, "2026-01-03"),
153 linked[2],
154 entry(9, "2026-01-05"),
155 ]);
156 });
157});