import { fireEvent, render, screen, waitFor } from "@testing-library/react"; import { useState } from "react"; import { beforeAll, describe, expect, it, vi } from "vitest"; import { createDefaultWorkspaceUserProjectI18nRuntime } from "@tutti-os/workspace-user-project/i18n"; import { WorkspaceUserProjectSelect } from "@tutti-os/workspace-user-project/ui"; import { AgentGUIActivityHostProvider } from "../../agentActivityHost"; import type { AgentHostInputApi } from "../../host/agentHostApi"; import { AgentProjectDropdown } from "./AgentComposerProjectMenu"; beforeAll(() => { Object.defineProperties(HTMLElement.prototype, { hasPointerCapture: { configurable: true, value: () => false }, releasePointerCapture: { configurable: true, value: () => undefined }, setPointerCapture: { configurable: true, value: () => undefined } }); }); describe("AgentProjectDropdown project selection intent", () => { it("keeps an explicitly unscoped home composer instead of restoring the default project", async () => { const defaultProject = { id: "project-alpha", label: "Alpha", path: "/workspace/alpha", pinnedAtUnixMs: 0 }; const getDefaultSelection = vi.fn(async () => ({ path: defaultProject.path })); const onProjectPathChange = vi.fn(); render( ({ projects: [defaultProject] }) })} > ); await waitFor(() => expect(getDefaultSelection).toHaveBeenCalledTimes(1)); expect(onProjectPathChange).not.toHaveBeenCalled(); expect(screen.getByRole("combobox")).toHaveTextContent("No project"); }); }); describe("WorkspaceUserProjectSelect render budget", () => { it("does not construct project action content while closed", async () => { const renderAddProjectIcon = vi.fn(() => ); const alphaProject = { id: "project-alpha", label: "Alpha", path: "/workspace/alpha", pinnedAtUnixMs: 0 }; const betaProject = { id: "project-beta", label: "Beta", path: "/workspace/beta", pinnedAtUnixMs: 0 }; const onProjectPathChange = vi.fn(); render( alphaProject, list: async () => ({ projects: [alphaProject, betaProject] }) }} renderAddProjectIcon={renderAddProjectIcon} selectedProjectPath={alphaProject.path} shouldApplyPreparedSelection={false} onProjectPathChange={onProjectPathChange} /> ); const trigger = await screen.findByRole("combobox", { name: "Project" }); expect(renderAddProjectIcon).not.toHaveBeenCalled(); fireEvent.pointerDown(trigger, { button: 0, ctrlKey: false, pointerType: "mouse" }); const betaOption = await screen.findByRole("option", { name: "Beta" }); expect(renderAddProjectIcon).toHaveBeenCalledTimes(1); fireEvent.pointerDown(betaOption, { button: 0, ctrlKey: false }); fireEvent.click(betaOption); expect(onProjectPathChange).toHaveBeenCalledWith(betaProject.path, { action: "select_existing" }); await waitFor(() => expect(screen.queryByRole("option", { name: "Beta" })).toBeNull() ); }); it("prepares once for the controlled path after an external project is linked", async () => { const project = { id: "project-alpha", label: "Alpha", path: "/workspace/alpha", pinnedAtUnixMs: 0 }; const linkedProject = { id: "project-beta", label: "Beta", path: "/workspace/beta", pinnedAtUnixMs: 0 }; const prepareSelection = vi.fn(async () => ({ isSelectedPathMissing: false, projects: [project], selection: { kind: "none" as const } })); const useProject = vi.fn(async () => linkedProject); const api = { list: async () => ({ projects: [project] }), prepareSelection, selectDirectory: vi.fn(async () => ({ path: linkedProject.path })), use: useProject }; function ControlledSelect() { const [path, setPath] = useState(null); return ( ); } render(); const trigger = await screen.findByRole("combobox", { name: "Project" }); await waitFor(() => expect(prepareSelection).toHaveBeenCalledTimes(1)); fireEvent.pointerDown(trigger, { button: 0, ctrlKey: false, pointerType: "mouse" }); const option = await screen.findByRole("option", { name: "Use existing project" }); fireEvent.pointerDown(option, { button: 0, ctrlKey: false }); fireEvent.click(option); await waitFor(() => expect(useProject).toHaveBeenCalledTimes(1)); await waitFor(() => expect(prepareSelection).toHaveBeenCalledTimes(2)); }); }); function createAgentHostApi( userProjects: Pick< NonNullable, "getDefaultSelection" | "list" > ): AgentHostInputApi { return { clipboard: { writeText: async () => {} }, filesystem: { readFileText: async () => ({ content: "" }) }, userProjects: { ...userProjects, pin: async () => {}, use: async ({ path }) => ({ id: path, label: path, path, pinnedAtUnixMs: 0 }) }, workspace: { ensureDirectory: async () => {}, readFile: async () => ({ bytes: new Uint8Array() }), selectDirectory: async () => null, selectFiles: async () => [], writeFileText: async () => {} } }; }