Files
kandev/apps/web/components/task/editor-worktree-options.test.ts
T
João Salavisa ea84b913b4 feat: choose worktree when opening IDE on multi-repo tasks (#1743)
* feat: choose worktree when opening IDE on multi-repo tasks

On multi-repo tasks the open-in-IDE button always opened the first
worktree. The editors menu now shows a worktree picker (repo + branch)
when a session has multiple worktrees, and each editor in the dropdown
expands into a per-worktree submenu.

- open-editor/open-folder accept an optional worktree_id; empty keeps
  the first-worktree fallback so existing clients are unaffected
- session DTOs now serialize the full worktrees list instead of only
  the flattened first-worktree fields
- useSessionWorktrees unions API worktrees with the WS-populated list
- new Playwright spec covers the picker on a real multi-repo session

* fix: address review feedback on worktree picker

- fall back on empty-string worktree fields, not only null/undefined
- bind optional open-folder body via EOF check instead of ContentLength
  so chunked requests keep their worktree_id
- wrap disabled editor buttons in a focusable span so tooltips work
- add hook test asserting worktreeId is forwarded as worktree_id

* fix: map editor service errors to HTTP status, drop e2e sleep

- open-editor/open-folder return 404/400/409 for known service errors
  (unknown worktree or editor, invalid config, unavailable editor)
  instead of a blanket 500
- replace the picker spec's 750ms sleep with an explicit composer click
  so the autofocus race is deterministic

---------

Co-authored-by: Kandev Agent <agent@kandev.dev>
2026-07-16 22:43:06 +01:00

45 lines
1.8 KiB
TypeScript

import { describe, expect, it } from "vitest";
import { buildWorktreeOptions } from "./editor-worktree-options";
import type { Worktree } from "@/lib/state/slices/session/types";
import type { Repository } from "@/lib/types/http";
function repo(id: string, name: string): Repository {
return { id, name } as Repository;
}
describe("buildWorktreeOptions", () => {
it("labels worktrees with their repository name", () => {
const worktrees: Worktree[] = [
{ id: "wt-1", sessionId: "s-1", repositoryId: "repo-a", path: "/tasks/x/a", branch: "kd/x" },
{ id: "wt-2", sessionId: "s-1", repositoryId: "repo-b", path: "/tasks/x/b", branch: "kd/y" },
];
const options = buildWorktreeOptions(worktrees, [repo("repo-a", "api"), repo("repo-b", "web")]);
expect(options).toEqual([
{ worktreeId: "wt-1", label: "api", branch: "kd/x" },
{ worktreeId: "wt-2", label: "web", branch: "kd/y" },
]);
});
it("falls back to the worktree folder name when the repository is unknown", () => {
const worktrees: Worktree[] = [
{ id: "wt-1", sessionId: "s-1", repositoryId: "missing", path: "/tasks/x/kandev" },
];
const options = buildWorktreeOptions(worktrees, []);
expect(options).toEqual([{ worktreeId: "wt-1", label: "kandev", branch: undefined }]);
});
it("handles Windows-style paths", () => {
const worktrees: Worktree[] = [{ id: "wt-1", sessionId: "s-1", path: "C:\\tasks\\x\\kandev" }];
expect(buildWorktreeOptions(worktrees, [])[0].label).toBe("kandev");
});
it("falls back to a positional label without repository or path", () => {
const worktrees: Worktree[] = [
{ id: "wt-1", sessionId: "s-1" },
{ id: "wt-2", sessionId: "s-1" },
];
const options = buildWorktreeOptions(worktrees, []);
expect(options.map((o) => o.label)).toEqual(["Worktree 1", "Worktree 2"]);
});
});