test: file-tree load more
This commit is contained in:
parent
03aa1a7e7e
commit
e9a2b42d6d
4 changed files with 129 additions and 15 deletions
|
|
@ -3,6 +3,114 @@ import { expect, test, describe } from "bun:test";
|
|||
import { render, screen, fireEvent } from "@testing-library/react";
|
||||
import { FileTree, type FileEntry } from "../file-tree";
|
||||
|
||||
describe("FileTree Pagination", () => {
|
||||
const testFiles: FileEntry[] = [
|
||||
{ name: "root", path: "/root", type: "folder" },
|
||||
{ name: "file1", path: "/root/file1", type: "file" },
|
||||
{ name: "file2", path: "/root/file2", type: "file" },
|
||||
];
|
||||
|
||||
test("shows load more button when hasMore is true", () => {
|
||||
render(
|
||||
<FileTree
|
||||
files={testFiles}
|
||||
expandedFolders={new Set(["/root"])}
|
||||
getFolderPagination={() => ({ hasMore: true, isLoadingMore: false })}
|
||||
/>,
|
||||
);
|
||||
|
||||
expect(screen.getByText("Load more files")).toBeTruthy();
|
||||
});
|
||||
|
||||
test("does not show load more button when hasMore is false", () => {
|
||||
render(
|
||||
<FileTree
|
||||
files={testFiles}
|
||||
expandedFolders={new Set(["/root"])}
|
||||
getFolderPagination={() => ({ hasMore: false, isLoadingMore: false })}
|
||||
/>,
|
||||
);
|
||||
|
||||
expect(screen.queryByText("Load more files")).toBeNull();
|
||||
});
|
||||
|
||||
test("calls onLoadMore with folder path when load more button is clicked", () => {
|
||||
let loadMoreCalled = false;
|
||||
let loadMorePath = "";
|
||||
|
||||
render(
|
||||
<FileTree
|
||||
files={testFiles}
|
||||
expandedFolders={new Set(["/root"])}
|
||||
getFolderPagination={(path) => {
|
||||
if (path === "/root") {
|
||||
return { hasMore: true, isLoadingMore: false };
|
||||
}
|
||||
return { hasMore: false, isLoadingMore: false };
|
||||
}}
|
||||
onLoadMore={(path) => {
|
||||
loadMoreCalled = true;
|
||||
loadMorePath = path;
|
||||
}}
|
||||
/>,
|
||||
);
|
||||
|
||||
const loadMoreButton = screen.getByText("Load more files");
|
||||
fireEvent.click(loadMoreButton);
|
||||
|
||||
expect(loadMoreCalled).toBe(true);
|
||||
expect(loadMorePath).toBe("/root");
|
||||
});
|
||||
|
||||
test("shows loading state when isLoadingMore is true", () => {
|
||||
render(
|
||||
<FileTree
|
||||
files={testFiles}
|
||||
expandedFolders={new Set(["/root"])}
|
||||
getFolderPagination={() => ({ hasMore: true, isLoadingMore: true })}
|
||||
/>,
|
||||
);
|
||||
|
||||
expect(screen.getByText("Loading more...")).toBeTruthy();
|
||||
});
|
||||
|
||||
test("load more button appears for nested folders with hasMore", () => {
|
||||
const nestedFiles: FileEntry[] = [
|
||||
{ name: "root", path: "/root", type: "folder" },
|
||||
{ name: "child", path: "/root/child", type: "folder" },
|
||||
{ name: "file1", path: "/root/child/file1", type: "file" },
|
||||
];
|
||||
|
||||
render(
|
||||
<FileTree
|
||||
files={nestedFiles}
|
||||
expandedFolders={new Set(["/root", "/root/child"])}
|
||||
getFolderPagination={(path) => {
|
||||
if (path === "/root/child") {
|
||||
return { hasMore: true, isLoadingMore: false };
|
||||
}
|
||||
return { hasMore: false, isLoadingMore: false };
|
||||
}}
|
||||
onLoadMore={() => {}}
|
||||
/>,
|
||||
);
|
||||
|
||||
expect(screen.getByText("Load more files")).toBeTruthy();
|
||||
});
|
||||
|
||||
test("load more button does not appear when folder is collapsed", () => {
|
||||
render(
|
||||
<FileTree
|
||||
files={testFiles}
|
||||
expandedFolders={new Set([])}
|
||||
getFolderPagination={() => ({ hasMore: true, isLoadingMore: false })}
|
||||
/>,
|
||||
);
|
||||
|
||||
expect(screen.queryByText("Load more files")).toBeNull();
|
||||
});
|
||||
});
|
||||
|
||||
describe("FileTree Selection Logic", () => {
|
||||
const testFiles: FileEntry[] = [
|
||||
{ name: "root", path: "/root", type: "folder" },
|
||||
|
|
|
|||
|
|
@ -8,7 +8,15 @@
|
|||
* Original source: https://github.com/stackblitz/bolt.new
|
||||
*/
|
||||
|
||||
import { ChevronDown, ChevronRight, File as FileIcon, Folder as FolderIcon, FolderOpen, Loader2, MoreHorizontal } from "lucide-react";
|
||||
import {
|
||||
ChevronDown,
|
||||
ChevronRight,
|
||||
File as FileIcon,
|
||||
Folder as FolderIcon,
|
||||
FolderOpen,
|
||||
Loader2,
|
||||
MoreHorizontal,
|
||||
} from "lucide-react";
|
||||
import { memo, type ReactNode, useCallback, useEffect, useMemo, useState } from "react";
|
||||
import { cn } from "~/client/lib/utils";
|
||||
import { Checkbox } from "~/client/components/ui/checkbox";
|
||||
|
|
@ -304,11 +312,11 @@ export const FileTree = memo((props: Props) => {
|
|||
// Build a map of folder paths that need pagination to their last child's index
|
||||
const folderPaginationMap = useMemo(() => {
|
||||
const map = new Map<string, number>();
|
||||
|
||||
|
||||
for (let i = 0; i < filteredFileList.length; i++) {
|
||||
const item = filteredFileList[i];
|
||||
const parentPath = item.fullPath.slice(0, item.fullPath.lastIndexOf("/")) || "/";
|
||||
|
||||
|
||||
if (parentPath !== "/") {
|
||||
const pagination = getFolderPagination?.(parentPath);
|
||||
if (pagination?.hasMore && !collapsedFolders.has(parentPath)) {
|
||||
|
|
@ -317,7 +325,7 @@ export const FileTree = memo((props: Props) => {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
return map;
|
||||
}, [filteredFileList, getFolderPagination, collapsedFolders]);
|
||||
|
||||
|
|
@ -539,7 +547,13 @@ const LoadMoreButton = memo(({ depth, onClick, isLoading }: LoadMoreButtonProps)
|
|||
<NodeButton
|
||||
depth={depth}
|
||||
className="text-muted-foreground hover:bg-accent/50 cursor-pointer"
|
||||
icon={isLoading ? <Loader2 className="w-4 h-4 shrink-0 animate-spin" /> : <MoreHorizontal className="w-4 h-4 shrink-0" />}
|
||||
icon={
|
||||
isLoading ? (
|
||||
<Loader2 className="w-4 h-4 shrink-0 animate-spin" />
|
||||
) : (
|
||||
<MoreHorizontal className="w-4 h-4 shrink-0" />
|
||||
)
|
||||
}
|
||||
onClick={onClick}
|
||||
>
|
||||
<span className="text-xs">{isLoading ? "Loading more..." : "Load more files"}</span>
|
||||
|
|
|
|||
|
|
@ -297,14 +297,6 @@ const checkHealth = async (idOrShortId: string | number) => {
|
|||
const DEFAULT_PAGE_SIZE = 500;
|
||||
const MAX_PAGE_SIZE = 500;
|
||||
|
||||
interface DirEntry {
|
||||
name: string;
|
||||
path: string;
|
||||
type: "directory" | "file";
|
||||
size?: number;
|
||||
modifiedAt?: number;
|
||||
}
|
||||
|
||||
const listFiles = async (
|
||||
idOrShortId: string | number,
|
||||
subPath?: string,
|
||||
|
|
@ -361,7 +353,7 @@ const listFiles = async (
|
|||
return {
|
||||
name: dirent.name,
|
||||
path: `/${relativePath}`,
|
||||
type: dirent.isDirectory() ? "directory" : "file",
|
||||
type: dirent.isDirectory() ? ("directory" as const) : ("file" as const),
|
||||
size: dirent.isFile() ? stats.size : undefined,
|
||||
modifiedAt: stats.mtimeMs,
|
||||
};
|
||||
|
|
|
|||
|
|
@ -170,4 +170,4 @@ async function main(): Promise<void> {
|
|||
}
|
||||
}
|
||||
|
||||
main();
|
||||
void main();
|
||||
|
|
|
|||
Loading…
Reference in a new issue