1import { EnvironmentId, ThreadId } from "@t3tools/contracts";
2import { describe, expect, it } from "vite-plus/test";
3
4import {
5 matchComposerEnvironmentItems,
6 matchComposerThreadItems,
7 type ComposerThreadCandidate,
8} from "./composerThreadItems.ts";
9
10const environmentId = EnvironmentId.make("env-composer");
11
12function shell(
13 id: string,
14 title: string,
15 overrides: Partial<ComposerThreadCandidate> = {},
16): ComposerThreadCandidate {
17 return {
18 environmentId,
19 id: ThreadId.make(id),
20 title,
21 branch: null,
22 createdAt: "2026-10-01T00:00:00.000Z",
23 updatedAt: "2026-10-01T00:00:00.000Z",
24 latestUserMessageAt: null,
25 opens: null,
26 archivedAt: null,
27 lineage: { rootThreadId: ThreadId.make(id), parentThreadId: null, relationshipToParent: null },
28 ...overrides,
29 };
30}
31
32it("ranks thread mentions like the palette and leaves out subagent threads", () => {
33 const items = matchComposerThreadItems({
34 shells: [
35 shell("newer-mid-word", "Remodel the settings page", {
36 updatedAt: "2026-10-20T00:00:00.000Z",
37 }),
38 shell("word", "Fix model picker"),
39 shell("subagent", "Model review", {
40 lineage: {
41 rootThreadId: ThreadId.make("word"),
42 parentThreadId: ThreadId.make("word"),
43 relationshipToParent: "subagent",
44 },
45 }),
46 shell("other-environment", "Model docs", { environmentId: EnvironmentId.make("env-other") }),
47 ],
48 environmentId,
49 linkedEnvironments: new Map(),
50 excludeThreadId: null,
51 query: "model",
52 now: Date.parse("2026-10-23T00:00:00.000Z"),
53 });
54 expect(items.map((item) => item.thread.threadId)).toEqual(["word", "newer-mid-word"]);
55});
56
57it("also offers threads on linked machines, naming the machine, but no other machine's", () => {
58 const vps = EnvironmentId.make("env-vps");
59 const items = matchComposerThreadItems({
60 shells: [
61 shell("here", "Deploy script"),
62 shell("there", "Deploy script", { environmentId: vps }),
63 shell("unlinked", "Deploy notes", { environmentId: EnvironmentId.make("env-unlinked") }),
64 ],
65 environmentId,
66 linkedEnvironments: new Map([[vps, "Hetzner VPS"]]),
67 excludeThreadId: null,
68 query: "deploy",
69 now: Date.parse("2026-10-23T00:00:00.000Z"),
70 });
71 expect(items.map(({ thread, label, description }) => ({ thread, label, description }))).toEqual([
72 { thread: { environmentId, threadId: "here" }, label: "Deploy script", description: "Thread" },
73 {
74 thread: { environmentId: vps, threadId: "there" },
75 label: "Deploy script",
76 description: "Thread on Hetzner VPS",
77 },
78 ]);
79});
80
81describe("matchComposerEnvironmentItems", () => {
82 const environments = [
83 { environmentId: EnvironmentId.make("env-here"), label: "Laptop", machine: "laptop" },
84 { environmentId: EnvironmentId.make("env-vps"), label: "Hetzner VPS", machine: "cloud" },
85 { environmentId: EnvironmentId.make("env-mini"), label: "Mac mini", machine: "mac-mini" },
86 ] as const;
87
88 it("offers the other machines matching the query, never the composer's own", () => {
89 expect(
90 matchComposerEnvironmentItems({
91 environments,
92 environmentId: EnvironmentId.make("env-here"),
93 query: " vps ",
94 }),
95 ).toEqual([
96 {
97 id: "environment:env-vps",
98 type: "environment",
99 environmentId: "env-vps",
100 machine: "cloud",
101 label: "Hetzner VPS",
102 description: "Machine",
103 },
104 ]);
105 expect(
106 matchComposerEnvironmentItems({
107 environments,
108 environmentId: EnvironmentId.make("env-here"),
109 query: "lap",
110 }),
111 ).toEqual([]);
112 expect(
113 matchComposerEnvironmentItems({
114 environments,
115 environmentId: EnvironmentId.make("env-here"),
116 query: "",
117 }),
118 ).toEqual([]);
119 });
120});