Files
AgentHub/client/src/lib/__tests__/gitTargetApi.test.ts
T
nntrivi2001 5084334d81 feat(workspace): a session in its own worktree, Claude's way
Desktop-parity phase 6, following Claude Desktop:
- the workspace home's Worktree checkbox starts the pane with
  `claude --worktree=<name>`, so Claude Code makes, populates
  (.worktreeinclude, WorktreeCreate hook) and cleans up
  <repo>/.claude/worktrees/<name>; AgentHub only excludes
  /.claude/worktrees/ at the repo root (Claude does not)
- server/lib/session-worktree.js resolves a session's worktree from the
  session row only (owned by the workspace, registered by git worktree
  list); every lane git route takes ?session= and answers ENOWORKTREE
  instead of falling back to the main folder
- the session view's Changes panel works in the opened run's worktree
  (dashboard_runs.worktree, GitTarget in the client api)
- Resume/Fork start in the session's worktree; a gone one is ENOWORKTREE
- Archive of a worktree session removes the worktree after asking:
  git worktree remove without --force, git branch -d (unmerged work keeps
  its branch), a lock held by a live pid refused, a stale one cleared
- listWorktrees reads `locked <reason>` locks
- tests scrub GIT_* env so the pre-commit suite cannot act on this repo
2026-10-02 13:22:29 +07:00

40 lines
1.4 KiB
TypeScript

/**
* @file Pins where a workspace git call goes: the workspace's own routes, or —
* for a session's worktree target — the same routes with `?session=`, which
* the server resolves to that session's worktree.
* @author Nguyễn Ngọc Trí Vĩ <vinnt@smartgift.vn>
*/
import { describe, it, expect, vi, afterEach } from "vitest";
import { api, gitTarget } from "../api";
afterEach(() => {
vi.unstubAllGlobals();
});
function stubFetch() {
const spy = vi.fn().mockResolvedValue({
ok: true,
status: 200,
headers: { get: () => "application/json" },
json: async () => ({}),
});
vi.stubGlobal("fetch", spy);
return spy;
}
describe("git targets", () => {
it("adds session= for a worktree target and nothing for the workspace", async () => {
const spy = stubFetch();
await api.lanes.changes(3, { range: "work" });
await api.lanes.changes(gitTarget(3, "s-1"), { range: "work" });
await api.lanes.gitAction(gitTarget(3, "s-1"), "stage", { paths: ["a"] });
await api.lanes.git(gitTarget(3, null));
const urls = spy.mock.calls.map((c) => String(c[0]));
expect(urls[0]).toMatch(/\/lanes\/3\/git\/changes\?range=work$/);
expect(urls[1]).toMatch(/\/lanes\/3\/git\/changes\?range=work&session=s-1$/);
expect(urls[2]).toMatch(/\/lanes\/3\/git\/stage\?session=s-1$/);
expect(urls[3]).toMatch(/\/lanes\/3\/git$/);
});
});