mirror of
https://github.com/DeusData/codebase-memory-mcp.git
synced 2026-10-26 06:43:15 +08:00
fix(graph-ui): navigate Windows drive breadcrumbs to real paths
The index file picker built breadcrumb targets as '/' + segments, so on a Windows drive path (C:/Users/rap) clicking a crumb browsed to '/C:/...', which the backend rejected as 'not a directory'. Only the '.. (up)' button worked. Build drive-aware crumb targets (C:/, C:/Users) and drop the bogus unified '/' root crumb on Windows drive paths; POSIX behavior is unchanged. Add a regression test for Windows breadcrumb navigation and cleanup() for test isolation. Signed-off-by: Zadak <rarepops@protonmail.com>
This commit is contained in:
@@ -1,6 +1,6 @@
|
||||
/* @vitest-environment jsdom */
|
||||
import "@testing-library/jest-dom/vitest";
|
||||
import { fireEvent, render, screen, waitFor, act } from "@testing-library/react";
|
||||
import { cleanup, fireEvent, render, screen, waitFor, act } from "@testing-library/react";
|
||||
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
|
||||
import { StatsTab, IndexProgress } from "./StatsTab";
|
||||
import { messages } from "../lib/i18n";
|
||||
@@ -43,6 +43,7 @@ function mockProjectsFetch(extra?: (url: string, init?: RequestInit) => Response
|
||||
|
||||
describe("StatsTab index modal", () => {
|
||||
afterEach(() => {
|
||||
cleanup();
|
||||
vi.unstubAllGlobals();
|
||||
});
|
||||
|
||||
@@ -93,6 +94,39 @@ describe("StatsTab index modal", () => {
|
||||
expect(screen.getByRole("button", { name: "Index beta" })).toBeInTheDocument();
|
||||
expect(screen.getByRole("button", { name: "Browse D:/" })).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it("navigates Windows breadcrumb segments to real drive paths", async () => {
|
||||
const fetchMock = mockProjectsFetch((url) => {
|
||||
if (url.startsWith("/api/browse")) {
|
||||
return new Response(JSON.stringify({
|
||||
path: "C:/Users/rap",
|
||||
parent: "C:/Users",
|
||||
dirs: ["Documents", "Downloads"],
|
||||
roots: ["C:/", "D:/"],
|
||||
}), { status: 200, headers: { "Content-Type": "application/json" } });
|
||||
}
|
||||
return undefined;
|
||||
});
|
||||
|
||||
render(<StatsTab onSelectProject={() => {}} />);
|
||||
fireEvent.click(await screen.findByRole("button", { name: "Index your first repository" }));
|
||||
|
||||
/* No bogus unified "/" root crumb on a Windows drive path. */
|
||||
await screen.findByRole("button", { name: "C:" });
|
||||
expect(screen.queryByRole("button", { name: "/" })).not.toBeInTheDocument();
|
||||
|
||||
/* Clicking the drive crumb browses to "C:/", not "/C:". */
|
||||
fireEvent.click(screen.getByRole("button", { name: "C:" }));
|
||||
await waitFor(() => {
|
||||
expect(fetchMock).toHaveBeenCalledWith("/api/browse?path=C%3A%2F");
|
||||
});
|
||||
|
||||
/* Clicking a nested crumb browses to "C:/Users", not "/C:/Users". */
|
||||
fireEvent.click(screen.getByRole("button", { name: "Users" }));
|
||||
await waitFor(() => {
|
||||
expect(fetchMock).toHaveBeenCalledWith("/api/browse?path=C%3A%2FUsers");
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe("IndexProgress", () => {
|
||||
|
||||
@@ -239,6 +239,16 @@ function CreateIndexModal({ onClose, onCreated }: { onClose: () => void; onCreat
|
||||
/* Breadcrumb segments */
|
||||
const displayPath = currentPath.replace(/\\/g, "/");
|
||||
const segments = displayPath.split("/").filter(Boolean);
|
||||
/* A Windows drive path ("C:/Users/rap") has no unified "/" root — its first
|
||||
* segment is the drive letter. Build crumb targets accordingly so clicking a
|
||||
* segment navigates to a real directory instead of a bogus "/C:/..." path
|
||||
* that the backend rejects as "not a directory". */
|
||||
const isWinPath = /^[A-Za-z]:$/.test(segments[0] ?? "");
|
||||
const crumbPath = (i: number): string => {
|
||||
const parts = segments.slice(0, i + 1);
|
||||
if (isWinPath) return parts.length === 1 ? `${parts[0]}/` : parts.join("/");
|
||||
return "/" + parts.join("/");
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="fixed inset-0 z-50 flex items-center justify-center" onClick={onClose}>
|
||||
@@ -297,12 +307,14 @@ function CreateIndexModal({ onClose, onCreated }: { onClose: () => void; onCreat
|
||||
|
||||
{/* Breadcrumb */}
|
||||
<div className="px-5 py-2 border-y border-border/20 flex items-center gap-0.5 overflow-x-auto text-[11px] shrink-0">
|
||||
<button onClick={() => browse("/")} className="text-primary/60 hover:text-primary shrink-0 transition-colors">/</button>
|
||||
{!isWinPath && (
|
||||
<button onClick={() => browse("/")} className="text-primary/60 hover:text-primary shrink-0 transition-colors">/</button>
|
||||
)}
|
||||
{segments.map((seg, i) => (
|
||||
<span key={i} className="flex items-center gap-0.5 shrink-0">
|
||||
<span className="text-foreground/15">/</span>
|
||||
{(i > 0 || !isWinPath) && <span className="text-foreground/15">/</span>}
|
||||
<button
|
||||
onClick={() => browse("/" + segments.slice(0, i + 1).join("/"))}
|
||||
onClick={() => browse(crumbPath(i))}
|
||||
className={`transition-colors ${i === segments.length - 1 ? "text-foreground/70 font-medium" : "text-primary/50 hover:text-primary"}`}
|
||||
>
|
||||
{seg}
|
||||
|
||||
Reference in New Issue
Block a user