feat: deliver the redesigned desktop workspace
Rebuild downloads, link collection, settings, history, and statistics around a responsive desktop shell with compact account and queue tables, contextual navigation, persistent update affordances, unified overlays, and accessible keyboard interactions. Add safe history-folder reveal IPC, responsive 2560/1920/1366/1120 coverage, deterministic visual fixtures, focused component regressions, and release-tree exclusions for internal working files. Bump the public application version to 2.0.13.
This commit is contained in:
@@ -0,0 +1,102 @@
|
||||
import { renderToStaticMarkup } from "react-dom/server";
|
||||
import { describe, expect, it, vi } from "vitest";
|
||||
import {
|
||||
clampContextMenuPosition,
|
||||
ContextMenu,
|
||||
getContextMenuKeyboardAction,
|
||||
getContextMenuSubmenuKeyboardAction,
|
||||
getContextSubmenuPosition
|
||||
} from "../src/renderer/ui/ContextMenu";
|
||||
|
||||
describe("ContextMenu", () => {
|
||||
it("renders menu semantics and marks buttons as menu items", () => {
|
||||
const html = renderToStaticMarkup(
|
||||
<ContextMenu ariaLabel="Aktionen" onClose={() => {}} open x={40} y={60}>
|
||||
<button>Öffnen</button>
|
||||
<button disabled>Gesperrt</button>
|
||||
</ContextMenu>
|
||||
);
|
||||
|
||||
expect(html).toContain("role=\"menu\"");
|
||||
expect(html).toContain("aria-label=\"Aktionen\"");
|
||||
expect(html.match(/role=\"menuitem\"/g)).toHaveLength(2);
|
||||
expect(html).toContain("tabindex=\"-1\"");
|
||||
});
|
||||
|
||||
it("server-renders without layout-effect warnings", () => {
|
||||
const error = vi.spyOn(console, "error").mockImplementation(() => {});
|
||||
|
||||
renderToStaticMarkup(
|
||||
<ContextMenu onClose={() => {}} open x={0} y={0}>
|
||||
<button>Öffnen</button>
|
||||
</ContextMenu>
|
||||
);
|
||||
|
||||
expect(error).not.toHaveBeenCalled();
|
||||
error.mockRestore();
|
||||
});
|
||||
|
||||
it("clamps every edge to the visible viewport", () => {
|
||||
expect(clampContextMenuPosition(790, 590, 220, 180, 800, 600)).toEqual({ x: 580, y: 420 });
|
||||
expect(clampContextMenuPosition(-12, -8, 220, 180, 800, 600)).toEqual({ x: 0, y: 0 });
|
||||
expect(clampContextMenuPosition(40, 60, 220, 180, 800, 600)).toEqual({ x: 40, y: 60 });
|
||||
});
|
||||
|
||||
it("navigates enabled items, activates Enter and closes only the menu on Escape", () => {
|
||||
const enabled = [true, false, true, true];
|
||||
|
||||
expect(getContextMenuKeyboardAction("ArrowDown", 0, enabled)).toEqual({ type: "focus", index: 2 });
|
||||
expect(getContextMenuKeyboardAction("ArrowDown", 3, enabled)).toEqual({ type: "focus", index: 0 });
|
||||
expect(getContextMenuKeyboardAction("ArrowUp", 0, enabled)).toEqual({ type: "focus", index: 3 });
|
||||
expect(getContextMenuKeyboardAction("Home", 3, enabled)).toEqual({ type: "focus", index: 0 });
|
||||
expect(getContextMenuKeyboardAction("End", 0, enabled)).toEqual({ type: "focus", index: 3 });
|
||||
expect(getContextMenuKeyboardAction("Enter", 2, enabled)).toEqual({ type: "activate", index: 2 });
|
||||
expect(getContextMenuKeyboardAction("Escape", 2, enabled)).toEqual({ type: "close" });
|
||||
expect(getContextMenuKeyboardAction("ArrowDown", -1, [false, false])).toBeNull();
|
||||
});
|
||||
|
||||
it("opens and leaves submenus with standard keyboard commands", () => {
|
||||
expect(getContextMenuSubmenuKeyboardAction("Enter", true, false)).toBe("open");
|
||||
expect(getContextMenuSubmenuKeyboardAction("ArrowRight", true, false)).toBe("open");
|
||||
expect(getContextMenuSubmenuKeyboardAction("ArrowLeft", false, true)).toBe("close");
|
||||
expect(getContextMenuSubmenuKeyboardAction("Escape", false, true)).toBe("close");
|
||||
expect(getContextMenuSubmenuKeyboardAction("ArrowDown", true, false)).toBeNull();
|
||||
});
|
||||
|
||||
it("renders nested priority choices as an announced submenu", () => {
|
||||
const html = renderToStaticMarkup(
|
||||
<ContextMenu onClose={() => {}} open x={0} y={0}>
|
||||
<div className="ctx-menu-sub">
|
||||
<button aria-haspopup="menu">Priorität</button>
|
||||
<div className="ctx-menu-sub-items" role="menu">
|
||||
<button>Hoch</button>
|
||||
<button>Standard</button>
|
||||
<button>Niedrig</button>
|
||||
</div>
|
||||
</div>
|
||||
</ContextMenu>
|
||||
);
|
||||
|
||||
expect(html).toContain("aria-haspopup=\"menu\"");
|
||||
expect(html.match(/role=\"menu\"/g)).toHaveLength(2);
|
||||
expect(html.match(/role=\"menuitem\"/g)).toHaveLength(4);
|
||||
});
|
||||
|
||||
it("places submenus inside the viewport on every edge", () => {
|
||||
expect(getContextSubmenuPosition(
|
||||
{ left: 700, right: 790, top: 40 },
|
||||
{ width: 180, height: 150 },
|
||||
{ width: 800, height: 600 }
|
||||
)).toEqual({ x: 520, y: 40 });
|
||||
expect(getContextSubmenuPosition(
|
||||
{ left: 8, right: 98, top: 40 },
|
||||
{ width: 180, height: 150 },
|
||||
{ width: 800, height: 600 }
|
||||
)).toEqual({ x: 98, y: 40 });
|
||||
expect(getContextSubmenuPosition(
|
||||
{ left: 500, right: 590, top: 560 },
|
||||
{ width: 180, height: 150 },
|
||||
{ width: 800, height: 600 }
|
||||
)).toEqual({ x: 590, y: 450 });
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user