| 1 | import { describe, expect, it } from "vite-plus/test"; |
| 2 | |
| 3 | import { sourceControlClients } from "@t3tools/client-runtime/source-control-clients"; |
| 4 | |
| 5 | import { resolveLinkPullRequestInput } from "./LinkPullRequestDialog"; |
| 6 | |
| 7 | const changeRequestWebUrl = (kind: string, host: string, repository: string, number: number) => |
| 8 | sourceControlClients.get(kind).changeRequestUrl({ host, repository, number }); |
| 9 | |
| 10 | const project = { |
| 11 | host: "github.com", |
| 12 | repository: "acme/web", |
| 13 | webUrl: (number: number) => changeRequestWebUrl("github", "github.com", "acme/web", number), |
| 14 | }; |
| 15 | |
| 16 | describe("resolveLinkPullRequestInput", () => { |
| 17 | it.each([ |
| 18 | ["https://bitbucket.org/acme/web/pull-requests/42", "bitbucket.org"], |
| 19 | ["https://github.acme.test/acme/web/pull/42", "github.acme.test"], |
| 20 | ["https://git.acme.test/acme/web/-/merge_requests/42", "git.acme.test"], |
| 21 | ])("links supported host URL %s without a thread project", (url, host) => { |
| 22 | expect( |
| 23 | resolveLinkPullRequestInput({ |
| 24 | reference: ` ${url} `, |
| 25 | project: null, |
| 26 | hasProject: (candidate) => candidate.host === host, |
| 27 | }), |
| 28 | ).toEqual({ link: { host, repository: "acme/web", number: 42, url } }); |
| 29 | }); |
| 30 | |
| 31 | it("validates the full Azure repository when resolving a browser URL", () => { |
| 32 | const hasProject = (reference: { host: string; repository: string }) => |
| 33 | reference.host === "dev.azure.com" && reference.repository === "org-a/project/_git/web"; |
| 34 | expect( |
| 35 | resolveLinkPullRequestInput({ |
| 36 | reference: "https://dev.azure.com/org-a/project/_git/web/pullrequest/42", |
| 37 | project: null, |
| 38 | hasProject, |
| 39 | }), |
| 40 | ).toMatchObject({ link: { repository: "org-a/project/_git/web", number: 42 } }); |
| 41 | expect( |
| 42 | resolveLinkPullRequestInput({ |
| 43 | reference: "https://dev.azure.com/org-b/project/_git/web/pullrequest/42", |
| 44 | project: null, |
| 45 | hasProject, |
| 46 | }), |
| 47 | ).toMatchObject({ error: expect.stringContaining("org-b/project/_git/web") }); |
| 48 | }); |
| 49 | |
| 50 | it("resolves bare Azure numbers into canonical browser URLs", () => { |
| 51 | expect( |
| 52 | resolveLinkPullRequestInput({ |
| 53 | reference: "#42", |
| 54 | project: { |
| 55 | host: "ssh.dev.azure.com", |
| 56 | repository: "v3/org/project/web", |
| 57 | webUrl: (number) => |
| 58 | changeRequestWebUrl("azure-devops", "ssh.dev.azure.com", "v3/org/project/web", number), |
| 59 | }, |
| 60 | hasProject: () => true, |
| 61 | }), |
| 62 | ).toMatchObject({ |
| 63 | link: { |
| 64 | host: "dev.azure.com", |
| 65 | repository: "org/project/_git/web", |
| 66 | number: 42, |
| 67 | url: "https://dev.azure.com/org/project/_git/web/pullrequest/42", |
| 68 | }, |
| 69 | }); |
| 70 | }); |
| 71 | |
| 72 | it("returns null for input that is not a reference", () => { |
| 73 | expect( |
| 74 | resolveLinkPullRequestInput({ reference: "hello", project, hasProject: () => true }), |
| 75 | ).toBeNull(); |
| 76 | }); |
| 77 | |
| 78 | it("resolves a bare number against the thread's own repository", () => { |
| 79 | expect( |
| 80 | resolveLinkPullRequestInput({ reference: "#42", project, hasProject: () => true }), |
| 81 | ).toEqual({ |
| 82 | link: { |
| 83 | host: "github.com", |
| 84 | repository: "acme/web", |
| 85 | number: 42, |
| 86 | url: "https://github.com/acme/web/pull/42", |
| 87 | }, |
| 88 | }); |
| 89 | }); |
| 90 | |
| 91 | it("links a URL from another repository on a host with a project", () => { |
| 92 | expect( |
| 93 | resolveLinkPullRequestInput({ |
| 94 | reference: "https://github.com/acme/api/pull/7", |
| 95 | project, |
| 96 | hasProject: (reference) => reference.host === "github.com", |
| 97 | }), |
| 98 | ).toEqual({ |
| 99 | link: { |
| 100 | host: "github.com", |
| 101 | repository: "acme/api", |
| 102 | number: 7, |
| 103 | url: "https://github.com/acme/api/pull/7", |
| 104 | }, |
| 105 | }); |
| 106 | }); |
| 107 | |
| 108 | it("refuses a URL on a host nothing is checked out from", () => { |
| 109 | const result = resolveLinkPullRequestInput({ |
| 110 | reference: "https://gitlab.com/acme/api/-/merge_requests/7", |
| 111 | project, |
| 112 | hasProject: () => false, |
| 113 | }); |
| 114 | expect(result).toMatchObject({ error: expect.stringContaining("gitlab.com") }); |
| 115 | }); |
| 116 | |
| 117 | it("asks for a URL when a bare number has no project to resolve against", () => { |
| 118 | expect( |
| 119 | resolveLinkPullRequestInput({ reference: "12", project: null, hasProject: () => true }), |
| 120 | ).toMatchObject({ error: expect.stringContaining("full URL") }); |
| 121 | }); |
| 122 | |
| 123 | it("accepts a checkout command as a reference", () => { |
| 124 | expect( |
| 125 | resolveLinkPullRequestInput({ |
| 126 | reference: "gh pr checkout https://github.com/acme/web/pull/3", |
| 127 | project, |
| 128 | hasProject: () => true, |
| 129 | }), |
| 130 | ).toMatchObject({ link: { number: 3, repository: "acme/web" } }); |
| 131 | }); |
| 132 | }); |
| 133 | |
| 134 | describe("changeRequestWebUrl", () => { |
| 135 | it("knows the four hosts and nothing else", () => { |
| 136 | expect(changeRequestWebUrl("gitlab", "gitlab.com", "g/sub/repo", 5)).toBe( |
| 137 | "https://gitlab.com/g/sub/repo/-/merge_requests/5", |
| 138 | ); |
| 139 | expect(changeRequestWebUrl("unknown", "x", "a/b", 1)).toBeNull(); |
| 140 | }); |
| 141 | }); |