// @vitest-environment jsdom import { describe, it, expect } from "vitest"; import { act, renderHook } from "@testing-library/react"; import { useRowSelection } from "./useRowSelection"; const ABCDE = ["a", "b", "c", "d", "e"]; function setup(shown = ABCDE, all = ABCDE, scope: unknown = 1) { return renderHook( (p: { all: string[]; shown: string[]; scope: unknown }) => useRowSelection(p.all, p.shown, p.scope), { initialProps: { all, shown, scope } }, ); } describe("useRowSelection", () => { it("toggles one row at a time, and says the selected rows in the order shown", () => { const { result } = setup(); act(() => result.current.toggle("d")); act(() => result.current.toggle("b")); expect(result.current.shownSelected).toEqual(["b", "d"]); act(() => result.current.toggle("d")); expect(result.current.shownSelected).toEqual(["b"]); expect(result.current.isSelected("b")).toBe(true); expect(result.current.isSelected("d")).toBe(false); }); it("selects the range from the last row toggled, in either direction", () => { const { result } = setup(); act(() => result.current.toggle("b")); act(() => result.current.toggle("d", true)); expect(result.current.shownSelected).toEqual(["b", "c", "d"]); act(() => { result.current.clear(); }); act(() => result.current.toggle("d")); act(() => result.current.toggle("b", true)); expect(result.current.shownSelected).toEqual(["b", "c", "d"]); }); it("takes a range's state from the row the range ends on: it can clear rows too", () => { const { result } = setup(); act(() => result.current.toggle("a")); act(() => result.current.toggle("e", true)); expect(result.current.shownSelected).toEqual(ABCDE); // `e` is selected, so a range ending on it clears; the anchor moved to `e` with the last click. act(() => result.current.toggle("b", true)); expect(result.current.shownSelected).toEqual(["a"]); }); it("leaves rows outside the range alone", () => { const { result } = setup(); act(() => result.current.toggle("a")); act(() => result.current.toggle("c")); act(() => result.current.toggle("e", true)); expect(result.current.shownSelected).toEqual(["a", "c", "d", "e"]); }); it("follows the order on screen, not the stored one", () => { const { result } = setup(["e", "c", "a", "b", "d"]); act(() => result.current.toggle("c")); act(() => result.current.toggle("b", true)); expect(result.current.shownSelected).toEqual(["c", "a", "b"]); }); it("makes a range with no row to start from a plain toggle", () => { const { result } = setup(); act(() => result.current.toggle("c", true)); expect(result.current.shownSelected).toEqual(["c"]); }); it("makes a range whose first row a filter hid a plain toggle", () => { const { result, rerender } = setup(); act(() => result.current.toggle("a")); rerender({ all: ABCDE, shown: ["c", "d", "e"], scope: 1 }); act(() => result.current.toggle("e", true)); expect(result.current.shownSelected).toEqual(["e"]); }); it("has the header box cover the rows shown, and keep what a filter hides", () => { const { result, rerender } = setup(); expect(result.current.headerState).toBe("none"); act(() => result.current.toggle("a")); expect(result.current.headerState).toBe("some"); rerender({ all: ABCDE, shown: ["c", "d"], scope: 1 }); // The one selected row is hidden: nothing SHOWN is selected, and acting counts what is shown. expect(result.current.headerState).toBe("none"); expect(result.current.shownSelected).toEqual([]); act(() => result.current.toggleAllShown()); expect(result.current.headerState).toBe("all"); expect(result.current.shownSelected).toEqual(["c", "d"]); act(() => result.current.toggleAllShown()); expect(result.current.headerState).toBe("none"); rerender({ all: ABCDE, shown: ABCDE, scope: 1 }); expect(result.current.shownSelected).toEqual(["a"]); }); it("selects every row shown from a partial selection", () => { const { result } = setup(); act(() => result.current.toggle("b")); act(() => result.current.toggleAllShown()); expect(result.current.shownSelected).toEqual(ABCDE); }); it("has no header state to show over no rows", () => { const { result } = setup([], []); expect(result.current.headerState).toBe("none"); act(() => result.current.toggleAllShown()); expect(result.current.headerState).toBe("none"); }); it("drops a row that left the list, so it does not come back selected", () => { const { result, rerender } = setup(); act(() => result.current.toggle("b")); act(() => result.current.toggle("c")); rerender({ all: ["a", "c", "d", "e"], shown: ["a", "c", "d", "e"], scope: 1 }); expect(result.current.shownSelected).toEqual(["c"]); rerender({ all: ABCDE, shown: ABCDE, scope: 1 }); expect(result.current.shownSelected).toEqual(["c"]); }); it("clears on a change of scope, in the render that changes it", () => { const { result, rerender } = setup(); act(() => result.current.toggle("a")); // The same videos in another list are not selected there. rerender({ all: ABCDE, shown: ABCDE, scope: 2 }); expect(result.current.shownSelected).toEqual([]); act(() => result.current.toggle("c", true)); expect(result.current.shownSelected).toEqual(["c"]); }); it("counts the selected rows a filter hides", () => { const { result, rerender } = setup(); act(() => result.current.toggle("a")); act(() => result.current.toggle("c")); expect(result.current.hiddenCount).toBe(0); rerender({ all: ABCDE, shown: ["c", "d"], scope: 1 }); expect(result.current.hiddenCount).toBe(1); }); it("ticks rows again on top of what is selected by then", () => { const { result } = setup(); act(() => result.current.toggle("e")); act(() => result.current.select(["a", "b"])); expect(result.current.shownSelected).toEqual(["a", "b", "e"]); }); it("says whether clearing did anything", () => { const { result } = setup(); let cleared = true; act(() => { cleared = result.current.clear(); }); expect(cleared).toBe(false); act(() => result.current.toggle("a")); act(() => { cleared = result.current.clear(); }); expect(cleared).toBe(true); expect(result.current.shownSelected).toEqual([]); }); it("counts a selected row a filter hides as something to clear", () => { const { result, rerender } = setup(); act(() => result.current.toggle("a")); rerender({ all: ABCDE, shown: ["c"], scope: 1 }); let cleared = false; act(() => { cleared = result.current.clear(); }); expect(cleared).toBe(true); rerender({ all: ABCDE, shown: ABCDE, scope: 1 }); expect(result.current.shownSelected).toEqual([]); }); });