release: prepare v2.0.18 interface and reset reliability update

Preserve package progress and history across immediate cleanup, make extraction resets wait for all post-processing tasks, and keep archive diagnostics out of compact status cells. Rework account creation and settings selectors, improve context-menu placement, remove accidental row dragging, and expand regression coverage for the corrected workflows.
This commit is contained in:
Sucukdeluxe
2026-08-11 00:46:46 +02:00
parent 9b1eeada2e
commit ef5c20c8df
25 changed files with 1953 additions and 952 deletions
+85 -16
View File
@@ -22,9 +22,11 @@ import {
buildTargetedAccountCheck,
filterAccountAddOptions,
getSettingsSaveLabel,
getSettingsSelectNavigationIndex,
projectAccountRows,
pruneAccountSelection,
reconcileAccountAddDraft,
resolveHistoryRetentionSelection,
sortAccountRows,
type AccountAddOption,
type AccountRowSource,
@@ -53,6 +55,10 @@ const accountWorkspaceSource = readFileSync(
new URL("../src/renderer/views/settings/AccountWorkspace.tsx", import.meta.url),
"utf8"
);
const settingsCss = readFileSync(
new URL("../src/renderer/views/settings/settings.css", import.meta.url),
"utf8"
);
function sourceBlock(source: string, start: string, end: string): string {
return source.slice(source.indexOf(start), source.indexOf(end, source.indexOf(start)));
@@ -105,7 +111,7 @@ function accountSources(): AccountRowSource[] {
},
dailyLimitBytes: 10 * GIB,
dailyUsageBytes: 4 * GIB,
username: "stored@example.test",
username: "stored-user",
credentialKind: "password",
canCheck: true
},
@@ -310,7 +316,8 @@ describe("settings model", () => {
const rows = projectAccountRows(accountSources(), [], NOW);
expect(rows.map((row) => row.id)).toEqual(accountSources().map((source) => buildAccountRowId(source.service, source.mode, source.identityId)));
expect(rows[0].username).toBe("verified@example.test");
expect(rows[0].username).toBe("stored-user");
expect(rows[0].email).toBe("verified@example.test");
expect(rows[0].credential).toBe("••••••");
expect(rows[1].credential).toBe("API-Key");
expect(rows.map((row) => row.status.tone)).toEqual(["ok", "free", "invalid", "unknown", "disabled"]);
@@ -420,7 +427,7 @@ describe("settings views", () => {
expect(html.match(/data-sliding-selection-active="true"/g)).toHaveLength(1);
});
it("offers English and German as a live language setting", () => {
it("offers animated language and bounded history retention choices", () => {
const form = buildSettingsFormViewModel({
settings: defaultSettings(),
section: "allgemein",
@@ -439,6 +446,53 @@ describe("settings views", () => {
{ value: "de", label: "Deutsch" }
]
});
const historyRetention = form.groups.flatMap((group) => group.fields).find((field) => field.id === "historyRetentionMode");
expect(historyRetention).toEqual({
id: "historyRetentionMode",
kind: "select",
label: "Verlauf speichern",
value: "permanent",
options: [
{ value: "never", label: "Nie" },
{ value: "session", label: "Nur aktuelle Session" },
{ value: "permanent-100", label: "Nur letzte 100 Einträge" },
{ value: "permanent-250", label: "Nur letzte 250 Einträge" },
{ value: "permanent", label: "Dauerhaft" }
]
});
const html = renderToStaticMarkup(<SettingsForm actions={{ onAction: () => {}, onChange: () => {} }} model={form} />);
expect(html).toContain("class=\"settings-select\"");
expect(html).toContain("role=\"combobox\"");
expect(html).toContain("role=\"listbox\"");
expect(settingsCss).toMatch(/\.settings-select-options\s*\{[^}]*opacity:\s*0[^}]*transform:\s*translateY\(-6px\)[^}]*transition:/s);
expect(settingsCss).toMatch(/\.settings-select\.is-open\s+\.settings-select-options\s*\{[^}]*opacity:\s*1[^}]*transform:\s*translateY\(0\)/s);
});
it("supports keyboard navigation in animated settings selects", () => {
expect(getSettingsSelectNavigationIndex(1, 3, "ArrowDown")).toBe(2);
expect(getSettingsSelectNavigationIndex(2, 3, "ArrowDown")).toBe(0);
expect(getSettingsSelectNavigationIndex(0, 3, "ArrowUp")).toBe(2);
expect(getSettingsSelectNavigationIndex(1, 3, "Home")).toBe(0);
expect(getSettingsSelectNavigationIndex(1, 3, "End")).toBe(2);
const source = readFileSync(new URL("../src/renderer/views/settings/SettingsForm.tsx", import.meta.url), "utf8");
expect(source).toContain("optionRefs.current[nextIndex]?.focus()");
expect(source).toContain('event.key === "Home"');
expect(source).toContain('event.key === "End"');
expect(source).toContain("onBlur={onBlur}");
});
it("clears a bounded history preset when permanent retention is selected", () => {
expect(resolveHistoryRetentionSelection("permanent", 100, "permanent")).toEqual({
historyRetentionMode: "permanent",
historyMaxEntries: 500
});
expect(resolveHistoryRetentionSelection("permanent", 250, "permanent-100")).toEqual({
historyRetentionMode: "permanent",
historyMaxEntries: 100
});
});
it("renders one real sidebar marker and all sections", () => {
@@ -610,19 +664,22 @@ describe("account workspace", () => {
expect(addHtml).toContain("Account hinzufügen");
expect(addHtml).toContain("Prüfen und speichern");
expect(count(addHtml, "<select")).toBe(1);
expect(addHtml).toContain('aria-label="Dienst / Zugangstyp"');
expect(count(addHtml, "<option")).toBe(options.length);
options.forEach((option) => expect(addHtml).toContain(`value="${option.id}"`));
expect(addHtml).toContain('<option value="megadebrid-api" selected="">Mega-Debrid · API</option>');
expect(count(addHtml, "<select")).toBe(0);
expect(addHtml).toContain('aria-label="Dienst oder Zugangstyp suchen"');
expect(addHtml).toContain('role="listbox"');
expect(addHtml).toContain('class="settings-account-picker-header"');
expect(addHtml).toContain("Dienst");
expect(addHtml).toContain("Typ/Funktion");
options.forEach((option) => expect(addHtml).toContain(`data-account-option-id="${option.id}"`));
expect(addHtml).toContain('data-account-option-id="megadebrid-api"');
expect(addHtml).toContain('aria-selected="true"');
expect(addHtml).toContain("Weiteren Account hinzufügen");
expect(addHtml).toContain("Login:Passwort");
expect(addHtml).not.toContain('type="search"');
expect(addHtml).not.toContain("Account-Typ filtern");
expect(addHtml).not.toContain("settings-account-picker-row");
expect(addHtml).toContain('type="search"');
expect(addHtml).toContain("settings-account-picker-row");
expect(count(addHtml, 'class="settings-account-dialog-fields"')).toBe(1);
expect(addHtml.indexOf('aria-label="Dienst / Zugangstyp"')).toBeLessThan(addHtml.indexOf("settings-account-option-meta"));
expect(addHtml.indexOf("settings-account-option-meta")).toBeLessThan(addHtml.indexOf("settings-account-dialog-fields"));
expect(addHtml.indexOf('aria-label="Dienst oder Zugangstyp suchen"')).toBeLessThan(addHtml.indexOf("settings-account-picker-table"));
expect(addHtml.indexOf("settings-account-picker-table")).toBeLessThan(addHtml.indexOf("settings-account-dialog-fields"));
expect(editHtml).toContain("Account bearbeiten");
expect(editHtml).toContain("member@example.test");
expect(editHtml).toContain("Entfernen");
@@ -631,7 +688,7 @@ describe("account workspace", () => {
expect(count(editHtml, "type=\"password\"")).toBe(2);
});
it("selects the account option through the single service selector", () => {
it("selects the account option through the compact service table", () => {
const selected: string[] = [];
const tree = AccountAddDialog({
actions: {
@@ -653,12 +710,24 @@ describe("account workspace", () => {
busy: false
}
});
const selector = findElement(tree, (element) => element.type === "select" && element.props["aria-label"] === "Dienst / Zugangstyp");
const selector = findElement(tree, (element) => element.props["data-account-option-id"] === "debridlink-api");
selector.props.onChange({ target: { value: "debridlink-api" } });
selector.props.onClick();
expect(selected).toEqual(["debridlink-api"]);
});
it("keeps stored usernames separate from provider email addresses", () => {
const rows = projectAccountRows(accountSources(), [], NOW);
expect(rows[0].username).toBe("stored-user");
expect(rows[0].email).toBe("verified@example.test");
expect(ACCOUNT_COLUMNS).toContain("E-Mail");
const html = renderToStaticMarkup(<AccountWorkspace actions={workspaceActions()} model={workspaceModel()} />);
expect(html).toContain("stored-user");
expect(html).toContain("verified@example.test");
});
});
describe("settings App integration", () => {