diff --git a/src/lib/utils/propCategories.js b/src/lib/utils/propCategories.js index b4de10a8..941363cc 100644 --- a/src/lib/utils/propCategories.js +++ b/src/lib/utils/propCategories.js @@ -58,6 +58,9 @@ export const GRID_MAYBE_FUNCTIONS = { sendToClipboard: 1, processDataFromClipboard: 1, + // Columns + processUnpinnedColumns: 1, + // Exporting getCustomContentBelowRow: 1, shouldRowBeSkipped: 1, diff --git a/tests/assets/dashAgGridFunctions.js b/tests/assets/dashAgGridFunctions.js index df7255d5..669b1f70 100644 --- a/tests/assets/dashAgGridFunctions.js +++ b/tests/assets/dashAgGridFunctions.js @@ -439,6 +439,15 @@ dagfuncs.dateFilterComparator = (filterLocalDateAtMidnight, cellValue) => { // END test_custom_filter.py +// FOR test_column_pinning.py +dagfuncs.unpinAllButFirstColumn = (params) => { + const {api} = params; + // columns contains the columns AgGrid would like to unpin, but we can override that by returning + // a different set of columns. In this case, we will unpin all columns except the first column + return api.getColumns().filter((col, index) => index > 0 && col.isPinned()); +} +// END test_column_pinning.py + // FOR test_quick_filter.py dagfuncs.quickFilterMatcher = (quickFilterParts, rowQuickFilterAggregateText) => { return quickFilterParts.every(part => rowQuickFilterAggregateText.match(part)); diff --git a/tests/test_column_pinning.py b/tests/test_column_pinning.py new file mode 100644 index 00000000..f91eb20f --- /dev/null +++ b/tests/test_column_pinning.py @@ -0,0 +1,49 @@ +import dash_ag_grid as dag +from dash import Dash, html, dcc +from . import utils +import uuid + + +def test_cd001_process_unpinned_columns(dash_duo): + """ Test that the processUnpinnedColumns function is called when the available viewport space is exceeded and the right most columns are unpinned.""" + + column_count = 10 + row_count = 10 + row_data = [ + {f"COL_{col_idx}": uuid.uuid4().hex for col_idx in range(column_count)} for _ in range(row_count) + ] + + app = Dash(__name__) + column_defs = [ + { + "field": col, + "pinned": "left" + } + for col in row_data[0].keys() + ] + + app.layout = html.Div( + [ + dcc.Markdown( + "This grid uses a javascript function to make sure," + " that the right most columns are unpinned when the available viewport space is exceeded." + ), + dag.AgGrid( + columnDefs=column_defs, + rowData=row_data, + id="grid", + dashGridOptions={ + "processUnpinnedColumns": {"function": "unpinAllButFirstColumn(params)"}, + } + ), + ], + style={"margin": 20}, + ) + dash_duo.start_server(app) + + grid = utils.Grid(dash_duo, "grid") + + grid.wait_for_pinned_column(col_id="COL_0", pin_state="left") + grid.wait_for_pinned_column(col_id="COL_1", pin_state="scrolling") + grid.wait_for_pinned_column(col_id="COL_4", pin_state="scrolling") + diff --git a/tests/utils.py b/tests/utils.py index e2634aca..feae68eb 100644 --- a/tests/utils.py +++ b/tests/utils.py @@ -1,3 +1,5 @@ +from typing import Literal + from selenium.webdriver.common.action_chains import ActionChains from dash.testing.wait import until @@ -44,11 +46,40 @@ def _wait_for_count(self, selector, expected, description): raise ValueError(f"found {len(els)} {description}, expected {expected}") def wait_for_pinned_cols(self, expected): - # TODO: is there a pinned right? + self.wait_for_pinned_column_count(expected, pin_state="left") + + def _header_class_for_pin_state(self, pin_state: Literal["left", "right", "scrolling"]): + """Return the appropriate header class for the given pin state.""" + if pin_state == "scrolling": + return "ag-header-viewport" + elif pin_state == "left": + return "ag-pinned-left-header" + elif pin_state == "right": + return "ag-pinned-right-header" + else: + raise ValueError(f"Invalid pin_state: {pin_state}") + + def wait_for_pinned_column_count(self, expected_count, pin_state: Literal["left", "right", "scrolling"]): + """Wait for the number of columns in the specified pin state to match the expected count.""" + header_class = self._header_class_for_pin_state(pin_state) self._wait_for_count( - f'#{self.id} .ag-pinned-left-header [aria-rowindex="1"] .ag-header-cell', - expected, - "pinned_cols", + f'#{self.id} .{header_class} [aria-rowindex="1"] .ag-header-cell', + expected_count, + f"pinned_cols '{pin_state}'", + ) + + def wait_for_pinned_column( + self, + col_id: str, + pin_state: Literal["left", "right", "scrolling"], + ) -> None: + """Wait for a column to be in the specified pin state.""" + header_class = self._header_class_for_pin_state(pin_state) + + self._wait_for_count( + f'#{self.id} .{header_class} [aria-rowindex="1"] .ag-header-cell[col-id="{col_id}"]', + 1, + f"column '{col_id}' pinned '{pin_state}'", ) def wait_for_viewport_cols(self, expected):