Files
kandev/apps/web/components/task/file-browser-responsive.test.tsx
T

151 lines
5.2 KiB
TypeScript

import type { ReactNode } from "react";
import type { FileTreeNode } from "@/lib/types/backend";
import type * as FileBrowserParts from "./file-browser-parts";
import { cleanup, render, screen } from "@testing-library/react";
import { afterEach, describe, expect, it, vi } from "vitest";
const NODE_ACTIONS_TEST_ID = "file-tree-node-actions";
const { responsive, node } = vi.hoisted(() => ({
responsive: { isMobile: false, isFinePointer: true },
node: { name: "README.md", path: "README.md", is_dir: false, size: 0 },
}));
vi.mock("@/hooks/use-responsive-breakpoint", () => ({
useResponsiveBreakpoint: () => responsive,
}));
vi.mock("@/hooks/domains/session/use-session", () => ({
useSession: () => ({ session: null, isFailed: false, errorMessage: null }),
}));
vi.mock("@/hooks/domains/workspace/use-repository", () => ({ useRepository: () => null }));
vi.mock("@/hooks/domains/session/use-session-git-status", () => ({
useSessionGitStatus: () => null,
}));
vi.mock("@/components/state-provider", () => ({
useAppStore: (selector: (state: unknown) => unknown) =>
selector({
sessionWorktreesBySessionId: { itemsBySessionId: {} },
workspaceFilesRefresh: { bySessionId: {} },
worktrees: { items: {} },
repositories: { itemsByWorkspaceId: {} },
}),
useAppStoreApi: () => ({
getState: () => ({ workspaceRestoration: { byEnvironmentId: {} } }),
}),
}));
vi.mock("@/hooks/use-open-session-folder", () => ({
useOpenSessionFolder: () => ({ open: vi.fn() }),
}));
vi.mock("@/hooks/use-copy-to-clipboard", () => ({
useCopyToClipboard: () => ({ copied: false, copy: vi.fn() }),
}));
vi.mock("@/components/toast-provider", () => ({ useToast: () => ({ toast: vi.fn() }) }));
vi.mock("react-i18next", () => ({ useTranslation: () => ({ t: (key: string) => key }) }));
vi.mock("@/hooks/use-multi-select", () => ({
useMultiSelect: () => ({
selectedPaths: new Set(),
setSelectedPaths: vi.fn(),
isSelected: () => false,
handleClick: () => false,
clearSelection: vi.fn(),
selectAll: vi.fn(),
}),
}));
vi.mock("@/lib/state/context-files-store", () => ({
useContextFilesStore: (selector: (state: { addFile: () => void }) => unknown) =>
selector({ addFile: vi.fn() }),
}));
vi.mock("./file-browser-header", () => ({ FileBrowserHeader: () => null }));
vi.mock("./file-browser-hooks", () => ({
useFileBrowserSearch: () => ({}),
useFileBrowserTree: () => ({
tree: node,
isLoadingTree: false,
loadState: "loaded",
loadError: null,
visibleRows: [],
visibleLoadingPaths: new Set(),
expandedPaths: new Set(),
setTree: vi.fn(),
setExpandedPaths: vi.fn(),
isLoading: false,
loadTree: vi.fn(),
}),
useScrollPersistence: () => {},
loadNodeChildren: vi.fn(),
toggleFolderExpand: vi.fn(),
fetchAndOpenFile: () => Promise.resolve(),
}));
vi.mock("./file-browser-path", () => ({
getFileBrowserSessionWorkspacePath: () => null,
resolveFileBrowserPaths: () => ({ fullPath: "/workspace", displayPath: "workspace" }),
}));
vi.mock("./file-tree-reveal", () => ({ useFileTreeReveal: () => {} }));
vi.mock("./file-tree-editor-menu", () => ({
FileTreeEditorProvider: ({ children }: { children: ReactNode }) => <>{children}</>,
}));
vi.mock("./file-context-menu", () => ({ useFileDeleteAction: () => null }));
vi.mock("./file-browser-content-area", async () => {
const actual = await vi.importActual<typeof FileBrowserParts>("./file-browser-parts");
return {
...actual,
FileBrowserContentArea: ({
showTouchActions,
onAddToChatContext,
}: {
showTouchActions: boolean;
onAddToChatContext: (treeNode: FileTreeNode) => void;
}) => (
<actual.FileTreeNodeTouchActions
node={node}
showTouchActions={showTouchActions}
onAddToChatContext={onAddToChatContext}
/>
),
};
});
import { FileBrowser } from "./file-browser";
function renderFileBrowser() {
render(<FileBrowser sessionId="session-1" onOpenFile={vi.fn()} />);
}
afterEach(() => {
cleanup();
responsive.isMobile = false;
responsive.isFinePointer = true;
});
describe("FileBrowser responsive touch action wiring", () => {
it("releases row-owned confirmations when the file session changes", () => {
responsive.isMobile = true;
const { rerender } = render(<FileBrowser sessionId="session-1" onOpenFile={vi.fn()} />);
const trigger = screen.getByTestId(NODE_ACTIONS_TEST_ID);
rerender(<FileBrowser sessionId="session-2" onOpenFile={vi.fn()} />);
expect(trigger.isConnected).toBe(false);
expect(screen.getByTestId(NODE_ACTIONS_TEST_ID)).not.toBe(trigger);
});
it("renders the 44px action for a coarse-pointer desktop workbench", () => {
responsive.isFinePointer = false;
renderFileBrowser();
const trigger = screen.getByTestId(NODE_ACTIONS_TEST_ID);
expect(trigger.className).toContain("min-h-11");
expect(trigger.className).toContain("min-w-11");
});
it("hides the touch action only for fine-pointer desktop", () => {
renderFileBrowser();
expect(screen.queryByTestId(NODE_ACTIONS_TEST_ID)).toBeNull();
});
it("renders the touch action for mobile", () => {
responsive.isMobile = true;
renderFileBrowser();
expect(screen.getByTestId(NODE_ACTIONS_TEST_ID)).toBeTruthy();
});
});