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:
Sucukdeluxe
2026-08-20 08:40:25 +02:00
parent 1f2f10e803
commit 32fc1e7321
6 changed files with 274 additions and 25 deletions
+108
View File
@@ -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"]);
});
});