Skip to content
Source
Sign inSign up

pullRequest.test.ts

149 lines4.8 KB

1import { SourceControlProviderKind } from "./sourceControl.ts";
2import * as Schema from "effect/Schema";
3import { describe, expect, it } from "vite-plus/test";
4​
5import {
6 PullRequestDetail,
7 PullRequestListResult,
8 resolvePullRequestAuthorFilter,
9} from "./pullRequest.ts";
10​
11const decodeDetail = Schema.decodeUnknownSync(Schema.toCodecJson(PullRequestDetail));
12​
13const LIST_RESULT: PullRequestListResult = {
14 viewers: { "github.com": "bilal", "gitlab.com": "bilal.hassan" },
15 providers: [
16 {
17 host: "github.com",
18 kind: SourceControlProviderKind.make("github"),
19 searchesOnHost: true,
20 projectCount: 1,
21 configured: true,
22 detail: null,
23 },
24 {
25 host: "gitlab.com",
26 kind: SourceControlProviderKind.make("gitlab"),
27 searchesOnHost: true,
28 projectCount: 1,
29 configured: false,
30 detail: "glab is not installed.",
31 },
32 ],
33 entries: [
34 {
35 provider: SourceControlProviderKind.make("github"),
36 host: "github.com",
37 projectId: "project-1" as PullRequestListResult["entries"][number]["projectId"],
38 projectTitle: "t3code",
39 repository: "pingdotgg/t3code",
40 number: 1,
41 title: "Add a pull requests page",
42 url: "https://github.com/pingdotgg/t3code/pull/1",
43 author: { login: "octocat", name: null, avatarUrl: null },
44 headBranch: "feat/page",
45 baseBranch: "main",
46 state: "open",
47 isDraft: false,
48 mergeability: "mergeable",
49 additions: 1,
50 deletions: 0,
51 createdAt: "2026-07-01T00:00:00Z",
52 updatedAt: "2026-07-02T00:00:00Z",
53 viewerReviewRequested: false,
54 labels: [{ name: "backend", color: null }],
55 },
56 ],
57 errors: [],
58 truncated: false,
59 nextCursors: { "github.com pingdotgg/t3code": "2026-07-02T00:00:00Z|1|1" },
60};
61​
62describe("PullRequestListResult", () => {
63 /**
64 * The RPC builds this codec at call time, so a shape it cannot lower — an open-keyed record
65 * with an optional value, for one — fails as an interrupted request rather than as a schema
66 * error.
67 */
68 it("round-trips through the JSON codec the RPC serializes with", () => {
69 const codec = Schema.toCodecJson(PullRequestListResult);
70​
71 const decoded = Schema.decodeSync(codec)(Schema.encodeUnknownSync(codec)(LIST_RESULT));
72​
73 expect(decoded).toStrictEqual(LIST_RESULT);
74 });
75});
76​
77describe("PullRequestCapabilities", () => {
78 const base = {
79 diff: true,
80 comment: true,
81 actions: [],
82 mergeMethods: [],
83 search: true,
84 review: { inlineComment: true, reply: true, resolve: true, verdicts: [] },
85 reviewers: { request: true, listCandidates: true },
86 };
87​
88 it("loads a newer server's detail response, dropping unknown options in order", () => {
89 const options = {
90 actions: ["close", "archive", "merge"],
91 verdicts: ["request-changes", "block", "approve"],
92 updateMethods: ["rebase", "fast-forward", "merge"],
93 };
94 const detail = {
95 ...LIST_RESULT.entries[0],
96 workspaceRoot: "/workspace/t3code",
97 body: "",
98 changedFiles: 1,
99 mergedAt: null,
100 closedAt: null,
101 reviewers: [],
102 checks: [],
103 mergeCapabilities: { merge: true, squash: true, rebase: true },
104 capabilities: {
105 ...base,
106 actions: options.actions,
107 mergeMethods: ["squash", "fast-forward", "merge"],
108 updateMethods: options.updateMethods,
109 review: { ...base.review, verdicts: options.verdicts },
110 },
111 viewerPermissions: { ...options, comment: true, resolve: true, requestReviewers: true },
112 };
113 const decoded = decodeDetail(detail);
114​
115 expect(decoded.capabilities).toEqual({
116 ...base,
117 actions: ["close", "merge"],
118 mergeMethods: ["squash", "merge"],
119 updateMethods: ["rebase", "merge"],
120 review: { ...base.review, verdicts: ["request-changes", "approve"] },
121 });
122 expect(decoded.viewerPermissions).toEqual({
123 actions: ["close", "merge"],
124 verdicts: ["request-changes", "approve"],
125 updateMethods: ["rebase", "merge"],
126 comment: true,
127 resolve: true,
128 requestReviewers: true,
129 });
130 expect(decoded.title).toBe(detail.title);
131 });
132});
133​
134describe("resolvePullRequestAuthorFilter", () => {
135 it("reads me as the signed-in account on the host", () => {
136 expect(resolvePullRequestAuthorFilter("me", "bilal")).toBe("bilal");
137 expect(resolvePullRequestAuthorFilter(" @Me ", "bilal")).toBe("bilal");
138 });
139​
140 it("keeps me as a literal login when the host has no viewer", () => {
141 expect(resolvePullRequestAuthorFilter("me", null)).toBe("me");
142 expect(resolvePullRequestAuthorFilter("@me", " ")).toBe("@me");
143 });
144​
145 it("passes any other login through trimmed", () => {
146 expect(resolvePullRequestAuthorFilter(" ana ", "bilal")).toBe("ana");
147 expect(resolvePullRequestAuthorFilter("meredith", "bilal")).toBe("meredith");
148 });
149});