Skip to content
Source
Sign inSign up

worktreeCleanup.test.ts

91 lines3.2 KB

1import { EnvironmentId, ProjectId, ProviderInstanceId, ThreadId } from "@t3tools/contracts";
2import { describe, expect, it } from "vite-plus/test";
3​
4import { DEFAULT_INTERACTION_MODE, DEFAULT_RUNTIME_MODE, type Thread } from "./types";
5import { makeThreadFixture } from "./test-fixtures";
6import { getOrphanedWorktreePathForThread } from "./worktreeCleanup";
7​
8const localEnvironmentId = EnvironmentId.make("environment-local");
9​
10function makeThread(overrides: Partial<Thread> = {}): Thread {
11 return makeThreadFixture({
12 id: ThreadId.make("thread-1"),
13 environmentId: localEnvironmentId,
14 projectId: ProjectId.make("project-1"),
15 title: "Thread",
16 modelSelection: {
17 instanceId: ProviderInstanceId.make("codex"),
18 model: "gpt-5.3-codex",
19 },
20 runtimeMode: DEFAULT_RUNTIME_MODE,
21 interactionMode: DEFAULT_INTERACTION_MODE,
22 runtime: null,
23 messages: [],
24 proposedPlans: [],
25 createdAt: "2026-02-13T00:00:00.000Z",
26 updatedAt: "2026-02-13T00:00:00.000Z",
27 archivedAt: null,
28 settledOverride: null,
29 settledAt: null,
30 deletedAt: null,
31 latestRun: null,
32 branch: null,
33 worktreePath: null,
34 ...overrides,
35 });
37​
38describe("getOrphanedWorktreePathForThread", () => {
39 it("returns null when another thread links to the same worktree", () => {
40 const threads = [
41 makeThread({
42 id: ThreadId.make("thread-1"),
43 worktreePath: "/tmp/repo/worktrees/feature-a",
44 }),
45 makeThread({
46 id: ThreadId.make("thread-2"),
47 worktreePath: "/tmp/repo/worktrees/feature-a",
48 }),
49 ];
50 const result = getOrphanedWorktreePathForThread(threads, ThreadId.make("thread-1"));
51 expect(result).toBeNull();
52 });
53​
54 it.each([
55 ["/tmp/repo/worktrees/feature-a", "/tmp/repo/worktrees/feature-a/"],
56 ["/tmp/repo/worktrees/feature-a", "/tmp/repo/worktrees/feature-a//"],
57 ["C:\\repo\\worktrees\\feature-a", "c:/repo/worktrees/feature-a/"],
58 ])("treats %s and %s as the same worktree", (targetPath, otherPath) => {
59 const threads = [
60 makeThread({ id: ThreadId.make("thread-1"), worktreePath: targetPath }),
61 makeThread({ id: ThreadId.make("thread-2"), worktreePath: otherPath }),
62 ];
63 expect(getOrphanedWorktreePathForThread(threads, ThreadId.make("thread-1"))).toBeNull();
64 expect(getOrphanedWorktreePathForThread(threads, ThreadId.make("thread-2"))).toBeNull();
65 });
66​
67 it("keeps case-sensitive POSIX worktrees apart", () => {
68 const threads = [
69 makeThread({ id: ThreadId.make("thread-1"), worktreePath: "/tmp/repo/worktrees/Feature" }),
70 makeThread({ id: ThreadId.make("thread-2"), worktreePath: "/tmp/repo/worktrees/feature" }),
71 ];
72 expect(getOrphanedWorktreePathForThread(threads, ThreadId.make("thread-1"))).toBe(
73 "/tmp/repo/worktrees/Feature",
74 );
75 });
76​
77 it("ignores threads linked to different worktrees", () => {
78 const threads = [
79 makeThread({
80 id: ThreadId.make("thread-1"),
81 worktreePath: "/tmp/repo/worktrees/feature-a",
82 }),
83 makeThread({
84 id: ThreadId.make("thread-2"),
85 worktreePath: "/tmp/repo/worktrees/feature-b",
86 }),
87 ];
88 const result = getOrphanedWorktreePathForThread(threads, ThreadId.make("thread-1"));
89 expect(result).toBe("/tmp/repo/worktrees/feature-a");
90 });
91});