fix: align live speed and split package reset
This commit is contained in:
@@ -295,6 +295,17 @@ describe("desktop shell", () => {
|
||||
expect(menu).not.toContain("ctx-menu-active");
|
||||
});
|
||||
|
||||
it("offers separate package reset actions for errors and the complete package", () => {
|
||||
const source = readFileSync(new URL("../src/renderer/App.tsx", import.meta.url), "utf8");
|
||||
const menu = source.slice(source.indexOf('ariaLabel="Downloadaktionen"'), source.indexOf('ariaLabel="Priorität"'));
|
||||
|
||||
expect(menu).toContain("Nur fehlerhafte Dateien zurücksetzen");
|
||||
expect(menu).toContain("Gesamtes Paket zurücksetzen");
|
||||
expect(menu).toContain("getPackageErrorItemIds");
|
||||
expect(menu).toContain("window.rd.resetItems(errorItemIds)");
|
||||
expect(menu).toContain("window.rd.resetPackage(id)");
|
||||
});
|
||||
|
||||
it("styles checked columns with a green mark and normal text", () => {
|
||||
const shellCss = readFileSync(new URL("../src/renderer/shell/shell.css", import.meta.url), "utf8");
|
||||
|
||||
|
||||
@@ -49,6 +49,33 @@ describe("runWithLimitedConcurrency", () => {
|
||||
});
|
||||
|
||||
describe("download live update cadence", () => {
|
||||
it("uses the same rolling traffic window for item and package speed", () => {
|
||||
const root = fs.mkdtempSync(path.join(os.tmpdir(), "rd-speed-window-"));
|
||||
tempDirs.push(root);
|
||||
const manager = new DownloadManager(
|
||||
defaultSettings(),
|
||||
emptySession(),
|
||||
createStoragePaths(path.join(root, "state"))
|
||||
);
|
||||
manager.addPackages([{ name: "speed", links: ["https://dummy/speed.bin"] }]);
|
||||
const initial = manager.getSnapshot();
|
||||
const packageId = initial.session.packageOrder[0];
|
||||
const itemId = initial.session.packages[packageId].itemIds[0];
|
||||
const internal = manager as any;
|
||||
internal.session.running = true;
|
||||
internal.session.paused = false;
|
||||
internal.session.packages[packageId].status = "downloading";
|
||||
internal.session.items[itemId].status = "downloading";
|
||||
internal.session.items[itemId].speedBps = 47_870_000;
|
||||
|
||||
internal.recordSpeed(18_550_000, packageId, itemId);
|
||||
|
||||
const snapshot = manager.getSnapshot();
|
||||
expect(snapshot.packageSpeedBps[packageId]).toBe(18_550_000);
|
||||
expect(snapshot.session.items[itemId].speedBps).toBe(snapshot.packageSpeedBps[packageId]);
|
||||
expect(internal.session.items[itemId].speedBps).toBe(47_870_000);
|
||||
});
|
||||
|
||||
it.each([69, 661, 2_470])("emits a running queue snapshot no sooner than 750 ms for %i items", async (itemCount) => {
|
||||
vi.useFakeTimers();
|
||||
try {
|
||||
@@ -8858,6 +8885,76 @@ describe("download manager", () => {
|
||||
}
|
||||
});
|
||||
|
||||
it("resets only selected package errors and preserves successful downloaded files", async () => {
|
||||
const root = fs.mkdtempSync(path.join(os.tmpdir(), "rd-selective-package-reset-"));
|
||||
tempDirs.push(root);
|
||||
const session = emptySession();
|
||||
const packageId = "selective-reset-package";
|
||||
const outputDir = path.join(root, "downloads", "selective-reset");
|
||||
const createdAt = Date.now();
|
||||
const itemIds = ["successful", "download-error", "extract-error"];
|
||||
fs.mkdirSync(outputDir, { recursive: true });
|
||||
session.packageOrder = [packageId];
|
||||
session.packages[packageId] = {
|
||||
id: packageId,
|
||||
name: "selective-reset",
|
||||
outputDir,
|
||||
extractDir: path.join(root, "extract", "selective-reset"),
|
||||
status: "failed",
|
||||
itemIds,
|
||||
cancelled: false,
|
||||
enabled: true,
|
||||
createdAt,
|
||||
updatedAt: createdAt
|
||||
};
|
||||
for (const itemId of itemIds) {
|
||||
const targetPath = path.join(outputDir, `${itemId}.rar`);
|
||||
fs.writeFileSync(targetPath, itemId);
|
||||
session.items[itemId] = {
|
||||
id: itemId,
|
||||
packageId,
|
||||
url: `https://dummy/${itemId}`,
|
||||
provider: "realdebrid",
|
||||
status: itemId === "download-error" ? "failed" : "completed",
|
||||
retries: 0,
|
||||
speedBps: 0,
|
||||
downloadedBytes: itemId.length,
|
||||
totalBytes: itemId.length,
|
||||
progressPercent: 100,
|
||||
fileName: `${itemId}.rar`,
|
||||
targetPath,
|
||||
resumable: true,
|
||||
attempts: 1,
|
||||
lastError: itemId === "successful" ? "" : "Fehler",
|
||||
fullStatus: itemId === "successful" ? "Entpackt" : itemId === "extract-error" ? "Entpack-Fehler: CRCERROR" : "Fehlgeschlagen",
|
||||
createdAt,
|
||||
updatedAt: createdAt
|
||||
};
|
||||
}
|
||||
const manager = new DownloadManager(
|
||||
defaultSettings(),
|
||||
session,
|
||||
createStoragePaths(path.join(root, "state"))
|
||||
);
|
||||
|
||||
await manager.resetItems(["download-error", "extract-error"]);
|
||||
|
||||
const snapshot = manager.getSnapshot().session;
|
||||
expect(snapshot.items.successful.status).toBe("completed");
|
||||
expect(snapshot.items.successful.targetPath).toBe(path.join(outputDir, "successful.rar"));
|
||||
expect(fs.existsSync(snapshot.items.successful.targetPath)).toBe(true);
|
||||
for (const itemId of ["download-error", "extract-error"]) {
|
||||
expect(snapshot.items[itemId]).toEqual(expect.objectContaining({
|
||||
status: "queued",
|
||||
downloadedBytes: 0,
|
||||
totalBytes: null,
|
||||
targetPath: "",
|
||||
fullStatus: "Wartet"
|
||||
}));
|
||||
expect(fs.existsSync(path.join(outputDir, `${itemId}.rar`))).toBe(false);
|
||||
}
|
||||
});
|
||||
|
||||
it("does not freeze the scheduler when a reset item's old task is parked in a non-abort-observing await", async () => {
|
||||
const root = fs.mkdtempSync(path.join(os.tmpdir(), "rd-dm-"));
|
||||
tempDirs.push(root);
|
||||
|
||||
@@ -17,6 +17,8 @@ import {
|
||||
getRemainingDownloadBytes,
|
||||
getPendingDownloadItemCount,
|
||||
getDownloadSpeedBps,
|
||||
getPackageErrorItemIds,
|
||||
isDownloadItemError,
|
||||
buildDownloadLogicalRows,
|
||||
type DownloadSidebarFilter,
|
||||
type DownloadsModelInput
|
||||
@@ -888,6 +890,21 @@ describe("downloads model", () => {
|
||||
expect(getDownloadSpeedBps({ stale: -1, invalid: Number.NaN })).toBe(0);
|
||||
});
|
||||
|
||||
it("selects only failed downloads and extraction errors for a package error reset", () => {
|
||||
const packageEntry = pkg("package-a", "Reset", ["successful", "download-error", "extract-error"]);
|
||||
const items = {
|
||||
successful: item("successful", packageEntry.id, "completed", { fullStatus: "Entpackt" }),
|
||||
"download-error": item("download-error", packageEntry.id, "failed", { fullStatus: "Fehlgeschlagen nach 3 Versuchen" }),
|
||||
"extract-error": item("extract-error", packageEntry.id, "completed", { fullStatus: "Entpack-Fehler: CRCERROR" })
|
||||
};
|
||||
|
||||
expect(isDownloadItemError(items.successful)).toBe(false);
|
||||
expect(getPackageErrorItemIds([packageEntry.id], { [packageEntry.id]: packageEntry }, items)).toEqual([
|
||||
"download-error",
|
||||
"extract-error"
|
||||
]);
|
||||
});
|
||||
|
||||
it("exports the sidebar filter contract used by the downloads shell", () => {
|
||||
const filter: DownloadSidebarFilter = "queued";
|
||||
|
||||
|
||||
+4
-1
@@ -185,7 +185,10 @@ describe("renderer localization", () => {
|
||||
it.each([
|
||||
["Entpackte Downloads sind ausgeblendet", "Extracted downloads are hidden"],
|
||||
["Deaktiviere „Entpackte Einträge ausblenden“, um sie wieder anzuzeigen.", "Disable “Hide extracted entries” to show them again."],
|
||||
["Downloads können derzeit nicht gestartet werden", "Downloads cannot be started right now"]
|
||||
["Downloads können derzeit nicht gestartet werden", "Downloads cannot be started right now"],
|
||||
["Nur fehlerhafte Dateien zurücksetzen", "Reset failed files only"],
|
||||
["Gesamtes Paket zurücksetzen", "Reset entire package"],
|
||||
["Ausgewählte Pakete vollständig zurücksetzen", "Reset selected packages completely"]
|
||||
])("translates download state text %s in both directions", (german, english) => {
|
||||
expect(translateUiText(german, "en")).toBe(english);
|
||||
expect(translateUiText(english, "de")).toBe(german);
|
||||
|
||||
Reference in New Issue
Block a user