diff --git a/ui/src/components/WorkFolderBrowser.selection.test.tsx b/ui/src/components/WorkFolderBrowser.selection.test.tsx index dd3e5baa43..6ce77a3971 100644 --- a/ui/src/components/WorkFolderBrowser.selection.test.tsx +++ b/ui/src/components/WorkFolderBrowser.selection.test.tsx @@ -99,13 +99,13 @@ describe("cached file selection and retained trash", () => { expect(checkbox("a.txt")).not.toBeNull(); expect(deleted).toEqual([]); }); - it("refreshes partial successes and leaves only failed files selected for retry", async () => { + it.each([["a.txt", "b.txt"], ["b.txt", "a.txt"]])("refreshes partial successes when selected in order %s, %s", async (first, second) => { const operate = api.operation.getMockImplementation()!; api.operation.mockImplementation(async (target, operation) => { if (operation.path === "b.txt") throw new Error("Storage unavailable"); return operate(target, operation); }); - await click(checkbox("a.txt")); await click(checkbox("b.txt")); + await click(checkbox(first)); await click(checkbox(second)); await click(button("Move 2 files to trash")!); expect(checkbox("a.txt")).toBeNull(); expect(checkbox("b.txt").checked).toBe(true); diff --git a/ui/src/components/WorkFolderBrowser.tsx b/ui/src/components/WorkFolderBrowser.tsx index f6015f9ce2..5ab993f68e 100644 --- a/ui/src/components/WorkFolderBrowser.tsx +++ b/ui/src/components/WorkFolderBrowser.tsx @@ -69,11 +69,17 @@ export function WorkFolderBrowser({ owner, exampleFiles, readOnly = false, fillH if (action.type === "upload") for (const file of action.files) await workFoldersApi.upload(owner, file, directory ? `${directory}/${file.name}` : file.name, crypto.randomUUID()); else if (action.type === "mkdir") { await workFoldersApi.operation(owner, { action: "mkdir", path: directory }, crypto.randomUUID()); setExpanded((before) => new Set([...before, directory])); } else if (action.type === "deleteSelected") { + const failures: string[] = []; for (const path of action.paths) { - await workFoldersApi.operation(owner, { action: "delete", path }, crypto.randomUUID()); - setCheckedFiles((before) => new Set([...before].filter((candidate) => candidate !== path && !candidate.startsWith(`${path}/`)))); - setSelectedPath((before) => before === path || before?.startsWith(`${path}/`) ? null : before); + try { + await workFoldersApi.operation(owner, { action: "delete", path }, crypto.randomUUID()); + setCheckedFiles((before) => new Set([...before].filter((candidate) => candidate !== path && !candidate.startsWith(`${path}/`)))); + setSelectedPath((before) => before === path || before?.startsWith(`${path}/`) ? null : before); + } catch (error) { + failures.push(`${path}: ${error instanceof Error ? error.message : "Could not move to trash"}`); + } } + if (failures.length > 0) throw new Error(failures.join("; ")); } else if (action.type === "restore" || action.type === "purge") await workFoldersApi.operation(owner, { action: action.type, fileId: action.fileId }, crypto.randomUUID()); else for (const run of (syncQuery.data ?? []).filter((run) => run.active)) await workFoldersApi.refresh(owner, run.runId);