feat(lanes): auto-setup (profile/agents/mcp) after Add Lane (F5)

This commit is contained in:
2026-08-06 11:49:11 +07:00
parent 5dd4793b88
commit dd0cb4ad7c
3 changed files with 99 additions and 1 deletions
@@ -18,7 +18,15 @@ import type { CwdSuggestion } from "../../../lib/api";
import type { Lane } from "../../../lib/types";
vi.mock("../../../lib/api", () => ({
api: { lanes: { branches: vi.fn(), worktree: vi.fn() } },
api: {
lanes: {
branches: vi.fn(),
worktree: vi.fn(),
profileInit: vi.fn(),
agentsInstall: vi.fn(),
mcpSync: vi.fn(),
},
},
}));
function laneFixture(over: Partial<Lane> = {}): Lane {
@@ -76,6 +84,12 @@ async function focusField(user: ReturnType<typeof userEvent.setup>, el: HTMLElem
beforeEach(() => {
vi.mocked(api.lanes.branches).mockReset();
vi.mocked(api.lanes.worktree).mockReset();
vi.mocked(api.lanes.profileInit).mockResolvedValue({
scaffolded: false,
reason: "no detectable Node.js project",
});
vi.mocked(api.lanes.agentsInstall).mockResolvedValue({ installed: [] });
vi.mocked(api.lanes.mcpSync).mockResolvedValue({ servers: [], profilesSeeded: [] });
});
describe("AddLaneModal", () => {
@@ -169,4 +183,51 @@ describe("AddLaneModal", () => {
renderModal({ open: false });
expect(screen.queryByRole("dialog")).toBeNull();
});
it("fires profileInit, agentsInstall, and mcpSync after a successful worktree call", async () => {
vi.mocked(api.lanes.branches).mockResolvedValue({ branches: ["main"], current: "main" });
vi.mocked(api.lanes.worktree).mockResolvedValue({
lane: { id: 42, title: "demo", cwd: "/lanes/demo", status: "provisioning" } as Lane,
});
const onAdded = vi.fn();
renderModal({ onAdded });
const user = userEvent.setup();
const repoField = screen.getByLabelText("Source repository");
await focusField(user, repoField);
await user.type(repoField, "/Users/tester/projects/repo");
await screen.findByLabelText("Branch to fork from");
await user.type(screen.getByLabelText("Title"), "demo");
await user.click(screen.getByRole("button", { name: "Add lane" }));
await waitFor(() => expect(api.lanes.worktree).toHaveBeenCalled());
await waitFor(() => expect(api.lanes.profileInit).toHaveBeenCalledWith(42));
expect(api.lanes.agentsInstall).toHaveBeenCalledWith(42);
expect(api.lanes.mcpSync).toHaveBeenCalledWith(42);
await waitFor(() => expect(onAdded).toHaveBeenCalled());
});
it("still calls onAdded and closes even when every setup call fails", async () => {
vi.mocked(api.lanes.branches).mockResolvedValue({ branches: ["main"], current: "main" });
vi.mocked(api.lanes.worktree).mockResolvedValue({
lane: { id: 43, title: "demo2", cwd: "/lanes/demo2", status: "provisioning" } as Lane,
});
vi.mocked(api.lanes.profileInit).mockRejectedValue(new Error("boom"));
vi.mocked(api.lanes.agentsInstall).mockRejectedValue(new Error("boom"));
vi.mocked(api.lanes.mcpSync).mockRejectedValue(new Error("boom"));
const onAdded = vi.fn();
const onClose = vi.fn();
renderModal({ onAdded, onClose });
const user = userEvent.setup();
const repoField = screen.getByLabelText("Source repository");
await focusField(user, repoField);
await user.type(repoField, "/Users/tester/projects/repo");
await screen.findByLabelText("Branch to fork from");
await user.type(screen.getByLabelText("Title"), "demo2");
await user.click(screen.getByRole("button", { name: "Add lane" }));
await waitFor(() => expect(onAdded).toHaveBeenCalled());
expect(onClose).toHaveBeenCalled();
});
});