Files
kandev/apps/web/hooks/use-open-session-in-editor.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

67 lines
2.0 KiB
TypeScript

import { describe, it, expect, vi, beforeEach } from "vitest";
import { renderHook } from "@testing-library/react";
const mockOpenSessionInEditor = vi.fn();
const mockOpenFileInVscode = vi.fn();
const mockOpenInternalVscode = vi.fn();
vi.mock("@/lib/api", () => ({
openSessionInEditor: (...args: unknown[]) => mockOpenSessionInEditor(...args),
}));
vi.mock("@/lib/api/domains/vscode-api", () => ({
openFileInVscode: (...args: unknown[]) => mockOpenFileInVscode(...args),
}));
vi.mock("@/lib/state/dockview-store", () => ({
useDockviewStore: {
getState: () => ({ openInternalVscode: mockOpenInternalVscode }),
},
}));
vi.mock("@/lib/http/use-request", () => ({
useRequest: (fn: (options?: unknown) => Promise<unknown>) => ({
run: fn,
status: "idle",
isLoading: false,
}),
}));
import { useOpenSessionInEditor } from "./use-open-session-in-editor";
describe("useOpenSessionInEditor", () => {
beforeEach(() => {
vi.clearAllMocks();
mockOpenSessionInEditor.mockResolvedValue({});
});
it("forwards the selected worktree as worktree_id", async () => {
const { result } = renderHook(() => useOpenSessionInEditor("session-1"));
await result.current.open({ editorId: "editor-1", worktreeId: "wt-2" });
expect(mockOpenSessionInEditor).toHaveBeenCalledWith(
"session-1",
expect.objectContaining({ editor_id: "editor-1", worktree_id: "wt-2" }),
expect.anything(),
);
});
it("omits worktree_id when no worktree is selected", async () => {
const { result } = renderHook(() => useOpenSessionInEditor("session-1"));
await result.current.open({ editorId: "editor-1" });
const payload = mockOpenSessionInEditor.mock.calls[0][1] as Record<string, unknown>;
expect(payload.worktree_id).toBeUndefined();
});
it("does not call the API without a session id", async () => {
const { result } = renderHook(() => useOpenSessionInEditor(null));
await result.current.open({ editorId: "editor-1", worktreeId: "wt-2" });
expect(mockOpenSessionInEditor).not.toHaveBeenCalled();
});
});