release: ship v2.0.23 queue and account reliability fixes
Keep active packages in activation order, remove status-driven package expansion, refresh parked Mega-Debrid work after account-pool changes, normalize RapidGator aliases, aggregate missed update notes, isolate new account credentials, and update regression coverage and release documentation.
This commit is contained in:
+186
-174
@@ -1,144 +1,144 @@
|
||||
import { readFileSync } from "node:fs";
|
||||
import { renderToStaticMarkup } from "react-dom/server";
|
||||
import { describe, expect, it } from "vitest";
|
||||
import { runLatestUpdateCheck, shouldApplyUpdateCheckResult } from "../src/renderer/App";
|
||||
import type { UpdateCheckResult } from "../src/shared/types";
|
||||
import { AppHeader } from "../src/renderer/shell/AppHeader";
|
||||
import { getUpdateDialogFocusTarget, UpdateExperience } from "../src/renderer/shell/UpdateExperience";
|
||||
|
||||
const callbacks = {
|
||||
onOpen: () => {},
|
||||
onClose: () => {},
|
||||
onInstall: () => {},
|
||||
onLater: () => {}
|
||||
};
|
||||
|
||||
describe("update experience", () => {
|
||||
it("renders the available update and prompt as one accessible experience", () => {
|
||||
const html = renderToStaticMarkup(
|
||||
<UpdateExperience
|
||||
available
|
||||
currentVersion="v2.0.12"
|
||||
latestTag="v9.9.9"
|
||||
open
|
||||
progress={0}
|
||||
releaseNotes="Changes"
|
||||
state="prompt"
|
||||
{...callbacks}
|
||||
/>
|
||||
);
|
||||
|
||||
expect(html).toContain("aria-label=\"Update verfügbar\"");
|
||||
expect(html).toContain("role=\"tooltip\"");
|
||||
expect(html).toContain("Eine neue Version ist bereit. Klicke hier, um sie zu installieren.");
|
||||
expect(html).toContain("role=\"dialog\"");
|
||||
expect(html).toContain("aria-modal=\"true\"");
|
||||
expect(html).toContain("Update installieren");
|
||||
expect(html).toContain("Jetzt aktualisieren");
|
||||
expect(html).toContain("Später");
|
||||
expect(html).toContain("Changes");
|
||||
expect(html).toContain("<details");
|
||||
});
|
||||
|
||||
it("keeps the update affordance but removes the dialog when the prompt is closed", () => {
|
||||
const html = renderToStaticMarkup(
|
||||
<UpdateExperience
|
||||
available
|
||||
currentVersion="v2.0.12"
|
||||
latestTag="v9.9.9"
|
||||
open={false}
|
||||
progress={0}
|
||||
releaseNotes="Changes"
|
||||
state="prompt"
|
||||
{...callbacks}
|
||||
/>
|
||||
);
|
||||
|
||||
expect(html).toContain("aria-label=\"Update verfügbar\"");
|
||||
expect(html).not.toContain("role=\"dialog\"");
|
||||
});
|
||||
|
||||
it("renders active progress without controls that could close the installation", () => {
|
||||
const html = renderToStaticMarkup(
|
||||
<UpdateExperience
|
||||
available
|
||||
currentVersion="v2.0.12"
|
||||
latestTag="v9.9.9"
|
||||
open
|
||||
progress={{ percent: 47, text: "Update-Download: 47% (47 MB / 100 MB)" }}
|
||||
releaseNotes=""
|
||||
state="downloading"
|
||||
{...callbacks}
|
||||
/>
|
||||
);
|
||||
|
||||
expect(html).toContain("Update-Download: 47% (47 MB / 100 MB)");
|
||||
expect(html).toContain("aria-valuenow=\"47\"");
|
||||
expect(html).not.toContain("Später");
|
||||
expect(html).not.toContain("Jetzt aktualisieren");
|
||||
expect(html).not.toContain("aria-label=\"Schließen\"");
|
||||
});
|
||||
|
||||
it("preserves the original installation error in the reusable dialog", () => {
|
||||
const html = renderToStaticMarkup(
|
||||
<UpdateExperience
|
||||
available
|
||||
currentVersion="v2.0.12"
|
||||
latestTag="v9.9.9"
|
||||
open
|
||||
progress={{ percent: null, text: "Update-Fehler: Originale Prüfsummenmeldung" }}
|
||||
releaseNotes=""
|
||||
state="error"
|
||||
{...callbacks}
|
||||
/>
|
||||
);
|
||||
|
||||
expect(html).toContain("Update-Fehler: Originale Prüfsummenmeldung");
|
||||
expect(html).toContain("aria-label=\"Schließen\"");
|
||||
});
|
||||
|
||||
it("renders nothing when no update is available and no dialog is active", () => {
|
||||
const html = renderToStaticMarkup(
|
||||
<UpdateExperience
|
||||
available={false}
|
||||
currentVersion="v2.0.12"
|
||||
latestTag=""
|
||||
open={false}
|
||||
progress={0}
|
||||
releaseNotes=""
|
||||
state="prompt"
|
||||
{...callbacks}
|
||||
/>
|
||||
);
|
||||
|
||||
expect(html).toBe("");
|
||||
});
|
||||
|
||||
it("places the update affordance in the accessible global header action group", () => {
|
||||
const html = renderToStaticMarkup(
|
||||
<AppHeader
|
||||
activeView="downloads"
|
||||
actions={(
|
||||
<UpdateExperience
|
||||
available
|
||||
currentVersion="v2.0.12"
|
||||
latestTag="v9.9.9"
|
||||
open={false}
|
||||
progress={0}
|
||||
releaseNotes=""
|
||||
state="prompt"
|
||||
{...callbacks}
|
||||
/>
|
||||
)}
|
||||
onViewChange={() => {}}
|
||||
/>
|
||||
);
|
||||
|
||||
expect(html).toContain("role=\"group\"");
|
||||
expect(html).toContain("aria-label=\"Globale Aktionen\"");
|
||||
expect(html).toContain("aria-label=\"Update verfügbar\"");
|
||||
});
|
||||
|
||||
import { readFileSync } from "node:fs";
|
||||
import { renderToStaticMarkup } from "react-dom/server";
|
||||
import { describe, expect, it } from "vitest";
|
||||
import { runLatestUpdateCheck, shouldApplyUpdateCheckResult } from "../src/renderer/App";
|
||||
import type { UpdateCheckResult } from "../src/shared/types";
|
||||
import { AppHeader } from "../src/renderer/shell/AppHeader";
|
||||
import { getUpdateDialogFocusTarget, UpdateExperience } from "../src/renderer/shell/UpdateExperience";
|
||||
|
||||
const callbacks = {
|
||||
onOpen: () => {},
|
||||
onClose: () => {},
|
||||
onInstall: () => {},
|
||||
onLater: () => {}
|
||||
};
|
||||
|
||||
describe("update experience", () => {
|
||||
it("renders the available update and prompt as one accessible experience", () => {
|
||||
const html = renderToStaticMarkup(
|
||||
<UpdateExperience
|
||||
available
|
||||
currentVersion="v2.0.12"
|
||||
latestTag="v9.9.9"
|
||||
open
|
||||
progress={0}
|
||||
releaseNotes="Changes"
|
||||
state="prompt"
|
||||
{...callbacks}
|
||||
/>
|
||||
);
|
||||
|
||||
expect(html).toContain("aria-label=\"Update verfügbar\"");
|
||||
expect(html).toContain("role=\"tooltip\"");
|
||||
expect(html).toContain("Eine neue Version ist bereit. Klicke hier, um sie zu installieren.");
|
||||
expect(html).toContain("role=\"dialog\"");
|
||||
expect(html).toContain("aria-modal=\"true\"");
|
||||
expect(html).toContain("Update installieren");
|
||||
expect(html).toContain("Jetzt aktualisieren");
|
||||
expect(html).toContain("Später");
|
||||
expect(html).toContain("Changes");
|
||||
expect(html).toContain("<details");
|
||||
});
|
||||
|
||||
it("keeps the update affordance but removes the dialog when the prompt is closed", () => {
|
||||
const html = renderToStaticMarkup(
|
||||
<UpdateExperience
|
||||
available
|
||||
currentVersion="v2.0.12"
|
||||
latestTag="v9.9.9"
|
||||
open={false}
|
||||
progress={0}
|
||||
releaseNotes="Changes"
|
||||
state="prompt"
|
||||
{...callbacks}
|
||||
/>
|
||||
);
|
||||
|
||||
expect(html).toContain("aria-label=\"Update verfügbar\"");
|
||||
expect(html).not.toContain("role=\"dialog\"");
|
||||
});
|
||||
|
||||
it("renders active progress without controls that could close the installation", () => {
|
||||
const html = renderToStaticMarkup(
|
||||
<UpdateExperience
|
||||
available
|
||||
currentVersion="v2.0.12"
|
||||
latestTag="v9.9.9"
|
||||
open
|
||||
progress={{ percent: 47, text: "Update-Download: 47% (47 MB / 100 MB)" }}
|
||||
releaseNotes=""
|
||||
state="downloading"
|
||||
{...callbacks}
|
||||
/>
|
||||
);
|
||||
|
||||
expect(html).toContain("Update-Download: 47% (47 MB / 100 MB)");
|
||||
expect(html).toContain("aria-valuenow=\"47\"");
|
||||
expect(html).not.toContain("Später");
|
||||
expect(html).not.toContain("Jetzt aktualisieren");
|
||||
expect(html).not.toContain("aria-label=\"Schließen\"");
|
||||
});
|
||||
|
||||
it("preserves the original installation error in the reusable dialog", () => {
|
||||
const html = renderToStaticMarkup(
|
||||
<UpdateExperience
|
||||
available
|
||||
currentVersion="v2.0.12"
|
||||
latestTag="v9.9.9"
|
||||
open
|
||||
progress={{ percent: null, text: "Update-Fehler: Originale Prüfsummenmeldung" }}
|
||||
releaseNotes=""
|
||||
state="error"
|
||||
{...callbacks}
|
||||
/>
|
||||
);
|
||||
|
||||
expect(html).toContain("Update-Fehler: Originale Prüfsummenmeldung");
|
||||
expect(html).toContain("aria-label=\"Schließen\"");
|
||||
});
|
||||
|
||||
it("renders nothing when no update is available and no dialog is active", () => {
|
||||
const html = renderToStaticMarkup(
|
||||
<UpdateExperience
|
||||
available={false}
|
||||
currentVersion="v2.0.12"
|
||||
latestTag=""
|
||||
open={false}
|
||||
progress={0}
|
||||
releaseNotes=""
|
||||
state="prompt"
|
||||
{...callbacks}
|
||||
/>
|
||||
);
|
||||
|
||||
expect(html).toBe("");
|
||||
});
|
||||
|
||||
it("places the update affordance in the accessible global header action group", () => {
|
||||
const html = renderToStaticMarkup(
|
||||
<AppHeader
|
||||
activeView="downloads"
|
||||
actions={(
|
||||
<UpdateExperience
|
||||
available
|
||||
currentVersion="v2.0.12"
|
||||
latestTag="v9.9.9"
|
||||
open={false}
|
||||
progress={0}
|
||||
releaseNotes=""
|
||||
state="prompt"
|
||||
{...callbacks}
|
||||
/>
|
||||
)}
|
||||
onViewChange={() => {}}
|
||||
/>
|
||||
);
|
||||
|
||||
expect(html).toContain("role=\"group\"");
|
||||
expect(html).toContain("aria-label=\"Globale Aktionen\"");
|
||||
expect(html).toContain("aria-label=\"Update verfügbar\"");
|
||||
});
|
||||
|
||||
it("uses the specified transient and modal elevation tokens", () => {
|
||||
const css = readFileSync(new URL("../src/renderer/shell/shell.css", import.meta.url), "utf8");
|
||||
|
||||
@@ -146,37 +146,49 @@ describe("update experience", () => {
|
||||
expect(css).toMatch(/\.md-update-dialog\s*\{[^}]*box-shadow:\s*0 12px 40px rgb\(0 0 0 \/ 45%\)/s);
|
||||
});
|
||||
|
||||
it("keeps forward and reverse tabbing inside the update dialog", () => {
|
||||
expect(getUpdateDialogFocusTarget(false, -1, 4)).toBe(0);
|
||||
expect(getUpdateDialogFocusTarget(true, -1, 4)).toBe(3);
|
||||
expect(getUpdateDialogFocusTarget(false, 3, 4)).toBe(0);
|
||||
expect(getUpdateDialogFocusTarget(true, 0, 4)).toBe(3);
|
||||
expect(getUpdateDialogFocusTarget(false, 1, 4)).toBeNull();
|
||||
expect(getUpdateDialogFocusTarget(false, -1, 0)).toBeNull();
|
||||
it("uses a light-blue update affordance and a bounded scrollable changelog", () => {
|
||||
const css = readFileSync(new URL("../src/renderer/shell/shell.css", import.meta.url), "utf8");
|
||||
const theme = readFileSync(new URL("../src/renderer/theme.css", import.meta.url), "utf8");
|
||||
|
||||
expect(theme).toMatch(/--ui-update:\s*#BAD0FC;/);
|
||||
expect(theme).toMatch(/--ui-update-hover:\s*#8AA5DC;/);
|
||||
expect(theme).toMatch(/--ui-update-text:\s*#181A1F;/);
|
||||
expect(css).toMatch(/\.md-update-trigger\s*\{[^}]*background:\s*var\(--ui-update\);[^}]*color:\s*var\(--ui-update-text\);/s);
|
||||
expect(css).toMatch(/\.md-update-trigger:hover\s*\{[^}]*background:\s*var\(--ui-update-hover\);/s);
|
||||
expect(css).toMatch(/\.md-update-release-notes pre\s*\{[^}]*max-height:\s*min\(360px, 45vh\);[^}]*overflow-y:\s*auto;/s);
|
||||
});
|
||||
|
||||
it("rejects stale update-check completions without discarding the latest state", () => {
|
||||
expect(shouldApplyUpdateCheckResult(4, 4)).toBe(true);
|
||||
expect(shouldApplyUpdateCheckResult(3, 4)).toBe(false);
|
||||
expect(shouldApplyUpdateCheckResult(4, 5)).toBe(false);
|
||||
});
|
||||
|
||||
it("applies only the latest result when update checks complete out of order", async () => {
|
||||
const generation = { current: 0 };
|
||||
const applied: string[] = [];
|
||||
let finishStartup: ((result: UpdateCheckResult) => void) | undefined;
|
||||
let finishManual: ((result: UpdateCheckResult) => void) | undefined;
|
||||
const startup = new Promise<UpdateCheckResult>((resolve) => { finishStartup = resolve; });
|
||||
const manual = new Promise<UpdateCheckResult>((resolve) => { finishManual = resolve; });
|
||||
const apply = (result: UpdateCheckResult): void => { applied.push(result.latestTag); };
|
||||
|
||||
const startupRun = runLatestUpdateCheck(generation, () => startup, apply);
|
||||
const manualRun = runLatestUpdateCheck(generation, () => manual, apply);
|
||||
finishManual?.({ updateAvailable: true, currentVersion: "2.0.12", latestVersion: "9.9.9", latestTag: "v9.9.9", releaseUrl: "https://example.test/v9.9.9" });
|
||||
await manualRun;
|
||||
finishStartup?.({ updateAvailable: false, currentVersion: "2.0.12", latestVersion: "2.0.12", latestTag: "v2.0.12", releaseUrl: "https://example.test/v2.0.12" });
|
||||
await startupRun;
|
||||
|
||||
expect(applied).toEqual(["v9.9.9"]);
|
||||
});
|
||||
});
|
||||
|
||||
it("keeps forward and reverse tabbing inside the update dialog", () => {
|
||||
expect(getUpdateDialogFocusTarget(false, -1, 4)).toBe(0);
|
||||
expect(getUpdateDialogFocusTarget(true, -1, 4)).toBe(3);
|
||||
expect(getUpdateDialogFocusTarget(false, 3, 4)).toBe(0);
|
||||
expect(getUpdateDialogFocusTarget(true, 0, 4)).toBe(3);
|
||||
expect(getUpdateDialogFocusTarget(false, 1, 4)).toBeNull();
|
||||
expect(getUpdateDialogFocusTarget(false, -1, 0)).toBeNull();
|
||||
});
|
||||
|
||||
it("rejects stale update-check completions without discarding the latest state", () => {
|
||||
expect(shouldApplyUpdateCheckResult(4, 4)).toBe(true);
|
||||
expect(shouldApplyUpdateCheckResult(3, 4)).toBe(false);
|
||||
expect(shouldApplyUpdateCheckResult(4, 5)).toBe(false);
|
||||
});
|
||||
|
||||
it("applies only the latest result when update checks complete out of order", async () => {
|
||||
const generation = { current: 0 };
|
||||
const applied: string[] = [];
|
||||
let finishStartup: ((result: UpdateCheckResult) => void) | undefined;
|
||||
let finishManual: ((result: UpdateCheckResult) => void) | undefined;
|
||||
const startup = new Promise<UpdateCheckResult>((resolve) => { finishStartup = resolve; });
|
||||
const manual = new Promise<UpdateCheckResult>((resolve) => { finishManual = resolve; });
|
||||
const apply = (result: UpdateCheckResult): void => { applied.push(result.latestTag); };
|
||||
|
||||
const startupRun = runLatestUpdateCheck(generation, () => startup, apply);
|
||||
const manualRun = runLatestUpdateCheck(generation, () => manual, apply);
|
||||
finishManual?.({ updateAvailable: true, currentVersion: "2.0.12", latestVersion: "9.9.9", latestTag: "v9.9.9", releaseUrl: "https://example.test/v9.9.9" });
|
||||
await manualRun;
|
||||
finishStartup?.({ updateAvailable: false, currentVersion: "2.0.12", latestVersion: "2.0.12", latestTag: "v2.0.12", releaseUrl: "https://example.test/v2.0.12" });
|
||||
await startupRun;
|
||||
|
||||
expect(applied).toEqual(["v9.9.9"]);
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user