fix: show loading while retrying cached file previews

Co-Authored-By: Paperclip <noreply@paperclip.ing>
This commit is contained in:
Dotta 2026-09-08 18:58:02 -05:00
parent 14151ff69f
commit 7fb72800ab
2 changed files with 23 additions and 2 deletions

View File

@ -6,8 +6,9 @@ import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
import { TooltipProvider } from "@/components/ui/tooltip";
import { WorkFolderBrowser } from "./WorkFolderBrowser";
const api = vi.hoisted(() => ({ list: vi.fn(), sync: vi.fn(), operation: vi.fn() }));
const api = vi.hoisted(() => ({ list: vi.fn(), sync: vi.fn(), operation: vi.fn(), preview: vi.fn(), downloadUrl: vi.fn() }));
vi.mock("@/api/work-folders", () => ({ workFoldersApi: api }));
vi.mock("@/components/FileViewerSheet", () => ({ FileContentViewer: ({ content }: { content: { content: { data: string } } }) => <pre>{content.content.data}</pre> }));
const owner = { companyId: "company", scope: "task" as const, ownerId: "task" };
const a = { id: "a", path: "a.txt", kind: "file" };
const b = { id: "b", path: "b.txt", kind: "file" };
@ -29,6 +30,8 @@ beforeEach(async () => {
active = [a, b]; deleted = [];
api.list.mockImplementation(async (_owner, trash) => ({ files: [...(trash ? deleted : active)] }));
api.sync.mockResolvedValue([]);
api.preview.mockImplementation(async (_owner, file) => ({ content: { data: `contents of ${file.path}` } }));
api.downloadUrl.mockReturnValue("/download");
api.operation.mockImplementation(async (_owner, operation) => {
if (operation.action === "delete") {
deleted.push(...active.filter((file) => file.path === operation.path));
@ -49,6 +52,24 @@ afterEach(async () => {
await act(async () => root.unmount()); client.clear(); container.remove(); vi.clearAllMocks();
});
describe("cached file selection and retained trash", () => {
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"));
expect(container.textContent).toContain("contents of a.txt");
api.preview.mockRejectedValueOnce(new Error("File preview could not be loaded"));
await act(async () => { await client.invalidateQueries({ queryKey: ["work-folders", owner.companyId, owner.scope, owner.ownerId, "preview"] }); });
await settle();
expect(container.textContent).toContain("File preview could not be loaded");
await click(row("b.txt"));
let resolvePreview!: (value: { content: { data: string } }) => void;
api.preview.mockImplementationOnce(() => new Promise((resolve) => { resolvePreview = resolve; }));
await click(row("a.txt"));
expect(container.textContent).toContain("Loading preview…");
expect(container.textContent).not.toContain("File preview could not be loaded");
await act(async () => { resolvePreview({ content: { data: "restored file contents" } }); });
await settle();
expect(container.textContent).toContain("restored file contents");
});
it("only shows trash action for checked files and restores from the Trash tab", async () => {
expect(button("to trash")).toBeUndefined();
await click(checkbox("a.txt"));

View File

@ -143,7 +143,7 @@ export function WorkFolderBrowser({ owner, exampleFiles, readOnly = false, fillH
{selected && <div className="flex items-center gap-2"><span className="min-w-0 flex-1 truncate text-sm">{selected.path}</span>
{selected.kind === "file" && !exampleFiles && <Button asChild size="sm" variant="outline"><a href={workFoldersApi.downloadUrl(owner, selected.path)} download><Download aria-hidden />Download</a></Button>}
</div>}
{preview.isLoading ? <p className="text-sm text-muted-foreground">Loading preview…</p> : preview.error ? <p role="alert" className="text-sm text-muted-foreground">{preview.error.message}</p> : preview.data ?
{preview.isLoading || (preview.isFetching && preview.isError) ? <p className="text-sm text-muted-foreground">Loading preview…</p> : preview.error ? <p role="alert" className="text-sm text-muted-foreground">{preview.error.message}</p> : preview.data ?
<div className={cn("flex min-h-0 flex-col overflow-auto rounded-md border", fillHeight ? "flex-1" : "max-h-96")}><FileContentViewer content={preview.data} highlightedLine={null} /></div> : <p className="text-sm text-muted-foreground">Select a file to preview it.</p>}
</div>
</TabsContent>}