Skip to content
Source
Sign inSign up

pending-new-tasks-model.test.ts

145 lines5.2 KB

1import {
2 CommandId,
3 EnvironmentId,
4 MessageId,
5 ProjectId,
6 ProviderInstanceId,
7 ThreadId,
8} from "@t3tools/contracts";
9import { describe, expect, it } from "vite-plus/test";
10​
11import {
12 buildPendingNewTasks,
13 type PendingDraftProjectDefaults,
14 pendingDraftBranchNeedsRefs,
15 resolvePendingDraftBranch,
16} from "./pending-new-tasks-model";
17import type { QueuedThreadMessage } from "./thread-outbox-model";
18import type { ComposerDraft, ComposerDraftWorkspaceSelection } from "./use-composer-drafts";
19​
20const projectCwd = "/repo";
21const localDefault: PendingDraftProjectDefaults = {
22 canChooseWorkspace: true,
23 defaultMode: "local",
24 defaultModeSettled: true,
25};
26const worktreeDefault: PendingDraftProjectDefaults = { ...localDefault, defaultMode: "worktree" };
27const resolve = (
28 input: Omit<Parameters<typeof resolvePendingDraftBranch>[0], "defaults" | "isGitRepo"> & {
29 readonly defaults?: PendingDraftProjectDefaults;
30 },
31) => resolvePendingDraftBranch({ defaults: localDefault, isGitRepo: true, ...input });
32const branches = [
33 { name: "feature", current: true, isDefault: false },
34 { name: "origin/main", current: false, isDefault: true, isRemote: true },
35];
36​
37function draft(workspaceSelection?: ComposerDraftWorkspaceSelection): ComposerDraft {
38 return {
39 text: "unsent",
40 attachments: [],
41 project: {
42 environmentId: EnvironmentId.make("env-1"),
43 projectId: ProjectId.make("project-1"),
44 createdAt: "2026-10-26T00:00:00.000Z",
45 },
46 ...(workspaceSelection ? { workspaceSelection } : {}),
47 };
49​
50describe("resolvePendingDraftBranch", () => {
51 it("shows the project's default for a draft that never picked a mode", () => {
52 const untouched = draft();
53 expect(pendingDraftBranchNeedsRefs(untouched, worktreeDefault)).toBe(true);
54 expect(resolve({ draft: untouched, defaults: worktreeDefault, branches, projectCwd })).toBe(
55 "origin/main",
56 );
57 // Until t3.json settles, the environment default may still be replaced.
58 expect(
59 resolve({
60 draft: untouched,
61 defaults: { ...worktreeDefault, defaultModeSettled: false },
62 branches,
63 projectCwd,
64 }),
65 ).toBeNull();
66 // A local default starts on the checkout without naming a branch, as on main.
67 expect(pendingDraftBranchNeedsRefs(untouched, localDefault)).toBe(false);
68 expect(resolve({ draft: untouched, branches, projectCwd })).toBeNull();
69 });
70​
71 it("shows the default base for a worktree draft that never picked one", () => {
72 const worktree = draft({ mode: "worktree", branch: null, worktreePath: null });
73 expect(pendingDraftBranchNeedsRefs(worktree, localDefault)).toBe(true);
74 expect(resolve({ draft: worktree, branches, projectCwd })).toBe("origin/main");
75 });
76​
77 it("shows the current branch for a local draft that follows the checkout", () => {
78 const local = draft({
79 mode: "local",
80 branch: null,
81 worktreePath: null,
82 followsCurrentCheckout: true,
83 });
84 expect(resolve({ draft: local, branches, projectCwd })).toBe("feature");
85 expect(resolve({ draft: local, branches: [], projectCwd })).toBeNull();
86 });
87​
88 it("keeps a chosen branch without loading refs", () => {
89 const chosen = draft({ mode: "local", branch: "release", worktreePath: null });
90 expect(pendingDraftBranchNeedsRefs(chosen, localDefault)).toBe(false);
91 expect(resolve({ draft: chosen, branches, projectCwd })).toBe("release");
92 });
93});
94​
95function queuedCreation(environmentId: string): QueuedThreadMessage {
96 return {
97 environmentId: EnvironmentId.make(environmentId),
98 threadId: ThreadId.make(`thread-${environmentId}`),
99 messageId: MessageId.make(`message-${environmentId}`),
100 commandId: CommandId.make(`command-${environmentId}`),
101 text: "Test",
102 attachments: [],
103 modelSelection: { instanceId: ProviderInstanceId.make("codex"), model: "gpt-5.6-sol" },
104 creation: {
105 projectId: ProjectId.make("project-1"),
106 workspaceMode: "local",
107 branch: null,
108 worktreePath: null,
109 },
110 createdAt: "2026-11-10T01:10:00.000Z",
111 };
113​
114describe("buildPendingNewTasks", () => {
115 it("lists only creations the outbox is not sending yet", () => {
116 const sending = EnvironmentId.make("sending");
117 const syncing = EnvironmentId.make("syncing");
118 const readOnly = EnvironmentId.make("read-only");
119 const retrying = EnvironmentId.make("retrying");
120 const { modelSelection: _model, ...modelless } = queuedCreation("sending");
121 const tasks = buildPendingNewTasks({
122 queuedMessages: [
123 ...["sending", "syncing", "read-only", "retrying", "offline"].map(queuedCreation),
124 { ...modelless, messageId: MessageId.make("message-modelless") },
125 ],
126 drafts: {},
127 sendGate: {
128 connectedEnvironmentIds: new Set([sending, syncing, readOnly, retrying]),
129 operableEnvironmentIds: new Set([sending, syncing, retrying]),
130 shellStatuses: new Map([
131 [sending, "live"],
132 [syncing, "synchronizing"],
133 [readOnly, "live"],
134 [retrying, "live"],
135 ]),
136 retryingMessageIds: { [MessageId.make("message-retrying")]: true },
137 },
138 });
139​
140 expect(tasks).toHaveLength(5);
141 expect(new Set(tasks.map((task) => task.environmentId))).toEqual(
142 new Set(["syncing", "read-only", "retrying", "offline", "sending"]),
143 );
144 });
145});