fix: stabilize narrow-window telemetry UI

Collect bandwidth history for the full running session, keep responsive service and status labels readable, repair nested menu overflow, and correct semantic state colors.

Update active queue counters immediately for terminal items, add localized integer formatting, eliminate progress label overlap, and cover the regressions with focused tests.
This commit is contained in:
Sucukdeluxe
2026-08-10 21:06:09 +02:00
parent b670b92147
commit 7dce19119f
15 changed files with 195 additions and 69 deletions
+7
View File
@@ -30,6 +30,13 @@ describe("desktop shell", () => {
expect(shellCss).toMatch(/\.md-application-menu-tree > \.menu-bar-item > \.menu-dropdown\s*\{[^}]*right:\s*0;[^}]*left:\s*auto;/s);
});
it("keeps application submenus visible inside narrow right-aligned windows", () => {
const shellCss = readFileSync(new URL("../src/renderer/shell/shell.css", import.meta.url), "utf8");
expect(shellCss).toMatch(/\.md-application-menu-tree :where\(\.menu-dropdown, \.menu-submenu-dropdown\)\s*\{[^}]*overflow:\s*visible;/s);
expect(shellCss).toMatch(/\.md-application-menu-tree \.menu-submenu-dropdown\s*\{[^}]*right:\s*100%;[^}]*left:\s*auto;/s);
});
it("uses the product asset in the header brand", () => {
const html = renderToStaticMarkup(<AppHeader activeView="downloads" actions={null} onViewChange={() => {}} />);
expect(html).toContain("Multi-Debrid Downloader");
+35 -2
View File
@@ -10,6 +10,7 @@ import {
buildDownloadsViewModel,
classifyDownloadStatus,
getDownloadQueueTotalBytes,
getPendingDownloadItemCount,
getDownloadSpeedBps,
type DownloadSidebarFilter,
type DownloadsModelInput
@@ -33,7 +34,7 @@ import {
downloadColumnDefinitions,
getAvailabilitySummary
} from "../src/renderer/views/downloads/DownloadsTable";
import { normalizeDownloadServiceLabel } from "../src/renderer/download-format";
import { compactDownloadServiceLabel, normalizeDownloadServiceLabel } from "../src/renderer/download-format";
import { getRollingMetricDirection } from "../src/renderer/ui/RollingMetricValue";
const now = new Date(2026, 7, 10, 12, 0, 0, 0).getTime();
@@ -125,6 +126,28 @@ describe("Download-Gesamtgröße", () => {
});
});
describe("laufender Queue-Linkzähler", () => {
it("sinkt sofort, sobald eine Unterdatei abgeschlossen ist", () => {
const items = [
item("queued", "package-a", "queued"),
item("active", "package-a", "downloading"),
item("done", "package-a", "completed"),
item("failed", "package-a", "failed")
];
expect(getPendingDownloadItemCount(items)).toBe(2);
});
it("formatiert große Sidebar-Zähler mit deutschen Tausenderpunkten", () => {
const model = withRuntime(createInput(), {
status: { ...withRuntime(createInput()).status, packages: 130, links: 2484 }
});
const html = renderToStaticMarkup(<DownloadsSidebarStatus model={model} />);
expect(html).toContain(">2.484<");
});
});
describe("responsive Downloadstatus und Servicebezeichnungen", () => {
it("keeps full status details while providing compact table text", () => {
expect(compactDownloadStatus("Link wird umgewandelt")).toBe("Umwandeln");
@@ -137,6 +160,8 @@ describe("responsive Downloadstatus und Servicebezeichnungen", () => {
expect(normalizeDownloadServiceLabel("Mega-Debrid Web (Web Account)")).toBe("Mega-Debrid Web");
expect(normalizeDownloadServiceLabel("Mega-Debrid API (API Account)")).toBe("Mega-Debrid API");
expect(normalizeDownloadServiceLabel("Real-Debrid (Web Account)")).toBe("Real-Debrid (Web Account)");
expect(normalizeDownloadServiceLabel("Mega-Debrid Web (Web Account), Mega-Debrid API (API Account)")).toBe("Mega-Debrid Web, Mega-Debrid API");
expect(compactDownloadServiceLabel("Mega-Debrid Web (Web Account), Mega-Debrid API (API Account)")).toBe("Mega-Debrid");
});
});
@@ -622,7 +647,14 @@ describe("downloads view", () => {
expect(css).toMatch(/\.downloads-package-items\s*\{[^}]*height:\s*auto;[^}]*overflow:\s*hidden;/s);
expect(css).toMatch(/\.downloads-package-items\.is-collapsed\s*\{[^}]*height:\s*0;[^}]*opacity:\s*0;[^}]*pointer-events:\s*none;/s);
expect(css).toMatch(/\.downloads-meter-label\.is-track\s*\{[^}]*color:\s*var\(--ui-progress-track-text/s);
expect(css).toMatch(/\.downloads-meter-label\s*\{[^}]*font-weight:\s*700;/s);
expect(css).toMatch(/\.downloads-meter-label\.is-track\s*\{[^}]*clip-path:\s*inset\(0 0 0 var\(--downloads-progress\)\);/s);
expect(css).toMatch(/\.downloads-meter-label\.is-filled\s*\{[^}]*color:\s*var\(--ui-progress-fill-text/s);
expect(css).toMatch(/\.downloads-link-state\.online\s*\{[^}]*background:\s*var\(--ui-success\);/s);
expect(css).toMatch(/\.downloads-status-cell\s*\{[^}]*container-type:\s*inline-size;/s);
expect(css).toMatch(/\.downloads-service-cell\s*\{[^}]*container-type:\s*inline-size;/s);
expect(css).toMatch(/@container\s*\(max-width:\s*150px\)[\s\S]*\.downloads-status-full[^{]*\{[^}]*display:\s*none;[\s\S]*\.downloads-status-compact[^{]*\{[^}]*display:\s*inline;/s);
expect(css).toMatch(/@container\s*\(max-width:\s*150px\)[\s\S]*\.downloads-service-full[^{]*\{[^}]*display:\s*none;[\s\S]*\.downloads-service-compact[^{]*\{[^}]*display:\s*inline;/s);
expect(readFileSync(new URL("../src/renderer/views/downloads/DownloadsTable.tsx", import.meta.url), "utf8")).toMatch(/\.animate\(\[\{ height: "0px", opacity: 0 \}, \{ height: `\$\{targetHeight\}px`, opacity: 1 \}\]/);
expect(css).toMatch(/\.downloads-footer\s*\{[^}]*height:\s*60px;[^}]*padding:\s*0 12px 0 60px;/s);
expect(css).toMatch(/\.downloads-package-card\s*\{[^}]*border:\s*0;[^}]*border-bottom:\s*1px solid color-mix\(in srgb, var\(--ui-border\) 72%, transparent\);[^}]*padding:\s*0;/s);
@@ -800,7 +832,8 @@ describe("download table row contracts", () => {
expect(html).toContain('class="downloads-status-compact"');
expect(html).toContain("DL läuft");
expect(html).toContain('title="Mega-Debrid Web (Web Account)"');
expect(html).toContain(">Mega-Debrid Web</span>");
expect(html).toContain('class="downloads-service-full">Mega-Debrid Web</span>');
expect(html).toContain('class="downloads-service-compact">Mega-Debrid</span>');
});
it("sets the whole visible selection atomically from the header checkbox", () => {
+1
View File
@@ -697,6 +697,7 @@ describe("settings geometry", () => {
expect(css).toMatch(/\.settings-form-column\s*{[^}]*width:\s*500px;[^}]*max-width:\s*100%;/s);
expect(css).toMatch(/\.settings-control\s*{[^}]*height:\s*44px;[^}]*border-radius:\s*6px;/s);
expect(css).toMatch(/\.settings-switch\s*{[^}]*width:\s*40px;[^}]*height:\s*20px;/s);
expect(css).toMatch(/\.settings-switch\.is-on\s*{[^}]*border-color:\s*var\(--ui-success\);[^}]*background:\s*var\(--ui-success\);/s);
expect(css).toMatch(/\.settings-account-table-header\s*{[^}]*height:\s*41px;/s);
expect(css).toMatch(/\.settings-account-table-header\s*{[^}]*overflow:\s*hidden;/s);
expect(css).toMatch(/\.settings-account-table-grid\s*{[^}]*color:\s*var\(--ui-text\);/s);
+26 -2
View File
@@ -3,7 +3,7 @@ import { isValidElement, type ReactElement, type ReactNode } from "react";
import { renderToStaticMarkup } from "react-dom/server";
import { describe, expect, it } from "vitest";
import type { DownloadItem, DownloadStatus, UiSnapshot } from "../src/shared/types";
import { readBandwidthChartPalette } from "../src/renderer/App";
import { appendBandwidthSample, readBandwidthChartPalette } from "../src/renderer/App";
import {
buildStatisticsViewModel,
type StatisticsMetric,
@@ -17,7 +17,22 @@ import {
} from "../src/renderer/views/statistics/StatisticsView";
import { createVisualFixture } from "./visual/fixtures";
const now = new Date(2026, 7, 10, 12, 0, 0, 0).getTime();
const now = new Date(2026, 7, 10, 12, 0, 0, 0).getTime();
describe("bandwidth sampling", () => {
it("keeps the latest minute and normalizes invalid speeds", () => {
const history = [
{ time: now - 61000, speed: 1 },
{ time: now - 59000, speed: 2 }
];
expect(appendBandwidthSample(history, Number.NaN, now)).toEqual([
{ time: now - 59000, speed: 2 },
{ time: now, speed: 0 }
]);
expect(history).toHaveLength(2);
});
});
function createSnapshot(): UiSnapshot {
return structuredClone(createVisualFixture("empty").snapshot);
@@ -385,6 +400,15 @@ describe("statistics view", () => {
});
describe("bandwidth chart palette", () => {
it("collects bandwidth samples from the mounted application instead of the statistics tab", () => {
const source = readFileSync(new URL("../src/renderer/App.tsx", import.meta.url), "utf8");
const chartBlock = source.slice(source.indexOf("const BandwidthChart"), source.indexOf("interface DownloadSpeedSparklineProps"));
expect(source).toContain("appendBandwidthSample(speedHistoryRef.current, liveDownloadSpeedBps");
expect(chartBlock).not.toContain("history.push");
expect(chartBlock).not.toContain('item.status === "downloading"');
});
it("defines semantic speed and progress text colors for both themes", () => {
const css = readFileSync(new URL("../src/renderer/theme.css", import.meta.url), "utf8");
const dark = css.match(/:root,\s*:root\[data-theme="dark"\]\s*\{([\s\S]*?)\}/)?.[1];