release: prepare v2.0.82

This commit is contained in:
Sucukdeluxe
2026-09-01 00:13:30 +02:00
parent d58cfba817
commit e7639bc4df
23 changed files with 313 additions and 59 deletions
+1 -1
View File
@@ -39,7 +39,7 @@ describe("account operation errors", () => {
expect(formatAccountOperationError(
"Account konnte nicht gespeichert werden",
new Error("Error invoking remote method: proxy_only_account:proxy_list_missing")
)).toBe("Account konnte nicht gespeichert werden: Proxy-only ist aktiviert, aber es ist keine Proxy-Liste hinterlegt. Hinterlege sie unter Einstellungen → Geschwindigkeit.");
)).toBe("Account konnte nicht gespeichert werden: Proxy-only ist aktiviert, aber es ist keine Proxy-Liste hinterlegt. Hinterlege sie unter Einstellungen → Geschwindigkeit & Proxy.");
expect(formatAccountOperationError(
"Prüfung fehlgeschlagen",
"proxy_only_account:proxy_unreachable"
+5 -2
View File
@@ -281,8 +281,11 @@ describe("Download-ETA", () => {
});
describe("verbleibendes Downloadvolumen", () => {
it("shows a fourth TB decimal so fast downloads stay visibly responsive", () => {
expect(formatRemainingDownloadBytes({ bytes: Math.round(1.125 * 1024 ** 4), unknownItems: 0 })).toBe("1.1250 TB");
it("uses TB precision that changes on every normal large-queue live tick", () => {
const before = Math.round(1.125 * 1024 ** 4);
const after = before - 40 * 1024 ** 2;
expect(formatRemainingDownloadBytes({ bytes: before, unknownItems: 0 })).toBe("1.12500 TB");
expect(formatRemainingDownloadBytes({ bytes: after, unknownItems: 0 })).toBe("1.12496 TB");
});
it("summiert nur offene bekannte Restbytes und klemmt überladene Fortschritte bei null", () => {
+6 -4
View File
@@ -18,6 +18,8 @@ describe("renderer localization", () => {
expect(translateUiText("Passwort/Zugang", "en")).toBe("Password/access");
expect(translateUiText("Animationen", "en")).toBe("Animations");
expect(translateUiText("Animations", "de")).toBe("Animationen");
expect(translateUiText("Geschwindigkeit & Proxy", "en")).toBe("Speed & Proxy");
expect(translateUiText("Fehlende Arbeitsordner beim Start anlegen", "en")).toBe("Create missing work folders on startup");
});
it.each([
@@ -29,11 +31,11 @@ describe("renderer localization", () => {
});
it.each([
["Proxy-only ist aktiviert, aber es ist keine Proxy-Liste hinterlegt. Hinterlege sie unter Einstellungen → Geschwindigkeit.", "Proxy-only is enabled, but no proxy list is configured. Add one under Settings → Speed."],
["Proxy-only ist aktiviert, aber die hinterlegte Proxy-Liste kann nicht gelesen werden. Prüfe die Datei unter Einstellungen → Geschwindigkeit.", "Proxy-only is enabled, but the configured proxy list cannot be read. Check the file under Settings → Speed."],
["Proxy-only ist aktiviert, aber es ist keine Proxy-Liste hinterlegt. Hinterlege sie unter Einstellungen → Geschwindigkeit & Proxy.", "Proxy-only is enabled, but no proxy list is configured. Add one under Settings → Speed & Proxy."],
["Proxy-only ist aktiviert, aber die hinterlegte Proxy-Liste kann nicht gelesen werden. Prüfe die Datei unter Einstellungen → Geschwindigkeit & Proxy.", "Proxy-only is enabled, but the configured proxy list cannot be read. Check the file under Settings → Speed & Proxy."],
["Proxy-only ist aktiviert, aber die hinterlegte Proxy-Liste ist leer oder enthält keine gültigen HTTP-Proxys.", "Proxy-only is enabled, but the configured proxy list is empty or contains no valid HTTP proxies."],
["Proxy-only ist aktiviert, aber der feste API-Proxy ist in der Liste nicht verfügbar. Prüfe den Listeneintrag unter Einstellungen → Geschwindigkeit.", "Proxy-only is enabled, but the fixed API proxy is not available in the list. Check the list entry under Settings → Speed."],
["Proxy-only ist aktiviert, aber der feste API-Proxy ist nicht erreichbar oder lehnt die Verbindung ab. Prüfe den Proxy unter Einstellungen → Geschwindigkeit.", "Proxy-only is enabled, but the fixed API proxy is unreachable or refuses the connection. Check the proxy under Settings → Speed."]
["Proxy-only ist aktiviert, aber der feste API-Proxy ist in der Liste nicht verfügbar. Prüfe den Listeneintrag unter Einstellungen → Geschwindigkeit & Proxy.", "Proxy-only is enabled, but the fixed API proxy is not available in the list. Check the list entry under Settings → Speed & Proxy."],
["Proxy-only ist aktiviert, aber der feste API-Proxy ist nicht erreichbar oder lehnt die Verbindung ab. Prüfe den Proxy unter Einstellungen → Geschwindigkeit & Proxy.", "Proxy-only is enabled, but the fixed API proxy is unreachable or refuses the connection. Check the proxy under Settings → Speed & Proxy."]
])("translates Proxy-only account guidance %s", (german, english) => {
expect(translateUiText(german, "en")).toBe(english);
expect(translateUiText(english, "de")).toBe(german);
+9
View File
@@ -4,6 +4,15 @@ import { createRendererSettings } from "../src/main/renderer-state";
import { validateRendererSettingsUpdate } from "../src/main/renderer-settings";
describe("renderer settings validation", () => {
it("projects and validates startup work-directory creation", () => {
const current = { ...defaultSettings(), createWorkDirectoriesOnStartup: true };
const projected = createRendererSettings(current);
expect(projected.createWorkDirectoriesOnStartup).toBe(true);
expect(validateRendererSettingsUpdate({ createWorkDirectoriesOnStartup: false }, current))
.toEqual({ createWorkDirectoriesOnStartup: false });
});
it("projects and validates the semantic theme preference independently from the resolved palette", () => {
const current = { ...defaultSettings(), theme: "dark" as const, themePreference: "system" as const };
const projected = createRendererSettings(current);
+19 -2
View File
@@ -391,7 +391,7 @@ describe("settings model", () => {
{ id: "allgemein", label: "Allgemein" },
{ id: "accounts", label: "Accounts" },
{ id: "extract", label: "Entpacken" },
{ id: "speed", label: "Geschwindigkeit" },
{ id: "speed", label: "Geschwindigkeit & Proxy" },
{ id: "cleanup", label: "Bereinigung" },
{ id: "updates", label: "Updates" }
]);
@@ -642,6 +642,23 @@ describe("settings views", () => {
});
});
it("offers the disabled-by-default startup work-directory switch", () => {
const form = buildSettingsFormViewModel({
settings: { ...createRendererSettings(defaultSettings()), archivePasswordList: "", notifyUrl: "" },
section: "allgemein",
speedLimitInput: "0",
scheduleSpeedInputs: {}
});
const field = form.groups.flatMap((group) => group.fields)
.find((entry) => entry.id === "createWorkDirectoriesOnStartup");
expect(field).toEqual(expect.objectContaining({
kind: "switch",
label: "Fehlende Arbeitsordner beim Start anlegen",
value: false
}));
});
it("projects proxy segmentation controls with safe defaults and connection totals", () => {
const form = buildSettingsFormViewModel({
settings: {
@@ -833,7 +850,7 @@ describe("settings views", () => {
const html = renderToStaticMarkup(<SettingsSidebar actions={viewActions()} model={viewModel()} />);
expect(count(html, "data-visual-region=\"settings-sidebar\"")).toBe(1);
for (const section of SETTINGS_SECTIONS) {
expect(html).toContain(section.label);
expect(html).toContain(section.label.replace(/&/g, "&amp;"));
}
expect(html).toContain("aria-current=\"page\"");
});
+101
View File
@@ -0,0 +1,101 @@
import fs from "node:fs";
import os from "node:os";
import path from "node:path";
import { afterEach, describe, expect, it } from "vitest";
import { defaultSettings } from "../src/main/constants";
import { ensureStartupWorkDirectories } from "../src/main/startup-work-directories";
const tempDirs: string[] = [];
function tempRoot(): string {
const root = fs.mkdtempSync(path.join(os.tmpdir(), "mdd-startup-dirs-"));
tempDirs.push(root);
return root;
}
afterEach(() => {
for (const directory of tempDirs.splice(0)) {
fs.rmSync(directory, { recursive: true, force: true });
}
});
describe("startup work directories", () => {
it("does nothing while the setting is disabled", () => {
const root = tempRoot();
const outputDir = path.join(root, "downloads");
const result = ensureStartupWorkDirectories({
...defaultSettings(),
outputDir,
createWorkDirectoriesOnStartup: false
});
expect(result).toEqual({ created: [], existing: [], failures: [] });
expect(fs.existsSync(outputDir)).toBe(false);
});
it("creates only the enabled work directories and preserves existing contents", () => {
const root = tempRoot();
const outputDir = path.join(root, "downloads");
const extractDir = path.join(root, "extracted");
const mkvLibraryDir = path.join(root, "videos");
fs.mkdirSync(outputDir, { recursive: true });
fs.writeFileSync(path.join(outputDir, "keep.bin"), "keep");
const result = ensureStartupWorkDirectories({
...defaultSettings(),
outputDir,
extractDir,
mkvLibraryDir,
autoExtract: true,
collectMkvToLibrary: true,
createWorkDirectoriesOnStartup: true
});
expect(result.created.map((entry) => entry.kind)).toEqual(["extract", "video-library"]);
expect(result.existing.map((entry) => entry.kind)).toEqual(["download"]);
expect(result.failures).toEqual([]);
expect(fs.readFileSync(path.join(outputDir, "keep.bin"), "utf8")).toBe("keep");
expect(fs.statSync(extractDir).isDirectory()).toBe(true);
expect(fs.statSync(mkvLibraryDir).isDirectory()).toBe(true);
});
it("skips inactive optional directories", () => {
const root = tempRoot();
const outputDir = path.join(root, "downloads");
const extractDir = path.join(root, "extracted");
const mkvLibraryDir = path.join(root, "videos");
const result = ensureStartupWorkDirectories({
...defaultSettings(),
outputDir,
extractDir,
mkvLibraryDir,
autoExtract: false,
collectMkvToLibrary: false,
createWorkDirectoriesOnStartup: true
});
expect(result.created.map((entry) => entry.kind)).toEqual(["download"]);
expect(fs.existsSync(extractDir)).toBe(false);
expect(fs.existsSync(mkvLibraryDir)).toBe(false);
});
it("reports one invalid target without blocking the other directories", () => {
const root = tempRoot();
const blockedOutput = path.join(root, "blocked");
const extractDir = path.join(root, "extracted");
fs.writeFileSync(blockedOutput, "file");
const result = ensureStartupWorkDirectories({
...defaultSettings(),
outputDir: blockedOutput,
extractDir,
autoExtract: true,
createWorkDirectoriesOnStartup: true
});
expect(result.failures.map((entry) => entry.kind)).toEqual(["download"]);
expect(result.created.map((entry) => entry.kind)).toEqual(["extract"]);
expect(fs.statSync(extractDir).isDirectory()).toBe(true);
});
});
+14 -2
View File
@@ -301,11 +301,23 @@ describe("settings storage", () => {
});
it("uses English for new installations and preserves only supported languages", () => {
expect(defaultSettings().language).toBe("en");
expect(normalizeSettings({ ...defaultSettings(), language: "de" }).language).toBe("de");
expect(defaultSettings().language).toBe("en");
expect(normalizeSettings({ ...defaultSettings(), language: "de" }).language).toBe("de");
expect(normalizeSettings({ ...defaultSettings(), language: "fr" as "en" }).language).toBe("en");
});
it("keeps startup work-directory creation opt-in and persists an explicit enable", () => {
const legacy = { ...defaultSettings() } as Partial<AppSettings>;
delete legacy.createWorkDirectoriesOnStartup;
expect(defaultSettings().createWorkDirectoriesOnStartup).toBe(false);
expect(normalizeSettings(legacy as AppSettings).createWorkDirectoriesOnStartup).toBe(false);
expect(normalizeSettings({
...defaultSettings(),
createWorkDirectoriesOnStartup: true
}).createWorkDirectoriesOnStartup).toBe(true);
});
it("migrates a legacy shared Mega-Debrid pool into only the preferred mode", () => {
const legacy = { ...defaultSettings() } as Partial<AppSettings>;
legacy.megaCredentials = "legacy@example.test:legacy-pass";
+4 -3
View File
@@ -90,9 +90,10 @@ function createSettings(): AppSettings {
providerPrimary: "realdebrid",
providerSecondary: "megadebrid-api",
providerTertiary: "bestdebrid",
autoProviderFallback: true,
outputDir: "C:\\Visual\\Downloads",
packageName: "",
autoProviderFallback: true,
outputDir: "C:\\Visual\\Downloads",
createWorkDirectoriesOnStartup: false,
packageName: "",
autoExtract: true,
autoRename4sf4sj: true,
keepGermanAudioOnly: false,