Scope selected item runs and stop mutations precisely, attach hybrid work to the active run, retain password progress, and report finalization with real percentages. Drain stale post-processing before manual extraction, allow package and child extraction with open siblings, retry nonterminal hybrid archive attempts, and replace misleading aggregate retry counts with actionable conversion status.
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 type { DownloadPackageRow } from "../src/renderer/views/downloads/downloads-model";
|
|
import { buildPackagePresentation } from "../src/renderer/views/downloads/package-presentation";
|
|
|
|
function item(id: string, fullStatus: string, overrides: Partial<DownloadItem> = {}): DownloadItem {
|
|
return {
|
|
id,
|
|
packageId: "pkg",
|
|
url: `https://example.invalid/${id}`,
|
|
provider: "realdebrid",
|
|
status: "completed",
|
|
retries: 0,
|
|
speedBps: 0,
|
|
downloadedBytes: 100,
|
|
totalBytes: 100,
|
|
progressPercent: 100,
|
|
fileName: `${id}.rar`,
|
|
targetPath: `C:\\Downloads\\${id}.rar`,
|
|
resumable: true,
|
|
attempts: 0,
|
|
lastError: "",
|
|
fullStatus,
|
|
createdAt: 1,
|
|
updatedAt: 1,
|
|
...overrides
|
|
};
|
|
}
|
|
|
|
function row(items: DownloadItem[], overrides: Partial<PackageEntry> = {}): DownloadPackageRow {
|
|
const entry = {
|
|
id: "pkg",
|
|
name: "Paket",
|
|
outputDir: "C:\\Downloads\\Paket",
|
|
extractDir: "C:\\Downloads\\_entpackt\\Paket",
|
|
itemIds: items.map((entry) => entry.id),
|
|
enabled: true,
|
|
cancelled: false,
|
|
status: "completed",
|
|
priority: "normal",
|
|
createdAt: 1,
|
|
updatedAt: 1,
|
|
...overrides
|
|
} as PackageEntry;
|
|
return { package: entry, items, allItems: items, collapsed: true };
|
|
}
|
|
|
|
describe("download package presentation", () => {
|
|
it("reserves 90 percent for completed downloads and 10 percent for extraction", () => {
|
|
expect(buildPackagePresentation(row([
|
|
item("a", "Entpack-Fehler: Passwort"),
|
|
item("b", "Entpack-Fehler: CRC")
|
|
])).progress.value).toBe(90);
|
|
|
|
expect(buildPackagePresentation(row([
|
|
item("a", "Entpackt in 4s"),
|
|
item("b", "Entpack-Fehler: CRC")
|
|
])).progress.value).toBe(95);
|
|
|
|
expect(buildPackagePresentation(row([
|
|
item("a", "Entpackt in 4s"),
|
|
item("b", "Entpackt in 5s")
|
|
])).progress.value).toBe(100);
|
|
});
|
|
|
|
it("keeps ordinary completed downloads at 100 percent when no extraction phase exists", () => {
|
|
const presentation = buildPackagePresentation(row([
|
|
item("a", "Fertig"),
|
|
item("b", "Fertig")
|
|
]));
|
|
expect(presentation.progress.value).toBe(100);
|
|
expect(presentation.status).toBe("Fertig");
|
|
});
|
|
|
|
it("does not move backwards when an archive download enters extraction", () => {
|
|
const active = item("archive", "Download läuft", {
|
|
status: "downloading",
|
|
downloadedBytes: 99,
|
|
progressPercent: 99
|
|
});
|
|
const before = buildPackagePresentation(row([active], { status: "downloading" }));
|
|
const after = buildPackagePresentation(row([{ ...active, status: "completed", downloadedBytes: 100, progressPercent: 100, fullStatus: "Entpacken - Ausstehend" }], { status: "extracting" }));
|
|
|
|
expect(before.progress.value).toBe(89);
|
|
expect(after.progress.value).toBe(90);
|
|
});
|
|
|
|
it("includes finalization progress in the reserved extraction range", () => {
|
|
const presentation = buildPackagePresentation(row([
|
|
item("archive", "Finalisieren - 99% · release.part01.rar")
|
|
], { status: "extracting", postProcessLabel: "Finalisieren - 99% (0/1) · release.part01.rar" }));
|
|
|
|
expect(presentation.progress.value).toBe(99);
|
|
expect(presentation.status).toBe("Finalisieren - 99% (0/1) · release.part01.rar");
|
|
});
|
|
|
|
it("summarizes mixed extraction errors and a live retry instead of showing a fraction", () => {
|
|
const items = [
|
|
...Array.from({ length: 7 }, (_, index) => item(`failed-${index}`, "Entpack-Fehler: Keine entpackten Dateien erkannt")),
|
|
item("retry", "Link-Umwandlung erneut, Versuch 6/...", {
|
|
status: "validating",
|
|
retries: 6,
|
|
downloadedBytes: 0,
|
|
progressPercent: 0
|
|
})
|
|
];
|
|
const presentation = buildPackagePresentation(row(items, { status: "queued" }));
|
|
|
|
expect(presentation.status).toBe("7 Entpackfehler · Link-Umwandlung erneut");
|
|
expect(presentation.details).toContain("7 Entpackfehler");
|
|
expect(presentation.details).toContain("Link-Umwandlung erneut");
|
|
});
|
|
|
|
it("describes parallel link conversion retries without presenting the item count as attempts", () => {
|
|
const retries = Array.from({ length: 20 }, (_, index) => item(`retry-${index}`, "Link-Umwandlung erneut, Versuch 2/...", {
|
|
status: "validating",
|
|
retries: 2,
|
|
downloadedBytes: 0,
|
|
progressPercent: 0
|
|
}));
|
|
|
|
const presentation = buildPackagePresentation(row(retries, { status: "queued" }));
|
|
|
|
expect(presentation.status).toBe("Link-Umwandlung erneut");
|
|
expect(presentation.status).not.toContain("20");
|
|
});
|
|
|
|
it("keeps a single normal active download compact", () => {
|
|
const presentation = buildPackagePresentation(row([
|
|
item("active", "Download läuft", {
|
|
status: "downloading",
|
|
downloadedBytes: 50,
|
|
progressPercent: 50
|
|
})
|
|
], { status: "downloading" }));
|
|
|
|
expect(presentation.status).toBe("Download läuft");
|
|
});
|
|
});
|