fix: move folder action into editor dropdown (#3904)
This commit is contained in:
@@ -0,0 +1,80 @@
|
||||
"use client";
|
||||
|
||||
import { useRef, type ReactNode } from "react";
|
||||
import { IconChevronDown, IconFolderOpen, IconLoader2 } from "@tabler/icons-react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { Button } from "@kandev/ui/button";
|
||||
import {
|
||||
DropdownMenu,
|
||||
DropdownMenuContent,
|
||||
DropdownMenuItem,
|
||||
DropdownMenuSeparator,
|
||||
DropdownMenuTrigger,
|
||||
} from "@kandev/ui/dropdown-menu";
|
||||
import { useTaskFolderAction } from "@/hooks/use-task-folder-action";
|
||||
import { TaskFolderPicker } from "./task-folder-picker";
|
||||
|
||||
export function EditorActionsDropdown({
|
||||
sessionId,
|
||||
children,
|
||||
}: {
|
||||
sessionId: string | null;
|
||||
children: ReactNode;
|
||||
}) {
|
||||
const { t } = useTranslation();
|
||||
const action = useTaskFolderAction(sessionId);
|
||||
const triggerRef = useRef<HTMLButtonElement>(null);
|
||||
const selectedSessionRef = useRef<string | null>(null);
|
||||
|
||||
return (
|
||||
<>
|
||||
<DropdownMenu>
|
||||
<DropdownMenuTrigger asChild>
|
||||
<Button
|
||||
ref={triggerRef}
|
||||
size="sm"
|
||||
variant="outline"
|
||||
className="rounded-none border-0 border-l px-2 cursor-pointer focus-visible:ring-inset"
|
||||
data-testid="editors-menu-list"
|
||||
aria-label={t("task:editorActions")}
|
||||
disabled={!sessionId}
|
||||
>
|
||||
<IconChevronDown className="h-4 w-4" />
|
||||
</Button>
|
||||
</DropdownMenuTrigger>
|
||||
<DropdownMenuContent
|
||||
align="end"
|
||||
onCloseAutoFocus={(event) => {
|
||||
const selectedSession = selectedSessionRef.current;
|
||||
selectedSessionRef.current = null;
|
||||
if (!selectedSession || selectedSession !== sessionId) return;
|
||||
event.preventDefault();
|
||||
triggerRef.current?.focus();
|
||||
action.open(triggerRef.current ?? undefined);
|
||||
}}
|
||||
>
|
||||
{children}
|
||||
<DropdownMenuSeparator />
|
||||
<DropdownMenuItem
|
||||
className="cursor-pointer"
|
||||
disabled={action.disabled}
|
||||
onSelect={() => {
|
||||
selectedSessionRef.current = sessionId;
|
||||
}}
|
||||
>
|
||||
{action.isLoading ? (
|
||||
<IconLoader2 className="size-4 animate-spin" aria-hidden />
|
||||
) : (
|
||||
<IconFolderOpen className="size-4" aria-hidden />
|
||||
)}
|
||||
{t("editors:openFolder")}
|
||||
</DropdownMenuItem>
|
||||
</DropdownMenuContent>
|
||||
</DropdownMenu>
|
||||
<span role="status" className="sr-only">
|
||||
{action.isLoading ? t("editors:openingFolder") : ""}
|
||||
</span>
|
||||
<TaskFolderPicker action={action} />
|
||||
</>
|
||||
);
|
||||
}
|
||||
+97
-22
@@ -1,16 +1,25 @@
|
||||
import { act, cleanup, fireEvent, render, screen, waitFor } from "@testing-library/react";
|
||||
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
|
||||
import { TooltipProvider } from "@kandev/ui/tooltip";
|
||||
import { OpenTaskFolderButton } from "./open-task-folder-button";
|
||||
import { EditorsMenu } from "./editors-menu";
|
||||
import type { EditorOption } from "@/lib/types/http";
|
||||
|
||||
const fixtures = vi.hoisted(() => ({
|
||||
folderOpeningAvailable: true,
|
||||
editors: [] as EditorOption[],
|
||||
openEditor: vi.fn(),
|
||||
worktrees: [] as { id: string; repositoryId: string; path: string; branch: string }[],
|
||||
openSessionFolder: vi.fn(),
|
||||
toast: vi.fn(),
|
||||
}));
|
||||
vi.mock("@/hooks/domains/settings/use-editors", () => ({
|
||||
useEditors: () => ({ folderOpeningAvailable: fixtures.folderOpeningAvailable }),
|
||||
useEditors: () => ({
|
||||
editors: fixtures.editors,
|
||||
folderOpeningAvailable: fixtures.folderOpeningAvailable,
|
||||
}),
|
||||
}));
|
||||
vi.mock("@/hooks/use-open-session-in-editor", () => ({
|
||||
useOpenSessionInEditor: () => ({ open: fixtures.openEditor, isLoading: false }),
|
||||
}));
|
||||
vi.mock("@/lib/api", () => ({ openSessionFolder: fixtures.openSessionFolder }));
|
||||
vi.mock("@/components/toast-provider", () => ({ useToast: () => ({ toast: fixtures.toast }) }));
|
||||
@@ -19,18 +28,22 @@ vi.mock("@/hooks/domains/session/use-session-worktrees", () => ({
|
||||
}));
|
||||
vi.mock("@/components/state-provider", () => ({
|
||||
useAppStore: (select: (state: unknown) => unknown) =>
|
||||
select({ repositories: { itemsByWorkspaceId: {} } }),
|
||||
select({
|
||||
repositories: { itemsByWorkspaceId: {} },
|
||||
userSettings: { defaultEditorId: "editor-1" },
|
||||
}),
|
||||
}));
|
||||
vi.mock("@/hooks/use-responsive-breakpoint", () => ({
|
||||
useResponsiveBreakpoint: () => ({ isMobile: false, isFinePointer: true }),
|
||||
}));
|
||||
|
||||
const OPEN_FOLDER_LABEL = "Open folder";
|
||||
const EDITOR_MENU_TRIGGER_TEST_ID = "editors-menu-list";
|
||||
|
||||
function view(sessionId: string | null) {
|
||||
return (
|
||||
<TooltipProvider>
|
||||
<OpenTaskFolderButton sessionId={sessionId} />
|
||||
<EditorsMenu activeSessionId={sessionId} embeddedVscodeSupported={false} />
|
||||
</TooltipProvider>
|
||||
);
|
||||
}
|
||||
@@ -38,16 +51,30 @@ function view(sessionId: string | null) {
|
||||
beforeEach(() => {
|
||||
vi.clearAllMocks();
|
||||
fixtures.worktrees = [];
|
||||
fixtures.editors = [];
|
||||
fixtures.folderOpeningAvailable = true;
|
||||
fixtures.openSessionFolder.mockResolvedValue({ success: true });
|
||||
});
|
||||
afterEach(cleanup);
|
||||
|
||||
// @covers AC-TASKS-OPEN-FOLDER-001.1, AC-TASKS-OPEN-FOLDER-001.2, AC-TASKS-OPEN-FOLDER-001.3
|
||||
async function openMenu() {
|
||||
fireEvent.keyDown(screen.getByTestId(EDITOR_MENU_TRIGGER_TEST_ID), { key: "ArrowDown" });
|
||||
return screen.findByRole("menu");
|
||||
}
|
||||
async function chooseFolder() {
|
||||
await openMenu();
|
||||
fireEvent.click(screen.getByRole("menuitem", { name: OPEN_FOLDER_LABEL }));
|
||||
await waitFor(() => expect(screen.queryByRole("menu")).toBeNull());
|
||||
}
|
||||
|
||||
describe("task folder action", () => {
|
||||
it("opens without any editor configuration", async () => {
|
||||
render(view("s1"));
|
||||
fireEvent.click(screen.getByRole("button", { name: OPEN_FOLDER_LABEL }));
|
||||
expect((screen.getByTestId(EDITOR_MENU_TRIGGER_TEST_ID) as HTMLButtonElement).disabled).toBe(
|
||||
false,
|
||||
);
|
||||
await chooseFolder();
|
||||
await waitFor(() =>
|
||||
expect(fixtures.openSessionFolder).toHaveBeenCalledWith(
|
||||
"s1",
|
||||
@@ -58,14 +85,14 @@ describe("task folder action", () => {
|
||||
});
|
||||
it("disables opening without a session", () => {
|
||||
render(view(null));
|
||||
expect(
|
||||
(screen.getByRole("button", { name: OPEN_FOLDER_LABEL }) as HTMLButtonElement).disabled,
|
||||
).toBe(true);
|
||||
expect((screen.getByTestId(EDITOR_MENU_TRIGGER_TEST_ID) as HTMLButtonElement).disabled).toBe(
|
||||
true,
|
||||
);
|
||||
});
|
||||
it("sends the only worktree explicitly", async () => {
|
||||
fixtures.worktrees = [{ id: "wt-1", repositoryId: "r1", path: "/repo-one", branch: "main" }];
|
||||
render(view("s1"));
|
||||
fireEvent.click(screen.getByRole("button", { name: OPEN_FOLDER_LABEL }));
|
||||
await chooseFolder();
|
||||
await waitFor(() =>
|
||||
expect(fixtures.openSessionFolder).toHaveBeenCalledWith(
|
||||
"s1",
|
||||
@@ -80,9 +107,9 @@ describe("task folder action", () => {
|
||||
{ id: "wt-2", repositoryId: "r2", path: "/repo-two", branch: "feature" },
|
||||
];
|
||||
render(view("s1"));
|
||||
fireEvent.click(screen.getByRole("button", { name: OPEN_FOLDER_LABEL }));
|
||||
await chooseFolder();
|
||||
expect(fixtures.openSessionFolder).not.toHaveBeenCalled();
|
||||
fireEvent.click(screen.getByRole("button", { name: /repo-two/ }));
|
||||
fireEvent.click(await screen.findByRole("button", { name: /repo-two/ }));
|
||||
await waitFor(() =>
|
||||
expect(fixtures.openSessionFolder).toHaveBeenCalledWith(
|
||||
"s1",
|
||||
@@ -97,8 +124,8 @@ describe("task folder action", () => {
|
||||
{ id: "wt-2", repositoryId: "r2", path: "/repo-two", branch: "feature" },
|
||||
];
|
||||
const { rerender } = render(view("s1"));
|
||||
fireEvent.click(screen.getByRole("button", { name: OPEN_FOLDER_LABEL }));
|
||||
expect(screen.getByRole("dialog")).toBeTruthy();
|
||||
await chooseFolder();
|
||||
expect(await screen.findByRole("dialog")).toBeTruthy();
|
||||
rerender(view("s2"));
|
||||
await waitFor(() => expect(screen.queryByRole("dialog")).toBeNull());
|
||||
expect(fixtures.openSessionFolder).not.toHaveBeenCalled();
|
||||
@@ -111,8 +138,8 @@ describe("task folder action", () => {
|
||||
{ id: "wt-2", repositoryId: "r2", path: "/repo-two", branch: "feature" },
|
||||
];
|
||||
render(view("s1"));
|
||||
fireEvent.click(screen.getByRole("button", { name: OPEN_FOLDER_LABEL }));
|
||||
fireEvent.click(screen.getByRole("button", { name: "Cancel" }));
|
||||
await chooseFolder();
|
||||
fireEvent.click(await screen.findByRole("button", { name: "Cancel" }));
|
||||
await waitFor(() => expect(screen.queryByRole("dialog")).toBeNull());
|
||||
expect(fixtures.openSessionFolder).not.toHaveBeenCalled();
|
||||
});
|
||||
@@ -124,26 +151,74 @@ describe("task folder action", () => {
|
||||
}),
|
||||
);
|
||||
render(view("s1"));
|
||||
const button = screen.getByRole("button", { name: OPEN_FOLDER_LABEL });
|
||||
fireEvent.click(button);
|
||||
expect((button as HTMLButtonElement).disabled).toBe(true);
|
||||
await chooseFolder();
|
||||
await openMenu();
|
||||
expect(
|
||||
screen.getByRole("menuitem", { name: OPEN_FOLDER_LABEL }).getAttribute("aria-disabled"),
|
||||
).toBe("true");
|
||||
await act(async () => {
|
||||
finish({ success: true });
|
||||
});
|
||||
expect((button as HTMLButtonElement).disabled).toBe(false);
|
||||
expect(
|
||||
screen.getByRole("menuitem", { name: OPEN_FOLDER_LABEL }).getAttribute("aria-disabled"),
|
||||
).not.toBe("true");
|
||||
});
|
||||
});
|
||||
|
||||
it("blocks the picker and requests when the host opener is missing", () => {
|
||||
it("blocks the picker and requests when the host opener is missing", async () => {
|
||||
fixtures.folderOpeningAvailable = false;
|
||||
fixtures.worktrees = [
|
||||
{ id: "wt-1", repositoryId: "r1", path: "/one", branch: "main" },
|
||||
{ id: "wt-2", repositoryId: "r2", path: "/two", branch: "main" },
|
||||
];
|
||||
render(view("s1"));
|
||||
const button = screen.getByRole("button", { name: OPEN_FOLDER_LABEL }) as HTMLButtonElement;
|
||||
expect(button.disabled).toBe(true);
|
||||
await openMenu();
|
||||
const button = screen.getByRole("menuitem", { name: OPEN_FOLDER_LABEL });
|
||||
expect(button.getAttribute("aria-disabled")).toBe("true");
|
||||
fireEvent.click(button);
|
||||
expect(screen.queryByRole("dialog")).toBeNull();
|
||||
expect(fixtures.openSessionFolder).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("reports folder errors and permits retry without launching an editor", async () => {
|
||||
fixtures.openSessionFolder.mockRejectedValueOnce(new Error("host failure"));
|
||||
render(view("s1"));
|
||||
await chooseFolder();
|
||||
await waitFor(() =>
|
||||
expect(fixtures.toast).toHaveBeenCalledWith(
|
||||
expect.objectContaining({ title: "Failed to open folder", variant: "error" }),
|
||||
),
|
||||
);
|
||||
await chooseFolder();
|
||||
await waitFor(() => expect(fixtures.openSessionFolder).toHaveBeenCalledTimes(2));
|
||||
expect(fixtures.openEditor).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("keeps editor launching available when the folder opener is unavailable", async () => {
|
||||
fixtures.folderOpeningAvailable = false;
|
||||
fixtures.editors = [
|
||||
{
|
||||
id: "editor-1",
|
||||
type: "editor",
|
||||
name: "Test editor",
|
||||
kind: "custom",
|
||||
installed: true,
|
||||
enabled: true,
|
||||
},
|
||||
];
|
||||
render(view("s1"));
|
||||
fireEvent.click(screen.getByTestId("editors-menu-open"));
|
||||
expect(fixtures.openEditor).toHaveBeenCalledWith({ editorId: "editor-1", worktreeId: undefined });
|
||||
await openMenu();
|
||||
fireEvent.click(screen.getByRole("menuitem", { name: "Test editor" }));
|
||||
expect(fixtures.openEditor).toHaveBeenCalledTimes(2);
|
||||
expect(fixtures.openSessionFolder).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
// @covers AC-TASKS-OPEN-FOLDER-001.1
|
||||
it("announces both editor and folder actions on the dropdown trigger", () => {
|
||||
render(view("s1"));
|
||||
expect(screen.getByRole("button", { name: "Editor and folder actions" })).toBe(
|
||||
screen.getByTestId(EDITOR_MENU_TRIGGER_TEST_ID),
|
||||
);
|
||||
});
|
||||
@@ -1,7 +1,8 @@
|
||||
"use client";
|
||||
|
||||
import { useMemo } from "react";
|
||||
import { IconChevronDown, IconCode, IconLoader2 } from "@tabler/icons-react";
|
||||
import { EditorActionsDropdown } from "./editor-actions-dropdown";
|
||||
import { IconCode, IconLoader2 } from "@tabler/icons-react";
|
||||
import { Button } from "@kandev/ui/button";
|
||||
import {
|
||||
DropdownMenu,
|
||||
@@ -202,33 +203,20 @@ export function EditorsMenu({ activeSessionId, embeddedVscodeSupported }: Editor
|
||||
worktreeOptions={worktreeOptions}
|
||||
onOpen={(worktreeId) => openWith(resolvedEditorId, worktreeId)}
|
||||
/>
|
||||
<DropdownMenu>
|
||||
<DropdownMenuTrigger asChild>
|
||||
<Button
|
||||
size="sm"
|
||||
variant="outline"
|
||||
className="rounded-none border-0 border-l px-2 cursor-pointer focus-visible:ring-inset"
|
||||
data-testid="editors-menu-list"
|
||||
disabled={!activeSessionId || enabledEditors.length === 0}
|
||||
>
|
||||
<IconChevronDown className="h-4 w-4" />
|
||||
</Button>
|
||||
</DropdownMenuTrigger>
|
||||
<DropdownMenuContent align="end">
|
||||
{enabledEditors.length === 0 ? (
|
||||
<DropdownMenuItem disabled>{t("task:noEditorsAvailable")}</DropdownMenuItem>
|
||||
) : (
|
||||
enabledEditors.map((editor: EditorOption) => (
|
||||
<EditorMenuEntry
|
||||
key={editor.id}
|
||||
editor={editor}
|
||||
worktreeOptions={worktreeOptions}
|
||||
onOpen={openWith}
|
||||
/>
|
||||
))
|
||||
)}
|
||||
</DropdownMenuContent>
|
||||
</DropdownMenu>
|
||||
<EditorActionsDropdown sessionId={activeSessionId}>
|
||||
{enabledEditors.length === 0 ? (
|
||||
<DropdownMenuItem disabled>{t("task:noEditorsAvailable")}</DropdownMenuItem>
|
||||
) : (
|
||||
enabledEditors.map((editor: EditorOption) => (
|
||||
<EditorMenuEntry
|
||||
key={editor.id}
|
||||
editor={editor}
|
||||
worktreeOptions={worktreeOptions}
|
||||
onOpen={openWith}
|
||||
/>
|
||||
))
|
||||
)}
|
||||
</EditorActionsDropdown>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,46 +0,0 @@
|
||||
"use client";
|
||||
|
||||
import { IconFolderOpen, IconLoader2 } from "@tabler/icons-react";
|
||||
import { Button } from "@kandev/ui/button";
|
||||
import { Tooltip, TooltipContent, TooltipTrigger } from "@kandev/ui/tooltip";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { useTaskFolderAction } from "@/hooks/use-task-folder-action";
|
||||
import { TaskFolderPicker } from "./task-folder-picker";
|
||||
|
||||
export function OpenTaskFolderButton({ sessionId }: { sessionId: string | null }) {
|
||||
const { t } = useTranslation();
|
||||
const action = useTaskFolderAction(sessionId);
|
||||
return (
|
||||
<>
|
||||
<Tooltip>
|
||||
<TooltipTrigger asChild>
|
||||
<span tabIndex={action.disabled ? 0 : -1} className="inline-flex">
|
||||
<Button
|
||||
variant="outline"
|
||||
size="icon"
|
||||
className="size-7 cursor-pointer"
|
||||
aria-label={t("editors:openFolder")}
|
||||
aria-busy={action.isLoading}
|
||||
data-testid="open-task-folder"
|
||||
disabled={action.disabled}
|
||||
onClick={(event) => action.open(event.currentTarget)}
|
||||
>
|
||||
{action.isLoading ? (
|
||||
<IconLoader2 className="size-4 animate-spin" aria-hidden />
|
||||
) : (
|
||||
<IconFolderOpen className="size-4" aria-hidden />
|
||||
)}
|
||||
</Button>
|
||||
</span>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent>
|
||||
{sessionId ? t("editors:openFolderOnHost") : t("task:selectASessionToOpenIts")}
|
||||
</TooltipContent>
|
||||
</Tooltip>
|
||||
<span role="status" className="sr-only">
|
||||
{action.isLoading ? t("editors:openingFolder") : ""}
|
||||
</span>
|
||||
<TaskFolderPicker action={action} />
|
||||
</>
|
||||
);
|
||||
}
|
||||
@@ -8,7 +8,6 @@ import { Tooltip, TooltipContent, TooltipTrigger } from "@kandev/ui/tooltip";
|
||||
import { PageTopbar, type ParentCrumb } from "@/components/page-topbar";
|
||||
import { useOfficeProject } from "@/hooks/use-office-workspace-data";
|
||||
import { TaskTopBarTitle } from "@/components/task/task-top-bar-title";
|
||||
import { OpenTaskFolderButton } from "@/components/task/open-task-folder-button";
|
||||
import { EditorsMenu } from "@/components/task/editors-menu";
|
||||
import { LayoutPresetSelector } from "@/components/task/layout-preset-selector";
|
||||
import { TaskRightPanelsToggle } from "@/components/task/task-right-panels-toggle";
|
||||
@@ -357,7 +356,6 @@ function TopbarToolsGroup({
|
||||
activeSessionId={activeSessionId ?? null}
|
||||
embeddedVscodeSupported={embeddedVscodeSupported ?? false}
|
||||
/>
|
||||
<OpenTaskFolderButton sessionId={activeSessionId ?? null} />
|
||||
</div>
|
||||
)}
|
||||
{showDebugToggle && (
|
||||
|
||||
@@ -7,7 +7,7 @@ import {
|
||||
import { SessionPage } from "../../pages/session-page";
|
||||
|
||||
// @covers AC-TASKS-OPEN-FOLDER-001.1, AC-TASKS-OPEN-FOLDER-001.3
|
||||
test("task tools open the folder beside the IDE action and recover from errors", async ({
|
||||
test("editor dropdown opens the folder without configured editors and recovers from errors", async ({
|
||||
testPage,
|
||||
apiClient,
|
||||
seedData,
|
||||
@@ -24,7 +24,9 @@ test("task tools open the folder beside the IDE action and recover from errors",
|
||||
executor_profile_id: seedData.worktreeExecutorProfileId,
|
||||
},
|
||||
);
|
||||
await mockFolderAvailability(testPage, true);
|
||||
await testPage.route("**/api/v1/editors", (route) =>
|
||||
route.fulfill({ json: { editors: [], folder_opening_available: true } }),
|
||||
);
|
||||
await testPage.goto(`/t/${task.id}`);
|
||||
const session = new SessionPage(testPage);
|
||||
await session.waitForLoad();
|
||||
@@ -39,22 +41,28 @@ test("task tools open the folder beside the IDE action and recover from errors",
|
||||
json: attempts === 1 ? { error: "failed to open folder" } : { success: true },
|
||||
});
|
||||
});
|
||||
const folder = testPage.getByTestId("open-task-folder");
|
||||
await expect(testPage.getByTestId("open-task-folder")).toHaveCount(0);
|
||||
await expect(testPage.getByTestId("editors-menu-open")).toBeDisabled();
|
||||
const trigger = testPage.getByTestId("editors-menu-list");
|
||||
await expect(trigger).toHaveAccessibleName("Editor and folder actions");
|
||||
await expect(trigger).toBeEnabled();
|
||||
expect((await trigger.boundingBox())!.height).toBeCloseTo(28, 0);
|
||||
await trigger.click();
|
||||
const folder = testPage.getByRole("menuitem", { name: "Open folder", exact: true });
|
||||
await expect(folder).toBeVisible();
|
||||
await expect(folder).toHaveAccessibleName("Open folder");
|
||||
const editorBox = await testPage.getByTestId("editors-menu-list").boundingBox();
|
||||
const folderBox = await folder.boundingBox();
|
||||
expect(folderBox!.height).toBeCloseTo(28, 0);
|
||||
expect(folderBox!.x).toBeGreaterThanOrEqual(editorBox!.x + editorBox!.width);
|
||||
expect(folderBox!.x - editorBox!.x - editorBox!.width).toBeLessThan(20);
|
||||
await testPage.screenshot({ path: test.info().outputPath("desktop-folder-shortcut.png") });
|
||||
await testPage.screenshot({
|
||||
path: test.info().outputPath("desktop-folder-menu.png"),
|
||||
animations: "disabled",
|
||||
});
|
||||
await folder.click();
|
||||
await expect(testPage.getByText("Failed to open folder", { exact: true })).toBeVisible();
|
||||
await trigger.click();
|
||||
await folder.click();
|
||||
await expect.poll(() => attempts).toBe(2);
|
||||
expect(
|
||||
requests.every((url) => url.endsWith(`/task-sessions/${task.session_id}/open-folder`)),
|
||||
).toBe(true);
|
||||
await trigger.click();
|
||||
await expect(folder).toBeEnabled();
|
||||
});
|
||||
|
||||
@@ -99,17 +107,23 @@ test("desktop folder picker opens only the selected repository", async ({
|
||||
payloads.push(route.request().postDataJSON());
|
||||
await route.fulfill({ json: { success: true } });
|
||||
});
|
||||
const trigger = testPage.getByTestId("open-task-folder");
|
||||
const trigger = testPage.getByTestId("editors-menu-list");
|
||||
await trigger.focus();
|
||||
await testPage.keyboard.press("Enter");
|
||||
await testPage.getByRole("menuitem", { name: "Open folder", exact: true }).focus();
|
||||
await testPage.keyboard.press("Enter");
|
||||
const picker = testPage.getByRole("dialog", { name: "Choose a folder" });
|
||||
await expect(picker).toBeVisible();
|
||||
await testPage.screenshot({ path: test.info().outputPath("desktop-folder-picker.png") });
|
||||
await testPage.screenshot({
|
||||
path: test.info().outputPath("desktop-folder-picker.png"),
|
||||
animations: "disabled",
|
||||
});
|
||||
await picker.getByRole("button", { name: "Cancel", exact: true }).click();
|
||||
await expect(picker).not.toBeVisible();
|
||||
await expect(trigger).toBeFocused();
|
||||
expect(payloads).toEqual([]);
|
||||
await trigger.click();
|
||||
await testPage.getByRole("menuitem", { name: "Open folder", exact: true }).click();
|
||||
await picker.getByRole("button", { name: /Folder second repo/ }).click();
|
||||
await expect.poll(() => payloads).toEqual([{ worktree_id: worktreeId }]);
|
||||
await expect(picker).not.toBeVisible();
|
||||
@@ -142,7 +156,8 @@ test("missing host folder opener disables the action before any picker opens", a
|
||||
const session = new SessionPage(testPage);
|
||||
await session.waitForLoad();
|
||||
await session.waitForChatIdle();
|
||||
await expect(testPage.getByTestId("open-task-folder")).toBeDisabled();
|
||||
await testPage.getByTestId("editors-menu-list").click();
|
||||
await expect(testPage.getByRole("menuitem", { name: "Open folder", exact: true })).toBeDisabled();
|
||||
await expect(testPage.getByRole("dialog", { name: "Choose a folder" })).not.toBeVisible();
|
||||
expect(requests).toBe(0);
|
||||
});
|
||||
|
||||
@@ -2373,5 +2373,6 @@
|
||||
"checkoutOptions.credentialsUnsupported": "Use Kandev-managed Git credentials for advanced checkout options.",
|
||||
"toolPayloadRemovedAt": "Tool details removed on {{date}}.",
|
||||
"toolPayloadRemoved": "Tool details removed.",
|
||||
"openTaskSwitcherFor": "Open task switcher: {{title}}"
|
||||
"openTaskSwitcherFor": "Open task switcher: {{title}}",
|
||||
"editorActions": "Editor and folder actions"
|
||||
}
|
||||
|
||||
@@ -2373,5 +2373,6 @@
|
||||
"checkoutOptions.credentialsUnsupported": "高度なチェックアウトオプションには、Kandev 管理の Git 認証情報を使って。",
|
||||
"toolPayloadRemovedAt": "ツール詳細は {{date}} に削除済み。",
|
||||
"toolPayloadRemoved": "ツール詳細は削除済み。",
|
||||
"openTaskSwitcherFor": "タスク切替を開く: {{title}}"
|
||||
"openTaskSwitcherFor": "タスク切替を開く: {{title}}",
|
||||
"editorActions": "エディターとフォルダーの操作"
|
||||
}
|
||||
|
||||
@@ -2373,5 +2373,6 @@
|
||||
"checkoutOptions.credentialsUnsupported": "Ũśē Ķàńďēv-ḿàńàĝēď Ĝĩţ ćŕēďēńţĩàĺś ƒōŕ àďvàńćēď ćĥēćķōũţ ōƥţĩōńś.",
|
||||
"toolPayloadRemovedAt": "Ţōōĺ ďēţàĩĺś ŕēḿōvēď ōń {{date}}.",
|
||||
"toolPayloadRemoved": "Ţōōĺ ďēţàĩĺś ŕēḿōvēď.",
|
||||
"openTaskSwitcherFor": "Ōƥēń ţàśķ śŵĩţćĥēŕ: {{title}}"
|
||||
"openTaskSwitcherFor": "Ōƥēń ţàśķ śŵĩţćĥēŕ: {{title}}",
|
||||
"editorActions": "Ēďĩţōŕ àńď ƒōĺďēŕ àćţĩōńś"
|
||||
}
|
||||
|
||||
@@ -2373,5 +2373,6 @@
|
||||
"checkoutOptions.credentialsUnsupported": "Utilize credenciais Git geridas pelo Kandev para as opções avançadas de obtenção.",
|
||||
"toolPayloadRemovedAt": "Detalhes da ferramenta removidos em {{date}}.",
|
||||
"toolPayloadRemoved": "Detalhes da ferramenta removidos.",
|
||||
"openTaskSwitcherFor": "Abrir seletor de tarefas: {{title}}"
|
||||
"openTaskSwitcherFor": "Abrir seletor de tarefas: {{title}}",
|
||||
"editorActions": "Ações do editor e da pasta"
|
||||
}
|
||||
|
||||
@@ -2373,5 +2373,6 @@
|
||||
"checkoutOptions.credentialsUnsupported": "请使用由 Kandev 管理的 Git 凭据启用高级检出选项。",
|
||||
"toolPayloadRemovedAt": "工具详细数据已于 {{date}} 删除。",
|
||||
"toolPayloadRemoved": "工具详细数据已删除。",
|
||||
"openTaskSwitcherFor": "打开任务切换器:{{title}}"
|
||||
"openTaskSwitcherFor": "打开任务切换器:{{title}}",
|
||||
"editorActions": "编辑器和文件夹操作"
|
||||
}
|
||||
|
||||
@@ -2373,5 +2373,6 @@
|
||||
"checkoutOptions.credentialsUnsupported": "請使用由 Kandev 管理的 Git 認證資料啓用高級檢出選項。",
|
||||
"toolPayloadRemovedAt": "工具詳細數據已於 {{date}} 刪除。",
|
||||
"toolPayloadRemoved": "工具詳細數據已刪除。",
|
||||
"openTaskSwitcherFor": "開啟任務切換器:{{title}}"
|
||||
"openTaskSwitcherFor": "開啟任務切換器:{{title}}",
|
||||
"editorActions": "編輯器和資料夾操作"
|
||||
}
|
||||
|
||||
@@ -2373,5 +2373,6 @@
|
||||
"checkoutOptions.credentialsUnsupported": "請使用由 Kandev 管理的 Git 認證資訊啟用高階檢出選項。",
|
||||
"toolPayloadRemovedAt": "工具詳細資料已於 {{date}} 刪除。",
|
||||
"toolPayloadRemoved": "工具詳細資料已刪除。",
|
||||
"openTaskSwitcherFor": "開啟任務切換器:{{title}}"
|
||||
"openTaskSwitcherFor": "開啟任務切換器:{{title}}",
|
||||
"editorActions": "編輯器和資料夾操作"
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user