test(channels): cover ActionMenu keyboard-skip + stats ETA value; drop redundant disabledIndices
Deep-review follow-up on R13 S3. - Add a roving-keyboard-nav test proving a disabled ActionMenu item is skipped by arrow keys, and assert the stats "left" row renders the formatEta value (formatEta had no coverage anywhere). - Remove the disabledIndices plumbing from AnchoredPopover + ActionMenu: it was redundant. useListNavigation already skips items via their DOM disabled/aria-disabled attribute, and PopoverItem renders a native disabled button — verified against @floating-ui/react 0.27 isListIndexDisabled and by a mutation test. Behaviour is unchanged and now guarded by the new test.
This commit is contained in:
@@ -73,6 +73,43 @@ describe("ActionMenu", () => {
|
||||
expect(onSelect).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("skips a disabled item in roving keyboard nav", () => {
|
||||
// items[0] disabled, items[1] + items[2] enabled. Roving keyboard focus (the `tabindex=0` item,
|
||||
// which tracks useListNavigation's activeIndex) must NEVER land on the disabled index-0 — arrowing
|
||||
// skips straight over it. The skip comes for free: PopoverItem renders a native `disabled` button
|
||||
// and useListNavigation reads that DOM state (no explicit disabledIndices needed). Without it,
|
||||
// ArrowUp/Down would strand roving focus on the inert index-0 button. (floating-ui wires this on
|
||||
// real key nav, not on open, in jsdom — so we drive it with arrow keys.)
|
||||
renderMenu([
|
||||
{ id: "sync", label: "Sync", onSelect: vi.fn(), disabled: true },
|
||||
{ id: "backfill", label: "Backfill", onSelect: vi.fn() },
|
||||
{ id: "reset", label: "Reset", onSelect: vi.fn() },
|
||||
]);
|
||||
fireEvent.click(screen.getByRole("button", { name: "Catalog actions" }));
|
||||
const [disabled, backfill, reset] = screen.getAllByRole("menuitem", { hidden: true }) as [
|
||||
HTMLElement,
|
||||
HTMLElement,
|
||||
HTMLElement,
|
||||
];
|
||||
const menu = screen.getByRole("menu", { hidden: true });
|
||||
const rover = () =>
|
||||
[disabled, backfill, reset].find((el) => el.getAttribute("tabindex") === "0")?.textContent;
|
||||
|
||||
// The disabled item never becomes the roving target, whichever way we arrow.
|
||||
fireEvent.keyDown(menu, { key: "ArrowDown" }); // skips disabled[0] -> first enabled
|
||||
expect(rover()).toBe("Backfill");
|
||||
expect(disabled.getAttribute("tabindex")).toBe("-1");
|
||||
|
||||
fireEvent.keyDown(menu, { key: "ArrowDown" });
|
||||
expect(rover()).toBe("Reset");
|
||||
|
||||
fireEvent.keyDown(menu, { key: "ArrowUp" }); // back to Backfill
|
||||
expect(rover()).toBe("Backfill");
|
||||
fireEvent.keyDown(menu, { key: "ArrowUp" }); // wraps over disabled[0] to the last item
|
||||
expect(rover()).toBe("Reset");
|
||||
expect(disabled.getAttribute("tabindex")).toBe("-1"); // still never the disabled item
|
||||
});
|
||||
|
||||
it("honours a custom trigger node", () => {
|
||||
renderMenu([{ id: "sync", label: "Sync", onSelect: vi.fn() }]);
|
||||
cleanup();
|
||||
|
||||
Reference in New Issue
Block a user