import { resolveColumnStyle, dataTableBodyMode, distributeColumnWidths, resizeColumnPair, MIN_COLUMN_WIDTH, type DataTableColumn, } from "../data-table"; // The component module pulls in `react-native-reanimated` (via Skeleton), // which ships ESM that the unit-suite babel transform does not process. The // shared setup already stubs `react-native` / `nativewind`; stub the animation // layer here too so the pure helpers under test load without a render runtime. // `jest.mock` is hoisted above the import by babel-plugin-jest-hoist (same // pattern as the existing context tests). jest.mock("react-native-reanimated", () => ({ __esModule: true, default: { View: "Animated.View" }, useSharedValue: () => ({ value: 0 }), useAnimatedStyle: () => ({}), withTiming: (v: number) => v, withRepeat: (v: number) => v, withSequence: (v: number) => v, cancelAnimation: () => {}, })); interface Row { id: string; name: string; } describe("resolveColumnStyle", () => { it("uses a fixed width when provided", () => { const style = resolveColumnStyle({ key: "id", header: "ID", width: 80 }); expect(style.width).toBe(80); expect(style.flex).toBeUndefined(); }); it("falls back to flex: 1 when neither width nor flex is set", () => { const style = resolveColumnStyle({ key: "name", header: "Name" }); expect(style.flex).toBe(1); expect(style.width).toBeUndefined(); }); it("honours an explicit flex grow factor", () => { const style = resolveColumnStyle({ key: "name", header: "Name", flex: 3 }); expect(style.flex).toBe(3); }); it("prefers width over flex when both are given", () => { const style = resolveColumnStyle({ key: "id", header: "ID", width: 50, flex: 2 }); expect(style.width).toBe(50); expect(style.flex).toBeUndefined(); }); it("maps alignment to the matching flex alignItems", () => { expect(resolveColumnStyle({ key: "id", header: "ID" }).alignItems).toBe("flex-start"); expect(resolveColumnStyle({ key: "id", header: "ID", align: "center" }).alignItems).toBe( "center" ); expect(resolveColumnStyle({ key: "id", header: "ID", align: "right" }).alignItems).toBe( "flex-end" ); }); }); describe("dataTableBodyMode", () => { it("shows skeleton rows while loading regardless of row count", () => { expect(dataTableBodyMode(true, 0)).toBe("loading"); expect(dataTableBodyMode(true, 5)).toBe("loading"); }); it("shows the empty state when not loading and there are no rows", () => { expect(dataTableBodyMode(false, 0)).toBe("empty"); }); it("renders data when not loading and rows are present", () => { expect(dataTableBodyMode(false, 3)).toBe("data"); }); }); describe("DataTable column rendering", () => { const rows: Row[] = [ { id: "1", name: "Frodo" }, { id: "2", name: "Sam" }, ]; it("resolves a cell value from the row by column key", () => { const col: DataTableColumn = { key: "name", header: "Name" }; const value = String((rows[0] as Record)[col.key] ?? ""); expect(value).toBe("Frodo"); }); it("invokes a custom renderer with the whole row", () => { const render = jest.fn((row: Row) => row.name.toUpperCase()); const col: DataTableColumn = { key: "name", header: "Name", render }; const out = col.render?.(rows[1]); expect(render).toHaveBeenCalledWith(rows[1]); expect(out).toBe("SAM"); }); }); describe("distributeColumnWidths", () => { const cols = [ { key: "a", header: "A" }, { key: "b", header: "B" }, ]; it("divides the container by flex so the table fills its space", () => { expect(distributeColumnWidths(cols, 600)).toEqual([300, 300]); }); it("weights by each column's flex", () => { const weighted = [ { key: "a", header: "A", flex: 3 }, { key: "b", header: "B", flex: 1 }, ]; expect(distributeColumnWidths(weighted, 800)).toEqual([600, 200]); }); it("takes fixed widths out before dividing the rest", () => { const mixed = [ { key: "a", header: "A", width: 100 }, { key: "b", header: "B" }, { key: "c", header: "C" }, ]; expect(distributeColumnWidths(mixed, 500)).toEqual([100, 200, 200]); }); it("never hands out less than the minimum, or a negative", () => { // A container narrower than its fixed columns: the flexible ones would // otherwise get negative widths and collapse every cell in the row. const mixed = [ { key: "a", header: "A", width: 400 }, { key: "b", header: "B" }, ]; expect(distributeColumnWidths(mixed, 100)).toEqual([400, MIN_COLUMN_WIDTH]); }); }); describe("resizeColumnPair", () => { it("moves width from the neighbour, leaving the total alone", () => { const before = [200, 200, 200]; const after = resizeColumnPair(before, 0, 40); expect(after).toEqual([240, 160, 200]); // The property that matters: dragging a boundary must not change how wide // the table is, or every column to the right crawls sideways. expect(after.reduce((a, b) => a + b, 0)).toBe( before.reduce((a, b) => a + b, 0) ); }); it("stops at the minimum rather than inverting the pair", () => { const after = resizeColumnPair([100, 100], 0, 500); expect(after).toEqual([200 - MIN_COLUMN_WIDTH, MIN_COLUMN_WIDTH]); const back = resizeColumnPair([100, 100], 0, -500); expect(back).toEqual([MIN_COLUMN_WIDTH, 200 - MIN_COLUMN_WIDTH]); }); it("is a no-op past the last boundary", () => { expect(resizeColumnPair([100, 100], 1, 20)).toEqual([100, 100]); }); });