fix: label file selections and distinguish pending trash
Co-Authored-By: Paperclip <noreply@paperclip.ing>
This commit is contained in:
parent
a72fd32191
commit
90cad7196b
|
|
@ -85,6 +85,8 @@ describe("FileTree", () => {
|
|||
const input = row("docs")?.querySelector("input[type='checkbox']") as HTMLInputElement | null;
|
||||
expect(input?.checked).toBe(false);
|
||||
expect(input?.indeterminate).toBe(true);
|
||||
expect(input?.getAttribute("aria-label")).toBe("Select docs");
|
||||
expect(row("docs/a.md")?.querySelector("input")?.getAttribute("aria-label")).toBe("Select docs/a.md");
|
||||
expect(row("docs")?.getAttribute("aria-checked")).toBe("mixed");
|
||||
});
|
||||
|
||||
|
|
|
|||
|
|
@ -439,6 +439,7 @@ export function FileTree({
|
|||
<label className="flex items-center pl-2" onClick={(event) => event.stopPropagation()}>
|
||||
<input
|
||||
type="checkbox"
|
||||
aria-label={`Select ${node.path}`}
|
||||
checked={allChecked}
|
||||
ref={(element) => {
|
||||
if (element) element.indeterminate = someChecked;
|
||||
|
|
|
|||
|
|
@ -52,6 +52,20 @@ afterEach(async () => {
|
|||
await act(async () => root.unmount()); client.clear(); container.remove(); vi.clearAllMocks();
|
||||
});
|
||||
describe("cached file selection and retained trash", () => {
|
||||
it("does not report empty trash or no saved files while loading or after a list failure", async () => {
|
||||
let rejectList!: (error: Error) => void;
|
||||
api.list.mockImplementationOnce(() => new Promise((_resolve, reject) => { rejectList = reject; }));
|
||||
await click(button("Trash")!);
|
||||
expect(container.textContent).toContain("Loading trash…");
|
||||
expect(container.textContent).not.toContain("Trash is empty.");
|
||||
expect(container.textContent).not.toContain("No saved files");
|
||||
await act(async () => { rejectList(new Error("Storage unavailable")); });
|
||||
await settle();
|
||||
expect(container.textContent).toContain("Trash could not be loaded.");
|
||||
expect(container.textContent).toContain("Storage unavailable");
|
||||
expect(container.textContent).not.toContain("Trash is empty.");
|
||||
expect(container.textContent).not.toContain("No saved files");
|
||||
});
|
||||
it("does not flash an old preview error while retrying a cached file", async () => {
|
||||
const row = (path: string) => container.querySelector<HTMLElement>(`[data-file-tree-path="${path}"]`)!;
|
||||
await click(row("a.txt"));
|
||||
|
|
|
|||
|
|
@ -82,6 +82,8 @@ export function WorkFolderBrowser({ owner, exampleFiles, readOnly = false, fillH
|
|||
: saving ? "Saving…"
|
||||
: failures.length > 0 ? "Run save failed"
|
||||
: !lastSaved && statuses.some((status) => status.active) ? "Waiting for first save"
|
||||
: !lastSaved && !exampleFiles && filesQuery.isPending ? "Loading files…"
|
||||
: !lastSaved && filesQuery.isError && !filesQuery.data ? "File list unavailable"
|
||||
: lastSaved || lastOperation || files.length > 0 ? "Saved"
|
||||
: "No saved files";
|
||||
const disabled = mutation.isPending || Boolean(exampleFiles);
|
||||
|
|
@ -117,7 +119,7 @@ export function WorkFolderBrowser({ owner, exampleFiles, readOnly = false, fillH
|
|||
</ul>
|
||||
</div>}
|
||||
<p className="sr-only" aria-live="polite">{announcement}</p>
|
||||
{trash ? <TabsContent value="trash" className={cn("overflow-auto", fillHeight ? "min-h-0 flex-1" : "max-h-96")}><p className="mb-3 text-sm text-muted-foreground">Deleted cached files are retained here. Restore them to return them to Files.</p>{files.length === 0 ? <p className="text-sm text-muted-foreground">Trash is empty.</p> : files.map((file) => <div key={file.id} className="flex items-center gap-2 border-b py-2">
|
||||
{trash ? <TabsContent value="trash" className={cn("overflow-auto", fillHeight ? "min-h-0 flex-1" : "max-h-96")}><p className="mb-3 text-sm text-muted-foreground">Deleted cached files are retained here. Restore them to return them to Files.</p>{!exampleFiles && filesQuery.isPending ? <p role="status" className="text-sm text-muted-foreground">Loading trash…</p> : filesQuery.isError && !filesQuery.data ? <p className="text-sm text-muted-foreground">Trash could not be loaded.</p> : files.length === 0 ? <p className="text-sm text-muted-foreground">Trash is empty.</p> : files.map((file) => <div key={file.id} className="flex items-center gap-2 border-b py-2">
|
||||
<span className="min-w-0 flex-1 truncate text-sm">{file.path}</span>{canManageTrash && <Button size="sm" variant="outline" disabled={disabled} onClick={() => mutation.mutate({ type: "restore", fileId: file.id })}><RotateCcw aria-hidden />Restore</Button>}
|
||||
{!readOnly && <AlertDialog><AlertDialogTrigger asChild><Button size="sm" variant="ghost" disabled={disabled}>Purge…</Button></AlertDialogTrigger>
|
||||
<AlertDialogContent><AlertDialogHeader><AlertDialogTitle>Permanently delete {file.path}?</AlertDialogTitle>
|
||||
|
|
|
|||
Loading…
Reference in New Issue