- Add pi-worktree-switch.ts with pure functions: - validateSwitchInput: validates target is non-empty - resolveWorktreeTarget: resolves path or branch name to worktree path - isAlreadyInWorktree: checks if cwd matches target - Wire up createSwitchWorktreeTool with full validation pipeline: 1. Validate input parameters 2. List worktrees and resolve target to path 3. Refuse if already in target worktree 4. Fail fast on uncommitted changes in source 5. Change cwd via ctx.chdir() 6. Return success with branch info - Remove dead resolveWorktreeTarget from main module - 21 unit tests, 103 total passing, 0 regressions
141 lines
4.9 KiB
TypeScript
141 lines
4.9 KiB
TypeScript
/**
|
|
* Unit tests for git_worktree_switch validation and resolution functions.
|
|
*
|
|
* Tests cover input validation, target resolution, already-there check,
|
|
* and the integrated tool execute path.
|
|
*/
|
|
|
|
import { describe, it, expect } from "bun:test";
|
|
import {
|
|
validateSwitchInput,
|
|
resolveWorktreeTarget,
|
|
isAlreadyInWorktree,
|
|
} from "./pi-worktree-switch";
|
|
import type { WorktreeEntry } from "./pi-worktree-parser";
|
|
|
|
// ─── validateSwitchInput ──────────────────────────────────────────────
|
|
|
|
describe("validateSwitchInput (Issue #6)", () => {
|
|
it("returns null for valid absolute path", () => {
|
|
const result = validateSwitchInput("/tmp/worktrees/feature-x");
|
|
expect(result).toBeNull();
|
|
});
|
|
|
|
it("returns null for valid relative path", () => {
|
|
const result = validateSwitchInput("../worktrees/feature-x");
|
|
expect(result).toBeNull();
|
|
});
|
|
|
|
it("returns null for valid branch name", () => {
|
|
const result = validateSwitchInput("feature-x");
|
|
expect(result).toBeNull();
|
|
});
|
|
|
|
it("returns null for branch name with slashes", () => {
|
|
const result = validateSwitchInput("feature/my-branch");
|
|
expect(result).toBeNull();
|
|
});
|
|
|
|
it("returns error for empty target", () => {
|
|
const result = validateSwitchInput("");
|
|
expect(result).not.toBeNull();
|
|
if (result) expect(result.message).toContain("cannot be empty");
|
|
});
|
|
|
|
it("returns error for whitespace-only target", () => {
|
|
const result = validateSwitchInput(" ");
|
|
expect(result).not.toBeNull();
|
|
});
|
|
|
|
it("includes suggestion in error for empty target", () => {
|
|
const result = validateSwitchInput("");
|
|
expect(result).not.toBeNull();
|
|
if (result) expect(result.suggestion).toBeDefined();
|
|
});
|
|
});
|
|
|
|
// ─── resolveWorktreeTarget ────────────────────────────────────────────
|
|
|
|
describe("resolveWorktreeTarget (Issue #6)", () => {
|
|
const worktrees: WorktreeEntry[] = [
|
|
{ path: "/home/user/project", commit: "abc1234", branch: "main", isCurrent: true },
|
|
{ path: "/tmp/feature-x", commit: "def5678", branch: "feature-x", isCurrent: false },
|
|
{ path: "/tmp/bugfix", commit: "fedcba9", branch: "bugfix/issue-42", isCurrent: false },
|
|
];
|
|
|
|
it("resolves by exact path match", () => {
|
|
const result = resolveWorktreeTarget("/tmp/feature-x", worktrees);
|
|
expect(result).toBe("/tmp/feature-x");
|
|
});
|
|
|
|
it("resolves by branch name", () => {
|
|
const result = resolveWorktreeTarget("feature-x", worktrees);
|
|
expect(result).toBe("/tmp/feature-x");
|
|
});
|
|
|
|
it("resolves branch name with slashes", () => {
|
|
const result = resolveWorktreeTarget("bugfix/issue-42", worktrees);
|
|
expect(result).toBe("/tmp/bugfix");
|
|
});
|
|
|
|
it("returns undefined for unknown target", () => {
|
|
const result = resolveWorktreeTarget("nonexistent", worktrees);
|
|
expect(result).toBeUndefined();
|
|
});
|
|
|
|
it("returns undefined for unknown path", () => {
|
|
const result = resolveWorktreeTarget("/tmp/does-not-exist", worktrees);
|
|
expect(result).toBeUndefined();
|
|
});
|
|
|
|
it("resolves main repo by path", () => {
|
|
const result = resolveWorktreeTarget("/home/user/project", worktrees);
|
|
expect(result).toBe("/home/user/project");
|
|
});
|
|
|
|
it("resolves main repo by branch name", () => {
|
|
const result = resolveWorktreeTarget("main", worktrees);
|
|
expect(result).toBe("/home/user/project");
|
|
});
|
|
|
|
it("prefers exact path match over branch name match", () => {
|
|
// If a target matches both a path and a branch name, path wins
|
|
const result = resolveWorktreeTarget("/tmp/feature-x", worktrees);
|
|
expect(result).toBe("/tmp/feature-x");
|
|
});
|
|
|
|
it("returns undefined for empty worktree list", () => {
|
|
const result = resolveWorktreeTarget("anything", []);
|
|
expect(result).toBeUndefined();
|
|
});
|
|
});
|
|
|
|
// ─── isAlreadyInWorktree ──────────────────────────────────────────────
|
|
|
|
describe("isAlreadyInWorktree (Issue #6)", () => {
|
|
it("returns true when cwd matches target path exactly", () => {
|
|
const result = isAlreadyInWorktree("/tmp/feature-x", "/tmp/feature-x");
|
|
expect(result).toBe(true);
|
|
});
|
|
|
|
it("returns false when cwd differs from target path", () => {
|
|
const result = isAlreadyInWorktree("/tmp/feature-x", "/home/user/project");
|
|
expect(result).toBe(false);
|
|
});
|
|
|
|
it("returns true when paths match with trailing slash difference", () => {
|
|
const result = isAlreadyInWorktree("/tmp/feature-x/", "/tmp/feature-x");
|
|
expect(result).toBe(true);
|
|
});
|
|
|
|
it("returns true when both have trailing slashes", () => {
|
|
const result = isAlreadyInWorktree("/tmp/feature-x/", "/tmp/feature-x/");
|
|
expect(result).toBe(true);
|
|
});
|
|
|
|
it("handles relative target path resolved against cwd", () => {
|
|
// If target is relative and resolves to same as cwd, already there
|
|
const result = isAlreadyInWorktree(".", "/tmp/feature-x");
|
|
expect(result).toBe(true);
|
|
});
|
|
});
|