Skip to content
Source
Sign inSign up

promptLinks.test.ts

172 lines6.2 KB

1import {
2 EnvironmentId,
3 ProjectId,
4 ProviderDriverKind,
5 ProviderInstanceId,
6 ThreadId,
7 type PullRequestLinkedThreadsResult,
8 type RepositoryIdentity,
9 type ThreadPullRequestLink,
10} from "@t3tools/contracts";
11import { AsyncResult } from "effect/reactivity";
12import { describe, expect, it } from "vite-plus/test";
13​
14import {
15 findPromptLinkProjects,
16 findPromptLinkPullRequestThread,
17 pickPromptLinkPullRequestThread,
18 resolvePromptLinkModelSelection,
19} from "./promptLinks.ts";
20​
21const identity = (canonicalKey: string, origin?: string): RepositoryIdentity => ({
22 canonicalKey,
23 locator: { source: "git-remote", remoteName: "origin", remoteUrl: `https://${canonicalKey}` },
24 ...(origin ? { origin: { canonicalKey: origin } } : {}),
25});
26​
27describe("findPromptLinkProjects", () => {
28 it("keeps exact matches in order and never a fork of the repository", () => {
29 const projects = [
30 {
31 id: "fork",
32 repositoryIdentity: identity("github.com/pingdotgg/t3code", "github.com/me/t3code"),
33 },
34 { id: "none", repositoryIdentity: null },
35 { id: "recent", repositoryIdentity: identity("github.com/PingDotGG/T3Code") },
36 { id: "older", repositoryIdentity: identity("github.com/pingdotgg/t3code") },
37 ];
38 const ids = (owner: string, name: string) =>
39 findPromptLinkProjects(projects, { owner, name }).map((project) => project.id);
40 expect(ids("pingdotgg", "t3code")).toEqual(["recent", "older"]);
41 expect(ids("me", "t3code")).toEqual(["fork"]);
42 expect(ids("gitlab", "x")).toEqual([]);
43 });
44​
45 it("matches a checkout of the repository's GitCafe mirror", () => {
46 const projects = [
47 { id: "gitcafe", repositoryIdentity: identity("git.cafe/pingdotgg/t3code") },
48 { id: "staging", repositoryIdentity: identity("staging.git.cafe/pingdotgg/t3code") },
49 { id: "gitlab", repositoryIdentity: identity("gitlab.com/pingdotgg/t3code") },
50 ];
51 expect(
52 findPromptLinkProjects(projects, { owner: "pingdotgg", name: "t3code" }).map(
53 (project) => project.id,
54 ),
55 ).toEqual(["gitcafe", "staging"]);
56 });
57});
58​
59describe("findPromptLinkPullRequestThread", () => {
60 it("takes the newest linked thread, never a side chat that inherited the link", () => {
61 const link: ThreadPullRequestLink = {
62 host: "github.com",
63 repository: "acme/web",
64 number: 7,
65 url: "https://github.com/acme/web/pull/7",
66 source: "manual",
67 linkedAt: "2026-01-01T00:00:00.000Z",
68 snapshot: null,
69 stack: null,
70 };
71 const thread = (id: string, updatedAt: string, sideChat?: boolean) => ({
72 id,
73 updatedAt,
74 pullRequests: [link],
75 ...(sideChat === undefined ? {} : { lineage: { sideChat } }),
76 });
77 const found = findPromptLinkPullRequestThread(
78 [
79 thread("old", "2026-01-01T00:00:00.000Z"),
80 thread("parent", "2026-01-02T00:00:00.000Z", false),
81 thread("side-chat", "2026-01-03T00:00:00.000Z", true),
82 ],
83 { owner: "acme", name: "web", number: 7 },
84 );
85 expect(found?.id).toBe("parent");
86 });
87});
88​
89describe("pickPromptLinkPullRequestThread", () => {
90 it("takes the first open thread in order once every earlier lookup has answered", () => {
91 const laptop = EnvironmentId.make("laptop");
92 const server = EnvironmentId.make("server");
93 const linked = (
94 id: string,
95 archivedAt: string | null = null,
96 ): PullRequestLinkedThreadsResult => ({
97 threads: [{ id: ThreadId.make(id), projectId: ProjectId.make("p"), title: id, archivedAt }],
98 });
99 const none: PullRequestLinkedThreadsResult = { threads: [] };
100 const pick = (
101 ...results: ReadonlyArray<AsyncResult.AsyncResult<PullRequestLinkedThreadsResult, unknown>>
102 ) =>
103 pickPromptLinkPullRequestThread(
104 results.map((result, index) => ({ environmentId: index === 0 ? laptop : server, result })),
105 );
106 // A value shown while it refetches is not an answer yet.
107 expect(pick(AsyncResult.success(none, { waiting: true }))).toBeUndefined();
108 expect(pick(AsyncResult.success(none), AsyncResult.initial(true))).toBeUndefined();
109 expect(pick(AsyncResult.initial(true), AsyncResult.success(linked("b")))).toBeUndefined();
110 const laptopThread = { environmentId: laptop, id: ThreadId.make("a") };
111 expect(pick(AsyncResult.success(linked("a")), AsyncResult.initial(true))).toEqual(laptopThread);
112 expect(pick(AsyncResult.success(linked("a")), AsyncResult.success(linked("b")))).toEqual(
113 laptopThread,
114 );
115 expect(pick(AsyncResult.success(none), AsyncResult.success(linked("old")))).toEqual({
116 environmentId: server,
117 id: ThreadId.make("old"),
118 });
119 expect(
120 pick(
121 AsyncResult.success(linked("archived", "2026-01-01T00:00:00.000Z")),
122 AsyncResult.fail(1),
123 ),
124 ).toBeNull();
125 });
126});
127​
128describe("resolvePromptLinkModelSelection", () => {
129 const provider = (
130 instanceId: string,
131 driver: string,
132 status: "ready" | "warning" | "error" | "disabled" = "ready",
133 ) => ({
134 instanceId: ProviderInstanceId.make(instanceId),
135 driver: ProviderDriverKind.make(driver),
136 enabled: true,
137 status,
138 models: [
139 { slug: "small", name: "Small" },
140 { slug: "big", name: "Big", aliases: ["opus"], isDefault: true },
141 ],
142 });
143​
144 it("maps the requested provider and model onto a usable instance", () => {
145 const providers = [provider("broken", "claudeAgent", "error"), provider("work", "claudeAgent")];
146 expect(
147 resolvePromptLinkModelSelection(providers, { provider: "claude", model: "small" }),
148 ).toEqual({
149 selection: { instanceId: "work", model: "small" },
150 exact: true,
151 });
152 expect(
153 resolvePromptLinkModelSelection(providers, { provider: "claudeagent", model: "opus" }),
154 ).toEqual({
155 selection: { instanceId: "work", model: "big" },
156 exact: true,
157 });
158 });
159​
160 it("uses the instance default for an unknown model, and nothing for a missing provider", () => {
161 const providers = [provider("codex", "codex")];
162 expect(
163 resolvePromptLinkModelSelection(providers, { provider: "codex", model: "nope" }),
164 ).toEqual({
165 selection: { instanceId: "codex", model: "big" },
166 exact: false,
167 });
168 expect(
169 resolvePromptLinkModelSelection(providers, { provider: "cursor", model: null }),
170 ).toBeNull();
171 });
172});