test(web): add 291 tests for parsers, hooks, and UI components
Lib utilities: scopeImportParser (31), status-styles (58), planningEntryIds (10), uuid (11). Hooks: useFilters (28), useRowOrder (18), usePermissions (30), useViewPrefs (24). Components: AnimatedModal (14), DateInput (22), InfoTooltip (13), ProgressRing (19). Web test suite: 75 → 87 files, 553 → 844 tests. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,241 @@
|
||||
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<ViewPrefsHandle, "rowOrder" | "setRowOrder"> {
|
||||
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([]);
|
||||
});
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user