From add0f2788faa79450a613480f9603b2ca4bd72ce Mon Sep 17 00:00:00 2001 From: Nicolas Meienberger Date: Tue, 31 Mar 2026 23:05:19 +0200 Subject: [PATCH] fix: restoring snapshots that have unrelated root paths --- .../components/__test__/restore-form.test.tsx | 47 +++++++++++++++++++ .../__tests__/snapshot-tree-browser.test.tsx | 27 +++++++++++ .../file-browsers/snapshot-tree-browser.tsx | 7 ++- 3 files changed, 79 insertions(+), 2 deletions(-) diff --git a/app/client/components/__test__/restore-form.test.tsx b/app/client/components/__test__/restore-form.test.tsx index 3638f1a5..463162ef 100644 --- a/app/client/components/__test__/restore-form.test.tsx +++ b/app/client/components/__test__/restore-form.test.tsx @@ -75,4 +75,51 @@ describe("RestoreForm", () => { }); }); }); + + test("restores the selected full path when the display root is unrelated", async () => { + let restoreRequestBody: unknown; + + server.use( + http.get("/api/v1/repositories/:shortId/snapshots/:snapshotId/files", () => { + return HttpResponse.json({ + files: [ + { name: "project", path: "/mnt/project", type: "dir" }, + { name: "a.txt", path: "/mnt/project/a.txt", type: "file" }, + ], + }); + }), + http.post("/api/v1/repositories/:shortId/restore", async ({ request }) => { + restoreRequestBody = await request.json(); + return HttpResponse.json({ + success: true, + message: "Snapshot restored successfully", + filesRestored: 1, + filesSkipped: 0, + }); + }), + ); + + render( + , + ); + + const row = await screen.findByRole("button", { name: "mnt" }); + await userEvent.click(within(row).getByRole("checkbox")); + await userEvent.click(screen.getByRole("button", { name: "Restore 1 item" })); + + await waitFor(() => { + expect(restoreRequestBody).toEqual({ + snapshotId: "snap-1", + include: ["/mnt"], + selectedItemKind: "dir", + overwrite: "always", + }); + }); + }); }); diff --git a/app/client/components/file-browsers/__tests__/snapshot-tree-browser.test.tsx b/app/client/components/file-browsers/__tests__/snapshot-tree-browser.test.tsx index 3e8fc202..8fc91c01 100644 --- a/app/client/components/file-browsers/__tests__/snapshot-tree-browser.test.tsx +++ b/app/client/components/file-browsers/__tests__/snapshot-tree-browser.test.tsx @@ -134,6 +134,33 @@ describe("SnapshotTreeBrowser", () => { expect(selectedKind === "dir").toBe(true); }); + test("keeps full paths when the display root does not contain the query root", async () => { + mockListSnapshotFiles(); + + let selectedPaths: Set | undefined; + let selectedKind: "file" | "dir" | null = null; + + renderSnapshotTreeBrowser({ + queryBasePath: "/mnt/project", + displayBasePath: "/other/root", + withCheckboxes: true, + onSelectionChange: (paths) => { + selectedPaths = paths; + }, + onSingleSelectionKindChange: (kind) => { + selectedKind = kind; + }, + }); + + const row = await screen.findByRole("button", { name: "mnt" }); + const checkbox = within(row).getByRole("checkbox"); + + await userEvent.click(checkbox); + + expect(selectedPaths ? Array.from(selectedPaths) : []).toEqual(["/mnt"]); + expect(selectedKind === "dir").toBe(true); + }); + test("shows selected folder state when full paths are provided from the parent", async () => { mockListSnapshotFiles(); diff --git a/app/client/components/file-browsers/snapshot-tree-browser.tsx b/app/client/components/file-browsers/snapshot-tree-browser.tsx index 8a764415..6491e45f 100644 --- a/app/client/components/file-browsers/snapshot-tree-browser.tsx +++ b/app/client/components/file-browsers/snapshot-tree-browser.tsx @@ -4,7 +4,7 @@ import { listSnapshotFilesOptions } from "~/client/api-client/@tanstack/react-qu import { FileBrowser, type FileBrowserUiProps } from "~/client/components/file-browsers/file-browser"; import { useFileBrowser } from "~/client/hooks/use-file-browser"; import { parseError } from "~/client/lib/errors"; -import { normalizeAbsolutePath } from "@zerobyte/core/utils"; +import { isPathWithin, normalizeAbsolutePath } from "@zerobyte/core/utils"; import { logger } from "~/client/lib/logger"; function createPathPrefixFns(basePath: string) { @@ -48,6 +48,9 @@ export const SnapshotTreeBrowser = (props: SnapshotTreeBrowserProps) => { const queryClient = useQueryClient(); const normalizedQueryBasePath = normalizeAbsolutePath(queryBasePath); const normalizedDisplayBasePath = normalizeAbsolutePath(displayBasePath ?? "/"); + const effectiveDisplayBasePath = isPathWithin(normalizedDisplayBasePath, normalizedQueryBasePath) + ? normalizedDisplayBasePath + : "/"; const { data, isLoading, error } = useQuery({ ...listSnapshotFilesOptions({ @@ -57,7 +60,7 @@ export const SnapshotTreeBrowser = (props: SnapshotTreeBrowserProps) => { enabled, }); - const displayPathFns = useMemo(() => createPathPrefixFns(normalizedDisplayBasePath), [normalizedDisplayBasePath]); + const displayPathFns = useMemo(() => createPathPrefixFns(effectiveDisplayBasePath), [effectiveDisplayBasePath]); const displaySelectedPaths = useMemo(() => { if (!selectedPaths) return undefined;