import { useForm, useFieldArray } from "react-hook-form"; import { describe, it, expect } from "vitest"; import { renderWithProviders, screen } from "@/test/render"; import { LineItemsTable, type LineColumn } from "./line-items-table"; interface TestFormValues { lines: { id?: string; label: string }[]; } const columns: LineColumn[] = [ { id: "label", header: "Label", cell: ({ index, form }) => ( form.setValue(`lines.${index}.label`, e.target.value)} /> ), }, ]; function Harness({ sortable, initial = ["a", "b", "c"], }: { sortable: boolean; initial?: string[]; }) { const form = useForm({ defaultValues: { lines: initial.map((label) => ({ label })) }, }); const fieldArray = useFieldArray({ control: form.control, name: "lines" }); return ( ({ label: "" })} /> ); } describe("LineItemsTable", () => { describe("sortable={false} (default)", () => { it("does not render drag handles", async () => { await renderWithProviders(); expect(screen.queryByLabelText(/drag to reorder/i)).not.toBeInTheDocument(); }); }); describe("sortable={true}", () => { it("renders a drag handle per row with accessible labels", async () => { await renderWithProviders(); expect(screen.getByLabelText("Drag to reorder line 1")).toBeInTheDocument(); expect(screen.getByLabelText("Drag to reorder line 2")).toBeInTheDocument(); expect(screen.getByLabelText("Drag to reorder line 3")).toBeInTheDocument(); }); it("adds an aria-roledescription so the handle is announced as sortable", async () => { await renderWithProviders(); const handle = screen.getByLabelText("Drag to reorder line 1"); // dnd-kit's useSortable supplies aria-roledescription="sortable" via its // attributes spread. We assert it's present so SR users get the cue. expect(handle).toHaveAttribute("aria-roledescription", "sortable"); }); it("adds a leading column to the header row to align the handle column", async () => { const { container } = await renderWithProviders(); const headerCells = container.querySelectorAll("thead th"); // 1 (grip) + 1 (label) + 1 (trailing remove column) expect(headerCells.length).toBe(3); }); // Note: actual drag-end reordering is exercised by the Playwright e2e // (e2e/stories/purchase-order-lifecycle/purchaser--edit-purchase-order.spec.ts). // dnd-kit's KeyboardSensor relies on getBoundingClientRect() to pick a drop // target, and jsdom returns zero-sized rects, so the keyboard interaction // can't be meaningfully simulated here. }); });