import { describe, expect, it, vi } from "vitest"; import { renderHook, act } from "@testing-library/react"; import { useRowOrder } from "./useRowOrder.js"; import type { ViewPrefsHandle } from "./useViewPrefs.js"; // --------------------------------------------------------------------------- // Helpers // --------------------------------------------------------------------------- /** Build a minimal ViewPrefsHandle stub for testing. */ function makePrefs( rowOrder: string[] = [], setRowOrder = vi.fn(), ): Pick { return { rowOrder, setRowOrder }; } type Row = { id: string; label: string }; function makeRows(ids: string[]): Row[] { return ids.map((id) => ({ id, label: `Label-${id}` })); } // --------------------------------------------------------------------------- // Tests // --------------------------------------------------------------------------- describe("useRowOrder", () => { // ------------------------------------------------------------------------- // orderedRows — sorting logic // ------------------------------------------------------------------------- describe("orderedRows", () => { it("returns rows unchanged when activeSortField is set", () => { const rows = makeRows(["b", "a", "c"]); const prefs = makePrefs(["a", "b", "c"]); const { result } = renderHook(() => useRowOrder(rows, prefs, "name", vi.fn())); // Even though rowOrder says a→b→c, the active sort must suppress it expect(result.current.orderedRows.map((r) => r.id)).toEqual(["b", "a", "c"]); }); it("returns rows unchanged when rowOrder is empty", () => { const rows = makeRows(["b", "a", "c"]); const prefs = makePrefs([]); const { result } = renderHook(() => useRowOrder(rows, prefs, null, vi.fn())); expect(result.current.orderedRows.map((r) => r.id)).toEqual(["b", "a", "c"]); }); it("sorts rows according to rowOrder when no sort is active", () => { const rows = makeRows(["b", "a", "c"]); const prefs = makePrefs(["a", "b", "c"]); const { result } = renderHook(() => useRowOrder(rows, prefs, null, vi.fn())); expect(result.current.orderedRows.map((r) => r.id)).toEqual(["a", "b", "c"]); }); it("places rows missing from rowOrder at the end", () => { const rows = makeRows(["x", "a", "b", "y"]); // Only a and b have explicit positions; x and y go to the end const prefs = makePrefs(["a", "b"]); const { result } = renderHook(() => useRowOrder(rows, prefs, null, vi.fn())); const ids = result.current.orderedRows.map((r) => r.id); expect(ids.indexOf("a")).toBeLessThan(ids.indexOf("x")); expect(ids.indexOf("b")).toBeLessThan(ids.indexOf("x")); }); it("handles a rowOrder that is a partial subset of rows", () => { const rows = makeRows(["c", "a", "b"]); const prefs = makePrefs(["b", "c"]); const { result } = renderHook(() => useRowOrder(rows, prefs, null, vi.fn())); const ids = result.current.orderedRows.map((r) => r.id); expect(ids[0]).toBe("b"); expect(ids[1]).toBe("c"); // "a" has no saved position → appended after expect(ids[2]).toBe("a"); }); it("does not mutate the original rows array", () => { const rows = makeRows(["c", "a", "b"]); const originalRef = rows; const prefs = makePrefs(["a", "b", "c"]); renderHook(() => useRowOrder(rows, prefs, null, vi.fn())); expect(rows).toBe(originalRef); }); }); // ------------------------------------------------------------------------- // isCustomOrder // ------------------------------------------------------------------------- describe("isCustomOrder", () => { it("is false when rowOrder is empty", () => { const { result } = renderHook(() => useRowOrder(makeRows(["a"]), makePrefs([]), null, vi.fn()), ); expect(result.current.isCustomOrder).toBe(false); }); it("is false when activeSortField is set (even if rowOrder is non-empty)", () => { const { result } = renderHook(() => useRowOrder(makeRows(["a", "b"]), makePrefs(["b", "a"]), "name", vi.fn()), ); expect(result.current.isCustomOrder).toBe(false); }); it("is true when rowOrder is non-empty and no sort is active", () => { const { result } = renderHook(() => useRowOrder(makeRows(["a", "b"]), makePrefs(["b", "a"]), null, vi.fn()), ); expect(result.current.isCustomOrder).toBe(true); }); }); // ------------------------------------------------------------------------- // reorder // ------------------------------------------------------------------------- describe("reorder", () => { it("calls setRowOrder with the reordered ID list", () => { const setRowOrder = vi.fn(); const rows = makeRows(["a", "b", "c"]); const prefs = makePrefs([], setRowOrder); const { result } = renderHook(() => useRowOrder(rows, prefs, null, vi.fn())); act(() => { result.current.reorder("c", "a"); }); // "c" dragged to position of "a": result should be [c, a, b] expect(setRowOrder).toHaveBeenCalledWith(["c", "a", "b"]); }); it("calls resetSort to clear any active column sort", () => { const resetSort = vi.fn(); const rows = makeRows(["a", "b", "c"]); const prefs = makePrefs([], vi.fn()); const { result } = renderHook(() => useRowOrder(rows, prefs, null, resetSort)); act(() => { result.current.reorder("b", "a"); }); expect(resetSort).toHaveBeenCalledOnce(); }); it("is a no-op when draggedId equals targetId", () => { const setRowOrder = vi.fn(); const resetSort = vi.fn(); const rows = makeRows(["a", "b"]); const prefs = makePrefs([], setRowOrder); const { result } = renderHook(() => useRowOrder(rows, prefs, null, resetSort)); act(() => { result.current.reorder("a", "a"); }); expect(setRowOrder).not.toHaveBeenCalled(); expect(resetSort).not.toHaveBeenCalled(); }); it("is a no-op when draggedId is not found in current rows", () => { const setRowOrder = vi.fn(); const rows = makeRows(["a", "b"]); const prefs = makePrefs([], setRowOrder); const { result } = renderHook(() => useRowOrder(rows, prefs, null, vi.fn())); act(() => { result.current.reorder("unknown", "a"); }); expect(setRowOrder).not.toHaveBeenCalled(); }); it("is a no-op when targetId is not found in current rows", () => { const setRowOrder = vi.fn(); const rows = makeRows(["a", "b"]); const prefs = makePrefs([], setRowOrder); const { result } = renderHook(() => useRowOrder(rows, prefs, null, vi.fn())); act(() => { result.current.reorder("a", "unknown"); }); expect(setRowOrder).not.toHaveBeenCalled(); }); it("moves an item from the end to the beginning", () => { const setRowOrder = vi.fn(); const rows = makeRows(["a", "b", "c"]); const prefs = makePrefs([], setRowOrder); const { result } = renderHook(() => useRowOrder(rows, prefs, null, vi.fn())); act(() => { result.current.reorder("c", "a"); }); expect(setRowOrder).toHaveBeenCalledWith(["c", "a", "b"]); }); it("moves an item from the beginning to the end", () => { const setRowOrder = vi.fn(); const rows = makeRows(["a", "b", "c"]); const prefs = makePrefs([], setRowOrder); const { result } = renderHook(() => useRowOrder(rows, prefs, null, vi.fn())); act(() => { result.current.reorder("a", "c"); }); expect(setRowOrder).toHaveBeenCalledWith(["b", "c", "a"]); }); it("reorders correctly when a custom rowOrder is already applied", () => { const setRowOrder = vi.fn(); // Existing saved order: b, a, c → orderedRows will be [b, a, c] const rows = makeRows(["a", "b", "c"]); const prefs = makePrefs(["b", "a", "c"], setRowOrder); const { result } = renderHook(() => useRowOrder(rows, prefs, null, vi.fn())); // Drag "c" (last) to the position of "b" (first) act(() => { result.current.reorder("c", "b"); }); expect(setRowOrder).toHaveBeenCalledWith(["c", "b", "a"]); }); }); // ------------------------------------------------------------------------- // resetOrder // ------------------------------------------------------------------------- describe("resetOrder", () => { it("calls setRowOrder with an empty array", () => { const setRowOrder = vi.fn(); const rows = makeRows(["a", "b"]); const prefs = makePrefs(["b", "a"], setRowOrder); const { result } = renderHook(() => useRowOrder(rows, prefs, null, vi.fn())); act(() => { result.current.resetOrder(); }); expect(setRowOrder).toHaveBeenCalledWith([]); }); }); });