fix(downloads): remove column reorder flicker
Clear old FLIP transforms before committing the new download grid order. Serialize and coalesce column-order persistence, keep live download snapshots from overwriting local layout state, reassert imported settings after older requests, and block overlapping pointer or arrow moves during settling.
This commit is contained in:
@@ -1,4 +1,5 @@
|
||||
import { describe, expect, it, vi } from "vitest";
|
||||
import * as columnDrag from "../src/renderer/views/downloads/column-drag";
|
||||
import { calculateColumnDragPreview, updateDownloadColumnDrag, type DownloadColumnDragSession } from "../src/renderer/views/downloads/column-drag";
|
||||
|
||||
const columns = [
|
||||
@@ -51,4 +52,111 @@ describe("animated download column drag", () => {
|
||||
expect(setProperty).toHaveBeenCalledTimes(1);
|
||||
expect(setProperty).toHaveBeenCalledWith("--downloads-active-drag-x", "25px");
|
||||
});
|
||||
|
||||
it.each([
|
||||
{ draggedId: "name", measurements: columns, next: ["size", "name", "status"] },
|
||||
{
|
||||
draggedId: "name",
|
||||
measurements: [
|
||||
{ id: "size", left: 0, width: 150 },
|
||||
{ id: "name", left: 150, width: 300 },
|
||||
{ id: "status", left: 450, width: 100 }
|
||||
],
|
||||
next: ["name", "size", "status"]
|
||||
}
|
||||
])("clears transforms before committing a $draggedId grid move", ({ draggedId, measurements, next }) => {
|
||||
const commitDownloadColumnDrag = (columnDrag as unknown as {
|
||||
commitDownloadColumnDrag?: (session: DownloadColumnDragSession, order: string[], commit: (order: string[]) => void) => void;
|
||||
}).commitDownloadColumnDrag;
|
||||
expect(commitDownloadColumnDrag).toBeTypeOf("function");
|
||||
if (!commitDownloadColumnDrag) return;
|
||||
|
||||
const events: string[] = [];
|
||||
const root = {
|
||||
classList: { remove: () => events.push("clear-classes") },
|
||||
dataset: { columnDragging: draggedId },
|
||||
querySelectorAll: () => [],
|
||||
style: {
|
||||
removeProperty: (property: string) => events.push(`clear:${property}`)
|
||||
}
|
||||
} as unknown as HTMLElement;
|
||||
const session = {
|
||||
active: true,
|
||||
draggedId,
|
||||
measurements,
|
||||
pointerId: -1,
|
||||
preview: calculateColumnDragPreview(measurements, draggedId, next[0] === draggedId ? -160 : 160),
|
||||
root,
|
||||
startX: 0
|
||||
} as DownloadColumnDragSession;
|
||||
|
||||
commitDownloadColumnDrag(session, next, (order) => {
|
||||
events.push(`commit:${order.join("|")}`);
|
||||
});
|
||||
|
||||
expect(events.at(-1)).toBe(`commit:${next.join("|")}`);
|
||||
expect(events.slice(0, -1)).toContain("clear:--downloads-column-drag-name");
|
||||
expect(events.slice(0, -1)).toContain("clear:--downloads-column-drag-size");
|
||||
});
|
||||
|
||||
it("serializes and coalesces rapid persistence requests", async () => {
|
||||
const createDownloadColumnOrderPersistence = (columnDrag as unknown as {
|
||||
createDownloadColumnOrderPersistence?: (
|
||||
initial: string[],
|
||||
persist: (order: string[]) => Promise<string[]>,
|
||||
apply: (order: string[]) => void
|
||||
) => { enqueue: (order: string[]) => void; whenIdle: () => Promise<void> };
|
||||
}).createDownloadColumnOrderPersistence;
|
||||
expect(createDownloadColumnOrderPersistence).toBeTypeOf("function");
|
||||
if (!createDownloadColumnOrderPersistence) return;
|
||||
|
||||
const pending: Array<(order: string[]) => void> = [];
|
||||
const persisted: string[][] = [];
|
||||
const applied: string[][] = [];
|
||||
const coordinator = createDownloadColumnOrderPersistence(["name", "size"], (order) => {
|
||||
persisted.push(order);
|
||||
return new Promise((resolve) => pending.push(resolve));
|
||||
}, (order) => applied.push(order));
|
||||
|
||||
coordinator.enqueue(["size", "name"]);
|
||||
coordinator.enqueue(["name", "size"]);
|
||||
coordinator.enqueue(["size", "name"]);
|
||||
expect(persisted).toEqual([["size", "name"]]);
|
||||
|
||||
pending.shift()?.(["size", "name"]);
|
||||
await vi.waitFor(() => expect(persisted).toEqual([["size", "name"], ["size", "name"]]));
|
||||
pending.shift()?.(["size", "name"]);
|
||||
await coordinator.whenIdle();
|
||||
|
||||
expect(applied.at(-1)).toEqual(["size", "name"]);
|
||||
});
|
||||
|
||||
it("reasserts an authoritative import after an older request finishes", async () => {
|
||||
const createDownloadColumnOrderPersistence = (columnDrag as unknown as {
|
||||
createDownloadColumnOrderPersistence?: (
|
||||
initial: string[],
|
||||
persist: (order: string[]) => Promise<string[]>,
|
||||
apply: (order: string[]) => void
|
||||
) => { applyAuthoritative: (order: string[]) => void; enqueue: (order: string[]) => void; whenIdle: () => Promise<void> };
|
||||
}).createDownloadColumnOrderPersistence;
|
||||
expect(createDownloadColumnOrderPersistence).toBeTypeOf("function");
|
||||
if (!createDownloadColumnOrderPersistence) return;
|
||||
|
||||
const pending: Array<(order: string[]) => void> = [];
|
||||
const persisted: string[][] = [];
|
||||
const applied: string[][] = [];
|
||||
const coordinator = createDownloadColumnOrderPersistence(["name", "size"], (order) => {
|
||||
persisted.push(order);
|
||||
return new Promise((resolve) => pending.push(resolve));
|
||||
}, (order) => applied.push(order));
|
||||
|
||||
coordinator.enqueue(["size", "name"]);
|
||||
coordinator.applyAuthoritative(["status", "name", "size"]);
|
||||
pending.shift()?.(["size", "name"]);
|
||||
await vi.waitFor(() => expect(persisted.at(-1)).toEqual(["status", "name", "size"]));
|
||||
pending.shift()?.(["status", "name", "size"]);
|
||||
await coordinator.whenIdle();
|
||||
|
||||
expect(applied.at(-1)).toEqual(["status", "name", "size"]);
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user