import * as Schema from "effect/Schema";import { describe, expect, it } from "vite-plus/test"; import { PullRequestListInput, PullRequestListResult, PullRequestReviewerRequestInput,} from "./pullRequest.ts"; const decodeListResult = Schema.decodeUnknownSync(PullRequestListResult);const decodeListInput = Schema.decodeUnknownSync(PullRequestListInput);const decodeReviewerRequest = Schema.decodeUnknownSync(PullRequestReviewerRequestInput); const LIST_RESULT: PullRequestListResult = { viewers: { "github.com": "bilal", "gitlab.com": "bilal.hassan" }, providers: [ { host: "github.com", kind: "github", searchesOnHost: true, projectCount: 1, configured: true, detail: null, }, { host: "gitlab.com", kind: "gitlab", searchesOnHost: true, projectCount: 1, configured: false, detail: "glab is not installed.", }, ], entries: [ { provider: "github", host: "github.com", projectId: "project-1" as PullRequestListResult["entries"][number]["projectId"], projectTitle: "t3code", repository: "pingdotgg/t3code", number: 1, title: "Add a pull requests page", url: "https://github.com/pingdotgg/t3code/pull/1", author: { login: "octocat", name: null, avatarUrl: null }, headBranch: "feat/page", baseBranch: "main", state: "open", isDraft: false, mergeability: "mergeable", additions: 1, deletions: 0, createdAt: "2026-07-01T00:00:00Z", updatedAt: "2026-07-02T00:00:00Z", viewerReviewRequested: false, labels: [{ name: "backend", color: null }], }, ], errors: [], truncated: false, nextCursors: { "github.com pingdotgg/t3code": "2026-07-02T00:00:00Z|1|1" },}; describe("PullRequestListResult", () => { /** * The RPC builds this codec at call time, so a shape it cannot lower — an open-keyed record * with an optional value, for one — fails as an interrupted request rather than as a schema * error. Building it here turns that into a test failure instead. */ it("round-trips through the JSON codec the RPC serializes with", () => { const codec = Schema.toCodecJson(PullRequestListResult); const decoded = Schema.decodeUnknownSync(codec)(Schema.encodeUnknownSync(codec)(LIST_RESULT)); expect(decoded).toStrictEqual(LIST_RESULT); }); it("keys a viewer by host, so two hosts of one kind stay separate accounts", () => { const decoded = decodeListResult({ ...LIST_RESULT, viewers: { "github.com": "bilal", "github.acme.dev": "b.hassan" }, }); expect(decoded.viewers["github.com"]).toBe("bilal"); expect(decoded.viewers["github.acme.dev"]).toBe("b.hassan"); });}); describe("PullRequestListInput", () => { it("trims a search, so what is sent is what was typed", () => { expect(decodeListInput({ state: "open", query: " page " }).query).toBe("page"); }); it("has no search when none was asked for", () => { expect(decodeListInput({ state: "open" }).query).toBeUndefined(); }); it("bounds a search, because it travels into a command and a query string", () => { expect(decodeListInput({ state: "open", query: "p".repeat(200) }).query).toHaveLength(200); expect(() => decodeListInput({ state: "open", query: "p".repeat(201) })).toThrow(); }); it("takes back the continuation a result handed out, keyed the way it arrived", () => { const cursors = { "github.com pingdotgg/t3code": "2026-07-02T00:00:00Z|99|1,2" }; expect(decodeListInput({ state: "open", cursors }).cursors).toStrictEqual(cursors); }); it("has no continuation when the listing is being read from the top", () => { expect(decodeListInput({ state: "open" }).cursors).toBeUndefined(); }); it("bounds a continuation, because it comes back from the page and goes into a filter", () => { const long = (length: number) => ({ "github.com acme/web": "c".repeat(length) }); expect(decodeListInput({ state: "open", cursors: long(4096) })).toBeDefined(); expect(() => decodeListInput({ state: "open", cursors: long(4097) })).toThrow(); });}); describe("PullRequestReviewerRequestInput", () => { const ref = { projectId: "p1", repository: "acme/web", number: 1 }; const reviewer = { id: "octocat", kind: "user" }; it("carries the same shape whichever direction the request goes", () => { // One operation, turned around: `requested` is the whole difference between asking somebody // for a review and taking the request back. for (const requested of [true, false]) { expect(decodeReviewerRequest({ ...ref, reviewers: [reviewer], requested }).requested).toBe( requested, ); } }); it("refuses a request that names nobody, which no host would do anything with", () => { expect(() => decodeReviewerRequest({ ...ref, reviewers: [], requested: true })).toThrow(); }); it("bounds the reviewers, because they travel into a body the page composed", () => { const many = (count: number) => Array.from({ length: count }, (_, index) => ({ id: `user${index}`, kind: "user" })); expect( decodeReviewerRequest({ ...ref, reviewers: many(25), requested: true }).reviewers, ).toHaveLength(25); expect(() => decodeReviewerRequest({ ...ref, reviewers: many(26), requested: true })).toThrow(); }); it("keeps a team apart from a person, which is a different thing to ask", () => { expect( decodeReviewerRequest({ ...ref, reviewers: [reviewer, { id: "web-platform", kind: "team" }], requested: true, }).reviewers.map((entry) => entry.kind), ).toEqual(["user", "team"]); });});
import * as Schema from "effect/Schema";
import { describe, expect, it } from "vite-plus/test";
import {
PullRequestListInput,
PullRequestListResult,
PullRequestReviewerRequestInput,
} from "./pullRequest.ts";
const decodeListResult = Schema.decodeUnknownSync(PullRequestListResult);
const decodeListInput = Schema.decodeUnknownSync(PullRequestListInput);
const decodeReviewerRequest = Schema.decodeUnknownSync(PullRequestReviewerRequestInput);
const LIST_RESULT: PullRequestListResult = {
viewers: { "github.com": "bilal", "gitlab.com": "bilal.hassan" },
providers: [
{
host: "github.com",
kind: "github",
searchesOnHost: true,
projectCount: 1,
configured: true,
detail: null,
},
{
host: "gitlab.com",
kind: "gitlab",
searchesOnHost: true,
projectCount: 1,
configured: false,
detail: "glab is not installed.",
},
],
entries: [
{
provider: "github",
host: "github.com",
projectId: "project-1" as PullRequestListResult["entries"][number]["projectId"],
projectTitle: "t3code",
repository: "pingdotgg/t3code",
number: 1,
title: "Add a pull requests page",
url: "https://github.com/pingdotgg/t3code/pull/1",
author: { login: "octocat", name: null, avatarUrl: null },
headBranch: "feat/page",
baseBranch: "main",
state: "open",
isDraft: false,
mergeability: "mergeable",
additions: 1,
deletions: 0,
createdAt: "2026-07-01T00:00:00Z",
updatedAt: "2026-07-02T00:00:00Z",
viewerReviewRequested: false,
labels: [{ name: "backend", color: null }],
},
],
errors: [],
truncated: false,
nextCursors: { "github.com pingdotgg/t3code": "2026-07-02T00:00:00Z|1|1" },
};
describe("PullRequestListResult", () => {
/**
* The RPC builds this codec at call time, so a shape it cannot lower — an open-keyed record
* with an optional value, for one — fails as an interrupted request rather than as a schema
* error. Building it here turns that into a test failure instead.
*/
it("round-trips through the JSON codec the RPC serializes with", () => {
const codec = Schema.toCodecJson(PullRequestListResult);
const decoded = Schema.decodeUnknownSync(codec)(Schema.encodeUnknownSync(codec)(LIST_RESULT));
expect(decoded).toStrictEqual(LIST_RESULT);
});
it("keys a viewer by host, so two hosts of one kind stay separate accounts", () => {
const decoded = decodeListResult({
...LIST_RESULT,
viewers: { "github.com": "bilal", "github.acme.dev": "b.hassan" },
});
expect(decoded.viewers["github.com"]).toBe("bilal");
expect(decoded.viewers["github.acme.dev"]).toBe("b.hassan");
});
});
describe("PullRequestListInput", () => {
it("trims a search, so what is sent is what was typed", () => {
expect(decodeListInput({ state: "open", query: " page " }).query).toBe("page");
});
it("has no search when none was asked for", () => {
expect(decodeListInput({ state: "open" }).query).toBeUndefined();
});
it("bounds a search, because it travels into a command and a query string", () => {
expect(decodeListInput({ state: "open", query: "p".repeat(200) }).query).toHaveLength(200);
expect(() => decodeListInput({ state: "open", query: "p".repeat(201) })).toThrow();
});
it("takes back the continuation a result handed out, keyed the way it arrived", () => {
const cursors = { "github.com pingdotgg/t3code": "2026-07-02T00:00:00Z|99|1,2" };
expect(decodeListInput({ state: "open", cursors }).cursors).toStrictEqual(cursors);
});
it("has no continuation when the listing is being read from the top", () => {
expect(decodeListInput({ state: "open" }).cursors).toBeUndefined();
});
it("bounds a continuation, because it comes back from the page and goes into a filter", () => {
const long = (length: number) => ({ "github.com acme/web": "c".repeat(length) });
expect(decodeListInput({ state: "open", cursors: long(4096) })).toBeDefined();
expect(() => decodeListInput({ state: "open", cursors: long(4097) })).toThrow();
});
});
describe("PullRequestReviewerRequestInput", () => {
const ref = { projectId: "p1", repository: "acme/web", number: 1 };
const reviewer = { id: "octocat", kind: "user" };
it("carries the same shape whichever direction the request goes", () => {
// One operation, turned around: `requested` is the whole difference between asking somebody
// for a review and taking the request back.
for (const requested of [true, false]) {
expect(decodeReviewerRequest({ ...ref, reviewers: [reviewer], requested }).requested).toBe(
requested,
);
}
});
it("refuses a request that names nobody, which no host would do anything with", () => {
expect(() => decodeReviewerRequest({ ...ref, reviewers: [], requested: true })).toThrow();
});
it("bounds the reviewers, because they travel into a body the page composed", () => {
const many = (count: number) =>
Array.from({ length: count }, (_, index) => ({ id: `user${index}`, kind: "user" }));
expect(
decodeReviewerRequest({ ...ref, reviewers: many(25), requested: true }).reviewers,
).toHaveLength(25);
expect(() => decodeReviewerRequest({ ...ref, reviewers: many(26), requested: true })).toThrow();
});
it("keeps a team apart from a person, which is a different thing to ask", () => {
expect(
decodeReviewerRequest({
...ref,
reviewers: [reviewer, { id: "web-platform", kind: "team" }],
requested: true,
}).reviewers.map((entry) => entry.kind),
).toEqual(["user", "team"]);
});
});