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.
75 lines
2.9 KiB
TypeScript
75 lines
2.9 KiB
TypeScript
// @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]);
|
|
});
|
|
});
|