Skip to content
Source
Sign inSign up

pullRequestLinkInput.test.ts

205 lines7.0 KB

1import { describe, expect, it } from "vite-plus/test";
2​
3import { sourceControlClients } from "./sourceControlClients.ts";
4​
5import { parsePullRequestReference, resolveLinkPullRequestInput } from "./pullRequestLinkInput.ts";
6​
7const changeRequestWebUrl = (kind: string, host: string, repository: string, number: number) =>
8 sourceControlClients.get(kind).changeRequestUrl({ host, repository, number });
9​
10const project = {
11 host: "github.com",
12 repository: "acme/web",
13 terminology: sourceControlClients.get("github").changeRequest,
14 webUrl: (number: number) => changeRequestWebUrl("github", "github.com", "acme/web", number),
15};
16​
17describe("resolveLinkPullRequestInput", () => {
18 it.each(["git.cafe", "staging.git.cafe"])("canonicalizes both GitCafe routes on %s", (host) => {
19 for (const [repository, route] of [
20 ["web", "pull"],
21 ["web", "pulls"],
22 ["pull", "pull"],
23 ["pull", "pulls"],
24 ["pulls", "pull"],
25 ["pulls", "pulls"],
26 ]) {
27 const url = `https://${host}/acme/${repository}/${route}/42`;
28 expect(
29 sourceControlClients.get("gitcafe").parseChangeRequestUrl(new URL(url)),
30 ).not.toBeNull();
31 expect(parsePullRequestReference(url)).toBe(url);
32 expect(
33 resolveLinkPullRequestInput({ reference: url, project: null, hasProject: () => true }),
34 ).toEqual({
35 link: {
36 host,
37 repository: `acme/${repository}`,
38 number: 42,
39 url: `https://${host}/acme/${repository}/pulls/42`,
40 },
41 });
42 }
43 });
44​
45 it.each([
46 ["https://bitbucket.org/acme/web/pull-requests/42", "bitbucket.org"],
47 ["https://github.acme.test/acme/web/pull/42", "github.acme.test"],
48 ["https://git.acme.test/acme/web/-/merge_requests/42", "git.acme.test"],
49 ])("links supported host URL %s without a thread project", (url, host) => {
50 expect(
51 resolveLinkPullRequestInput({
52 reference: ` ${url} `,
53 project: null,
54 hasProject: (candidate) => candidate.host === host,
55 }),
56 ).toEqual({ link: { host, repository: "acme/web", number: 42, url } });
57 });
58​
59 it("validates the full Azure repository when resolving a browser URL", () => {
60 const hasProject = (reference: { host: string; repository: string }) =>
61 reference.host === "dev.azure.com" && reference.repository === "org-a/project/_git/web";
62 expect(
63 resolveLinkPullRequestInput({
64 reference: "https://dev.azure.com/org-a/project/_git/web/pullrequest/42",
65 project: null,
66 hasProject,
67 }),
68 ).toMatchObject({ link: { repository: "org-a/project/_git/web", number: 42 } });
69 expect(
70 resolveLinkPullRequestInput({
71 reference: "https://dev.azure.com/org-b/project/_git/web/pullrequest/42",
72 project: null,
73 hasProject,
74 }),
75 ).toMatchObject({ error: expect.stringContaining("org-b/project/_git/web") });
76 });
77​
78 it("resolves bare Azure numbers into canonical browser URLs", () => {
79 expect(
80 resolveLinkPullRequestInput({
81 reference: "#42",
82 project: {
83 host: "ssh.dev.azure.com",
84 repository: "v3/org/project/web",
85 terminology: sourceControlClients.get("azure-devops").changeRequest,
86 webUrl: (number) =>
87 changeRequestWebUrl("azure-devops", "ssh.dev.azure.com", "v3/org/project/web", number),
88 },
89 hasProject: () => true,
90 }),
91 ).toMatchObject({
92 link: {
93 host: "dev.azure.com",
94 repository: "org/project/_git/web",
95 number: 42,
96 url: "https://dev.azure.com/org/project/_git/web/pullrequest/42",
97 },
98 });
99 });
100​
101 it("resolves a bare number against the thread's own repository", () => {
102 expect(
103 resolveLinkPullRequestInput({ reference: "#42", project, hasProject: () => true }),
104 ).toEqual({
105 link: {
106 host: "github.com",
107 repository: "acme/web",
108 number: 42,
109 url: "https://github.com/acme/web/pull/42",
110 },
111 });
112 });
113​
114 it("names the project host's change request when it has no URL for a number", () => {
115 expect(
116 resolveLinkPullRequestInput({
117 reference: "#42",
118 project: {
119 ...project,
120 terminology: sourceControlClients.get("gitlab").changeRequest,
121 webUrl: () => null,
122 },
123 hasProject: () => true,
124 }),
125 ).toEqual({
126 error: "Paste a full URL; this project's host has no known merge request URL.",
127 });
128 });
129​
130 it("links a URL from another repository on a host with a project", () => {
131 expect(
132 resolveLinkPullRequestInput({
133 reference: "https://github.com/acme/api/pull/7",
134 project,
135 hasProject: (reference) => reference.host === "github.com",
136 }),
137 ).toEqual({
138 link: {
139 host: "github.com",
140 repository: "acme/api",
141 number: 7,
142 url: "https://github.com/acme/api/pull/7",
143 },
144 });
145 });
146​
147 it("refuses a URL on a host nothing is checked out from", () => {
148 const result = resolveLinkPullRequestInput({
149 reference: "https://gitlab.com/acme/api/-/merge_requests/7",
150 project,
151 hasProject: () => false,
152 });
153 expect(result).toMatchObject({ error: expect.stringContaining("gitlab.com") });
154 });
155});
156​
157describe("parsePullRequestReference", () => {
158 it("accepts legacy Azure DevOps pull request URLs", () => {
159 expect(
160 parsePullRequestReference("https://acme.visualstudio.com/project/_git/t3code/pullrequest/42"),
161 ).toBe("https://acme.visualstudio.com/project/_git/t3code/pullrequest/42");
162 expect(
163 parsePullRequestReference(
164 "https://acme.visualstudio.com/DefaultCollection/project/_git/t3code/pullrequest/42",
165 ),
166 ).toBe("https://acme.visualstudio.com/DefaultCollection/project/_git/t3code/pullrequest/42");
167 });
168​
169 it.each([
170 "https://code.example.com/group/project/-/merge_requests/42",
171 "http://git.internal/group/sub/project/-/merge_requests/7",
172 ])("accepts self-hosted GitLab merge request URL %s", (url) => {
173 expect(parsePullRequestReference(url)).toBe(url);
174 });
175​
176 it.each([
177 "https://github.acme.test/acme/web/pull/42",
178 "https://git.corp.example/acme/web/pull/42/files?diff=split",
179 ])("accepts GitHub Enterprise Server pull request URL %s", (url) => {
180 expect(parsePullRequestReference(url)).toBe(url);
181 expect(parsePullRequestReference(`gh pr checkout ${url}`)).toBe(url);
182 });
183​
184 it("accepts gh pr checkout commands with raw numbers", () => {
185 expect(parsePullRequestReference("gh pr checkout 42")).toBe("42");
186 });
187​
188 it("accepts glab mr checkout commands with raw numbers", () => {
189 expect(parsePullRequestReference("glab mr checkout 42")).toBe("42");
190 });
191​
192 it("accepts az repos pr checkout commands with equals-style ids", () => {
193 expect(parsePullRequestReference("az repos pr checkout --id=42")).toBe("42");
194 });
195​
196 it("accepts az repos pr checkout commands with extra flags", () => {
197 expect(parsePullRequestReference("az repos pr checkout --id 42 --remote-name origin")).toBe(
198 "42",
199 );
200 });
201​
202 it("rejects non-pull-request input", () => {
203 expect(parsePullRequestReference("feature/my-branch")).toBeNull();
204 });
205});