import { renderHook } from "@testing-library/react-hooks"; import { GridCell, GridCellKind, GridColumn, Rectangle } from "../src"; import { getDataEditorTheme } from "../src/common/styles"; import type { DataGridSearchProps } from "../src/data-grid-search/data-grid-search"; import { CellRenderers } from "../src/data-grid/cells"; import type { GetCellRendererCallback } from "../src/data-grid/cells/cell-types"; import type { CellArray } from "../src/data-grid/data-grid-types"; import { useColumnSizer } from "../src/data-editor/use-column-sizer"; const COLUMNS: GridColumn[] = [ { title: "A", id: "ColumnA", }, { title: "B", width: 160, icon: "headerCode", }, ]; const A_COPY_OF_COLUMNS_THAT_ALSO_HAS_A_NEW_COLUMN_TITLED_C_THAT_WE_WILL_MEASURE_SOON_ENOUGH: GridColumn[] = [ ...COLUMNS, { title: "C", id: "ColumnC", }, ]; const A_BUNCH_OF_COLUMNS_THAT_ALREADY_HAVE_SIZES_WE_DONT_WANT_TO_MEASURE_THESE: GridColumn[] = [ { title: "A", width: 120, }, { title: "B", width: 160, icon: "headerCode", }, ]; type DataBuilder = (x: number, y: number) => string; function buildCellsForSelectionGetter(dataBuilder: DataBuilder): DataGridSearchProps["getCellsForSelection"] { return (selection: Rectangle): CellArray => { const result: GridCell[][] = []; for (let y = selection.y; y < selection.y + selection.height; y++) { const row: GridCell[] = []; for (let x = selection.x; x < selection.x + selection.width; x++) { const data = dataBuilder(x, y); row.push({ allowOverlay: false, kind: GridCellKind.Text, displayData: data, data, }); } result.push(row); } return result; }; } const getShortCellsForSelection = jest.fn(buildCellsForSelectionGetter((x, y) => `column ${x} row ${y}`)); const getLongCellsForSelection = jest.fn( buildCellsForSelectionGetter((x, y) => `This cell is in column number ${x} and row number ${y}`) ); beforeEach(() => { getShortCellsForSelection.mockClear(); getLongCellsForSelection.mockClear(); }); const theme = getDataEditorTheme(); const abortController = new AbortController(); const getCellRenderer: GetCellRendererCallback = cell => { if (cell.kind === GridCellKind.Custom) return undefined; return CellRenderers[cell.kind] as any; }; describe("use-column-sizer", () => { it("Measures a simple cell", async () => { const { result } = renderHook(() => useColumnSizer( COLUMNS, 1000, getShortCellsForSelection, 400, 20, 500, theme, getCellRenderer, abortController ) ); const columnA = result.current.find(col => col.title === "A"); const columnB = result.current.find(col => col.title === "B"); expect(columnA).toBeDefined(); expect(columnB).toBeDefined(); // Keep in sync with the `displayData` up there. expect(columnA?.width).toBe(32); expect(columnB?.width).toBe(160); }); it("Measures the last row", async () => { // eslint-disable-next-line sonarjs/no-identical-functions renderHook(() => useColumnSizer( COLUMNS, 1000, getShortCellsForSelection, 400, 20, 500, theme, getCellRenderer, abortController ) ); expect(getShortCellsForSelection).toBeCalledTimes(2); expect(getShortCellsForSelection).toHaveBeenNthCalledWith( 1, { x: 0, y: 0, width: 2, height: 9, }, expect.anything() ); expect(getShortCellsForSelection).toHaveBeenLastCalledWith( { x: 0, y: 999, width: 2, height: 1, }, expect.anything() ); }); it("Measures new columns when they arrive, doesn't re-measure existing ones", async () => { const { result, rerender } = renderHook( ({ getCellsForSelection, columns }) => useColumnSizer( columns, 1000, getCellsForSelection, 400, 20, 500, theme, getCellRenderer, abortController ), { initialProps: { getCellsForSelection: getShortCellsForSelection, columns: COLUMNS, }, } ); const shortColumnA = result.current.find(col => col.title === "A"); const shortColumnB = result.current.find(col => col.title === "B"); const shortColumnC = result.current.find(col => col.title === "C"); expect(shortColumnA).toBeDefined(); expect(shortColumnB).toBeDefined(); expect(shortColumnC).not.toBeDefined(); // Keep in sync with getShortCellsForSelection up there. expect(shortColumnA?.width).toBe(32); expect(shortColumnB?.width).toBe(160); // Re render with longer text and a new column with title C rerender({ getCellsForSelection: getLongCellsForSelection, columns: A_COPY_OF_COLUMNS_THAT_ALSO_HAS_A_NEW_COLUMN_TITLED_C_THAT_WE_WILL_MEASURE_SOON_ENOUGH, // I told you! }); const longColumnA = result.current.find(col => col.title === "A"); const longColumnB = result.current.find(col => col.title === "B"); const longColumnC = result.current.find(col => col.title === "C"); expect(longColumnA).toBeDefined(); expect(longColumnB).toBeDefined(); expect(longColumnC).toBeDefined(); // Keep in sync with getLongCellsForSelection up there. expect(longColumnA?.width).toBe(32); expect(longColumnB?.width).toBe(160); expect(longColumnC?.width).toBe(66); }); it("Returns the default sizes if getCellsForSelection is not provided", async () => { const { result } = renderHook(() => useColumnSizer(COLUMNS, 1000, undefined, 400, 20, 500, theme, getCellRenderer, abortController) ); const columnA = result.current.find(col => col.title === "A"); const columnB = result.current.find(col => col.title === "B"); expect(columnA).toBeDefined(); expect(columnB).toBeDefined(); // The default is 150, keep in sync with `defaultSize` in use-cell-sizer.ts expect(columnA?.width).toBe(150); expect(columnB?.width).toBe(160); }); it("Does not measure anything if every column is sized", async () => { const { result } = renderHook(() => useColumnSizer( A_BUNCH_OF_COLUMNS_THAT_ALREADY_HAVE_SIZES_WE_DONT_WANT_TO_MEASURE_THESE, 1000, undefined, 400, 50, 500, theme, getCellRenderer, abortController ) ); expect(result.current).toBe(A_BUNCH_OF_COLUMNS_THAT_ALREADY_HAVE_SIZES_WE_DONT_WANT_TO_MEASURE_THESE); }); });