fix(files): review round 3, the page — a search waits for the server, the viewer keeps its file, a drop ends the drag
- past the list's cap a search waits in a skeleton, never showing the newest files' 'no match' for a file the server is about to find; a sort keeps its stand-in rows - the viewer keeps the file it was opened on when a server answer replaces the rows under it - a drop another zone took still clears the page's drop overlay - 'The first N items' only for a server sort; the past-the-end notice no longer claims the file exists - a failed server search is tested to say so
This commit is contained in:
@@ -38,6 +38,8 @@ const mocks = vi.hoisted(() => ({
|
||||
searched: [] as unknown[],
|
||||
// The server's search/sort answer is still on its way (no data yet).
|
||||
wideLoading: false,
|
||||
// The server's search/sort answer failed.
|
||||
wideFailed: false,
|
||||
foldersFailed: false,
|
||||
notify: vi.fn(),
|
||||
}));
|
||||
@@ -73,6 +75,14 @@ vi.mock("../lib/useLiveQuery", () => ({
|
||||
if (opts?.enabled === false) return { data: undefined, isError: false, isLoading: false };
|
||||
mocks.keys.push(key);
|
||||
const asked = key[1] as { q?: string; sort?: string } | undefined;
|
||||
if (key[0] === "files" && (asked?.q || asked?.sort) && mocks.wideFailed)
|
||||
return {
|
||||
data: undefined,
|
||||
isError: true,
|
||||
isLoading: false,
|
||||
isFetching: false,
|
||||
refetch: vi.fn(),
|
||||
};
|
||||
if (key[0] === "files" && (asked?.q || asked?.sort) && mocks.wideLoading)
|
||||
return {
|
||||
data: undefined,
|
||||
@@ -154,6 +164,7 @@ afterEach(() => {
|
||||
mocks.fetching = false;
|
||||
mocks.titleArgs = [];
|
||||
mocks.wideLoading = false;
|
||||
mocks.wideFailed = false;
|
||||
mocks.notify.mockClear();
|
||||
resetQueueWatch();
|
||||
});
|
||||
@@ -476,11 +487,55 @@ describe("Files · a library past the list's cap", () => {
|
||||
expect(screen.getByText("files.countFirst")).toBeTruthy();
|
||||
cleanup();
|
||||
|
||||
// A search with no sort of the server's: it answers the newest matches first.
|
||||
mount(ALL_FILES, vi.fn(), "clip");
|
||||
expect(screen.getByText("files.countNewest")).toBeTruthy();
|
||||
cleanup();
|
||||
|
||||
mocks.searched = [file(9999, { display_name: "Régi recept" })];
|
||||
mount(ALL_FILES, vi.fn(), "recept");
|
||||
expect(screen.getByText("files.count")).toBeTruthy();
|
||||
});
|
||||
|
||||
it("a search past the cap waits for the server's answer — never a 'no match' it has not given", () => {
|
||||
mocks.folders = [YT];
|
||||
mocks.files = capped();
|
||||
mocks.wideLoading = true;
|
||||
mount(ALL_FILES, vi.fn(), "recept");
|
||||
expect(screen.getByRole("status")).toBeTruthy();
|
||||
expect(screen.queryByText("files.empty.search")).toBeNull();
|
||||
});
|
||||
|
||||
it("a server search that failed says so, not the newest files searched here", () => {
|
||||
mocks.folders = [YT];
|
||||
mocks.files = capped();
|
||||
mocks.wideFailed = true;
|
||||
mount(ALL_FILES, vi.fn(), "clip");
|
||||
expect(screen.getByText("common.loadError")).toBeTruthy();
|
||||
expect(screen.queryByText("Clip 1")).toBeNull();
|
||||
});
|
||||
|
||||
it("a file opened while the server's sort was on its way stays open when the answer leaves it out", async () => {
|
||||
mocks.folders = [YT];
|
||||
const doc = file(7, {
|
||||
job_kind: "upload",
|
||||
display_name: "Doc 7",
|
||||
asset: { media_kind: "file", preview: "pdf", container: "pdf", size_bytes: 10 },
|
||||
} as never);
|
||||
mocks.files = [doc, ...capped().slice(1)];
|
||||
mocks.wideLoading = true;
|
||||
mount();
|
||||
fireEvent.click(nameHeader());
|
||||
fireEvent.click(screen.getByText("Doc 7"));
|
||||
expect(await screen.findByRole("dialog")).toBeTruthy();
|
||||
// The answer: the first files by name, without it.
|
||||
mocks.wideLoading = false;
|
||||
mocks.searched = Array.from({ length: FILES_LIST_CAP }, (_, i) => file(i + 1000));
|
||||
fireEvent.click(nameHeader());
|
||||
expect(screen.getByText("Clip 1499")).toBeTruthy(); // by name, from the end
|
||||
expect(screen.getByRole("dialog")).toBeTruthy();
|
||||
});
|
||||
|
||||
it("an arrival waits for the server's answer: the table can only turn to a file it holds", async () => {
|
||||
mocks.folders = [YT];
|
||||
mocks.files = capped();
|
||||
@@ -1013,6 +1068,24 @@ describe("Files · dropping a file on the page", () => {
|
||||
expect(drag("drop", [new File(["y"], "masik.pdf")]).defaultPrevented).toBe(true);
|
||||
});
|
||||
|
||||
it("a drop another zone took still ends the page's drag", () => {
|
||||
mocks.files = [];
|
||||
mocks.folders = [YT, OWN];
|
||||
mount({ kind: "folder", id: 2 });
|
||||
drag("dragenter");
|
||||
expect(screen.getByText("files.drop.here:Cimboráknak")).toBeTruthy();
|
||||
// Taken by a zone over the page (the issue composer's screenshot box): no dragleave follows.
|
||||
const e = new Event("drop", { bubbles: true, cancelable: true });
|
||||
Object.defineProperty(e, "dataTransfer", {
|
||||
value: { types: ["Files"], files: Object.assign([], { item: () => null }) },
|
||||
});
|
||||
e.preventDefault();
|
||||
act(() => {
|
||||
window.dispatchEvent(e);
|
||||
});
|
||||
expect(screen.queryByText("files.drop.here:Cimboráknak")).toBeNull();
|
||||
});
|
||||
|
||||
it("the open Upload dialog's own zone keeps the drop it accepted", async () => {
|
||||
mocks.files = [];
|
||||
mocks.folders = [YT, OWN];
|
||||
|
||||
Reference in New Issue
Block a user