From 1874b345cee312ff235f28fe04a4b0ba139af197 Mon Sep 17 00:00:00 2001 From: alexcjohnson Date: Thu, 9 Feb 2023 19:00:52 -0500 Subject: [PATCH 1/4] move existing "tests" into new examples folder --- .../add_remove_columns.py} | 0 .../add_remove_update_rows.py} | 0 tests/{ => examples}/assets/dashAgGridComponentFunctions.js | 0 tests/{ => examples}/assets/dashAgGridFunctions.js | 0 tests/{ => examples}/persist_row_groups.py | 0 tests/{ => examples}/selections_complex.py | 0 tests/{ => examples}/selections_complex_alternate.py | 0 7 files changed, 0 insertions(+), 0 deletions(-) rename tests/{test_add_remove_columns.py => examples/add_remove_columns.py} (100%) rename tests/{test_add_remove_update_rows.py => examples/add_remove_update_rows.py} (100%) rename tests/{ => examples}/assets/dashAgGridComponentFunctions.js (100%) rename tests/{ => examples}/assets/dashAgGridFunctions.js (100%) rename tests/{ => examples}/persist_row_groups.py (100%) rename tests/{ => examples}/selections_complex.py (100%) rename tests/{ => examples}/selections_complex_alternate.py (100%) diff --git a/tests/test_add_remove_columns.py b/tests/examples/add_remove_columns.py similarity index 100% rename from tests/test_add_remove_columns.py rename to tests/examples/add_remove_columns.py diff --git a/tests/test_add_remove_update_rows.py b/tests/examples/add_remove_update_rows.py similarity index 100% rename from tests/test_add_remove_update_rows.py rename to tests/examples/add_remove_update_rows.py diff --git a/tests/assets/dashAgGridComponentFunctions.js b/tests/examples/assets/dashAgGridComponentFunctions.js similarity index 100% rename from tests/assets/dashAgGridComponentFunctions.js rename to tests/examples/assets/dashAgGridComponentFunctions.js diff --git a/tests/assets/dashAgGridFunctions.js b/tests/examples/assets/dashAgGridFunctions.js similarity index 100% rename from tests/assets/dashAgGridFunctions.js rename to tests/examples/assets/dashAgGridFunctions.js diff --git a/tests/persist_row_groups.py b/tests/examples/persist_row_groups.py similarity index 100% rename from tests/persist_row_groups.py rename to tests/examples/persist_row_groups.py diff --git a/tests/selections_complex.py b/tests/examples/selections_complex.py similarity index 100% rename from tests/selections_complex.py rename to tests/examples/selections_complex.py diff --git a/tests/selections_complex_alternate.py b/tests/examples/selections_complex_alternate.py similarity index 100% rename from tests/selections_complex_alternate.py rename to tests/examples/selections_complex_alternate.py From 77c441b003d5c6f49e22f24081c78fa5c80aaa0d Mon Sep 17 00:00:00 2001 From: alexcjohnson Date: Mon, 13 Feb 2023 14:44:27 -0500 Subject: [PATCH 2/4] basic test framework --- pytest.ini | 2 + tests/test_column_drag.py | 50 ++++++++++++++++++++ tests/test_filter.py | 35 ++++++++++++++ tests/utils.py | 98 +++++++++++++++++++++++++++++++++++++++ 4 files changed, 185 insertions(+) create mode 100644 tests/test_column_drag.py create mode 100644 tests/test_filter.py create mode 100644 tests/utils.py diff --git a/pytest.ini b/pytest.ini index b6302a60..8b16d3d9 100644 --- a/pytest.ini +++ b/pytest.ini @@ -1,4 +1,6 @@ [pytest] +junit_family = xunit1 + testpaths = tests/ addopts = -rsxX -vv log_format = %(asctime)s | %(levelname)s | %(name)s:%(lineno)d | %(message)s diff --git a/tests/test_column_drag.py b/tests/test_column_drag.py new file mode 100644 index 00000000..b107cbde --- /dev/null +++ b/tests/test_column_drag.py @@ -0,0 +1,50 @@ +from dash import Dash, html +from dash_ag_grid import AgGrid +import plotly.express as px + +from . import utils + + +df = px.data.election() +default_display_cols = ["district_id", "district", "winner"] + + +def test_cd001_drag_columns(dash_duo): + app = Dash() + app.layout = html.Div([ + AgGrid( + id="grid", + rowData=df.to_dict("records"), + columnDefs=[ + {"headerName": col.capitalize(), "field": col} + for col in default_display_cols + ], + ) + ]) + + dash_duo.start_server(app) + + grid = utils.Grid(dash_duo, "grid") + + grid.wait_for_all_header_texts(["District_id", "District", "Winner"]) + grid.wait_for_pinned_cols(0) + grid.wait_for_viewport_cols(3) + + grid.drag_col(2, 0) # last column first but not pinned + + grid.wait_for_all_header_texts(["Winner", "District_id", "District"]) + grid.wait_for_pinned_cols(0) + grid.wait_for_viewport_cols(3) + + grid.pin_col(1) # middle column pinned + + grid.wait_for_all_header_texts(["District_id", "Winner", "District"]) + grid.wait_for_pinned_cols(1) + grid.wait_for_viewport_cols(2) + + # pin first non-pinned column by dragging it to its own left edge + grid.pin_col(1, 1) + + grid.wait_for_all_header_texts(["District_id", "Winner", "District"]) + grid.wait_for_pinned_cols(2) + grid.wait_for_viewport_cols(1) diff --git a/tests/test_filter.py b/tests/test_filter.py new file mode 100644 index 00000000..d7978cb5 --- /dev/null +++ b/tests/test_filter.py @@ -0,0 +1,35 @@ +from dash import Dash, html +from dash_ag_grid import AgGrid +import plotly.express as px + +from . import utils + + +df = px.data.election() +default_display_cols = ["district_id", "district", "winner"] + + +def test_fi001_floating_filter(dash_duo): + app = Dash() + app.layout = html.Div([ + AgGrid( + id="grid", + rowData=df.to_dict("records"), + columnDefs=[ + {"headerName": col.capitalize(), "field": col} + for col in default_display_cols + ], + defaultColDef={"filter": True, "floatingFilter": True} + ) + ]) + + dash_duo.start_server(app) + + grid = utils.Grid(dash_duo, "grid") + + grid.wait_for_cell_text(0, 1, "101-Bois-de-Liesse") + + grid.set_filter(0, "12") + + grid.wait_for_cell_text(0, 1, "112-DeLorimier") + grid.wait_for_rendered_rows(5) diff --git a/tests/utils.py b/tests/utils.py new file mode 100644 index 00000000..3b9bfc84 --- /dev/null +++ b/tests/utils.py @@ -0,0 +1,98 @@ +from selenium.webdriver.common.action_chains import ActionChains + +from dash.testing.wait import until +from dash.testing.errors import TestingTimeoutError + +# we use zero-based columns, but aria colindex is one-based +# so we need to add 1 in a lot of places + +class Grid: + def __init__(self, dash_duo, grid_id): + self.dash_duo = dash_duo + self.id = grid_id + + def get_header_cell(self, col): + return self.dash_duo.find_element( + f'#{self.id} [aria-rowindex="1"] .ag-header-cell[aria-colindex="{col + 1}"]' + ) + + def wait_for_header_text(self, col, expected): + self.dash_duo.wait_for_text_to_equal( + f'#{self.id} [aria-rowindex="1"] .ag-header-cell[aria-colindex="{col + 1}"] .ag-header-cell-text', + expected + ) + + def wait_for_all_header_texts(self, expected): + for col, val in enumerate(expected): + self.wait_for_header_text(col, val) + cols = len(self.dash_duo.find_elements(f'#{self.id} [aria-rowindex="1"] .ag-header-cell')) + assert cols == len(expected) + + def _wait_for_count(self, selector, expected, description): + try: + until(lambda: len(self.dash_duo.find_elements(selector)) == expected, timeout=3) + except TestingTimeoutError: + els = self.dash_duo.find_elements(selector) + 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_count( + f'#{self.id} .ag-pinned-left-header [aria-rowindex="1"] .ag-header-cell', + expected, + "pinned_cols" + ) + + def wait_for_viewport_cols(self, expected): + self._wait_for_count( + f'#{self.id} .ag-header-viewport [aria-rowindex="1"] .ag-header-cell', + expected, + "viewport_cols" + ) + + def drag_col(self, from_index, to_index): + from_col = self.get_header_cell(from_index) + to_col = self.get_header_cell(to_index) + ( + ActionChains(self.dash_duo.driver) + .move_to_element(from_col) + .click_and_hold() + .move_to_location( + to_col.location["x"] + to_col.size["width"] * 0.8, + to_col.location["y"] + to_col.size["height"] * 0.5 + ) + .pause(0.5) + .release() + ).perform() + + def pin_col(self, col, pinned_cols=0): + from_col = self.get_header_cell(col) + pin_col = self.get_header_cell(pinned_cols) + ( + ActionChains(self.dash_duo.driver) + .move_to_element(from_col) + .click_and_hold() + .move_to_location( + pin_col.location["x"] + pin_col.size["width"] * 0.1, + pin_col.location["y"] + pin_col.size["height"] * 0.5 + ) + .pause(1) + .release() + ).perform() + + def wait_for_rendered_rows(self, expected): + self._wait_for_count(f"#{self.id} .ag-row", expected, "rendered rows") + + def wait_for_cell_text(self, row, col, expected): + self.dash_duo.wait_for_text_to_equal( + f'#{self.id} .ag-row[row-index="{row}"] .ag-cell[aria-colindex="{col + 1}"]', + expected + ) + + def set_filter(self, col, val): + filter_input = self.dash_duo.find_element( + f'#{self.id} .ag-floating-filter[aria-colindex="{col + 1}"] input' + ) + self.dash_duo.clear_input(filter_input) + filter_input.send_keys(val) From aeb0ef67b36222a4f0356de666d6b89ced603b41 Mon Sep 17 00:00:00 2001 From: alexcjohnson Date: Mon, 13 Feb 2023 17:27:45 -0500 Subject: [PATCH 3/4] circleci initial commit --- .circleci/config.yml | 38 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 38 insertions(+) create mode 100644 .circleci/config.yml diff --git a/.circleci/config.yml b/.circleci/config.yml new file mode 100644 index 00000000..6aa29ba7 --- /dev/null +++ b/.circleci/config.yml @@ -0,0 +1,38 @@ +version: 2.1 + +orbs: + browser-tools: circleci/browser-tools@1.4.0 + +jobs: + test: + docker: + - image: cimg/python:3.9-node + auth: + username: dashautomation + password: $DASH_PAT_DOCKERHUB + steps: + - checkout + - browser-tools/install-chrome + - browser-tools/install-chromedriver + - run: + name: Install Python deps + command: | + python -m venv venv && . venv/bin/activate + pip install --upgrade pip wheel + pip install -r tests/requirements.txt + - run: + name: Build package + command: | + . venv/bin/activate + npm ci + npm run build + - run: + name: Run tests + command: | + . venv/bin/activate + pytest --headless + +workflows: + run-tests: + jobs: + - test From ab5ea878032cd9359ecb602391e35e739258bf5b Mon Sep 17 00:00:00 2001 From: alexcjohnson Date: Mon, 13 Feb 2023 18:09:27 -0500 Subject: [PATCH 4/4] downgrade to node 16 on CI node 18 gives SSL error in webpack: error:0308010C:digital envelope routines::unsupported --- .circleci/config.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index 6aa29ba7..40be081f 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -6,7 +6,7 @@ orbs: jobs: test: docker: - - image: cimg/python:3.9-node + - image: cimg/python:3.9.9-node auth: username: dashautomation password: $DASH_PAT_DOCKERHUB