Normalize RapidGator aliases, stabilize active queue ordering, preserve user-controlled package expansion, and align compact queue status presentation. Separate Mega-Debrid API and Web credential pools, migrate legacy credentials and disabled states safely, refresh live account availability without restart, honor disabled credential persistence, and invalidate Web sessions without allowing stale in-flight, retry, or queued logins to restore old cookies. Expand regression coverage for account isolation, legacy migration, session races, live scheduler refresh, update notes, and responsive queue behavior.
140 lines
4.9 KiB
TypeScript
140 lines
4.9 KiB
TypeScript
import { describe, expect, it } from "vitest";
|
|
import type { DownloadItem, PackageEntry } from "../src/shared/types";
|
|
import { sortPackagesForDisplay } from "../src/renderer/package-order";
|
|
|
|
function createPackage(id: string, itemIds: string[], downloadStartedAt = 0): PackageEntry {
|
|
const now = Date.now();
|
|
return {
|
|
id,
|
|
name: id,
|
|
outputDir: "",
|
|
extractDir: "",
|
|
status: "queued",
|
|
itemIds,
|
|
cancelled: false,
|
|
enabled: true,
|
|
priority: "normal",
|
|
createdAt: now,
|
|
updatedAt: now,
|
|
downloadStartedAt
|
|
};
|
|
}
|
|
|
|
function createItem(id: string, packageId: string, status: DownloadItem["status"], downloadedBytes: number): DownloadItem {
|
|
const now = Date.now();
|
|
return {
|
|
id,
|
|
packageId,
|
|
url: `https://hoster.example/${id}`,
|
|
provider: null,
|
|
status,
|
|
retries: 0,
|
|
speedBps: 0,
|
|
downloadedBytes,
|
|
totalBytes: downloadedBytes,
|
|
progressPercent: downloadedBytes > 0 ? 50 : 0,
|
|
fileName: `${id}.bin`,
|
|
targetPath: "",
|
|
resumable: true,
|
|
attempts: 0,
|
|
lastError: "",
|
|
fullStatus: "",
|
|
createdAt: now,
|
|
updatedAt: now
|
|
};
|
|
}
|
|
|
|
describe("sortPackagesForDisplay", () => {
|
|
it("floats active packages to the top, keeping queue order within each group", () => {
|
|
// pkg-a and pkg-b both have an active (downloading) item -> both float up in
|
|
// their original queue order; pkg-c (queued only) sinks below.
|
|
const packages = [
|
|
createPackage("pkg-a", ["a1", "a2"]),
|
|
createPackage("pkg-c", ["c1"]),
|
|
createPackage("pkg-b", ["b1", "b2"])
|
|
];
|
|
const items: Record<string, DownloadItem> = {
|
|
a1: createItem("a1", "pkg-a", "downloading", 250),
|
|
a2: createItem("a2", "pkg-a", "completed", 500),
|
|
c1: createItem("c1", "pkg-c", "queued", 0),
|
|
b1: createItem("b1", "pkg-b", "downloading", 800),
|
|
b2: createItem("b2", "pkg-b", "completed", 900)
|
|
};
|
|
|
|
const sorted = sortPackagesForDisplay(packages, items, true, true);
|
|
|
|
// active group [pkg-a, pkg-b] in queue order, then rest [pkg-c]
|
|
expect(sorted.map((pkg) => pkg.id)).toEqual(["pkg-a", "pkg-b", "pkg-c"]);
|
|
});
|
|
|
|
it("does NOT reshuffle active packages when only their progress changes (anti-flicker)", () => {
|
|
const packages = [
|
|
createPackage("pkg-a", ["a1"]),
|
|
createPackage("pkg-b", ["b1"])
|
|
];
|
|
// Both active. pkg-b initially has more bytes than pkg-a.
|
|
const before: Record<string, DownloadItem> = {
|
|
a1: createItem("a1", "pkg-a", "downloading", 100),
|
|
b1: createItem("b1", "pkg-b", "downloading", 900)
|
|
};
|
|
const orderBefore = sortPackagesForDisplay(packages, before, true, true).map((p) => p.id);
|
|
|
|
// A progress tick: pkg-a overtakes pkg-b in bytes. Order must NOT change —
|
|
// both are still active, so they keep queue order. (Old code swapped them.)
|
|
const after: Record<string, DownloadItem> = {
|
|
a1: createItem("a1", "pkg-a", "downloading", 5000),
|
|
b1: createItem("b1", "pkg-b", "downloading", 950)
|
|
};
|
|
const orderAfter = sortPackagesForDisplay(packages, after, true, true).map((p) => p.id);
|
|
|
|
expect(orderBefore).toEqual(["pkg-a", "pkg-b"]);
|
|
expect(orderAfter).toEqual(orderBefore);
|
|
});
|
|
|
|
it("keeps package order untouched when auto sort is disabled", () => {
|
|
const packages = [
|
|
createPackage("pkg-a", ["a1"]),
|
|
createPackage("pkg-b", ["b1"]),
|
|
createPackage("pkg-c", ["c1"])
|
|
];
|
|
const items: Record<string, DownloadItem> = {
|
|
a1: createItem("a1", "pkg-a", "queued", 0),
|
|
b1: createItem("b1", "pkg-b", "downloading", 500),
|
|
c1: createItem("c1", "pkg-c", "queued", 0)
|
|
};
|
|
|
|
const sorted = sortPackagesForDisplay(packages, items, true, false);
|
|
|
|
expect(sorted.map((pkg) => pkg.id)).toEqual(["pkg-a", "pkg-b", "pkg-c"]);
|
|
});
|
|
|
|
it("keeps every active package in activation order when a new package starts", () => {
|
|
const packages = [
|
|
createPackage("pkg-new", ["new-item"], 200),
|
|
createPackage("pkg-existing", ["existing-item"], 100)
|
|
];
|
|
const items: Record<string, DownloadItem> = {
|
|
"new-item": createItem("new-item", "pkg-new", "downloading", 100),
|
|
"existing-item": createItem("existing-item", "pkg-existing", "downloading", 200)
|
|
};
|
|
const sorted = sortPackagesForDisplay(packages, items, true, true);
|
|
|
|
expect(sorted.map((pkg) => pkg.id)).toEqual(["pkg-existing", "pkg-new"]);
|
|
});
|
|
|
|
it("keeps queue order for active packages without a recorded start time", () => {
|
|
const packages = [
|
|
createPackage("pkg-a", ["a1"]),
|
|
createPackage("pkg-b", ["b1"]),
|
|
createPackage("pkg-c", ["c1"])
|
|
];
|
|
const items: Record<string, DownloadItem> = {
|
|
a1: createItem("a1", "pkg-a", "completed", 500),
|
|
b1: createItem("b1", "pkg-b", "downloading", 200),
|
|
c1: createItem("c1", "pkg-c", "downloading", 100)
|
|
};
|
|
|
|
expect(sortPackagesForDisplay(packages, items, true, true).map((pkg) => pkg.id)).toEqual(["pkg-b", "pkg-c", "pkg-a"]);
|
|
});
|
|
});
|