release: publish v2.0.21 responsive accessibility audit
Refine all primary views across supported desktop widths, preserve compact download actions, strengthen keyboard and assistive semantics, confirm irreversible actions, complete new bilingual UI strings, and unify live speed visualization colors.
This commit is contained in:
@@ -1,4 +1,5 @@
|
||||
import { isValidElement, type ReactElement, type ReactNode } from "react";
|
||||
import { readFileSync } from "node:fs";
|
||||
import { isValidElement, type ReactElement, type ReactNode } from "react";
|
||||
import { renderToStaticMarkup } from "react-dom/server";
|
||||
import { describe, expect, it } from "vitest";
|
||||
import {
|
||||
@@ -185,7 +186,7 @@ describe("CollectorView", () => {
|
||||
expect(empty).not.toContain("aria-label=\"Seitennavigation\"");
|
||||
});
|
||||
|
||||
it("renders compact occupied rows and removes the empty marker", () => {
|
||||
it("renders compact occupied rows and removes the empty marker", () => {
|
||||
const html = renderToStaticMarkup(
|
||||
<CollectorView
|
||||
actions={createActions()}
|
||||
@@ -199,10 +200,58 @@ describe("CollectorView", () => {
|
||||
expect(html).toContain("data-visual-region=\"collector-toolbar\"");
|
||||
expect(html).toContain("data-visual-region=\"collector-table-body\"");
|
||||
expect(html).not.toContain("data-visual-region=\"downloads-toolbar\"");
|
||||
expect(html).not.toContain("aria-label=\"Seitennavigation\"");
|
||||
});
|
||||
|
||||
it("separates local input, queue submission, search, selection and local removal callbacks", () => {
|
||||
expect(html).not.toContain("aria-label=\"Seitennavigation\"");
|
||||
});
|
||||
|
||||
it("gives every row checkbox a unique accessible name with its link and collection", () => {
|
||||
const content = CollectorContent({
|
||||
actions: createActions(),
|
||||
model: buildCollectorViewModel(populatedTabs, "tab-a", "", false, [])
|
||||
});
|
||||
const labels: string[] = [];
|
||||
visitElements(content, (element) => {
|
||||
if (element.type === "input" && element.props.type === "checkbox") {
|
||||
labels.push(element.props["aria-label"]);
|
||||
}
|
||||
});
|
||||
|
||||
expect(labels).toEqual([
|
||||
"https://example.test/a aus Sammlung A, Zeile 1 auswählen",
|
||||
"https://example.test/b aus Sammlung A, Zeile 3 auswählen"
|
||||
]);
|
||||
expect(new Set(labels).size).toBe(labels.length);
|
||||
});
|
||||
|
||||
it("uses a high-contrast table heading token in both themes", () => {
|
||||
const css = readFileSync(new URL("../src/renderer/views/collector/collector.css", import.meta.url), "utf8");
|
||||
|
||||
expect(css).toMatch(/\.collector-table-header-row\s*{[^}]*color:\s*var\(--ui-text-secondary\);/s);
|
||||
});
|
||||
|
||||
it("uses the semantic danger text token for the removal action", () => {
|
||||
const css = readFileSync(new URL("../src/renderer/views/collector/collector.css", import.meta.url), "utf8");
|
||||
|
||||
expect(css).toMatch(/\.collector-action-danger:not\(:disabled\)\s*{[^}]*color:\s*var\(--ui-danger-text\);/s);
|
||||
});
|
||||
|
||||
it("disables queue submission only when the active collection has no links", () => {
|
||||
const emptyActive = CollectorToolbar({
|
||||
actions: createActions(),
|
||||
model: buildCollectorViewModel([
|
||||
{ id: "tab-a", name: "Sammlung A", text: "" },
|
||||
{ id: "tab-b", name: "Sammlung B", text: "https://example.test/b" }
|
||||
], "tab-a", "", false, [])
|
||||
});
|
||||
const filteredActive = CollectorToolbar({
|
||||
actions: createActions(),
|
||||
model: buildCollectorViewModel(populatedTabs, "tab-a", "kein-treffer", false, [])
|
||||
});
|
||||
|
||||
expect(findButton(emptyActive, "An Downloads übergeben").props.disabled).toBe(true);
|
||||
expect(findButton(filteredActive, "An Downloads übergeben").props.disabled).toBe(false);
|
||||
});
|
||||
|
||||
it("separates local input, queue submission, search, selection and local removal callbacks", () => {
|
||||
let inputOpens = 0;
|
||||
let queueSubmits = 0;
|
||||
let query = "";
|
||||
@@ -235,7 +284,7 @@ describe("CollectorView", () => {
|
||||
search.props.onChange({ target: { value: "release" } });
|
||||
expect(query).toBe("release");
|
||||
|
||||
const checkbox = findElement(content, (element) => element.type === "input" && element.props["aria-label"] === "Link auswählen");
|
||||
const checkbox = findElement(content, (element) => element.type === "input" && element.props.type === "checkbox");
|
||||
checkbox.props.onChange();
|
||||
findButton(toolbar, "Auswahl entfernen").props.onClick();
|
||||
expect(selected).toBe("tab-a:0");
|
||||
@@ -243,7 +292,7 @@ describe("CollectorView", () => {
|
||||
expect(queueSubmits).toBe(1);
|
||||
});
|
||||
|
||||
it("names the input dialog and commits only through the local draft callback", () => {
|
||||
it("names the input dialog and commits only through the local draft callback", () => {
|
||||
let value = "";
|
||||
let commits = 0;
|
||||
const dialog = CollectorInputDialog({
|
||||
@@ -265,6 +314,13 @@ describe("CollectorView", () => {
|
||||
textbox.props.onChange({ target: { value: "https://example.test/new" } });
|
||||
findButton(dialog, "Übernehmen").props.onClick();
|
||||
expect(value).toBe("https://example.test/new");
|
||||
expect(commits).toBe(1);
|
||||
});
|
||||
});
|
||||
expect(commits).toBe(1);
|
||||
});
|
||||
|
||||
it("moves the search field onto a separate compact row instead of overlapping actions", () => {
|
||||
const css = readFileSync(new URL("../src/renderer/views/collector/collector.css", import.meta.url), "utf8");
|
||||
|
||||
expect(css).toMatch(/@media \(max-width: 1120px\)[\s\S]*\.collector-toolbar\s*\{[^}]*flex-wrap:\s*wrap;/s);
|
||||
expect(css).toMatch(/@media \(max-width: 1120px\)[\s\S]*\.collector-toolbar \.ui-toolbar-search\s*\{[^}]*flex:\s*1 0 100%;[^}]*width:\s*100%;/s);
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user