feat(design): reorder without a drag everywhere a grip drags (WCAG 2.5.7, E28 S3e4a)
One rule, two forms: useOrderItems (Top / Up / Down / Bottom rows of a row menu) and OrderButtons (four buttons with the place, or a compact Up / Down pair). A move that would go nowhere is shown off. - YouTube playlist rows: the four moves now come from useOrderItems. - Plex playlist rows: a block among the blocks, an episode within its show, off while a write is in flight; drag and menu share moveBlock/moveEpisode. - Saved views editor: the selected view's four buttons (owner: A), and the grip's keyboard pick-up (its instructions promised one it did not have). - Side panel groups in edit mode: an Up / Down pair (owner: A); a group the stored order does not hold yet moves too; the name wraps there. - The grip's touch-action: none is now test-held.
This commit is contained in:
@@ -0,0 +1,74 @@
|
||||
// @vitest-environment jsdom
|
||||
//
|
||||
// The non-drag reorder (WCAG 2.5.7): the four menu rows and the four buttons, one rule — a move that
|
||||
// would go nowhere is shown off, and the rest report the index the item lands on.
|
||||
import { describe, it, expect, vi, afterEach } from "vitest";
|
||||
import { render, renderHook, screen, fireEvent, cleanup } from "@testing-library/react";
|
||||
|
||||
vi.mock("react-i18next", async (importOriginal) => {
|
||||
const actual = await importOriginal<typeof import("react-i18next")>();
|
||||
return {
|
||||
...actual,
|
||||
useTranslation: () => ({
|
||||
t: (key: string, o?: { pos?: number; total?: number }) =>
|
||||
o?.pos !== undefined ? `${key}:${o.pos}/${o.total}` : key,
|
||||
}),
|
||||
};
|
||||
});
|
||||
|
||||
import { OrderButtons } from "./OrderButtons";
|
||||
import { useOrderItems } from "./useOrderItems";
|
||||
|
||||
afterEach(cleanup);
|
||||
|
||||
describe("useOrderItems", () => {
|
||||
const rows = (...a: Parameters<ReturnType<typeof useOrderItems>>) =>
|
||||
renderHook(() => useOrderItems()).result.current(...a);
|
||||
|
||||
it("offers top, up, down and bottom, each reporting where the item lands", () => {
|
||||
const onMove = vi.fn();
|
||||
const items = rows(2, 5, onMove);
|
||||
expect(items.map((i) => i.id)).toEqual(["order-top", "order-up", "order-down", "order-bottom"]);
|
||||
expect(items.map((i) => i.label)).toEqual([
|
||||
"common.order.top",
|
||||
"common.order.up",
|
||||
"common.order.down",
|
||||
"common.order.bottom",
|
||||
]);
|
||||
expect(items[0]!.separatorBefore).toBe(true);
|
||||
items.forEach((i) => i.onSelect!());
|
||||
expect(onMove.mock.calls.map((c) => c[0])).toEqual([0, 1, 3, 4]);
|
||||
});
|
||||
|
||||
it("turns off the moves that go nowhere: the first item up, the last down", () => {
|
||||
expect(rows(0, 3, vi.fn()).map((i) => !!i.disabled)).toEqual([true, true, false, false]);
|
||||
expect(rows(2, 3, vi.fn()).map((i) => !!i.disabled)).toEqual([false, false, true, true]);
|
||||
});
|
||||
|
||||
it("turns all four off with the reason under the first row only", () => {
|
||||
const items = rows(1, 3, vi.fn(), { reason: "sorted" });
|
||||
expect(items.every((i) => i.disabled)).toBe(true);
|
||||
expect(items.map((i) => ("note" in i ? i.note : undefined))).toEqual([
|
||||
"sorted",
|
||||
undefined,
|
||||
undefined,
|
||||
undefined,
|
||||
]);
|
||||
});
|
||||
});
|
||||
|
||||
describe("OrderButtons", () => {
|
||||
it("shows the item's place, moves it, and holds the moves that go nowhere off", () => {
|
||||
const onMove = vi.fn();
|
||||
render(<OrderButtons from={0} count={3} onMove={onMove} />);
|
||||
expect(screen.getByRole("group").getAttribute("aria-label")).toBe(
|
||||
"common.order.label common.order.place:1/3",
|
||||
);
|
||||
const btn = (name: string) => screen.getByRole("button", { name }) as HTMLButtonElement;
|
||||
expect(btn("common.order.top").disabled).toBe(true);
|
||||
expect(btn("common.order.up").disabled).toBe(true);
|
||||
fireEvent.click(btn("common.order.down"));
|
||||
fireEvent.click(btn("common.order.bottom"));
|
||||
expect(onMove.mock.calls.map((c) => c[0])).toEqual([1, 2]);
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user